diff --git a/.clang-format b/.clang-format index ef523006d7f..c433c9809ef 100644 --- a/.clang-format +++ b/.clang-format @@ -23,19 +23,19 @@ AlignEscapedNewlines: DontAlign AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true -AllowShortBlocksOnASingleLine: false +AllowShortBlocksOnASingleLine: Never AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: Inline AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false -AlwaysBreakTemplateDeclarations: true +AlwaysBreakTemplateDeclarations: Yes BinPackArguments: false BinPackParameters: false BraceWrapping: AfterClass: true - AfterControlStatement: false + AfterControlStatement: Never AfterEnum: false AfterFunction: true AfterNamespace: false @@ -100,7 +100,7 @@ PenaltyExcessCharacter: 50 PenaltyReturnTypeOnItsOwnLine: 300 PointerAlignment: Right ReflowComments: false -SortIncludes: true +SortIncludes: CaseSensitive SortUsingDeclarations: true SpaceAfterCStyleCast: true SpaceAfterTemplateKeyword: false @@ -113,6 +113,6 @@ SpacesInContainerLiterals: false SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false -Standard: Cpp11 +Standard: c++11 TabWidth: 4 UseTab: Never diff --git a/.github/workflows/build_cmake.yml b/.github/workflows/build_cmake.yml index 17cb3ea370c..a1d4c93fa00 100644 --- a/.github/workflows/build_cmake.yml +++ b/.github/workflows/build_cmake.yml @@ -7,9 +7,9 @@ on: - 'doc/**' env: - QT_VERSION: 6.5.0 + QT_VERSION: 6.5.1 MACOS_DEPLOYMENT_TARGET: 10.15 - CLANG_VERSION: 16.0.0 + CLANG_VERSION: 16.0.2 ELFUTILS_VERSION: 0.175 CMAKE_VERSION: 3.21.1 NINJA_VERSION: 1.10.2 @@ -577,7 +577,11 @@ jobs: endif() if ("${{ runner.os }}" STREQUAL "macOS") - set(ENV{CMAKE_OSX_ARCHITECTURES} "x86_64;arm64") + if (${{github.ref}} MATCHES "tags/v(.*)") + set(ENV{CMAKE_OSX_ARCHITECTURES} "x86_64;arm64") + else() + set(ENV{CMAKE_OSX_ARCHITECTURES} "x86_64") + endif() endif() execute_process( diff --git a/CMakeLists.txt b/CMakeLists.txt index 09dc721201a..55bf2bfca85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,11 +68,24 @@ if(MSVC) add_compile_options(/wd4573) endif() -find_package(Qt5 +find_package(Qt6 ${IDE_QT_VERSION_MIN} COMPONENTS Concurrent Core Gui Network PrintSupport Qml Sql Widgets Xml Core5Compat ${QT_TEST_COMPONENT} REQUIRED ) +# hack for Qbs which still supports Qt5 and Qt6 +if (TARGET Qt6::Core5CompatPrivate) + if (CMAKE_VERSION VERSION_LESS 3.18) + set_property(TARGET Qt6::Core5CompatPrivate PROPERTY IMPORTED_GLOBAL TRUE) + endif() + add_library(Qt6Core5CompatPrivate ALIAS Qt6::Core5CompatPrivate) +endif() +if (TARGET Qt6::Core5Compat) + if (CMAKE_VERSION VERSION_LESS 3.18) + set_property(TARGET Qt6::Core5Compat PROPERTY IMPORTED_GLOBAL TRUE) + endif() + add_library(Qt6Core5Compat ALIAS Qt6::Core5Compat) +endif() # Common intermediate directory for QML modules which are defined via qt_add_qml_module() set(QT_QML_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/qml_modules") @@ -85,8 +98,8 @@ if (MSVC AND QT_FEATURE_static_runtime) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() -find_package(Qt5 COMPONENTS LinguistTools QUIET) -find_package(Qt5 COMPONENTS Quick QuickWidgets Designer DesignerComponents Help SerialPort Svg Tools QUIET) +find_package(Qt6 OPTIONAL_COMPONENTS Quick QuickWidgets Designer DesignerComponentsPrivate + Help SerialPort Svg Tools LinguistTools QUIET) find_package(Threads) find_package(Clang QUIET) diff --git a/README.md b/README.md index 9c2128010d7..151be8c678c 100644 --- a/README.md +++ b/README.md @@ -747,3 +747,188 @@ SQLite (https://www.sqlite.org) is in the Public Domain. public domain worldwide. This software is distributed without any warranty. http://creativecommons.org/publicdomain/zero/1.0/ + +### WinPty + + Implementation of a pseudo terminal for Windows. + + https://github.com/rprichard/winpty + + The MIT License (MIT) + + Copyright (c) 2011-2016 Ryan Prichard + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to + deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. + + +### ptyqt + + Pty-Qt is small library for access to console applications by pseudo-terminal interface on Mac, + Linux and Windows. On Mac and Linux it uses standard PseudoTerminal API and on Windows it uses + WinPty(prefer) or ConPty. + + https://github.com/kafeg/ptyqt + + MIT License + + Copyright (c) 2019 Vitaly Petrov, v31337@gmail.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +### libvterm + + An abstract C99 library which implements a VT220 or xterm-like terminal emulator. + It doesn't use any particular graphics toolkit or output system, instead it invokes callback + function pointers that its embedding program should provide it to draw on its behalf. + It avoids calling malloc() during normal running state, allowing it to be used in embedded kernel + situations. + + https://www.leonerd.org.uk/code/libvterm/ + + The MIT License + + Copyright (c) 2008 Paul Evans + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +### terminal/shellintegrations + + The Terminal plugin uses scripts to integrate with the shell. The scripts are + located in the Qt Creator source tree in src/plugins/terminal/shellintegrations. + + https://github.com/microsoft/vscode/tree/main/src/vs/workbench/contrib/terminal/browser/media + + MIT License + + Copyright (c) 2015 - present Microsoft Corporation + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +### terminal/shellintegrations/clink + + The Terminal plugin uses a lua script to integrate with the cmd shell when using clink. + + https://github.com/chrisant996/clink-gizmos + + MIT License + + Copyright (c) 2023 Chris Antos + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +### cmake + + The CMake project manager uses the CMake lexer code for parsing CMake files + + https://gitlab.kitware.com/cmake/cmake.git + + CMake - Cross Platform Makefile Generator + Copyright 2000-2023 Kitware, Inc. and Contributors + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Kitware, Inc. nor the names of Contributors + may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/cmake/FindQt5.cmake b/cmake/FindQt5.cmake deleted file mode 100644 index fc16ec17237..00000000000 --- a/cmake/FindQt5.cmake +++ /dev/null @@ -1,103 +0,0 @@ -#.rst: -# FindQt5 -# ------- -# -# Qt5 wrapper around Qt6 CMake code. -# - -unset(__arguments) -if (Qt5_FIND_QUIETLY) - list(APPEND __arguments QUIET) -endif() -if (Qt5_FIND_REQUIRED) - list(APPEND __arguments REQUIRED) -endif() - -if (Qt5_FIND_COMPONENTS) - # for some reason QUIET doesn't really work when passed to the arguments list - if (Qt5_FIND_QUIETLY) - list(APPEND __arguments OPTIONAL_COMPONENTS) - else() - list(APPEND __arguments COMPONENTS) - endif() -endif() - -find_package(Qt6 ${Qt5_FIND_VERSION} CONFIG COMPONENTS Core QUIET) -if (NOT Qt6_FOUND) - # remove Core5Compat from components to find in Qt5, but add a dummy target, - # which unfortunately cannot start with "Qt6::" - # also remove Tools, where some tools have moved in Qt6, e.g. from Help - list(REMOVE_ITEM Qt5_FIND_COMPONENTS Core5Compat) - list(REMOVE_ITEM Qt5_FIND_COMPONENTS Tools) - find_package(Qt5 ${Qt5_FIND_VERSION} CONFIG ${__arguments} ${Qt5_FIND_COMPONENTS}) - if (NOT TARGET Qt6Core5Compat) - add_library(Qt6Core5Compat INTERFACE) - endif() - - # Remove Qt6 from the not found packages in Qt5 mode - get_property(not_found_packages GLOBAL PROPERTY "PACKAGES_NOT_FOUND") - if(not_found_packages) - list(REMOVE_ITEM not_found_packages Qt6) - set_property(GLOBAL PROPERTY "PACKAGES_NOT_FOUND" "${not_found_packages}") - endif() - return() -else() - # since Qt 6.2 some components are renamed to *Private - foreach(possible_private_libs DesignerComponents QmlDebug) - list(FIND Qt5_FIND_COMPONENTS ${possible_private_libs} dcIndex) - if(dcIndex GREATER_EQUAL 0) - find_package(Qt6${possible_private_libs}Private CONFIG QUIET) - if(TARGET Qt6::${possible_private_libs}Private) - set_property(TARGET Qt6::${possible_private_libs}Private PROPERTY IMPORTED_GLOBAL TRUE) - add_library(Qt5::${possible_private_libs} ALIAS Qt6::${possible_private_libs}Private) - list(REMOVE_AT Qt5_FIND_COMPONENTS ${dcIndex}) - endif() - endif() - endforeach() - find_package(Qt6 CONFIG ${__arguments} ${Qt5_FIND_COMPONENTS}) -endif() - -set(__additional_imported_components ATSPI2_nolink) # Work around QTBUG-97023 -foreach(comp IN LISTS Qt5_FIND_COMPONENTS __additional_imported_components) - if(TARGET Qt6::${comp}) - if (NOT TARGET Qt5::${comp}) - if (NOT QT_FEATURE_static) - set_property(TARGET Qt6::${comp} PROPERTY IMPORTED_GLOBAL TRUE) - endif() - add_library(Qt5::${comp} ALIAS Qt6::${comp}) - endif() - if (TARGET Qt6::${comp}Private AND NOT TARGET Qt5::${comp}Private) - if (NOT QT_FEATURE_static) - set_property(TARGET Qt6::${comp}Private PROPERTY IMPORTED_GLOBAL TRUE) - endif() - add_library(Qt5::${comp}Private ALIAS Qt6::${comp}Private) - endif() - endif() -endforeach() - -# alias Qt6::Core5Compat to Qt6Core5Compat to make consistent with Qt5 path -if (TARGET Qt6::Core5Compat AND NOT TARGET Qt6Core5Compat) - add_library(Qt6Core5Compat ALIAS Qt6::Core5Compat) -endif() - -set(Qt5_FOUND ${Qt6_FOUND}) -set(Qt5_VERSION ${Qt6_VERSION}) - -foreach(tool qmake lrelease lupdate moc rcc qhelpgenerator) - if (TARGET Qt6::${tool} AND NOT TARGET Qt5::${tool}) - add_executable(Qt5::${tool} IMPORTED GLOBAL) - get_target_property(imported_location Qt6::${tool} IMPORTED_LOCATION) - # handle separate tools for each configuration - if (NOT imported_location) - get_target_property(imported_location Qt6::${tool} IMPORTED_LOCATION_RELEASE) - endif() - set_target_properties(Qt5::${tool} PROPERTIES IMPORTED_LOCATION "${imported_location}") - endif() -endforeach() - -if (NOT DEFINED qt5_wrap_cpp) - function(qt5_wrap_cpp outfiles) - qt6_wrap_cpp(${outfiles} ${ARGN}) - set(${outfiles} ${${outfiles}} PARENT_SCOPE) - endfunction() -endif() diff --git a/cmake/QtCreatorAPI.cmake b/cmake/QtCreatorAPI.cmake index 53404cad781..effbb454047 100644 --- a/cmake/QtCreatorAPI.cmake +++ b/cmake/QtCreatorAPI.cmake @@ -188,7 +188,7 @@ function(add_qtc_library name) set(TEST_DEFINES WITH_TESTS SRCDIR="${CMAKE_CURRENT_SOURCE_DIR}") endif() - if(_arg_STATIC AND UNIX) + if((_arg_STATIC OR _arg_OBJECT) AND UNIX) # not added by Qt if reduce_relocations is turned off for it set_target_properties(${name} PROPERTIES POSITION_INDEPENDENT_CODE ON) endif() @@ -628,6 +628,7 @@ function(add_qtc_executable name) update_cached_list(__QTC_EXECUTABLES "${name}") + condition_info(_extra_text _arg_CONDITION) if (NOT _arg_CONDITION) set(_arg_CONDITION ON) endif() @@ -648,6 +649,9 @@ function(add_qtc_executable name) else() set(_executable_enabled OFF) endif() + if (NOT _arg_INTERNAL_ONLY) + add_feature_info("Executable ${name}" _executable_enabled "${_extra_text}") + endif() if (NOT _executable_enabled) return() endif() diff --git a/cmake/QtCreatorAPIInternal.cmake b/cmake/QtCreatorAPIInternal.cmake index 224ae4b2b14..ecb6d17f360 100644 --- a/cmake/QtCreatorAPIInternal.cmake +++ b/cmake/QtCreatorAPIInternal.cmake @@ -182,7 +182,7 @@ endfunction() function(qtc_add_link_flags_no_undefined target) # needs CheckLinkerFlags - if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.18) + if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.18 AND NOT MSVC) set(no_undefined_flag "-Wl,--no-undefined") check_linker_flag(CXX ${no_undefined_flag} QTC_LINKER_SUPPORTS_NO_UNDEFINED) if (NOT QTC_LINKER_SUPPORTS_NO_UNDEFINED) @@ -224,7 +224,7 @@ function(set_explicit_moc target_name file) set(file_dependencies DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${target_name}.json") endif() set_property(SOURCE "${file}" PROPERTY SKIP_AUTOMOC ON) - qt5_wrap_cpp(file_moc "${file}" ${file_dependencies}) + qt_wrap_cpp(file_moc "${file}" ${file_dependencies}) target_sources(${target_name} PRIVATE "${file_moc}") endfunction() @@ -413,6 +413,7 @@ function(enable_pch target) CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN ON CXX_EXTENSIONS OFF + POSITION_INDEPENDENT_CODE ON ) target_link_libraries(${pch_target} PRIVATE ${pch_dependency}) endif() diff --git a/cmake/QtCreatorDocumentation.cmake b/cmake/QtCreatorDocumentation.cmake index bc18f798289..8fa0c58d93e 100644 --- a/cmake/QtCreatorDocumentation.cmake +++ b/cmake/QtCreatorDocumentation.cmake @@ -10,7 +10,7 @@ add_feature_info("Build online documentation" WITH_ONLINE_DOCS "") # Used for QT_INSTALL_DOCS function(qt5_query_qmake) if (NOT TARGET Qt::qmake) - message(FATAL_ERROR "Qmake was not found. Add find_package(Qt5 COMPONENTS Core) to CMake to enable.") + message(FATAL_ERROR "Qmake was not found. Add find_package(Qt6 COMPONENTS Core) to CMake to enable.") endif() # dummy check for if we already queried qmake if (QT_INSTALL_BINS) @@ -142,7 +142,7 @@ function(_setup_qhelpgenerator_targets _qdocconf_file _html_outputdir) endif() if (NOT TARGET Qt::qhelpgenerator) - message(WARNING "qhelpgenerator missing: No QCH documentation targets were generated. Add find_package(Qt5 COMPONENTS Help) to CMake to enable.") + message(WARNING "qhelpgenerator missing: No QCH documentation targets were generated. Add find_package(Qt6 COMPONENTS Help) to CMake to enable.") return() endif() diff --git a/cmake/QtCreatorIDEBranding.cmake b/cmake/QtCreatorIDEBranding.cmake index 0cafd6591fa..0d9e4b81860 100644 --- a/cmake/QtCreatorIDEBranding.cmake +++ b/cmake/QtCreatorIDEBranding.cmake @@ -1,6 +1,6 @@ -set(IDE_VERSION "10.0.1") # The IDE version. -set(IDE_VERSION_COMPAT "10.0.0") # The IDE Compatibility version. -set(IDE_VERSION_DISPLAY "10.0.1") # The IDE display version. +set(IDE_VERSION "10.0.82") # The IDE version. +set(IDE_VERSION_COMPAT "10.0.82") # The IDE Compatibility version. +set(IDE_VERSION_DISPLAY "11.0.0-beta1") # The IDE display version. set(IDE_COPYRIGHT_YEAR "2023") # The IDE current copyright year. set(IDE_SETTINGSVARIANT "QtProject") # The IDE settings variation. diff --git a/cmake/QtCreatorTranslations.cmake b/cmake/QtCreatorTranslations.cmake index 7c795f65a09..4803d66be5e 100644 --- a/cmake/QtCreatorTranslations.cmake +++ b/cmake/QtCreatorTranslations.cmake @@ -30,7 +30,7 @@ function(_extract_ts_data_from_targets outprefix) set(_target_sources "") if(_source_files) - list(FILTER _source_files EXCLUDE REGEX ".*[.]json[.]in|.*[.]svg") + list(FILTER _source_files EXCLUDE REGEX ".*[.]json[.]in|.*[.]svg|.*[.]pro|.*[.]css") list(APPEND _target_sources ${_source_files}) endif() if(_extra_translations) @@ -130,7 +130,7 @@ endfunction() function(add_translation_targets file_prefix) if (NOT TARGET Qt::lrelease OR NOT TARGET Qt::lupdate) # No Qt translation tools were found: Skip this directory - message(WARNING "No Qt translation tools found, skipping translation targets. Add find_package(Qt5 COMPONENTS LinguistTools) to CMake to enable.") + message(WARNING "No Qt translation tools found, skipping translation targets. Add find_package(Qt6 COMPONENTS LinguistTools) to CMake to enable.") return() endif() diff --git a/coin/instructions/build.yaml b/coin/instructions/build.yaml index 75818065a40..abeb792c4d5 100644 --- a/coin/instructions/build.yaml +++ b/coin/instructions/build.yaml @@ -31,7 +31,7 @@ instructions: maxTimeBetweenOutput: 3600 userMessageOnFailure: "Failed to run build.py, check logs." - type: ChangeDirectory - directory: "{{.AgentWorkingDir}}/build/qtsdk/packaging-tools" + directory: "{{.AgentWorkingDir}}/build/tqtc-qtsdk/packaging_tools" - type: ExecuteCommand command: "python3 -m pipenv run python -u bld_sdktool.py --qt-url {{.Env.QTC_SDKTOOL_QT_BASE_URL}}{{.Env.QTC_SDKTOOL_QT_EXT}} --qt-build {{.AgentWorkingDir}}/build/sdktool/qt --src {{.AgentWorkingDir}}/qt-creator/qt-creator/src/tools/sdktool --build {{.AgentWorkingDir}}/build/sdktool/build --install {{.AgentWorkingDir}}/build/sdktool/install --make-command make" maxTimeInSeconds: 36000 @@ -65,7 +65,7 @@ instructions: maxTimeBetweenOutput: 3600 userMessageOnFailure: "Failed to run build.py, check logs." - type: ChangeDirectory - directory: "{{.AgentWorkingDir}}/build/qtsdk/packaging-tools" + directory: "{{.AgentWorkingDir}}/build/tqtc-qtsdk/packaging_tools" - type: EnvironmentVariable variableName: MACOSX_DEPLOYMENT_TARGET variableValue: "{{.Env.SDKTOOL_MACOSX_DEPLOYMENT_TARGET}}" @@ -117,7 +117,7 @@ instructions: maxTimeBetweenOutput: 3600 userMessageOnFailure: "Failed to run build.py, check logs." - type: ChangeDirectory - directory: "{{.AgentWorkingDir}}\\build\\qtsdk\\packaging-tools" + directory: "{{.AgentWorkingDir}}\\build\\tqtc-qtsdk\\packaging_tools" - type: ExecuteCommand command: "python -m pipenv run python -u bld_sdktool.py --qt-url {{.Env.QTC_SDKTOOL_QT_BASE_URL}}{{.Env.QTC_SDKTOOL_QT_EXT}} --qt-build {{.AgentWorkingDir}}\\build\\sdktool\\qt --src {{.AgentWorkingDir}}\\qt-creator\\qt-creator\\src\\tools\\sdktool --build {{.AgentWorkingDir}}\\build\\sdktool\\build --install {{.AgentWorkingDir}}\\build\\sdktool\\install --make-command nmake" maxTimeInSeconds: 36000 diff --git a/coin/instructions/common_environment.yaml b/coin/instructions/common_environment.yaml index b8be5f5c4a4..ad59636650c 100644 --- a/coin/instructions/common_environment.yaml +++ b/coin/instructions/common_environment.yaml @@ -7,10 +7,10 @@ instructions: variableValue: "RelWithDebInfo" - type: EnvironmentVariable variableName: LLVM_BASE_URL - variableValue: http://master.qt.io/development_releases/prebuilt/libclang/libclang-release_16.0.0-based + variableValue: http://master.qt.io/development_releases/prebuilt/libclang/libclang-release_16.0.2-based - type: EnvironmentVariable variableName: QTC_QT_BASE_URL - variableValue: "http://ci-files02-hki.intra.qt.io/packages/jenkins/archive/qt/6.5/6.5.0-released/Qt" + variableValue: "http://ci-files02-hki.intra.qt.io/packages/jenkins/archive/qt/6.5/6.5.1-released/Qt" - type: EnvironmentVariable variableName: QTC_QT_MODULES variableValue: "qt5compat qtbase qtdeclarative qtimageformats qtquick3d qtquickcontrols2 qtquicktimeline qtserialport qtshadertools qtsvg qttools qttranslations qtwebengine" diff --git a/coin/instructions/provision.yaml b/coin/instructions/provision.yaml index c11325d1375..75fab041366 100644 --- a/coin/instructions/provision.yaml +++ b/coin/instructions/provision.yaml @@ -16,13 +16,6 @@ instructions: directory: "{{.BuildDir}}" - type: ChangeDirectory directory: "{{.BuildDir}}" - - type: InstallSourceArchive - maxTimeInSeconds: 600 - maxTimeBetweenOutput: 600 - project: qtsdk/qtsdk - ref: master - directory: "build/qtsdk" - userMessageOnFailure: "Failed to install qtsdk, check logs" - type: InstallSourceArchive maxTimeInSeconds: 600 maxTimeBetweenOutput: 600 @@ -57,7 +50,7 @@ instructions: property: host.os not_equals_value: Windows - type: ChangeDirectory - directory: "{{.BuildDir}}/qtsdk/packaging-tools" + directory: "{{.BuildDir}}/tqtc-qtsdk/packaging_tools" - type: ExecuteCommand command: "python3 -m pipenv run python -u install_qt.py --qt-path {{.BuildDir}}/qt_install_dir --base-url {{.Env.QTC_QT_BASE_URL}} --base-url-postfix={{.Env.QTC_QT_POSTFIX}} --icu7z http://master.qt.io/development_releases/prebuilt/icu/prebuilt/56.1/icu-linux-g++-Rhel7.2-x64.7z {{.Env.QTC_QT_MODULES}}" executeCommandArgumentSplitingBehavior: SplitAfterVariableSubstitution diff --git a/dist/changelog/changes-11.0.0.md b/dist/changelog/changes-11.0.0.md new file mode 100644 index 00000000000..5600390c091 --- /dev/null +++ b/dist/changelog/changes-11.0.0.md @@ -0,0 +1,223 @@ +Qt Creator 11 +============= + +Qt Creator version 11 contains bug fixes and new features. + +The most important changes are listed in this document. For a complete list of +changes, see the Git log for the Qt Creator sources that you can check out from +the public Git repository. For example: + + git clone git://code.qt.io/qt-creator/qt-creator.git + git log --cherry-pick --pretty=oneline origin/10.0..v11.0.0 + +General +------- + +* Added a `Terminal` view (QTCREATORBUG-8511) + * Opt-out via `Preferences` > `Terminal` preferences + * Added support for + * different shells, colors, fonts, and multiple tabs + * opening file paths in Qt Creator with `Ctrl+click` (`Cmd+click` on + macOS) +* Added a more spacious "relaxed" toolbar style `Environment > Interface` +* Added a pin button to progress details instead of automatically resetting + their position (QTCREATORBUG-28829) +* Improved the selection and navigation in the `Issues` view + (QTCREATORBUG-26128, QTCREATORBUG-27006, QTCREATORBUG-27506) +* Locator + * Improved performance + * Added the creation of directories to the `Files in File System` filter + * Added device roots and browsing remote file systems to the + `Files in File System` filter + +Editing +------- + +* Improved the performance of the multi-cursor support +* Fixed the saving of hardlinked files (QTCREATORBUG-19651) +* Fixed an issue of copy and paste with multiple cursors (QTCREATORBUG-29117) + +### C++ + +* Improved the style of forward declarations in the outline (QTCREATORBUG-312) +* Added highlighting for typed string literals and user-defined literals + (QTCREATORBUG-28869) +* Added the option to create class members from assignments (QTCREATORBUG-1918) +* Fixed that locator showed both the declaration and the definition of symbols + (QTCREATORBUG-13894) +* Fixed the handling of C++20 keywords and concepts +* Built-in + * Fixed support for `if`-statements with initializer (QTCREATORBUG-29182) + +### Language Server Protocol + +* Added experimental support for GitHub Copilot + ([GitHub documentation](https://github.com/features/copilot)) +* Added missing actions for opening the `Call Hierarchy` (QTCREATORBUG-28839, + QTCREATORBUG-28842) + +### QML + +* Fixed the reformatting in the presence of JavaScript directives and function + return type annotations (QTCREATORBUG-29001, QTCREATORBUG-29046) +* Fixed that reformatting changed `of` to `in` (QTCREATORBUG-29123) +* Fixed the completion for Qt Quick Controls (QTCREATORBUG-28648) + +### Python + +* Added the option to create a virtual environment (`venv`) to the Python + interpreter selector and the wizard (PYSIDE-2152) + +### Markdown + +* Added a Markdown editor with preview (QTCREATORBUG-27883) +* Added a wizard for Markdown files (QTCREATORBUG-29056) + +Projects +-------- + +* Made it possible to add devices without going through the wizard +* Added support for moving files to a different directory when renaming + (QTCREATORBUG-15981) + +### CMake + +* Implemented adding files to the project (QTCREATORBUG-25922, + QTCREATORBUG-26006, QTCREATORBUG-27213, QTCREATORBUG-27538, + QTCREATORBUG-28493, QTCREATORBUG-28904, QTCREATORBUG-28985, + QTCREATORBUG-29006) +* Fixed issues with detecting a configured Qt version when importing a build + (QTCREATORBUG-29075) + +### Python + +* Added an option for the interpreter to the wizards + +### vcpkg + +* Added experimental support for `vcpkg` + ([vcpgk documentation](https://vcpkg.io/en/)) +* Added an option for the `vcpkg` installation location +* Added a search dialog for packages +* Added a wizard and an editor for `vcpkg.json` files + +Debugging +--------- + +* Improved the UI for enabling and disabling debuggers (QTCREATORBUG-28627) + +### C++ + +* Added an option for the default number of array elements to show + (`Preferences > Debugger > Locals & Expressions > Default array size`) +* CDB + * Added automatic source file mapping for Qt packages + * Fixed the variables view on remote Windows devices (QTCREATORBUG-29000) +* LLDB + * Fixed that long lines in the application output were broken into multiple + lines (QTCREATORBUG-29098) + +### Qt Quick + +* Improved the auto-detection if QML debugging is required (QTCREATORBUG-28627) +* Added an option for disabling static analyzer messages to + `Qt Quick > QML/JS Editing` (QTCREATORBUG-29095) + +Analyzer +-------- + +### Clang + +* Fixed that a `.clang-tidy` file in the project directory was not used by + default (QTCREATORBUG-28852) + +### Axivion + +* Added experimental support + +Version Control Systems +----------------------- + +### Git + +* Instant Blame + * Improved the performance (QTCREATORBUG-29151) + * Fixed that it did not show at the end of the document + +Platforms +--------- + +### Android + +* Fixed an issue with building library targets (QTCREATORBUG-26980) + +### Remote Linux + +* Removed the automatic sourcing of target-side shell profiles + +### Docker + +* Added support for `qmake` based projects (QTCREATORBUG-29140) +* Fixed issues after deleting the Docker image for a registered Docker device + (QTCREATORBUG-28880) + +### QNX + +* Added `slog2info` as a requirement for devices +* Fixed the support for remote working directories (QTCREATORBUG-28900) + +Credits for these changes go to: +-------------------------------- +Aleksei German +Alessandro Portale +Alexander Drozdov +Alexander Pershin +Ali Kianian +Alibek Omarov +Amr Essam +Andre Hartmann +André Pönitz +Artem Mukhin +Artem Sokolovskii +Assam Boudjelthia +Björn Schäpers +Brook Cronin +Burak Hancerli +Christian Kandeler +Christian Stenger +Cristian Adam +David Schulz +Eike Ziller +Esa Törmänen +Fabian Kosmale +Filippo Gentile +Friedemann Kleint +Henning Gruendl +Jaroslaw Kobus +Jussi Witick +Kai Köhne +Knud Dollereder +Knut Petter Svendsen +Leena Miettinen +Mahmoud Badri +Marco Bubke +Marcus Tillmanns +Martin Delille +Mats Honkamaa +Miikka Heikkinen +Mitch Curtis +Niels Weber +Orgad Shaneh +Pranta Dastider +Robert Löhning +Samuel Ghinet +Semih Yavuz +Tasuku Suzuki +Thiago Macieira +Thomas Hartmann +Tim Jenssen +Tim Jenßen +Ulf Hermann +Vikas Pachdha +Yasser Grimes +Yixue Wang diff --git a/doc/qtcreator/config/style/qt5-sidebar.html b/doc/qtcreator/config/style/qt5-sidebar.html index e38e76483a8..48111154eb8 100644 --- a/doc/qtcreator/config/style/qt5-sidebar.html +++ b/doc/qtcreator/config/style/qt5-sidebar.html @@ -1,6 +1,6 @@
diff --git a/doc/qtcreator/images/creator_advanceduse.svg b/doc/qtcreator/images/creator_advanceduse.svg deleted file mode 100644 index 7a7b77186b4..00000000000 --- a/doc/qtcreator/images/creator_advanceduse.svg +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - diff --git a/doc/qtcreator/images/creator_buildingrunning.svg b/doc/qtcreator/images/creator_buildingrunning.svg deleted file mode 100644 index 3d4808cdb58..00000000000 --- a/doc/qtcreator/images/creator_buildingrunning.svg +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - 1 - 2 - 3 - - - - - - - - - - - - 1 - 2 - 3 - - - diff --git a/doc/qtcreator/images/creator_coding.svg b/doc/qtcreator/images/creator_coding.svg deleted file mode 100644 index 39149cdf397..00000000000 --- a/doc/qtcreator/images/creator_coding.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -// main.cpp -#include <QApplication> -#include <QTableView> -#include "mymodel.h" -int main(int argc, char *argv[]) -{ - QApplication a(argc, argv); - QTableView tableView; - MyModel myModel; -} - diff --git a/doc/qtcreator/images/creator_designinguserinterface.svg b/doc/qtcreator/images/creator_designinguserinterface.svg deleted file mode 100644 index b3aff292403..00000000000 --- a/doc/qtcreator/images/creator_designinguserinterface.svg +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - -1 -2 -3 - - - ON - - - - - - - - - - - - - - diff --git a/doc/qtcreator/images/creator_gettinghelp.svg b/doc/qtcreator/images/creator_gettinghelp.svg deleted file mode 100644 index 6663ed53eb9..00000000000 --- a/doc/qtcreator/images/creator_gettinghelp.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - -? - diff --git a/doc/qtcreator/images/creator_gettingstarted.svg b/doc/qtcreator/images/creator_gettingstarted.svg deleted file mode 100644 index 779ae13947f..00000000000 --- a/doc/qtcreator/images/creator_gettingstarted.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/doc/qtcreator/images/creator_managingprojects.svg b/doc/qtcreator/images/creator_managingprojects.svg deleted file mode 100644 index fb934268a09..00000000000 --- a/doc/qtcreator/images/creator_managingprojects.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - -C++ -qml - - - - - diff --git a/doc/qtcreator/images/creator_publishing.svg b/doc/qtcreator/images/creator_publishing.svg deleted file mode 100644 index 4954f8930c2..00000000000 --- a/doc/qtcreator/images/creator_publishing.svg +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - 2 - 3 - - - diff --git a/doc/qtcreator/images/creator_testing.svg b/doc/qtcreator/images/creator_testing.svg deleted file mode 100644 index 14e75f3489f..00000000000 --- a/doc/qtcreator/images/creator_testing.svg +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/doc/qtcreator/images/front-advanced.png b/doc/qtcreator/images/front-advanced.png index 87780aa3863..99422ed74f0 100644 Binary files a/doc/qtcreator/images/front-advanced.png and b/doc/qtcreator/images/front-advanced.png differ diff --git a/doc/qtcreator/images/front-coding.png b/doc/qtcreator/images/front-coding.png index edfc5509c52..b39a8c33db0 100644 Binary files a/doc/qtcreator/images/front-coding.png and b/doc/qtcreator/images/front-coding.png differ diff --git a/doc/qtcreator/images/front-gs.png b/doc/qtcreator/images/front-gs.png index 27706a8cc6a..b529411a7df 100644 Binary files a/doc/qtcreator/images/front-gs.png and b/doc/qtcreator/images/front-gs.png differ diff --git a/doc/qtcreator/images/front-help.png b/doc/qtcreator/images/front-help.png index d2f9d42fece..f9640f48634 100644 Binary files a/doc/qtcreator/images/front-help.png and b/doc/qtcreator/images/front-help.png differ diff --git a/doc/qtcreator/images/front-preview.png b/doc/qtcreator/images/front-preview.png index fe7aab39668..9cb894c183e 100644 Binary files a/doc/qtcreator/images/front-preview.png and b/doc/qtcreator/images/front-preview.png differ diff --git a/doc/qtcreator/images/front-projects.png b/doc/qtcreator/images/front-projects.png index 69414f48626..bd3412e8489 100644 Binary files a/doc/qtcreator/images/front-projects.png and b/doc/qtcreator/images/front-projects.png differ diff --git a/doc/qtcreator/images/front-publishing.png b/doc/qtcreator/images/front-publishing.png index d6bf6582150..9946aa36cd3 100644 Binary files a/doc/qtcreator/images/front-publishing.png and b/doc/qtcreator/images/front-publishing.png differ diff --git a/doc/qtcreator/images/front-testing.png b/doc/qtcreator/images/front-testing.png index 1bdbe4dc052..69b1fe45d56 100644 Binary files a/doc/qtcreator/images/front-testing.png and b/doc/qtcreator/images/front-testing.png differ diff --git a/doc/qtcreator/images/front-ui.png b/doc/qtcreator/images/front-ui.png index d413b52dbfd..ee966bffc11 100644 Binary files a/doc/qtcreator/images/front-ui.png and b/doc/qtcreator/images/front-ui.png differ diff --git a/doc/qtcreator/images/qtcreator-add-online-doc.png b/doc/qtcreator/images/qtcreator-add-online-doc.png deleted file mode 100644 index 761b3c74f87..00000000000 Binary files a/doc/qtcreator/images/qtcreator-add-online-doc.png and /dev/null differ diff --git a/doc/qtcreator/images/qtcreator-add-online-doc.webp b/doc/qtcreator/images/qtcreator-add-online-doc.webp new file mode 100644 index 00000000000..94872a519fd Binary files /dev/null and b/doc/qtcreator/images/qtcreator-add-online-doc.webp differ diff --git a/doc/qtcreator/images/qtcreator-add-resource-wizard4.png b/doc/qtcreator/images/qtcreator-add-resource-wizard4.png deleted file mode 100644 index ea985fc5ea0..00000000000 Binary files a/doc/qtcreator/images/qtcreator-add-resource-wizard4.png and /dev/null differ diff --git a/doc/qtcreator/images/qtcreator-build-settings-cmake-configure.webp b/doc/qtcreator/images/qtcreator-build-settings-cmake-configure.webp index 4b38b162f4c..0038dc47631 100644 Binary files a/doc/qtcreator/images/qtcreator-build-settings-cmake-configure.webp and b/doc/qtcreator/images/qtcreator-build-settings-cmake-configure.webp differ diff --git a/doc/qtcreator/images/qtcreator-locator-customize.png b/doc/qtcreator/images/qtcreator-locator-customize.png deleted file mode 100644 index 8b28fa80fe5..00000000000 Binary files a/doc/qtcreator/images/qtcreator-locator-customize.png and /dev/null differ diff --git a/doc/qtcreator/images/qtcreator-locator-customize.webp b/doc/qtcreator/images/qtcreator-locator-customize.webp new file mode 100644 index 00000000000..5a6d4e90d34 Binary files /dev/null and b/doc/qtcreator/images/qtcreator-locator-customize.webp differ diff --git a/doc/qtcreator/images/qtcreator-locator-example.png b/doc/qtcreator/images/qtcreator-locator-example.png deleted file mode 100644 index 8c23d94f721..00000000000 Binary files a/doc/qtcreator/images/qtcreator-locator-example.png and /dev/null differ diff --git a/doc/qtcreator/images/qtcreator-locator-example.webp b/doc/qtcreator/images/qtcreator-locator-example.webp new file mode 100644 index 00000000000..3de6d262eb1 Binary files /dev/null and b/doc/qtcreator/images/qtcreator-locator-example.webp differ diff --git a/doc/qtcreator/images/qtcreator-locator-open.png b/doc/qtcreator/images/qtcreator-locator-open.png deleted file mode 100644 index 186cc6504b9..00000000000 Binary files a/doc/qtcreator/images/qtcreator-locator-open.png and /dev/null differ diff --git a/doc/qtcreator/images/qtcreator-locator-open.webp b/doc/qtcreator/images/qtcreator-locator-open.webp new file mode 100644 index 00000000000..eb8005b35ef Binary files /dev/null and b/doc/qtcreator/images/qtcreator-locator-open.webp differ diff --git a/doc/qtcreator/images/qtcreator-locator.png b/doc/qtcreator/images/qtcreator-locator.png deleted file mode 100644 index 82fff209d83..00000000000 Binary files a/doc/qtcreator/images/qtcreator-locator.png and /dev/null differ diff --git a/doc/qtcreator/images/qtcreator-locator.webp b/doc/qtcreator/images/qtcreator-locator.webp new file mode 100644 index 00000000000..c97a1a7634e Binary files /dev/null and b/doc/qtcreator/images/qtcreator-locator.webp differ diff --git a/doc/qtcreator/images/qtcreator-markdown-editor.webp b/doc/qtcreator/images/qtcreator-markdown-editor.webp new file mode 100644 index 00000000000..0943d90fb3b Binary files /dev/null and b/doc/qtcreator/images/qtcreator-markdown-editor.webp differ diff --git a/doc/qtcreator/images/qtcreator-new-file.webp b/doc/qtcreator/images/qtcreator-new-file.webp new file mode 100644 index 00000000000..4429f37cd3c Binary files /dev/null and b/doc/qtcreator/images/qtcreator-new-file.webp differ diff --git a/doc/qtcreator/src/debugger/qtquick-debugging.qdoc b/doc/qtcreator/src/debugger/qtquick-debugging.qdoc index 833cbe69c47..c0246fc7b6f 100644 --- a/doc/qtcreator/src/debugger/qtquick-debugging.qdoc +++ b/doc/qtcreator/src/debugger/qtquick-debugging.qdoc @@ -38,22 +38,26 @@ The process of setting up debugging for Qt Quick projects depends on the \l{Creating Qt Quick Projects}{type of the project}: Qt Quick UI or Qt Quick Application, and the Qt version used. + + \section2 Debugging Qt Quick UI Projects \endif - To debug Qt Quick UI projects, select the \uicontrol {Enable QML} check box in the - \uicontrol {Debugger Settings} in \uicontrol Projects mode \uicontrol {Run Settings}. + To debug Qt Quick UI projects (.qmlproject), select the + \uicontrol {Enable QML} check box in \uicontrol {Debugger settings} + in \uicontrol Projects mode \uicontrol {Run Settings}. \if defined(qtcreator) + \section2 Debugging Qt Quick Applications + To debug Qt Quick Applications: \list 1 - \li If you use qmake as the build system, make sure that - debugging is enabled in the \uicontrol {Build Settings}, - \uicontrol {QML debugging and profiling} field, either - explicitly for the project or globally by default. + \li To create a build configuration that supports QML debugging, + select \uicontrol {Projects} > \uicontrol {Build} > + \uicontrol {QML debugging and profiling} > \uicontrol Enable. - \image qtcreator-projectpane.png "qmake general build settings pane" + \image qtcreator-build-settings-cmake-configure.webp {Build settings for a CMake project} \note Debugging requires opening a socket at a TCP port, which presents a security risk. Anyone on the Internet could connect @@ -61,9 +65,9 @@ functions. Therefore, you must make sure that the port is properly protected by a firewall. - \li In the \uicontrol {Run Settings}, \uicontrol {Debugger Settings} section, select - the \uicontrol {Enable QML} check box to enable - QML debugging. + \li In \uicontrol {Run Settings} > \uicontrol {Debugger settings}, select + the \uicontrol {Enable QML} check box to enable QML debugging for + running applications. \li Select \uicontrol Build > \uicontrol {Rebuild Project} to clean and rebuild the project. @@ -79,6 +83,43 @@ automatically installed during \QC and Qt installation. Do not delete them if you plan to debug QML applications. + \section2 Using Default Values + + You can enable or disable QML debugging globally in \uicontrol Edit > + \uicontrol Preferences > \uicontrol {Build & Run} > + \uicontrol {Default Build Properties}. + + \image qtcreator-build-settings-default.png "Default Build Properties tab in Build & Run Preferences" + + The value of the \uicontrol {QML debugging} field determines what happens + when creating new build configurations. The values \uicontrol Enable + and \uicontrol Disable explicitly set QML debugging for the new build + configuration to that value, regardless of what type of build + configuration you create. + + \uicontrol {Use Project Default} makes the values depend on the type of + build configuration: \uicontrol Debug, \uicontrol Profile, or + \uicontrol Release. When you use \l {CMake Build Configuration}{CMake} or + \l {qmake Build Configuration}{qmake} as a build system, debug and profile + build configurations have QML debugging enabled. However, release build + configurations do not include QML debugging because the debugging feature + makes applications vulnerable. + + The \uicontrol {Leave at Default} option in \uicontrol {Projects} > + \uicontrol {Build} > \uicontrol {QML debugging and profiling} is needed to keep existing, + already configured CMake build directories intact. Also, it + enables you to import an existing build into \QC without enforced changes, + so that you don't have to worry about a complete rebuild of the project, for + example. Even if you later change the configuration of the build outside of + \QC, it will be kept as you want it. + + There are some known issues in the interaction between the global setting + in \uicontrol Edit > \uicontrol Preferences > \uicontrol {Build & Run} > + \uicontrol {Default Build Properties} and the build configuration. + For example, for qmake the global setting only affects build configurations + that are automatically created when enabling a kit. Also, CMake ignores the + global setting. + \section1 Mixed C++/QML Debugging To debug both the C++ and QML parts of your application at the same time, @@ -87,7 +128,7 @@ \uicontrol{Run Settings}. \endif - \image qtquick-debugging-settings.png + \image qtquick-debugging-settings.png {Debugger settings section in Run Settings} \section1 Starting QML Debugging diff --git a/doc/qtcreator/src/editors/creator-coding.qdoc b/doc/qtcreator/src/editors/creator-coding.qdoc index 9a45600f791..6823bf1c8ff 100644 --- a/doc/qtcreator/src/editors/creator-coding.qdoc +++ b/doc/qtcreator/src/editors/creator-coding.qdoc @@ -68,6 +68,10 @@ \list + \li \l{Editing Markdown Files} + + Edit and preview markdown (.md) files. + \li \l{Using Language Servers} The language client offers code completion, highlighting of the diff --git a/doc/qtcreator/src/editors/creator-locator.qdoc b/doc/qtcreator/src/editors/creator-locator.qdoc index 232b5587274..e5a55f8d6a9 100644 --- a/doc/qtcreator/src/editors/creator-locator.qdoc +++ b/doc/qtcreator/src/editors/creator-locator.qdoc @@ -1,4 +1,4 @@ -// Copyright (C) 2021 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only /*! @@ -15,7 +15,9 @@ To open it as a centered popup, click \inlineimage icons/magnifier.png (\uicontrol Options) in it and select \uicontrol {Open as Centered Popup}. - \image qtcreator-locator.png "List of locator filters" + \image qtcreator-locator.webp "List of locator filters" + + \section1 Activating the Locator To activate the locator: @@ -32,43 +34,35 @@ \endlist - To open a QML file called \e HelloWorld.qml in the currently open project - using the locator: + \section1 Using Locator Filters - \list 1 + You can locate not only files, but any items defined by \e{locator filters}, + as well as trigger global actions and perform other tasks, such as build + projects or run external tools. - \li Activate the locator by pressing \key {Ctrl+K}. + The filters that are available depend on the file type. For more information + about what a particular locator filter does, see the tooltips that appear + when you hover over a filter in the locator. For longer descriptions of the + filters, select \uicontrol Configure to open the \uicontrol Locator + \l{Creating Locator Filters}{preferences}. - \li Start typing the filename. + To use a locator filter, type its prefix followed by \key Space. The prefix + is usually short, from one to three characters. Then type the search string + (for example, a filename or class name) or the command to execute. - \image qtcreator-locator-open.png "List of files found in the locator" + You can also double-click a locator filter in the filter list to use it. Use + the up and down arrow keys or the \key Ctrl+P and \key Ctrl+N + keyboard shortcuts to move up and down the list, and then press \key Enter + to use the selected filter. - \li Move to the filename in the list and press \key Enter. - - The file opens in the editor. - - \li To move to a line in the file, enter the line number in the locator. - - \endlist - - To move directly to a particular line and column in the document when you - open the document, append them to the file name in the locator, separated by - plus signs (+) or colons (:). For example, to open \e HelloWorld.qml to line - 41 and column 2, enter: \c {HelloWorld.qml:41:2}. - - If the path to a file is very long, it might not fit into the locator - window. To view the full path, press \key Alt when the filename is selected - or use the handle next to the locator window to increase the window width. - - It is also possible to enter only a part of a search string. As you type, + As you type a search string, the locator shows the occurrences of that string regardless of where in the name of an component it appears. Some locator filters, such as colon, \c m, and \c t, support \e fuzzy matching, which means that you can enter the uppercase letters to locate a symbol when using camel case or the letters after the underscore when using snake case. - To narrow down the search results, you can use the following wildcard - characters: + To narrow down the search results, use the following wildcard characters: \list @@ -78,117 +72,69 @@ \endlist - \section1 Using Locator Filters + \section2 Locating Files - The locator enables you to browse not only files, but any items defined by - \b{locator filters}. The filters that are available depend on the file type: + For example, to open a QML file called \e HelloWorld.qml in the currently + open project using the locator: - \list + \list 1 - \li Locating any open document (\c {o}) + \li Press \key {Ctrl+K} to activate the locator. - \li Locating files anywhere on your file system (\c {f}). You can use - environment variables in the \c {f} filter. For example, use - \c {f $ENVVAR} to expand the environment variable \c ENVVAR on Unix - systems and \c {f %ENVVAR%} to expand it on Windows systems. + \li Start typing the filename. - \li Locating files belonging to your project (\c {p}), such as source, - header, resource, and \c {.ui} files, or to any project (\c {a}) + \image qtcreator-locator-open.webp "List of files found in the locator" - \if defined(qtcreator) - \li Locating bookmarks (\c {b}). - For more information, see \l{Using Bookmarks}. + \li Use the arrow keys to move to the filename in the list and press + \key Enter. - \li Locating class (\c {c}), enum, function (\c {m}), and type alias - definitions in your project or anywhere referenced from your - project (\c {:}) - \endif + The file opens in the editor. - \li Locating QML methods (\c {m}) - - \li Locating symbols in the current document (\c {.}) - - \li Locating a specific line and column in the document displayed in - your editor (\c {l :}) - - \li Opening help topics, including Qt documentation (\c {?}) - - \li Performing web searches (\c {r}) - - \if defined(qtcreator) - \li Running text editing macros that you record and save (\c {rm}). For - more information, see \l{Using Text Editing Macros} - \endif - - \li Executing JavaScript (\c {=}), especially useful for calculations. - For more information, see \l{Executing JavaScript}. - - \li Executing shell commands (\c {!}) - - \li Executing version control system commands - \if defined(qtcreator) - (\c {bzr}, \c {cvs}, \c {git}, \c {hg}, or \c {svn}). - For more information, see \l{Using Version Control Systems}. - \else - (\c {git}). For more information, see \l{Using Git}. - \endif - - \li Triggering actions (\c {t}) - - \li Searching for issues from the \l{https://bugreports.qt.io/} - {Qt Project Bug Tracker} (\c bug). - - \li Searching for applications, documents, and other files by using - platform-specific external tools or commands (\c md). The following - tools are used by default, but you can configure the locator to - use any other command: - - \list - \li On \macOS: using Spotlight - \li On Windows: using \l{https://www.voidtools.com/downloads/} - {Everything} - \li On Linux: using the \c Locate command - \endlist - - \if defined(qtcreator) - \li Running external tools (\c x) - \li Using CMake to build the project for the current run configuration - (\c {cm}). For more information, see \l {Setting up CMake}. - \li Opening the CMakeLists.txt file for the current run configuration in - the editor (\c {cmo}). This is the same build target as when you - select \uicontrol Build > \uicontrol {Build for Run Configuration}. - \li Running a particular run configuration (\c {rr} \e {}) - \li Switching to a particular run configuration (\c {sr} \e {}) - \endif + \li To move to a line in the file, enter the line number in the locator. \endlist - To use a specific locator filter, type the assigned prefix followed by - \key Space. The prefix is usually a single character. Then type the search - string (typically, a filename or class name) or the command to execute. - - You can also double-click a locator filter in the filter list to use it. You - can use the up and down arrow keys or the \key Ctrl+P and \key Ctrl+N - keyboard shortcuts to move up and down the list, and then press \key Enter - to use the selected filter. + If the path to a file is very long, it might not fit into the locator + window. To view the full path, press \key Alt when the filename is selected + or use the handle next to the locator window to increase the window width. \if defined(qtcreator) - For example, to locate symbols matching QDataStream: + If the locator does not find some files, see \l{Specifying Project Contents} + for how to make them known to the locator. + \endif + + \section2 Locating Lines and Columns + + To move directly to a particular line and column in the document when you + open the document, append the line and column number to the file name in + the locator, separated by plus signs (+) or colons (:). + + For example, to open \e HelloWorld.qml to line + 41 and column 2, enter: + + \code + HelloWorld.qml:41:2 + \endcode + + \if defined(qtcreator) + \section2 Locating Symbols + + For example, to locate symbols matching \c {QGuiApplication}: \list 1 \li Activate the locator. \li Enter a colon (:) followed by a space and the upper case letters in - the symbol name (QDataStream): + the symbol name (here, \c {QGuiApplication}): \code - : qds + : qga \endcode The locator lists the results. - \image qtcreator-locator-example.png "List of files matching the locator filter" + \image qtcreator-locator-example.webp "List of files matching the locator filter" \endlist @@ -197,16 +143,21 @@ such as \c {Utils::*View}. \endif - For example, to create a new file and open it in the editor, type \c f + \section2 Creating Files from Locator + + To create a new file and open it in the editor, type \c f followed by \key Space, followed by path and file name, and then press \key Enter. - You can use the filter that triggers menu commands to open sessions. Enter + You can use the filter that triggers menu commands to open + \l{Managing Sessions}{sessions}. Enter \c {t yoursess} or \c {t sess yoursess} to trigger \uicontrol File > \uicontrol Sessions > \e yoursessionname. - By default, the following filters are enabled and you do not need to use - their prefixes explicitly: + \section2 Default Filters + + By default, you can use the following preset locator filters without a + prefix: \list @@ -218,16 +169,11 @@ \endlist - \if defined(qtcreator) - If locator does not find some files, see \l{Specifying Project Contents} - for how to make them known to the locator. - \endif + \section1 Changing Locator Filters - \section1 Configuring Locator Filters - - If the default filters do not match your use case, you can check whether you - can change them. For all filters, you can change the filter prefix and - restrict the search to items that match the filter. + You can change the preset locator filters to match your use case. For + example, you can change the filter prefix and restrict the search to + items that match the filter. To configure a locator filter: @@ -266,11 +212,11 @@ \li Select \uicontrol Edit > \uicontrol Preferences > \uicontrol Environment > \uicontrol Locator > - \uicontrol {Web Search (prefix: r)} > \uicontrol Edit. + \uicontrol {Web Search} > \uicontrol Edit. \li Select \uicontrol Add to add a new entry to the list. - \image qtcreator-add-online-doc.png "List of URLs in Filter Configuration dialog" + \image qtcreator-add-online-doc.webp "List of URLs in Filter Configuration dialog" \li Double-click the new entry to specify a URL and a search command. For example, \c {http://www.google.com/search?q=%1}. @@ -281,7 +227,7 @@ \section1 Creating Locator Filters - You can create custom locator filters for finding in a directory structure + You can create custom locator filters for searching in a directory structure or on the web. To quickly access files not directly mentioned in your project, you can @@ -295,7 +241,7 @@ \li In the locator, select \uicontrol Options > \uicontrol Configure to open the \uicontrol Locator preferences. - \image qtcreator-locator-customize.png "Locator preferences" + \image qtcreator-locator-customize.webp "Locator preferences" \li Select \uicontrol Add > \uicontrol {Files in Directories} to add a directory filter or \uicontrol {URL Template} to add a URL @@ -310,7 +256,7 @@ \li In the \uicontrol {File pattern} field, specify file patterns to restrict the search to files that match the pattern. - Use a comma separated list. For example, to search for all + Separate the patterns with commas. For example, to search for all \c {.qml} and \c {.ui.qml} files, enter \c{*.qml,*.ui.qml} \li In the \uicontrol {Exclusion pattern} field, specify file @@ -329,9 +275,9 @@ \section1 Configuring Locator Cache The locator searches the files matching your file pattern in the directories - you have selected and caches that information. The cache for all default - filters is updated as you write your code. By default, \QC updates the - filters created by you once an hour. + you have selected and caches that information. \QC updates the cache for all + preset filters as you write code. By default, \QC updates your custom + filters once an hour. To update the cached information manually, select \uicontrol Options > \uicontrol Refresh in the locator. @@ -349,8 +295,7 @@ \section1 Executing JavaScript - The locator has a JavaScript interpreter that you can use to - perform calculations. + The locator has a JavaScript interpreter for performing calculations. Beside simple mathematical operations, like ((1 + 2) * 3), the following built-in functions exist: diff --git a/doc/qtcreator/src/editors/creator-only/creator-fakevim.qdoc b/doc/qtcreator/src/editors/creator-only/creator-fakevim.qdoc index e6f910b22a1..dba4740fd69 100644 --- a/doc/qtcreator/src/editors/creator-only/creator-fakevim.qdoc +++ b/doc/qtcreator/src/editors/creator-only/creator-fakevim.qdoc @@ -4,7 +4,7 @@ /*! \previouspage creator-editor-options-text.html \page creator-editor-fakevim.html - \nextpage creator-language-servers.html + \nextpage creator-markdown-editor.html \title Using FakeVim Mode diff --git a/doc/qtcreator/src/editors/creator-only/creator-language-server.qdoc b/doc/qtcreator/src/editors/creator-only/creator-language-server.qdoc index 248569aa78d..348845a7bd2 100644 --- a/doc/qtcreator/src/editors/creator-only/creator-language-server.qdoc +++ b/doc/qtcreator/src/editors/creator-only/creator-language-server.qdoc @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only /*! - \previouspage creator-editor-fakevim.html + \previouspage creator-markdown-editor.html \page creator-language-servers.html \nextpage creator-mime-types.html diff --git a/doc/qtcreator/src/editors/creator-only/creator-markdown-editor.qdoc b/doc/qtcreator/src/editors/creator-only/creator-markdown-editor.qdoc new file mode 100644 index 00000000000..9a3d2444e61 --- /dev/null +++ b/doc/qtcreator/src/editors/creator-only/creator-markdown-editor.qdoc @@ -0,0 +1,22 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \previouspage creator-editor-fakevim.html + \page creator-markdown-editor.html + \nextpage creator-language-servers.html + + \title Editing Markdown Files + + Open \l{https://www.markdownguide.org/basic-syntax/}{markdown} (.md) files + or select \uicontrol File > \uicontrol {New File} > \uicontrol {General} > + \uicontrol {Markdown File} to create a new file. + + \image qtcreator-markdown-editor.webp {Markdown file in Preview and Editor views} + + When you edit the file in the editor, you can see the changes in the preview. + + To hide and show the views, select \uicontrol {Show Preview} and + \uicontrol {Show Editor}. To swap the places of the views, select + \uicontrol {Swap Views}. +*/ diff --git a/doc/qtcreator/src/howto/creator-keyboard-shortcuts.qdoc b/doc/qtcreator/src/howto/creator-keyboard-shortcuts.qdoc index 75036e6cd55..ef5d0cca4c6 100644 --- a/doc/qtcreator/src/howto/creator-keyboard-shortcuts.qdoc +++ b/doc/qtcreator/src/howto/creator-keyboard-shortcuts.qdoc @@ -438,7 +438,7 @@ Works with namespaces, classes, functions, variables, include statements, and macros. Also, opens URLs in the default browser - and Qt resource files (.qrc) in the \l{Creating Resource Files} + and Qt resource files (.qrc) in the \l{Resource Files} {resource editor} \li F2 \row @@ -729,7 +729,7 @@ \li \li Alt+S, Alt+D \row - \li Diff project + \li Diff project or repository \li \li \li Alt+G, Alt+Shift+D @@ -753,7 +753,7 @@ \li Alt+P, Alt+F \li \row - \li Log project + \li Log repository \li \li \li Alt+G, Alt+K diff --git a/doc/qtcreator/src/howto/creator-only/creator-how-tos.qdoc b/doc/qtcreator/src/howto/creator-only/creator-how-tos.qdoc index da32fd41263..e380c1e6e84 100644 --- a/doc/qtcreator/src/howto/creator-only/creator-how-tos.qdoc +++ b/doc/qtcreator/src/howto/creator-only/creator-how-tos.qdoc @@ -171,16 +171,17 @@ Use the \uicontrol Locator to browse through projects, files, classes, functions, documentation, and file systems. + + \image qtcreator-locator-open.webp "List of found files" + To quickly access files not directly mentioned in your project, you can create your own locator filters. That way you can locate files in a directory structure you have defined. - \image qtcreator-locator.png "List of locator filters" - To create locator filters, select \uicontrol Edit > \uicontrol Preferences > \uicontrol Environment > \uicontrol Locator > \uicontrol Add. - \image qtcreator-locator-customize.png "Locator preferences" + \image qtcreator-locator-customize.webp "Locator preferences" For more information, see \l{Creating Locator Filters}. @@ -190,6 +191,8 @@ You can now do basic calculations, with options to copy the results to the clipboard by navigating through the entries and pressing \key {Enter}. + \image qtcreator-locator.webp "List of locator filters" + For more information, see \l{Executing JavaScript}. \section1 Jump to a function in QML code diff --git a/doc/qtcreator/src/linux-mobile/linuxdev.qdoc b/doc/qtcreator/src/linux-mobile/linuxdev.qdoc index b874866f60c..028619c7fad 100644 --- a/doc/qtcreator/src/linux-mobile/linuxdev.qdoc +++ b/doc/qtcreator/src/linux-mobile/linuxdev.qdoc @@ -111,7 +111,7 @@ \li Select \uicontrol Edit > \uicontrol Preferences > \uicontrol Kits > \uicontrol Add to add a kit for building for the device. Select the Qt version, compiler, and device that you added above, and select - \uicontrol {Remote Linux Device} in \uicontrol {Device type}. + \uicontrol {Remote Linux Device} in \uicontrol {Run device type}. To build on the remote device, select \uicontrol {Remote Linux Device} also in \uicontrol {Build device}. diff --git a/doc/qtcreator/src/overview/creator-acknowledgements.qdoc b/doc/qtcreator/src/overview/creator-acknowledgements.qdoc index 2ef5125bab2..7f607374759 100644 --- a/doc/qtcreator/src/overview/creator-acknowledgements.qdoc +++ b/doc/qtcreator/src/overview/creator-acknowledgements.qdoc @@ -1002,5 +1002,116 @@ https://creativecommons.org/publicdomain/zero/1.0/ + \li \b WinPty + + Implementation of a pseudo terminal for Windows. + + The sources can be found in: + \list + \li \l https://github.com/rprichard/winpty + \endlist + + Distributed under the MIT license. + + \include license-mit.qdocinc + + \li \b ptyqt + + Pty-Qt is small library for access to console applications by a + pseudo-terminal interface on \macos, Linux and Windows. On \macos and + Linux it uses standard PseudoTerminal API and on Windows it uses + WinPty or ConPty. + + \list + \li \l https://github.com/kafeg/ptyqt + \endlist + + Distributed under the MIT license. + + \include license-mit.qdocinc + + \li \b libvterm + + An abstract C99 library, which implements a VT220 or xterm-like terminal + emulator. It doesn't use any particular graphics toolkit or output + system. Instead it invokes callback function pointers that its embedding + program should provide to draw on its behalf. It avoids calling malloc() + during normal running state, allowing it to be used in embedded kernels. + + \list + \li \l https://www.leonerd.org.uk/code/libvterm/ + \endlist + + Distributed under the MIT license. + + \include license-mit.qdocinc + + \li \b terminal/shellintegrations + + The Terminal plugin uses scripts to integrate with the shell. The scripts are + located in the Qt Creator source tree in src/plugins/terminal/shellintegrations. + + \list + \li \l https://github.com/microsoft/vscode/tree/main/src/vs/workbench/contrib/terminal/browser/media + \endlist + + Distributed under the MIT license. + + \include license-mit.qdocinc + + \li \b terminal/shellintegrations/clink + + The Terminal plugin uses a lua script to integrate with the cmd shell when using clink. + + \list + \li \l https://github.com/chrisant996/clink-gizmos + \endlist + + Distributed under the MIT license. + + \include license-mit.qdocinc + + \li \b cmake + + The CMake project manager uses the CMake lexer code for parsing CMake files. + + \list + \li \l https://gitlab.kitware.com/cmake/cmake.git + \endlist + + CMake - Cross Platform Makefile Generator + Copyright 2000-2023 Kitware, Inc. and Contributors + All rights reserved. + + \badcode + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Kitware, Inc. nor the names of Contributors + may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + \endcode + + \endlist */ diff --git a/doc/qtcreator/src/projects/creator-only/creator-files-creating.qdoc b/doc/qtcreator/src/projects/creator-only/creator-files-creating.qdoc index 646fdce899b..b0f188c0834 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-files-creating.qdoc +++ b/doc/qtcreator/src/projects/creator-only/creator-files-creating.qdoc @@ -1,4 +1,4 @@ -// Copyright (C) 2022 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only /*! @@ -8,122 +8,33 @@ \title Creating Files - You can use wizard templates to add individual files to your - \l{Creating Projects}{projects}. - The following table lists the wizard templates for creating files. + \image qtcreator-new-file.webp {New File wizard} - \table - \header - \li Category - \li Wizard Template - \li Purpose - \row - \li {1,3} C/C++ - \li C++ Class - \li C++ header and source file for a new class that you can add to - a C++ project. - \row - \li C/C++ Source File - \li C++ source file that you can add to a C++ project. - \row - \li C/C++ Header File - \li C++ header file that you can add to a C++ project. - \row - \li {1,3} Modeling - \li State Chart - \li State Chart XML (SCXML) file that has boilerplate - code for state machines. You can use the classes in the - \l {Qt SCXML} module to embed state machines created from - the files in Qt applications. - \row - \li Model - \li Universal Modeling Language (UML) style model with a structured - diagram. However, the model editor uses a variant of UML and - has only a subset of properties for specifying the - appearance of model elements. For more information, see - \l {Modeling}. - \row - \li Scratch Model - \li Scratch model using a temporary file. - \row - \li {1,7} Qt - \li Qt Item Model - \li Source and header files that you can use to create classes - derived from QAbstractItemModel, QAbstractTableModel, or - QAbstractListModel. - \row - \li \QD Form Class - \li \QD form and a matching class for implementing a UI based - on Qt widgets. - \row - \li \QD Form - \li \QD form for Qt widget based projects. This is useful - if you already have an existing class for the UI logic. - \row - \li Qt Resource File - \li Resource file for storing binary files in the application - executable. - \row - \li QML File (Qt Quick 2) - \li QML file that imports Qt Quick 2.0 for use in Qt Quick projects. - \row - \li Qt Quick UI File - \li \l{UI Files}{UI file} (\e .ui.qml) and the corresponding - implementation file (\e .qml) for use in Qt Quick projects. - \row - \li JS File - \li JavaScript file that you can use to write the application logic - in Qt Quick projects. - \row - \li {1,4} GLSL - \li Fragment Shader (OpenGL/ES 2.0) - \li Fragment shader that generates the final pixel colors for - triangles, points, and lines rendered with OpenGL. You can use - it in both Qt Quick projects and Qt widget based projects. - \row - \li Vertex Shader (OpenGL/ES 2.0) - \li Vertex shader that transforms the positions, normals, and - texture coordinates of triangles, points, and lines rendered - with OpenGL. You can use it in both Qt Quick projects and Qt - widget based projects. - \row - \li Fragment Shader (Desktop OpenGL) - \li Fragment shader for use in both Qt Quick projects and Qt - widget based projects. - \row - \li Vertex Shader (Desktop OpenGL) - \li Vertex shader for use in both Qt Quick projects and Qt - widget based projects. - \row - \li {1,2} General - \li Empty File - \li Empty file that you can save with any filename extension. - \row - \li Scratch Buffer - \li Scratch buffer that uses temporary files. You can - create this type of files for temporarily storing information - that you do not intend to save - \row - \li Java - \li Java File - \li Java class files that you can use to create Java classes. - \row - \li {1,2} Python - \li Python Class - \li Python class file. - \row - \li Python File - \li Python script file using UTF-8 encoding. - \row - \li {1,2} Nim (experimental) - \li Nim Script File - \li Empty Nim script file using UTF-8 encoding. - \row - \li Nim File - \li Empty Nim source file using UTF-8 encoding. - \endtable + Use wizard templates to add individual files to your \l{Creating Projects} + {projects}: - \section1 Creating C++ Classes + \list + \li \uicontrol {C/C++}: header and source files for new classes. + \li \uicontrol {Modeling}: State Chart XML (SCXML) files, + Universal Modeling Language (UML) style \l {Modeling}{models}, + and scratch models that use a temporary file. + \li \uicontrol {Qt}: source and header files for item, table, + or list models, \QD forms and a matching classes for Qt Widgets + projects, Qt resource files, as well as QML and JavaScript files + for Qt Quick projects. + \li \uicontrol {GLSL}: fragment and vertex shaders. + \li \uicontrol {General}: markdown files, empty files that you can save + with any filename extension, and scratch buffers that use temporary + files. + \li \uicontrol {Java}: class files. + \li \uicontrol {Python}: class and script files for Python projects. + \li \uicontrol {Nim} (experimental): empty Nim source and script files. + \endlist + + The \uicontrol {New File} dialog shows detailed information about each file + wizard template. + + \section1 C++ Classes The \uicontrol {C++ Class Wizard} allows you to create a C++ header and source file for a new class that you can add to a C++ project. Specify the class @@ -149,7 +60,7 @@ You can create your own project and class wizards. For more information, see \l{Adding New Custom Wizards}. - \section1 Creating Resource Files + \section1 Resource Files \QC supports the \l{The Qt Resource System}{Qt Resource System}, which is a platform-independent mechanism for storing files in the application's @@ -187,7 +98,7 @@ The above functions are also available in the context menu in the \uicontrol Projects view. - \section1 Creating OpenGL Fragment and Vertex Shaders + \section1 OpenGL Fragment and Vertex Shaders Qt supports integration with OpenGL implementations on all platforms, which allows you to display hardware accelerated 3D graphics diff --git a/doc/qtcreator/src/projects/creator-only/creator-projects-build-systems.qdocinc b/doc/qtcreator/src/projects/creator-only/creator-projects-build-systems.qdocinc index b3300865dd6..05a340d0df2 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-projects-build-systems.qdocinc +++ b/doc/qtcreator/src/projects/creator-only/creator-projects-build-systems.qdocinc @@ -1,4 +1,4 @@ -// Copyright (C) 2020 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only // ********************************************************************** @@ -12,29 +12,33 @@ \section1 Selecting the Build System - You can use several build systems to build your projects. + You can use several build systems to build your projects: - \l{qmake Manual}{qmake} is a cross-platform system for build automation + \list + + \li \l{qmake Manual}{qmake} is a cross-platform system for build automation that helps simplify the build process for development projects across different platforms. qmake automates the generation of build configurations so that you need only a few lines of information to create each configuration. Qt installers install and configure qmake. To use one of the other supported build systems, you need to set it up. - \l {Build with CMake}{CMake} is an alternative to qmake for automating the + \li \l {Build with CMake}{CMake} is an alternative to qmake for automating the generation of build configurations. For more information, see \l {Setting Up CMake}. - \l {https://mesonbuild.com/}{Meson} Meson is a fast and user-friendly + \li \l {https://mesonbuild.com/}{Meson} is a fast and user-friendly open-source build system that aims to minimize the time developers spend writing or debugging build definitions and waiting for the build system to start compiling code. For more information, see \l {Setting Up Meson}. - \l{Qbs Manual}{Qbs} is an all-in-one build tool that generates a build graph + \li \l{Qbs Manual}{Qbs} is an all-in-one build tool that generates a build graph from a high-level project description (like qmake or CMake do) and executes the commands in the low-level build graph (like make does). For more information, see \l{Setting Up Qbs}. + \endlist + To export a project to some other build system, such as Microsoft Visual Studio, select \uicontrol Build > \uicontrol {Run Generator}, and select a generator in the list. \QC generates the build files, such as .vcxproj, diff --git a/doc/qtcreator/src/projects/creator-only/creator-projects-creating.qdoc b/doc/qtcreator/src/projects/creator-only/creator-projects-creating.qdoc index 8dd448e410b..6ea2b20b036 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-projects-creating.qdoc +++ b/doc/qtcreator/src/projects/creator-only/creator-projects-creating.qdoc @@ -1,4 +1,4 @@ -// Copyright (C) 2022 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only // ********************************************************************** @@ -49,7 +49,7 @@ The installers create \l{glossary-buildandrun-kit}{kits} and specify build and run settings for the installed device types. However, you might need to install and configure some additional software on the devices to be able to - connect to them from the development PC. + \l{Connecting Devices}{connect} to them from the development PC. \include creator-projects-build-systems.qdocinc build systems @@ -71,146 +71,49 @@ \section1 Selecting Project Type - The following table lists the wizard templates for creating projects. + The following table lists the types of wizard templates that you can use + for creating projects. The \uicontrol {New Project} dialog shows detailed + information about each project wizard template. \table \header \li Category - \li Wizard Template \li Purpose \row - \li Application (\QMCU) - \li MCU Support Application - \li Creates an application that uses a subset of Qt QML and - Qt Quick Controls types (as supported by \QMCU) that - you can deploy, run, and debug on MCU boards. For more - information, see \l {Connecting MCUs}. + \li Application + \li Use many UI technologies (Qt Widgets and Qt Quick) and + programming languages (C++, QML, and Python) to create + applications for different purposes that you can run on + many target platforms (desktop, mobile, and embedded). \row - \li {1,3} Application (Qt) - \li Qt Widgets Application - \li Uses \QD forms to design a Qt widget based user interface for - the desktop and C++ to implement the application logic. + \li Library or plugin + \li Create a shared or static C++ library, a C++ plugin for Qt Quick + application extensions, or a \QC plugin. \row - \li Qt Console Application - \li Uses a single main.cpp file. + \li Other project + \li Create custom \l{Developing Widget Based Applications}{\QD} + widgets or widget collections, + \l{Qt Quick UI Projects}{Qt Quick UI projects}, + \l {Creating Tests}{auto-test projects}, + \l{Adding Subprojects to Projects}{subprojects}, + empty qmake projects, or qmake projects for testing + code snippets. \row - \li Qt Quick Application - \li Creates a Qt Quick application project that can have both - QML and C++ code. You can build the application and deploy it - to desktop, embedded, and mobile target platforms. + \li Non-Qt project + \li Create plain C or C++ applications or \l {Setting Up Nimble} + {Nim or Nimble} applications (experimental) + \row + \li Imported project + \li Import projects from a supported \l{Using Version Control Systems} + {version control system}, such as Bazaar, CVS, Git, Mercurial, or + Subversion. - You can select an option to create a project that you can open - in \QDS, which has a visual editor for Qt Quick UIs. - \row - \li {1,4} Application (Qt for Python) - \li Empty Application - \li Creates a \l{https://doc.qt.io/qtforpython/index.html} - {Qt for Python} application that has only the main - code for a QApplication. - \row - \li Empty Window - \li Creates a Qt for Python application that has an empty - window. - \row - \li Window UI - \li Creates a Qt for Python application that has an empty - window with a widget-based UI. Preferred approach that requires - you to generate a Python file from the .ui file, to import - it directly into your application. - \row - \li Qt Quick Application - Empty - \li Creates a Python project that has an empty Qt Quick - Application. - \row - \li {1,3} Library - \li C++ Library - \li A shared or static C++ library based on qmake. - \row - \li Qt Quick 2 Extension Plugin - \li Creates a C++ plugin that makes it possible to offer extensions - that the QQmlEngine class can load dynamically into Qt Quick - applications. - \row - \li \QC Plugin - \li Creates a \QC plugin. - \row - \li {1,6} Other Project - \li Qt Custom Designer Widget - \li Creates a custom \QD widget or widget collection. - \row - \li Qt Quick UI Prototype - \li Creates a \l{Creating Qt Quick UI Projects}{Qt Quick UI project} - with a single QML file that has the main view. You can - preview Qt Quick UI projects in the - \l{Validating with Target Hardware}{QML Scene preview tool}. - You do not need to build them because they do not have any - C++ code. - - Use this template only if you are prototyping. You cannot create - a full application by using this template. - - You cannot deploy Qt Quick UI projects to embedded or mobile - target platforms. For those platforms, create a Qt Quick - application instead. - \row - \li Auto Test Project - \li Creates a project with boilerplate code for a Qt or Google - test. For more information, see \l {Creating Tests}. - \row - \li Subdirs Project - \li Creates a subproject that enables you to structure your qmake - projects as a tree hierarchy. - \row - \li Empty qmake Project - \li Creates an empty qmake project that uses qmake as the build - system but does not use any default classes. - \row - \li Code Snippet - \li Creates a qmake project from a code snippet. When working on - bug reports that have a code snippet, you can place the code - snippet into a project to compile and check it. - \row - \li {1,4} Non-Qt Project - \li Plain C Application - \li Creates a plain C application that uses qmake, Qbs, or CMake - but does not use the Qt library. - \row - \li Plain C++ Application - \li Creates a plain C++ application that uses qmake, Qbs, or CMake - but does not use the Qt library. - \row - \li Nim Application (experimental) - \li Creates a Nim application that uses Nimble, but does not use the - Qt library. For more information, see \l {Setting Up Nimble}. - \row - \li Nimble Application (experimental) - \li Creates a Nimble application that uses Nimble, but does not use - the Qt library. For more information, see - \l {Setting Up Nimble}. - \row - \li {1,3} Import Project - \li Project from version control - \li Imports a project from a supported version control system, such - as Bazaar, CVS, Git, Mercurial, or Subversion. For more - information about how \QC integrates version control systems, - see \l{Using Version Control Systems}. - \row - \li Import as qmake or CMake Project (Limited Functionality) - \li Imports an existing project that does not use any of the - supported build systems: qmake, Qbs, CMake, or Autotools. The - template creates a project file, which enables you to use - \QC as a code editor and as a launcher for debugging and - analysis tools. However, if you want to build the project, - you might need to edit the generated project file. - \row - \li Import Existing Project - \li Imports an existing project that does not use any of the - supported build systems: qmake, Qbs, CMake, or Autotools. - This enables you to use \QC as a code editor. + You can also import existing projects that do not use any of the + supported build systems to use \QC as a code editor and as a + launcher for debugging and analysis tools. \row \li Squish - \li Squish Test Suite - \li Creates a new \l {Using Squish}{Squish test suite}. + \li Create new \l {Using Squish}{Squish test suites}. \endtable diff --git a/doc/qtcreator/src/projects/creator-only/creator-projects-targets.qdoc b/doc/qtcreator/src/projects/creator-only/creator-projects-targets.qdoc index 04b639f1dd3..08a6d988207 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-projects-targets.qdoc +++ b/doc/qtcreator/src/projects/creator-only/creator-projects-targets.qdoc @@ -97,8 +97,8 @@ used for the \c CurrentKit:FileSystemName variable, which determines the name of the shadow build directory, for example. \row - \li \uicontrol{Device type} - \li Type of the device. + \li \uicontrol{Run device type} + \li Type of the run device. Double-click the icon next to the field to select the image that is displayed in the kit selector for this kit. You can use any @@ -107,7 +107,7 @@ logo as an icon allows you to easily see, which compiler is used to build the project for the selected kit. \row - \li \uicontrol Device + \li \uicontrol {Run device} \li The device to run applications on. \row \li \uicontrol {Build device} diff --git a/doc/qtcreator/src/python/creator-python-project.qdocinc b/doc/qtcreator/src/python/creator-python-project.qdocinc index 436e712f0b1..0ef6bb2d3fd 100644 --- a/doc/qtcreator/src/python/creator-python-project.qdocinc +++ b/doc/qtcreator/src/python/creator-python-project.qdocinc @@ -140,7 +140,7 @@ For more information about the \uicontrol {Qt for Python - Qt Quick Application - Empty} wizard, see - \l {Creating Qt Quick Based Python Applications}. + \l {Qt Quick Based Python Applications}. For examples of creating Qt for Python applications, see \l {https://doc.qt.io/qtforpython/tutorials/index.html} @@ -151,7 +151,7 @@ //! [python qml project wizards] - \section1 Creating Qt Quick Based Python Applications + \section1 Qt Quick Based Python Applications The \uicontrol {Qt for Python - Qt Quick Application - Empty} wizard enables you to create a Python project that has a main QML file. Specify the diff --git a/doc/qtcreator/src/qtcreator-toc.qdoc b/doc/qtcreator/src/qtcreator-toc.qdoc index e2299cd05c0..e18f60632b6 100644 --- a/doc/qtcreator/src/qtcreator-toc.qdoc +++ b/doc/qtcreator/src/qtcreator-toc.qdoc @@ -1,4 +1,4 @@ -// Copyright (C) 2021 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only @@ -7,6 +7,12 @@ \title All Topics + \note The following list has links to all the individual topics (HTML files) + in the \QC Manual. Use your browser's page search to find a link to a + particular topic in the list. For a more extensive search, use the + \uicontrol Search function in the \l{https://doc.qt.io/qtcreator/} + {Qt documentation} portal or in the \l {Using the Help Mode}{Help} mode. + \list \li \l{Getting Started} \list @@ -122,6 +128,7 @@ \li \l{Specifying Text Editor Settings} \li \l{Using FakeVim Mode} \endlist + \li \l{Editing Markdown Files} \li \l{Using Language Servers} \li \l{Editing MIME Types} \li \l{Modeling} diff --git a/doc/qtcreator/src/qtcreator.qdoc b/doc/qtcreator/src/qtcreator.qdoc index 50cbe7024e6..280a65ab19b 100644 --- a/doc/qtcreator/src/qtcreator.qdoc +++ b/doc/qtcreator/src/qtcreator.qdoc @@ -1,4 +1,4 @@ -// Copyright (C) 2021 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only // ********************************************************************** @@ -29,9 +29,11 @@ appropriate \l{http://qt.io/licensing/}{Qt license}. For more information, see \l{Commercial Features}. + + \table \row - \li {4,1} \b {\l{All Topics}} + \li {4,1} \b {\l{All Topics}{Click Here for a List of All Topics}} \row \li \inlineimage front-gs.png \li \inlineimage front-projects.png diff --git a/doc/qtcreator/src/qtquick/creator-only/qt-design-viewer.qdoc b/doc/qtcreator/src/qtquick/creator-only/qt-design-viewer.qdoc index 3cb1c61e1b7..dd1178e5e39 100644 --- a/doc/qtcreator/src/qtquick/creator-only/qt-design-viewer.qdoc +++ b/doc/qtcreator/src/qtquick/creator-only/qt-design-viewer.qdoc @@ -19,7 +19,7 @@ However, the actual performance of the application once started is indistinguishable from the same application running on the desktop. - You can run \l{Creating Qt Quick UI Projects}{Qt Quick UI projects}, which + You can run \l{Qt Quick UI Projects}{Qt Quick UI projects}, which have a .qmlproject file that define the main QML file and the import paths. Compress the project folder into a ZIP file that you upload to \QDV. diff --git a/doc/qtcreator/src/qtquick/creator-only/qtquick-creating.qdoc b/doc/qtcreator/src/qtquick/creator-only/qtquick-creating.qdoc index 2c8a4e8e8e1..55458349642 100644 --- a/doc/qtcreator/src/qtquick/creator-only/qtquick-creating.qdoc +++ b/doc/qtcreator/src/qtquick/creator-only/qtquick-creating.qdoc @@ -1,4 +1,4 @@ -// Copyright (C) 2021 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only // ********************************************************************** @@ -14,58 +14,28 @@ \title Creating Qt Quick Projects - The following table lists the wizard templates for creating a new - Qt Quick project from scratch. + Use the following wizard templates to create new Qt Quick projects: - \table - \header - \li Category - \li Wizard Template - \li Purpose - \row - \li Application (Qt) - \li Qt Quick Application - \li Creates a Qt Quick application project that can have both - QML and C++ code. You can build the application and deploy it - to desktop, embedded, and mobile target platforms. + \list + \li \uicontrol {Application (Qt)} > \uicontrol {Qt Quick Application} + \li \uicontrol {Application (Qt for Python)} > + \uicontrol {Qt for Python - Qt Quick Application} + \li \uicontrol {Other Project} > \uicontrol {Qt Quick UI Prototype} + \li \uicontrol {Library} > \uicontrol {Qt Quick 2 Extension Plugin} + \endlist - You can select an option to create a project that you can open - in \QDS. - \row - \li Application (Qt for Python) - \li Qt for Python - Qt Quick Application - \li Creates a Python project that has an empty Qt Quick - Application. - \row - \li Other Project - \li Qt Quick UI Prototype - \li Creates a Qt Quick UI project with a single QML file that - has the main view. You can preview Qt Quick UI projects - in the QML Scene preview tool. You do not need to build them - because they do not have any C++ code. - - This project type is compatible with \QDS. However, use this - template only if you are prototyping. You cannot create - a full application by using this template. - - You cannot deploy Qt Quick UI projects to embedded or - mobile target platforms. For those platforms, create a Qt Quick - application instead. - \row - \li Library - \li Qt Quick 2 Extension Plugin - \li Creates C++ plugins that make it possible to offer extensions - that can be loaded dynamically into Qt Quick applications. - \endtable + The \uicontrol {New Project} dialog shows detailed information about each + project wizard template. \note The SDK for a particular target platform might install additional templates for that platform. For example, the QNX templates are installed as part of the QNX SDK. \QC creates the necessary boilerplate files. Some of the files are - specific to a particular target platform. + specific to a particular target platform. You can use wizard templates + also to \l{Creating Files}{add files} to the projects. - \section1 Creating Qt Quick Applications + \section1 Qt Quick Applications \list 1 @@ -142,7 +112,7 @@ \include creator-python-project.qdocinc python qml project wizards - \section1 Creating Qt Quick UI Projects + \section1 Qt Quick UI Projects Qt Quick UI Prototype projects are useful for testing or prototyping user interfaces, diff --git a/doc/qtcreator/src/qtquick/creator-only/qtquick-tutorial-create-empty-project.qdocinc b/doc/qtcreator/src/qtquick/creator-only/qtquick-tutorial-create-empty-project.qdocinc index f01fae3bd8a..d0a5b94cc07 100644 --- a/doc/qtcreator/src/qtquick/creator-only/qtquick-tutorial-create-empty-project.qdocinc +++ b/doc/qtcreator/src/qtquick/creator-only/qtquick-tutorial-create-empty-project.qdocinc @@ -69,7 +69,7 @@ \endlist For more information about the settings that you skipped and the other - wizard templates available, see \l{Creating Qt Quick Applications}. + wizard templates available, see \l{Qt Quick Applications}. //! [qtquick empty application] */ diff --git a/doc/qtcreator/src/qtquick/qtquick-live-preview.qdoc b/doc/qtcreator/src/qtquick/qtquick-live-preview.qdoc index 95cd83edea5..01aaa637c4c 100644 --- a/doc/qtcreator/src/qtquick/qtquick-live-preview.qdoc +++ b/doc/qtcreator/src/qtquick/qtquick-live-preview.qdoc @@ -22,7 +22,7 @@ In addition, you can use \QDV to run \if defined(qtcreator) - \l{Creating Qt Quick UI Projects}{Qt Quick UI projects} + \l{Qt Quick UI Projects}{Qt Quick UI projects} \else applications \endif diff --git a/doc/qtcreator/src/user-interface/creator-views.qdoc b/doc/qtcreator/src/user-interface/creator-views.qdoc index 856dfd15eef..e676ed18907 100644 --- a/doc/qtcreator/src/user-interface/creator-views.qdoc +++ b/doc/qtcreator/src/user-interface/creator-views.qdoc @@ -88,6 +88,7 @@ (\uicontrol {Synchronize with Editor}). \endlist + \if defined(qtcreator) \section1 Viewing the Class Hierarchy The \uicontrol {Class View} shows the class hierarchy of the currently @@ -144,4 +145,5 @@ To keep the view synchronized with the file currently open in the editor, select \inlineimage icons/linkicon.png (\uicontrol {Synchronize with Editor}). + \endif */ diff --git a/doc/qtcreator/src/webassembly/creator-webassembly.qdoc b/doc/qtcreator/src/webassembly/creator-webassembly.qdoc index d01c038ce5b..c05f84832e5 100644 --- a/doc/qtcreator/src/webassembly/creator-webassembly.qdoc +++ b/doc/qtcreator/src/webassembly/creator-webassembly.qdoc @@ -95,7 +95,7 @@ \li Select \uicontrol Edit > \uicontrol Preferences > \uicontrol Kits > \uicontrol Add. \li In the \uicontrol Name field, specify a name for the kit. - \li In the \uicontrol {Device type} field, select + \li In the \uicontrol {Run device type} field, select \uicontrol {WebAssembly Runtime}. The value of the \uicontrol Device field is automatically set to \uicontrol {Web Browser}. diff --git a/doc/qtcreator/src/widgets/qtdesigner-app-tutorial.qdoc b/doc/qtcreator/src/widgets/qtdesigner-app-tutorial.qdoc index 1298e81e9f8..ad38116dffb 100644 --- a/doc/qtcreator/src/widgets/qtdesigner-app-tutorial.qdoc +++ b/doc/qtcreator/src/widgets/qtdesigner-app-tutorial.qdoc @@ -1,4 +1,4 @@ -// Copyright (C) 2022 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only // ********************************************************************** @@ -294,12 +294,6 @@ and select \uicontrol{Finish} or \uicontrol Done to open the file in the code editor. - \li In the \uicontrol Copy to Clipboard dialog, select \uicontrol Yes to - copy the path to the resource file to the clipboard for adding it - to the CMakeLists.txt file. - - \image qtcreator-add-resource-wizard4.png "Copy to Clipboard dialog" - \li Select \uicontrol Add > \uicontrol {Add Prefix}. \li In the \uicontrol{Prefix} field, replace the default prefix with a slash diff --git a/doc/qtcreatordev/config/qtcreator-developer.qdocconf b/doc/qtcreatordev/config/qtcreator-developer.qdocconf index 417b1ffd426..0ce340b2b78 100644 --- a/doc/qtcreatordev/config/qtcreator-developer.qdocconf +++ b/doc/qtcreatordev/config/qtcreator-developer.qdocconf @@ -16,6 +16,7 @@ headerdirs = . \ ../src \ ../../../src/libs/aggregation \ ../../../src/libs/extensionsystem \ + ../../../src/libs/solutions/tasking \ ../../../src/libs/utils \ ../../../src/plugins/coreplugin @@ -23,11 +24,14 @@ sourcedirs = . \ ../src \ ../../../src/libs/aggregation \ ../../../src/libs/extensionsystem \ + ../../../src/libs/solutions/tasking \ ../../../src/libs/utils \ ../../../src/plugins/coreplugin -excludedirs = ../../../src/libs/aggregation/examples +excludedirs = ../../../src/libs/aggregation/examples \ + ../../../src/libs/utils/mimetypes2 + # -- Uncomment following option to generate complete documentation # instead of public API documentation only. diff --git a/doc/qtcreatordev/src/common-extension-tasks.qdoc b/doc/qtcreatordev/src/common-extension-tasks.qdoc index fca444f5e02..c2a63680b94 100644 --- a/doc/qtcreatordev/src/common-extension-tasks.qdoc +++ b/doc/qtcreatordev/src/common-extension-tasks.qdoc @@ -52,7 +52,7 @@ \li Add support for a new version control system. \li Version control systems integrated in \QC are Bazaar, CVS, Git, Mecurial, Perforce, and Subversion. - \li \l{Core::IVersionControl} + \li \c{Core::IVersionControl} \row \li Add a view to the navigation sidebar. @@ -94,7 +94,7 @@ \li For a text typed in by the user you provide a list of things to show in the popup. When the user selects an entry you are requested to do whatever you want. - \li \l{Core::ILocatorFilter}, \l{Core::BaseFileFilter} + \li \l{Core::ILocatorFilter} \row \li Show a progress indicator for a concurrently running task. diff --git a/doc/qtcreatordev/src/plugin-lifecycle.qdoc b/doc/qtcreatordev/src/plugin-lifecycle.qdoc index ffd40635ce5..ec741c89a88 100644 --- a/doc/qtcreatordev/src/plugin-lifecycle.qdoc +++ b/doc/qtcreatordev/src/plugin-lifecycle.qdoc @@ -28,8 +28,7 @@ tracks the state of the plugin. You can get the \l{ExtensionSystem::PluginSpec} instances via the plugin manager's \l{ExtensionSystem::PluginManager::plugins()}{plugins()} - function, or, after a plugin is loaded, through the plugin's - \l{ExtensionSystem::IPlugin::pluginSpec()}{pluginSpec()} function. + function. \li Sets the plugins to \c Read state. diff --git a/doc/qtcreatordev/src/plugin-tests.qdoc b/doc/qtcreatordev/src/plugin-tests.qdoc index 78d9c6c4b6e..d3a4dd35a56 100644 --- a/doc/qtcreatordev/src/plugin-tests.qdoc +++ b/doc/qtcreatordev/src/plugin-tests.qdoc @@ -41,8 +41,8 @@ failure. To add plugin tests, add a QObject based class with private slots for your - tests, and register it with \l{ExtensionSystem::IPlugin::addTest()} in your - plugin's \l{ExtensionSystem::IPlugin::initialized()} method. Guard all test + tests, and register it with \c{ExtensionSystem::IPlugin::addTest()} in your + plugin's \l{ExtensionSystem::IPlugin::initialize()} method. Guard all test related code with a check for \c{WITH_TESTS}, to avoid shipping a binary release of your plugin with test functions. diff --git a/doc/qtcreatordev/src/qtcreator-dev.qdoc b/doc/qtcreatordev/src/qtcreator-dev.qdoc index ace8f860374..5e11b9e7b44 100644 --- a/doc/qtcreatordev/src/qtcreator-dev.qdoc +++ b/doc/qtcreatordev/src/qtcreator-dev.qdoc @@ -202,7 +202,7 @@ One way to handle that would be to let the tool create an output file, which is then opened within \QC. You provide an editor (probably read-only) for handling this file. For lists of issues, consider creating task list files - which are shown in \l Issues. + which are shown in \uicontrol Issues. \list \li \l{https://doc.qt.io/qtcreator/creator-task-lists.html} diff --git a/doc/qtcreatordev/src/qtcreator-module.qdoc b/doc/qtcreatordev/src/qtcreator-module.qdoc index 6a6b1a62f10..18a56103353 100644 --- a/doc/qtcreatordev/src/qtcreator-module.qdoc +++ b/doc/qtcreatordev/src/qtcreator-module.qdoc @@ -34,6 +34,11 @@ for plugins and basic mechanisms for plugin interaction like an object pool. + \row + \li \l{Tasking} + \li A solution containing a TaskTree and other classes for writing + declarative trees of asynchronous task flows. + \row \li \l{Utils} \li Useful classes that are reused in a lot of places in Qt Creator code. diff --git a/doc/qtcreatordev/src/qtcreator-ui-text.qdoc b/doc/qtcreatordev/src/qtcreator-ui-text.qdoc index 84688a4b857..42d5d9466c2 100644 --- a/doc/qtcreatordev/src/qtcreator-ui-text.qdoc +++ b/doc/qtcreatordev/src/qtcreator-ui-text.qdoc @@ -91,14 +91,14 @@ case, use book style capitalization and do not add a period after the tool tip. - \image qtcreator-tooltip.png "Tooltip" + \image qtcreator-tooltip.png {Tooltip} Tooltips can also contain full sentences. Try to make them as short and concise as possible, while still making them grammatically correct. Use sentence style capitalization and punctuation as you would for any sentence. - \image qtcreator-tooltip-long.png "Sentence as a tooltip" + \image qtcreator-tooltip-long.png {Sentence as a tooltip} \section3 Writing Tooltips in Design Mode @@ -333,7 +333,7 @@ \li Opens when users right-click on the screen. Contents depend on the context. - \image qtcreator-context-menu.png "Context menu" + \image qtcreator-context-menu.png {Context menu} \li You can add menu items that are relevant in a particular context. Follow the conventions for naming menu items. @@ -343,7 +343,7 @@ \li User interface element that usually contains a number of choices or allows the user to give input to the application. Opens when users select a menu item or button. - \image qtcreator-dialog.png "Dialog" + \image qtcreator-dialog.png {Dialog} \li Use the menu item or button name as the dialog name. You can also combine the menu item or button name and the name of the object that is managed in the dialog. For example, the \uicontrol Add @@ -354,7 +354,7 @@ \li Allows you to browse not only files, but any items defined by locator filters. - \image qtcreator-locator.png "Locator" + \image qtcreator-locator.webp {Locator} \li You can add locator filters. Check that the filter is not already in use and give the filter a descriptive name. @@ -365,7 +365,7 @@ \li Contains menu items that represent commands or options and that are logically grouped and displayed. A menu can also contain submenus. - \image qtcreator-menu.png "Menu" + \image qtcreator-menu.png {Menu} \li You can create new menus. Use short, but descriptive names that are consistent with existing menu names. Use unambigious names. @@ -373,7 +373,7 @@ \row \li Menu item \li Represents a command or an option for users to choose. - \image qtcreator-menu-item.png "Menu item" + \image qtcreator-menu-item.png {Menu item} \li You can add new items to menus. Use short, but descriptive names that are consistent with existing menu names. Use unambigious names. @@ -381,7 +381,7 @@ \li Message box \li Dialog that provides feedback to users, in the form of status information, a warning, or an error message. - \image qtcreator-error-message.png "Message box" + \image qtcreator-error-message.png {Message box} Output from Qt Creator should be displayed in output views, instead. \li Use the event as the title and provide a solution in the @@ -390,21 +390,21 @@ \li Mode \li Modes correspond to complete screens of controls, specialized for a task. - \image qtcreator-mode-selector.png "Mode selector" + \image qtcreator-mode-selector.png {Mode selector} \li You can add a mode for a new type of editor, for example. Use descriptive, but short mode names. They have to fit in the \uicontrol {Mode selector}. \row \li Output \li Views to display output from Qt Creator. - \image qtcreator-output-pane.png "Output" + \image qtcreator-output-pane.png {Output} \li Use descriptive names for output views. \row \li Sidebar \li A view available in the \uicontrol Edit and \uicontrol Debug modes that you can use to browse projects, files, and bookmarks, and to view the class hierarchy. - \image qtcreator-sidebar-menu.png "Sidebar" + \image qtcreator-sidebar-menu.png {Sidebar} \li You can add views to the sidebar menu. Use descriptive names for them. \row @@ -413,7 +413,7 @@ provides them with functions for managing the information. Available in \uicontrol Debug mode, for interaction with the program that is running under the control of the debugger. - \image qtcreator-debugger-views.webp "Views" + \image qtcreator-debugger-views.webp {Views} \li Use descriptive names for views. \endtable diff --git a/qbs/imports/QtcTestFiles.qbs b/qbs/imports/QtcTestFiles.qbs new file mode 100644 index 00000000000..ab27a8df8a1 --- /dev/null +++ b/qbs/imports/QtcTestFiles.qbs @@ -0,0 +1,6 @@ +import qbs 1.0 + +Group { + name: "Unit tests" + condition: qtc.testsEnabled +} diff --git a/qbs/modules/qtc/qtc.qbs b/qbs/modules/qtc/qtc.qbs index c37e05ce9dd..226d432b181 100644 --- a/qbs/modules/qtc/qtc.qbs +++ b/qbs/modules/qtc/qtc.qbs @@ -6,16 +6,16 @@ import qbs.Utilities Module { Depends { name: "cpp"; required: false } - property string qtcreator_display_version: '10.0.1' + property string qtcreator_display_version: '11.0.0-beta1' property string ide_version_major: '10' property string ide_version_minor: '0' - property string ide_version_release: '1' + property string ide_version_release: '82' property string qtcreator_version: ide_version_major + '.' + ide_version_minor + '.' + ide_version_release property string ide_compat_version_major: '10' property string ide_compat_version_minor: '0' - property string ide_compat_version_release: '0' + property string ide_compat_version_release: '82' property string qtcreator_compat_version: ide_compat_version_major + '.' + ide_compat_version_minor + '.' + ide_compat_version_release diff --git a/share/qtcreator/CMakeLists.txt b/share/qtcreator/CMakeLists.txt index 0a868915a3d..f8be01a7a7b 100644 --- a/share/qtcreator/CMakeLists.txt +++ b/share/qtcreator/CMakeLists.txt @@ -35,12 +35,9 @@ set(resource_files debugger/libcpp_stdtypes.py debugger/stdtypes.py debugger/utils.py + debugger/loadorder.txt ) -if (APPLE) - set(resource_directories ${resource_directories} scripts) -endif() - # copy resource directories during build qtc_copy_to_builddir(copy_share_to_builddir DIRECTORIES ${resource_directories} diff --git a/share/qtcreator/debugger/creatortypes.py b/share/qtcreator/debugger/creatortypes.py index af790544601..a11377b0db5 100644 --- a/share/qtcreator/debugger/creatortypes.py +++ b/share/qtcreator/debugger/creatortypes.py @@ -235,7 +235,7 @@ def qdump__Utils__Port(d, value): -def qdump__Utils__Environment(d, value): +def x_qdump__Utils__Environment(d, value): qdump__Utils__NameValueDictionary(d, value) @@ -243,7 +243,7 @@ def qdump__Utils__DictKey(d, value): d.putStringValue(value["name"]) -def qdump__Utils__NameValueDictionary(d, value): +def x_qdump__Utils__NameValueDictionary(d, value): dptr = d.extractPointer(value) if d.qtVersion() >= 0x60000: if dptr == 0: diff --git a/share/qtcreator/debugger/dumper.py b/share/qtcreator/debugger/dumper.py index 84008943031..50dabd217fd 100644 --- a/share/qtcreator/debugger/dumper.py +++ b/share/qtcreator/debugger/dumper.py @@ -106,7 +106,7 @@ class Children(): self.d.putNumChild(0) if self.d.currentMaxNumChild is not None: if self.d.currentMaxNumChild < self.d.currentNumChild: - self.d.put('{name="",value="",type="",numchild="0"},') + self.d.put('{name="",value="",type="",numchild="1"},') self.d.currentChildType = self.savedChildType self.d.currentChildNumChild = self.savedChildNumChild self.d.currentNumChild = self.savedNumChild @@ -195,10 +195,16 @@ class DumperBase(): self.childrenPrefix = 'children=[' self.childrenSuffix = '],' - self.dumpermodules = [ - os.path.splitext(os.path.basename(p))[0] for p in - glob.glob(os.path.join(os.path.dirname(__file__), '*types.py')) - ] + self.dumpermodules = [] + + try: + # Fails in the piping case + self.dumpermodules = [ + os.path.splitext(os.path.basename(p))[0] for p in + glob.glob(os.path.join(os.path.dirname(__file__), '*types.py')) + ] + except: + pass # These values are never used, but the variables need to have # some value base for the swapping logic in Children.__enter__() @@ -215,7 +221,7 @@ class DumperBase(): def setVariableFetchingOptions(self, args): self.resultVarName = args.get('resultvarname', '') - self.expandedINames = set(args.get('expanded', [])) + self.expandedINames = args.get('expanded', {}) self.stringCutOff = int(args.get('stringcutoff', 10000)) self.displayStringLimit = int(args.get('displaystringlimit', 100)) self.typeformats = args.get('typeformats', {}) @@ -298,6 +304,11 @@ class DumperBase(): return range(0, self.currentNumChild) return range(min(self.currentMaxNumChild, self.currentNumChild)) + def maxArrayCount(self): + if self.currentIName in self.expandedINames: + return self.expandedINames[self.currentIName] + return 100 + def enterSubItem(self, item): if self.useTimeStamps: item.startTime = time.time() @@ -872,7 +883,7 @@ class DumperBase(): self.output.append(stuff) def takeOutput(self): - res = '\n'.join(self.output) + res = ''.join(self.output) self.output = [] return res @@ -2233,7 +2244,7 @@ class DumperBase(): res = self.currentValue return res # The 'short' display. - def putArrayData(self, base, n, innerType, childNumChild=None, maxNumChild=10000): + def putArrayData(self, base, n, innerType, childNumChild=None): self.checkIntType(base) self.checkIntType(n) addrBase = base @@ -2241,6 +2252,7 @@ class DumperBase(): self.putNumChild(n) #DumperBase.warn('ADDRESS: 0x%x INNERSIZE: %s INNERTYPE: %s' % (addrBase, innerSize, innerType)) enc = innerType.simpleEncoding() + maxNumChild = self.maxArrayCount() if enc: self.put('childtype="%s",' % innerType.name) self.put('addrbase="0x%x",' % addrBase) @@ -2248,7 +2260,7 @@ class DumperBase(): self.put('arrayencoding="%s",' % enc) self.put('endian="%s",' % self.packCode) if n > maxNumChild: - self.put('childrenelided="%s",' % n) # FIXME: Act on that in frontend + self.put('childrenelided="%s",' % n) n = maxNumChild self.put('arraydata="') self.put(self.readMemory(addrBase, n * innerSize)) @@ -2282,7 +2294,7 @@ class DumperBase(): def putPlotData(self, base, n, innerType, maxNumChild=1000 * 1000): self.putPlotDataHelper(base, n, innerType, maxNumChild=maxNumChild) if self.isExpanded(): - self.putArrayData(base, n, innerType, maxNumChild=maxNumChild) + self.putArrayData(base, n, innerType) def putSpecialArgv(self, value): """ diff --git a/share/qtcreator/debugger/lldbbridge.py b/share/qtcreator/debugger/lldbbridge.py index c14bf30286f..bef97482af6 100644 --- a/share/qtcreator/debugger/lldbbridge.py +++ b/share/qtcreator/debugger/lldbbridge.py @@ -870,6 +870,7 @@ class Dumper(DumperBase): self.startMode_ = args.get('startmode', 1) self.breakOnMain_ = args.get('breakonmain', 0) self.useTerminal_ = args.get('useterminal', 0) + self.firstStop_ = True pargs = self.hexdecode(args.get('processargs', '')) self.processArgs_ = pargs.split('\0') if len(pargs) else [] self.environment_ = args.get('environment', []) @@ -930,6 +931,8 @@ class Dumper(DumperBase): if self.startMode_ == DebuggerStartMode.AttachExternal: attach_info = lldb.SBAttachInfo(self.attachPid_) + if self.breakOnMain_: + self.createBreakpointAtMain() self.process = self.target.Attach(attach_info, error) if not error.Success(): self.reportState('enginerunfailed') @@ -1474,6 +1477,12 @@ class Dumper(DumperBase): self.reportState("inferiorstopok") else: self.reportState("stopped") + if self.firstStop_: + self.firstStop_ = False + if self.useTerminal_: + # When using a terminal, the process will be interrupted on startup. + # We therefore need to continue it here. + self.process.Continue() else: self.reportState(self.stateName(state)) @@ -2120,7 +2129,7 @@ class SummaryDumper(Dumper, LogMixin): # Expand variable if we need synthetic children oldExpanded = self.expandedINames - self.expandedINames = [value.name] if expanded else [] + self.expandedINames = {value.name: 100} if expanded else {} savedOutput = self.output self.output = [] diff --git a/share/qtcreator/debugger/loadorder.txt b/share/qtcreator/debugger/loadorder.txt new file mode 100644 index 00000000000..23d4dbf3a4a --- /dev/null +++ b/share/qtcreator/debugger/loadorder.txt @@ -0,0 +1,13 @@ +utils +gdbtracepoint +dumper +***bridge*** +boosttypes +creatortypes +libcpp_stdtypes +misctypes +opencvtypes +personaltypes +qttypes +stdtypes +android_stdtypes diff --git a/share/qtcreator/debugger/pdbbridge.py b/share/qtcreator/debugger/pdbbridge.py index e91c56e7163..228f4c8c1fc 100644 --- a/share/qtcreator/debugger/pdbbridge.py +++ b/share/qtcreator/debugger/pdbbridge.py @@ -1445,7 +1445,7 @@ class QtcInternalDumper(): self.updateData(args) def updateData(self, args): - self.expandedINames = __builtins__.set(args.get('expanded', [])) + self.expandedINames = args.get('expanded', {}) self.typeformats = args.get('typeformats', {}) self.formats = args.get('formats', {}) self.output = '' diff --git a/share/qtcreator/debugger/utils.py b/share/qtcreator/debugger/utils.py index fe2e558e711..8019d1e530a 100644 --- a/share/qtcreator/debugger/utils.py +++ b/share/qtcreator/debugger/utils.py @@ -4,6 +4,7 @@ # Debugger start modes. Keep in sync with DebuggerStartMode in debuggerconstants.h +# MT: Why does this not match (anymore?) to debuggerconstants.h : DebuggerStartMode ? class DebuggerStartMode(): ( NoStartMode, diff --git a/share/qtcreator/qml-type-descriptions/qt5QtQuick2-bundle.json b/share/qtcreator/qml-type-descriptions/qt5QtQuick2-bundle.json index 51bdff39b61..42222fd2dd3 100644 --- a/share/qtcreator/qml-type-descriptions/qt5QtQuick2-bundle.json +++ b/share/qtcreator/qml-type-descriptions/qt5QtQuick2-bundle.json @@ -199,6 +199,29 @@ "QtQuick.Controls.Styles 1.2", "QtQuick.Controls.Styles 1.3", "QtQuick.Controls.Styles 1.4", + "QtQuick.Controls.Basic 2.0", + "QtQuick.Controls.Basic 2.1", + "QtQuick.Controls.Basic 2.2", + "QtQuick.Controls.Basic 2.3", + "QtQuick.Controls.Basic 2.12", + "QtQuick.Controls.Basic 2.13", + "QtQuick.Controls.Basic 2.14", + "QtQuick.Controls.Basic 2.15", + "QtQuick.Controls.Fusion 2.0", + "QtQuick.Controls.Fusion 2.1", + "QtQuick.Controls.Fusion 2.2", + "QtQuick.Controls.Fusion 2.3", + "QtQuick.Controls.Fusion 2.12", + "QtQuick.Controls.Fusion 2.13", + "QtQuick.Controls.Fusion 2.14", + "QtQuick.Controls.Imagine 2.0", + "QtQuick.Controls.Imagine 2.1", + "QtQuick.Controls.Imagine 2.2", + "QtQuick.Controls.Imagine 2.3", + "QtQuick.Controls.Imagine 2.12", + "QtQuick.Controls.Imagine 2.13", + "QtQuick.Controls.Imagine 2.14", + "QtQuick.Controls.Imagine 2.15", "QtQuick.Dialogs 1.0", "QtQuick.Dialogs 1.1", "QtQuick.Dialogs 1.2", diff --git a/share/qtcreator/qml-type-descriptions/qt5QtQuick2ext-macos-bundle.json b/share/qtcreator/qml-type-descriptions/qt5QtQuick2ext-macos-bundle.json new file mode 100644 index 00000000000..5d34b4f2bf4 --- /dev/null +++ b/share/qtcreator/qml-type-descriptions/qt5QtQuick2ext-macos-bundle.json @@ -0,0 +1,10 @@ +{ + "name": "QtQuickControlsMacOS", + "searchPaths": [], + "installPaths": [], + "implicitImports": [], + "supportedImports": [ + "QtQuick.Controls.macOS", + "QtQuick.Controls.iOS" + ] +} diff --git a/share/qtcreator/qml-type-descriptions/qt5QtQuick2ext-win-bundle.json b/share/qtcreator/qml-type-descriptions/qt5QtQuick2ext-win-bundle.json new file mode 100644 index 00000000000..2d908c5841a --- /dev/null +++ b/share/qtcreator/qml-type-descriptions/qt5QtQuick2ext-win-bundle.json @@ -0,0 +1,9 @@ +{ + "name": "QtQuickControlsWin", + "searchPaths": [], + "installPaths": [], + "implicitImports": [], + "supportedImports": [ + "QtQuick.Controls.Windows" + ] +} diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/CMakeLists.main.txt.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/common/CMakeLists.main.txt.tpl index 086b6bc2e9d..ef31652b0eb 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/common/CMakeLists.main.txt.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/CMakeLists.main.txt.tpl @@ -1,44 +1,36 @@ cmake_minimum_required(VERSION 3.21.1) -set(BUILD_QDS_COMPONENTS ON CACHE BOOL "Build design studio components") option(LINK_INSIGHT "Link Qt Insight Tracker library" ON) +option(BUILD_QDS_COMPONENTS "Build design studio components" ON) project(%{ProjectName}App LANGUAGES CXX) -set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) -find_package(QT NAMES Qt6 COMPONENTS Gui Qml Quick) -find_package(Qt6 REQUIRED COMPONENTS Core Qml Quick) +find_package(Qt6 6.2 REQUIRED COMPONENTS Core Gui Qml Quick) -# To build this application you need Qt 6.2.0 or higher -if (Qt6_VERSION VERSION_LESS 6.2.0) -message(FATAL_ERROR "You need Qt 6.2.0 or newer to build the application.") +if (Qt6_VERSION VERSION_GREATER_EQUAL 6.3) + qt_standard_project_setup() endif() -qt_add_executable(${CMAKE_PROJECT_NAME} src/main.cpp) +qt_add_executable(%{ProjectName}App src/main.cpp) -# qt_standard_project_setup() requires Qt 6.3 or higher. See https://doc.qt.io/qt-6/qt-standard-project-setup.html for details. -if (${QT_VERSION_MINOR} GREATER_EQUAL 3) -qt6_standard_project_setup() -endif() - -qt_add_resources(${CMAKE_PROJECT_NAME} "configuration" +qt_add_resources(%{ProjectName}App "configuration" PREFIX "/" FILES qtquickcontrols2.conf ) -target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE - Qt${QT_VERSION_MAJOR}::Core - Qt${QT_VERSION_MAJOR}::Gui - Qt${QT_VERSION_MAJOR}::Quick - Qt${QT_VERSION_MAJOR}::Qml +target_link_libraries(%{ProjectName}App PRIVATE + Qt6::Core + Qt6::Gui + Qt6::Qml + Qt6::Quick ) -if (${BUILD_QDS_COMPONENTS}) +if (BUILD_QDS_COMPONENTS) include(${CMAKE_CURRENT_SOURCE_DIR}/qmlcomponents) -endif () +endif() include(${CMAKE_CURRENT_SOURCE_DIR}/qmlmodules) @@ -46,6 +38,8 @@ if (LINK_INSIGHT) include(${CMAKE_CURRENT_SOURCE_DIR}/insight) endif () -install(TARGETS ${CMAKE_PROJECT_NAME} +install(TARGETS %{ProjectName}App BUNDLE DESTINATION . - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) diff --git a/share/qtcreator/scripts/openTerminal.py b/share/qtcreator/scripts/openTerminal.py deleted file mode 100755 index 3dd1bcb1b92..00000000000 --- a/share/qtcreator/scripts/openTerminal.py +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright (C) 2018 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -import os -import pipes -import stat -import subprocess -import sys -from tempfile import NamedTemporaryFile - -def quote_shell(arg): - return pipes.quote(arg) - -def clean_environment_script(): - # keep some basic environment settings to ensure functioning terminal and config files - env_to_keep = ' '.join(['_', 'HOME', 'LOGNAME', 'PWD', 'SHELL', 'TMPDIR', 'USER', 'TERM', - 'TERM_PROGRAM', 'TERM_PROGRAM_VERSION', 'TERM_SESSION_CLASS_ID', - 'TERM_SESSION_ID']) - return r''' -function ignore() { - local keys=(''' + env_to_keep + ''') - local v=$1 - for e in "${keys[@]}"; do [[ "$e" == "$v" ]] && return 0; done -} -while read -r line; do - key=$(echo $line | /usr/bin/cut -d '=' -f 1) - ignore $key || unset $key -done < <(env) -''' - -def system_login_script_bash(): - return r''' -[ -r /etc/profile ] && source /etc/profile -# fake behavior of /etc/profile as if BASH was set. It isn't for non-interactive shell -export PS1='\h:\W \u\$ ' -[ -r /etc/bashrc ] && source /etc/bashrc -''' - -def login_script_bash(): - return r''' -if [ -f $HOME/.bash_profile ]; then - source $HOME/.bash_profile -elif [ -f $HOME/.bash_login ]; then - source $HOME/.bash_login ] -elif [ -f $HOME/.profile ]; then - source $HOME/.profile -fi -''' - -def system_login_script_zsh(): - return '[ -r /etc/profile ] && source /etc/profile\n' - -def login_script_zsh(): - return r''' -[ -r $HOME/.zprofile ] && source $HOME/.zprofile -[ -r $HOME/.zshrc ] && source $HOME/.zshrc -[ -r $HOME/.zlogin ] && source $HOME/.zlogin -''' - -def environment_script(): - return ''.join(['export ' + quote_shell(key + '=' + os.environ[key]) + '\n' - for key in os.environ]) - -def zsh_setup(shell): - return (shell, - system_login_script_zsh, - login_script_zsh, - shell + ' -c', - shell + ' -d -f') - -def bash_setup(shell): - bash = shell if shell is not None and shell.endswith('/bash') else '/bin/bash' - return (bash, - system_login_script_bash, - login_script_bash, - bash + ' -c', - bash + ' --noprofile -l') - -def main(): - # create temporary file to be sourced into bash that deletes itself - with NamedTemporaryFile(mode='wt', delete=False) as shell_script: - shell = os.environ.get('SHELL') - shell, system_login_script, login_script, non_interactive_shell, interactive_shell = ( - zsh_setup(shell) if shell is not None and shell.endswith('/zsh') - else bash_setup(shell)) - - commands = ('#!' + shell + '\n' + - 'rm ' + quote_shell(shell_script.name) + '\n' + - clean_environment_script() + - system_login_script() + # /etc/(z)profile by default resets the path, so do first - environment_script() + - login_script() + - 'cd ' + quote_shell(os.getcwd()) + '\n' + - ('exec ' + non_interactive_shell + ' ' + - quote_shell(' '.join([quote_shell(arg) for arg in sys.argv[1:]])) + '\n' - if len(sys.argv) > 1 else 'exec ' + interactive_shell + '\n') - ) - shell_script.write(commands) - shell_script.flush() - os.chmod(shell_script.name, stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR) - # TODO /usr/bin/open doesn't work with notarized app in macOS 13, - # use osascript instead (QTCREATORBUG-28683). - # This has the disadvantage that the Terminal windows doesn't close - # automatically anymore. - # subprocess.call(['/usr/bin/open', '-a', 'Terminal', shell_script.name]) - subprocess.call(['/usr/bin/osascript', '-e', 'tell app "Terminal" to activate']) - subprocess.call(['/usr/bin/osascript', '-e', 'tell app "Terminal" to do script "' + shell_script.name + '"']) - -if __name__ == "__main__": - main() diff --git a/share/qtcreator/snippets/cpp.xml b/share/qtcreator/snippets/cpp.xml index 82a62b36e6a..ca62a37998a 100644 --- a/share/qtcreator/snippets/cpp.xml +++ b/share/qtcreator/snippets/cpp.xml @@ -204,5 +204,5 @@ case $value$: default: break; } -Q_PROPERTY($type$ $name$ READ $name$ WRITE set$name:c$ NOTIFY $name$Changed) +Q_PROPERTY($type$ $name$ READ $name$ WRITE set$name:c$ NOTIFY $name$Changed FINAL) diff --git a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_collection.cpp b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_collection.cpp index 7e64374bab7..c5438f45ca2 100644 --- a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_collection.cpp +++ b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_collection.cpp @@ -10,5 +10,3 @@ QList @COLLECTION_PLUGIN_CLASS@::customWidgets( { return m_widgets; } - -@COLLECTION_PLUGIN_EXPORT@ diff --git a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_collection.h b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_collection.h index f9a898e6a8a..e8e792915bb 100644 --- a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_collection.h +++ b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_collection.h @@ -15,9 +15,9 @@ class @COLLECTION_PLUGIN_CLASS@ : public QObject, public QDesignerCustomWidgetCo @COLLECTION_PLUGIN_METADATA@ public: - explicit @COLLECTION_PLUGIN_CLASS@(QObject *parent = 0); + explicit @COLLECTION_PLUGIN_CLASS@(QObject *parent = nullptr); - virtual QList customWidgets() const; + QList customWidgets() const override; private: QList m_widgets; diff --git a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_plugin.pro b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_plugin.pro index 53d12b7397f..ab125bfb499 100644 --- a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_plugin.pro +++ b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_plugin.pro @@ -7,11 +7,7 @@ SOURCES =@PLUGIN_SOURCES@ RESOURCES = @PLUGIN_RESOURCES@ LIBS += -L. @WIDGET_LIBS@ -greaterThan(QT_MAJOR_VERSION, 4) { - QT += designer -} else { - CONFIG += designer -} +QT += designer target.path = $$[QT_INSTALL_PLUGINS]/designer INSTALLS += target diff --git a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_single.cpp b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_single.cpp index 6ffc8481da2..06790ac5478 100644 --- a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_single.cpp +++ b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_single.cpp @@ -6,7 +6,6 @@ @PLUGIN_CLASS@::@PLUGIN_CLASS@(QObject *parent) : QObject(parent) { - m_initialized = false; } void @PLUGIN_CLASS@::initialize(QDesignerFormEditorInterface * /* core */) @@ -61,11 +60,10 @@ bool @PLUGIN_CLASS@::isContainer() const QString @PLUGIN_CLASS@::domXml() const { - return QLatin1String("@WIDGET_DOMXML@"); + return QLatin1String(@WIDGET_DOMXML@); } QString @PLUGIN_CLASS@::includeFile() const { return QLatin1String("@WIDGET_HEADER@"); } -@SINGLE_PLUGIN_EXPORT@ diff --git a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_single.h b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_single.h index 0767c0581df..2402458092e 100644 --- a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_single.h +++ b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_single.h @@ -14,22 +14,22 @@ class @PLUGIN_CLASS@ : public QObject, public QDesignerCustomWidgetInterface @SINGLE_PLUGIN_METADATA@ public: - @PLUGIN_CLASS@(QObject *parent = 0); + explicit @PLUGIN_CLASS@(QObject *parent = nullptr); - bool isContainer() const; - bool isInitialized() const; - QIcon icon() const; - QString domXml() const; - QString group() const; - QString includeFile() const; - QString name() const; - QString toolTip() const; - QString whatsThis() const; - QWidget *createWidget(QWidget *parent); - void initialize(QDesignerFormEditorInterface *core); + bool isContainer() const override; + bool isInitialized() const override; + QIcon icon() const override; + QString domXml() const override; + QString group() const override; + QString includeFile() const override; + QString name() const override; + QString toolTip() const override; + QString whatsThis() const override; + QWidget *createWidget(QWidget *parent) override; + void initialize(QDesignerFormEditorInterface *core) override; private: - bool m_initialized; + bool m_initialized = false; }; @if ! '%{Cpp:PragmaOnce}' diff --git a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_widget.h b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_widget.h index 50256c5ff54..3ec192c6d31 100644 --- a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_widget.h +++ b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_widget.h @@ -12,7 +12,7 @@ class @WIDGET_CLASS@ : public @WIDGET_BASE_CLASS@ Q_OBJECT public: - @WIDGET_CLASS@(QWidget *parent = 0); + explicit @WIDGET_CLASS@(QWidget *parent = nullptr); }; @if ! '%{Cpp:PragmaOnce}' diff --git a/share/qtcreator/templates/wizards/autotest/files/tst.pro b/share/qtcreator/templates/wizards/autotest/files/tst.pro index 670c72e7987..1a4b00a5e8e 100644 --- a/share/qtcreator/templates/wizards/autotest/files/tst.pro +++ b/share/qtcreator/templates/wizards/autotest/files/tst.pro @@ -70,6 +70,35 @@ isEmpty(BOOST_INCLUDE_DIR): { SOURCES += \\ %{MainCppName} @endif +@if "%{TestFrameWork}" == "BoostTest_dyn" +TEMPLATE = app +CONFIG -= qt +CONFIG -= app_bundle +CONFIG += console + +isEmpty(BOOST_INSTALL_DIR): BOOST_INSTALL_DIR=$$(BOOST_INSTALL_DIR) +@if "%{BoostInstallDir}" != "" +# set by Qt Creator wizard +isEmpty(BOOST_INSTALL_DIR): BOOST_INSTALL_DIR="%{BoostInstallDir}" +@endif +!isEmpty(BOOST_INSTALL_DIR) { + win32: INCLUDEPATH *= $${BOOST_INSTALL_DIR} + else: INCLUDEPATH *= $${BOOST_INSTALL_DIR}/include +} +# Windows: adapt to name scheme, e.g. lib64-msvc-14.2 +!isEmpty(BOOST_INSTALL_DIR): LIBS *= -L$${BOOST_INSTALL_DIR}/lib +# Windows: adapt to name scheme, e.g. boost_unit_test_framework-vc142-mt-gd-x64-1_80 +LIBS *= -lboost_unit_test_framework +DEFINES *= BOOST_UNIT_TEST_FRAMEWORK_DYN_LINK + +isEmpty(BOOST_INSTALL_DIR): { + message("BOOST_INSTALL_DIR is not set, assuming Boost can be found automatically in your system") +} + +SOURCES += \\ + %{MainCppName} \\ + %{TestCaseFileWithCppSuffix} +@endif @if "%{TestFrameWork}" == "Catch2" TEMPLATE = app @if "%{Catch2NeedsQt}" == "true" diff --git a/share/qtcreator/templates/wizards/autotest/files/tst.qbs b/share/qtcreator/templates/wizards/autotest/files/tst.qbs index 99a218090ed..dda4df02cc9 100644 --- a/share/qtcreator/templates/wizards/autotest/files/tst.qbs +++ b/share/qtcreator/templates/wizards/autotest/files/tst.qbs @@ -7,6 +7,11 @@ import "googlecommon.js" as googleCommon import qbs.Environment import qbs.File @endif +@if "%{TestFrameWork}" == "BoostTest_dyn" +import qbs.Environment +import qbs.File +import qbs.FileInfo +@endif @if "%{TestFrameWork}" == "Catch2" import qbs.Environment import qbs.File @@ -116,6 +121,40 @@ CppApplication { files: [ "%{MainCppName}" ] +@endif +@if "%{TestFrameWork}" == "BoostTest_dyn" + type: "application" + + property string boostInstallDir: { + if (typeof Environment.getEnv("BOOST_INSTALL_DIR") !== 'undefined') + return Environment.getEnv("BOOST_INSTALL_DIR"); + return "%{BoostInstallDir}"; // set by Qt Creator wizard + } + + Properties { + condition: boostInstallDir && File.exists(boostInstallDir) + cpp.includePaths: base.concat([qbs.hostOS.contains("windows") + ? boostInstallDir + : FileInfo.joinPaths(boostInstallDir, "include")]) + // Windows: adapt to different directory layout, e.g. "lib64-msvc-14.2" + cpp.libraryPaths: base.concat([FileInfo.joinPaths(boostInstallDir, "lib")]) + } + cpp.defines: base.concat("BOOST_UNIT_TEST_FRAMEWORK_DYN_LINK") + // Windows: adapt to name scheme, e.g. "boost_unit_test_framework-vc142-mt-gd-x64-1_80" + cpp.dynamicLibraries: ["boost_unit_test_framework"] + + condition: { + if (!boostInstallDir) + console.log("BOOST_INSTALL_DIR is not set, assuming Boost can be " + + "found automatically in your system"); + return true; + } + + files: [ + "%{MainCppName}", + "%{TestCaseFileWithCppSuffix}", + ] + @endif @if "%{TestFrameWork}" == "Catch2" type: "application" diff --git a/share/qtcreator/templates/wizards/autotest/files/tst.txt b/share/qtcreator/templates/wizards/autotest/files/tst.txt index 7e1bd4c46ee..739ec2c26a8 100644 --- a/share/qtcreator/templates/wizards/autotest/files/tst.txt +++ b/share/qtcreator/templates/wizards/autotest/files/tst.txt @@ -133,6 +133,33 @@ elseif (EXISTS ${BOOST_INCLUDE_DIR}) include_directories(${BOOST_INCLUDE_DIR}) endif () @endif +@if "%{TestFrameWork}" == "BoostTest_dyn" +set(Boost_USE_STATIC_LIBS OFF) +set(Boost_USE_MULTITHREADED ON) +set(Boost_USE_STATIC_RUNTIME OFF) + +if (DEFINED ENV{BOOST_INSTALL_DIR}) + set(BOOST_INSTALL_DIR $ENV{BOOST_INSTALL_DIR}) +else () + set(BOOST_INSTALL_DIR "%{BoostInstallDir}") # set by Qt Creator wizard +endif () +if (BOOST_INSTALL_DIR STREQUAL "") + message("BOOST_INSTALL_DIR not set, assuming Boost can be found automatically in your system") +elseif (EXISTS ${BOOST_INSTALL_DIR}) + set(BOOST_ROOT ${BOOST_INSTALL_DIR}) +endif () +find_package(Boost COMPONENTS unit_test_framework REQUIRED) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_executable(%{TestCaseName} %{MainCppName} %{TestCaseFileGTestWithCppSuffix}) +add_test(NAME %{TestCaseName} COMMAND %{TestCaseName}) +if (Boost_FOUND) + include_directories(${Boost_INCLUDE_DIRS}) + target_link_libraries(%{TestCaseName} PUBLIC ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}) +endif () +@endif @if "%{TestFrameWork}" == "Catch2" SET(CMAKE_CXX_STANDARD 11) diff --git a/share/qtcreator/templates/wizards/autotest/files/tst_main.cpp b/share/qtcreator/templates/wizards/autotest/files/tst_main.cpp index ac88e1b0f1d..1c915089d6f 100644 --- a/share/qtcreator/templates/wizards/autotest/files/tst_main.cpp +++ b/share/qtcreator/templates/wizards/autotest/files/tst_main.cpp @@ -21,13 +21,22 @@ int main(int argc, char *argv[]) } @endif @if "%{TestFrameWork}" == "BoostTest" -#define BOOST_TEST_MODULE %{TestSuiteName} +#define BOOST_TEST_MODULE My test module #include +BOOST_AUTO_TEST_SUITE( %{TestSuiteName} ) + BOOST_AUTO_TEST_CASE( %{TestCaseName} ) { BOOST_TEST( true /* test assertion */ ); } + +BOOST_AUTO_TEST_SUITE_END() +@endif +@if "%{TestFrameWork}" == "BoostTest_dyn" +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE My test module +#include @endif @if "%{TestFrameWork}" == "Catch2" @if "%{Catch2NeedsQt}" == "true" diff --git a/share/qtcreator/templates/wizards/autotest/files/tst_src_boost.cpp b/share/qtcreator/templates/wizards/autotest/files/tst_src_boost.cpp new file mode 100644 index 00000000000..d523db48b99 --- /dev/null +++ b/share/qtcreator/templates/wizards/autotest/files/tst_src_boost.cpp @@ -0,0 +1,13 @@ +%{Cpp:LicenseTemplate}\ +@if "%{TestFrameWork}" == "BoostTest_dyn" +#define BOOST_TEST_DYN_LINK +#include + +BOOST_AUTO_TEST_SUITE( %{TestSuiteName} ) + +BOOST_AUTO_TEST_CASE( %{TestCaseName} ) +{ + BOOST_TEST( true /* test assertion */ ); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/share/qtcreator/templates/wizards/autotest/wizard.json b/share/qtcreator/templates/wizards/autotest/wizard.json index ae8fdb9d121..6f19d679eac 100644 --- a/share/qtcreator/templates/wizards/autotest/wizard.json +++ b/share/qtcreator/templates/wizards/autotest/wizard.json @@ -90,9 +90,13 @@ "value": "QtQuickTest" }, { - "trKey": "Boost Test", + "trKey": "Boost Test (header only)", "value": "BoostTest" }, + { + "trKey": "Boost Test (shared libraries)", + "value": "BoostTest_dyn" + }, { "trKey": "Catch2", "value": "Catch2" @@ -113,7 +117,7 @@ { "name": "TestSuiteName", "trDisplayName": "Test suite name:", - "visible": "%{JS: ['BoostTest', 'GTest'].indexOf(value('TestFrameWork')) >= 0}", + "visible": "%{JS: ['BoostTest', 'BoostTest_dyn', 'GTest'].indexOf(value('TestFrameWork')) >= 0}", "mandatory": true, "type": "LineEdit", "data": { "validator": "^[a-zA-Z_0-9]+$" } @@ -181,6 +185,16 @@ "kind": "existingDirectory" } }, + { + "name": "BoostInstallDir", + "trDisplayName": "Boost install directory (optional):", + "visible": "%{JS: value('TestFrameWork') == 'BoostTest_dyn'}", + "mandatory": false, + "type": "PathChooser", + "data": { + "kind": "existingDirectory" + } + }, { "name": "CatchIncDir", "trDisplayName": "Catch2 include directory (optional):", @@ -300,9 +314,14 @@ { "source": "files/tst_main.cpp", "target": "%{MainCppName}", - "condition": "%{JS: ['GTest', 'QtQuickTest', 'BoostTest', 'Catch2'].indexOf(value('TestFrameWork')) >= 0}", + "condition": "%{JS: ['GTest', 'QtQuickTest', 'BoostTest', 'BoostTest_dyn', 'Catch2'].indexOf(value('TestFrameWork')) >= 0}", "openInEditor": true }, + { + "source": "files/tst_src_boost.cpp", + "target": "%{TestCaseFileWithCppSuffix}", + "condition": "%{JS: value('TestFrameWork') === 'BoostTest_dyn'}" + }, { "source": "files/tst_qml.tmpl", "target": "%{TestCaseFileWithQmlSuffix}", diff --git a/share/qtcreator/templates/wizards/codesnippet/wizard.json b/share/qtcreator/templates/wizards/codesnippet/wizard.json index 40757cf5715..c64ad36822e 100644 --- a/share/qtcreator/templates/wizards/codesnippet/wizard.json +++ b/share/qtcreator/templates/wizards/codesnippet/wizard.json @@ -3,7 +3,7 @@ "supportedProjectTypes": [ "CMakeProjectManager.CMakeProject" ], "id": "Z.Snippet", "category": "H.Project", - "trDescription": "Creates a CMake-based test project for which a code snippet can be entered.", + "trDescription": "Creates a CMake-based test project where you can enter a code snippet to compile and check it.", "trDisplayName": "Code Snippet", "trDisplayCategory": "Other Project", "featuresRequired": [ "QtSupport.Wizards.FeatureQt" ], diff --git a/share/qtcreator/templates/wizards/files/markdown/file.md b/share/qtcreator/templates/wizards/files/markdown/file.md new file mode 100644 index 00000000000..1c00de1fca7 --- /dev/null +++ b/share/qtcreator/templates/wizards/files/markdown/file.md @@ -0,0 +1,51 @@ +# First Level Heading + +Paragraph. + +## Second Level Heading + +Paragraph. + +- bullet ++ other bullet +* another bullet + * child bullet + +1. ordered +2. next ordered + +### Third Level Heading + +Some *italic* and **bold** text and `inline code`. + +An empty line starts a new paragraph. + +Use two spaces at the end +to force a line break. + +A horizontal ruler follows: + +--- + +Add links inline like [this link to the Qt homepage](https://www.qt.io), +or with a reference like [this other link to the Qt homepage][1]. + + Add code blocks with + four spaces at the front. + +> A blockquote +> starts with > +> +> and has the same paragraph rules as normal text. + +First Level Heading in Alternate Style +====================================== + +Paragraph. + +Second Level Heading in Alternate Style +--------------------------------------- + +Paragraph. + +[1]: https://www.qt.io diff --git a/share/qtcreator/templates/wizards/files/markdown/wizard.json b/share/qtcreator/templates/wizards/files/markdown/wizard.json new file mode 100644 index 00000000000..a5e49c755e1 --- /dev/null +++ b/share/qtcreator/templates/wizards/files/markdown/wizard.json @@ -0,0 +1,42 @@ +{ + "version": 1, + "supportedProjectTypes": [ ], + "id": "E.Markdown", + "category": "U.General", + "trDescription": "Creates a markdown file.", + "trDisplayName": "Markdown File", + "trDisplayCategory": "General", + "iconText": "md", + "platformIndependent": true, + "enabled": "%{JS: value('Plugins').indexOf('TextEditor') >= 0}", + + "options": [ + { "key": "FileName", "value": "%{JS: Util.fileName(value('TargetPath'), 'md')}" } + ], + + "pages" : + [ + { + "trDisplayName": "Location", + "trShortTitle": "Location", + "typeId": "File" + }, + { + "trDisplayName": "Project Management", + "trShortTitle": "Summary", + "typeId": "Summary" + } + ], + "generators" : + [ + { + "typeId": "File", + "data": + { + "source": "file.md", + "target": "%{FileName}", + "openInEditor": true + } + } + ] +} diff --git a/share/qtcreator/templates/wizards/files/testing/file.cpp b/share/qtcreator/templates/wizards/files/testing/file.cpp new file mode 100644 index 00000000000..e515339e2db --- /dev/null +++ b/share/qtcreator/templates/wizards/files/testing/file.cpp @@ -0,0 +1,35 @@ +%{Cpp:LicenseTemplate} +@if "%{TestFrameWork}" == "GTest" +#include +#include + +using namespace testing; + +TEST(%{TestSuiteName}, %{TestCaseName}) +{ + EXPECT_EQ(1, 1); + ASSERT_THAT(0, Eq(0)); +} + +@endif +@if "%{TestFrameWork}" == "BoostTest" +#define BOOST_TEST_DYN_LINK +#include +BOOST_AUTO_TEST_SUITE( %{TestSuiteName} ) + +BOOST_AUTO_TEST_CASE( %{TestCaseName} ) +{ + BOOST_TEST( true /* test assertion */ ); +} + +BOOST_AUTO_TEST_SUITE_END() +@endif +@if "%{TestFrameWork}" == "Catch2" +#include + +TEST_CASE("Another test with Catch2", "[fancy]") +{ + REQUIRE(0 == 0); +} + +@endif diff --git a/share/qtcreator/templates/wizards/files/testing/wizard.json b/share/qtcreator/templates/wizards/files/testing/wizard.json new file mode 100644 index 00000000000..34170434bea --- /dev/null +++ b/share/qtcreator/templates/wizards/files/testing/wizard.json @@ -0,0 +1,146 @@ +{ + "version": 1, + "supportedProjectTypes": [ ], + "id": "M.TestCase", + "category": "X.Testing", + "trDescription": "Creates a source file that you can add to an existing test project.", + "trDisplayName": "Test Case", + "trDisplayCategory": "Test Case", + "icon": "../../autotest/autotest.png", + "iconKind": "Themed", + "enabled": "%{JS: value('Plugins').indexOf('AutoTest') >= 0}", + + "options": [ + { "key": "TargetPath", "value": "%{Path}" }, + { "key": "QmlFileName", "value": "%{JS: Util.fileName(value('QmlSrcFile').startsWith('tst_') ? value('QmlSrcFile') : 'tst_' + value('QmlSrcFile'), '.qml')}" }, + { "key": "CppFileName", "value": "%{JS: Util.fileName(value('CppSrcFile'), Util.preferredSuffix('text/x-c++src'))}" } + ], + + "pages" : + [ + { + "trDisplayName": "Test Information", + "trShortTitle": "Details", + "typeId": "Fields", + "data": + [ + { + "name": "info", + "type": "Label", + "data": + { + "wordWrap": true, + "trText": "You must tell Qt Creator which test framework is used inside the project.\n\nYou should not mix multiple test frameworks in a project." + } + }, + { + "name": "TestFrameWork", + "trDisplayName": "Test framework:", + "type": "ComboBox", + "data": + { + "index": 0, + "items": + [ + { + "trKey": "Google Test", + "value": "GTest" + }, + { + "trKey": "Qt Quick Test", + "value": "QtQuickTest" + }, + { + "trKey": "Boost Test", + "value": "BoostTest" + }, + { + "trKey": "Catch2", + "value": "Catch2" + } + ] + } + }, + { + "name": "TestSuiteName", + "trDisplayName": "Test suite name:", + "visible": "%{JS: ['BoostTest', 'GTest'].indexOf(value('TestFrameWork')) >= 0}", + "mandatory": true, + "type": "LineEdit", + "data": { "validator": "^[a-zA-Z_0-9]+$" } + }, + { + "name": "TestCaseName", + "trDisplayName": "Test case name:", + "mandatory": true, + "type": "LineEdit", + "data": { "validator": "^[a-zA-Z_0-9]+$" } + }, + { + "name": "GenerateInitAndCleanup", + "trDisplayName": "Generate initialization and cleanup code", + "visible": "%{JS: value('TestFrameWork') === 'QtQuickTest' }", + "type": "CheckBox", + "data": { + "checked": false + } + }, + { + "name": "CppSrcFile", + "type": "LineEdit", + "trDisplayName": "Source file:", + "mandatory": true, + "visible": "%{JS: value('TestFrameWork') !== 'QtQuickTest' }", + "data": { "trText": "%{JS: 'tst_' + value('TestCaseName').toLowerCase() + '.' + Util.preferredSuffix('text/x-c++src')}" } + }, + { + "name": "QmlSrcFile", + "type": "LineEdit", + "trDisplayName": "Source file:", + "mandatory": true, + "visible": "%{JS: value('TestFrameWork') === 'QtQuickTest' }", + "data": { "trText": "%{JS: 'tst_' + value('TestCaseName').toLowerCase() + '.qml'}" } + }, + { + "name": "Path", + "type": "PathChooser", + "trDisplayName": "Path:", + "mandatory": true, + "data": + { + "kind": "directory", + "basePath": "%{InitialPath}", + "path": "%{InitialPath}" + } + } + ] + }, + { + "trDisplayName": "Project Management", + "trShortTitle": "Summary", + "typeId": "Summary" + } + ], + "generators" : + [ + { + "typeId": "File", + "data": + [ + { + "source": "file.cpp", + "target": "%{CppFileName}", + "condition": "%{JS: value('TestFrameWork') !== 'QtQuickTest'}", + "openInEditor": true, + "options": { "key": "Cpp:License:FileName", "value": "%{CppFileName}" } + }, + { + "source": "../../autotest/files/tst_qml.tmpl", + "target": "%{QmlFileName}", + "condition": "%{JS: value('TestFrameWork') === 'QtQuickTest'}", + "openInEditor": true + } + ] + } + ] +} diff --git a/share/qtcreator/templates/wizards/projects/consoleapp/wizard.json b/share/qtcreator/templates/wizards/projects/consoleapp/wizard.json index fa801e67f4f..1817b79d938 100644 --- a/share/qtcreator/templates/wizards/projects/consoleapp/wizard.json +++ b/share/qtcreator/templates/wizards/projects/consoleapp/wizard.json @@ -3,7 +3,7 @@ "supportedProjectTypes": [ "MesonProjectManager.MesonProject", "CMakeProjectManager.CMakeProject", "Qbs.QbsProject", "Qt4ProjectManager.Qt4Project" ], "id": "E.QtCore", "category": "D.ApplicationQt", - "trDescription": "Creates a project containing a single main.cpp file with a stub implementation.\n\nPreselects a desktop Qt for building the application if available.", + "trDescription": "Creates a project containing a single main.cpp file with a stub implementation and no graphical UI.\n\nPreselects a desktop Qt for building the application if available.", "trDisplayName": "Qt Console Application", "trDisplayCategory": "Application (Qt)", "icon": "../../global/consoleapplication.png", diff --git a/share/qtcreator/templates/wizards/projects/cpplibrary/wizard.json b/share/qtcreator/templates/wizards/projects/cpplibrary/wizard.json index 0a9bb685b7b..28e2242f7de 100644 --- a/share/qtcreator/templates/wizards/projects/cpplibrary/wizard.json +++ b/share/qtcreator/templates/wizards/projects/cpplibrary/wizard.json @@ -3,7 +3,7 @@ "supportedProjectTypes": [ "MesonProjectManager.MesonProject", "CMakeProjectManager.CMakeProject", "Qbs.QbsProject", "Qt4ProjectManager.Qt4Project" ], "id": "H.CppLibrary", "category": "G.Library", - "trDescription": "Creates a C++ library. This can be used to create:
  • a shared C++ library for use with QPluginLoader and runtime (Plugins)
  • a shared or static C++ library for use with another project at linktime
", + "trDescription": "Creates a C++ library. You can create:
  • a shared C++ library for use with QPluginLoader and runtime (Plugins)
  • a shared or static C++ library for use with another project at linktime
", "trDisplayName": "C++ Library", "trDisplayCategory": "Library", "icon": "../../global/lib.png", diff --git a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/empty/wizard.json b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/empty/wizard.json index 2d566dc1f66..8ca7c0cdcc5 100644 --- a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/empty/wizard.json +++ b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/empty/wizard.json @@ -26,20 +26,28 @@ { "trDisplayName": "Define Project Details", "trShortTitle": "Details", - "typeId": "Fields", - "data" : - [ - { - "name": "PySideVersion", - "trDisplayName": "PySide version:", - "type": "ComboBox", - "data": + "typeId": "PythonConfiguration", + "data": + { + "index": 0, + "items": + [ { - "index": 1, - "items": [ "PySide2", "PySide6" ] + "trKey": "PySide 6", + "value": + { + "PySideVersion": "PySide6" + } + }, + { + "trKey": "PySide 2", + "value": + { + "PySideVersion": "PySide2" + } } - } - ] + ] + } }, { "trDisplayName": "Project Management", diff --git a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/mainwindow/wizard.json b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/mainwindow/wizard.json index 58e9f8cd526..9156caaffd8 100644 --- a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/mainwindow/wizard.json +++ b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/mainwindow/wizard.json @@ -30,16 +30,6 @@ "typeId": "Fields", "data" : [ - { - "name": "PySideVersion", - "trDisplayName": "PySide version:", - "type": "ComboBox", - "data": - { - "index": 1, - "items": [ "PySide2", "PySide6" ] - } - }, { "name": "Class", "trDisplayName": "Class name:", @@ -77,6 +67,32 @@ } ] }, + { + "trDisplayName": "Define Project Details", + "trShortTitle": "Details", + "typeId": "PythonConfiguration", + "data": + { + "index": 0, + "items": + [ + { + "trKey": "PySide 6", + "value": + { + "PySideVersion": "PySide6" + } + }, + { + "trKey": "PySide 2", + "value": + { + "PySideVersion": "PySide2" + } + } + ] + } + }, { "trDisplayName": "Project Management", "trShortTitle": "Summary", diff --git a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/qtquickapplication/wizard.json b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/qtquickapplication/wizard.json index c04407347c8..b1b75fc0885 100644 --- a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/qtquickapplication/wizard.json +++ b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/qtquickapplication/wizard.json @@ -30,67 +30,59 @@ { "trDisplayName": "Define Project Details", "trShortTitle": "Details", - "typeId": "Fields", + "typeId": "PythonConfiguration", "data": - [ - { - "name": "QtVersion", - "trDisplayName": "PySide version:", - "type": "ComboBox", - "data": + { + "index": 0, + "items": + [ { - "index": 0, - "items": - [ - { - "trKey": "PySide 6", - "value": - { - "QtQuickVersion": "", - "QtQuickWindowVersion": "", - "PySideVersion": "PySide6" - } - }, - { - "trKey": "PySide 5.15", - "value": - { - "QtQuickVersion": "2.15", - "QtQuickWindowVersion": "2.15", - "PySideVersion": "PySide2" - } - }, - { - "trKey": "PySide 5.14", - "value": - { - "QtQuickVersion": "2.14", - "QtQuickWindowVersion": "2.14", - "PySideVersion": "PySide2" - } - }, - { - "trKey": "PySide 5.13", - "value": - { - "QtQuickVersion": "2.13", - "QtQuickWindowVersion": "2.13", - "PySideVersion": "PySide2" - } - }, - { - "trKey": "PySide 5.12", - "value": - { - "QtQuickVersion": "2.12", - "QtQuickWindowVersion": "2.12", - "PySideVersion": "PySide2" - } - } - ] + "trKey": "PySide 6", + "value": + { + "QtQuickVersion": "", + "QtQuickWindowVersion": "", + "PySideVersion": "PySide6" + } + }, + { + "trKey": "PySide 5.15", + "value": + { + "QtQuickVersion": "2.15", + "QtQuickWindowVersion": "2.15", + "PySideVersion": "PySide2" + } + }, + { + "trKey": "PySide 5.14", + "value": + { + "QtQuickVersion": "2.14", + "QtQuickWindowVersion": "2.14", + "PySideVersion": "PySide2" + } + }, + { + "trKey": "PySide 5.13", + "value": + { + "QtQuickVersion": "2.13", + "QtQuickWindowVersion": "2.13", + "PySideVersion": "PySide2" + } + }, + { + "trKey": "PySide 5.12", + "value": + { + "QtQuickVersion": "2.12", + "QtQuickWindowVersion": "2.12", + "PySideVersion": "PySide2" + } } - } - ] + ] + } }, { "trDisplayName": "Project Management", diff --git a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/widget/wizard.json b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/widget/wizard.json index 951be734757..6251eaf3341 100644 --- a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/widget/wizard.json +++ b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/widget/wizard.json @@ -3,7 +3,7 @@ "supportedProjectTypes": [ "PythonProject" ], "id": "F.QtForPythonApplicationWindowWidget", "category": "F.ApplicationPySide", - "trDescription": "Creates a Qt for Python application that includes a Qt Designer-based widget (ui file) - Requires .ui to Python conversion", + "trDescription": "Creates a Qt for Python application that includes a Qt Designer-based widget (ui file). Requires .ui to Python conversion.", "trDisplayName": "Window UI", "trDisplayCategory": "Application (Qt for Python)", "icon": "../icons/icon.png", @@ -30,16 +30,6 @@ "typeId": "Fields", "data" : [ - { - "name": "PySideVersion", - "trDisplayName": "PySide version:", - "type": "ComboBox", - "data": - { - "index": 1, - "items": [ "PySide2", "PySide6" ] - } - }, { "name": "Class", "trDisplayName": "Class name:", @@ -77,6 +67,32 @@ } ] }, + { + "trDisplayName": "Define Python Interpreter", + "trShortTitle": "Interpreter", + "typeId": "PythonConfiguration", + "data": + { + "index": 0, + "items": + [ + { + "trKey": "PySide 6", + "value": + { + "PySideVersion": "PySide6" + } + }, + { + "trKey": "PySide 2", + "value": + { + "PySideVersion": "PySide2" + } + } + ] + } + }, { "trDisplayName": "Project Management", "trShortTitle": "Summary", diff --git a/share/qtcreator/templates/wizards/projects/qtquickapplication/tmpl.qbs b/share/qtcreator/templates/wizards/projects/qtquickapplication/tmpl.qbs deleted file mode 100644 index 050ace84150..00000000000 --- a/share/qtcreator/templates/wizards/projects/qtquickapplication/tmpl.qbs +++ /dev/null @@ -1,21 +0,0 @@ -import qbs -CppApplication { -@if "%{UseVirtualKeyboard}" == "true" - Depends { name: "Qt"; submodules: ["quick", "virtualkeyboard"] } -@else - Depends { name: "Qt.quick" } -@endif - install: true - // Additional import path used to resolve QML modules in Qt Creator's code model - property pathList qmlImportPaths: [] - - files: [ - "%{MainCppFileName}", - ] - - Group { - Qt.core.resourcePrefix: "%{ProjectName}/" - fileTags: ["qt.qml.qml", "qt.core.resource_data"] - files: ["Main.qml"] - } -} diff --git a/share/qtcreator/templates/wizards/projects/qtquickapplication/wizard.json b/share/qtcreator/templates/wizards/projects/qtquickapplication/wizard.json index 88d63e19f9a..42b2d1ec0c1 100644 --- a/share/qtcreator/templates/wizards/projects/qtquickapplication/wizard.json +++ b/share/qtcreator/templates/wizards/projects/qtquickapplication/wizard.json @@ -1,28 +1,27 @@ { "version": 1, - "supportedProjectTypes": [ "CMakeProjectManager.CMakeProject", "QbsProjectManager.QbsProject" ], + "supportedProjectTypes": [ "CMakeProjectManager.CMakeProject" ], "id": "U.QtQuickApplicationEmpty", "category": "D.ApplicationQt", - "trDescription": "Creates a Qt Quick application that contains an empty window. Optionally, you can create a Qt Design Studio project.", + "trDescription": "Creates a Qt Quick application that can have both QML and C++ code. You can build the application and deploy it to desktop, embedded, and mobile target platforms.\n\nYou can select an option to create a project that you can open in Qt Design Studio, which has a visual editor for Qt Quick UIs.", "trDisplayName": "Qt Quick Application", "trDisplayCategory": "Application (Qt)", "icon": "icon.png", "iconKind": "Themed", "featuresRequired": [ "QtSupport.Wizards.FeatureQtQmlCMakeApi" ], - "enabled": "%{JS: value('Plugins').indexOf('CMakeProjectManager') >= 0 || value('Plugins').indexOf('QbsProjectManager') >= 0 }", + "enabled": "%{JS: value('Plugins').indexOf('CMakeProjectManager') >= 0 }", "options": [ - { "key": "ProjectFile", "value": "%{JS: value('BuildSystem') === 'cmake' ? '%{ProjectDirectory}/CMakeLists.txt' : '%{ProjectDirectory}/' + '%{ProjectName}'.toLowerCase() + '.qbs' }" }, + { "key": "ProjectFile", "value": "%{ProjectDirectory}/CMakeLists.txt" }, { "key": "MainCppFileName", "value": "%{JS: 'main.' + Util.preferredSuffix('text/x-c++src') }" }, { "key": "UseVirtualKeyboardByDefault", "value": "%{JS: value('Plugins').indexOf('Boot2Qt') >= 0 || value('Plugins').indexOf('Boot2QtQdb') >= 0 }" }, { "key": "TargetName", "value": "%{JS: 'app' + value('ProjectName') }" }, { "key": "HasQSPSetup", "value": "%{JS: value('MinimumSupportedQtVersion') > '6.2' }"}, { "key": "HasFailureSignal", "value": "%{JS: value('MinimumSupportedQtVersion') > '6.3' }"}, - { "key": "UsesAutoResourcePrefix", "value": "%{JS: value('MinimumSupportedQtVersion') > '6.4' && value('BuildSystem') === 'cmake' }"}, + { "key": "UsesAutoResourcePrefix", "value": "%{JS: value('MinimumSupportedQtVersion') > '6.4' }"}, { "key": "HasLoadFromModule", "value": "%{JS: value('MinimumSupportedQtVersion') > '6.4' && value('UsesAutoResourcePrefix') }"}, { "key": "QdsWizardPath", "value": "%{IDE:ResourcePath}/qmldesigner/studio_templates/projects" }, - { "key": "QdsProjectStyle", "value": "%{JS: value('BuildSystem') === 'cmake' ? %{QdsProjectStyleInput} : false }" }, { "key": "NoQdsProjectStyle", "value": "%{JS: !%{QdsProjectStyle} }" }, { "key": "ImportModuleName", "value": "%{ProjectName}" }, @@ -47,38 +46,6 @@ "trShortTitle": "Location", "typeId": "Project" }, - { - "trDisplayName": "Define Build System", - "trShortTitle": "Build System", - "typeId": "Fields", - "enabled": "%{JS: ! %{IsSubproject}}", - "data": - [ - { - "name": "BuildSystem", - "trDisplayName": "Build system:", - "type": "ComboBox", - "persistenceKey": "BuildSystemType", - "data": - { - "index": 0, - "items": - [ - { - "trKey": "CMake", - "value": "cmake", - "condition": "%{JS: value('Plugins').indexOf('CMakeProjectManager') >= 0}" - }, - { - "trKey": "Qbs", - "value": "qbs", - "condition": "%{JS: value('Plugins').indexOf('QbsProjectManager') >= 0}" - } - ] - } - } - ] - }, { "trDisplayName": "Define Project Details", "trShortTitle": "Details", @@ -86,12 +53,11 @@ "data": [ { - "name": "QdsProjectStyleInput", - "trDisplayName": "Create a project that you can open in Qt Design Studio", - "trToolTip": "Create a project with a structure that is compatible both with Qt Design Studio (via .qmlproject) and with Qt Creator (via CMakeLists.txt). It contains a .ui.qml form that you can visually edit in Qt Design Studio.", + "name": "QdsProjectStyle", + "trDisplayName": "Creates a project that you can open in Qt Design Studio.", + "trToolTip": "Creates a project with a structure that is compatible both with Qt Design Studio (via .qmlproject) and with Qt Creator (via CMakeLists.txt). It contains a .ui.qml form that you can visually edit in Qt Design Studio.", "type": "CheckBox", "span": true, - "visible": "%{JS: value('BuildSystem') === 'cmake'}", "persistenceKey": "QtQuick.QdsProjectStyle", "data": { @@ -111,11 +77,15 @@ }, { "name": "MinimumSupportedQtVersion", - "trDisplayName": "The minimum version of Qt you want to build the application for", + "trDisplayName": "The minimum version of Qt you want to build the application for.", "type": "ComboBox", "data": { - "items": [ "6.2", "6.4", "6.5" ], + "items": [ + { "trKey": "Qt 6.2", "value": "6.2" }, + { "trKey": "Qt 6.4", "value": "6.4" }, + { "trKey": "Qt 6.5", "value": "6.5" } + ], "index": 1 } } @@ -146,13 +116,7 @@ { "source": "CMakeLists.txt", "openAsProject": true, - "condition": "%{JS: %{NoQdsProjectStyle} && value('BuildSystem') === 'cmake' }" - }, - { - "source": "tmpl.qbs", - "target": "%{ProjectFile}", - "openAsProject": true, - "condition": "%{JS: value('BuildSystem') === 'qbs' }" + "condition": "%{NoQdsProjectStyle}" }, { "source": "main.cpp", diff --git a/share/qtcreator/templates/wizards/projects/qtquickuiprototype/wizard.json b/share/qtcreator/templates/wizards/projects/qtquickuiprototype/wizard.json index aba8309365b..251500b4949 100644 --- a/share/qtcreator/templates/wizards/projects/qtquickuiprototype/wizard.json +++ b/share/qtcreator/templates/wizards/projects/qtquickuiprototype/wizard.json @@ -3,7 +3,7 @@ "supportedProjectTypes": [ "QmlProjectManager.QmlProject" ], "id": "QA.QtQuickUi", "category": "H.Project", - "trDescription": "Creates a Qt Quick 2 UI project with a QML entry point. To use it, you need to have a QML runtime environment.\n\nUse this only if you are prototyping. You cannot create a full application with this. Consider using a Qt Quick Application project instead.", + "trDescription": "Creates a Qt Quick UI project for previewing and prototyping designs.\n\nTo develop a full application, create a Qt Quick Application project instead.", "trDisplayName": "Qt Quick UI Prototype", "trDisplayCategory": "Other Project", "icon": "qtquickuiprototype.png", diff --git a/share/qtcreator/templates/wizards/projects/qtwidgetsapplication/wizard.json b/share/qtcreator/templates/wizards/projects/qtwidgetsapplication/wizard.json index e9d005507dc..58abd94344d 100644 --- a/share/qtcreator/templates/wizards/projects/qtwidgetsapplication/wizard.json +++ b/share/qtcreator/templates/wizards/projects/qtwidgetsapplication/wizard.json @@ -3,7 +3,7 @@ "supportedProjectTypes": [ "MesonProjectManager.MesonProject","CMakeProjectManager.CMakeProject", "Qt4ProjectManager.Qt4Project", "Qbs.QbsProject" ], "id": "C.QtWidgets", "category": "D.ApplicationQt", - "trDescription": "Creates a widget-based Qt application that contains a Qt Designer-based main window.\n\nPreselects a desktop Qt for building the application if available.", + "trDescription": "Creates a widget-based Qt application that contains a Qt Designer-based main window and C++ source and header files to implement the application logic.\n\nPreselects a desktop Qt for building the application if available.", "trDisplayName": "Qt Widgets Application", "trDisplayCategory": "Application (Qt)", "icon": "../../global/guiapplication.png", diff --git a/share/qtcreator/templates/wizards/qtcreatorplugin/CMakeLists.txt b/share/qtcreator/templates/wizards/qtcreatorplugin/CMakeLists.txt index 48884eae644..a60e46cb7cd 100644 --- a/share/qtcreator/templates/wizards/qtcreatorplugin/CMakeLists.txt +++ b/share/qtcreator/templates/wizards/qtcreatorplugin/CMakeLists.txt @@ -22,6 +22,23 @@ find_package(QtCreator REQUIRED COMPONENTS Core) find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets) set(QtX Qt${QT_VERSION_MAJOR}) +# Add a CMake option that enables building your plugin with tests. +# You don't want your released plugin binaries to contain tests, +# so make that default to 'NO'. +# Enable tests by passing -DWITH_TESTS=ON to CMake. +option(WITH_TESTS "Builds with tests" NO) + +if(WITH_TESTS) + # Look for QtTest + find_package(${QtX} REQUIRED COMPONENTS Test) + + # Tell CMake functions like add_qtc_plugin about the QtTest component. + set(IMPLICIT_DEPENDS Qt::Test) + + # Enable ctest for auto tests. + enable_testing() +endif() + add_qtc_plugin(%{PluginName} PLUGIN_DEPENDS QtCreator::Core diff --git a/share/qtcreator/templates/wizards/qtcreatorplugin/myplugin_global.h b/share/qtcreator/templates/wizards/qtcreatorplugin/myplugin_global.h index b8b387bc01f..ad863f17945 100644 --- a/share/qtcreator/templates/wizards/qtcreatorplugin/myplugin_global.h +++ b/share/qtcreator/templates/wizards/qtcreatorplugin/myplugin_global.h @@ -6,6 +6,8 @@ #define %{GLOBAL_GUARD} @endif +#include + #if defined(%{LibraryDefine}) # define %{LibraryExport} Q_DECL_EXPORT #else diff --git a/share/qtcreator/themes/dark.creatortheme b/share/qtcreator/themes/dark.creatortheme index 2a12fa82618..a214688bf90 100644 --- a/share/qtcreator/themes/dark.creatortheme +++ b/share/qtcreator/themes/dark.creatortheme @@ -240,7 +240,6 @@ BadgeLabelBackgroundColorChecked=normalBackground BadgeLabelBackgroundColorUnchecked=selectedBackground BadgeLabelTextColorChecked=text BadgeLabelTextColorUnchecked=text -CanceledSearchTextColor=ff0000 ComboBoxArrowColor=text ComboBoxArrowColorDisabled=text ComboBoxTextColor=text @@ -441,6 +440,27 @@ QmlDesigner_FormeditorBackgroundColor=qmlDesignerButtonColor QmlDesigner_AlternateBackgroundColor=qmlDesignerButtonColor QmlDesigner_ScrollBarHandleColor=ff505050 +TerminalForeground=ffffffff +TerminalBackground=normalBackground +TerminalSelection=7fffffff +TerminalFindMatch=7fffff00 +TerminalAnsi0=000000 +TerminalAnsi1=8b1b10 +TerminalAnsi2=4aa32e +TerminalAnsi3=9a9a2f +TerminalAnsi4=0058D1 +TerminalAnsi5=a320ac +TerminalAnsi6=49a3b0 +TerminalAnsi7=bfbfbf +TerminalAnsi8=666666 +TerminalAnsi9=d22d1f +TerminalAnsi10=62d63f +TerminalAnsi11=e5e54b +TerminalAnsi12=003EFF +TerminalAnsi13=d22dde +TerminalAnsi14=69e2e4 +TerminalAnsi15=e5e5e6 + [Flags] ComboBoxDrawTextShadow=false DerivePaletteFromTheme=true diff --git a/share/qtcreator/themes/default.creatortheme b/share/qtcreator/themes/default.creatortheme index fc4aa4ee8e1..a7095266a8b 100644 --- a/share/qtcreator/themes/default.creatortheme +++ b/share/qtcreator/themes/default.creatortheme @@ -232,7 +232,6 @@ BadgeLabelBackgroundColorChecked=ffe0e0e0 BadgeLabelBackgroundColorUnchecked=ff808080 BadgeLabelTextColorChecked=ff606060 BadgeLabelTextColorUnchecked=ffffffff -CanceledSearchTextColor=ffff0000 ComboBoxArrowColor=ffb8b5b2 ComboBoxArrowColorDisabled=ffdcdcdc ComboBoxTextColor=ffffffff @@ -410,6 +409,27 @@ QmlDesigner_FormeditorBackgroundColor=qmlDesignerButtonColor QmlDesigner_AlternateBackgroundColor=qmlDesignerButtonColor QmlDesigner_ScrollBarHandleColor=ff7a7a7a +TerminalForeground=ff000000 +TerminalBackground=ffffffff +TerminalSelection=3f000000 +TerminalFindMatch=7fffff00 +TerminalAnsi0=000000 +TerminalAnsi1=8b1b10 +TerminalAnsi2=4aa32e +TerminalAnsi3=9a9a2f +TerminalAnsi4=0000ab +TerminalAnsi5=a320ac +TerminalAnsi6=49a3b0 +TerminalAnsi7=bfbfbf +TerminalAnsi8=666666 +TerminalAnsi9=d22d1f +TerminalAnsi10=62d63f +TerminalAnsi11=dac911 +TerminalAnsi12=0000fe +TerminalAnsi13=d22dde +TerminalAnsi14=69e2e4 +TerminalAnsi15=e5e5e6 + [Flags] ComboBoxDrawTextShadow=true DerivePaletteFromTheme=false diff --git a/share/qtcreator/themes/design-light.creatortheme b/share/qtcreator/themes/design-light.creatortheme index bcf49864551..eeac285a5fe 100644 --- a/share/qtcreator/themes/design-light.creatortheme +++ b/share/qtcreator/themes/design-light.creatortheme @@ -243,7 +243,6 @@ BadgeLabelBackgroundColorChecked=ffe0e0e0 BadgeLabelBackgroundColorUnchecked=ff808080 BadgeLabelTextColorChecked=ff606060 BadgeLabelTextColorUnchecked=ffffffff -CanceledSearchTextColor=ff0000 ComboBoxArrowColor=toolBarItem ComboBoxArrowColorDisabled=toolBarItemDisabled ComboBoxTextColor=fancyBarsNormalTextColor @@ -451,6 +450,27 @@ PaletteWindowTextDisabled=textDisabled PaletteBaseDisabled=backgroundColorDisabled PaletteTextDisabled=textDisabled +TerminalForeground=ff000000 +TerminalBackground=normalBackground +TerminalSelection=3f000000 +TerminalFindMatch=7fffff00 +TerminalAnsi0=000000 +TerminalAnsi1=8b1b10 +TerminalAnsi2=4aa32e +TerminalAnsi3=9a9a2f +TerminalAnsi4=0000ab +TerminalAnsi5=a320ac +TerminalAnsi6=49a3b0 +TerminalAnsi7=bfbfbf +TerminalAnsi8=666666 +TerminalAnsi9=d22d1f +TerminalAnsi10=62d63f +TerminalAnsi11=dac911 +TerminalAnsi12=0000fe +TerminalAnsi13=d22dde +TerminalAnsi14=69e2e4 +TerminalAnsi15=e5e5e6 + [Flags] ComboBoxDrawTextShadow=false DerivePaletteFromTheme=true diff --git a/share/qtcreator/themes/design.creatortheme b/share/qtcreator/themes/design.creatortheme index 179bd740559..5e8294efa55 100644 --- a/share/qtcreator/themes/design.creatortheme +++ b/share/qtcreator/themes/design.creatortheme @@ -246,7 +246,6 @@ BadgeLabelBackgroundColorChecked=ffe0e0e0 BadgeLabelBackgroundColorUnchecked=ff808080 BadgeLabelTextColorChecked=ff606060 BadgeLabelTextColorUnchecked=ffffffff -CanceledSearchTextColor=ff0000 ComboBoxArrowColor=toolBarItem ComboBoxArrowColorDisabled=toolBarItemDisabled ComboBoxTextColor=fancyBarsNormalTextColor @@ -499,6 +498,27 @@ PaletteTextDisabled=textDisabled PaletteMid=ffafafaf PalettePlaceholderText=ff808081 +TerminalForeground=ffffffff +TerminalBackground=normalBackground +TerminalSelection=7fffffff +TerminalFindMatch=7fffff00 +TerminalAnsi0=000000 +TerminalAnsi1=8b1b10 +TerminalAnsi2=4aa32e +TerminalAnsi3=9a9a2f +TerminalAnsi4=0058D1 +TerminalAnsi5=a320ac +TerminalAnsi6=49a3b0 +TerminalAnsi7=bfbfbf +TerminalAnsi8=666666 +TerminalAnsi9=d22d1f +TerminalAnsi10=62d63f +TerminalAnsi11=e5e54b +TerminalAnsi12=003EFF +TerminalAnsi13=d22dde +TerminalAnsi14=69e2e4 +TerminalAnsi15=e5e5e6 + [Flags] ComboBoxDrawTextShadow=false DerivePaletteFromTheme=true diff --git a/share/qtcreator/themes/flat-dark.creatortheme b/share/qtcreator/themes/flat-dark.creatortheme index 93944b1cc91..bb6b905288c 100644 --- a/share/qtcreator/themes/flat-dark.creatortheme +++ b/share/qtcreator/themes/flat-dark.creatortheme @@ -243,7 +243,6 @@ BadgeLabelBackgroundColorChecked=ffe0e0e0 BadgeLabelBackgroundColorUnchecked=ff808080 BadgeLabelTextColorChecked=ff606060 BadgeLabelTextColorUnchecked=ffffffff -CanceledSearchTextColor=ff0000 ComboBoxArrowColor=toolBarItem ComboBoxArrowColorDisabled=toolBarItemDisabled ComboBoxTextColor=fancyBarsNormalTextColor @@ -444,6 +443,27 @@ PaletteTextDisabled=textDisabled PaletteMid=ffa0a0a0 PalettePlaceholderText=ff7f7f80 +TerminalForeground=ffffffff +TerminalBackground=normalBackground +TerminalSelection=7fffffff +TerminalFindMatch=7fffff00 +TerminalAnsi0=000000 +TerminalAnsi1=8b1b10 +TerminalAnsi2=4aa32e +TerminalAnsi3=9a9a2f +TerminalAnsi4=0058D1 +TerminalAnsi5=a320ac +TerminalAnsi6=49a3b0 +TerminalAnsi7=bfbfbf +TerminalAnsi8=666666 +TerminalAnsi9=d22d1f +TerminalAnsi10=62d63f +TerminalAnsi11=e5e54b +TerminalAnsi12=003EFF +TerminalAnsi13=d22dde +TerminalAnsi14=69e2e4 +TerminalAnsi15=e5e5e6 + [Flags] ComboBoxDrawTextShadow=false DerivePaletteFromTheme=true diff --git a/share/qtcreator/themes/flat-light.creatortheme b/share/qtcreator/themes/flat-light.creatortheme index bfccb3b8842..cbf4b09ce17 100644 --- a/share/qtcreator/themes/flat-light.creatortheme +++ b/share/qtcreator/themes/flat-light.creatortheme @@ -242,7 +242,6 @@ BadgeLabelBackgroundColorChecked=ffe0e0e0 BadgeLabelBackgroundColorUnchecked=ff808080 BadgeLabelTextColorChecked=ff606060 BadgeLabelTextColorUnchecked=ffffffff -CanceledSearchTextColor=ff0000 ComboBoxArrowColor=toolBarItem ComboBoxArrowColorDisabled=toolBarItemDisabled ComboBoxTextColor=fancyBarsNormalTextColor @@ -420,6 +419,27 @@ QmlDesigner_FormeditorBackgroundColor=qmlDesignerButtonColor QmlDesigner_AlternateBackgroundColor=qmlDesignerButtonColor QmlDesigner_ScrollBarHandleColor=ffcccccc +TerminalForeground=ff000000 +TerminalBackground=normalBackground +TerminalSelection=3f000000 +TerminalFindMatch=7fffff00 +TerminalAnsi0=000000 +TerminalAnsi1=8b1b10 +TerminalAnsi2=4aa32e +TerminalAnsi3=9a9a2f +TerminalAnsi4=0000ab +TerminalAnsi5=a320ac +TerminalAnsi6=49a3b0 +TerminalAnsi7=bfbfbf +TerminalAnsi8=666666 +TerminalAnsi9=d22d1f +TerminalAnsi10=62d63f +TerminalAnsi11=dac911 +TerminalAnsi12=0000fe +TerminalAnsi13=d22dde +TerminalAnsi14=69e2e4 +TerminalAnsi15=e5e5e6 + [Flags] ComboBoxDrawTextShadow=false DerivePaletteFromTheme=false diff --git a/share/qtcreator/themes/flat.creatortheme b/share/qtcreator/themes/flat.creatortheme index 04c31e64fca..a252562a56f 100644 --- a/share/qtcreator/themes/flat.creatortheme +++ b/share/qtcreator/themes/flat.creatortheme @@ -237,7 +237,6 @@ BadgeLabelBackgroundColorChecked=ffe0e0e0 BadgeLabelBackgroundColorUnchecked=ff808080 BadgeLabelTextColorChecked=ff606060 BadgeLabelTextColorUnchecked=ffffffff -CanceledSearchTextColor=ff0000 ComboBoxArrowColor=toolBarItem ComboBoxArrowColorDisabled=toolBarItemDisabled ComboBoxTextColor=fancyBarsNormalTextColor @@ -415,6 +414,27 @@ QmlDesigner_FormeditorBackgroundColor=qmlDesignerButtonColor QmlDesigner_AlternateBackgroundColor=qmlDesignerButtonColor QmlDesigner_ScrollBarHandleColor=ff595b5c +TerminalForeground=ff000000 +TerminalBackground=normalBackground +TerminalSelection=3f000000 +TerminalFindMatch=7fffff00 +TerminalAnsi0=000000 +TerminalAnsi1=8b1b10 +TerminalAnsi2=4aa32e +TerminalAnsi3=9a9a2f +TerminalAnsi4=0000ab +TerminalAnsi5=a320ac +TerminalAnsi6=49a3b0 +TerminalAnsi7=bfbfbf +TerminalAnsi8=666666 +TerminalAnsi9=d22d1f +TerminalAnsi10=62d63f +TerminalAnsi11=dac911 +TerminalAnsi12=0000fe +TerminalAnsi13=d22dde +TerminalAnsi14=69e2e4 +TerminalAnsi15=e5e5e6 + [Flags] ComboBoxDrawTextShadow=false DerivePaletteFromTheme=false diff --git a/share/qtcreator/translations/qtcreator_cs.ts b/share/qtcreator/translations/qtcreator_cs.ts index 7f62f6a363e..d0e98799683 100644 --- a/share/qtcreator/translations/qtcreator_cs.ts +++ b/share/qtcreator/translations/qtcreator_cs.ts @@ -9719,10 +9719,6 @@ se projektu '%2' nepodařilo přidat. Clone Session Zdvojit sezení - - <a href="qthelp://org.qt-project.qtcreator/doc/creator-quick-tour.html#session-management-in-qt-creator">What is a Session?</a> - <a href="qthelp://org.qt-project.qtcreator/doc/creator-quick-tour.html#session-management-in-qt-creator">Co je to sezení?</a> - Switch to session Přepnout na sezení @@ -9748,8 +9744,8 @@ se projektu '%2' nepodařilo přidat. &Otevřít - <a href="qthelp://org.qt-project.qtcreator/doc/creator-project-managing-sessions.html">What is a Session?</a> - <a href="qthelp://org.qt-project.qtcreator/doc/creator-project-managing-sessions.html">Co je sezení?</a> + What is a Session? + Co je sezení? New session name @@ -10551,8 +10547,8 @@ přidat do správy verzí (%2)? Při ukládání sezení se vyskytla chyba - Could not save session to file %1 - Sezení se nepodařilo uložit do souboru %1 + Could not save session to file "%1" + Sezení se nepodařilo uložit do souboru "%1" Untitled @@ -28918,8 +28914,8 @@ Server: %2. Rozdíly pro "%1" - ALT+Z,Alt+D - ALT+Z,Alt+D + Alt+Z,Alt+D + Alt+Z,Alt+D Meta+Z,Meta+D @@ -28934,8 +28930,8 @@ Server: %2. Záznamy pro "%1" - ALT+Z,Alt+L - ALT+Z,Alt+L + Alt+Z,Alt+L + Alt+Z,Alt+L Meta+Z,Meta+L @@ -28950,8 +28946,8 @@ Server: %2. Stav "%1" - ALT+Z,Alt+S - ALT+Z,Alt+S + Alt+Z,Alt+S + Alt+Z,Alt+S Meta+Z,Meta+S @@ -29015,8 +29011,8 @@ Server: %2. Zapsat-odevzdat (commit)... - ALT+Z,Alt+C - ALT+Z,Alt+C + Alt+Z,Alt+C + Alt+Z,Alt+C Meta+Z,Meta+C diff --git a/share/qtcreator/translations/qtcreator_da.ts b/share/qtcreator/translations/qtcreator_da.ts index 67e43c395b6..a52185b07d4 100644 --- a/share/qtcreator/translations/qtcreator_da.ts +++ b/share/qtcreator/translations/qtcreator_da.ts @@ -2709,8 +2709,8 @@ Lokale commits pushes ikke til master-grenen inden en normal commit udføres.Meta+Z,Meta+D - ALT+Z,Alt+D - ALT+Z,Alt+D + Alt+Z,Alt+D + Alt+Z,Alt+D Log Current File @@ -2725,8 +2725,8 @@ Lokale commits pushes ikke til master-grenen inden en normal commit udføres.Meta+Z,Meta+L - ALT+Z,Alt+L - ALT+Z,Alt+L + Alt+Z,Alt+L + Alt+Z,Alt+L Status Current File @@ -2741,8 +2741,8 @@ Lokale commits pushes ikke til master-grenen inden en normal commit udføres.Meta+Z,Meta+S - ALT+Z,Alt+S - ALT+Z,Alt+S + Alt+Z,Alt+S + Alt+Z,Alt+S Add @@ -2805,8 +2805,8 @@ Lokale commits pushes ikke til master-grenen inden en normal commit udføres.Meta+Z,Meta+C - ALT+Z,Alt+C - ALT+Z,Alt+C + Alt+Z,Alt+C + Alt+Z,Alt+C Uncommit... @@ -7810,8 +7810,8 @@ Vil du dræbe den? Patch-kommandoen konfigureret i de generelle "Miljø"-indstillinger findes ikke. - Running in %1: %2 %3 - Kører om %1: %2 %3 + Running in "%1": %2 %3. + Kører om "%1": %2 %3. Unable to launch "%1": %2 @@ -22723,8 +22723,8 @@ til projektet "%2". Genskab sidste session ved opstart - <a href="qthelp://org.qt-project.qtcreator/doc/creator-project-managing-sessions.html">What is a Session?</a> - <a href="qthelp://org.qt-project.qtcreator/doc/creator-project-managing-sessions.html">Hvad er en session?</a> + What is a Session? + Hvad er en session? Session @@ -24940,8 +24940,8 @@ Disse filer bevares. Fejl ved gemning af session - Could not save session to file %1 - Kunne ikke gemme session til filen %1 + Could not save session to file "%1" + Kunne ikke gemme session til filen "%1" Untitled @@ -38022,8 +38022,8 @@ skal være et repository krævet SSH-autentifikation (se dokumentation på SSH o Kører: %1 %2 - Running in %1: %2 %3 - Kører om %1: %2 %3 + Running in %1: %2 %3. + Kører om %1: %2 %3. @@ -39042,8 +39042,8 @@ skal være et repository krævet SSH-autentifikation (se dokumentation på SSH o QtC::CppEditor - For appropriate options, consult the GCC or Clang manual pages or the <a href="https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html">GCC online documentation</a>. - For passende valgmuligheder, konsulter GCC- eller Clang-manualsiderne eller <a href="https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html">GCC online dokumentationen</a>. + For appropriate options, consult the GCC or Clang manual pages or the [GCC online documentation](%1). + For passende valgmuligheder, konsulter GCC- eller Clang-manualsiderne eller [GCC online dokumentationen](%1). Each level adds checks to the previous level. For more information, see <a href="https://github.com/KDE/clazy">clazy's homepage</a>. diff --git a/share/qtcreator/translations/qtcreator_de.ts b/share/qtcreator/translations/qtcreator_de.ts index a49b915b5a2..f4886dc760d 100644 --- a/share/qtcreator/translations/qtcreator_de.ts +++ b/share/qtcreator/translations/qtcreator_de.ts @@ -11934,7 +11934,7 @@ Lokale Commits werden nicht zum Master-Branch gepusht, bis ein normaler Commit e Local filesystem: - Dateisystem: + Lokales Dateisystem: Options @@ -11942,7 +11942,7 @@ Lokale Commits werden nicht zum Master-Branch gepusht, bis ein normaler Commit e Remember specified location as default - Obige Einstellung als Vorgabe übernehmen + Angegebenen Ort als Vorgabe übernehmen Overwrite @@ -12047,8 +12047,8 @@ Lokale Pull-Operationen werden nicht auf den Master-Branch angewandt.Status von "%1" - ALT+Z,Alt+D - ALT+Z,Alt+D + Alt+Z,Alt+D + Alt+Z,Alt+D Triggers a Bazaar version control operation. @@ -12059,16 +12059,16 @@ Lokale Pull-Operationen werden nicht auf den Master-Branch angewandt.Meta+Z,Meta+D - ALT+Z,Alt+L - ALT+Z,Alt+L + Alt+Z,Alt+L + Alt+Z,Alt+L Meta+Z,Meta+L Meta+Z,Meta+L - ALT+Z,Alt+S - ALT+Z,Alt+S + Alt+Z,Alt+S + Alt+Z,Alt+S Meta+Z,Meta+S @@ -12124,15 +12124,15 @@ Lokale Pull-Operationen werden nicht auf den Master-Branch angewandt. Update... - Auf aktuellen Stand bringen... + Aktualisieren... Commit... Commit... - ALT+Z,Alt+C - ALT+Z,Alt+C + Alt+Z,Alt+C + Alt+Z,Alt+C Meta+Z,Meta+C @@ -12160,7 +12160,7 @@ Lokale Pull-Operationen werden nicht auf den Master-Branch angewandt. Unable to create a commit editor. - Es konnte kein Editor für den Commit angelegt werden. + Es konnte kein Commit-Editor angelegt werden. Commit changes for "%1". @@ -13043,8 +13043,8 @@ Zum Beispiel bewirkt die Angabe "Revision: 15" dass der Branch auf Rev %1 konnte nicht geladen werden: %2 - Attempt to include %1 which was already parsed. - Versuch, die Datei %1 einzufügen, welche schon ausgewertet wurde. + Attempt to include "%1" which was already parsed. + Versuch, die Datei "%1" einzufügen, welche schon ausgewertet wurde. yes @@ -13396,20 +13396,20 @@ Möchten Sie den Pfad zu den Quelldateien in die Zwischenablage kopieren?CMake-Generator fehlgeschlagen: %1. - Kit does not have a cmake binary set - Das Kit hat keine ausführbare CMake-Datei gesetzt + Kit does not have a cmake binary set. + Das Kit hat keine ausführbare CMake-Datei gesetzt. - Cannot create output directory "%1" - Das Ausgabeverzeichnis "%1" konnte nicht angelegt werden + Cannot create output directory "%1". + Das Ausgabeverzeichnis "%1" konnte nicht angelegt werden. - No valid cmake executable - Keine gültige ausführbare CMake-Datei + No valid cmake executable. + Keine gültige ausführbare CMake-Datei. - Running in %1: %2 - Führe in %1 aus: %2 + Running in "%1": %2. + Führe in "%1" aus: %2. <No CMake Tool available> @@ -13850,315 +13850,315 @@ Stellen Sie sicher, dass der Wert der CMAKE_BUILD_TYPE-Variable derselbe wie der QtC::CVS Annotate revision "%1" - Annotation für Revision "%1" + Annotation für Revision "%1" Ignore Whitespace - Leerzeichen ignorieren + Leerzeichen ignorieren Ignore Blank Lines - Leerzeilen ignorieren + Leerzeilen ignorieren &Edit - + B&earbeiten CVS Checkout - CVS-Checkout + CVS-Checkout Triggers a CVS version control operation. - + Führt eine Aktion des CVS-Versionskontrollsystems aus. &CVS - &CVS + &CVS Diff Current File - + Diff für aktuelle Datei Diff "%1" - Diff für "%1" + Diff für "%1" Meta+C,Meta+D - Meta+C,Meta+D + Meta+C,Meta+D Alt+C,Alt+D - Alt+C,Alt+D + Alt+C,Alt+D Filelog Current File - Filelog für Datei + Filelog für aktuelle Datei Filelog "%1" - Filelog für "%1" + Filelog für "%1" Annotate Current File - Annotation für Datei + Annotation für aktuelle Datei Annotate "%1" - Annotation für "%1" + Annotation für "%1" Add - Hinzufügen + Hinzufügen Add "%1" - "%1" hinzufügen + "%1" hinzufügen Meta+C,Meta+A - Meta+C,Meta+A + Meta+C,Meta+A Alt+C,Alt+A - Alt+C,Alt+A + Alt+C,Alt+A Commit Current File - Commit der aktuellen Datei + Commit der aktuellen Datei Commit "%1" - Commit von "%1" + Commit von "%1" Meta+C,Meta+C - Meta+C,Meta+C + Meta+C,Meta+C Alt+C,Alt+C - Alt+C,Alt+C + Alt+C,Alt+C Delete... - Löschen... + Löschen... Delete "%1"... - Lösche "%1"... + Lösche "%1"... Revert... - Rückgängig machen... + Rückgängig machen... Revert "%1"... - Änderungen in "%1" rückgängig machen... + Änderungen in "%1" rückgängig machen... Edit - + Anfordern (edit) Edit "%1" - "%1" anfordern + "%1" anfordern (edit) Unedit - Anforderung zurücknehmen + Anforderung zurücknehmen (unedit) Unedit "%1" - Anforderung der Datei '%1" zurücknehmen + Anforderung der Datei '%1" zurücknehmen (unedit) Unedit Repository - Anforderung im gesamten Repository zurücknehmen + Anforderung im gesamten Repository zurücknehmen (unedit) Diff Project - Diff für Projekt + Diff für Projekt Diff Project "%1" - Diff für Projekt "%1" + Diff für Projekt "%1" Project Status - Status des Projekts (status) + Status des Projekts Status of Project "%1" - Status des Projekts "%1" + Status des Projekts "%1" Log Project - Log für Projekt + Log für Projekt Log Project "%1" - Log für Projekt "%1" + Log für Projekt "%1" Update Project - Projekt auf aktuellen Stand bringen + Projekt auf aktuellen Stand bringen Update Project "%1" - Projekt "%1"auf aktuellen Stand bringen + Projekt "%1" auf aktuellen Stand bringen Commit Project - Commit des Projekts + Commit des Projekts Commit Project "%1" - Commit des Projekts "%1" + Commit des Projekts "%1" Update Directory - Verzeichnis aktualisieren + Verzeichnis aktualisieren Update Directory "%1" - Verzeichnis "%1" aktualisieren + Verzeichnis "%1" aktualisieren Commit Directory - Commit des Verzeichnisses + Commit des Verzeichnisses Commit Directory "%1" - Commit des Verzeichnisses "%1" + Commit des Verzeichnisses "%1" Diff Repository - Diff des Repositorys + Diff des Repositorys Repository Status - Status des Repositorys + Status des Repositorys Repository Log - Log des Repositorys + Log des Repositorys Update Repository - Repository auf den aktuellen Stand bringen + Repository auf den aktuellen Stand bringen Commit All Files - Commit aller Dateien + Commit aller Dateien Revert Repository... - Änderungen im gesamten Repository rückgängig machen... + Änderungen im gesamten Repository rückgängig machen... Revert Repository - Alle Änderungen rückgängig machen + Änderungen im gesamten Repository rückgängig machen Revert all pending changes to the repository? - Möchten Sie alle ausstehenden Änderungen des Repositorys verwerfen? + Möchten Sie alle ausstehenden Änderungen des Repositorys verwerfen? Revert failed: %1 - Fehler beim Rücksetzen der Änderungen: %1 + Fehler beim Rückgängigmachen der Änderungen: %1 The file has been changed. Do you want to revert it? - + Die Datei wurde geändert. Möchten Sie die Änderungen rückgängig machen? Another commit is currently being executed. - Es läuft bereits ein Commit-Vorgang. + Es läuft bereits ein Commit-Vorgang. There are no modified files. - Es gibt keine geänderten Dateien. + Es gibt keine geänderten Dateien. Would you like to discard your changes to the repository "%1"? - Möchten Sie alle ausstehenden Änderungen des Repositorys "%1" verwerfen? + Möchten Sie alle ausstehenden Änderungen des Repositorys "%1" verwerfen? Would you like to discard your changes to the file "%1"? - Möchten Sie alle ausstehenden Änderungen in der Datei "%1" verwerfen? + Möchten Sie alle ausstehenden Änderungen in der Datei "%1" verwerfen? Project status - Status des Projekts + Status des Projekts Repository status - Status des Repositorys + Status des Repositorys Cannot find repository for "%1". - Kann das Repository für "%1" nicht finden. + Kann das Repository für "%1" nicht finden. The initial revision %1 cannot be described. - Die erste Version (%1) kann nicht weiter beschrieben werden. + Die erste Version (%1) kann nicht beschrieben werden. Parsing of the log output failed. - Die Log-Ausgabe konnte nicht ausgewertet werden. + Die Log-Ausgabe konnte nicht ausgewertet werden. Could not find commits of id "%1" on %2. - Es konnten keine Commits des Datums %2 mit der ID "%1" gefunden werden. + Es konnten keine Commits des Datums %2 mit der ID "%1" gefunden werden. No CVS executable specified. - Es wurde keine ausführbare Datei für CVS angegeben. + Es wurde keine ausführbare Datei für CVS angegeben. CVS Command - CVS-Kommando + CVS-Kommando CVS command: - CVS-Kommando: + CVS-Kommando: CVS root: - CVS-Quelle (CVSROOT): + CVS-Quelle (CVSROOT): Describe all files matching commit id - Alle zur Commit-ID gehörenden Dateien beschreiben + Alle zur Commit-ID gehörenden Dateien beschreiben When checked, all files touched by a commit will be displayed when clicking on a revision number in the annotation view (retrieved via commit ID). Otherwise, only the respective file will be displayed. - Wenn die Option aktiviert ist, werden beim Klick auf die Revisionsnummer in der Annotationsansicht alle Dateien angezeigt, die zu einem Commit gehören (mittels Commit-ID bestimmt). Ansonsten wird nur die betreffende Datei angezeigt. + Wenn die Option aktiviert ist, werden beim Klick auf die Revisionsnummer in der Annotationsansicht alle Dateien angezeigt, die zu einem Commit gehören (mittels Commit-ID bestimmt). Ansonsten wird nur die betreffende Datei angezeigt. CVS - CVS + CVS Configuration - Konfiguration + Konfiguration Miscellaneous - Sonstige Einstellungen + Sonstige Einstellungen Added - Hinzugefügt + Hinzugefügt Removed - Gelöscht + Gelöscht Modified - Geändert + Geändert @@ -16566,8 +16566,8 @@ Trotzdem fortfahren? Das in den allgemeinen Umgebungseinstellungen konfigurierte patch-Kommando existiert nicht. - Running in %1: %2 %3 - Führe in %1 aus: %2 %3 + Running in "%1": %2 %3. + Führe in "%1" aus: %2 %3. Unable to launch "%1": %2 @@ -18199,16 +18199,16 @@ Möchten Sie sie jetzt auschecken? Bytes - KB - KB + KiB + KiB - GB - GB + GiB + GiB - TB - TB + TiB + TiB Enable crash reporting @@ -18989,8 +18989,8 @@ Doppelklicken Sie einen Eintrag um ihn zu ändern. Datei "%1" konnte nicht zum Schreiben der Protokolle geöffnet werden. - Save Enabled Categories As - Ausgewählte Kategorien speichern unter + Save Enabled Categories As... + Ausgewählte Kategorien speichern unter... Failed to write preset file "%1". @@ -19001,8 +19001,8 @@ Doppelklicken Sie einen Eintrag um ihn zu ändern. Ausgewählte Kategorien laden - Failed to open preset file "%1" for reading - Datei "%1" konnte nicht zum Lesen der Voreinstellung geöffnet werden + Failed to open preset file "%1" for reading. + Datei "%1" konnte nicht zum Lesen der Voreinstellung geöffnet werden. Failed to read preset file "%1": %2 @@ -20157,8 +20157,8 @@ z.B. name = "m_test_foo_": Diagnosekonfigurationen - For appropriate options, consult the GCC or Clang manual pages or the %1 GCC online documentation</a>. - Für passende Optionen lesen Sie das GCC- oder Clang-Handbuch oder auch die %1 GCC-Onlinedokumentation</a>. + For appropriate options, consult the GCC or Clang manual pages or the [GCC online documentation](%1). + Für passende Optionen lesen Sie das GCC- oder Clang-Handbuch oder auch die [GCC-Onlinedokumentation](%1). Copy... @@ -20339,8 +20339,8 @@ z.B. name = "m_test_foo_": Dateien ignorieren - <html><head/><body><p>Ignore files that match these wildcard patterns, one wildcard per line.</p></body></html> - <html><head/><body><p>Dateien ignorieren, die diesen Platzhalter-Filtern entsprechen. Ein Filter pro Zeile.</p></body></html> + Ignore files that match these wildcard patterns, one wildcard per line. + Dateien ignorieren, die diesen Platzhalter-Filtern entsprechen. Ein Filter pro Zeile. Ignore precompiled headers @@ -20465,8 +20465,8 @@ Dies ist normalerweise nicht empfehlenswert, da die Datei wahrscheinlich währen Möchten Sie stattdessen "%1" bearbeiten? - Open %1 - %1 öffnen + Open "%1" + "%1" öffnen &Refactor @@ -25092,8 +25092,8 @@ Das Setzen von Haltepunkten anhand von Dateinamen und Zeilennummern könnte fehl Schriftgröße des Debuggers mit Editor synchronisieren - <p>Not all source code lines generate executable code. Putting a breakpoint on such a line acts as if the breakpoint was set on the next line that generated code. Selecting 'Adjust Breakpoint Locations' shifts the red breakpoint markers in such cases to the location of the true breakpoint. - <p>Nicht aus allen Quellcode-Zeilen wird ausführbarer Code erzeugt. Wenn man dort einen Haltepunkt setzt, verhält er sich so, als ob er auf die nächste Zeile gesetzt worden wäre, aus der Maschinencode erzeugt wurde. Das Aktivieren der Einstellung 'Positionen der Haltepunkte korrigieren' bewirkt, dass der Haltepunkt-Marker in so einem Fall an die Stelle des resultierenden Haltepunkts verschoben wird. + Not all source code lines generate executable code. Putting a breakpoint on such a line acts as if the breakpoint was set on the next line that generated code. Selecting 'Adjust Breakpoint Locations' shifts the red breakpoint markers in such cases to the location of the true breakpoint. + Nicht aus allen Quellcode-Zeilen wird ausführbarer Code erzeugt. Wenn man dort einen Haltepunkt setzt, verhält er sich so, als ob er auf die nächste Zeile gesetzt worden wäre, aus der Maschinencode erzeugt wurde. Das Aktivieren der Einstellung 'Positionen der Haltepunkte korrigieren' bewirkt, dass der Haltepunkt-Marker in so einem Fall an die Stelle des resultierenden Haltepunkts verschoben wird. Stopping and stepping in the debugger will automatically open views associated with the current location. @@ -27079,497 +27079,495 @@ zu deaktivieren, deaktiviert auch die folgenden Plugins: QtC::Fossil Commit Editor - Commit-Editor + Commit-Editor Configure Repository - + Repository konfigurieren Existing user to become an author of changes made to the repository. - + Vorhandener Benutzer, der Autor der Änderungen im Repository werden soll. SSL/TLS Identity Key - + SSL/TLS Identity Key SSL/TLS client identity key to use if requested by the server. - + SSL/TLS Client Identity Key, der benutzt werden soll, wenn der Server diesen anfordert. Disable auto-sync - + Automatisches Synchronisieren (autosync) deaktivieren Disable automatic pull prior to commit or update and automatic push after commit or tag or branch creation. - + Deaktiviert die automatische Pull-Operation vor Commits oder Aktualisierungen und die automatische Push-Operation nach Commits oder dem Erstellen von Tags oder Branches. Repository User - + Benutzer des Repositorys User: - Nutzer: + Nutzer: Repository Settings - + Repository-Einstellungen SSL/TLS identity: - + SSL/TLS Identity: Ignore All Whitespace - + Alle Leerzeichen ignorieren Strip Trailing CR - + CR-Zeichen am Zeilenende entfernen Show Committers - + Committer anzeigen List Versions - + Versionen anzeigen Ancestors - + Vorfahren Descendants - + Nachfahren Unfiltered - Ungefiltert + Ungefiltert Lineage - + Abstammung Verbose - Ausführlich + Ausführlich Show files changed in each revision - Geänderte Dateien jeder Revision anzeigen + Geänderte Dateien jeder Revision anzeigen All Items - + Alle Elemente File Commits - + Datei-Commits Technical Notes - + Technische Anmerkungen (technotes) Tags - Tags + Tags Tickets - + Tickets Wiki Commits - + Wiki-Commits Item Types - + Element-Typen Private - + Privat - Create a private check-in that is never synced. -Children of private check-ins are automatically private. -Private check-ins are not pushed to the remote repository by default. - + Create a private check-in that is never synced. Children of private check-ins are automatically private. Private check-ins are not pushed to the remote repository by default. + Erstelle einen privaten Check-In, der niemals synchronisiert wird. Kinder von privaten Check-Ins sind automatisch privat. Private Check-Ins werden standardmäßig nicht zum entfernten Repository gepusht. Tag names to apply; comma-separated. - + Kommaseparierte Liste von Tag-Namen, die angewendet werden sollen. Current Information - + Aktuelle Informationen Local root: - + Lokale Root: Branch: - Branch: + Branch: Tags: - Schlüsselworte: + Tags: Commit Information - Informationen zu Commit + Informationen zum Commit New branch: - + Neuer Branch: Author: - Autor: + Autor: Message check failed. - + Die Überprüfung der Beschreibung schlug fehl. &Annotate %1 - + &Annotation für %1 Annotate &Parent Revision %1 - + Annotation der über&geordneten Revision %1 &Fossil - + &Fossil Annotate Current File - Annotation für Datei + Annotation für Datei Annotate "%1" - Annotation für "%1" + Annotation für "%1" Diff Current File - + Diff für aktuelle Datei Diff "%1" - Diff für "%1" + Diff für "%1" Meta+I,Meta+D - + Meta+I,Meta+D - ALT+I,Alt+D - + Alt+I,Alt+D + Alt+I,Alt+D Timeline Current File - + Zeitleiste für aktuelle Datei Timeline "%1" - + Zeitleiste für "%1" Meta+I,Meta+L - + Meta+I,Meta+L - ALT+I,Alt+L - + Alt+I,Alt+L + Alt+I,Alt+L Status Current File - Status der Datei + Status der aktuellen Datei Status "%1" - Status von "%1" + Status von "%1" Meta+I,Meta+S - + Meta+I,Meta+S - ALT+I,Alt+S - + Alt+I,Alt+S + Alt+I,Alt+S Add Current File - + Aktuelle Datei hinzufügen Add "%1" - "%1" hinzufügen + "%1" hinzufügen Delete Current File... - + Aktuelle Datei löschen... Delete "%1"... - Lösche "%1"... + Lösche "%1"... Revert Current File... - Änderungen der Datei rückgängig machen... + Änderungen der aktuellen Datei rückgängig machen... Revert "%1"... - Änderungen in "%1" rückgängig machen... + Änderungen in "%1" rückgängig machen... Diff - Diff + Diff Timeline - + Zeitleiste Meta+I,Meta+T - + Meta+I,Meta+T - ALT+I,Alt+T - + Alt+I,Alt+T + Alt+I,Alt+T Revert... - Rückgängig machen... + Rückgängig machen... Status - Status + Status Revert - Rückgängig machen + Rückgängig machen Pull... - Pull... + Pull... Push... - Push... + Push... Update... - Auf aktuellen Stand bringen... + Aktualisieren... Meta+I,Meta+U - + Meta+I,Meta+U - ALT+I,Alt+U - + Alt+I,Alt+U + Alt+I,Alt+U Commit... - Commit... + Commit... Meta+I,Meta+C - + Meta+I,Meta+C - ALT+I,Alt+C - + Alt+I,Alt+C + Alt+I,Alt+C - Settings ... - + Settings... + Einstellungen... Create Repository... - Repository erzeugen... + Repository erzeugen... Remote repository is not defined. - + Es ist kein entferntes Repository definiert. Update - Aktualisieren + Aktualisieren There are no changes to commit. - Es sind keine ausstehenden Änderungen vorhanden. + Es sind keine ausstehenden Änderungen vorhanden. Unable to create an editor for the commit. - Es konnte kein Editor für den Commit angelegt werden. + Es konnte kein Editor für den Commit angelegt werden. Unable to create a commit editor. - Es konnte kein Editor für den Commit angelegt werden. + Es konnte kein Commit-Editor angelegt werden. Commit changes for "%1". - Commit der Änderungen in "%1". + Commit der Änderungen in "%1". Choose Checkout Directory - + Verzeichnis für Checkout wählen The directory "%1" is already managed by a version control system (%2). Would you like to specify another directory? - Das Verzeichnis "%1" steht bereits unter Verwaltung eines Versionskontrollsystems (%2). Möchten Sie einen anderes Verzeichnis angeben? + Das Verzeichnis "%1" steht bereits unter Verwaltung eines Versionskontrollsystems (%2). Möchten Sie ein anderes Verzeichnis angeben? Repository already under version control - Repository bereits unter Versionskontrolle + Repository bereits unter Versionskontrolle Repository Created - Repository erstellt + Repository erstellt A version control repository has been created in %1. - Ein Repository für Versionskontrolle wurde im Verzeichnis %1 erstellt. + Ein Repository für Versionskontrolle wurde im Verzeichnis %1 erstellt. Repository Creation Failed - Fehlschlag bei Erstellung des Repositorys + Fehlschlag bei Erstellung des Repositorys A version control repository could not be created in %1. - Im Verzeichnis %1 konnte kein Repository für die Versionskontrolle erstellt werden. + Im Verzeichnis %1 konnte kein Repository für die Versionskontrolle erstellt werden. Fossil - + Fossil Specify a revision other than the default? - Möchten Sie eine Revision angeben? + Möchten Sie eine Revision angeben? Checkout revision, can also be a branch or a tag name. - + Checkout der Revision erstellen, kann auch der Name eines Branches oder Tags sein. Revision - Revision + Revision Fossil Command - + Fossil-Kommando Command: - + Kommando: Fossil Repositories - + Fossil-Repositorys Default path: - + Vorgabeverzeichnis: Directory to store local repositories by default. - + Vorgabeverzeichnis für lokale Repositorys. Default user: - + Vorgabe-Benutzer: Log width: - + Breite des Logs: The width of log entry line (>20). Choose 0 to see a single line per entry. - + Die Breite der Zeilen in Logs (>20). Wählen Sie 0, um eine einzelne Zeile pro Eintrag anzuzeigen. Timeout: - Zeitlimit: + Zeitlimit: s - s + s Log count: - Log-Anzeige beschränken auf: + Log-Anzeige beschränken auf: The number of recent commit log entries to show. Choose 0 to see all entries. - + Zahl der anzuzeigenden Logeinträge, 0 für unbegrenzt. Configuration - Konfiguration + Konfiguration Local Repositories - + Lokale Repositorys User - Nutzer + Nutzer Miscellaneous - Sonstige Einstellungen + Sonstige Einstellungen Pull Source - + Quelle für Pull-Operation Push Destination - + Ziel für Push-Operation Default location - Vorgabe + Vorgabe Local filesystem: - Dateisystem: + Lokales Dateisystem: Specify URL: - URL: + URL: For example: https://[user[:pass]@]host[:port]/[path] - + Zum Beispiel: https://[user[:pass]@]host[:port]/[path] Remember specified location as default - Obige Einstellung als Vorgabe übernehmen + Angegebenen Ort als Vorgabe übernehmen Include private branches - + Private Branches einbeziehen Allow transfer of private branches. - + Übertragen von privaten Branches erlauben. Remote Location - + Entfernter Ort Options - Einstellungen + Einstellungen @@ -29091,7 +29089,7 @@ Nicht markiert - Die Änderung ist kein Entwurf. Filter log entries by text in the commit message. - Log-Einträge nach Nachrichtentext filtern. + Log-Einträge nach Beschreibung filtern. Filter by content @@ -31887,8 +31885,8 @@ Beispiel: *.cpp%1*.h In die Zwischenablage kopieren - Cannot handle MIME type of message %1 - MIME type %1 der Nachricht kann nicht verarbeitet werden + Cannot handle MIME type "%1" of message. + MIME type "%1" der Nachricht kann nicht verarbeitet werden. Generic StdIO Language Server @@ -32023,7 +32021,7 @@ Beispiel: *.cpp%1*.h In "%1" ist keine ID angegeben. - Could not parse JSON message "%1". + Could not parse JSON message: "%1". Die JSON-Nachricht konnte nicht ausgewertet werden: "%1". @@ -32653,7 +32651,7 @@ Beispiel: *.cpp%1*.h Local filesystem: - Dateisystem: + Lokales Dateisystem: Default Location @@ -32813,7 +32811,7 @@ Beispiel: *.cpp%1*.h Update... - Auf aktuellen Stand bringen... + Aktualisieren... Import... @@ -34170,7 +34168,7 @@ You might find further explanations in the Application Output view. Update Project "%1" - Projekt "%1"auf aktuellen Stand bringen + Projekt "%1" auf aktuellen Stand bringen Describe... @@ -35115,8 +35113,8 @@ Title of a the cloned RunConfiguration window, text of the window Bei Start letzte Sitzung laden - <a href="qthelp://org.qt-project.qtcreator/doc/creator-project-managing-sessions.html">What is a Session?</a> - <a href="qthelp://org.qt-project.qtcreator/doc/creator-project-managing-sessions.html">Was ist eine Sitzung?</a> + What is a Session? + Was ist eine Sitzung? &Open @@ -35822,8 +35820,8 @@ Rename %2 to %3 anyway? Fehler beim Speichern der Sitzung - Could not save session to file %1 - Die Sitzung konnte nicht unter %1 gespeichert werden + Could not save session to file "%1" + Die Sitzung konnte nicht unter "%1" gespeichert werden Untitled @@ -37951,7 +37949,7 @@ Wählt eine für Desktop-Entwicklung geeignete Qt-Version aus, sofern sie verfü Leeres Fenster - Creates a Qt for Python application that includes a Qt Designer-based widget (ui file) - Requires .ui to Python conversion + Creates a Qt for Python application that includes a Qt Designer-based widget (ui file). Requires .ui to Python conversion. Erstellt eine Qt for Python-Anwendung, die ein Qt Designer-basiertes Widget (ui-Datei) enthält. Erfordert Umwandlung von .ui nach Python. @@ -38087,16 +38085,16 @@ Benutzen Sie dies nur für Prototypen. Sie können damit keine vollständige Anw Nimble-Anwendung - Create a project that you can open in Qt Design Studio - Erstellt ein Projekt, das in Qt Design Studio geöffnet werden kann + Creates a project that you can open in Qt Design Studio. + Erstellt ein Projekt, das in Qt Design Studio geöffnet werden kann. - Create a project with a structure that is compatible both with Qt Design Studio (via .qmlproject) and with Qt Creator (via CMakeLists.txt). It contains a .ui.qml form that you can visually edit in Qt Design Studio. + Creates a project with a structure that is compatible both with Qt Design Studio (via .qmlproject) and with Qt Creator (via CMakeLists.txt). It contains a .ui.qml form that you can visually edit in Qt Design Studio. Erstellt ein Projekt mit einer Struktur, die sowohl mit Qt Design Studio (via qmlproject) als auch mit Qt Creator (via CMakeLists.txt) kompatibel ist. Es enthält ein .ui.qml-Formular, das in Qt Design Studio visuell bearbeitet werden kann. - The minimum version of Qt you want to build the application for - Die niedrigste Qt-Version, die Sie zum Bauen der Anwendung benutzen wollen + The minimum version of Qt you want to build the application for. + Die niedrigste Qt-Version, die Sie zum Bauen der Anwendung benutzen wollen. Creates a Qt Quick application that contains an empty window. Optionally, you can create a Qt Design Studio project. @@ -38799,7 +38797,7 @@ Benutzen Sie dies nur für Prototypen. Sie können damit keine vollständige Anw %1: Vollständiger Pfad zur Hauptdatei. - %1: The name the active kit. + %1: The name of the active kit. %1 is something like "Active project" %1: Der Name des aktiven Kits. @@ -39110,7 +39108,7 @@ Sie werden erhalten. &Configure Project - Projekt &Konfigurieren + Projekt &konfigurieren Enable Kit for Project "%1" @@ -40165,12 +40163,12 @@ fails because Clang does not understand the target architecture. Ausführbare Datei ist leer. - %1 does not exist. - %1 existiert nicht. + "%1" does not exist. + "%1" existiert nicht. - %1 is not an executable file. - %1 ist keine ausführbare Datei. + "%1" is not an executable file. + "%1" ist keine ausführbare Datei. &Add @@ -40209,8 +40207,8 @@ fails because Clang does not understand the target architecture. Python Language Server benutzen - For a complete list of available options, consult the <a href="https://github.com/python-lsp/python-lsp-server/blob/develop/CONFIGURATION.md">Python LSP Server configuration documentation</a>. - Für eine vollständige Liste der verfügbaren Optionen siehe auch die <a href="https://github.com/python-lsp/python-lsp-server/blob/develop/CONFIGURATION.md">Dokumentation zur Python LSP-Server-Konfiguration</a>. + For a complete list of available options, consult the [Python LSP Server configuration documentation](%1). + Für eine vollständige Liste der verfügbaren Optionen siehe auch die [Dokumentation zur Python LSP-Server-Konfiguration](%1). Advanced @@ -41533,12 +41531,12 @@ Weder der Pfad zur Bibliothek noch der Pfad zu den Headerdateien wird zur .pro-D Kein qmake-Build-Schritt in der aktiven Build-Konfiguration - Cannot create output directory "%1" - Das Ausgabeverzeichnis "%1" konnte nicht angelegt werden + Cannot create output directory "%1". + Das Ausgabeverzeichnis "%1" konnte nicht angelegt werden. - Running in %1: %2 - Führe in %1 aus: %2 + Running in "%1": %2. + Führe in "%1" aus: %2. Build @@ -41991,8 +41989,8 @@ Für CMake-Projekte stellen Sie sicher, dass die Variable QML_IMPORT_PATH in CMa Package-Import erfordert eine Versionsnummer - Nested inline components are not supported - Verschachtelte Inline-Komponenten werden nicht unterstützt + Nested inline components are not supported. + Verschachtelte Inline-Komponenten werden nicht unterstützt. Errors while loading qmltypes from %1: @@ -44977,7 +44975,7 @@ wirklich löschen? Vollständiger Pfad zum Ziel-"bin"-Verzeichnis der Qt-Version des aktiven Kits des aktiven Projekts.<br>Wahrscheinlich sollten Sie stattdessen %1 nutzen. - Full path to the libexec bin directory of the Qt version in the active kit of the active project. + Full path to the libexec directory of the Qt version in the active kit of the active project. Vollständiger Pfad zum Host-"libexec"-Verzeichnis der Qt-Version des aktiven Kits des aktiven Projekts. @@ -47616,7 +47614,7 @@ Failed to open file "%1" Update Project "%1" - Projekt "%1"auf aktuellen Stand bringen + Projekt "%1" auf aktuellen Stand bringen Revert Repository... @@ -51132,16 +51130,16 @@ Die Trace-Daten sind verloren. Der Rückgabewert des Prozesses konnte nicht erhalten werden: %1 - copyFile is not implemented for "%1" - copyFile ist für "%1" nicht implementiert + copyFile is not implemented for "%1". + copyFile ist für "%1" nicht implementiert. - Cannot copy from %1, it is not a directory. - %1 kann nicht kopiert werden, da es kein Verzeichnis ist. + Cannot copy from "%1", it is not a directory. + "%1" kann nicht kopiert werden, da es kein Verzeichnis ist. - Cannot copy %1 to %2, it is not a writable directory. - %1 kann nicht nach %2 kopiert werden, da es kein schreibbares Verzeichnis ist. + Cannot copy "%1" to "%2", it is not a writable directory. + "%1" kann nicht nach "%2" kopiert werden, da es kein schreibbares Verzeichnis ist. Failed to copy recursively from "%1" to "%2" while trying to create tar archive from source: %3 @@ -51152,16 +51150,16 @@ Die Trace-Daten sind verloren. Rekursives Kopieren von "%1" nach "%2" beim Auspacken des Tar-Archivs am Ziel fehlgeschlagen: %3 - fileContents is not implemented for "%1" - fileContents ist für "%1" nicht implementiert + fileContents is not implemented for "%1". + fileContents ist für "%1" nicht implementiert. - writeFileContents is not implemented for "%1" - writeFileContents ist für "%1" nicht implementiert + writeFileContents is not implemented for "%1". + writeFileContents ist für "%1" nicht implementiert. - createTempFile is not implemented for "%1" - createTempFile ist für "%1" nicht implementiert + createTempFile is not implemented for "%1". + createTempFile ist für "%1" nicht implementiert. Refusing to remove root directory. @@ -51184,28 +51182,28 @@ Die Trace-Daten sind verloren. Kopieren der Datei "%1" nach "%2" ist fehlgeschlagen. - File "%1" does not exist - Datei "%1" existiert nicht + File "%1" does not exist. + Datei "%1" existiert nicht. - Could not open File "%1" - Die Datei "%1" konnte nicht geöffnet werden + Could not open File "%1". + Die Datei "%1" konnte nicht geöffnet werden. Cannot read "%1": %2 "%1" kann nicht gelesen werden: %2 - Could not open file "%1" for writing - Die Datei "%1" konnte nicht zum Schreiben geöffnet werden + Could not open file "%1" for writing. + Die Datei "%1" konnte nicht zum Schreiben geöffnet werden. - Could not write to file "%1" (only %2 of %3 bytes written) - Die Datei "%1" konnte nicht geschrieben werden (es wurden nur %2 von %3 Bytes geschrieben) + Could not write to file "%1" (only %2 of %3 bytes written). + Die Datei "%1" konnte nicht geschrieben werden (es wurden nur %2 von %3 Bytes geschrieben). - Could not create temporary file in "%1" (%2) - Es konnte keine temporäre Datei in "%1" erstellt werden (%2) + Could not create temporary file in "%1" (%2). + Es konnte keine temporäre Datei in "%1" erstellt werden (%2). Failed to copy file "%1" to "%2": %3 @@ -51224,8 +51222,8 @@ Die Trace-Daten sind verloren. Die temporäre Datei "%1" konnte nicht erstellt werden: %2 - Failed creating temporary file "%1" (too many tries) - Die temporäre Datei "%1" konnte nicht erstellt werden (zu viele Versuche) + Failed creating temporary file "%1" (too many tries). + Die temporäre Datei "%1" konnte nicht erstellt werden (zu viele Versuche). Failed to create directory "%1". @@ -51252,14 +51250,12 @@ Die Trace-Daten sind verloren. Arbeitsbereiche löschen - Delete workspace %1? - Arbeitsbereich %1 löschen? + Delete workspace "%1"? + Arbeitsbereich "%1" löschen? - Delete these workspaces? - %1 - Diese Arbeitsbereiche löschen? - %1 + Delete these workspaces? + Diese Arbeitsbereiche löschen? File Error @@ -53075,7 +53071,7 @@ Wird ein Problem gefunden, dann wird die Anwendung angehalten und kann untersuch Clean Repository - Repository bereinigen + Repository bereinigen Delete... @@ -53190,26 +53186,23 @@ Wird ein Problem gefunden, dann wird die Anwendung angehalten und kann untersuch Warning: The commit subject is very short. - + Warnung: Der Titel des Commits ist sehr kurz. Warning: The commit subject is too long. - + Warnung: Der Titel des Commits ist zu lang. Hint: Aim for a shorter commit subject. - + Hinweis: Versuchen Sie, einen kürzeren Titel zu vergeben. Hint: The second line of a commit message should be empty. - + Hinweis: Die zweite Zeile der Beschreibung sollte leer sein. - - <p>Writing good commit messages</p><ul><li>Avoid very short commit messages.</li><li>Consider the first line as subject (like in email) and keep it shorter than %n characters.</li><li>After an empty second line, a longer description can be added.</li><li>Describe why the change was done, not how it was done.</li></ul> - - - - + + <p>Writing good commit messages</p><ul><li>Avoid very short commit messages.</li><li>Consider the first line as a subject (like in emails) and keep it shorter than 72 characters.</li><li>After an empty second line, a longer description can be added.</li><li>Describe why the change was done, not how it was done.</li></ul> + <p>Gute Beschreibungen für Commits schreiben</p><ul><li>Vermeiden Sie sehr kurze Beschreibungen.</li><li>Betrachten Sie die erste Zeile als Betreff (wie in einer E-Mail) und halten Sie sie kürzer als 72 Zeichen.</li><li>Eine längere Beschreibung kann nach einer leeren zweiten Zeile folgen.</li><li>Beschreiben Sie, weshalb die Änderung vorgenommen wurde, nicht wie sie vorgenommen wurde.</li></ul> Update in progress @@ -53263,11 +53256,11 @@ Wird ein Problem gefunden, dann wird die Anwendung angehalten und kann untersuch Running: %1 - Führe aus: %1 + Führe aus: %1 - Running in %1: %2 - Führe in %1 aus: %2 {1:?} + Running in "%1": %2. + Führe in "%1" aus: %2. Failed to retrieve data. @@ -53307,7 +53300,7 @@ Wird ein Problem gefunden, dann wird die Anwendung angehalten und kann untersuch Command started... - Kommando gestartet... + Kommando gestartet... Checkout @@ -53315,15 +53308,15 @@ Wird ein Problem gefunden, dann wird die Anwendung angehalten und kann untersuch No job running, please abort. - Kein laufender Job, bitte brechen Sie ab. + Kein laufender Job, bitte brechen Sie ab. Succeeded. - Erfolgreich beendet. + Erfolgreich beendet. Failed. - Fehlgeschlagen. + Fehlgeschlagen. "%1" (%2) not found. @@ -53404,91 +53397,91 @@ Wird ein Problem gefunden, dann wird die Anwendung angehalten und kann untersuch User/&alias configuration file: - Nutzer/&Alias-Konfigurationsdatei: + Nutzer/&Alias-Konfigurationsdatei: A file listing nicknames in a 4-column mailmap format: 'name <email> alias <email>'. - Eine Datei, die Nutzernamen in einem vierspaltigen Format (mailmap) enthält: + Eine Datei, die Nutzernamen in einem vierspaltigen Format (mailmap) enthält: 'Name <E-Mail> Alias <E-Mail>'. User &fields configuration file: - Nutzer&feld-Konfigurationsdatei: + Nutzer&feld-Konfigurationsdatei: A simple file containing lines with field names like "Reviewed-By:" which will be added below the submit editor. - Eine Datei, die Zeilen mit Feldnamen (zum Beispiel "Reviewed-By:") enthält, die im Abgabefenster unter der Beschreibung erscheinen. + Eine Datei, die Zeilen mit Feldnamen (zum Beispiel "Reviewed-By:") enthält, die im Abgabefenster unter der Beschreibung erscheinen. Submit message &check script: - Skript zur &Überprüfung der Beschreibung: + Skript zur &Überprüfung der Beschreibung: An executable which is called with the submit message in a temporary file as first argument. It should return with an exit != 0 and a message on standard error to indicate failure. - Eine ausführbare Datei, die mit der Beschreibung in einer temporären Datei als erstem Kommandozeilenparameter aufgerufen wird. Bei Fehlschlag sollte sie einen Rückgabewert ungleich Null mit einer entsprechende Nachricht auf der Fehlerausgabe zurückgeben. + Eine ausführbare Datei, die mit der Beschreibung in einer temporären Datei als erstem Kommandozeilenparameter aufgerufen wird. Bei Fehlschlag sollte sie einen Rückgabewert ungleich Null mit einer entsprechenden Nachricht auf der Fehlerausgabe zurückgeben. &SSH prompt command: - Graphische &SSH-Passwortabfrage: + Graphische &SSH-Passwortabfrage: Specifies a command that is executed to graphically prompt for a password, should a repository require SSH-authentication (see documentation on SSH and the environment variable SSH_ASKPASS). - Kommando zur graphischen Passwortabfrage bei SSH-Authorisierung eines Repositorys + Kommando zur graphischen Passwortabfrage bei SSH-Authorisierung eines Repositorys (siehe SSH-Dokumentation zur Umgebungsvariable SSH-ASKPASS). Wrap submit message at: - Beschreibung umbrechen bei: + Beschreibung umbrechen bei: characters - Zeichen + Zeichen Reset VCS Cache - VCS-Cache zurücksetzen + VCS-Cache zurücksetzen Reset information about which version control system handles which directory. - Die Zuordnung, welches Versionsverwaltungssystem welches Verzeichnis behandelt, zurücksetzen. + Die Zuordnung zurücksetzen, welches Versionsverwaltungssystem welches Verzeichnis behandelt. Log count: - Log-Anzeige beschränken auf: + Log-Anzeige beschränken auf: Timeout: - Zeitlimit: + Zeitlimit: s - s + s &Open "%1" - + "%1" ö&ffnen &Copy to clipboard: "%1" - + In die Zwischenablage &kopieren: "%1" Fossil File Log Editor - + Fossil Datei-Log-Editor Fossil Annotation Editor - + Fossil Annotations-Editor Fossil Diff Editor - + Fossil Diff-Editor Fossil Commit Log Editor - + Fossil Commit-Log-Editor diff --git a/share/qtcreator/translations/qtcreator_es.ts b/share/qtcreator/translations/qtcreator_es.ts index ff32f1a64cb..a4dc26ed74f 100644 --- a/share/qtcreator/translations/qtcreator_es.ts +++ b/share/qtcreator/translations/qtcreator_es.ts @@ -7179,8 +7179,8 @@ al control de versiones (%2)? Error guardando sesión - Could not save session to file %1 - No se pudo guardar la sesion al archivo %1 + Could not save session to file "%1" + No se pudo guardar la sesion al archivo "%1" Qt Creator diff --git a/share/qtcreator/translations/qtcreator_fr.ts b/share/qtcreator/translations/qtcreator_fr.ts index 5ee57efddef..14a146f2109 100644 --- a/share/qtcreator/translations/qtcreator_fr.ts +++ b/share/qtcreator/translations/qtcreator_fr.ts @@ -9879,10 +9879,6 @@ francis : voila une nouvelle suggestion :) Rename session Renommer la session - - <a href="qthelp://com.nokia.qtcreator/doc/creator-project-managing-sessions.html">What is a Session?</a> - <a href="qthelp://com.nokia.qtcreator/doc/creator-project-managing-sessions.html">Qu'est-ce qu'une session?</a> - Automatically restore the last session when Qt Creator is started. Restaurer automatiquement la dernière session quand Qt Creator est démarré. @@ -9892,8 +9888,8 @@ francis : voila une nouvelle suggestion :) Restaurer la dernière session au démarrage - <a href="qthelp://org.qt-project.qtcreator/doc/creator-project-managing-sessions.html">What is a Session?</a> - <a href="qthelp://org.qt-project.qtcreator/doc/creator-project-managing-sessions.html">Qu'est-ce qu'une session ?</a> + What is a Session? + Qu'est-ce qu'une session ? Custom Process Step @@ -10841,8 +10837,8 @@ au système de gestion de version (%2) ? Erreur lors de l'enregistrement de la session - Could not save session to file %1 - Impossible d'enregistrer la session dans le fichier %1 + Could not save session to file "%1" + Impossible d'enregistrer la session dans le fichier "%1" Untitled @@ -28509,8 +28505,8 @@ Les version de Qt précédentes ont des limitations lors de la compilation des f Faire un diff de "%1" - ALT+Z,Alt+D - ALT+Z, Alt+D + Alt+Z,Alt+D + Alt+Z, Alt+D Meta+Z,Meta+D @@ -28525,8 +28521,8 @@ Les version de Qt précédentes ont des limitations lors de la compilation des f Réaliser un log de "%1" - ALT+Z,Alt+L - ALT+Z, Alt+L + Alt+Z,Alt+L + Alt+Z,Alt+L Meta+Z,Meta+L @@ -28541,8 +28537,8 @@ Les version de Qt précédentes ont des limitations lors de la compilation des f Statut "%1" - ALT+Z,Alt+S - ALT+Z, Alt+S + Alt+Z,Alt+S + Alt+Z,Alt+S Meta+Z,Meta+S @@ -28605,8 +28601,8 @@ Les version de Qt précédentes ont des limitations lors de la compilation des f Commit... - ALT+Z,Alt+C - ALT+Z, Alt+C + Alt+Z,Alt+C + Alt+Z,Alt+C Meta+Z,Meta+C diff --git a/share/qtcreator/translations/qtcreator_hr.ts b/share/qtcreator/translations/qtcreator_hr.ts index 86fd5ff6388..bdc95b998f8 100644 --- a/share/qtcreator/translations/qtcreator_hr.ts +++ b/share/qtcreator/translations/qtcreator_hr.ts @@ -2809,8 +2809,8 @@ p, li { white-space: pre-wrap; } Dodatne C++ predprocesorske direktive za %1: - For appropriate options, consult the GCC or Clang manual pages or the <a href="https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html">GCC online documentation</a>. - Odgovarajuće opcije potraži na stranicama GCC ili Clang priručnika ili na <a href="https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html">GCC online dokumentaciji</a>. + For appropriate options, consult the GCC or Clang manual pages or the [GCC online documentation](%1). + Odgovarajuće opcije potraži na stranicama GCC ili Clang priručnika ili na [GCC online dokumentaciji](%1). Use diagnostic flags from build system @@ -5588,8 +5588,8 @@ Greška: %5 Vrati izvorno stanje posljednje sesije prilikom pokretanja programa - <a href="qthelp://org.qt-project.qtcreator/doc/creator-project-managing-sessions.html">What is a Session?</a> - <a href="qthelp://org.qt-project.qtcreator/doc/creator-project-managing-sessions.html">Što je sesija?</a> + What is a Session? + Što je sesija? @@ -10705,8 +10705,8 @@ will also disable the following plugins: Očekivana vrsta %1, ali vrijednost je sadržavala %2 - Could not parse JSON message "%1". - Nije moguće obraditi JSON poruku "%1". + Could not parse JSON message: "%1". + Nije moguće obraditi JSON poruku: "%1". Expected a JSON object, but got a JSON "%1". @@ -14932,8 +14932,8 @@ Check the test environment. Meta+Z,Meta+D - ALT+Z,Alt+D - ALT+Z,Alt+D + Alt+Z,Alt+D + Alt+Z,Alt+D Log Current File @@ -14948,8 +14948,8 @@ Check the test environment. Meta+Z,Meta+L - ALT+Z,Alt+L - ALT+Z,Alt+L + Alt+Z,Alt+L + Alt+Z,Alt+L Status Current File @@ -14964,8 +14964,8 @@ Check the test environment. Meta+Z,Meta+S - ALT+Z,Alt+S - ALT+Z,Alt+S + Alt+Z,Alt+S + Alt+Z,Alt+S Add @@ -15028,8 +15028,8 @@ Check the test environment. Meta+Z,Meta+C - ALT+Z,Alt+C - ALT+Z,Alt+C + Alt+Z,Alt+C + Alt+Z,Alt+C Uncommit... @@ -18572,8 +18572,8 @@ Do you want to kill it? U općim postavkama za „Okruženje” konfigurirana zakrpna naredba ne postoji. - Running in %1: %2 %3 - Pokrenuto u %1: %2 %3 + Running in "%1": %2 %3. + Pokrenuto u "%1": %2 %3. Unable to launch "%1": %2 @@ -35830,8 +35830,8 @@ What do you want to do? Radi: %1 %2 - Running in %1: %2 %3 - Radi u %1: %2 %3 + Running in "%1": %2 %3. + Radi u "%1": %2 %3. Name of the version control system in use by the current project. diff --git a/share/qtcreator/translations/qtcreator_hu.ts b/share/qtcreator/translations/qtcreator_hu.ts index ad58a02fa31..843d55c8525 100644 --- a/share/qtcreator/translations/qtcreator_hu.ts +++ b/share/qtcreator/translations/qtcreator_hu.ts @@ -11592,8 +11592,8 @@ a verziókövetőhöz (%2)? Hiba történt a szakasz mentése közben - Could not save session to file %1 - Nem sikerült a szakasz %1 fájlba mentése + Could not save session to file "%1" + Nem sikerült a szakasz "%1" fájlba mentése Qt Creator diff --git a/share/qtcreator/translations/qtcreator_it.ts b/share/qtcreator/translations/qtcreator_it.ts index ccd8832a98b..8e092a00d77 100644 --- a/share/qtcreator/translations/qtcreator_it.ts +++ b/share/qtcreator/translations/qtcreator_it.ts @@ -7033,8 +7033,8 @@ al VCS (%2)? Errore durante il salvataggio della sessione - Could not save session to file %1 - Impossibile salvare la sessione sul file %1 + Could not save session to file "%1" + Impossibile salvare la sessione sul file "%1" Qt Creator diff --git a/share/qtcreator/translations/qtcreator_ja.ts b/share/qtcreator/translations/qtcreator_ja.ts index efdd53f0586..7bba62d3666 100644 --- a/share/qtcreator/translations/qtcreator_ja.ts +++ b/share/qtcreator/translations/qtcreator_ja.ts @@ -4403,8 +4403,8 @@ Add, modify, and remove document filters, which determine the documentation set 起動時に最後のセッションを復元する - <a href="qthelp://org.qt-project.qtcreator/doc/creator-project-managing-sessions.html">What is a Session?</a> - <a href="qthelp://org.qt-project.qtcreator/doc/creator-project-managing-sessions.html">セッションとは?</a> + What is a Session? + セッションとは? Automatically restores the last session when Qt Creator is started. @@ -9342,8 +9342,8 @@ will also disable the following plugins: パッケージをインポートするにはバージョン番号が含まれている必要があります - Nested inline components are not supported - インラインコンポーネントのネストはサポートされていません + Nested inline components are not supported. + インラインコンポーネントのネストはサポートされていません。 'int' or 'real' @@ -11816,8 +11816,8 @@ in the system's browser for manual download. Meta+Z,Meta+D - ALT+Z,Alt+D - ALT+Z,Alt+D + Alt+Z,Alt+D + Alt+Z,Alt+D Log Current File @@ -11832,8 +11832,8 @@ in the system's browser for manual download. Meta+Z,Meta+L - ALT+Z,Alt+L - ALT+Z,Alt+L + Alt+Z,Alt+L + Alt+Z,Alt+L Status Current File @@ -11848,8 +11848,8 @@ in the system's browser for manual download. Meta+Z,Meta+S - ALT+Z,Alt+S - ALT+Z,Alt+S + Alt+Z,Alt+S + Alt+Z,Alt+S Add @@ -11912,8 +11912,8 @@ in the system's browser for manual download. Meta+Z,Meta+C - ALT+Z,Alt+C - ALT+Z,Alt+C + Alt+Z,Alt+C + Alt+Z,Alt+C Uncommit... @@ -24418,8 +24418,8 @@ to project "%2". セッション %1 を保存できませんでした - Could not save session to file %1 - セッション %1 を保存できません + Could not save session to file "%1" + セッション "%1" を保存できません Untitled @@ -33563,8 +33563,8 @@ Android 5 ではローカルの Qt ライブラリをデプロイできません 削除 - For appropriate options, consult the GCC or Clang manual pages or the <a href="https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html">GCC online documentation</a>. - オプションの詳細は GCC や Clang のマニュアル、または <a href="https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html">GCC のオンラインドキュメント</a> を参照してください。 + For appropriate options, consult the GCC or Clang manual pages or the [GCC online documentation](%1). + オプションの詳細は GCC や Clang のマニュアル、または [GCC のオンラインドキュメント](%1) を参照してください。 Copy Diagnostic Configuration @@ -42568,14 +42568,12 @@ Output: ワークスペースを削除する - Delete workspace %1? - ワークスペース %1 を削除しますか? + Delete workspace "%1"? + ワークスペース "%1" を削除しますか? - Delete these workspaces? - %1 - これらのワークスペースを削除しますか? - %1 + Delete these workspaces? + これらのワークスペースを削除しますか? Cannot Restore Workspace diff --git a/share/qtcreator/translations/qtcreator_pl.ts b/share/qtcreator/translations/qtcreator_pl.ts index 56cdaa31b63..fc280a71457 100644 --- a/share/qtcreator/translations/qtcreator_pl.ts +++ b/share/qtcreator/translations/qtcreator_pl.ts @@ -903,8 +903,8 @@ &Otwórz - <a href="qthelp://org.qt-project.qtcreator/doc/creator-project-managing-sessions.html">What is a Session?</a> - <a href="qthelp://org.qt-project.qtcreator/doc/creator-project-managing-sessions.html">Co to jest sesja?</a> + What is a Session? + Co to jest sesja? Restore last session on startup @@ -7967,8 +7967,8 @@ do projektu "%2". Błąd podczas zachowywania sesji - Could not save session to file %1 - Nie można zachować sesji w pliku %1 + Could not save session to file "%1" + Nie można zachować sesji w pliku "%1" Untitled @@ -13881,8 +13881,8 @@ Local pulls are not applied to the master branch. Pokaż różnice w "%1" - ALT+Z,Alt+D - ALT+Z,Alt+D + Alt+Z,Alt+D + Alt+Z,Alt+D Meta+Z,Meta+D @@ -13897,8 +13897,8 @@ Local pulls are not applied to the master branch. Log "%1" - ALT+Z,Alt+L - ALT+Z,Alt+L + Alt+Z,Alt+L + Alt+Z,Alt+L Meta+Z,Meta+L @@ -13913,8 +13913,8 @@ Local pulls are not applied to the master branch. Stan "%1" - ALT+Z,Alt+S - ALT+Z,Alt+S + Alt+Z,Alt+S + Alt+Z,Alt+S Meta+Z,Meta+S @@ -13977,8 +13977,8 @@ Local pulls are not applied to the master branch. Utwórz poprawkę... - ALT+Z,Alt+C - ALT+Z,Alt+C + Alt+Z,Alt+C + Alt+Z,Alt+C Meta+Z,Meta+C @@ -28622,8 +28622,8 @@ Do you want to check them out now? Brak skonfigurowanej komendy "patch" w głównych ustawieniach środowiska. - Running in %1: %2 %3 - Uruchamianie w %1: %2 %3 + Running in "%1": %2 %3. + Uruchamianie w "%1": %2 %3. Unable to launch "%1": %2 @@ -31184,8 +31184,8 @@ Pliki z katalogu źródłowego pakietu Android są kopiowane do katalogu budowan Uruchamianie: %1 %2 - Running in %1: %2 %3 - Uruchamianie w %1: %2 %3 + Running in "%1": %2 %3. + Uruchamianie w "%1": %2 %3. @@ -34259,8 +34259,8 @@ Te pliki są zabezpieczone. Usuń - For appropriate options, consult the GCC or Clang manual pages or the <a href="https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html">GCC online documentation</a>. - Sposoby konfigurowania opisane są w podręczniku GCC lub Clang lub w <a href="https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html">dokumentacji online GCC</a>. + For appropriate options, consult the GCC or Clang manual pages or the [GCC online documentation](%1). + Sposoby konfigurowania opisane są w podręczniku GCC lub Clang lub w [dokumentacji online GCC](%1). Copy Diagnostic Configuration diff --git a/share/qtcreator/translations/qtcreator_ru.ts b/share/qtcreator/translations/qtcreator_ru.ts index e9fd7a81937..0c4dd669944 100644 --- a/share/qtcreator/translations/qtcreator_ru.ts +++ b/share/qtcreator/translations/qtcreator_ru.ts @@ -32,14 +32,12 @@ Удалить сессии - Delete workspace %1? - Удалить сессию %1? + Delete workspace "%1"? + Удалить сессию "%1"? - Delete these workspaces? - %1 - Удалить следующие сессии? - %1 + Delete these workspaces? + Удалить следующие сессии? Cannot Restore Workspace @@ -4182,7 +4180,7 @@ Local commits are not pushed to the master branch until a normal commit is perfo Состояние «%1» - ALT+Z,Alt+D + Alt+Z,Alt+D @@ -4190,7 +4188,7 @@ Local commits are not pushed to the master branch until a normal commit is perfo Meta+Z,Meta+D - ALT+Z,Alt+L + Alt+Z,Alt+L @@ -4198,7 +4196,7 @@ Local commits are not pushed to the master branch until a normal commit is perfo Meta+Z,Meta+L - ALT+Z,Alt+S + Alt+Z,Alt+S @@ -4262,7 +4260,7 @@ Local commits are not pushed to the master branch until a normal commit is perfo Фиксировать... - ALT+Z,Alt+C + Alt+Z,Alt+C @@ -10566,8 +10564,8 @@ Double-click to edit item. Команда patch, настроенная в общих настройках «Среды», отсутствует. - Running in %1: %2 %3 - Выполняется в %1: %2 %3 + Running in "%1": %2 %3. + Выполняется в "%1": %2 %3. Unable to launch "%1": %2 @@ -11170,8 +11168,8 @@ to version control (%2) Имя класса. - For appropriate options, consult the GCC or Clang manual pages or the <a href="https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html">GCC online documentation</a>. - Описание параметров можно найти страницах man GCC или Clang или в <a href="https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html">Документации GCC</a>. + For appropriate options, consult the GCC or Clang manual pages or the [GCC online documentation](%1). + Описание параметров можно найти страницах man GCC или Clang или в [Документации GCC](%1). Use diagnostic flags from build system @@ -29153,8 +29151,8 @@ to project "%2". &Удалить - <a href="qthelp://org.qt-project.qtcreator/doc/creator-project-managing-sessions.html">What is a Session?</a> - <a href="qthelp://org.qt-project.qtcreator/doc/creator-project-managing-sessions.html">Что такое сессия?</a> + What is a Session? + Что такое сессия? Restore last session on startup @@ -32162,8 +32160,8 @@ These files are preserved. Ошибка при сохранении сессии - Could not save session to file %1 - Не удалось сохранить сессию %1 + Could not save session to file "%1" + Не удалось сохранить сессию "%1" Untitled @@ -47662,8 +47660,8 @@ What do you want to do? Исполнение: %1 %2 - Running in %1: %2 %3 - Исполнение в %1: %2 %3 + Running in "%1": %2 %3. + Исполнение в "%1": %2 %3. &Undo diff --git a/share/qtcreator/translations/qtcreator_sl.ts b/share/qtcreator/translations/qtcreator_sl.ts index 6d74be5bd7e..8292124dfe6 100644 --- a/share/qtcreator/translations/qtcreator_sl.ts +++ b/share/qtcreator/translations/qtcreator_sl.ts @@ -6772,8 +6772,8 @@ enojen »Vstopi« za oddajo signala pa vas bo privedel neposredno do ustrezne pr &Odpri - <a href="qthelp://org.qt-project.qtcreator/doc/creator-project-managing-sessions.html">What is a Session?</a> - <a href="qthelp://org.qt-project.qtcreator/doc/creator-project-managing-sessions.html">Kaj je seja?</a> + What is a Session? + Kaj je seja? New session name @@ -7426,8 +7426,8 @@ v sistem za nadzor različic (%2)? Napaka med shranjevanjem seje - Could not save session to file %1 - Ni bilo moč shraniti seje v datoteko %1 + Could not save session to file "%1" + Ni bilo moč shraniti seje v datoteko "%1" Untitled @@ -19894,8 +19894,8 @@ Seznam za strežnik je: %2. Razlike v »%1« - ALT+Z,Alt+D - ALT+Z,Alt+D + Alt+Z,Alt+D + Alt+Z,Alt+D Log Current File @@ -19906,8 +19906,8 @@ Seznam za strežnik je: %2. Dnevnik za »%1« - ALT+Z,Alt+L - ALT+Z,Alt+L + Alt+Z,Alt+L + Alt+Z,Alt+L Status Current File @@ -19918,8 +19918,8 @@ Seznam za strežnik je: %2. Stanje za »%1« - ALT+Z,Alt+S - ALT+Z,Alt+S + Alt+Z,Alt+S + Alt+Z,Alt+S Add @@ -19978,8 +19978,8 @@ Seznam za strežnik je: %2. Zapiši ... - ALT+Z,Alt+C - ALT+Z,Alt+C + Alt+Z,Alt+C + Alt+Z,Alt+C Create Repository... diff --git a/share/qtcreator/translations/qtcreator_uk.ts b/share/qtcreator/translations/qtcreator_uk.ts index c50e8d22385..47c7b8a0931 100644 --- a/share/qtcreator/translations/qtcreator_uk.ts +++ b/share/qtcreator/translations/qtcreator_uk.ts @@ -456,8 +456,8 @@ Local commits are not pushed to the master branch until a normal commit is perfo Зміни в "%1" - ALT+Z,Alt+D - ALT+Z,Alt+D + Alt+Z,Alt+D + Alt+Z,Alt+D Meta+Z,Meta+D @@ -472,8 +472,8 @@ Local commits are not pushed to the master branch until a normal commit is perfo Історія "%1" - ALT+Z,Alt+L - ALT+Z,Alt+L + Alt+Z,Alt+L + Alt+Z,Alt+L Meta+Z,Meta+L @@ -488,8 +488,8 @@ Local commits are not pushed to the master branch until a normal commit is perfo - ALT+Z,Alt+S - ALT+Z,Alt+S + Alt+Z,Alt+S + Alt+Z,Alt+S Meta+Z,Meta+S @@ -552,8 +552,8 @@ Local commits are not pushed to the master branch until a normal commit is perfo - ALT+Z,Alt+C - ALT+Z,Alt+C + Alt+Z,Alt+C + Alt+Z,Alt+C Meta+Z,Meta+C @@ -14143,8 +14143,8 @@ Reason: %2 Помилка при збереженні сесії - Could not save session to file %1 - Не вдалось зберегти сесію до файлу %1 + Could not save session to file "%1" + Не вдалось зберегти сесію до файлу "%1" Untitled @@ -42730,8 +42730,8 @@ the program. Видалити - For appropriate options, consult the GCC or Clang manual pages or the <a href="https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html">GCC online documentation</a>. - Для відповідних опцій, переглянть сторінки man до GCC або Clang або ж <a href="https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html">документацію GCC в мережі</a>. + For appropriate options, consult the GCC or Clang manual pages or the [GCC online documentation](%1). + Для відповідних опцій, переглянть сторінки man до GCC або Clang або ж [документацію GCC в мережі](%1). Copy Diagnostic Configuration diff --git a/share/qtcreator/translations/qtcreator_zh_CN.ts b/share/qtcreator/translations/qtcreator_zh_CN.ts index ba78031390d..5d038593cc8 100644 --- a/share/qtcreator/translations/qtcreator_zh_CN.ts +++ b/share/qtcreator/translations/qtcreator_zh_CN.ts @@ -32,14 +32,12 @@ 删除工作区 - Delete workspace %1? - 删除 %1 工作区? + Delete workspace "%1"? + 删除 "%1" 工作区? - Delete these workspaces? - %1 - 删除这些工作区? - %1 + Delete these workspaces? + 删除这些工作区? Cannot Restore Workspace @@ -4480,8 +4478,8 @@ This flag will allow push to proceed. Diff "%1" - ALT+Z,Alt+D - ALT+Z,Alt+D + Alt+Z,Alt+D + Alt+Z,Alt+D Meta+Z,Meta+D @@ -4496,8 +4494,8 @@ This flag will allow push to proceed. Log "%1" - ALT+Z,Alt+L - ALT+Z,Alt+L + Alt+Z,Alt+L + Alt+Z,Alt+L Meta+Z,Meta+L @@ -4512,8 +4510,8 @@ This flag will allow push to proceed. Status "%1" - ALT+Z,Alt+S - ALT+Z,Alt+S + Alt+Z,Alt+S + Alt+Z,Alt+S Meta+Z,Meta+S @@ -4576,8 +4574,8 @@ This flag will allow push to proceed. 提交... - ALT+Z,Alt+C - ALT+Z,Alt+C + Alt+Z,Alt+C + Alt+Z,Alt+C Meta+Z,Meta+C @@ -28222,8 +28220,8 @@ to project "%2". 打开(&S) - <a href="qthelp://org.qt-project.qtcreator/doc/creator-project-managing-sessions.html">What is a Session?</a> - <a href="qthelp://org.qt-project.qtcreator/doc/creator-quick-tour.html#session-management-in-qt-creator">什么是会话?</a> + What is a Session? + 什么是会话? Restore last session on startup @@ -30031,8 +30029,8 @@ These files are preserved. 保存会话时发生错误 - Could not save session to file %1 - 无法将会话保存到文件 %1 + Could not save session to file "%1" + 无法将会话保存到文件 "%1" Untitled diff --git a/share/qtcreator/translations/qtcreator_zh_TW.ts b/share/qtcreator/translations/qtcreator_zh_TW.ts index c1f35c81802..f5c68598b06 100644 --- a/share/qtcreator/translations/qtcreator_zh_TW.ts +++ b/share/qtcreator/translations/qtcreator_zh_TW.ts @@ -6500,8 +6500,8 @@ Add, modify, and remove document filters, which determine the documentation set 重新命名工作階段 - <a href="qthelp://org.qt-project.qtcreator/doc/creator-project-managing-sessions.html">What is a Session?</a> - <a href="qthelp://org.qt-project.qtcreator/doc/creator-project-managing-sessions.html">什麼是工作階段?</a> + What is a Session? + 什麼是工作階段? Automatically restore the last session when Qt Creator is started. @@ -7218,8 +7218,8 @@ to version control (%2)? 儲存工作階段時發生錯誤 - Could not save session to file %1 - 無法儲存工作階段至檔案 %1 + Could not save session to file "%1" + 無法儲存工作階段至檔案 "%1" Untitled @@ -17607,8 +17607,8 @@ Local pulls are not applied to the master branch. 比較 "%1" - ALT+Z,Alt+D - ALT+Z,Alt+D + Alt+Z,Alt+D + Alt+Z,Alt+D Meta+Z,Meta+D @@ -17623,8 +17623,8 @@ Local pulls are not applied to the master branch. "%1" 的紀錄 - ALT+Z,Alt+L - ALT+Z,Alt+L + Alt+Z,Alt+L + Alt+Z,Alt+L Meta+Z,Meta+L @@ -17639,8 +17639,8 @@ Local pulls are not applied to the master branch. "%1" 的狀態 - ALT+Z,Alt+S - ALT+Z,Alt+S + Alt+Z,Alt+S + Alt+Z,Alt+S Meta+Z,Meta+S @@ -17703,8 +17703,8 @@ Local pulls are not applied to the master branch. 提交... - ALT+Z,Alt+C - ALT+Z,Alt+C + Alt+Z,Alt+C + Alt+Z,Alt+C Meta+Z,Meta+C diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b567bbe120e..792b3012f27 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -36,9 +36,6 @@ install(EXPORT QtCreator ) file(WRITE ${CMAKE_BINARY_DIR}/cmake/QtCreatorConfig.cmake " -\# add module path for special FindQt5.cmake that considers Qt6 too -list(APPEND CMAKE_MODULE_PATH \${CMAKE_CURRENT_LIST_DIR}) - \# force plugins to same path naming conventions as Qt Creator \# otherwise plugins will not be found if(UNIX AND NOT APPLE) @@ -50,10 +47,10 @@ if(UNIX AND NOT APPLE) endif() include(CMakeFindDependencyMacro) -find_dependency(Qt5 ${IDE_QT_VERSION_MIN} +find_dependency(Qt6 ${IDE_QT_VERSION_MIN} COMPONENTS Concurrent Core Gui Widgets Core5Compat Network PrintSupport Qml Sql REQUIRED ) -find_dependency(Qt5 COMPONENTS Quick QuickWidgets QUIET) +find_dependency(Qt6 COMPONENTS Quick QuickWidgets QUIET) if (NOT IDE_VERSION) include(\${CMAKE_CURRENT_LIST_DIR}/QtCreatorIDEBranding.cmake) @@ -87,7 +84,6 @@ file(COPY ${PROJECT_SOURCE_DIR}/cmake/QtCreatorDocumentation.cmake ${PROJECT_SOURCE_DIR}/cmake/QtCreatorAPI.cmake ${PROJECT_SOURCE_DIR}/cmake/QtCreatorAPIInternal.cmake - ${PROJECT_SOURCE_DIR}/cmake/FindQt5.cmake ${PROJECT_SOURCE_DIR}/cmake/Config.cmake.in ${PROJECT_SOURCE_DIR}/cmake/QtcSeparateDebugInfo.cmake ${PROJECT_SOURCE_DIR}/cmake/QtcSeparateDebugInfo.Info.plist.in @@ -102,7 +98,6 @@ install( ${PROJECT_SOURCE_DIR}/cmake/QtCreatorDocumentation.cmake ${PROJECT_SOURCE_DIR}/cmake/QtCreatorAPI.cmake ${PROJECT_SOURCE_DIR}/cmake/QtCreatorAPIInternal.cmake - ${PROJECT_SOURCE_DIR}/cmake/FindQt5.cmake ${PROJECT_SOURCE_DIR}/cmake/Config.cmake.in ${PROJECT_SOURCE_DIR}/cmake/QtcSeparateDebugInfo.cmake ${PROJECT_SOURCE_DIR}/cmake/QtcSeparateDebugInfo.Info.plist.in diff --git a/src/app/main.cpp b/src/app/main.cpp index b0ca009b8c9..3a2cb5bfaae 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -22,29 +22,20 @@ #include #include -#include #include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include #include +#include #include #include +#include #include -#include +#include #include +#include +#include +#include #include #include @@ -527,11 +518,6 @@ int main(int argc, char **argv) QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); } - if (Utils::HostOsInfo::isRunningUnderRosetta()) { - // work around QTBUG-97085: QRegularExpression jitting is not reentrant under Rosetta - qputenv("QT_ENABLE_REGEXP_JIT", "0"); - } - if (Utils::HostOsInfo::isLinuxHost() && !qEnvironmentVariableIsSet("GTK_THEME")) // Work around QTCREATORBUG-28497: // Prevent Qt's GTK3 platform theme plugin from enforcing a dark palette @@ -589,11 +575,12 @@ int main(int argc, char **argv) SharedTools::QtSingleApplication::setAttribute(Qt::AA_ShareOpenGLContexts); - int numberofArguments = static_cast(options.appArguments.size()); + int numberOfArguments = static_cast(options.appArguments.size()); - SharedTools::QtSingleApplication app((QLatin1String(Core::Constants::IDE_DISPLAY_NAME)), - numberofArguments, - options.appArguments.data()); + std::unique_ptr + appPtr(SharedTools::createApplication(QLatin1String(Core::Constants::IDE_DISPLAY_NAME), + numberOfArguments, options.appArguments.data())); + SharedTools::QtSingleApplication &app = *appPtr; QCoreApplication::setApplicationName(Core::Constants::IDE_CASED_ID); QCoreApplication::setApplicationVersion(QLatin1String(Core::Constants::IDE_VERSION_LONG)); QCoreApplication::setOrganizationName(QLatin1String(Core::Constants::IDE_SETTINGSVARIANT_STR)); diff --git a/src/libs/3rdparty/CMakeLists.txt b/src/libs/3rdparty/CMakeLists.txt index 7cf97ab87fc..0cf9818ed54 100644 --- a/src/libs/3rdparty/CMakeLists.txt +++ b/src/libs/3rdparty/CMakeLists.txt @@ -1,2 +1,8 @@ add_subdirectory(cplusplus) add_subdirectory(syntax-highlighting) +add_subdirectory(libvterm) +add_subdirectory(libptyqt) + +if(WIN32) + add_subdirectory(winpty) +endif() diff --git a/src/libs/3rdparty/cplusplus/AST.cpp b/src/libs/3rdparty/cplusplus/AST.cpp index 5d7c9e2a90c..5c59bb683ca 100644 --- a/src/libs/3rdparty/cplusplus/AST.cpp +++ b/src/libs/3rdparty/cplusplus/AST.cpp @@ -911,6 +911,8 @@ int DeclaratorAST::lastToken() const return candidate; if (equal_token) return equal_token + 1; + if (requiresClause) + return requiresClause->lastToken(); if (post_attribute_list) if (int candidate = post_attribute_list->lastToken()) return candidate; @@ -1657,12 +1659,18 @@ int LambdaDeclaratorAST::firstToken() const if (trailing_return_type) if (int candidate = trailing_return_type->firstToken()) return candidate; + if (requiresClause) + if (int candidate = requiresClause->firstToken()) + return candidate; return 0; } /** \generated */ int LambdaDeclaratorAST::lastToken() const { + if (requiresClause) + if (int candidate = requiresClause->firstToken()) + return candidate; if (trailing_return_type) if (int candidate = trailing_return_type->lastToken()) return candidate; @@ -1690,6 +1698,15 @@ int LambdaExpressionAST::firstToken() const if (lambda_introducer) if (int candidate = lambda_introducer->firstToken()) return candidate; + if (templateParameters) + if (int candidate = templateParameters->firstToken()) + return candidate; + if (requiresClause) + if (int candidate = requiresClause->firstToken()) + return candidate; + if (attributes) + if (int candidate = attributes->firstToken()) + return candidate; if (lambda_declarator) if (int candidate = lambda_declarator->firstToken()) return candidate; @@ -1708,6 +1725,15 @@ int LambdaExpressionAST::lastToken() const if (lambda_declarator) if (int candidate = lambda_declarator->lastToken()) return candidate; + if (attributes) + if (int candidate = attributes->firstToken()) + return candidate; + if (requiresClause) + if (int candidate = requiresClause->firstToken()) + return candidate; + if (templateParameters) + if (int candidate = templateParameters->firstToken()) + return candidate; if (lambda_introducer) if (int candidate = lambda_introducer->lastToken()) return candidate; @@ -3761,6 +3787,8 @@ int TemplateTypeParameterAST::firstToken() const { if (template_token) return template_token; + if (typeConstraint) + return typeConstraint->firstToken(); if (less_token) return less_token; if (template_parameter_list) @@ -3805,6 +3833,8 @@ int TemplateTypeParameterAST::lastToken() const return candidate; if (less_token) return less_token + 1; + if (typeConstraint) + return typeConstraint->lastToken(); if (template_token) return template_token + 1; return 1; @@ -4634,3 +4664,33 @@ int NoExceptOperatorExpressionAST::lastToken() const return noexcept_token + 1; return 1; } + +int TypeConstraintAST::firstToken() const +{ + if (nestedName) + return nestedName->firstToken(); + return conceptName->firstToken(); +} + +int TypeConstraintAST::lastToken() const +{ + if (greaterToken) + return greaterToken + 1; + return conceptName->lastToken(); +} + +int PlaceholderTypeSpecifierAST::firstToken() const +{ + if (typeConstraint) + return typeConstraint->firstToken(); + if (declTypetoken) + return declTypetoken; + return autoToken; +} + +int PlaceholderTypeSpecifierAST::lastToken() const +{ + if (rparenToken) + return rparenToken + 1; + return autoToken + 1; +} diff --git a/src/libs/3rdparty/cplusplus/AST.h b/src/libs/3rdparty/cplusplus/AST.h index 9aa81ccdf66..3f48e192b93 100644 --- a/src/libs/3rdparty/cplusplus/AST.h +++ b/src/libs/3rdparty/cplusplus/AST.h @@ -184,6 +184,7 @@ public: virtual ArrayInitializerAST *asArrayInitializer() { return nullptr; } virtual AsmDefinitionAST *asAsmDefinition() { return nullptr; } virtual AttributeSpecifierAST *asAttributeSpecifier() { return nullptr; } + virtual AwaitExpressionAST *asAwaitExpression() { return nullptr; } virtual BaseSpecifierAST *asBaseSpecifier() { return nullptr; } virtual BinaryExpressionAST *asBinaryExpression() { return nullptr; } virtual BoolLiteralAST *asBoolLiteral() { return nullptr; } @@ -290,6 +291,7 @@ public: virtual OperatorFunctionIdAST *asOperatorFunctionId() { return nullptr; } virtual ParameterDeclarationAST *asParameterDeclaration() { return nullptr; } virtual ParameterDeclarationClauseAST *asParameterDeclarationClause() { return nullptr; } + virtual PlaceholderTypeSpecifierAST *asPlaceholderTypeSpecifier() { return nullptr; } virtual PointerAST *asPointer() { return nullptr; } virtual PointerLiteralAST *asPointerLiteral() { return nullptr; } virtual PointerToMemberAST *asPointerToMember() { return nullptr; } @@ -322,6 +324,7 @@ public: virtual StringLiteralAST *asStringLiteral() { return nullptr; } virtual SwitchStatementAST *asSwitchStatement() { return nullptr; } virtual TemplateDeclarationAST *asTemplateDeclaration() { return nullptr; } + virtual ConceptDeclarationAST *asConceptDeclaration() { return nullptr; } virtual TemplateIdAST *asTemplateId() { return nullptr; } virtual TemplateTypeParameterAST *asTemplateTypeParameter() { return nullptr; } virtual ThisExpressionAST *asThisExpression() { return nullptr; } @@ -329,6 +332,7 @@ public: virtual TrailingReturnTypeAST *asTrailingReturnType() { return nullptr; } virtual TranslationUnitAST *asTranslationUnit() { return nullptr; } virtual TryBlockStatementAST *asTryBlockStatement() { return nullptr; } + virtual TypeConstraintAST *asTypeConstraint() { return nullptr; } virtual TypeConstructorCallAST *asTypeConstructorCall() { return nullptr; } virtual TypeIdAST *asTypeId() { return nullptr; } virtual TypeidExpressionAST *asTypeidExpression() { return nullptr; } @@ -336,9 +340,12 @@ public: virtual TypenameTypeParameterAST *asTypenameTypeParameter() { return nullptr; } virtual TypeofSpecifierAST *asTypeofSpecifier() { return nullptr; } virtual UnaryExpressionAST *asUnaryExpression() { return nullptr; } + virtual RequiresExpressionAST *asRequiresExpression() { return nullptr; } + virtual RequiresClauseAST *asRequiresClause() { return nullptr; } virtual UsingAST *asUsing() { return nullptr; } virtual UsingDirectiveAST *asUsingDirective() { return nullptr; } virtual WhileStatementAST *asWhileStatement() { return nullptr; } + virtual YieldExpressionAST *asYieldExpression() { return nullptr; } protected: virtual void accept0(ASTVisitor *visitor) = 0; @@ -636,6 +643,49 @@ protected: bool match0(AST *, ASTMatcher *) override; }; +class CPLUSPLUS_EXPORT TypeConstraintAST: public AST +{ +public: + NestedNameSpecifierListAST *nestedName = nullptr; + NameAST *conceptName = nullptr; + int lessToken = 0; + ExpressionListAST *templateArgs = nullptr; + int greaterToken = 0; + + TypeConstraintAST *asTypeConstraint() override { return this; } + + int firstToken() const override; + int lastToken() const override; + + TypeConstraintAST *clone(MemoryPool *pool) const override; + + void accept0(ASTVisitor *visitor) override; + bool match0(AST *, ASTMatcher *) override; +}; + +class CPLUSPLUS_EXPORT PlaceholderTypeSpecifierAST: public SpecifierAST +{ +public: + TypeConstraintAST *typeConstraint = nullptr; + int declTypetoken = 0; + int lparenToken = 0; + int decltypeToken = 0; + int autoToken = 0; + int rparenToken = 0; + +public: + PlaceholderTypeSpecifierAST *asPlaceholderTypeSpecifier() override { return this; } + + int firstToken() const override; + int lastToken() const override; + + PlaceholderTypeSpecifierAST *clone(MemoryPool *pool) const override; + +protected: + void accept0(ASTVisitor *visitor) override; + bool match0(AST *, ASTMatcher *) override; +}; + class CPLUSPLUS_EXPORT DeclaratorAST: public AST { public: @@ -646,6 +696,7 @@ public: SpecifierListAST *post_attribute_list = nullptr; int equal_token = 0; ExpressionAST *initializer = nullptr; + RequiresClauseAST *requiresClause = nullptr; public: DeclaratorAST *asDeclarator() override { return this; } @@ -1728,6 +1779,7 @@ public: int if_token = 0; int constexpr_token = 0; int lparen_token = 0; + StatementAST *initStmt = nullptr; ExpressionAST *condition = nullptr; int rparen_token = 0; StatementAST *statement = nullptr; @@ -2716,6 +2768,7 @@ public: int less_token = 0; DeclarationListAST *template_parameter_list = nullptr; int greater_token = 0; + RequiresClauseAST *requiresClause = nullptr; DeclarationAST *declaration = nullptr; public: // annotations @@ -2734,6 +2787,29 @@ protected: bool match0(AST *, ASTMatcher *) override; }; +class CPLUSPLUS_EXPORT ConceptDeclarationAST: public DeclarationAST +{ +public: + int concept_token = 0; + NameAST *name = nullptr; + SpecifierListAST *attributes = nullptr; + int equals_token = 0; + ExpressionAST *constraint = nullptr; + int semicolon_token = 0; + +public: + ConceptDeclarationAST *asConceptDeclaration() override { return this; } + + int firstToken() const override { return concept_token; } + int lastToken() const override { return semicolon_token + 1; } + + ConceptDeclarationAST *clone(MemoryPool *pool) const override; + +protected: + void accept0(ASTVisitor *visitor) override; + bool match0(AST *, ASTMatcher *) override; +}; + class CPLUSPLUS_EXPORT ThrowExpressionAST: public ExpressionAST { public: @@ -2753,6 +2829,44 @@ protected: bool match0(AST *, ASTMatcher *) override; }; +class CPLUSPLUS_EXPORT YieldExpressionAST: public ExpressionAST +{ +public: + int yield_token = 0; + ExpressionAST *expression = nullptr; + +public: + YieldExpressionAST *asYieldExpression() override { return this; } + + int firstToken() const override { return yield_token; } + int lastToken() const override { return expression->lastToken(); } + + YieldExpressionAST *clone(MemoryPool *pool) const override; + +protected: + void accept0(ASTVisitor *visitor) override; + bool match0(AST *, ASTMatcher *) override; +}; + +class CPLUSPLUS_EXPORT AwaitExpressionAST: public ExpressionAST +{ +public: + int await_token = 0; + ExpressionAST *castExpression = nullptr; + +public: + AwaitExpressionAST *asAwaitExpression() override { return this; } + + int firstToken() const override { return await_token; } + int lastToken() const override { return castExpression->lastToken(); } + + AwaitExpressionAST *clone(MemoryPool *pool) const override; + +protected: + void accept0(ASTVisitor *visitor) override; + bool match0(AST *, ASTMatcher *) override; +}; + class CPLUSPLUS_EXPORT NoExceptOperatorExpressionAST: public ExpressionAST { public: @@ -2883,6 +2997,7 @@ class CPLUSPLUS_EXPORT TemplateTypeParameterAST: public DeclarationAST { public: int template_token = 0; + TypeConstraintAST *typeConstraint = nullptr; int less_token = 0; DeclarationListAST *template_parameter_list = nullptr; int greater_token = 0; @@ -2927,6 +3042,48 @@ protected: bool match0(AST *, ASTMatcher *) override; }; +class CPLUSPLUS_EXPORT RequiresExpressionAST: public ExpressionAST +{ +public: + int requires_token = 0; + int lparen_token = 0; + ParameterDeclarationClauseAST *parameters = nullptr; + int rparen_token = 0; + int lbrace_token = 0; + int rbrace_token = 0; + +public: + RequiresExpressionAST *asRequiresExpression() override { return this; } + + int firstToken() const override { return requires_token; } + int lastToken() const override { return rbrace_token + 1; } + + RequiresExpressionAST *clone(MemoryPool *pool) const override; + +protected: + void accept0(ASTVisitor *visitor) override; + bool match0(AST *, ASTMatcher *) override; +}; + +class CPLUSPLUS_EXPORT RequiresClauseAST: public AST +{ +public: + int requires_token = 0; + ExpressionAST *constraint = nullptr; + +public: + RequiresClauseAST *asRequiresClause() override { return this; } + + int firstToken() const override { return requires_token; } + int lastToken() const override { return constraint->lastToken(); } + + RequiresClauseAST *clone(MemoryPool *pool) const override; + +protected: + void accept0(ASTVisitor *visitor) override; + bool match0(AST *, ASTMatcher *) override; +}; + class CPLUSPLUS_EXPORT UsingAST: public DeclarationAST { public: @@ -3522,6 +3679,9 @@ class LambdaExpressionAST: public ExpressionAST { public: LambdaIntroducerAST *lambda_introducer = nullptr; + DeclarationListAST *templateParameters = nullptr; + RequiresClauseAST *requiresClause = nullptr; + SpecifierListAST *attributes = nullptr; LambdaDeclaratorAST *lambda_declarator = nullptr; StatementAST *statement = nullptr; @@ -3602,6 +3762,7 @@ public: int mutable_token = 0; ExceptionSpecificationAST *exception_specification = nullptr; TrailingReturnTypeAST *trailing_return_type = nullptr; + RequiresClauseAST *requiresClause = nullptr; public: // annotations Function *symbol = nullptr; diff --git a/src/libs/3rdparty/cplusplus/ASTClone.cpp b/src/libs/3rdparty/cplusplus/ASTClone.cpp index 9e5a773bdb6..c9c2fc8294a 100644 --- a/src/libs/3rdparty/cplusplus/ASTClone.cpp +++ b/src/libs/3rdparty/cplusplus/ASTClone.cpp @@ -141,6 +141,34 @@ DecltypeSpecifierAST *DecltypeSpecifierAST::clone(MemoryPool *pool) const return ast; } +TypeConstraintAST *TypeConstraintAST::clone(MemoryPool *pool) const +{ + const auto ast = new (pool) TypeConstraintAST; + for (NestedNameSpecifierListAST *iter = nestedName, **ast_iter = &ast->nestedName; iter; + iter = iter->next, ast_iter = &(*ast_iter)->next) + *ast_iter = new (pool) NestedNameSpecifierListAST((iter->value) ? iter->value->clone(pool) : nullptr); + if (conceptName) + ast->conceptName = conceptName->clone(pool); + ast->lessToken = lessToken; + for (ExpressionListAST *iter = templateArgs, **ast_iter = &ast->templateArgs; + iter; iter = iter->next, ast_iter = &(*ast_iter)->next) + *ast_iter = new (pool) ExpressionListAST((iter->value) ? iter->value->clone(pool) : nullptr); + ast->greaterToken = greaterToken; + return ast; +} + +PlaceholderTypeSpecifierAST *PlaceholderTypeSpecifierAST::clone(MemoryPool *pool) const +{ + const auto ast = new (pool) PlaceholderTypeSpecifierAST; + if (typeConstraint) + ast->typeConstraint = typeConstraint->clone(pool); + ast->lparenToken = lparenToken; + ast->declTypetoken = declTypetoken; + ast->autoToken = autoToken; + ast->rparenToken = rparenToken; + return ast; +} + DeclaratorAST *DeclaratorAST::clone(MemoryPool *pool) const { DeclaratorAST *ast = new (pool) DeclaratorAST; @@ -757,6 +785,8 @@ IfStatementAST *IfStatementAST::clone(MemoryPool *pool) const ast->if_token = if_token; ast->constexpr_token = constexpr_token; ast->lparen_token = lparen_token; + if (initStmt) + ast->initStmt = initStmt->clone(pool); if (condition) ast->condition = condition->clone(pool); ast->rparen_token = rparen_token; @@ -1279,11 +1309,50 @@ TemplateDeclarationAST *TemplateDeclarationAST::clone(MemoryPool *pool) const iter; iter = iter->next, ast_iter = &(*ast_iter)->next) *ast_iter = new (pool) DeclarationListAST((iter->value) ? iter->value->clone(pool) : nullptr); ast->greater_token = greater_token; + if (requiresClause) + ast->requiresClause = requiresClause->clone(pool); if (declaration) ast->declaration = declaration->clone(pool); return ast; } +ConceptDeclarationAST *ConceptDeclarationAST::clone(MemoryPool *pool) const +{ + const auto ast = new (pool) ConceptDeclarationAST; + ast->concept_token = concept_token; + ast->name = name->clone(pool); + ast->equals_token = equals_token; + ast->semicolon_token = semicolon_token; + for (SpecifierListAST *iter = attributes, **ast_iter = &ast->attributes; + iter; iter = iter->next, ast_iter = &(*ast_iter)->next) { + *ast_iter = new (pool) SpecifierListAST((iter->value) ? iter->value->clone(pool) : nullptr); + } + ast->constraint = constraint->clone(pool); + return ast; +} + +RequiresExpressionAST *RequiresExpressionAST::clone(MemoryPool *pool) const +{ + const auto ast = new (pool) RequiresExpressionAST; + ast->requires_token = requires_token; + ast->lparen_token = lparen_token; + if (parameters) + ast->parameters = parameters->clone(pool); + ast->rparen_token = rparen_token; + ast->lbrace_token = lbrace_token; + ast->rbrace_token = rbrace_token; + return ast; +} + +RequiresClauseAST *RequiresClauseAST::clone(MemoryPool *pool) const +{ + const auto ast = new (pool) RequiresClauseAST; + ast->requires_token = requires_token; + if (constraint) + ast->constraint = constraint->clone(pool); + return ast; +} + ThrowExpressionAST *ThrowExpressionAST::clone(MemoryPool *pool) const { ThrowExpressionAST *ast = new (pool) ThrowExpressionAST; @@ -1293,6 +1362,24 @@ ThrowExpressionAST *ThrowExpressionAST::clone(MemoryPool *pool) const return ast; } +YieldExpressionAST *YieldExpressionAST::clone(MemoryPool *pool) const +{ + const auto ast = new (pool) YieldExpressionAST; + ast->yield_token = yield_token; + if (expression) + ast->expression = expression->clone(pool); + return ast; +} + +AwaitExpressionAST *AwaitExpressionAST::clone(MemoryPool *pool) const +{ + const auto ast = new (pool) AwaitExpressionAST; + ast->await_token = await_token; + if (castExpression) + ast->castExpression = castExpression->clone(pool); + return ast; +} + NoExceptOperatorExpressionAST *NoExceptOperatorExpressionAST::clone(MemoryPool *pool) const { NoExceptOperatorExpressionAST *ast = new (pool) NoExceptOperatorExpressionAST; @@ -1364,6 +1451,8 @@ TemplateTypeParameterAST *TemplateTypeParameterAST::clone(MemoryPool *pool) cons { TemplateTypeParameterAST *ast = new (pool) TemplateTypeParameterAST; ast->template_token = template_token; + if (typeConstraint) + ast->typeConstraint = typeConstraint->clone(pool); ast->less_token = less_token; for (DeclarationListAST *iter = template_parameter_list, **ast_iter = &ast->template_parameter_list; iter; iter = iter->next, ast_iter = &(*ast_iter)->next) @@ -1729,6 +1818,14 @@ LambdaExpressionAST *LambdaExpressionAST::clone(MemoryPool *pool) const LambdaExpressionAST *ast = new (pool) LambdaExpressionAST; if (lambda_introducer) ast->lambda_introducer = lambda_introducer->clone(pool); + for (DeclarationListAST *iter = templateParameters, **ast_iter = &ast->templateParameters; + iter; iter = iter->next, ast_iter = &(*ast_iter)->next) + *ast_iter = new (pool) DeclarationListAST((iter->value) ? iter->value->clone(pool) : nullptr); + if (requiresClause) + ast->requiresClause = requiresClause->clone(pool); + for (SpecifierListAST *iter = attributes, **ast_iter = &ast->attributes; + iter; iter = iter->next, ast_iter = &(*ast_iter)->next) + *ast_iter = new (pool) SpecifierListAST((iter->value) ? iter->value->clone(pool) : nullptr); if (lambda_declarator) ast->lambda_declarator = lambda_declarator->clone(pool); if (statement) @@ -1780,6 +1877,8 @@ LambdaDeclaratorAST *LambdaDeclaratorAST::clone(MemoryPool *pool) const ast->exception_specification = exception_specification->clone(pool); if (trailing_return_type) ast->trailing_return_type = trailing_return_type->clone(pool); + if (requiresClause) + ast->requiresClause = requiresClause->clone(pool); return ast; } diff --git a/src/libs/3rdparty/cplusplus/ASTMatch0.cpp b/src/libs/3rdparty/cplusplus/ASTMatch0.cpp index 4105ec3c52e..b58bd59efe0 100644 --- a/src/libs/3rdparty/cplusplus/ASTMatch0.cpp +++ b/src/libs/3rdparty/cplusplus/ASTMatch0.cpp @@ -112,6 +112,21 @@ bool DecltypeSpecifierAST::match0(AST *pattern, ASTMatcher *matcher) return false; } +bool TypeConstraintAST::match0(AST *pattern, ASTMatcher *matcher) +{ + if (const auto _other = pattern->asTypeConstraint()) + return matcher->match(this, _other); + + return false; +} + +bool PlaceholderTypeSpecifierAST::match0(AST *pattern, ASTMatcher *matcher) +{ + if (const auto _other = pattern->asPlaceholderTypeSpecifier()) + return matcher->match(this, _other); + return false; +} + bool DeclaratorAST::match0(AST *pattern, ASTMatcher *matcher) { if (DeclaratorAST *_other = pattern->asDeclarator()) @@ -896,6 +911,28 @@ bool TemplateDeclarationAST::match0(AST *pattern, ASTMatcher *matcher) return false; } +bool ConceptDeclarationAST::match0(AST *pattern, ASTMatcher *matcher) +{ + if (ConceptDeclarationAST *_other = pattern->asConceptDeclaration()) + return matcher->match(this, _other); + + return false; +} + +bool RequiresExpressionAST::match0(AST *pattern, ASTMatcher *matcher) +{ + if (const auto other = pattern->asRequiresExpression()) + return matcher->match(this, other); + return false; +} + +bool RequiresClauseAST::match0(AST *pattern, ASTMatcher *matcher) +{ + if (const auto other = pattern->asRequiresClause()) + return matcher->match(this, other); + return false; +} + bool ThrowExpressionAST::match0(AST *pattern, ASTMatcher *matcher) { if (ThrowExpressionAST *_other = pattern->asThrowExpression()) @@ -904,6 +941,20 @@ bool ThrowExpressionAST::match0(AST *pattern, ASTMatcher *matcher) return false; } +bool YieldExpressionAST::match0(AST *pattern, ASTMatcher *matcher) +{ + if (const auto other = pattern->asYieldExpression()) + return matcher->match(this, other); + return false; +} + +bool AwaitExpressionAST::match0(AST *pattern, ASTMatcher *matcher) +{ + if (const auto other = pattern->asAwaitExpression()) + return matcher->match(this, other); + return false; +} + bool NoExceptOperatorExpressionAST::match0(AST *pattern, ASTMatcher *matcher) { if (NoExceptOperatorExpressionAST *_other = pattern->asNoExceptOperatorExpression()) diff --git a/src/libs/3rdparty/cplusplus/ASTMatcher.cpp b/src/libs/3rdparty/cplusplus/ASTMatcher.cpp index fcfe86ab571..b264098a969 100644 --- a/src/libs/3rdparty/cplusplus/ASTMatcher.cpp +++ b/src/libs/3rdparty/cplusplus/ASTMatcher.cpp @@ -216,6 +216,25 @@ bool ASTMatcher::match(DecltypeSpecifierAST *node, DecltypeSpecifierAST *pattern return true; } +bool ASTMatcher::match(TypeConstraintAST *node, TypeConstraintAST *pattern) +{ + if (!pattern->nestedName) + pattern->nestedName = node->nestedName; + else if (!AST::match(node->nestedName, pattern->nestedName, this)) + return false; + if (!pattern->conceptName) + pattern->conceptName = node->conceptName; + else if (!AST::match(node->conceptName, pattern->conceptName, this)) + return false; + pattern->lessToken = node->lessToken; + if (!pattern->templateArgs) + pattern->templateArgs = node->templateArgs; + else if (!AST::match(node->templateArgs, pattern->templateArgs, this)) + return false; + pattern->greaterToken = node->greaterToken; + return true; +} + bool ASTMatcher::match(DeclaratorAST *node, DeclaratorAST *pattern) { (void) node; @@ -1299,6 +1318,11 @@ bool ASTMatcher::match(IfStatementAST *node, IfStatementAST *pattern) pattern->lparen_token = node->lparen_token; + if (!pattern->initStmt) + pattern->initStmt = node->initStmt; + else if (!AST::match(node->initStmt, pattern->initStmt, this)) + return false; + if (! pattern->condition) pattern->condition = node->condition; else if (! AST::match(node->condition, pattern->condition, this)) @@ -1749,6 +1773,19 @@ bool ASTMatcher::match(ParameterDeclarationClauseAST *node, ParameterDeclaration return true; } +bool ASTMatcher::match(PlaceholderTypeSpecifierAST *node, PlaceholderTypeSpecifierAST *pattern) +{ + if (!pattern->typeConstraint) + pattern->typeConstraint = node->typeConstraint; + else if (!AST::match(node->typeConstraint, pattern->typeConstraint, this)) + return false; + pattern->declTypetoken = node->declTypetoken; + pattern->lparenToken = node->lparenToken; + pattern->autoToken = node->autoToken; + pattern->rparenToken = node->rparenToken; + return true; +} + bool ASTMatcher::match(CallAST *node, CallAST *pattern) { (void) node; @@ -2173,6 +2210,11 @@ bool ASTMatcher::match(TemplateDeclarationAST *node, TemplateDeclarationAST *pat pattern->greater_token = node->greater_token; + if (! pattern->requiresClause) + pattern->requiresClause = node->requiresClause; + else if (! AST::match(node->requiresClause, pattern->requiresClause, this)) + return false; + if (! pattern->declaration) pattern->declaration = node->declaration; else if (! AST::match(node->declaration, pattern->declaration, this)) @@ -2181,6 +2223,50 @@ bool ASTMatcher::match(TemplateDeclarationAST *node, TemplateDeclarationAST *pat return true; } +bool ASTMatcher::match(ConceptDeclarationAST *node, ConceptDeclarationAST *pattern) +{ + pattern->concept_token = node->concept_token; + pattern->equals_token = node->equals_token; + pattern->semicolon_token = node->semicolon_token; + + if (!pattern->attributes) + pattern->attributes = node->attributes; + else if (!AST::match(node->attributes, pattern->attributes, this)) + return false; + + if (!pattern->constraint) + pattern->constraint = node->constraint; + else if (! AST::match(node->constraint, pattern->constraint, this)) + return false; + + return true; +} + +bool ASTMatcher::match(RequiresExpressionAST *node, RequiresExpressionAST *pattern) +{ + pattern->requires_token = node->requires_token; + pattern->lparen_token = node->lparen_token; + pattern->lbrace_token = node->lbrace_token; + pattern->rbrace_token = node->rbrace_token; + + if (!pattern->parameters) + pattern->parameters = node->parameters; + else if (!AST::match(node->parameters, pattern->parameters, this)) + return false; + + return true; +} + +bool ASTMatcher::match(RequiresClauseAST *node, RequiresClauseAST *pattern) +{ + pattern->requires_token = node->requires_token; + if (!pattern->constraint) + pattern->constraint = node->constraint; + else if (!AST::match(node->constraint, pattern->constraint, this)) + return false; + return true; +} + bool ASTMatcher::match(ThrowExpressionAST *node, ThrowExpressionAST *pattern) { (void) node; @@ -2196,6 +2282,26 @@ bool ASTMatcher::match(ThrowExpressionAST *node, ThrowExpressionAST *pattern) return true; } +bool ASTMatcher::match(YieldExpressionAST *node, YieldExpressionAST *pattern) +{ + pattern->yield_token = node->yield_token; + if (!pattern->expression) + pattern->expression = node->expression; + else if (!AST::match(node->expression, pattern->expression, this)) + return false; + return true; +} + +bool ASTMatcher::match(AwaitExpressionAST *node, AwaitExpressionAST *pattern) +{ + pattern->await_token = node->await_token; + if (!pattern->castExpression) + pattern->castExpression = node->castExpression; + else if (!AST::match(node->castExpression, pattern->castExpression, this)) + return false; + return true; +} + bool ASTMatcher::match(NoExceptOperatorExpressionAST *node, NoExceptOperatorExpressionAST *pattern) { (void) node; @@ -2317,6 +2423,11 @@ bool ASTMatcher::match(TemplateTypeParameterAST *node, TemplateTypeParameterAST pattern->template_token = node->template_token; + if (!pattern->typeConstraint) + pattern->typeConstraint = node->typeConstraint; + else if (!AST::match(node->typeConstraint, pattern->typeConstraint, this)) + return false; + pattern->less_token = node->less_token; if (! pattern->template_parameter_list) @@ -2950,6 +3061,21 @@ bool ASTMatcher::match(LambdaExpressionAST *node, LambdaExpressionAST *pattern) else if (! AST::match(node->lambda_introducer, pattern->lambda_introducer, this)) return false; + if (! pattern->templateParameters) + pattern->templateParameters = node->templateParameters; + else if (! AST::match(node->templateParameters, pattern->templateParameters, this)) + return false; + + if (! pattern->requiresClause) + pattern->requiresClause = node->requiresClause; + else if (! AST::match(node->requiresClause, pattern->requiresClause, this)) + return false; + + if (! pattern->attributes) + pattern->attributes = node->attributes; + else if (! AST::match(node->attributes, pattern->attributes, this)) + return false; + if (! pattern->lambda_declarator) pattern->lambda_declarator = node->lambda_declarator; else if (! AST::match(node->lambda_declarator, pattern->lambda_declarator, this)) @@ -3041,6 +3167,11 @@ bool ASTMatcher::match(LambdaDeclaratorAST *node, LambdaDeclaratorAST *pattern) else if (! AST::match(node->trailing_return_type, pattern->trailing_return_type, this)) return false; + if (! pattern->requiresClause) + pattern->requiresClause = node->requiresClause; + else if (! AST::match(node->requiresClause, pattern->requiresClause, this)) + return false; + return true; } diff --git a/src/libs/3rdparty/cplusplus/ASTMatcher.h b/src/libs/3rdparty/cplusplus/ASTMatcher.h index c243e18b7bb..fb6109a07dc 100644 --- a/src/libs/3rdparty/cplusplus/ASTMatcher.h +++ b/src/libs/3rdparty/cplusplus/ASTMatcher.h @@ -38,6 +38,7 @@ public: virtual bool match(ArrayAccessAST *node, ArrayAccessAST *pattern); virtual bool match(ArrayDeclaratorAST *node, ArrayDeclaratorAST *pattern); virtual bool match(ArrayInitializerAST *node, ArrayInitializerAST *pattern); + virtual bool match(AwaitExpressionAST *node, AwaitExpressionAST *pattern); virtual bool match(AsmDefinitionAST *node, AsmDefinitionAST *pattern); virtual bool match(BaseSpecifierAST *node, BaseSpecifierAST *pattern); virtual bool match(BinaryExpressionAST *node, BinaryExpressionAST *pattern); @@ -54,6 +55,7 @@ public: virtual bool match(CompoundExpressionAST *node, CompoundExpressionAST *pattern); virtual bool match(CompoundLiteralAST *node, CompoundLiteralAST *pattern); virtual bool match(CompoundStatementAST *node, CompoundStatementAST *pattern); + virtual bool match(ConceptDeclarationAST *node, ConceptDeclarationAST *pattern); virtual bool match(ConditionAST *node, ConditionAST *pattern); virtual bool match(ConditionalExpressionAST *node, ConditionalExpressionAST *pattern); virtual bool match(ContinueStatementAST *node, ContinueStatementAST *pattern); @@ -139,6 +141,7 @@ public: virtual bool match(OperatorFunctionIdAST *node, OperatorFunctionIdAST *pattern); virtual bool match(ParameterDeclarationAST *node, ParameterDeclarationAST *pattern); virtual bool match(ParameterDeclarationClauseAST *node, ParameterDeclarationClauseAST *pattern); + virtual bool match(PlaceholderTypeSpecifierAST *node, PlaceholderTypeSpecifierAST *pattern); virtual bool match(PointerAST *node, PointerAST *pattern); virtual bool match(PointerLiteralAST *node, PointerLiteralAST *pattern); virtual bool match(PointerToMemberAST *node, PointerToMemberAST *pattern); @@ -156,6 +159,8 @@ public: virtual bool match(QualifiedNameAST *node, QualifiedNameAST *pattern); virtual bool match(RangeBasedForStatementAST *node, RangeBasedForStatementAST *pattern); virtual bool match(ReferenceAST *node, ReferenceAST *pattern); + virtual bool match(RequiresClauseAST *node, RequiresClauseAST *pattern); + virtual bool match(RequiresExpressionAST *node, RequiresExpressionAST *pattern); virtual bool match(ReturnStatementAST *node, ReturnStatementAST *pattern); virtual bool match(SimpleDeclarationAST *node, SimpleDeclarationAST *pattern); virtual bool match(SimpleNameAST *node, SimpleNameAST *pattern); @@ -173,6 +178,7 @@ public: virtual bool match(TrailingReturnTypeAST *node, TrailingReturnTypeAST *pattern); virtual bool match(TranslationUnitAST *node, TranslationUnitAST *pattern); virtual bool match(TryBlockStatementAST *node, TryBlockStatementAST *pattern); + virtual bool match(TypeConstraintAST *node, TypeConstraintAST *pattern); virtual bool match(TypeConstructorCallAST *node, TypeConstructorCallAST *pattern); virtual bool match(TypeIdAST *node, TypeIdAST *pattern); virtual bool match(TypeidExpressionAST *node, TypeidExpressionAST *pattern); @@ -183,6 +189,7 @@ public: virtual bool match(UsingAST *node, UsingAST *pattern); virtual bool match(UsingDirectiveAST *node, UsingDirectiveAST *pattern); virtual bool match(WhileStatementAST *node, WhileStatementAST *pattern); + virtual bool match(YieldExpressionAST *node, YieldExpressionAST *pattern); }; } // namespace CPlusPlus diff --git a/src/libs/3rdparty/cplusplus/ASTVisit.cpp b/src/libs/3rdparty/cplusplus/ASTVisit.cpp index 2248f51a501..5b0ef3ce330 100644 --- a/src/libs/3rdparty/cplusplus/ASTVisit.cpp +++ b/src/libs/3rdparty/cplusplus/ASTVisit.cpp @@ -110,6 +110,23 @@ void DecltypeSpecifierAST::accept0(ASTVisitor *visitor) visitor->endVisit(this); } +void TypeConstraintAST::accept0(ASTVisitor *visitor) +{ + if (visitor->visit(this)) { + accept(nestedName, visitor); + accept(conceptName, visitor); + accept(templateArgs, visitor); + } + visitor->endVisit(this); +} + +void PlaceholderTypeSpecifierAST::accept0(ASTVisitor *visitor) +{ + if (visitor->visit(this)) + accept(typeConstraint, visitor); + visitor->endVisit(this); +} + void DeclaratorAST::accept0(ASTVisitor *visitor) { if (visitor->visit(this)) { @@ -546,6 +563,7 @@ void ForStatementAST::accept0(ASTVisitor *visitor) void IfStatementAST::accept0(ASTVisitor *visitor) { if (visitor->visit(this)) { + accept(initStmt, visitor); accept(condition, visitor); accept(statement, visitor); accept(else_statement, visitor); @@ -941,11 +959,36 @@ void TemplateDeclarationAST::accept0(ASTVisitor *visitor) { if (visitor->visit(this)) { accept(template_parameter_list, visitor); + accept(requiresClause, visitor); accept(declaration, visitor); } visitor->endVisit(this); } +void ConceptDeclarationAST::accept0(ASTVisitor *visitor) +{ + if (visitor->visit(this)) { + accept(name, visitor); + accept(attributes, visitor); + accept(constraint, visitor); + } + visitor->endVisit(this); +} + +void RequiresExpressionAST::accept0(ASTVisitor *visitor) +{ + if (visitor->visit(this)) + accept(parameters, visitor); + visitor->endVisit(this); +} + +void RequiresClauseAST::accept0(ASTVisitor *visitor) +{ + if (visitor->visit(this)) + accept(constraint, visitor); + visitor->endVisit(this); +} + void ThrowExpressionAST::accept0(ASTVisitor *visitor) { if (visitor->visit(this)) { @@ -954,6 +997,20 @@ void ThrowExpressionAST::accept0(ASTVisitor *visitor) visitor->endVisit(this); } +void YieldExpressionAST::accept0(ASTVisitor *visitor) +{ + if (visitor->visit(this)) + accept(expression, visitor); + visitor->endVisit(this); +} + +void AwaitExpressionAST::accept0(ASTVisitor *visitor) +{ + if (visitor->visit(this)) + accept(castExpression, visitor); + visitor->endVisit(this); +} + void NoExceptOperatorExpressionAST::accept0(ASTVisitor *visitor) { if (visitor->visit(this)) { @@ -1009,6 +1066,7 @@ void TypenameTypeParameterAST::accept0(ASTVisitor *visitor) void TemplateTypeParameterAST::accept0(ASTVisitor *visitor) { if (visitor->visit(this)) { + accept(typeConstraint, visitor); accept(template_parameter_list, visitor); accept(name, visitor); accept(type_id, visitor); @@ -1260,6 +1318,9 @@ void LambdaExpressionAST::accept0(ASTVisitor *visitor) { if (visitor->visit(this)) { accept(lambda_introducer, visitor); + accept(templateParameters, visitor); + accept(requiresClause, visitor); + accept(attributes, visitor); accept(lambda_declarator, visitor); accept(statement, visitor); } @@ -1297,6 +1358,7 @@ void LambdaDeclaratorAST::accept0(ASTVisitor *visitor) accept(attributes, visitor); accept(exception_specification, visitor); accept(trailing_return_type, visitor); + accept(requiresClause, visitor); } visitor->endVisit(this); } diff --git a/src/libs/3rdparty/cplusplus/ASTVisitor.h b/src/libs/3rdparty/cplusplus/ASTVisitor.h index 691b57e9ac5..9d9fec75b1d 100644 --- a/src/libs/3rdparty/cplusplus/ASTVisitor.h +++ b/src/libs/3rdparty/cplusplus/ASTVisitor.h @@ -81,6 +81,7 @@ public: virtual bool visit(ArrayDeclaratorAST *) { return true; } virtual bool visit(ArrayInitializerAST *) { return true; } virtual bool visit(AsmDefinitionAST *) { return true; } + virtual bool visit(AwaitExpressionAST *) { return true; } virtual bool visit(BaseSpecifierAST *) { return true; } virtual bool visit(BinaryExpressionAST *) { return true; } virtual bool visit(BoolLiteralAST *) { return true; } @@ -96,6 +97,7 @@ public: virtual bool visit(CompoundExpressionAST *) { return true; } virtual bool visit(CompoundLiteralAST *) { return true; } virtual bool visit(CompoundStatementAST *) { return true; } + virtual bool visit(ConceptDeclarationAST *) { return true; } virtual bool visit(ConditionAST *) { return true; } virtual bool visit(ConditionalExpressionAST *) { return true; } virtual bool visit(ContinueStatementAST *) { return true; } @@ -181,6 +183,7 @@ public: virtual bool visit(OperatorFunctionIdAST *) { return true; } virtual bool visit(ParameterDeclarationAST *) { return true; } virtual bool visit(ParameterDeclarationClauseAST *) { return true; } + virtual bool visit(PlaceholderTypeSpecifierAST *) { return true; } virtual bool visit(PointerAST *) { return true; } virtual bool visit(PointerLiteralAST *) { return true; } virtual bool visit(PointerToMemberAST *) { return true; } @@ -198,6 +201,8 @@ public: virtual bool visit(QualifiedNameAST *) { return true; } virtual bool visit(RangeBasedForStatementAST *) { return true; } virtual bool visit(ReferenceAST *) { return true; } + virtual bool visit(RequiresExpressionAST *) { return true; } + virtual bool visit(RequiresClauseAST *) { return true; } virtual bool visit(ReturnStatementAST *) { return true; } virtual bool visit(SimpleDeclarationAST *) { return true; } virtual bool visit(SimpleNameAST *) { return true; } @@ -215,6 +220,7 @@ public: virtual bool visit(TrailingReturnTypeAST *) { return true; } virtual bool visit(TranslationUnitAST *) { return true; } virtual bool visit(TryBlockStatementAST *) { return true; } + virtual bool visit(TypeConstraintAST *) { return true; } virtual bool visit(TypeConstructorCallAST *) { return true; } virtual bool visit(TypeIdAST *) { return true; } virtual bool visit(TypeidExpressionAST *) { return true; } @@ -225,6 +231,7 @@ public: virtual bool visit(UsingAST *) { return true; } virtual bool visit(UsingDirectiveAST *) { return true; } virtual bool visit(WhileStatementAST *) { return true; } + virtual bool visit(YieldExpressionAST *) { return true; } virtual void endVisit(AccessDeclarationAST *) {} virtual void endVisit(AliasDeclarationAST *) {} @@ -235,6 +242,7 @@ public: virtual void endVisit(ArrayDeclaratorAST *) {} virtual void endVisit(ArrayInitializerAST *) {} virtual void endVisit(AsmDefinitionAST *) {} + virtual void endVisit(AwaitExpressionAST *) {} virtual void endVisit(BaseSpecifierAST *) {} virtual void endVisit(BinaryExpressionAST *) {} virtual void endVisit(BoolLiteralAST *) {} @@ -250,6 +258,7 @@ public: virtual void endVisit(CompoundExpressionAST *) {} virtual void endVisit(CompoundLiteralAST *) {} virtual void endVisit(CompoundStatementAST *) {} + virtual void endVisit(ConceptDeclarationAST *) {} virtual void endVisit(ConditionAST *) {} virtual void endVisit(ConditionalExpressionAST *) {} virtual void endVisit(ContinueStatementAST *) {} @@ -335,6 +344,7 @@ public: virtual void endVisit(OperatorFunctionIdAST *) {} virtual void endVisit(ParameterDeclarationAST *) {} virtual void endVisit(ParameterDeclarationClauseAST *) {} + virtual void endVisit(PlaceholderTypeSpecifierAST *) {} virtual void endVisit(PointerAST *) {} virtual void endVisit(PointerLiteralAST *) {} virtual void endVisit(PointerToMemberAST *) {} @@ -352,6 +362,8 @@ public: virtual void endVisit(QualifiedNameAST *) {} virtual void endVisit(RangeBasedForStatementAST *) {} virtual void endVisit(ReferenceAST *) {} + virtual void endVisit(RequiresExpressionAST *) {} + virtual void endVisit(RequiresClauseAST *) {} virtual void endVisit(ReturnStatementAST *) {} virtual void endVisit(SimpleDeclarationAST *) {} virtual void endVisit(SimpleNameAST *) {} @@ -369,6 +381,7 @@ public: virtual void endVisit(TrailingReturnTypeAST *) {} virtual void endVisit(TranslationUnitAST *) {} virtual void endVisit(TryBlockStatementAST *) {} + virtual void endVisit(TypeConstraintAST *) {} virtual void endVisit(TypeConstructorCallAST *) {} virtual void endVisit(TypeIdAST *) {} virtual void endVisit(TypeidExpressionAST *) {} @@ -379,6 +392,7 @@ public: virtual void endVisit(UsingAST *) {} virtual void endVisit(UsingDirectiveAST *) {} virtual void endVisit(WhileStatementAST *) {} + virtual void endVisit(YieldExpressionAST *) {} private: TranslationUnit *_translationUnit; diff --git a/src/libs/3rdparty/cplusplus/ASTfwd.h b/src/libs/3rdparty/cplusplus/ASTfwd.h index 4b31aaf5902..102fda75fb5 100644 --- a/src/libs/3rdparty/cplusplus/ASTfwd.h +++ b/src/libs/3rdparty/cplusplus/ASTfwd.h @@ -40,6 +40,7 @@ class ArrayDeclaratorAST; class ArrayInitializerAST; class AsmDefinitionAST; class AttributeSpecifierAST; +class AwaitExpressionAST; class BaseSpecifierAST; class BinaryExpressionAST; class BoolLiteralAST; @@ -146,6 +147,7 @@ class OperatorAST; class OperatorFunctionIdAST; class ParameterDeclarationAST; class ParameterDeclarationClauseAST; +class PlaceholderTypeSpecifierAST; class PointerAST; class PointerLiteralAST; class PointerToMemberAST; @@ -166,6 +168,8 @@ class QtPropertyDeclarationItemAST; class QualifiedNameAST; class RangeBasedForStatementAST; class ReferenceAST; +class RequiresClauseAST; +class RequiresExpressionAST; class ReturnStatementAST; class SimpleDeclarationAST; class SimpleNameAST; @@ -178,6 +182,7 @@ class StdAttributeSpecifierAST; class StringLiteralAST; class SwitchStatementAST; class TemplateDeclarationAST; +class ConceptDeclarationAST; class TemplateIdAST; class TemplateTypeParameterAST; class ThisExpressionAST; @@ -185,6 +190,7 @@ class ThrowExpressionAST; class TrailingReturnTypeAST; class TranslationUnitAST; class TryBlockStatementAST; +class TypeConstraintAST; class TypeConstructorCallAST; class TypeIdAST; class TypeidExpressionAST; @@ -195,6 +201,7 @@ class UnaryExpressionAST; class UsingAST; class UsingDirectiveAST; class WhileStatementAST; +class YieldExpressionAST; typedef List ExpressionListAST; typedef List DeclarationListAST; diff --git a/src/libs/3rdparty/cplusplus/Bind.cpp b/src/libs/3rdparty/cplusplus/Bind.cpp index 7f10c79428c..c85d401c49b 100644 --- a/src/libs/3rdparty/cplusplus/Bind.cpp +++ b/src/libs/3rdparty/cplusplus/Bind.cpp @@ -933,6 +933,11 @@ bool Bind::visit(ParameterDeclarationClauseAST *ast) return false; } +bool Bind::visit(RequiresExpressionAST *) +{ + return false; +} + void Bind::parameterDeclarationClause(ParameterDeclarationClauseAST *ast, int lparen_token, Function *fun) { if (! ast) @@ -1523,6 +1528,8 @@ bool Bind::visit(IfStatementAST *ast) ast->symbol = block; Scope *previousScope = switchScope(block); + if (ast->initStmt) + this->statement(ast->initStmt); /*ExpressionTy condition =*/ this->expression(ast->condition); this->statement(ast->statement); this->statement(ast->else_statement); @@ -2527,6 +2534,11 @@ bool Bind::visit(TemplateTypeParameterAST *ast) return false; } +bool Bind::visit(TypeConstraintAST *) +{ + return false; +} + bool Bind::visit(UsingAST *ast) { int sourceLocation = location(ast->name, ast->firstToken()); diff --git a/src/libs/3rdparty/cplusplus/Bind.h b/src/libs/3rdparty/cplusplus/Bind.h index 3947e57026d..867ed7baaa8 100644 --- a/src/libs/3rdparty/cplusplus/Bind.h +++ b/src/libs/3rdparty/cplusplus/Bind.h @@ -128,6 +128,7 @@ protected: bool visit(NewTypeIdAST *ast) override; bool visit(OperatorAST *ast) override; bool visit(ParameterDeclarationClauseAST *ast) override; + bool visit(RequiresExpressionAST *ast) override; bool visit(TranslationUnitAST *ast) override; bool visit(ObjCProtocolRefsAST *ast) override; bool visit(ObjCMessageArgumentAST *ast) override; @@ -229,6 +230,7 @@ protected: bool visit(TemplateDeclarationAST *ast) override; bool visit(TypenameTypeParameterAST *ast) override; bool visit(TemplateTypeParameterAST *ast) override; + bool visit(TypeConstraintAST *ast) override; bool visit(UsingAST *ast) override; bool visit(UsingDirectiveAST *ast) override; bool visit(ObjCClassForwardDeclarationAST *ast) override; diff --git a/src/libs/3rdparty/cplusplus/CMakeLists.txt b/src/libs/3rdparty/cplusplus/CMakeLists.txt index 2758ffe6eeb..d9f130b470a 100644 --- a/src/libs/3rdparty/cplusplus/CMakeLists.txt +++ b/src/libs/3rdparty/cplusplus/CMakeLists.txt @@ -41,7 +41,6 @@ add_qtc_library(3rd_cplusplus OBJECT TypeVisitor.cpp TypeVisitor.h cppassert.h SKIP_PCH - PROPERTIES POSITION_INDEPENDENT_CODE ON ) set(export_symbol_declaration DEFINES CPLUSPLUS_BUILD_LIB) diff --git a/src/libs/3rdparty/cplusplus/Keywords.cpp b/src/libs/3rdparty/cplusplus/Keywords.cpp index 1637333b420..a60ec24aec9 100644 --- a/src/libs/3rdparty/cplusplus/Keywords.cpp +++ b/src/libs/3rdparty/cplusplus/Keywords.cpp @@ -603,6 +603,34 @@ static inline int classify7(const char *s, LanguageFeatures features) } } } + else if (features.cxx20Enabled && s[0] == 'c') { + if (s[1] == 'h') { + if (s[2] == 'a') { + if (s[3] == 'r') { + if (s[4] == '8') { + if (s[5] == '_') { + if (s[6] == 't') { + return T_CHAR8_T; + } + } + } + } + } + } + else if (s[1] == 'o') { + if (s[2] == 'n') { + if (s[3] == 'c') { + if (s[4] == 'e') { + if (s[5] == 'p') { + if (s[6] == 't') { + return T_CONCEPT; + } + } + } + } + } + } + } else if (s[0] == 'd') { if (s[1] == 'e') { if (s[2] == 'f') { @@ -847,7 +875,31 @@ static inline int classify8(const char *s, LanguageFeatures features) } } else if (s[1] == 'o') { - if (s[2] == 'n') { + if (features.cxx20Enabled && s[2] == '_') { + if (s[3] == 'a') { + if (s[4] == 'w') { + if (s[5] == 'a') { + if (s[6] == 'i') { + if (s[7] == 't') { + return T_CO_AWAIT; + } + } + } + } + } + else if (s[3] == 'y') { + if (s[4] == 'i') { + if (s[5] == 'e') { + if (s[6] == 'l') { + if (s[7] == 'd') { + return T_CO_YIELD; + } + } + } + } + } + } + else if (s[2] == 'n') { if (s[3] == 't') { if (s[4] == 'i') { if (s[5] == 'n') { @@ -945,6 +997,19 @@ static inline int classify8(const char *s, LanguageFeatures features) } } } + else if (features.cxx20Enabled && s[2] == 'q') { + if (s[3] == 'u') { + if (s[4] == 'i') { + if (s[5] == 'r') { + if (s[6] == 'e') { + if (s[7] == 's') { + return T_REQUIRES; + } + } + } + } + } + } } } else if (features.cxxEnabled && s[0] == 't') { @@ -1097,13 +1162,35 @@ static inline int classify9(const char *s, LanguageFeatures features) } } } - else if (features.cxx11Enabled && s[0] == 'c') { + else if (s[0] == 'c') { if (s[1] == 'o') { - if (s[2] == 'n') { + if (features.cxx20Enabled && s[2] == '_') { + if (s[3] == 'r') { + if (s[4] == 'e') { + if (s[5] == 't') { + if (s[6] == 'u') { + if (s[7] == 'r') { + if (s[8] == 'n') { + return T_CO_RETURN; + } + } + } + } + } + } + } + else if (s[2] == 'n') { if (s[3] == 's') { if (s[4] == 't') { if (s[5] == 'e') { - if (s[6] == 'x') { + if (features.cxx20Enabled && s[6] == 'v') { + if (s[7] == 'a') { + if (s[8] == 'l') { + return T_CONSTEVAL; + } + } + } + else if (features.cxx11Enabled && s[6] == 'x') { if (s[7] == 'p') { if (s[8] == 'r') { return T_CONSTEXPR; @@ -1111,6 +1198,15 @@ static inline int classify9(const char *s, LanguageFeatures features) } } } + else if (features.cxx20Enabled && s[5] == 'i') { + if (s[6] == 'n') { + if (s[7] == 'i') { + if (s[8] == 't') { + return T_CONSTINIT; + } + } + } + } } } } diff --git a/src/libs/3rdparty/cplusplus/Keywords.kwgen b/src/libs/3rdparty/cplusplus/Keywords.kwgen index 36300a87766..70deeb648d8 100644 --- a/src/libs/3rdparty/cplusplus/Keywords.kwgen +++ b/src/libs/3rdparty/cplusplus/Keywords.kwgen @@ -128,6 +128,16 @@ nullptr static_assert thread_local +%pre-check=features.cxx20Enabled +char8_t +concept +consteval +constinit +co_await +co_return +co_yield +requires + %pre-check=features.qtKeywordsEnabled emit foreach diff --git a/src/libs/3rdparty/cplusplus/Lexer.cpp b/src/libs/3rdparty/cplusplus/Lexer.cpp index e2f1b4e0e6b..fc1b72cb7db 100644 --- a/src/libs/3rdparty/cplusplus/Lexer.cpp +++ b/src/libs/3rdparty/cplusplus/Lexer.cpp @@ -217,14 +217,17 @@ void Lexer::scan_helper(Token *tok) tok->f.kind = s._tokenKind; const bool found = _expectedRawStringSuffix.isEmpty() ? scanUntilRawStringLiteralEndSimple() : scanUntilRawStringLiteralEndPrecise(); - if (found) + if (found) { + scanOptionalUserDefinedLiteral(tok); _state = 0; + } return; } else { // non-raw strings tok->f.joined = true; tok->f.kind = s._tokenKind; _state = 0; scanUntilQuote(tok, '"'); + scanOptionalUserDefinedLiteral(tok); return; } @@ -829,6 +832,8 @@ void Lexer::scanRawStringLiteral(Token *tok, unsigned char hint) _expectedRawStringSuffix.prepend(')'); _expectedRawStringSuffix.append('"'); } + if (closed) + scanOptionalUserDefinedLiteral(tok); } bool Lexer::scanUntilRawStringLiteralEndPrecise() diff --git a/src/libs/3rdparty/cplusplus/Parser.cpp b/src/libs/3rdparty/cplusplus/Parser.cpp index cb7bfa3a1b6..6bf19f59829 100644 --- a/src/libs/3rdparty/cplusplus/Parser.cpp +++ b/src/libs/3rdparty/cplusplus/Parser.cpp @@ -413,6 +413,7 @@ bool Parser::skipUntilStatement() case T_BREAK: case T_CONTINUE: case T_RETURN: + case T_CO_RETURN: case T_GOTO: case T_TRY: case T_CATCH: @@ -1247,6 +1248,8 @@ bool Parser::parseTemplateDeclaration(DeclarationAST *&node) ast->less_token = consumeToken(); if (maybeSplitGreaterGreaterToken() || LA() == T_GREATER || parseTemplateParameterList(ast->template_parameter_list)) match(T_GREATER, &ast->greater_token); + if (!parseRequiresClauseOpt(ast->requiresClause)) + return false; } while (LA()) { @@ -1255,6 +1258,8 @@ bool Parser::parseTemplateDeclaration(DeclarationAST *&node) ast->declaration = nullptr; if (parseDeclaration(ast->declaration)) break; + if (parseConceptDeclaration(ast->declaration)) + break; error(start_declaration, "expected a declaration"); rewind(start_declaration + 1); @@ -1265,6 +1270,205 @@ bool Parser::parseTemplateDeclaration(DeclarationAST *&node) return true; } +bool Parser::parseConceptDeclaration(DeclarationAST *&node) +{ + if (!_languageFeatures.cxx20Enabled) + return false; + if (LA() != T_CONCEPT) + return false; + + const auto ast = new (_pool) ConceptDeclarationAST; + ast->concept_token = consumeToken(); + if (!parseName(ast->name)) + return false; + parseAttributeSpecifier(ast->attributes); + if (LA() != T_EQUAL) + return false; + ast->equals_token = consumeToken(); + if (!parseLogicalOrExpression(ast->constraint)) + return false; + if (LA() != T_SEMICOLON) + return false; + ast->semicolon_token = consumeToken(); + node = ast; + return true; +} + +bool Parser::parsePlaceholderTypeSpecifier(PlaceholderTypeSpecifierAST *&node) +{ + if ((lookAtBuiltinTypeSpecifier() || _translationUnit->tokenAt(_tokenIndex).isKeyword()) + && (LA() != T_AUTO && LA() != T_DECLTYPE)) { + return false; + } + + TypeConstraintAST *typeConstraint = nullptr; + const int savedCursor = cursor(); + parseTypeConstraint(typeConstraint); + if (LA() != T_AUTO && (LA() != T_DECLTYPE || LA(1) != T_LPAREN || LA(2) != T_AUTO)) { + rewind(savedCursor); + return false; + } + const auto spec = new (_pool) PlaceholderTypeSpecifierAST; + spec->typeConstraint = typeConstraint; + if (LA() == T_DECLTYPE) { + spec->declTypetoken = consumeToken(); + if (LA() != T_LPAREN) + return false; + spec->lparenToken = consumeToken(); + if (LA() != T_AUTO) + return false; + spec->autoToken = consumeToken(); + if (LA() != T_RPAREN) + return false; + spec->rparenToken = consumeToken(); + } else { + spec->autoToken = consumeToken(); + } + node = spec; + return true; +} + +bool Parser::parseTypeConstraint(TypeConstraintAST *&node) +{ + if (!_languageFeatures.cxx20Enabled) + return false; + NestedNameSpecifierListAST *nestedName = nullptr; + parseNestedNameSpecifierOpt(nestedName, true); + NameAST *conceptName = nullptr; + if (!parseUnqualifiedName(conceptName, false)) + return false; + const auto typeConstraint = new (_pool) TypeConstraintAST; + typeConstraint->nestedName = nestedName; + typeConstraint->conceptName = conceptName; + if (LA() != T_LESS) { + node = typeConstraint; + return true; + } + typeConstraint->lessToken = consumeToken(); + if (LA() != T_GREATER) { + if (!parseTemplateArgumentList(typeConstraint->templateArgs)) + return false; + } + if (LA() != T_GREATER) + return false; + typeConstraint->greaterToken = consumeToken(); + node = typeConstraint; + return true; +} + +bool Parser::parseRequirement() +{ + if (LA() == T_TYPENAME) { // type-requirement + consumeToken(); + NameAST *name = nullptr; + if (!parseName(name, true)) + return false; + if (LA() != T_SEMICOLON) + return false; + consumeToken(); + return true; + } + if (LA() == T_LBRACE) { // compound-requirement + consumeToken(); + ExpressionAST *expr = nullptr; + if (!parseExpression(expr)) + return false; + if (LA() != T_RBRACE) + return false; + consumeToken(); + if (LA() == T_NOEXCEPT) + consumeToken(); + if (LA() == T_SEMICOLON) { + consumeToken(); + return true; + } + TypeConstraintAST *typeConstraint = nullptr; + if (!parseTypeConstraint(typeConstraint)) + return false; + if (LA() != T_SEMICOLON) + return false; + consumeToken(); + return true; + } + if (LA() == T_REQUIRES) { // nested-requirement + consumeToken(); + ExpressionAST *constraintExpr = nullptr; + if (!parseLogicalOrExpression(constraintExpr)) + return false; + if (LA() != T_SEMICOLON) + return false; + consumeToken(); + return true; + } + ExpressionAST *simpleExpr; + if (!parseExpression(simpleExpr)) // simple-requirement + return false; + if (LA() != T_SEMICOLON) + return false; + consumeToken(); + return true; +} + +bool Parser::parseRequiresClauseOpt(RequiresClauseAST *&node) +{ + if (!_languageFeatures.cxx20Enabled) + return true; + if (LA() != T_REQUIRES) + return true; + const auto ast = new (_pool) RequiresClauseAST; + ast->requires_token = consumeToken(); + if (!parsePrimaryExpression(ast->constraint)) + return false; + while (true) { + if (LA() != T_PIPE_PIPE && LA() != T_AMPER_AMPER) + break; + ExpressionAST *next = nullptr; + if (!parsePrimaryExpression(next)) + return false; + + // This won't yield the right precedence, but I don't care. + BinaryExpressionAST *expr = new (_pool) BinaryExpressionAST; + expr->left_expression = ast->constraint; + expr->binary_op_token = consumeToken(); + expr->right_expression = next; + ast->constraint = expr; + } + node = ast; + return true; +} + +bool Parser::parseRequiresExpression(ExpressionAST *&node) +{ + if (!_languageFeatures.cxx20Enabled) + return false; + if (LA() != T_REQUIRES) + return false; + + const auto ast = new (_pool) RequiresExpressionAST; + ast->requires_token = consumeToken(); + if (LA() == T_LPAREN) { + ast->lparen_token = consumeToken(); + if (!parseParameterDeclarationClause(ast->parameters)) + return false; + if (LA() != T_RPAREN) + return false; + ast->rparen_token = consumeToken(); + } + if (LA() != T_LBRACE) + return false; + ast->lbrace_token = consumeToken(); + if (!parseRequirement()) + return false; + while (LA() != T_RBRACE) { + if (!parseRequirement()) + return false; + } + ast->rbrace_token = consumeToken(); + + node = ast; + return true; +} + bool Parser::parseOperator(OperatorAST *&node) // ### FIXME { DEBUG_THIS_RULE(); @@ -1500,6 +1704,14 @@ bool Parser::parseDeclSpecifierSeq(SpecifierListAST *&decl_specifier_seq, NameAST *named_type_specifier = nullptr; SpecifierListAST **decl_specifier_seq_ptr = &decl_specifier_seq; for (;;) { + PlaceholderTypeSpecifierAST *placeholderSpec = nullptr; + // A simple auto is also technically a placeholder-type-specifier, but for historical + // reasons, it is handled further below. + if (LA() != T_AUTO && parsePlaceholderTypeSpecifier(placeholderSpec)) { + *decl_specifier_seq_ptr = new (_pool) SpecifierListAST(placeholderSpec); + decl_specifier_seq_ptr = &(*decl_specifier_seq_ptr)->next; + continue; + } if (! noStorageSpecifiers && ! onlySimpleTypeSpecifiers && lookAtStorageClassSpecifier()) { // storage-class-specifier SimpleSpecifierAST *spec = new (_pool) SimpleSpecifierAST; @@ -1550,8 +1762,9 @@ bool Parser::parseDeclSpecifierSeq(SpecifierListAST *&decl_specifier_seq, } decl_specifier_seq_ptr = &(*decl_specifier_seq_ptr)->next; has_type_specifier = true; - } else + } else { break; + } } return decl_specifier_seq != nullptr; @@ -1694,6 +1907,8 @@ bool Parser::hasAuto(SpecifierListAST *decl_specifier_list) const if (_translationUnit->tokenKind(simpleSpec->specifier_token) == T_AUTO) return true; } + if (spec->asPlaceholderTypeSpecifier()) + return true; } return false; } @@ -2012,8 +2227,8 @@ bool Parser::parseTypenameTypeParameter(DeclarationAST *&node) bool Parser::parseTemplateTypeParameter(DeclarationAST *&node) { DEBUG_THIS_RULE(); + TemplateTypeParameterAST *ast = new (_pool) TemplateTypeParameterAST; if (LA() == T_TEMPLATE) { - TemplateTypeParameterAST *ast = new (_pool) TemplateTypeParameterAST; ast->template_token = consumeToken(); if (LA() == T_LESS) ast->less_token = consumeToken(); @@ -2022,20 +2237,21 @@ bool Parser::parseTemplateTypeParameter(DeclarationAST *&node) ast->greater_token = consumeToken(); if (LA() == T_CLASS) ast->class_token = consumeToken(); - if (_languageFeatures.cxx11Enabled && LA() == T_DOT_DOT_DOT) - ast->dot_dot_dot_token = consumeToken(); - - // parse optional name - parseName(ast->name); - - if (LA() == T_EQUAL) { - ast->equal_token = consumeToken(); - parseTypeId(ast->type_id); - } - node = ast; - return true; + } else if (!parseTypeConstraint(ast->typeConstraint)) { + return false; } - return false; + if (_languageFeatures.cxx11Enabled && LA() == T_DOT_DOT_DOT) + ast->dot_dot_dot_token = consumeToken(); + + // parse optional name + parseName(ast->name); + + if (LA() == T_EQUAL) { + ast->equal_token = consumeToken(); + parseTypeId(ast->type_id); + } + node = ast; + return true; } bool Parser::lookAtTypeParameter() @@ -2071,10 +2287,9 @@ bool Parser::parseTypeParameter(DeclarationAST *&node) if (lookAtTypeParameter()) return parseTypenameTypeParameter(node); - else if (LA() == T_TEMPLATE) + if (LA() == T_TEMPLATE) return parseTemplateTypeParameter(node); - else - return false; + return parseTemplateTypeParameter(node); } bool Parser::parseTypeId(ExpressionAST *&node) @@ -2819,6 +3034,8 @@ bool Parser::parseInitDeclarator(DeclaratorAST *&node, SpecifierListAST *decl_sp } else if (node->core_declarator && node->core_declarator->asDecompositionDeclarator()) { error(cursor(), "structured binding needs initializer"); return false; + } else if (!parseRequiresClauseOpt(node->requiresClause)) { + return false; } return true; } @@ -3358,6 +3575,7 @@ bool Parser::parseStatement(StatementAST *&node, bool blockLabeledStatement) return parseGotoStatement(node); case T_RETURN: + case T_CO_RETURN: return parseReturnStatement(node); case T_LBRACE: @@ -3468,7 +3686,7 @@ bool Parser::parseGotoStatement(StatementAST *&node) bool Parser::parseReturnStatement(StatementAST *&node) { DEBUG_THIS_RULE(); - if (LA() == T_RETURN) { + if (LA() == T_RETURN || LA() == T_CO_RETURN) { ReturnStatementAST *ast = new (_pool) ReturnStatementAST; ast->return_token = consumeToken(); if (_languageFeatures.cxx11Enabled && LA() == T_LBRACE) @@ -3854,6 +4072,31 @@ bool Parser::parseIfStatement(StatementAST *&node) ast->constexpr_token = consumeToken(); } match(T_LPAREN, &ast->lparen_token); + + // C++17: init-statement + if (_languageFeatures.cxx17Enabled) { + const int savedCursor = cursor(); + const bool savedBlockErrors = _translationUnit->blockErrors(true); + bool foundInitStmt = parseExpressionOrDeclarationStatement(ast->initStmt); + if (foundInitStmt) + foundInitStmt = ast->initStmt; + if (foundInitStmt) { + if (const auto exprStmt = ast->initStmt->asExpressionStatement()) { + foundInitStmt = exprStmt->semicolon_token; + } else if (const auto declStmt = ast->initStmt->asDeclarationStatement()) { + foundInitStmt = declStmt->declaration + && declStmt->declaration->asSimpleDeclaration() + && declStmt->declaration->asSimpleDeclaration()->semicolon_token; + } else { + foundInitStmt = false; + } + } + if (!foundInitStmt) { + ast->initStmt = nullptr; + rewind(savedCursor); + } + _translationUnit->blockErrors(savedBlockErrors); + } parseCondition(ast->condition); match(T_RPAREN, &ast->rparen_token); if (! parseStatement(ast->statement)) @@ -4731,6 +4974,9 @@ bool Parser::parsePrimaryExpression(ExpressionAST *&node) case T_AT_SELECTOR: return parseObjCExpression(node); + case T_REQUIRES: + return parseRequiresExpression(node); + default: { NameAST *name = nullptr; if (parseNameId(name)) { @@ -5436,6 +5682,11 @@ bool Parser::parseUnaryExpression(ExpressionAST *&node) return parseNoExceptOperatorExpression(node); } + case T_CO_AWAIT: + if (!_languageFeatures.cxx20Enabled) + break; + return parseAwaitExpression(node); + default: break; } // switch @@ -5694,6 +5945,8 @@ bool Parser::parseAssignmentExpression(ExpressionAST *&node) DEBUG_THIS_RULE(); if (LA() == T_THROW) return parseThrowExpression(node); + else if (LA() == T_CO_YIELD) + return parseYieldExpression(node); else PARSE_EXPRESSION_WITH_OPERATOR_PRECEDENCE(node, Prec::Assignment) } @@ -5822,6 +6075,34 @@ bool Parser::parseThrowExpression(ExpressionAST *&node) return false; } +bool Parser::parseYieldExpression(ExpressionAST *&node) +{ + DEBUG_THIS_RULE(); + if (LA() != T_CO_YIELD) + return false; + const auto ast = new (_pool) YieldExpressionAST; + ast->yield_token = consumeToken(); + if (parseBracedInitList0x(ast->expression) || parseAssignmentExpression(ast->expression)) { + node = ast; + return true; + } + return false; +} + +bool Parser::parseAwaitExpression(ExpressionAST *&node) +{ + DEBUG_THIS_RULE(); + if (LA() != T_CO_AWAIT) + return false; + const auto ast = new (_pool) AwaitExpressionAST; + ast->await_token = consumeToken(); + if (parseCastExpression(ast->castExpression)) { + node = ast; + return true; + } + return false; +} + bool Parser::parseNoExceptOperatorExpression(ExpressionAST *&node) { DEBUG_THIS_RULE(); @@ -6757,6 +7038,15 @@ bool Parser::parseLambdaExpression(ExpressionAST *&node) if (parseLambdaIntroducer(lambda_introducer)) { LambdaExpressionAST *ast = new (_pool) LambdaExpressionAST; ast->lambda_introducer = lambda_introducer; + if (_languageFeatures.cxx20Enabled && LA() == T_LESS) { + consumeToken(); + parseTemplateParameterList(ast->templateParameters); + if (LA() != T_GREATER) + return false; + consumeToken(); + parseRequiresClauseOpt(ast->requiresClause); + } + parseOptionalAttributeSpecifierSequence(ast->attributes); parseLambdaDeclarator(ast->lambda_declarator); parseCompoundStatement(ast->statement); node = ast; @@ -6781,7 +7071,9 @@ bool Parser::parseLambdaIntroducer(LambdaIntroducerAST *&node) if (LA() == T_RBRACKET) { ast->rbracket_token = consumeToken(); - if (LA() == T_LPAREN || LA() == T_LBRACE) { + // FIXME: Attributes are also allowed ... + if (LA() == T_LPAREN || LA() == T_LBRACE + || (_languageFeatures.cxx20Enabled && LA() == T_LESS)) { node = ast; return true; } @@ -6897,6 +7189,7 @@ bool Parser::parseLambdaDeclarator(LambdaDeclaratorAST *&node) parseExceptionSpecification(ast->exception_specification); parseTrailingReturnType(ast->trailing_return_type); + parseRequiresClauseOpt(ast->requiresClause); node = ast; return true; diff --git a/src/libs/3rdparty/cplusplus/Parser.h b/src/libs/3rdparty/cplusplus/Parser.h index ba101ccd46e..e34a4ff5223 100644 --- a/src/libs/3rdparty/cplusplus/Parser.h +++ b/src/libs/3rdparty/cplusplus/Parser.h @@ -143,9 +143,17 @@ public: bool parseTemplateArgument(ExpressionAST *&node); bool parseTemplateArgumentList(ExpressionListAST *&node); bool parseTemplateDeclaration(DeclarationAST *&node); + bool parseConceptDeclaration(DeclarationAST *&node); + bool parsePlaceholderTypeSpecifier(PlaceholderTypeSpecifierAST *&node); + bool parseTypeConstraint(TypeConstraintAST *&node); + bool parseRequirement(); + bool parseRequiresClauseOpt(RequiresClauseAST *&node); + bool parseRequiresExpression(ExpressionAST *&node); bool parseTemplateParameter(DeclarationAST *&node); bool parseTemplateParameterList(DeclarationListAST *&node); bool parseThrowExpression(ExpressionAST *&node); + bool parseYieldExpression(ExpressionAST *&node); + bool parseAwaitExpression(ExpressionAST *&node); bool parseNoExceptOperatorExpression(ExpressionAST *&node); bool parseTryBlockStatement(StatementAST *&node, CtorInitializerAST **placeholder); bool parseCatchClause(CatchClauseListAST *&node); diff --git a/src/libs/3rdparty/cplusplus/Symbol.h b/src/libs/3rdparty/cplusplus/Symbol.h index 497226dcc1a..dc07ced907d 100644 --- a/src/libs/3rdparty/cplusplus/Symbol.h +++ b/src/libs/3rdparty/cplusplus/Symbol.h @@ -66,10 +66,10 @@ public: /// Returns this Symbol's source location. int sourceLocation() const { return _sourceLocation; } - /// \returns this Symbol's line number. The line number is 1-based. + /// Returns this Symbol's line number. The line number is 1-based. int line() const { return _line; } - /// \returns this Symbol's column number. The column number is 1-based. + /// Returns this Symbol's column number. The column number is 1-based. int column() const { return _column; } /// Returns this Symbol's file name. diff --git a/src/libs/3rdparty/cplusplus/Token.cpp b/src/libs/3rdparty/cplusplus/Token.cpp index 31fdb2036c2..2e8a0ce7f24 100644 --- a/src/libs/3rdparty/cplusplus/Token.cpp +++ b/src/libs/3rdparty/cplusplus/Token.cpp @@ -120,9 +120,15 @@ const char *token_names[] = { ("case"), ("catch"), ("class"), + ("co_await"), + ("co_return"), + ("co_yield"), + ("concept"), ("const"), ("const_cast"), + ("consteval"), ("constexpr"), + ("constinit"), ("continue"), ("decltype"), ("default"), @@ -151,6 +157,7 @@ const char *token_names[] = { ("public"), ("register"), ("reinterpret_cast"), + ("requires"), ("return"), ("sizeof"), ("static"), @@ -210,6 +217,7 @@ const char *token_names[] = { // Primitive types ("bool"), ("char"), + ("char8_t"), ("char16_t"), ("char32_t"), ("double"), diff --git a/src/libs/3rdparty/cplusplus/Token.h b/src/libs/3rdparty/cplusplus/Token.h index 3a04a44a86a..f853d29c77f 100644 --- a/src/libs/3rdparty/cplusplus/Token.h +++ b/src/libs/3rdparty/cplusplus/Token.h @@ -130,9 +130,15 @@ enum Kind { T_CASE, T_CATCH, T_CLASS, + T_CO_AWAIT, + T_CO_RETURN, + T_CO_YIELD, + T_CONCEPT, T_CONST, T_CONST_CAST, + T_CONSTEVAL, T_CONSTEXPR, + T_CONSTINIT, T_CONTINUE, T_DECLTYPE, T_DEFAULT, @@ -161,6 +167,7 @@ enum Kind { T_PUBLIC, T_REGISTER, T_REINTERPRET_CAST, + T_REQUIRES, T_RETURN, T_SIZEOF, T_STATIC, @@ -223,6 +230,7 @@ enum Kind { T_FIRST_PRIMITIVE, T_BOOL = T_FIRST_PRIMITIVE, T_CHAR, + T_CHAR8_T, T_CHAR16_T, T_CHAR32_T, T_DOUBLE, @@ -447,6 +455,7 @@ struct LanguageFeatures unsigned int cxxEnabled : 1; unsigned int cxx11Enabled : 1; unsigned int cxx14Enabled : 1; + unsigned int cxx17Enabled : 1; unsigned int cxx20Enabled : 1; unsigned int objCEnabled : 1; unsigned int c99Enabled : 1; diff --git a/src/libs/3rdparty/libptyqt/.clang-format b/src/libs/3rdparty/libptyqt/.clang-format new file mode 100644 index 00000000000..b861ff7a951 --- /dev/null +++ b/src/libs/3rdparty/libptyqt/.clang-format @@ -0,0 +1 @@ +{ "DisableFormat" : true } \ No newline at end of file diff --git a/src/libs/3rdparty/libptyqt/CMakeLists.txt b/src/libs/3rdparty/libptyqt/CMakeLists.txt new file mode 100644 index 00000000000..c6e8b745737 --- /dev/null +++ b/src/libs/3rdparty/libptyqt/CMakeLists.txt @@ -0,0 +1,28 @@ +set(SOURCES + iptyprocess.h + ptyqt.cpp ptyqt.h +) + +if (WIN32) + list(APPEND SOURCES + winptyprocess.cpp winptyprocess.h + conptyprocess.cpp conptyprocess.h + ) +else() + list(APPEND SOURCES unixptyprocess.cpp unixptyprocess.h) +endif() + +add_library(ptyqt STATIC ${SOURCES}) +target_link_libraries(ptyqt PUBLIC Qt::Core) + +if (WIN32) + target_link_libraries(ptyqt PRIVATE winpty Qt::Network) + #target_compile_definitions(ptyqt PRIVATE PTYQT_DEBUG) +endif() + +set_target_properties(ptyqt + PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR} + QT_COMPILE_OPTIONS_DISABLE_WARNINGS ON + POSITION_INDEPENDENT_CODE ON +) diff --git a/src/libs/3rdparty/libptyqt/LICENSE b/src/libs/3rdparty/libptyqt/LICENSE new file mode 100644 index 00000000000..73996c7c90e --- /dev/null +++ b/src/libs/3rdparty/libptyqt/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Vitaly Petrov, v31337@gmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/src/libs/3rdparty/libptyqt/conptyprocess.cpp b/src/libs/3rdparty/libptyqt/conptyprocess.cpp new file mode 100644 index 00000000000..cb18b332066 --- /dev/null +++ b/src/libs/3rdparty/libptyqt/conptyprocess.cpp @@ -0,0 +1,343 @@ +#include "conptyprocess.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define READ_INTERVAL_MSEC 500 + +HRESULT ConPtyProcess::createPseudoConsoleAndPipes(HPCON* phPC, HANDLE* phPipeIn, HANDLE* phPipeOut, qint16 cols, qint16 rows) +{ + HRESULT hr{ E_UNEXPECTED }; + HANDLE hPipePTYIn{ INVALID_HANDLE_VALUE }; + HANDLE hPipePTYOut{ INVALID_HANDLE_VALUE }; + + // Create the pipes to which the ConPTY will connect + if (CreatePipe(&hPipePTYIn, phPipeOut, NULL, 0) && + CreatePipe(phPipeIn, &hPipePTYOut, NULL, 0)) + { + // Create the Pseudo Console of the required size, attached to the PTY-end of the pipes + hr = m_winContext.createPseudoConsole({cols, rows}, hPipePTYIn, hPipePTYOut, 0, phPC); + + // Note: We can close the handles to the PTY-end of the pipes here + // because the handles are dup'ed into the ConHost and will be released + // when the ConPTY is destroyed. + if (INVALID_HANDLE_VALUE != hPipePTYOut) CloseHandle(hPipePTYOut); + if (INVALID_HANDLE_VALUE != hPipePTYIn) CloseHandle(hPipePTYIn); + } + + return hr; +} + +// Initializes the specified startup info struct with the required properties and +// updates its thread attribute list with the specified ConPTY handle +HRESULT ConPtyProcess::initializeStartupInfoAttachedToPseudoConsole(STARTUPINFOEX* pStartupInfo, HPCON hPC) +{ + HRESULT hr{ E_UNEXPECTED }; + + if (pStartupInfo) + { + SIZE_T attrListSize{}; + + pStartupInfo->StartupInfo.hStdInput = m_hPipeIn; + pStartupInfo->StartupInfo.hStdError = m_hPipeOut; + pStartupInfo->StartupInfo.hStdOutput = m_hPipeOut; + pStartupInfo->StartupInfo.dwFlags |= STARTF_USESTDHANDLES; + + pStartupInfo->StartupInfo.cb = sizeof(STARTUPINFOEX); + + // Get the size of the thread attribute list. + InitializeProcThreadAttributeList(NULL, 1, 0, &attrListSize); + + // Allocate a thread attribute list of the correct size + pStartupInfo->lpAttributeList = reinterpret_cast( + HeapAlloc(GetProcessHeap(), 0, attrListSize)); + + // Initialize thread attribute list + if (pStartupInfo->lpAttributeList + && InitializeProcThreadAttributeList(pStartupInfo->lpAttributeList, 1, 0, &attrListSize)) + { + // Set Pseudo Console attribute + hr = UpdateProcThreadAttribute( + pStartupInfo->lpAttributeList, + 0, + PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, + hPC, + sizeof(HPCON), + NULL, + NULL) + ? S_OK + : HRESULT_FROM_WIN32(GetLastError()); + } + else + { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + } + return hr; +} + +Q_DECLARE_METATYPE(HANDLE) + +ConPtyProcess::ConPtyProcess() + : IPtyProcess() + , m_ptyHandler { INVALID_HANDLE_VALUE } + , m_hPipeIn { INVALID_HANDLE_VALUE } + , m_hPipeOut { INVALID_HANDLE_VALUE } + , m_readThread(nullptr) +{ + qRegisterMetaType("HANDLE"); +} + +ConPtyProcess::~ConPtyProcess() +{ + kill(); +} + +bool ConPtyProcess::startProcess(const QString &executable, + const QStringList &arguments, + const QString &workingDir, + QStringList environment, + qint16 cols, + qint16 rows) +{ + if (!isAvailable()) { + m_lastError = m_winContext.lastError(); + return false; + } + + //already running + if (m_ptyHandler != INVALID_HANDLE_VALUE) + return false; + + QFileInfo fi(executable); + if (fi.isRelative() || !QFile::exists(executable)) { + //todo add auto-find executable in PATH env var + m_lastError = QString("ConPty Error: shell file path '%1' must be absolute").arg(executable); + return false; + } + + m_shellPath = executable; + m_shellPath.replace('/', '\\'); + m_size = QPair(cols, rows); + + //env + const QString env = environment.join(QChar(QChar::Null)) + QChar(QChar::Null); + LPVOID envPtr = env.isEmpty() ? nullptr : (LPVOID) env.utf16(); + + LPCWSTR workingDirPtr = workingDir.isEmpty() ? nullptr : (LPCWSTR) workingDir.utf16(); + + QStringList exeAndArgs = arguments; + exeAndArgs.prepend(m_shellPath); + std::wstring cmdArg{(LPCWSTR) (exeAndArgs.join(QLatin1String(" ")).utf16())}; + + HRESULT hr{E_UNEXPECTED}; + + // Create the Pseudo Console and pipes to it + hr = createPseudoConsoleAndPipes(&m_ptyHandler, &m_hPipeIn, &m_hPipeOut, cols, rows); + + if (S_OK != hr) { + m_lastError = QString("ConPty Error: CreatePseudoConsoleAndPipes fail"); + return false; + } + + // Initialize the necessary startup info struct + if (S_OK != initializeStartupInfoAttachedToPseudoConsole(&m_shellStartupInfo, m_ptyHandler)) { + m_lastError = QString("ConPty Error: InitializeStartupInfoAttachedToPseudoConsole fail"); + return false; + } + + hr = CreateProcessW(nullptr, // No module name - use Command Line + cmdArg.data(), // Command Line + nullptr, // Process handle not inheritable + nullptr, // Thread handle not inheritable + FALSE, // Inherit handles + EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, // Creation flags + envPtr, // Environment block + workingDirPtr, // Use parent's starting directory + &m_shellStartupInfo.StartupInfo, // Pointer to STARTUPINFO + &m_shellProcessInformation) // Pointer to PROCESS_INFORMATION + ? S_OK + : GetLastError(); + + if (S_OK != hr) { + m_lastError = QString("ConPty Error: Cannot create process -> %1").arg(hr); + return false; + } + + m_pid = m_shellProcessInformation.dwProcessId; + + // Notify when the shell process has been terminated + m_shellCloseWaitNotifier = new QWinEventNotifier(m_shellProcessInformation.hProcess, notifier()); + QObject::connect(m_shellCloseWaitNotifier, + &QWinEventNotifier::activated, + notifier(), + [this](HANDLE hEvent) { + DWORD exitCode = 0; + GetExitCodeProcess(hEvent, &exitCode); + m_exitCode = exitCode; + // Do not respawn if the object is about to be destructed + if (!m_aboutToDestruct) + emit notifier()->aboutToClose(); + m_shellCloseWaitNotifier->setEnabled(false); + }, Qt::QueuedConnection); + + //this code runned in separate thread + m_readThread = QThread::create([this]() { + //buffers + const DWORD BUFF_SIZE{1024}; + char szBuffer[BUFF_SIZE]{}; + + forever { + DWORD dwBytesRead{}; + + // Read from the pipe + BOOL result = ReadFile(m_hPipeIn, szBuffer, BUFF_SIZE, &dwBytesRead, NULL); + + const bool needMoreData = !result && GetLastError() == ERROR_MORE_DATA; + if (result || needMoreData) { + QMutexLocker locker(&m_bufferMutex); + m_buffer.m_readBuffer.append(szBuffer, dwBytesRead); + m_buffer.emitReadyRead(); + } + + const bool brokenPipe = !result && GetLastError() == ERROR_BROKEN_PIPE; + if (QThread::currentThread()->isInterruptionRequested() || brokenPipe) + break; + } + + CancelIoEx(m_hPipeIn, nullptr); + }); + + //start read thread + m_readThread->start(); + + return true; +} + +bool ConPtyProcess::resize(qint16 cols, qint16 rows) +{ + if (m_ptyHandler == nullptr) + { + return false; + } + + bool res = SUCCEEDED(m_winContext.resizePseudoConsole(m_ptyHandler, {cols, rows})); + + if (res) + { + m_size = QPair(cols, rows); + } + + return res; + + return true; +} + +bool ConPtyProcess::kill() +{ + bool exitCode = false; + + if (m_ptyHandler != INVALID_HANDLE_VALUE) { + m_aboutToDestruct = true; + + // Close ConPTY - this will terminate client process if running + m_winContext.closePseudoConsole(m_ptyHandler); + + // Clean-up the pipes + if (INVALID_HANDLE_VALUE != m_hPipeOut) + CloseHandle(m_hPipeOut); + if (INVALID_HANDLE_VALUE != m_hPipeIn) + CloseHandle(m_hPipeIn); + + if (m_readThread) { + m_readThread->requestInterruption(); + if (!m_readThread->wait(1000)) + m_readThread->terminate(); + m_readThread->deleteLater(); + m_readThread = nullptr; + } + + delete m_shellCloseWaitNotifier; + m_shellCloseWaitNotifier = nullptr; + + m_pid = 0; + m_ptyHandler = INVALID_HANDLE_VALUE; + m_hPipeIn = INVALID_HANDLE_VALUE; + m_hPipeOut = INVALID_HANDLE_VALUE; + + CloseHandle(m_shellProcessInformation.hThread); + CloseHandle(m_shellProcessInformation.hProcess); + + // Cleanup attribute list + if (m_shellStartupInfo.lpAttributeList) { + DeleteProcThreadAttributeList(m_shellStartupInfo.lpAttributeList); + HeapFree(GetProcessHeap(), 0, m_shellStartupInfo.lpAttributeList); + } + + exitCode = true; + } + + return exitCode; +} + +IPtyProcess::PtyType ConPtyProcess::type() +{ + return PtyType::ConPty; +} + +QString ConPtyProcess::dumpDebugInfo() +{ +#ifdef PTYQT_DEBUG + return QString("PID: %1, Type: %2, Cols: %3, Rows: %4") + .arg(m_pid).arg(type()) + .arg(m_size.first).arg(m_size.second); +#else + return QString("Nothing..."); +#endif +} + +QIODevice *ConPtyProcess::notifier() +{ + return &m_buffer; +} + +QByteArray ConPtyProcess::readAll() +{ + QByteArray result; + { + QMutexLocker locker(&m_bufferMutex); + result.swap(m_buffer.m_readBuffer); + } + return result; +} + +qint64 ConPtyProcess::write(const QByteArray &byteArray) +{ + DWORD dwBytesWritten{}; + WriteFile(m_hPipeOut, byteArray.data(), byteArray.size(), &dwBytesWritten, NULL); + return dwBytesWritten; +} + +bool ConPtyProcess::isAvailable() +{ +#ifdef TOO_OLD_WINSDK + return false; //very importnant! ConPty can be built, but it doesn't work if built with old sdk and Win10 < 1903 +#endif + + qint32 buildNumber = QSysInfo::kernelVersion().split(".").last().toInt(); + if (buildNumber < CONPTY_MINIMAL_WINDOWS_VERSION) + return false; + return m_winContext.init(); +} + +void ConPtyProcess::moveToThread(QThread *targetThread) +{ + //nothing for now... +} diff --git a/src/libs/3rdparty/libptyqt/conptyprocess.h b/src/libs/3rdparty/libptyqt/conptyprocess.h new file mode 100644 index 00000000000..d4ffd62b7ee --- /dev/null +++ b/src/libs/3rdparty/libptyqt/conptyprocess.h @@ -0,0 +1,165 @@ +#ifndef CONPTYPROCESS_H +#define CONPTYPROCESS_H + +#include "iptyprocess.h" +#include +#include +#include + +#include +#include +#include +#include +#include + +//Taken from the RS5 Windows SDK, but redefined here in case we're targeting <= 17733 +//Just for compile, ConPty doesn't work with Windows SDK < 17733 +#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE +#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE \ + ProcThreadAttributeValue(22, FALSE, TRUE, FALSE) + +typedef VOID* HPCON; + +#define TOO_OLD_WINSDK +#endif + +class QWinEventNotifier; + +template +std::vector vectorFromString(const std::basic_string &str) +{ + return std::vector(str.begin(), str.end()); +} + +//ConPTY available only on Windows 10 releazed after 1903 (19H1) Windows release +class WindowsContext +{ +public: + typedef HRESULT (*CreatePseudoConsolePtr)( + COORD size, // ConPty Dimensions + HANDLE hInput, // ConPty Input + HANDLE hOutput, // ConPty Output + DWORD dwFlags, // ConPty Flags + HPCON* phPC); // ConPty Reference + + typedef HRESULT (*ResizePseudoConsolePtr)(HPCON hPC, COORD size); + + typedef VOID (*ClosePseudoConsolePtr)(HPCON hPC); + + WindowsContext() + : createPseudoConsole(nullptr) + , resizePseudoConsole(nullptr) + , closePseudoConsole(nullptr) + { + + } + + bool init() + { + //already initialized + if (createPseudoConsole) + return true; + + //try to load symbols from library + //if it fails -> we can't use ConPty API + HANDLE kernel32Handle = LoadLibraryExW(L"kernel32.dll", 0, 0); + + if (kernel32Handle != nullptr) + { + createPseudoConsole = (CreatePseudoConsolePtr)GetProcAddress((HMODULE)kernel32Handle, "CreatePseudoConsole"); + resizePseudoConsole = (ResizePseudoConsolePtr)GetProcAddress((HMODULE)kernel32Handle, "ResizePseudoConsole"); + closePseudoConsole = (ClosePseudoConsolePtr)GetProcAddress((HMODULE)kernel32Handle, "ClosePseudoConsole"); + if (createPseudoConsole == NULL || resizePseudoConsole == NULL || closePseudoConsole == NULL) + { + m_lastError = QString("WindowsContext/ConPty error: %1").arg("Invalid on load API functions"); + return false; + } + } + else + { + m_lastError = QString("WindowsContext/ConPty error: %1").arg("Unable to load kernel32"); + return false; + } + + return true; + } + + QString lastError() + { + return m_lastError; + } + +public: + //vars + CreatePseudoConsolePtr createPseudoConsole; + ResizePseudoConsolePtr resizePseudoConsole; + ClosePseudoConsolePtr closePseudoConsole; + +private: + QString m_lastError; +}; + +class PtyBuffer : public QIODevice +{ + friend class ConPtyProcess; + Q_OBJECT +public: + + //just empty realization, we need only 'readyRead' signal of this class + qint64 readData(char *data, qint64 maxlen) { return 0; } + qint64 writeData(const char *data, qint64 len) { return 0; } + + bool isSequential() const { return true; } + qint64 bytesAvailable() const { return m_readBuffer.size(); } + qint64 size() const { return m_readBuffer.size(); } + + void emitReadyRead() + { + emit readyRead(); + } + +private: + QByteArray m_readBuffer; +}; + +class ConPtyProcess : public IPtyProcess +{ +public: + ConPtyProcess(); + ~ConPtyProcess(); + + bool startProcess(const QString &executable, + const QStringList &arguments, + const QString &workingDir, + QStringList environment, + qint16 cols, + qint16 rows); + bool resize(qint16 cols, qint16 rows); + bool kill(); + PtyType type(); + QString dumpDebugInfo(); + virtual QIODevice *notifier(); + virtual QByteArray readAll(); + virtual qint64 write(const QByteArray &byteArray); + bool isAvailable(); + void moveToThread(QThread *targetThread); + +private: + HRESULT createPseudoConsoleAndPipes(HPCON* phPC, HANDLE* phPipeIn, HANDLE* phPipeOut, qint16 cols, qint16 rows); + HRESULT initializeStartupInfoAttachedToPseudoConsole(STARTUPINFOEX* pStartupInfo, HPCON hPC); + +private: + WindowsContext m_winContext; + HPCON m_ptyHandler{INVALID_HANDLE_VALUE}; + HANDLE m_hPipeIn{INVALID_HANDLE_VALUE}, m_hPipeOut{INVALID_HANDLE_VALUE}; + + QThread *m_readThread{nullptr}; + QMutex m_bufferMutex; + PtyBuffer m_buffer; + bool m_aboutToDestruct{false}; + PROCESS_INFORMATION m_shellProcessInformation{}; + QWinEventNotifier *m_shellCloseWaitNotifier{nullptr}; + STARTUPINFOEX m_shellStartupInfo{}; +}; + +#endif // CONPTYPROCESS_H diff --git a/src/libs/3rdparty/libptyqt/iptyprocess.h b/src/libs/3rdparty/libptyqt/iptyprocess.h new file mode 100644 index 00000000000..3d974908c86 --- /dev/null +++ b/src/libs/3rdparty/libptyqt/iptyprocess.h @@ -0,0 +1,56 @@ +#ifndef IPTYPROCESS_H +#define IPTYPROCESS_H + +#include +#include +#include + +#define CONPTY_MINIMAL_WINDOWS_VERSION 18309 + +class IPtyProcess +{ +public: + enum PtyType { UnixPty = 0, WinPty = 1, ConPty = 2, AutoPty = 3 }; + + IPtyProcess() = default; + IPtyProcess(const IPtyProcess &) = delete; + IPtyProcess &operator=(const IPtyProcess &) = delete; + + virtual ~IPtyProcess() {} + + virtual bool startProcess(const QString &executable, + const QStringList &arguments, + const QString &workingDir, + QStringList environment, + qint16 cols, + qint16 rows) + = 0; + virtual bool resize(qint16 cols, qint16 rows) = 0; + virtual bool kill() = 0; + virtual PtyType type() = 0; + virtual QString dumpDebugInfo() = 0; + virtual QIODevice *notifier() = 0; + virtual QByteArray readAll() = 0; + virtual qint64 write(const QByteArray &byteArray) = 0; + virtual bool isAvailable() = 0; + virtual void moveToThread(QThread *targetThread) = 0; + qint64 pid() { return m_pid; } + QPair size() { return m_size; } + const QString lastError() { return m_lastError; } + int exitCode() { return m_exitCode; } + bool toggleTrace() + { + m_trace = !m_trace; + return m_trace; + } + +protected: + QString m_shellPath; + QString m_lastError; + qint64 m_pid{0}; + int m_exitCode{0}; + QPair m_size; //cols / rows + bool m_trace{false}; +}; + +#endif // IPTYPROCESS_H diff --git a/src/libs/3rdparty/libptyqt/ptyqt.cpp b/src/libs/3rdparty/libptyqt/ptyqt.cpp new file mode 100644 index 00000000000..b3e7aa1b164 --- /dev/null +++ b/src/libs/3rdparty/libptyqt/ptyqt.cpp @@ -0,0 +1,45 @@ +#include "ptyqt.h" +#include + +#ifdef Q_OS_WIN +#include "winptyprocess.h" +#include "conptyprocess.h" +#endif + +#ifdef Q_OS_UNIX +#include "unixptyprocess.h" +#endif + + +IPtyProcess *PtyQt::createPtyProcess(IPtyProcess::PtyType ptyType) +{ + switch (ptyType) + { +#ifdef Q_OS_WIN + case IPtyProcess::PtyType::WinPty: + return new WinPtyProcess(); + break; + case IPtyProcess::PtyType::ConPty: + return new ConPtyProcess(); + break; +#endif +#ifdef Q_OS_UNIX + case IPtyProcess::PtyType::UnixPty: + return new UnixPtyProcess(); + break; +#endif + case IPtyProcess::PtyType::AutoPty: + default: + break; + } + +#ifdef Q_OS_WIN + if (ConPtyProcess().isAvailable() && qgetenv("QTC_USE_WINPTY").isEmpty()) + return new ConPtyProcess(); + else + return new WinPtyProcess(); +#endif +#ifdef Q_OS_UNIX + return new UnixPtyProcess(); +#endif +} diff --git a/src/libs/3rdparty/libptyqt/ptyqt.h b/src/libs/3rdparty/libptyqt/ptyqt.h new file mode 100644 index 00000000000..23b80d346bb --- /dev/null +++ b/src/libs/3rdparty/libptyqt/ptyqt.h @@ -0,0 +1,12 @@ +#ifndef PTYQT_H +#define PTYQT_H + +#include "iptyprocess.h" + +class PtyQt +{ +public: + static IPtyProcess *createPtyProcess(IPtyProcess::PtyType ptyType); +}; + +#endif // PTYQT_H diff --git a/src/libs/3rdparty/libptyqt/ptyqt.qbs b/src/libs/3rdparty/libptyqt/ptyqt.qbs new file mode 100644 index 00000000000..7ff4da9f560 --- /dev/null +++ b/src/libs/3rdparty/libptyqt/ptyqt.qbs @@ -0,0 +1,45 @@ +import qbs + +Project { + name: "ptyqt" + + QtcLibrary { + Depends { name: "Qt.core" } + Depends { name: "Qt.network"; condition: qbs.targetOS.contains("windows") } + Depends { name: "winpty"; condition: qbs.targetOS.contains("windows") } + + type: "staticlibrary" + + files: [ + "iptyprocess.h", + "ptyqt.cpp", + "ptyqt.h", + ] + + Group { + name: "ptyqt UNIX files" + condition: qbs.targetOS.contains("unix") + files: [ + "unixptyprocess.cpp", + "unixptyprocess.h", + ] + } + + Group { + name: "ptyqt Windows files" + condition: qbs.targetOS.contains("windows") + files: [ + "conptyprocess.cpp", + "conptyprocess.h", + "winptyprocess.cpp", + "winptyprocess.h", + ] + } + + Export { + Depends { name: "cpp" } + Depends { name: "winpty"; condition: qbs.targetOS.contains("windows") } + cpp.includePaths: base.concat(exportingProduct.sourceDirectory) + } + } +} diff --git a/src/libs/3rdparty/libptyqt/unixptyprocess.cpp b/src/libs/3rdparty/libptyqt/unixptyprocess.cpp new file mode 100644 index 00000000000..8c018daf8c0 --- /dev/null +++ b/src/libs/3rdparty/libptyqt/unixptyprocess.cpp @@ -0,0 +1,374 @@ +#include "unixptyprocess.h" +#include + +#include +#include +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_FREEBSD) +#include +#endif +#include +#include +#include +#include +#include +#include + +UnixPtyProcess::UnixPtyProcess() + : IPtyProcess() + , m_readMasterNotify(0) +{ + m_shellProcess.setWorkingDirectory( + QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); +} + +UnixPtyProcess::~UnixPtyProcess() +{ + kill(); +} + +bool UnixPtyProcess::startProcess(const QString &shellPath, + const QStringList &arguments, + const QString &workingDir, + QStringList environment, + qint16 cols, + qint16 rows) +{ + if (!isAvailable()) { + m_lastError = QString("UnixPty Error: unavailable"); + return false; + } + + if (m_shellProcess.state() == QProcess::Running) + return false; + + QFileInfo fi(shellPath); + if (fi.isRelative() || !QFile::exists(shellPath)) { + //todo add auto-find executable in PATH env var + m_lastError = QString("UnixPty Error: shell file path must be absolute"); + return false; + } + + m_shellPath = shellPath; + m_size = QPair(cols, rows); + + int rc = 0; + + m_shellProcess.m_handleMaster = ::posix_openpt(O_RDWR | O_NOCTTY); + if (m_shellProcess.m_handleMaster <= 0) { + m_lastError = QString("UnixPty Error: unable to open master -> %1").arg(QLatin1String(strerror(errno))); + kill(); + return false; + } + + m_shellProcess.m_handleSlaveName = QLatin1String(ptsname(m_shellProcess.m_handleMaster)); + if (m_shellProcess.m_handleSlaveName.isEmpty()) { + m_lastError = QString("UnixPty Error: unable to get slave name -> %1").arg(QLatin1String(strerror(errno))); + kill(); + return false; + } + + rc = grantpt(m_shellProcess.m_handleMaster); + if (rc != 0) { + m_lastError + = QString("UnixPty Error: unable to change perms for slave -> %1").arg(QLatin1String(strerror(errno))); + kill(); + return false; + } + + rc = unlockpt(m_shellProcess.m_handleMaster); + if (rc != 0) { + m_lastError = QString("UnixPty Error: unable to unlock slave -> %1").arg(QLatin1String(strerror(errno))); + kill(); + return false; + } + + m_shellProcess.m_handleSlave = ::open(m_shellProcess.m_handleSlaveName.toLatin1().data(), + O_RDWR | O_NOCTTY); + if (m_shellProcess.m_handleSlave < 0) { + m_lastError = QString("UnixPty Error: unable to open slave -> %1").arg(QLatin1String(strerror(errno))); + kill(); + return false; + } + + rc = fcntl(m_shellProcess.m_handleMaster, F_SETFD, FD_CLOEXEC); + if (rc == -1) { + m_lastError + = QString("UnixPty Error: unable to set flags for master -> %1").arg(QLatin1String(strerror(errno))); + kill(); + return false; + } + + rc = fcntl(m_shellProcess.m_handleSlave, F_SETFD, FD_CLOEXEC); + if (rc == -1) { + m_lastError + = QString("UnixPty Error: unable to set flags for slave -> %1").arg(QLatin1String(strerror(errno))); + kill(); + return false; + } + + struct ::termios ttmode; + rc = tcgetattr(m_shellProcess.m_handleMaster, &ttmode); + if (rc != 0) { + m_lastError = QString("UnixPty Error: termios fail -> %1").arg(QLatin1String(strerror(errno))); + kill(); + return false; + } + + ttmode.c_iflag = ICRNL | IXON | IXANY | IMAXBEL | BRKINT; +#if defined(IUTF8) + ttmode.c_iflag |= IUTF8; +#endif + + ttmode.c_oflag = OPOST | ONLCR; + ttmode.c_cflag = CREAD | CS8 | HUPCL; + ttmode.c_lflag = ICANON | ISIG | IEXTEN | ECHO | ECHOE | ECHOK | ECHOKE | ECHOCTL; + + ttmode.c_cc[VEOF] = 4; + ttmode.c_cc[VEOL] = -1; + ttmode.c_cc[VEOL2] = -1; + ttmode.c_cc[VERASE] = 0x7f; + ttmode.c_cc[VWERASE] = 23; + ttmode.c_cc[VKILL] = 21; + ttmode.c_cc[VREPRINT] = 18; + ttmode.c_cc[VINTR] = 3; + ttmode.c_cc[VQUIT] = 0x1c; + ttmode.c_cc[VSUSP] = 26; + ttmode.c_cc[VSTART] = 17; + ttmode.c_cc[VSTOP] = 19; + ttmode.c_cc[VLNEXT] = 22; + ttmode.c_cc[VDISCARD] = 15; + ttmode.c_cc[VMIN] = 1; + ttmode.c_cc[VTIME] = 0; + +#if (__APPLE__) + ttmode.c_cc[VDSUSP] = 25; + ttmode.c_cc[VSTATUS] = 20; +#endif + + cfsetispeed(&ttmode, B38400); + cfsetospeed(&ttmode, B38400); + + rc = tcsetattr(m_shellProcess.m_handleMaster, TCSANOW, &ttmode); + if (rc != 0) { + m_lastError + = QString("UnixPty Error: unabble to set associated params -> %1").arg(QLatin1String(strerror(errno))); + kill(); + return false; + } + + m_readMasterNotify = new QSocketNotifier(m_shellProcess.m_handleMaster, + QSocketNotifier::Read, + &m_shellProcess); + m_readMasterNotify->setEnabled(true); + m_readMasterNotify->moveToThread(m_shellProcess.thread()); + QObject::connect(m_readMasterNotify, &QSocketNotifier::activated, [this](int socket) { + Q_UNUSED(socket) + + const size_t maxRead = 16 * 1024; + static std::array buffer; + + int len = ::read(m_shellProcess.m_handleMaster, buffer.data(), buffer.size()); + if (len > 0) { + m_shellReadBuffer.append(buffer.data(), len); + m_shellProcess.emitReadyRead(); + } + }); + + QObject::connect(&m_shellProcess, &QProcess::finished, &m_shellProcess, [this](int exitCode) { + m_exitCode = exitCode; + emit m_shellProcess.aboutToClose(); + m_readMasterNotify->disconnect(); + }); + + QStringList defaultVars; + + defaultVars.append("TERM=xterm-256color"); + defaultVars.append("ITERM_PROFILE=Default"); + defaultVars.append("XPC_FLAGS=0x0"); + defaultVars.append("XPC_SERVICE_NAME=0"); + defaultVars.append("LANG=en_US.UTF-8"); + defaultVars.append("LC_ALL=en_US.UTF-8"); + defaultVars.append("LC_CTYPE=UTF-8"); + defaultVars.append("INIT_CWD=" + QCoreApplication::applicationDirPath()); + defaultVars.append("COMMAND_MODE=unix2003"); + defaultVars.append("COLORTERM=truecolor"); + + QStringList varNames; + foreach (QString line, environment) { + varNames.append(line.split("=").first()); + } + + //append default env vars only if they don't exists in current env + foreach (QString defVar, defaultVars) { + if (!varNames.contains(defVar.split("=").first())) + environment.append(defVar); + } + + QProcessEnvironment envFormat; + foreach (QString line, environment) { + envFormat.insert(line.split("=").first(), line.split("=").last()); + } + m_shellProcess.setWorkingDirectory(workingDir); + m_shellProcess.setProcessEnvironment(envFormat); + m_shellProcess.setReadChannel(QProcess::StandardOutput); + m_shellProcess.start(m_shellPath, arguments); + if (!m_shellProcess.waitForStarted()) + return false; + + m_pid = m_shellProcess.processId(); + + resize(cols, rows); + + return true; +} + +bool UnixPtyProcess::resize(qint16 cols, qint16 rows) +{ + struct winsize winp; + winp.ws_col = cols; + winp.ws_row = rows; + winp.ws_xpixel = 0; + winp.ws_ypixel = 0; + + bool res = ((ioctl(m_shellProcess.m_handleMaster, TIOCSWINSZ, &winp) != -1) + && (ioctl(m_shellProcess.m_handleSlave, TIOCSWINSZ, &winp) != -1)); + + if (res) { + m_size = QPair(cols, rows); + } + + return res; +} + +bool UnixPtyProcess::kill() +{ + m_shellProcess.m_handleSlaveName = QString(); + if (m_shellProcess.m_handleSlave >= 0) { + ::close(m_shellProcess.m_handleSlave); + m_shellProcess.m_handleSlave = -1; + } + if (m_shellProcess.m_handleMaster >= 0) { + ::close(m_shellProcess.m_handleMaster); + m_shellProcess.m_handleMaster = -1; + } + + if (m_shellProcess.state() == QProcess::Running) { + m_readMasterNotify->disconnect(); + m_readMasterNotify->deleteLater(); + + m_shellProcess.terminate(); + m_shellProcess.waitForFinished(1000); + + if (m_shellProcess.state() == QProcess::Running) { + QProcess::startDetached(QString("kill -9 %1").arg(pid())); + m_shellProcess.kill(); + m_shellProcess.waitForFinished(1000); + } + + return (m_shellProcess.state() == QProcess::NotRunning); + } + return false; +} + +IPtyProcess::PtyType UnixPtyProcess::type() +{ + return IPtyProcess::UnixPty; +} + +QString UnixPtyProcess::dumpDebugInfo() +{ +#ifdef PTYQT_DEBUG + return QString("PID: %1, In: %2, Out: %3, Type: %4, Cols: %5, Rows: %6, IsRunning: %7, Shell: " + "%8, SlaveName: %9") + .arg(m_pid) + .arg(m_shellProcess.m_handleMaster) + .arg(m_shellProcess.m_handleSlave) + .arg(type()) + .arg(m_size.first) + .arg(m_size.second) + .arg(m_shellProcess.state() == QProcess::Running) + .arg(m_shellPath) + .arg(m_shellProcess.m_handleSlaveName); +#else + return QString("Nothing..."); +#endif +} + +QIODevice *UnixPtyProcess::notifier() +{ + return &m_shellProcess; +} + +QByteArray UnixPtyProcess::readAll() +{ + QByteArray tmpBuffer = m_shellReadBuffer; + m_shellReadBuffer.clear(); + return tmpBuffer; +} + +qint64 UnixPtyProcess::write(const QByteArray &byteArray) +{ + int result = ::write(m_shellProcess.m_handleMaster, byteArray.constData(), byteArray.size()); + Q_UNUSED(result) + + return byteArray.size(); +} + +bool UnixPtyProcess::isAvailable() +{ + //todo check something more if required + return true; +} + +void UnixPtyProcess::moveToThread(QThread *targetThread) +{ + m_shellProcess.moveToThread(targetThread); +} + +void ShellProcess::configChildProcess() +{ + dup2(m_handleSlave, STDIN_FILENO); + dup2(m_handleSlave, STDOUT_FILENO); + dup2(m_handleSlave, STDERR_FILENO); + + pid_t sid = setsid(); + ioctl(m_handleSlave, TIOCSCTTY, 0); + tcsetpgrp(m_handleSlave, sid); + +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_FREEBSD) + // on Android imposible to put record to the 'utmp' file + struct utmpx utmpxInfo; + memset(&utmpxInfo, 0, sizeof(utmpxInfo)); + + strncpy(utmpxInfo.ut_user, qgetenv("USER"), sizeof(utmpxInfo.ut_user) - 1); + + QString device(m_handleSlaveName); + if (device.startsWith("/dev/")) + device = device.mid(5); + + const auto deviceAsLatin1 = device.toLatin1(); + const char *d = deviceAsLatin1.constData(); + + strncpy(utmpxInfo.ut_line, d, sizeof(utmpxInfo.ut_line) - 1); + + strncpy(utmpxInfo.ut_id, d + strlen(d) - sizeof(utmpxInfo.ut_id), sizeof(utmpxInfo.ut_id)); + + struct timeval tv; + gettimeofday(&tv, 0); + utmpxInfo.ut_tv.tv_sec = tv.tv_sec; + utmpxInfo.ut_tv.tv_usec = tv.tv_usec; + + utmpxInfo.ut_type = USER_PROCESS; + utmpxInfo.ut_pid = getpid(); + + utmpxname(_PATH_UTMPX); + setutxent(); + pututxline(&utmpxInfo); + endutxent(); + +#if !defined(Q_OS_UNIX) + updwtmpx(_PATH_UTMPX, &loginInfo); +#endif + +#endif +} diff --git a/src/libs/3rdparty/libptyqt/unixptyprocess.h b/src/libs/3rdparty/libptyqt/unixptyprocess.h new file mode 100644 index 00000000000..e4df0d2f74d --- /dev/null +++ b/src/libs/3rdparty/libptyqt/unixptyprocess.h @@ -0,0 +1,66 @@ +#ifndef UNIXPTYPROCESS_H +#define UNIXPTYPROCESS_H + +#include "iptyprocess.h" +#include +#include + +// support for build with MUSL on Alpine Linux +#ifndef _PATH_UTMPX +#include +#define _PATH_UTMPX "/var/log/utmp" +#endif + +class ShellProcess : public QProcess +{ + friend class UnixPtyProcess; + Q_OBJECT +public: + ShellProcess() + : QProcess() + , m_handleMaster(-1) + , m_handleSlave(-1) + { + setProcessChannelMode(QProcess::SeparateChannels); + setChildProcessModifier([this]() { configChildProcess(); }); + } + + void emitReadyRead() { emit readyRead(); } + +protected: + void configChildProcess(); + +private: + int m_handleMaster, m_handleSlave; + QString m_handleSlaveName; +}; + +class UnixPtyProcess : public IPtyProcess +{ +public: + UnixPtyProcess(); + virtual ~UnixPtyProcess(); + + virtual bool startProcess(const QString &executable, + const QStringList &arguments, + const QString &workingDir, + QStringList environment, + qint16 cols, + qint16 rows); + virtual bool resize(qint16 cols, qint16 rows); + virtual bool kill(); + virtual PtyType type(); + virtual QString dumpDebugInfo(); + virtual QIODevice *notifier(); + virtual QByteArray readAll(); + virtual qint64 write(const QByteArray &byteArray); + virtual bool isAvailable(); + void moveToThread(QThread *targetThread); + +private: + ShellProcess m_shellProcess; + QSocketNotifier *m_readMasterNotify; + QByteArray m_shellReadBuffer; +}; + +#endif // UNIXPTYPROCESS_H diff --git a/src/libs/3rdparty/libptyqt/winptyprocess.cpp b/src/libs/3rdparty/libptyqt/winptyprocess.cpp new file mode 100644 index 00000000000..0509bb77c37 --- /dev/null +++ b/src/libs/3rdparty/libptyqt/winptyprocess.cpp @@ -0,0 +1,278 @@ +#include "winptyprocess.h" +#include +#include +#include +#include +#include +#include + +#define DEBUG_VAR_LEGACY "WINPTYDBG" +#define DEBUG_VAR_ACTUAL "WINPTY_DEBUG" +#define SHOW_CONSOLE_VAR "WINPTY_SHOW_CONSOLE" +#define WINPTY_AGENT_NAME "winpty-agent.exe" +#define WINPTY_DLL_NAME "winpty.dll" + +QString castErrorToString(winpty_error_ptr_t error_ptr) +{ + return QString::fromStdWString(winpty_error_msg(error_ptr)); +} + +WinPtyProcess::WinPtyProcess() + : IPtyProcess() + , m_ptyHandler(nullptr) + , m_innerHandle(nullptr) + , m_inSocket(nullptr) + , m_outSocket(nullptr) +{ +#ifdef PTYQT_DEBUG + m_trace = true; +#endif +} + +WinPtyProcess::~WinPtyProcess() +{ + kill(); +} + +bool WinPtyProcess::startProcess(const QString &executable, + const QStringList &arguments, + const QString &workingDir, + QStringList environment, + qint16 cols, + qint16 rows) +{ + if (!isAvailable()) + { + m_lastError = QString("WinPty Error: winpty-agent.exe or winpty.dll not found!"); + return false; + } + + //already running + if (m_ptyHandler != nullptr) + return false; + + QFileInfo fi(executable); + if (fi.isRelative() || !QFile::exists(executable)) + { + //todo add auto-find executable in PATH env var + m_lastError = QString("WinPty Error: shell file path must be absolute"); + return false; + } + + m_shellPath = executable; + m_shellPath.replace('/', '\\'); + m_size = QPair(cols, rows); + +#ifdef PTYQT_DEBUG + if (m_trace) + { + environment.append(QString("%1=1").arg(DEBUG_VAR_LEGACY)); + environment.append(QString("%1=trace").arg(DEBUG_VAR_ACTUAL)); + environment.append(QString("%1=1").arg(SHOW_CONSOLE_VAR)); + SetEnvironmentVariableA(DEBUG_VAR_LEGACY, "1"); + SetEnvironmentVariableA(DEBUG_VAR_ACTUAL, "trace"); + SetEnvironmentVariableA(SHOW_CONSOLE_VAR, "1"); + } +#endif + + //env + std::wstringstream envBlock; + foreach (QString line, environment) + { + envBlock << line.toStdWString() << L'\0'; + } + std::wstring env = envBlock.str(); + + //create start config + winpty_error_ptr_t errorPtr = nullptr; + winpty_config_t* startConfig = winpty_config_new(0, &errorPtr); + if (startConfig == nullptr) + { + m_lastError = QString("WinPty Error: create start config -> %1").arg(castErrorToString(errorPtr)); + return false; + } + winpty_error_free(errorPtr); + + //set params + winpty_config_set_initial_size(startConfig, cols, rows); + winpty_config_set_mouse_mode(startConfig, WINPTY_MOUSE_MODE_AUTO); + //winpty_config_set_agent_timeout(); + + //start agent + m_ptyHandler = winpty_open(startConfig, &errorPtr); + winpty_config_free(startConfig); //start config is local var, free it after use + + if (m_ptyHandler == nullptr) + { + m_lastError = QString("WinPty Error: start agent -> %1").arg(castErrorToString(errorPtr)); + return false; + } + winpty_error_free(errorPtr); + + //create spawn config + winpty_spawn_config_t* spawnConfig = winpty_spawn_config_new(WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, m_shellPath.toStdWString().c_str(), + //commandLine.toStdWString().c_str(), cwd.toStdWString().c_str(), + arguments.join(" ").toStdWString().c_str(), workingDir.toStdWString().c_str(), + env.c_str(), + &errorPtr); + + if (spawnConfig == nullptr) + { + m_lastError = QString("WinPty Error: create spawn config -> %1").arg(castErrorToString(errorPtr)); + return false; + } + winpty_error_free(errorPtr); + + //spawn the new process + BOOL spawnSuccess = winpty_spawn(m_ptyHandler, spawnConfig, &m_innerHandle, nullptr, nullptr, &errorPtr); + winpty_spawn_config_free(spawnConfig); //spawn config is local var, free it after use + if (!spawnSuccess) + { + m_lastError = QString("WinPty Error: start terminal process -> %1").arg(castErrorToString(errorPtr)); + return false; + } + winpty_error_free(errorPtr); + + m_pid = (int)GetProcessId(m_innerHandle); + + m_outSocket = new QLocalSocket(); + + // Notify when the shell process has been terminated + m_shellCloseWaitNotifier = new QWinEventNotifier(m_innerHandle, notifier()); + QObject::connect(m_shellCloseWaitNotifier, + &QWinEventNotifier::activated, + notifier(), + [this](HANDLE hEvent) { + DWORD exitCode = 0; + GetExitCodeProcess(hEvent, &exitCode); + m_exitCode = exitCode; + // Do not respawn if the object is about to be destructed + if (!m_aboutToDestruct) + emit notifier()->aboutToClose(); + m_shellCloseWaitNotifier->setEnabled(false); + }); + + //get pipe names + LPCWSTR conInPipeName = winpty_conin_name(m_ptyHandler); + m_conInName = QString::fromStdWString(std::wstring(conInPipeName)); + m_inSocket = new QLocalSocket(); + m_inSocket->connectToServer(m_conInName, QIODevice::WriteOnly); + m_inSocket->waitForConnected(); + + LPCWSTR conOutPipeName = winpty_conout_name(m_ptyHandler); + m_conOutName = QString::fromStdWString(std::wstring(conOutPipeName)); + m_outSocket->connectToServer(m_conOutName, QIODevice::ReadOnly); + m_outSocket->waitForConnected(); + + if (m_inSocket->state() != QLocalSocket::ConnectedState && m_outSocket->state() != QLocalSocket::ConnectedState) + { + m_lastError = QString("WinPty Error: Unable to connect local sockets -> %1 / %2").arg(m_inSocket->errorString()).arg(m_outSocket->errorString()); + m_inSocket->deleteLater(); + m_outSocket->deleteLater(); + m_inSocket = nullptr; + m_outSocket = nullptr; + return false; + } + + return true; +} + +bool WinPtyProcess::resize(qint16 cols, qint16 rows) +{ + if (m_ptyHandler == nullptr) + { + return false; + } + + bool res = winpty_set_size(m_ptyHandler, cols, rows, nullptr); + + if (res) + { + m_size = QPair(cols, rows); + } + + return res; +} + +bool WinPtyProcess::kill() +{ + bool exitCode = false; + if (m_innerHandle != nullptr && m_ptyHandler != nullptr) { + m_aboutToDestruct = true; + //disconnect all signals (readyRead, ...) + m_inSocket->disconnect(); + m_outSocket->disconnect(); + + //disconnect for server + m_inSocket->disconnectFromServer(); + m_outSocket->disconnectFromServer(); + + m_inSocket->deleteLater(); + m_outSocket->deleteLater(); + + m_inSocket = nullptr; + m_outSocket = nullptr; + + winpty_free(m_ptyHandler); + exitCode = CloseHandle(m_innerHandle); + + delete m_shellCloseWaitNotifier; + m_shellCloseWaitNotifier = nullptr; + + m_ptyHandler = nullptr; + m_innerHandle = nullptr; + m_conInName = QString(); + m_conOutName = QString(); + m_pid = 0; + } + return exitCode; +} + +IPtyProcess::PtyType WinPtyProcess::type() +{ + return PtyType::WinPty; +} + +QString WinPtyProcess::dumpDebugInfo() +{ +#ifdef PTYQT_DEBUG + return QString("PID: %1, ConIn: %2, ConOut: %3, Type: %4, Cols: %5, Rows: %6, IsRunning: %7, Shell: %8") + .arg(m_pid).arg(m_conInName).arg(m_conOutName).arg(type()) + .arg(m_size.first).arg(m_size.second).arg(m_ptyHandler != nullptr) + .arg(m_shellPath); +#else + return QString("Nothing..."); +#endif +} + +QIODevice *WinPtyProcess::notifier() +{ + return m_outSocket; +} + +QByteArray WinPtyProcess::readAll() +{ + return m_outSocket->readAll(); +} + +qint64 WinPtyProcess::write(const QByteArray &byteArray) +{ + return m_inSocket->write(byteArray); +} + +bool WinPtyProcess::isAvailable() +{ +#ifdef PTYQT_BUILD_STATIC + return QFile::exists(QCoreApplication::applicationDirPath() + "/" + WINPTY_AGENT_NAME); +#elif PTYQT_BUILD_DYNAMIC + return QFile::exists(QCoreApplication::applicationDirPath() + "/" + WINPTY_AGENT_NAME) + && QFile::exists(QCoreApplication::applicationDirPath() + "/" + WINPTY_DLL_NAME); +#endif + return true; +} + +void WinPtyProcess::moveToThread(QThread *targetThread) +{ + m_inSocket->moveToThread(targetThread); + m_outSocket->moveToThread(targetThread); +} diff --git a/src/libs/3rdparty/libptyqt/winptyprocess.h b/src/libs/3rdparty/libptyqt/winptyprocess.h new file mode 100644 index 00000000000..0bfb27c02c4 --- /dev/null +++ b/src/libs/3rdparty/libptyqt/winptyprocess.h @@ -0,0 +1,43 @@ +#ifndef WINPTYPROCESS_H +#define WINPTYPROCESS_H + +#include "iptyprocess.h" +#include "winpty.h" + +class QLocalSocket; +class QWinEventNotifier; + +class WinPtyProcess : public IPtyProcess +{ +public: + WinPtyProcess(); + ~WinPtyProcess(); + + bool startProcess(const QString &executable, + const QStringList &arguments, + const QString &workingDir, + QStringList environment, + qint16 cols, + qint16 rows); + bool resize(qint16 cols, qint16 rows); + bool kill(); + PtyType type(); + QString dumpDebugInfo(); + QIODevice *notifier(); + QByteArray readAll(); + qint64 write(const QByteArray &byteArray); + bool isAvailable(); + void moveToThread(QThread *targetThread); + +private: + winpty_t *m_ptyHandler; + HANDLE m_innerHandle; + QString m_conInName; + QString m_conOutName; + QLocalSocket *m_inSocket; + QLocalSocket *m_outSocket; + bool m_aboutToDestruct{false}; + QWinEventNotifier* m_shellCloseWaitNotifier; +}; + +#endif // WINPTYPROCESS_H diff --git a/src/libs/3rdparty/libvterm/CMakeLists.txt b/src/libs/3rdparty/libvterm/CMakeLists.txt new file mode 100644 index 00000000000..232217d9f58 --- /dev/null +++ b/src/libs/3rdparty/libvterm/CMakeLists.txt @@ -0,0 +1,18 @@ +add_qtc_library(libvterm STATIC + PUBLIC_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/include + PROPERTIES QT_COMPILE_OPTIONS_DISABLE_WARNINGS ON + SOURCES + src/encoding.c + src/fullwidth.inc + src/keyboard.c + src/mouse.c + src/parser.c + src/pen.c + src/rect.h + src/screen.c + src/state.c + src/unicode.c + src/utf8.h + src/vterm.c + src/vterm_internal.h +) diff --git a/src/libs/3rdparty/libvterm/CONTRIBUTING b/src/libs/3rdparty/libvterm/CONTRIBUTING new file mode 100644 index 00000000000..e9a8f0c3315 --- /dev/null +++ b/src/libs/3rdparty/libvterm/CONTRIBUTING @@ -0,0 +1,22 @@ +How to Contribute +----------------- + +The main resources for this library are: + + Launchpad + https://launchpad.net/libvterm + + IRC: + ##tty or #tickit on irc.libera.chat + + Email: + Paul "LeoNerd" Evans + + +Bug reports and feature requests can be sent to any of the above resources. + +New features, bug patches, etc.. should in the first instance be discussed via +any of the resources listed above, before starting work on the actual code. +There may be future plans or development already in-progress that could be +affected so it is better to discuss the ideas first before starting work +actually writing any code. diff --git a/src/libs/3rdparty/libvterm/LICENSE b/src/libs/3rdparty/libvterm/LICENSE new file mode 100644 index 00000000000..0d051634b28 --- /dev/null +++ b/src/libs/3rdparty/libvterm/LICENSE @@ -0,0 +1,23 @@ + + +The MIT License + +Copyright (c) 2008 Paul Evans + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/libs/3rdparty/libvterm/include/vterm.h b/src/libs/3rdparty/libvterm/include/vterm.h new file mode 100644 index 00000000000..cb16ff2a044 --- /dev/null +++ b/src/libs/3rdparty/libvterm/include/vterm.h @@ -0,0 +1,637 @@ +#ifndef __VTERM_H__ +#define __VTERM_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include "vterm_keycodes.h" + +#define VTERM_VERSION_MAJOR 0 +#define VTERM_VERSION_MINOR 3 + +#define VTERM_CHECK_VERSION \ + vterm_check_version(VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR) + +/* Any cell can contain at most one basic printing character and 5 combining + * characters. This number could be changed but will be ABI-incompatible if + * you do */ +#define VTERM_MAX_CHARS_PER_CELL 6 + +typedef struct VTerm VTerm; +typedef struct VTermState VTermState; +typedef struct VTermScreen VTermScreen; + +typedef struct { + int row; + int col; +} VTermPos; + +/* some small utility functions; we can just keep these static here */ + +/* order points by on-screen flow order */ +static inline int vterm_pos_cmp(VTermPos a, VTermPos b) +{ + return (a.row == b.row) ? a.col - b.col : a.row - b.row; +} + +typedef struct { + int start_row; + int end_row; + int start_col; + int end_col; +} VTermRect; + +/* true if the rect contains the point */ +static inline int vterm_rect_contains(VTermRect r, VTermPos p) +{ + return p.row >= r.start_row && p.row < r.end_row && + p.col >= r.start_col && p.col < r.end_col; +} + +/* move a rect */ +static inline void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta) +{ + rect->start_row += row_delta; rect->end_row += row_delta; + rect->start_col += col_delta; rect->end_col += col_delta; +} + +/** + * Bit-field describing the content of the tagged union `VTermColor`. + */ +typedef enum { + /** + * If the lower bit of `type` is not set, the colour is 24-bit RGB. + */ + VTERM_COLOR_RGB = 0x00, + + /** + * The colour is an index into a palette of 256 colours. + */ + VTERM_COLOR_INDEXED = 0x01, + + /** + * Mask that can be used to extract the RGB/Indexed bit. + */ + VTERM_COLOR_TYPE_MASK = 0x01, + + /** + * If set, indicates that this colour should be the default foreground + * color, i.e. there was no SGR request for another colour. When + * rendering this colour it is possible to ignore "idx" and just use a + * colour that is not in the palette. + */ + VTERM_COLOR_DEFAULT_FG = 0x02, + + /** + * If set, indicates that this colour should be the default background + * color, i.e. there was no SGR request for another colour. A common + * option when rendering this colour is to not render a background at + * all, for example by rendering the window transparently at this spot. + */ + VTERM_COLOR_DEFAULT_BG = 0x04, + + /** + * Mask that can be used to extract the default foreground/background bit. + */ + VTERM_COLOR_DEFAULT_MASK = 0x06 +} VTermColorType; + +/** + * Returns true if the VTERM_COLOR_RGB `type` flag is set, indicating that the + * given VTermColor instance is an indexed colour. + */ +#define VTERM_COLOR_IS_INDEXED(col) \ + (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_INDEXED) + +/** + * Returns true if the VTERM_COLOR_INDEXED `type` flag is set, indicating that + * the given VTermColor instance is an rgb colour. + */ +#define VTERM_COLOR_IS_RGB(col) \ + (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_RGB) + +/** + * Returns true if the VTERM_COLOR_DEFAULT_FG `type` flag is set, indicating + * that the given VTermColor instance corresponds to the default foreground + * color. + */ +#define VTERM_COLOR_IS_DEFAULT_FG(col) \ + (!!((col)->type & VTERM_COLOR_DEFAULT_FG)) + +/** + * Returns true if the VTERM_COLOR_DEFAULT_BG `type` flag is set, indicating + * that the given VTermColor instance corresponds to the default background + * color. + */ +#define VTERM_COLOR_IS_DEFAULT_BG(col) \ + (!!((col)->type & VTERM_COLOR_DEFAULT_BG)) + +/** + * Tagged union storing either an RGB color or an index into a colour palette. + * In order to convert indexed colours to RGB, you may use the + * vterm_state_convert_color_to_rgb() or vterm_screen_convert_color_to_rgb() + * functions which lookup the RGB colour from the palette maintained by a + * VTermState or VTermScreen instance. + */ +typedef union { + /** + * Tag indicating which union member is actually valid. This variable + * coincides with the `type` member of the `rgb` and the `indexed` struct + * in memory. Please use the `VTERM_COLOR_IS_*` test macros to check whether + * a particular type flag is set. + */ + uint8_t type; + + /** + * Valid if `VTERM_COLOR_IS_RGB(type)` is true. Holds the RGB colour values. + */ + struct { + /** + * Same as the top-level `type` member stored in VTermColor. + */ + uint8_t type; + + /** + * The actual 8-bit red, green, blue colour values. + */ + uint8_t red, green, blue; + } rgb; + + /** + * If `VTERM_COLOR_IS_INDEXED(type)` is true, this member holds the index into + * the colour palette. + */ + struct { + /** + * Same as the top-level `type` member stored in VTermColor. + */ + uint8_t type; + + /** + * Index into the colour map. + */ + uint8_t idx; + } indexed; +} VTermColor; + +/** + * Constructs a new VTermColor instance representing the given RGB values. + */ +static inline void vterm_color_rgb(VTermColor *col, uint8_t red, uint8_t green, + uint8_t blue) +{ + col->type = VTERM_COLOR_RGB; + col->rgb.red = red; + col->rgb.green = green; + col->rgb.blue = blue; +} + +/** + * Construct a new VTermColor instance representing an indexed color with the + * given index. + */ +static inline void vterm_color_indexed(VTermColor *col, uint8_t idx) +{ + col->type = VTERM_COLOR_INDEXED; + col->indexed.idx = idx; +} + +/** + * Compares two colours. Returns true if the colors are equal, false otherwise. + */ +int vterm_color_is_equal(const VTermColor *a, const VTermColor *b); + +typedef enum { + /* VTERM_VALUETYPE_NONE = 0 */ + VTERM_VALUETYPE_BOOL = 1, + VTERM_VALUETYPE_INT, + VTERM_VALUETYPE_STRING, + VTERM_VALUETYPE_COLOR, + + VTERM_N_VALUETYPES +} VTermValueType; + +typedef struct { + const char *str; + size_t len : 30; + bool initial : 1; + bool final : 1; +} VTermStringFragment; + +typedef union { + int boolean; + int number; + VTermStringFragment string; + VTermColor color; +} VTermValue; + +typedef enum { + /* VTERM_ATTR_NONE = 0 */ + VTERM_ATTR_BOLD = 1, // bool: 1, 22 + VTERM_ATTR_UNDERLINE, // number: 4, 21, 24 + VTERM_ATTR_ITALIC, // bool: 3, 23 + VTERM_ATTR_BLINK, // bool: 5, 25 + VTERM_ATTR_REVERSE, // bool: 7, 27 + VTERM_ATTR_CONCEAL, // bool: 8, 28 + VTERM_ATTR_STRIKE, // bool: 9, 29 + VTERM_ATTR_FONT, // number: 10-19 + VTERM_ATTR_FOREGROUND, // color: 30-39 90-97 + VTERM_ATTR_BACKGROUND, // color: 40-49 100-107 + VTERM_ATTR_SMALL, // bool: 73, 74, 75 + VTERM_ATTR_BASELINE, // number: 73, 74, 75 + + VTERM_N_ATTRS +} VTermAttr; + +typedef enum { + /* VTERM_PROP_NONE = 0 */ + VTERM_PROP_CURSORVISIBLE = 1, // bool + VTERM_PROP_CURSORBLINK, // bool + VTERM_PROP_ALTSCREEN, // bool + VTERM_PROP_TITLE, // string + VTERM_PROP_ICONNAME, // string + VTERM_PROP_REVERSE, // bool + VTERM_PROP_CURSORSHAPE, // number + VTERM_PROP_MOUSE, // number + + VTERM_N_PROPS +} VTermProp; + +enum { + VTERM_PROP_CURSORSHAPE_BLOCK = 1, + VTERM_PROP_CURSORSHAPE_UNDERLINE, + VTERM_PROP_CURSORSHAPE_BAR_LEFT, + + VTERM_N_PROP_CURSORSHAPES +}; + +enum { + VTERM_PROP_MOUSE_NONE = 0, + VTERM_PROP_MOUSE_CLICK, + VTERM_PROP_MOUSE_DRAG, + VTERM_PROP_MOUSE_MOVE, + + VTERM_N_PROP_MOUSES +}; + +typedef enum { + VTERM_SELECTION_CLIPBOARD = (1<<0), + VTERM_SELECTION_PRIMARY = (1<<1), + VTERM_SELECTION_SECONDARY = (1<<2), + VTERM_SELECTION_SELECT = (1<<3), + VTERM_SELECTION_CUT0 = (1<<4), /* also CUT1 .. CUT7 by bitshifting */ +} VTermSelectionMask; + +typedef struct { + const uint32_t *chars; + int width; + unsigned int protected_cell:1; /* DECSCA-protected against DECSEL/DECSED */ + unsigned int dwl:1; /* DECDWL or DECDHL double-width line */ + unsigned int dhl:2; /* DECDHL double-height line (1=top 2=bottom) */ +} VTermGlyphInfo; + +typedef struct { + unsigned int doublewidth:1; /* DECDWL or DECDHL line */ + unsigned int doubleheight:2; /* DECDHL line (1=top 2=bottom) */ + unsigned int continuation:1; /* Line is a flow continuation of the previous */ +} VTermLineInfo; + +/* Copies of VTermState fields that the 'resize' callback might have reason to + * edit. 'resize' callback gets total control of these fields and may + * free-and-reallocate them if required. They will be copied back from the + * struct after the callback has returned. + */ +typedef struct { + VTermPos pos; /* current cursor position */ + VTermLineInfo *lineinfos[2]; /* [1] may be NULL */ +} VTermStateFields; + +typedef struct { + /* libvterm relies on this memory to be zeroed out before it is returned + * by the allocator. */ + void *(*malloc)(size_t size, void *allocdata); + void (*free)(void *ptr, void *allocdata); +} VTermAllocatorFunctions; + +void vterm_check_version(int major, int minor); + +struct VTermBuilder { + int ver; /* currently unused but reserved for some sort of ABI version flag */ + + int rows, cols; + + const VTermAllocatorFunctions *allocator; + void *allocdata; + + /* Override default sizes for various structures */ + size_t outbuffer_len; /* default: 4096 */ + size_t tmpbuffer_len; /* default: 4096 */ +}; + +VTerm *vterm_build(const struct VTermBuilder *builder); + +/* A convenient shortcut for default cases */ +VTerm *vterm_new(int rows, int cols); +/* This shortcuts are generally discouraged in favour of just using vterm_build() */ +VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata); + +void vterm_free(VTerm* vt); + +void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp); +void vterm_set_size(VTerm *vt, int rows, int cols); + +int vterm_get_utf8(const VTerm *vt); +void vterm_set_utf8(VTerm *vt, int is_utf8); + +size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len); + +/* Setting output callback will override the buffer logic */ +typedef void VTermOutputCallback(const char *s, size_t len, void *user); +void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user); + +/* These buffer functions only work if output callback is NOT set + * These are deprecated and will be removed in a later version */ +size_t vterm_output_get_buffer_size(const VTerm *vt); +size_t vterm_output_get_buffer_current(const VTerm *vt); +size_t vterm_output_get_buffer_remaining(const VTerm *vt); + +/* This too */ +size_t vterm_output_read(VTerm *vt, char *buffer, size_t len); + +void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod); +void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod); + +void vterm_keyboard_start_paste(VTerm *vt); +void vterm_keyboard_end_paste(VTerm *vt); + +void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod); +void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod); + +// ------------ +// Parser layer +// ------------ + +/* Flag to indicate non-final subparameters in a single CSI parameter. + * Consider + * CSI 1;2:3:4;5a + * 1 4 and 5 are final. + * 2 and 3 are non-final and will have this bit set + * + * Don't confuse this with the final byte of the CSI escape; 'a' in this case. + */ +#define CSI_ARG_FLAG_MORE (1U<<31) +#define CSI_ARG_MASK (~(1U<<31)) + +#define CSI_ARG_HAS_MORE(a) ((a) & CSI_ARG_FLAG_MORE) +#define CSI_ARG(a) ((a) & CSI_ARG_MASK) + +/* Can't use -1 to indicate a missing argument; use this instead */ +#define CSI_ARG_MISSING ((1UL<<31)-1) + +#define CSI_ARG_IS_MISSING(a) (CSI_ARG(a) == CSI_ARG_MISSING) +#define CSI_ARG_OR(a,def) (CSI_ARG(a) == CSI_ARG_MISSING ? (def) : CSI_ARG(a)) +#define CSI_ARG_COUNT(a) (CSI_ARG(a) == CSI_ARG_MISSING || CSI_ARG(a) == 0 ? 1 : CSI_ARG(a)) + +typedef struct { + int (*text)(const char *bytes, size_t len, void *user); + int (*control)(unsigned char control, void *user); + int (*escape)(const char *bytes, size_t len, void *user); + int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user); + int (*osc)(int command, VTermStringFragment frag, void *user); + int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user); + int (*apc)(VTermStringFragment frag, void *user); + int (*pm)(VTermStringFragment frag, void *user); + int (*sos)(VTermStringFragment frag, void *user); + int (*resize)(int rows, int cols, void *user); +} VTermParserCallbacks; + +void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user); +void *vterm_parser_get_cbdata(VTerm *vt); + +/* Normally NUL, CAN, SUB and DEL are ignored. Setting this true causes them + * to be emitted by the 'control' callback + */ +void vterm_parser_set_emit_nul(VTerm *vt, bool emit); + +// ----------- +// State layer +// ----------- + +typedef struct { + int (*putglyph)(VTermGlyphInfo *info, VTermPos pos, void *user); + int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user); + int (*scrollrect)(VTermRect rect, int downward, int rightward, void *user); + int (*moverect)(VTermRect dest, VTermRect src, void *user); + int (*erase)(VTermRect rect, int selective, void *user); + int (*initpen)(void *user); + int (*setpenattr)(VTermAttr attr, VTermValue *val, void *user); + int (*settermprop)(VTermProp prop, VTermValue *val, void *user); + int (*bell)(void *user); + int (*resize)(int rows, int cols, VTermStateFields *fields, void *user); + int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user); + int (*sb_clear)(void *user); +} VTermStateCallbacks; + +typedef struct { + int (*control)(unsigned char control, void *user); + int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user); + int (*osc)(int command, VTermStringFragment frag, void *user); + int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user); + int (*apc)(VTermStringFragment frag, void *user); + int (*pm)(VTermStringFragment frag, void *user); + int (*sos)(VTermStringFragment frag, void *user); +} VTermStateFallbacks; + +typedef struct { + int (*set)(VTermSelectionMask mask, VTermStringFragment frag, void *user); + int (*query)(VTermSelectionMask mask, void *user); +} VTermSelectionCallbacks; + +VTermState *vterm_obtain_state(VTerm *vt); + +void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user); +void *vterm_state_get_cbdata(VTermState *state); + +void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *fallbacks, void *user); +void *vterm_state_get_unrecognised_fbdata(VTermState *state); + +void vterm_state_reset(VTermState *state, int hard); +void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos); +void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg); +void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col); +void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg); +void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col); +void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright); +int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val); +int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val); +void vterm_state_focus_in(VTermState *state); +void vterm_state_focus_out(VTermState *state); +const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row); + +/** + * Makes sure that the given color `col` is indeed an RGB colour. After this + * function returns, VTERM_COLOR_IS_RGB(col) will return true, while all other + * flags stored in `col->type` will have been reset. + * + * @param state is the VTermState instance from which the colour palette should + * be extracted. + * @param col is a pointer at the VTermColor instance that should be converted + * to an RGB colour. + */ +void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col); + +void vterm_state_set_selection_callbacks(VTermState *state, const VTermSelectionCallbacks *callbacks, void *user, + char *buffer, size_t buflen); + +void vterm_state_send_selection(VTermState *state, VTermSelectionMask mask, VTermStringFragment frag); + +// ------------ +// Screen layer +// ------------ + +typedef struct { + unsigned int bold : 1; + unsigned int underline : 3; + unsigned int italic : 1; + unsigned int blink : 1; + unsigned int reverse : 1; + unsigned int conceal : 1; + unsigned int strike : 1; + unsigned int font : 4; /* 0 to 9 */ + unsigned int dwl : 1; /* On a DECDWL or DECDHL line */ + unsigned int dhl : 2; /* On a DECDHL line (1=top 2=bottom) */ + unsigned int small : 1; + unsigned int baseline : 2; +} VTermScreenCellAttrs; + +enum { + VTERM_UNDERLINE_OFF, + VTERM_UNDERLINE_SINGLE, + VTERM_UNDERLINE_DOUBLE, + VTERM_UNDERLINE_CURLY, + VTERM_UNDERLINE_DOTTED, + VTERM_UNDERLINE_DASHED +}; + +enum { + VTERM_BASELINE_NORMAL, + VTERM_BASELINE_RAISE, + VTERM_BASELINE_LOWER, +}; + +typedef struct { + uint32_t chars[VTERM_MAX_CHARS_PER_CELL]; + char width; + VTermScreenCellAttrs attrs; + VTermColor fg, bg; +} VTermScreenCell; + +typedef struct { + int (*damage)(VTermRect rect, void *user); + int (*moverect)(VTermRect dest, VTermRect src, void *user); + int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user); + int (*settermprop)(VTermProp prop, VTermValue *val, void *user); + int (*bell)(void *user); + int (*resize)(int rows, int cols, void *user); + int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user); + int (*sb_popline)(int cols, VTermScreenCell *cells, void *user); + int (*sb_clear)(void* user); +} VTermScreenCallbacks; + +VTermScreen *vterm_obtain_screen(VTerm *vt); + +void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user); +void *vterm_screen_get_cbdata(VTermScreen *screen); + +void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermStateFallbacks *fallbacks, void *user); +void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen); + +void vterm_screen_enable_reflow(VTermScreen *screen, bool reflow); + +// Back-compat alias for the brief time it was in 0.3-RC1 +#define vterm_screen_set_reflow vterm_screen_enable_reflow + +void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen); + +typedef enum { + VTERM_DAMAGE_CELL, /* every cell */ + VTERM_DAMAGE_ROW, /* entire rows */ + VTERM_DAMAGE_SCREEN, /* entire screen */ + VTERM_DAMAGE_SCROLL, /* entire screen + scrollrect */ + + VTERM_N_DAMAGES +} VTermDamageSize; + +void vterm_screen_flush_damage(VTermScreen *screen); +void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size); + +void vterm_screen_reset(VTermScreen *screen, int hard); + +/* Neither of these functions NUL-terminate the buffer */ +size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect); +size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect); + +typedef enum { + VTERM_ATTR_BOLD_MASK = 1 << 0, + VTERM_ATTR_UNDERLINE_MASK = 1 << 1, + VTERM_ATTR_ITALIC_MASK = 1 << 2, + VTERM_ATTR_BLINK_MASK = 1 << 3, + VTERM_ATTR_REVERSE_MASK = 1 << 4, + VTERM_ATTR_STRIKE_MASK = 1 << 5, + VTERM_ATTR_FONT_MASK = 1 << 6, + VTERM_ATTR_FOREGROUND_MASK = 1 << 7, + VTERM_ATTR_BACKGROUND_MASK = 1 << 8, + VTERM_ATTR_CONCEAL_MASK = 1 << 9, + VTERM_ATTR_SMALL_MASK = 1 << 10, + VTERM_ATTR_BASELINE_MASK = 1 << 11, + + VTERM_ALL_ATTRS_MASK = (1 << 12) - 1 +} VTermAttrMask; + +int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs); + +int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell); + +int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos); + +/** + * Same as vterm_state_convert_color_to_rgb(), but takes a `screen` instead of a `state` + * instance. + */ +void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col); + +/** + * Similar to vterm_state_set_default_colors(), but also resets colours in the + * screen buffer(s) + */ +void vterm_screen_set_default_colors(VTermScreen *screen, const VTermColor *default_fg, const VTermColor *default_bg); + +// --------- +// Utilities +// --------- + +VTermValueType vterm_get_attr_type(VTermAttr attr); +VTermValueType vterm_get_prop_type(VTermProp prop); + +void vterm_scroll_rect(VTermRect rect, + int downward, + int rightward, + int (*moverect)(VTermRect src, VTermRect dest, void *user), + int (*eraserect)(VTermRect rect, int selective, void *user), + void *user); + +void vterm_copy_cells(VTermRect dest, + VTermRect src, + void (*copycell)(VTermPos dest, VTermPos src, void *user), + void *user); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/libs/3rdparty/libvterm/include/vterm_keycodes.h b/src/libs/3rdparty/libvterm/include/vterm_keycodes.h new file mode 100644 index 00000000000..661759febd4 --- /dev/null +++ b/src/libs/3rdparty/libvterm/include/vterm_keycodes.h @@ -0,0 +1,61 @@ +#ifndef __VTERM_INPUT_H__ +#define __VTERM_INPUT_H__ + +typedef enum { + VTERM_MOD_NONE = 0x00, + VTERM_MOD_SHIFT = 0x01, + VTERM_MOD_ALT = 0x02, + VTERM_MOD_CTRL = 0x04, + + VTERM_ALL_MODS_MASK = 0x07 +} VTermModifier; + +typedef enum { + VTERM_KEY_NONE, + + VTERM_KEY_ENTER, + VTERM_KEY_TAB, + VTERM_KEY_BACKSPACE, + VTERM_KEY_ESCAPE, + + VTERM_KEY_UP, + VTERM_KEY_DOWN, + VTERM_KEY_LEFT, + VTERM_KEY_RIGHT, + + VTERM_KEY_INS, + VTERM_KEY_DEL, + VTERM_KEY_HOME, + VTERM_KEY_END, + VTERM_KEY_PAGEUP, + VTERM_KEY_PAGEDOWN, + + VTERM_KEY_FUNCTION_0 = 256, + VTERM_KEY_FUNCTION_MAX = VTERM_KEY_FUNCTION_0 + 255, + + VTERM_KEY_KP_0, + VTERM_KEY_KP_1, + VTERM_KEY_KP_2, + VTERM_KEY_KP_3, + VTERM_KEY_KP_4, + VTERM_KEY_KP_5, + VTERM_KEY_KP_6, + VTERM_KEY_KP_7, + VTERM_KEY_KP_8, + VTERM_KEY_KP_9, + VTERM_KEY_KP_MULT, + VTERM_KEY_KP_PLUS, + VTERM_KEY_KP_COMMA, + VTERM_KEY_KP_MINUS, + VTERM_KEY_KP_PERIOD, + VTERM_KEY_KP_DIVIDE, + VTERM_KEY_KP_ENTER, + VTERM_KEY_KP_EQUAL, + + VTERM_KEY_MAX, // Must be last + VTERM_N_KEYS = VTERM_KEY_MAX +} VTermKey; + +#define VTERM_KEY_FUNCTION(n) (VTERM_KEY_FUNCTION_0+(n)) + +#endif diff --git a/src/libs/3rdparty/libvterm/src/encoding.c b/src/libs/3rdparty/libvterm/src/encoding.c new file mode 100644 index 00000000000..434ac3f99b7 --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/encoding.c @@ -0,0 +1,230 @@ +#include "vterm_internal.h" + +#define UNICODE_INVALID 0xFFFD + +#if defined(DEBUG) && DEBUG > 1 +# define DEBUG_PRINT_UTF8 +#endif + +struct UTF8DecoderData { + // number of bytes remaining in this codepoint + int bytes_remaining; + + // number of bytes total in this codepoint once it's finished + // (for detecting overlongs) + int bytes_total; + + int this_cp; +}; + +static void init_utf8(VTermEncoding *enc, void *data_) +{ + struct UTF8DecoderData *data = data_; + + data->bytes_remaining = 0; + data->bytes_total = 0; +} + +static void decode_utf8(VTermEncoding *enc, void *data_, + uint32_t cp[], int *cpi, int cplen, + const char bytes[], size_t *pos, size_t bytelen) +{ + struct UTF8DecoderData *data = data_; + +#ifdef DEBUG_PRINT_UTF8 + printf("BEGIN UTF-8\n"); +#endif + + for(; *pos < bytelen && *cpi < cplen; (*pos)++) { + unsigned char c = bytes[*pos]; + +#ifdef DEBUG_PRINT_UTF8 + printf(" pos=%zd c=%02x rem=%d\n", *pos, c, data->bytes_remaining); +#endif + + if(c < 0x20) // C0 + return; + + else if(c >= 0x20 && c < 0x7f) { + if(data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + cp[(*cpi)++] = c; +#ifdef DEBUG_PRINT_UTF8 + printf(" UTF-8 char: U+%04x\n", c); +#endif + data->bytes_remaining = 0; + } + + else if(c == 0x7f) // DEL + return; + + else if(c >= 0x80 && c < 0xc0) { + if(!data->bytes_remaining) { + cp[(*cpi)++] = UNICODE_INVALID; + continue; + } + + data->this_cp <<= 6; + data->this_cp |= c & 0x3f; + data->bytes_remaining--; + + if(!data->bytes_remaining) { +#ifdef DEBUG_PRINT_UTF8 + printf(" UTF-8 raw char U+%04x bytelen=%d ", data->this_cp, data->bytes_total); +#endif + // Check for overlong sequences + switch(data->bytes_total) { + case 2: + if(data->this_cp < 0x0080) data->this_cp = UNICODE_INVALID; + break; + case 3: + if(data->this_cp < 0x0800) data->this_cp = UNICODE_INVALID; + break; + case 4: + if(data->this_cp < 0x10000) data->this_cp = UNICODE_INVALID; + break; + case 5: + if(data->this_cp < 0x200000) data->this_cp = UNICODE_INVALID; + break; + case 6: + if(data->this_cp < 0x4000000) data->this_cp = UNICODE_INVALID; + break; + } + // Now look for plain invalid ones + if((data->this_cp >= 0xD800 && data->this_cp <= 0xDFFF) || + data->this_cp == 0xFFFE || + data->this_cp == 0xFFFF) + data->this_cp = UNICODE_INVALID; +#ifdef DEBUG_PRINT_UTF8 + printf(" char: U+%04x\n", data->this_cp); +#endif + cp[(*cpi)++] = data->this_cp; + } + } + + else if(c >= 0xc0 && c < 0xe0) { + if(data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x1f; + data->bytes_total = 2; + data->bytes_remaining = 1; + } + + else if(c >= 0xe0 && c < 0xf0) { + if(data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x0f; + data->bytes_total = 3; + data->bytes_remaining = 2; + } + + else if(c >= 0xf0 && c < 0xf8) { + if(data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x07; + data->bytes_total = 4; + data->bytes_remaining = 3; + } + + else if(c >= 0xf8 && c < 0xfc) { + if(data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x03; + data->bytes_total = 5; + data->bytes_remaining = 4; + } + + else if(c >= 0xfc && c < 0xfe) { + if(data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x01; + data->bytes_total = 6; + data->bytes_remaining = 5; + } + + else { + cp[(*cpi)++] = UNICODE_INVALID; + } + } +} + +static VTermEncoding encoding_utf8 = { + .init = &init_utf8, + .decode = &decode_utf8, +}; + +static void decode_usascii(VTermEncoding *enc, void *data, + uint32_t cp[], int *cpi, int cplen, + const char bytes[], size_t *pos, size_t bytelen) +{ + int is_gr = bytes[*pos] & 0x80; + + for(; *pos < bytelen && *cpi < cplen; (*pos)++) { + unsigned char c = bytes[*pos] ^ is_gr; + + if(c < 0x20 || c == 0x7f || c >= 0x80) + return; + + cp[(*cpi)++] = c; + } +} + +static VTermEncoding encoding_usascii = { + .decode = &decode_usascii, +}; + +struct StaticTableEncoding { + const VTermEncoding enc; + const uint32_t chars[128]; +}; + +static void decode_table(VTermEncoding *enc, void *data, + uint32_t cp[], int *cpi, int cplen, + const char bytes[], size_t *pos, size_t bytelen) +{ + struct StaticTableEncoding *table = (struct StaticTableEncoding *)enc; + int is_gr = bytes[*pos] & 0x80; + + for(; *pos < bytelen && *cpi < cplen; (*pos)++) { + unsigned char c = bytes[*pos] ^ is_gr; + + if(c < 0x20 || c == 0x7f || c >= 0x80) + return; + + if(table->chars[c]) + cp[(*cpi)++] = table->chars[c]; + else + cp[(*cpi)++] = c; + } +} + +#include "encoding/DECdrawing.inc" +#include "encoding/uk.inc" + +static struct { + VTermEncodingType type; + char designation; + VTermEncoding *enc; +} +encodings[] = { + { ENC_UTF8, 'u', &encoding_utf8 }, + { ENC_SINGLE_94, '0', (VTermEncoding*)&encoding_DECdrawing }, + { ENC_SINGLE_94, 'A', (VTermEncoding*)&encoding_uk }, + { ENC_SINGLE_94, 'B', &encoding_usascii }, + { 0 }, +}; + +/* This ought to be INTERNAL but isn't because it's used by unit testing */ +VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation) +{ + for(int i = 0; encodings[i].designation; i++) + if(encodings[i].type == type && encodings[i].designation == designation) + return encodings[i].enc; + return NULL; +} diff --git a/src/libs/3rdparty/libvterm/src/encoding/DECdrawing.inc b/src/libs/3rdparty/libvterm/src/encoding/DECdrawing.inc new file mode 100644 index 00000000000..47093ed0a81 --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/encoding/DECdrawing.inc @@ -0,0 +1,36 @@ +static const struct StaticTableEncoding encoding_DECdrawing = { + { .decode = &decode_table }, + { + [0x60] = 0x25C6, + [0x61] = 0x2592, + [0x62] = 0x2409, + [0x63] = 0x240C, + [0x64] = 0x240D, + [0x65] = 0x240A, + [0x66] = 0x00B0, + [0x67] = 0x00B1, + [0x68] = 0x2424, + [0x69] = 0x240B, + [0x6a] = 0x2518, + [0x6b] = 0x2510, + [0x6c] = 0x250C, + [0x6d] = 0x2514, + [0x6e] = 0x253C, + [0x6f] = 0x23BA, + [0x70] = 0x23BB, + [0x71] = 0x2500, + [0x72] = 0x23BC, + [0x73] = 0x23BD, + [0x74] = 0x251C, + [0x75] = 0x2524, + [0x76] = 0x2534, + [0x77] = 0x252C, + [0x78] = 0x2502, + [0x79] = 0x2A7D, + [0x7a] = 0x2A7E, + [0x7b] = 0x03C0, + [0x7c] = 0x2260, + [0x7d] = 0x00A3, + [0x7e] = 0x00B7, + } +}; diff --git a/src/libs/3rdparty/libvterm/src/encoding/uk.inc b/src/libs/3rdparty/libvterm/src/encoding/uk.inc new file mode 100644 index 00000000000..da1445decaf --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/encoding/uk.inc @@ -0,0 +1,6 @@ +static const struct StaticTableEncoding encoding_uk = { + { .decode = &decode_table }, + { + [0x23] = 0x00a3, + } +}; diff --git a/src/libs/3rdparty/libvterm/src/fullwidth.inc b/src/libs/3rdparty/libvterm/src/fullwidth.inc new file mode 100644 index 00000000000..a703529a76d --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/fullwidth.inc @@ -0,0 +1,111 @@ + { 0x1100, 0x115f }, + { 0x231a, 0x231b }, + { 0x2329, 0x232a }, + { 0x23e9, 0x23ec }, + { 0x23f0, 0x23f0 }, + { 0x23f3, 0x23f3 }, + { 0x25fd, 0x25fe }, + { 0x2614, 0x2615 }, + { 0x2648, 0x2653 }, + { 0x267f, 0x267f }, + { 0x2693, 0x2693 }, + { 0x26a1, 0x26a1 }, + { 0x26aa, 0x26ab }, + { 0x26bd, 0x26be }, + { 0x26c4, 0x26c5 }, + { 0x26ce, 0x26ce }, + { 0x26d4, 0x26d4 }, + { 0x26ea, 0x26ea }, + { 0x26f2, 0x26f3 }, + { 0x26f5, 0x26f5 }, + { 0x26fa, 0x26fa }, + { 0x26fd, 0x26fd }, + { 0x2705, 0x2705 }, + { 0x270a, 0x270b }, + { 0x2728, 0x2728 }, + { 0x274c, 0x274c }, + { 0x274e, 0x274e }, + { 0x2753, 0x2755 }, + { 0x2757, 0x2757 }, + { 0x2795, 0x2797 }, + { 0x27b0, 0x27b0 }, + { 0x27bf, 0x27bf }, + { 0x2b1b, 0x2b1c }, + { 0x2b50, 0x2b50 }, + { 0x2b55, 0x2b55 }, + { 0x2e80, 0x2e99 }, + { 0x2e9b, 0x2ef3 }, + { 0x2f00, 0x2fd5 }, + { 0x2ff0, 0x2ffb }, + { 0x3000, 0x303e }, + { 0x3041, 0x3096 }, + { 0x3099, 0x30ff }, + { 0x3105, 0x312f }, + { 0x3131, 0x318e }, + { 0x3190, 0x31ba }, + { 0x31c0, 0x31e3 }, + { 0x31f0, 0x321e }, + { 0x3220, 0x3247 }, + { 0x3250, 0x4dbf }, + { 0x4e00, 0xa48c }, + { 0xa490, 0xa4c6 }, + { 0xa960, 0xa97c }, + { 0xac00, 0xd7a3 }, + { 0xf900, 0xfaff }, + { 0xfe10, 0xfe19 }, + { 0xfe30, 0xfe52 }, + { 0xfe54, 0xfe66 }, + { 0xfe68, 0xfe6b }, + { 0xff01, 0xff60 }, + { 0xffe0, 0xffe6 }, + { 0x16fe0, 0x16fe3 }, + { 0x17000, 0x187f7 }, + { 0x18800, 0x18af2 }, + { 0x1b000, 0x1b11e }, + { 0x1b150, 0x1b152 }, + { 0x1b164, 0x1b167 }, + { 0x1b170, 0x1b2fb }, + { 0x1f004, 0x1f004 }, + { 0x1f0cf, 0x1f0cf }, + { 0x1f18e, 0x1f18e }, + { 0x1f191, 0x1f19a }, + { 0x1f200, 0x1f202 }, + { 0x1f210, 0x1f23b }, + { 0x1f240, 0x1f248 }, + { 0x1f250, 0x1f251 }, + { 0x1f260, 0x1f265 }, + { 0x1f300, 0x1f320 }, + { 0x1f32d, 0x1f335 }, + { 0x1f337, 0x1f37c }, + { 0x1f37e, 0x1f393 }, + { 0x1f3a0, 0x1f3ca }, + { 0x1f3cf, 0x1f3d3 }, + { 0x1f3e0, 0x1f3f0 }, + { 0x1f3f4, 0x1f3f4 }, + { 0x1f3f8, 0x1f43e }, + { 0x1f440, 0x1f440 }, + { 0x1f442, 0x1f4fc }, + { 0x1f4ff, 0x1f53d }, + { 0x1f54b, 0x1f54e }, + { 0x1f550, 0x1f567 }, + { 0x1f57a, 0x1f57a }, + { 0x1f595, 0x1f596 }, + { 0x1f5a4, 0x1f5a4 }, + { 0x1f5fb, 0x1f64f }, + { 0x1f680, 0x1f6c5 }, + { 0x1f6cc, 0x1f6cc }, + { 0x1f6d0, 0x1f6d2 }, + { 0x1f6d5, 0x1f6d5 }, + { 0x1f6eb, 0x1f6ec }, + { 0x1f6f4, 0x1f6fa }, + { 0x1f7e0, 0x1f7eb }, + { 0x1f90d, 0x1f971 }, + { 0x1f973, 0x1f976 }, + { 0x1f97a, 0x1f9a2 }, + { 0x1f9a5, 0x1f9aa }, + { 0x1f9ae, 0x1f9ca }, + { 0x1f9cd, 0x1f9ff }, + { 0x1fa70, 0x1fa73 }, + { 0x1fa78, 0x1fa7a }, + { 0x1fa80, 0x1fa82 }, + { 0x1fa90, 0x1fa95 }, diff --git a/src/libs/3rdparty/libvterm/src/keyboard.c b/src/libs/3rdparty/libvterm/src/keyboard.c new file mode 100644 index 00000000000..d31c8be12d3 --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/keyboard.c @@ -0,0 +1,226 @@ +#include "vterm_internal.h" + +#include + +#include "utf8.h" + +void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod) +{ + /* The shift modifier is never important for Unicode characters + * apart from Space + */ + if(c != ' ') + mod &= ~VTERM_MOD_SHIFT; + + if(mod == 0) { + // Normal text - ignore just shift + char str[6]; + int seqlen = fill_utf8(c, str); + vterm_push_output_bytes(vt, str, seqlen); + return; + } + + int needs_CSIu; + switch(c) { + /* Special Ctrl- letters that can't be represented elsewise */ + case 'i': case 'j': case 'm': case '[': + needs_CSIu = 1; + break; + /* Ctrl-\ ] ^ _ don't need CSUu */ + case '\\': case ']': case '^': case '_': + needs_CSIu = 0; + break; + /* Shift-space needs CSIu */ + case ' ': + needs_CSIu = !!(mod & VTERM_MOD_SHIFT); + break; + /* All other characters needs CSIu except for letters a-z */ + default: + needs_CSIu = (c < 'a' || c > 'z'); + } + + /* ALT we can just prefix with ESC; anything else requires CSI u */ + if(needs_CSIu && (mod & ~VTERM_MOD_ALT)) { + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod+1); + return; + } + + if(mod & VTERM_MOD_CTRL) + c &= 0x1f; + + vterm_push_output_sprintf(vt, "%s%c", mod & VTERM_MOD_ALT ? ESC_S : "", c); +} + +typedef struct { + enum { + KEYCODE_NONE, + KEYCODE_LITERAL, + KEYCODE_TAB, + KEYCODE_ENTER, + KEYCODE_SS3, + KEYCODE_CSI, + KEYCODE_CSI_CURSOR, + KEYCODE_CSINUM, + KEYCODE_KEYPAD, + } type; + char literal; + int csinum; +} keycodes_s; + +static keycodes_s keycodes[] = { + { KEYCODE_NONE }, // NONE + + { KEYCODE_ENTER, '\r' }, // ENTER + { KEYCODE_TAB, '\t' }, // TAB + { KEYCODE_LITERAL, '\x7f' }, // BACKSPACE == ASCII DEL + { KEYCODE_LITERAL, '\x1b' }, // ESCAPE + + { KEYCODE_CSI_CURSOR, 'A' }, // UP + { KEYCODE_CSI_CURSOR, 'B' }, // DOWN + { KEYCODE_CSI_CURSOR, 'D' }, // LEFT + { KEYCODE_CSI_CURSOR, 'C' }, // RIGHT + + { KEYCODE_CSINUM, '~', 2 }, // INS + { KEYCODE_CSINUM, '~', 3 }, // DEL + { KEYCODE_CSI_CURSOR, 'H' }, // HOME + { KEYCODE_CSI_CURSOR, 'F' }, // END + { KEYCODE_CSINUM, '~', 5 }, // PAGEUP + { KEYCODE_CSINUM, '~', 6 }, // PAGEDOWN +}; + +static keycodes_s keycodes_fn[] = { + { KEYCODE_NONE }, // F0 - shouldn't happen + { KEYCODE_SS3, 'P' }, // F1 + { KEYCODE_SS3, 'Q' }, // F2 + { KEYCODE_SS3, 'R' }, // F3 + { KEYCODE_SS3, 'S' }, // F4 + { KEYCODE_CSINUM, '~', 15 }, // F5 + { KEYCODE_CSINUM, '~', 17 }, // F6 + { KEYCODE_CSINUM, '~', 18 }, // F7 + { KEYCODE_CSINUM, '~', 19 }, // F8 + { KEYCODE_CSINUM, '~', 20 }, // F9 + { KEYCODE_CSINUM, '~', 21 }, // F10 + { KEYCODE_CSINUM, '~', 23 }, // F11 + { KEYCODE_CSINUM, '~', 24 }, // F12 +}; + +static keycodes_s keycodes_kp[] = { + { KEYCODE_KEYPAD, '0', 'p' }, // KP_0 + { KEYCODE_KEYPAD, '1', 'q' }, // KP_1 + { KEYCODE_KEYPAD, '2', 'r' }, // KP_2 + { KEYCODE_KEYPAD, '3', 's' }, // KP_3 + { KEYCODE_KEYPAD, '4', 't' }, // KP_4 + { KEYCODE_KEYPAD, '5', 'u' }, // KP_5 + { KEYCODE_KEYPAD, '6', 'v' }, // KP_6 + { KEYCODE_KEYPAD, '7', 'w' }, // KP_7 + { KEYCODE_KEYPAD, '8', 'x' }, // KP_8 + { KEYCODE_KEYPAD, '9', 'y' }, // KP_9 + { KEYCODE_KEYPAD, '*', 'j' }, // KP_MULT + { KEYCODE_KEYPAD, '+', 'k' }, // KP_PLUS + { KEYCODE_KEYPAD, ',', 'l' }, // KP_COMMA + { KEYCODE_KEYPAD, '-', 'm' }, // KP_MINUS + { KEYCODE_KEYPAD, '.', 'n' }, // KP_PERIOD + { KEYCODE_KEYPAD, '/', 'o' }, // KP_DIVIDE + { KEYCODE_KEYPAD, '\n', 'M' }, // KP_ENTER + { KEYCODE_KEYPAD, '=', 'X' }, // KP_EQUAL +}; + +void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod) +{ + if(key == VTERM_KEY_NONE) + return; + + keycodes_s k; + if(key < VTERM_KEY_FUNCTION_0) { + if(key >= sizeof(keycodes)/sizeof(keycodes[0])) + return; + k = keycodes[key]; + } + else if(key >= VTERM_KEY_FUNCTION_0 && key <= VTERM_KEY_FUNCTION_MAX) { + if((key - VTERM_KEY_FUNCTION_0) >= sizeof(keycodes_fn)/sizeof(keycodes_fn[0])) + return; + k = keycodes_fn[key - VTERM_KEY_FUNCTION_0]; + } + else if(key >= VTERM_KEY_KP_0) { + if((key - VTERM_KEY_KP_0) >= sizeof(keycodes_kp)/sizeof(keycodes_kp[0])) + return; + k = keycodes_kp[key - VTERM_KEY_KP_0]; + } + + switch(k.type) { + case KEYCODE_NONE: + break; + + case KEYCODE_TAB: + /* Shift-Tab is CSI Z but plain Tab is 0x09 */ + if(mod == VTERM_MOD_SHIFT) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z"); + else if(mod & VTERM_MOD_SHIFT) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod+1); + else + goto case_LITERAL; + break; + + case KEYCODE_ENTER: + /* Enter is CRLF in newline mode, but just LF in linefeed */ + if(vt->state->mode.newline) + vterm_push_output_sprintf(vt, "\r\n"); + else + goto case_LITERAL; + break; + + case KEYCODE_LITERAL: case_LITERAL: + if(mod & (VTERM_MOD_SHIFT|VTERM_MOD_CTRL)) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", k.literal, mod+1); + else + vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? ESC_S "%c" : "%c", k.literal); + break; + + case KEYCODE_SS3: case_SS3: + if(mod == 0) + vterm_push_output_sprintf_ctrl(vt, C1_SS3, "%c", k.literal); + else + goto case_CSI; + break; + + case KEYCODE_CSI: case_CSI: + if(mod == 0) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%c", k.literal); + else + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%d%c", mod + 1, k.literal); + break; + + case KEYCODE_CSINUM: + if(mod == 0) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d%c", k.csinum, k.literal); + else + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%d%c", k.csinum, mod + 1, k.literal); + break; + + case KEYCODE_CSI_CURSOR: + if(vt->state->mode.cursor) + goto case_SS3; + else + goto case_CSI; + + case KEYCODE_KEYPAD: + if(vt->state->mode.keypad) { + k.literal = k.csinum; + goto case_SS3; + } + else + goto case_LITERAL; + } +} + +void vterm_keyboard_start_paste(VTerm *vt) +{ + if(vt->state->mode.bracketpaste) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "200~"); +} + +void vterm_keyboard_end_paste(VTerm *vt) +{ + if(vt->state->mode.bracketpaste) + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "201~"); +} diff --git a/src/libs/3rdparty/libvterm/src/mouse.c b/src/libs/3rdparty/libvterm/src/mouse.c new file mode 100644 index 00000000000..bd713f8106a --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/mouse.c @@ -0,0 +1,99 @@ +#include "vterm_internal.h" + +#include "utf8.h" + +static void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row) +{ + modifiers <<= 2; + + switch(state->mouse_protocol) { + case MOUSE_X10: + if(col + 0x21 > 0xff) + col = 0xff - 0x21; + if(row + 0x21 > 0xff) + row = 0xff - 0x21; + + if(!pressed) + code = 3; + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%c%c%c", + (code | modifiers) + 0x20, col + 0x21, row + 0x21); + break; + + case MOUSE_UTF8: + { + char utf8[18]; size_t len = 0; + + if(!pressed) + code = 3; + + len += fill_utf8((code | modifiers) + 0x20, utf8 + len); + len += fill_utf8(col + 0x21, utf8 + len); + len += fill_utf8(row + 0x21, utf8 + len); + utf8[len] = 0; + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%s", utf8); + } + break; + + case MOUSE_SGR: + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "<%d;%d;%d%c", + code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm'); + break; + + case MOUSE_RXVT: + if(!pressed) + code = 3; + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%d;%d;%dM", + code | modifiers, col + 1, row + 1); + break; + } +} + +void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod) +{ + VTermState *state = vt->state; + + if(col == state->mouse_col && row == state->mouse_row) + return; + + state->mouse_col = col; + state->mouse_row = row; + + if((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons) || + (state->mouse_flags & MOUSE_WANT_MOVE)) { + int button = state->mouse_buttons & 0x01 ? 1 : + state->mouse_buttons & 0x02 ? 2 : + state->mouse_buttons & 0x04 ? 3 : 4; + output_mouse(state, button-1 + 0x20, 1, mod, col, row); + } +} + +void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod) +{ + VTermState *state = vt->state; + + int old_buttons = state->mouse_buttons; + + if(button > 0 && button <= 3) { + if(pressed) + state->mouse_buttons |= (1 << (button-1)); + else + state->mouse_buttons &= ~(1 << (button-1)); + } + + /* Most of the time we don't get button releases from 4/5 */ + if(state->mouse_buttons == old_buttons && button < 4) + return; + + if(!state->mouse_flags) + return; + + if(button < 4) { + output_mouse(state, button-1, pressed, mod, state->mouse_col, state->mouse_row); + } + else if(button < 6) { + output_mouse(state, button-4 + 0x40, pressed, mod, state->mouse_col, state->mouse_row); + } +} diff --git a/src/libs/3rdparty/libvterm/src/parser.c b/src/libs/3rdparty/libvterm/src/parser.c new file mode 100644 index 00000000000..b43a549cefc --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/parser.c @@ -0,0 +1,402 @@ +#include "vterm_internal.h" + +#include +#include + +#undef DEBUG_PARSER + +static bool is_intermed(unsigned char c) +{ + return c >= 0x20 && c <= 0x2f; +} + +static void do_control(VTerm *vt, unsigned char control) +{ + if(vt->parser.callbacks && vt->parser.callbacks->control) + if((*vt->parser.callbacks->control)(control, vt->parser.cbdata)) + return; + + DEBUG_LOG("libvterm: Unhandled control 0x%02x\n", control); +} + +static void do_csi(VTerm *vt, char command) +{ +#ifdef DEBUG_PARSER + printf("Parsed CSI args as:\n", arglen, args); + printf(" leader: %s\n", vt->parser.v.csi.leader); + for(int argi = 0; argi < vt->parser.v.csi.argi; argi++) { + printf(" %lu", CSI_ARG(vt->parser.v.csi.args[argi])); + if(!CSI_ARG_HAS_MORE(vt->parser.v.csi.args[argi])) + printf("\n"); + printf(" intermed: %s\n", vt->parser.intermed); + } +#endif + + if(vt->parser.callbacks && vt->parser.callbacks->csi) + if((*vt->parser.callbacks->csi)( + vt->parser.v.csi.leaderlen ? vt->parser.v.csi.leader : NULL, + vt->parser.v.csi.args, + vt->parser.v.csi.argi, + vt->parser.intermedlen ? vt->parser.intermed : NULL, + command, + vt->parser.cbdata)) + return; + + DEBUG_LOG("libvterm: Unhandled CSI %c\n", command); +} + +static void do_escape(VTerm *vt, char command) +{ + char seq[INTERMED_MAX+1]; + + size_t len = vt->parser.intermedlen; + strncpy(seq, vt->parser.intermed, len); + seq[len++] = command; + seq[len] = 0; + + if(vt->parser.callbacks && vt->parser.callbacks->escape) + if((*vt->parser.callbacks->escape)(seq, len, vt->parser.cbdata)) + return; + + DEBUG_LOG("libvterm: Unhandled escape ESC 0x%02x\n", command); +} + +static void string_fragment(VTerm *vt, const char *str, size_t len, bool final) +{ + VTermStringFragment frag = { + .str = str, + .len = len, + .initial = vt->parser.string_initial, + .final = final, + }; + + switch(vt->parser.state) { + case OSC: + if(vt->parser.callbacks && vt->parser.callbacks->osc) + (*vt->parser.callbacks->osc)(vt->parser.v.osc.command, frag, vt->parser.cbdata); + break; + + case DCS: + if(vt->parser.callbacks && vt->parser.callbacks->dcs) + (*vt->parser.callbacks->dcs)(vt->parser.v.dcs.command, vt->parser.v.dcs.commandlen, frag, vt->parser.cbdata); + break; + + case APC: + if(vt->parser.callbacks && vt->parser.callbacks->apc) + (*vt->parser.callbacks->apc)(frag, vt->parser.cbdata); + break; + + case PM: + if(vt->parser.callbacks && vt->parser.callbacks->pm) + (*vt->parser.callbacks->pm)(frag, vt->parser.cbdata); + break; + + case SOS: + if(vt->parser.callbacks && vt->parser.callbacks->sos) + (*vt->parser.callbacks->sos)(frag, vt->parser.cbdata); + break; + + case NORMAL: + case CSI_LEADER: + case CSI_ARGS: + case CSI_INTERMED: + case OSC_COMMAND: + case DCS_COMMAND: + break; + } + + vt->parser.string_initial = false; +} + +size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len) +{ + size_t pos = 0; + const char *string_start; + + switch(vt->parser.state) { + case NORMAL: + case CSI_LEADER: + case CSI_ARGS: + case CSI_INTERMED: + case OSC_COMMAND: + case DCS_COMMAND: + string_start = NULL; + break; + case OSC: + case DCS: + case APC: + case PM: + case SOS: + string_start = bytes; + break; + } + +#define ENTER_STATE(st) do { vt->parser.state = st; string_start = NULL; } while(0) +#define ENTER_NORMAL_STATE() ENTER_STATE(NORMAL) + +#define IS_STRING_STATE() (vt->parser.state >= OSC_COMMAND) + + for( ; pos < len; pos++) { + unsigned char c = bytes[pos]; + bool c1_allowed = !vt->mode.utf8; + + if(c == 0x00 || c == 0x7f) { // NUL, DEL + if(IS_STRING_STATE()) { + string_fragment(vt, string_start, bytes + pos - string_start, false); + string_start = bytes + pos + 1; + } + if(vt->parser.emit_nul) + do_control(vt, c); + continue; + } + if(c == 0x18 || c == 0x1a) { // CAN, SUB + vt->parser.in_esc = false; + ENTER_NORMAL_STATE(); + if(vt->parser.emit_nul) + do_control(vt, c); + continue; + } + else if(c == 0x1b) { // ESC + vt->parser.intermedlen = 0; + if(!IS_STRING_STATE()) + vt->parser.state = NORMAL; + vt->parser.in_esc = true; + continue; + } + else if(c == 0x07 && // BEL, can stand for ST in OSC or DCS state + IS_STRING_STATE()) { + // fallthrough + } + else if(c < 0x20) { // other C0 + if(vt->parser.state == SOS) + continue; // All other C0s permitted in SOS + + if(IS_STRING_STATE()) + string_fragment(vt, string_start, bytes + pos - string_start, false); + do_control(vt, c); + if(IS_STRING_STATE()) + string_start = bytes + pos + 1; + continue; + } + // else fallthrough + + size_t string_len = bytes + pos - string_start; + + if(vt->parser.in_esc) { + // Hoist an ESC letter into a C1 if we're not in a string mode + // Always accept ESC \ == ST even in string mode + if(!vt->parser.intermedlen && + c >= 0x40 && c < 0x60 && + ((!IS_STRING_STATE() || c == 0x5c))) { + c += 0x40; + c1_allowed = true; + if(string_len) + string_len -= 1; + vt->parser.in_esc = false; + } + else { + string_start = NULL; + vt->parser.state = NORMAL; + } + } + + switch(vt->parser.state) { + case CSI_LEADER: + /* Extract leader bytes 0x3c to 0x3f */ + if(c >= 0x3c && c <= 0x3f) { + if(vt->parser.v.csi.leaderlen < CSI_LEADER_MAX-1) + vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen++] = c; + break; + } + + /* else fallthrough */ + vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen] = 0; + + vt->parser.v.csi.argi = 0; + vt->parser.v.csi.args[0] = CSI_ARG_MISSING; + vt->parser.state = CSI_ARGS; + + /* fallthrough */ + case CSI_ARGS: + /* Numerical value of argument */ + if(c >= '0' && c <= '9') { + if(vt->parser.v.csi.args[vt->parser.v.csi.argi] == CSI_ARG_MISSING) + vt->parser.v.csi.args[vt->parser.v.csi.argi] = 0; + vt->parser.v.csi.args[vt->parser.v.csi.argi] *= 10; + vt->parser.v.csi.args[vt->parser.v.csi.argi] += c - '0'; + break; + } + if(c == ':') { + vt->parser.v.csi.args[vt->parser.v.csi.argi] |= CSI_ARG_FLAG_MORE; + c = ';'; + } + if(c == ';') { + vt->parser.v.csi.argi++; + vt->parser.v.csi.args[vt->parser.v.csi.argi] = CSI_ARG_MISSING; + break; + } + + /* else fallthrough */ + vt->parser.v.csi.argi++; + vt->parser.intermedlen = 0; + vt->parser.state = CSI_INTERMED; + case CSI_INTERMED: + if(is_intermed(c)) { + if(vt->parser.intermedlen < INTERMED_MAX-1) + vt->parser.intermed[vt->parser.intermedlen++] = c; + break; + } + else if(c == 0x1b) { + /* ESC in CSI cancels */ + } + else if(c >= 0x40 && c <= 0x7e) { + vt->parser.intermed[vt->parser.intermedlen] = 0; + do_csi(vt, c); + } + /* else was invalid CSI */ + + ENTER_NORMAL_STATE(); + break; + + case OSC_COMMAND: + /* Numerical value of command */ + if(c >= '0' && c <= '9') { + if(vt->parser.v.osc.command == -1) + vt->parser.v.osc.command = 0; + else + vt->parser.v.osc.command *= 10; + vt->parser.v.osc.command += c - '0'; + break; + } + if(c == ';') { + vt->parser.state = OSC; + string_start = bytes + pos + 1; + break; + } + + /* else fallthrough */ + string_start = bytes + pos; + string_len = 0; + vt->parser.state = OSC; + goto string_state; + + case DCS_COMMAND: + if(vt->parser.v.dcs.commandlen < CSI_LEADER_MAX) + vt->parser.v.dcs.command[vt->parser.v.dcs.commandlen++] = c; + + if(c >= 0x40 && c<= 0x7e) { + string_start = bytes + pos + 1; + vt->parser.state = DCS; + } + break; + +string_state: + case OSC: + case DCS: + case APC: + case PM: + case SOS: + if(c == 0x07 || (c1_allowed && c == 0x9c)) { + string_fragment(vt, string_start, string_len, true); + ENTER_NORMAL_STATE(); + } + break; + + case NORMAL: + if(vt->parser.in_esc) { + if(is_intermed(c)) { + if(vt->parser.intermedlen < INTERMED_MAX-1) + vt->parser.intermed[vt->parser.intermedlen++] = c; + } + else if(c >= 0x30 && c < 0x7f) { + do_escape(vt, c); + vt->parser.in_esc = 0; + ENTER_NORMAL_STATE(); + } + else { + DEBUG_LOG("TODO: Unhandled byte %02x in Escape\n", c); + } + break; + } + if(c1_allowed && c >= 0x80 && c < 0xa0) { + switch(c) { + case 0x90: // DCS + vt->parser.string_initial = true; + vt->parser.v.dcs.commandlen = 0; + ENTER_STATE(DCS_COMMAND); + break; + case 0x98: // SOS + vt->parser.string_initial = true; + ENTER_STATE(SOS); + string_start = bytes + pos + 1; + string_len = 0; + break; + case 0x9b: // CSI + vt->parser.v.csi.leaderlen = 0; + ENTER_STATE(CSI_LEADER); + break; + case 0x9d: // OSC + vt->parser.v.osc.command = -1; + vt->parser.string_initial = true; + string_start = bytes + pos + 1; + ENTER_STATE(OSC_COMMAND); + break; + case 0x9e: // PM + vt->parser.string_initial = true; + ENTER_STATE(PM); + string_start = bytes + pos + 1; + string_len = 0; + break; + case 0x9f: // APC + vt->parser.string_initial = true; + ENTER_STATE(APC); + string_start = bytes + pos + 1; + string_len = 0; + break; + default: + do_control(vt, c); + break; + } + } + else { + size_t eaten = 0; + if(vt->parser.callbacks && vt->parser.callbacks->text) + eaten = (*vt->parser.callbacks->text)(bytes + pos, len - pos, vt->parser.cbdata); + + if(!eaten) { + DEBUG_LOG("libvterm: Text callback did not consume any input\n"); + /* force it to make progress */ + eaten = 1; + } + + pos += (eaten - 1); // we'll ++ it again in a moment + } + break; + } + } + + if(string_start) { + size_t string_len = bytes + pos - string_start; + if(vt->parser.in_esc) + string_len -= 1; + string_fragment(vt, string_start, string_len, false); + } + + return len; +} + +void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user) +{ + vt->parser.callbacks = callbacks; + vt->parser.cbdata = user; +} + +void *vterm_parser_get_cbdata(VTerm *vt) +{ + return vt->parser.cbdata; +} + +void vterm_parser_set_emit_nul(VTerm *vt, bool emit) +{ + vt->parser.emit_nul = emit; +} diff --git a/src/libs/3rdparty/libvterm/src/pen.c b/src/libs/3rdparty/libvterm/src/pen.c new file mode 100644 index 00000000000..891a45cec78 --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/pen.c @@ -0,0 +1,613 @@ +#include "vterm_internal.h" + +#include + +/** + * Structure used to store RGB triples without the additional metadata stored in + * VTermColor. + */ +typedef struct { + uint8_t red, green, blue; +} VTermRGB; + +static const VTermRGB ansi_colors[] = { + /* R G B */ + { 0, 0, 0 }, // black + { 224, 0, 0 }, // red + { 0, 224, 0 }, // green + { 224, 224, 0 }, // yellow + { 0, 0, 224 }, // blue + { 224, 0, 224 }, // magenta + { 0, 224, 224 }, // cyan + { 224, 224, 224 }, // white == light grey + + // high intensity + { 128, 128, 128 }, // black + { 255, 64, 64 }, // red + { 64, 255, 64 }, // green + { 255, 255, 64 }, // yellow + { 64, 64, 255 }, // blue + { 255, 64, 255 }, // magenta + { 64, 255, 255 }, // cyan + { 255, 255, 255 }, // white for real +}; + +static int ramp6[] = { + 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF, +}; + +static int ramp24[] = { + 0x00, 0x0B, 0x16, 0x21, 0x2C, 0x37, 0x42, 0x4D, 0x58, 0x63, 0x6E, 0x79, + 0x85, 0x90, 0x9B, 0xA6, 0xB1, 0xBC, 0xC7, 0xD2, 0xDD, 0xE8, 0xF3, 0xFF, +}; + +static void lookup_default_colour_ansi(long idx, VTermColor *col) +{ + if (idx >= 0 && idx < 16) { + vterm_color_rgb( + col, + ansi_colors[idx].red, ansi_colors[idx].green, ansi_colors[idx].blue); + } +} + +static bool lookup_colour_ansi(const VTermState *state, long index, VTermColor *col) +{ + if(index >= 0 && index < 16) { + *col = state->colors[index]; + return true; + } + + return false; +} + +static bool lookup_colour_palette(const VTermState *state, long index, VTermColor *col) +{ + if(index >= 0 && index < 16) { + // Normal 8 colours or high intensity - parse as palette 0 + return lookup_colour_ansi(state, index, col); + } + else if(index >= 16 && index < 232) { + // 216-colour cube + index -= 16; + + vterm_color_rgb(col, ramp6[index/6/6 % 6], + ramp6[index/6 % 6], + ramp6[index % 6]); + + return true; + } + else if(index >= 232 && index < 256) { + // 24 greyscales + index -= 232; + + vterm_color_rgb(col, ramp24[index], ramp24[index], ramp24[index]); + + return true; + } + + return false; +} + +static int lookup_colour(const VTermState *state, int palette, const long args[], int argcount, VTermColor *col) +{ + switch(palette) { + case 2: // RGB mode - 3 args contain colour values directly + if(argcount < 3) + return argcount; + + vterm_color_rgb(col, CSI_ARG(args[0]), CSI_ARG(args[1]), CSI_ARG(args[2])); + + return 3; + + case 5: // XTerm 256-colour mode + if (!argcount || CSI_ARG_IS_MISSING(args[0])) { + return argcount ? 1 : 0; + } + + vterm_color_indexed(col, args[0]); + + return argcount ? 1 : 0; + + default: + DEBUG_LOG("Unrecognised colour palette %d\n", palette); + return 0; + } +} + +// Some conveniences + +static void setpenattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val) +{ +#ifdef DEBUG + if(type != vterm_get_attr_type(attr)) { + DEBUG_LOG("Cannot set attr %d as it has type %d, not type %d\n", + attr, vterm_get_attr_type(attr), type); + return; + } +#endif + if(state->callbacks && state->callbacks->setpenattr) + (*state->callbacks->setpenattr)(attr, val, state->cbdata); +} + +static void setpenattr_bool(VTermState *state, VTermAttr attr, int boolean) +{ + VTermValue val = { .boolean = boolean }; + setpenattr(state, attr, VTERM_VALUETYPE_BOOL, &val); +} + +static void setpenattr_int(VTermState *state, VTermAttr attr, int number) +{ + VTermValue val = { .number = number }; + setpenattr(state, attr, VTERM_VALUETYPE_INT, &val); +} + +static void setpenattr_col(VTermState *state, VTermAttr attr, VTermColor color) +{ + VTermValue val = { .color = color }; + setpenattr(state, attr, VTERM_VALUETYPE_COLOR, &val); +} + +static void set_pen_col_ansi(VTermState *state, VTermAttr attr, long col) +{ + VTermColor *colp = (attr == VTERM_ATTR_BACKGROUND) ? &state->pen.bg : &state->pen.fg; + + vterm_color_indexed(colp, col); + + setpenattr_col(state, attr, *colp); +} + +INTERNAL void vterm_state_newpen(VTermState *state) +{ + // 90% grey so that pure white is brighter + vterm_color_rgb(&state->default_fg, 240, 240, 240); + vterm_color_rgb(&state->default_bg, 0, 0, 0); + vterm_state_set_default_colors(state, &state->default_fg, &state->default_bg); + + for(int col = 0; col < 16; col++) + lookup_default_colour_ansi(col, &state->colors[col]); +} + +INTERNAL void vterm_state_resetpen(VTermState *state) +{ + state->pen.bold = 0; setpenattr_bool(state, VTERM_ATTR_BOLD, 0); + state->pen.underline = 0; setpenattr_int (state, VTERM_ATTR_UNDERLINE, 0); + state->pen.italic = 0; setpenattr_bool(state, VTERM_ATTR_ITALIC, 0); + state->pen.blink = 0; setpenattr_bool(state, VTERM_ATTR_BLINK, 0); + state->pen.reverse = 0; setpenattr_bool(state, VTERM_ATTR_REVERSE, 0); + state->pen.conceal = 0; setpenattr_bool(state, VTERM_ATTR_CONCEAL, 0); + state->pen.strike = 0; setpenattr_bool(state, VTERM_ATTR_STRIKE, 0); + state->pen.font = 0; setpenattr_int (state, VTERM_ATTR_FONT, 0); + state->pen.small = 0; setpenattr_bool(state, VTERM_ATTR_SMALL, 0); + state->pen.baseline = 0; setpenattr_int (state, VTERM_ATTR_BASELINE, 0); + + state->pen.fg = state->default_fg; setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->default_fg); + state->pen.bg = state->default_bg; setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->default_bg); +} + +INTERNAL void vterm_state_savepen(VTermState *state, int save) +{ + if(save) { + state->saved.pen = state->pen; + } + else { + state->pen = state->saved.pen; + + setpenattr_bool(state, VTERM_ATTR_BOLD, state->pen.bold); + setpenattr_int (state, VTERM_ATTR_UNDERLINE, state->pen.underline); + setpenattr_bool(state, VTERM_ATTR_ITALIC, state->pen.italic); + setpenattr_bool(state, VTERM_ATTR_BLINK, state->pen.blink); + setpenattr_bool(state, VTERM_ATTR_REVERSE, state->pen.reverse); + setpenattr_bool(state, VTERM_ATTR_CONCEAL, state->pen.conceal); + setpenattr_bool(state, VTERM_ATTR_STRIKE, state->pen.strike); + setpenattr_int (state, VTERM_ATTR_FONT, state->pen.font); + setpenattr_bool(state, VTERM_ATTR_SMALL, state->pen.small); + setpenattr_int (state, VTERM_ATTR_BASELINE, state->pen.baseline); + + setpenattr_col( state, VTERM_ATTR_FOREGROUND, state->pen.fg); + setpenattr_col( state, VTERM_ATTR_BACKGROUND, state->pen.bg); + } +} + +int vterm_color_is_equal(const VTermColor *a, const VTermColor *b) +{ + /* First make sure that the two colours are of the same type (RGB/Indexed) */ + if (a->type != b->type) { + return false; + } + + /* Depending on the type inspect the corresponding members */ + if (VTERM_COLOR_IS_INDEXED(a)) { + return a->indexed.idx == b->indexed.idx; + } + else if (VTERM_COLOR_IS_RGB(a)) { + return (a->rgb.red == b->rgb.red) + && (a->rgb.green == b->rgb.green) + && (a->rgb.blue == b->rgb.blue); + } + + return 0; +} + +void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg) +{ + *default_fg = state->default_fg; + *default_bg = state->default_bg; +} + +void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col) +{ + lookup_colour_palette(state, index, col); +} + +void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg) +{ + if(default_fg) { + state->default_fg = *default_fg; + state->default_fg.type = (state->default_fg.type & ~VTERM_COLOR_DEFAULT_MASK) + | VTERM_COLOR_DEFAULT_FG; + } + + if(default_bg) { + state->default_bg = *default_bg; + state->default_bg.type = (state->default_bg.type & ~VTERM_COLOR_DEFAULT_MASK) + | VTERM_COLOR_DEFAULT_BG; + } +} + +void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col) +{ + if(index >= 0 && index < 16) + state->colors[index] = *col; +} + +void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col) +{ + if (VTERM_COLOR_IS_INDEXED(col)) { /* Convert indexed colors to RGB */ + lookup_colour_palette(state, col->indexed.idx, col); + } + col->type &= VTERM_COLOR_TYPE_MASK; /* Reset any metadata but the type */ +} + +void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright) +{ + state->bold_is_highbright = bold_is_highbright; +} + +INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argcount) +{ + // SGR - ECMA-48 8.3.117 + + int argi = 0; + int value; + + while(argi < argcount) { + // This logic is easier to do 'done' backwards; set it true, and make it + // false again in the 'default' case + int done = 1; + + long arg; + switch(arg = CSI_ARG(args[argi])) { + case CSI_ARG_MISSING: + case 0: // Reset + vterm_state_resetpen(state); + break; + + case 1: { // Bold on + const VTermColor *fg = &state->pen.fg; + state->pen.bold = 1; + setpenattr_bool(state, VTERM_ATTR_BOLD, 1); + if(!VTERM_COLOR_IS_DEFAULT_FG(fg) && VTERM_COLOR_IS_INDEXED(fg) && fg->indexed.idx < 8 && state->bold_is_highbright) + set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, fg->indexed.idx + (state->pen.bold ? 8 : 0)); + break; + } + + case 3: // Italic on + state->pen.italic = 1; + setpenattr_bool(state, VTERM_ATTR_ITALIC, 1); + break; + + case 4: // Underline + state->pen.underline = VTERM_UNDERLINE_SINGLE; + if(CSI_ARG_HAS_MORE(args[argi])) { + argi++; + switch(CSI_ARG(args[argi])) { + case 0: + state->pen.underline = 0; + break; + case 1: + state->pen.underline = VTERM_UNDERLINE_SINGLE; + break; + case 2: + state->pen.underline = VTERM_UNDERLINE_DOUBLE; + break; + case 3: + state->pen.underline = VTERM_UNDERLINE_CURLY; + break; + case 4: + state->pen.underline = VTERM_UNDERLINE_DOTTED; + break; + case 5: + state->pen.underline = VTERM_UNDERLINE_DASHED; + break; + } + } + setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); + break; + + case 5: // Blink + state->pen.blink = 1; + setpenattr_bool(state, VTERM_ATTR_BLINK, 1); + break; + + case 7: // Reverse on + state->pen.reverse = 1; + setpenattr_bool(state, VTERM_ATTR_REVERSE, 1); + break; + + case 8: // Conceal on + state->pen.conceal = 1; + setpenattr_bool(state, VTERM_ATTR_CONCEAL, 1); + break; + + case 9: // Strikethrough on + state->pen.strike = 1; + setpenattr_bool(state, VTERM_ATTR_STRIKE, 1); + break; + + case 10: case 11: case 12: case 13: case 14: + case 15: case 16: case 17: case 18: case 19: // Select font + state->pen.font = CSI_ARG(args[argi]) - 10; + setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font); + break; + + case 21: // Underline double + state->pen.underline = VTERM_UNDERLINE_DOUBLE; + setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); + break; + + case 22: // Bold off + state->pen.bold = 0; + setpenattr_bool(state, VTERM_ATTR_BOLD, 0); + break; + + case 23: // Italic and Gothic (currently unsupported) off + state->pen.italic = 0; + setpenattr_bool(state, VTERM_ATTR_ITALIC, 0); + break; + + case 24: // Underline off + state->pen.underline = 0; + setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0); + break; + + case 25: // Blink off + state->pen.blink = 0; + setpenattr_bool(state, VTERM_ATTR_BLINK, 0); + break; + + case 27: // Reverse off + state->pen.reverse = 0; + setpenattr_bool(state, VTERM_ATTR_REVERSE, 0); + break; + + case 28: // Conceal off (Reveal) + state->pen.conceal = 0; + setpenattr_bool(state, VTERM_ATTR_CONCEAL, 0); + break; + + case 29: // Strikethrough off + state->pen.strike = 0; + setpenattr_bool(state, VTERM_ATTR_STRIKE, 0); + break; + + case 30: case 31: case 32: case 33: + case 34: case 35: case 36: case 37: // Foreground colour palette + value = CSI_ARG(args[argi]) - 30; + if(state->pen.bold && state->bold_is_highbright) + value += 8; + set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value); + break; + + case 38: // Foreground colour alternative palette + if(argcount - argi < 1) + return; + argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.fg); + setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); + break; + + case 39: // Foreground colour default + state->pen.fg = state->default_fg; + setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); + break; + + case 40: case 41: case 42: case 43: + case 44: case 45: case 46: case 47: // Background colour palette + value = CSI_ARG(args[argi]) - 40; + set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value); + break; + + case 48: // Background colour alternative palette + if(argcount - argi < 1) + return; + argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.bg); + setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); + break; + + case 49: // Default background + state->pen.bg = state->default_bg; + setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); + break; + + case 73: // Superscript + case 74: // Subscript + case 75: // Superscript/subscript off + state->pen.small = (arg != 75); + state->pen.baseline = + (arg == 73) ? VTERM_BASELINE_RAISE : + (arg == 74) ? VTERM_BASELINE_LOWER : + VTERM_BASELINE_NORMAL; + setpenattr_bool(state, VTERM_ATTR_SMALL, state->pen.small); + setpenattr_int (state, VTERM_ATTR_BASELINE, state->pen.baseline); + break; + + case 90: case 91: case 92: case 93: + case 94: case 95: case 96: case 97: // Foreground colour high-intensity palette + value = CSI_ARG(args[argi]) - 90 + 8; + set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value); + break; + + case 100: case 101: case 102: case 103: + case 104: case 105: case 106: case 107: // Background colour high-intensity palette + value = CSI_ARG(args[argi]) - 100 + 8; + set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value); + break; + + default: + done = 0; + break; + } + + if(!done) + DEBUG_LOG("libvterm: Unhandled CSI SGR %ld\n", arg); + + while(CSI_ARG_HAS_MORE(args[argi++])); + } +} + +static int vterm_state_getpen_color(const VTermColor *col, int argi, long args[], int fg) +{ + /* Do nothing if the given color is the default color */ + if (( fg && VTERM_COLOR_IS_DEFAULT_FG(col)) || + (!fg && VTERM_COLOR_IS_DEFAULT_BG(col))) { + return argi; + } + + /* Decide whether to send an indexed color or an RGB color */ + if (VTERM_COLOR_IS_INDEXED(col)) { + const uint8_t idx = col->indexed.idx; + if (idx < 8) { + args[argi++] = (idx + (fg ? 30 : 40)); + } + else if (idx < 16) { + args[argi++] = (idx - 8 + (fg ? 90 : 100)); + } + else { + args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48); + args[argi++] = CSI_ARG_FLAG_MORE | 5; + args[argi++] = idx; + } + } + else if (VTERM_COLOR_IS_RGB(col)) { + args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48); + args[argi++] = CSI_ARG_FLAG_MORE | 2; + args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.red; + args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.green; + args[argi++] = col->rgb.blue; + } + return argi; +} + +INTERNAL int vterm_state_getpen(VTermState *state, long args[], int argcount) +{ + int argi = 0; + + if(state->pen.bold) + args[argi++] = 1; + + if(state->pen.italic) + args[argi++] = 3; + + if(state->pen.underline == VTERM_UNDERLINE_SINGLE) + args[argi++] = 4; + if(state->pen.underline == VTERM_UNDERLINE_CURLY) + args[argi++] = 4 | CSI_ARG_FLAG_MORE, args[argi++] = 3; + + if(state->pen.blink) + args[argi++] = 5; + + if(state->pen.reverse) + args[argi++] = 7; + + if(state->pen.conceal) + args[argi++] = 8; + + if(state->pen.strike) + args[argi++] = 9; + + if(state->pen.font) + args[argi++] = 10 + state->pen.font; + + if(state->pen.underline == VTERM_UNDERLINE_DOUBLE) + args[argi++] = 21; + + argi = vterm_state_getpen_color(&state->pen.fg, argi, args, true); + + argi = vterm_state_getpen_color(&state->pen.bg, argi, args, false); + + if(state->pen.small) { + if(state->pen.baseline == VTERM_BASELINE_RAISE) + args[argi++] = 73; + else if(state->pen.baseline == VTERM_BASELINE_LOWER) + args[argi++] = 74; + } + + return argi; +} + +int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val) +{ + switch(attr) { + case VTERM_ATTR_BOLD: + val->boolean = state->pen.bold; + return 1; + + case VTERM_ATTR_UNDERLINE: + val->number = state->pen.underline; + return 1; + + case VTERM_ATTR_ITALIC: + val->boolean = state->pen.italic; + return 1; + + case VTERM_ATTR_BLINK: + val->boolean = state->pen.blink; + return 1; + + case VTERM_ATTR_REVERSE: + val->boolean = state->pen.reverse; + return 1; + + case VTERM_ATTR_CONCEAL: + val->boolean = state->pen.conceal; + return 1; + + case VTERM_ATTR_STRIKE: + val->boolean = state->pen.strike; + return 1; + + case VTERM_ATTR_FONT: + val->number = state->pen.font; + return 1; + + case VTERM_ATTR_FOREGROUND: + val->color = state->pen.fg; + return 1; + + case VTERM_ATTR_BACKGROUND: + val->color = state->pen.bg; + return 1; + + case VTERM_ATTR_SMALL: + val->boolean = state->pen.small; + return 1; + + case VTERM_ATTR_BASELINE: + val->number = state->pen.baseline; + return 1; + + case VTERM_N_ATTRS: + return 0; + } + + return 0; +} diff --git a/src/libs/3rdparty/libvterm/src/rect.h b/src/libs/3rdparty/libvterm/src/rect.h new file mode 100644 index 00000000000..2114f24c1be --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/rect.h @@ -0,0 +1,56 @@ +/* + * Some utility functions on VTermRect structures + */ + +#define STRFrect "(%d,%d-%d,%d)" +#define ARGSrect(r) (r).start_row, (r).start_col, (r).end_row, (r).end_col + +/* Expand dst to contain src as well */ +static void rect_expand(VTermRect *dst, VTermRect *src) +{ + if(dst->start_row > src->start_row) dst->start_row = src->start_row; + if(dst->start_col > src->start_col) dst->start_col = src->start_col; + if(dst->end_row < src->end_row) dst->end_row = src->end_row; + if(dst->end_col < src->end_col) dst->end_col = src->end_col; +} + +/* Clip the dst to ensure it does not step outside of bounds */ +static void rect_clip(VTermRect *dst, VTermRect *bounds) +{ + if(dst->start_row < bounds->start_row) dst->start_row = bounds->start_row; + if(dst->start_col < bounds->start_col) dst->start_col = bounds->start_col; + if(dst->end_row > bounds->end_row) dst->end_row = bounds->end_row; + if(dst->end_col > bounds->end_col) dst->end_col = bounds->end_col; + /* Ensure it doesn't end up negatively-sized */ + if(dst->end_row < dst->start_row) dst->end_row = dst->start_row; + if(dst->end_col < dst->start_col) dst->end_col = dst->start_col; +} + +/* True if the two rectangles are equal */ +static int rect_equal(VTermRect *a, VTermRect *b) +{ + return (a->start_row == b->start_row) && + (a->start_col == b->start_col) && + (a->end_row == b->end_row) && + (a->end_col == b->end_col); +} + +/* True if small is contained entirely within big */ +static int rect_contains(VTermRect *big, VTermRect *small) +{ + if(small->start_row < big->start_row) return 0; + if(small->start_col < big->start_col) return 0; + if(small->end_row > big->end_row) return 0; + if(small->end_col > big->end_col) return 0; + return 1; +} + +/* True if the rectangles overlap at all */ +static int rect_intersects(VTermRect *a, VTermRect *b) +{ + if(a->start_row > b->end_row || b->start_row > a->end_row) + return 0; + if(a->start_col > b->end_col || b->start_col > a->end_col) + return 0; + return 1; +} diff --git a/src/libs/3rdparty/libvterm/src/screen.c b/src/libs/3rdparty/libvterm/src/screen.c new file mode 100644 index 00000000000..9d1028e67ae --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/screen.c @@ -0,0 +1,1183 @@ +#include "vterm_internal.h" + +#include +#include + +#include "rect.h" +#include "utf8.h" + +#define UNICODE_SPACE 0x20 +#define UNICODE_LINEFEED 0x0a + +#undef DEBUG_REFLOW + +/* State of the pen at some moment in time, also used in a cell */ +typedef struct +{ + /* After the bitfield */ + VTermColor fg, bg; + + unsigned int bold : 1; + unsigned int underline : 3; + unsigned int italic : 1; + unsigned int blink : 1; + unsigned int reverse : 1; + unsigned int conceal : 1; + unsigned int strike : 1; + unsigned int font : 4; /* 0 to 9 */ + unsigned int small : 1; + unsigned int baseline : 2; + + /* Extra state storage that isn't strictly pen-related */ + unsigned int protected_cell : 1; + unsigned int dwl : 1; /* on a DECDWL or DECDHL line */ + unsigned int dhl : 2; /* on a DECDHL line (1=top 2=bottom) */ +} ScreenPen; + +/* Internal representation of a screen cell */ +typedef struct +{ + uint32_t chars[VTERM_MAX_CHARS_PER_CELL]; + ScreenPen pen; +} ScreenCell; + +struct VTermScreen +{ + VTerm *vt; + VTermState *state; + + const VTermScreenCallbacks *callbacks; + void *cbdata; + + VTermDamageSize damage_merge; + /* start_row == -1 => no damage */ + VTermRect damaged; + VTermRect pending_scrollrect; + int pending_scroll_downward, pending_scroll_rightward; + + int rows; + int cols; + + unsigned int global_reverse : 1; + unsigned int reflow : 1; + + /* Primary and Altscreen. buffers[1] is lazily allocated as needed */ + ScreenCell *buffers[2]; + + /* buffer will == buffers[0] or buffers[1], depending on altscreen */ + ScreenCell *buffer; + + /* buffer for a single screen row used in scrollback storage callbacks */ + VTermScreenCell *sb_buffer; + + ScreenPen pen; +}; + +static inline void clearcell(const VTermScreen *screen, ScreenCell *cell) +{ + cell->chars[0] = 0; + cell->pen = screen->pen; +} + +static inline ScreenCell *getcell(const VTermScreen *screen, int row, int col) +{ + if(row < 0 || row >= screen->rows) + return NULL; + if(col < 0 || col >= screen->cols) + return NULL; + return screen->buffer + (screen->cols * row) + col; +} + +static ScreenCell *alloc_buffer(VTermScreen *screen, int rows, int cols) +{ + ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * rows * cols); + + for(int row = 0; row < rows; row++) { + for(int col = 0; col < cols; col++) { + clearcell(screen, &new_buffer[row * cols + col]); + } + } + + return new_buffer; +} + +static void damagerect(VTermScreen *screen, VTermRect rect) +{ + VTermRect emit; + + switch(screen->damage_merge) { + case VTERM_DAMAGE_CELL: + /* Always emit damage event */ + emit = rect; + break; + + case VTERM_DAMAGE_ROW: + /* Emit damage longer than one row. Try to merge with existing damage in + * the same row */ + if(rect.end_row > rect.start_row + 1) { + // Bigger than 1 line - flush existing, emit this + vterm_screen_flush_damage(screen); + emit = rect; + } + else if(screen->damaged.start_row == -1) { + // None stored yet + screen->damaged = rect; + return; + } + else if(rect.start_row == screen->damaged.start_row) { + // Merge with the stored line + if(screen->damaged.start_col > rect.start_col) + screen->damaged.start_col = rect.start_col; + if(screen->damaged.end_col < rect.end_col) + screen->damaged.end_col = rect.end_col; + return; + } + else { + // Emit the currently stored line, store a new one + emit = screen->damaged; + screen->damaged = rect; + } + break; + + case VTERM_DAMAGE_SCREEN: + case VTERM_DAMAGE_SCROLL: + /* Never emit damage event */ + if(screen->damaged.start_row == -1) + screen->damaged = rect; + else { + rect_expand(&screen->damaged, &rect); + } + return; + + default: + DEBUG_LOG("TODO: Maybe merge damage for level %d\n", screen->damage_merge); + return; + } + + if(screen->callbacks && screen->callbacks->damage) + (*screen->callbacks->damage)(emit, screen->cbdata); +} + +static void damagescreen(VTermScreen *screen) +{ + VTermRect rect = { + .start_row = 0, + .end_row = screen->rows, + .start_col = 0, + .end_col = screen->cols, + }; + + damagerect(screen, rect); +} + +static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user) +{ + VTermScreen *screen = user; + ScreenCell *cell = getcell(screen, pos.row, pos.col); + + if(!cell) + return 0; + + int i; + for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) { + cell->chars[i] = info->chars[i]; + cell->pen = screen->pen; + } + if(i < VTERM_MAX_CHARS_PER_CELL) + cell->chars[i] = 0; + + for(int col = 1; col < info->width; col++) + getcell(screen, pos.row, pos.col + col)->chars[0] = (uint32_t)-1; + + VTermRect rect = { + .start_row = pos.row, + .end_row = pos.row+1, + .start_col = pos.col, + .end_col = pos.col+info->width, + }; + + cell->pen.protected_cell = info->protected_cell; + cell->pen.dwl = info->dwl; + cell->pen.dhl = info->dhl; + + damagerect(screen, rect); + + return 1; +} + +static void sb_pushline_from_row(VTermScreen *screen, int row) +{ + VTermPos pos = { .row = row }; + for(pos.col = 0; pos.col < screen->cols; pos.col++) + vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col); + + (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata); +} + +static int moverect_internal(VTermRect dest, VTermRect src, void *user) +{ + VTermScreen *screen = user; + + if(screen->callbacks && screen->callbacks->sb_pushline && + dest.start_row == 0 && dest.start_col == 0 && // starts top-left corner + dest.end_col == screen->cols && // full width + screen->buffer == screen->buffers[BUFIDX_PRIMARY]) { // not altscreen + for(int row = 0; row < src.start_row; row++) + sb_pushline_from_row(screen, row); + } + + int cols = src.end_col - src.start_col; + int downward = src.start_row - dest.start_row; + + int init_row, test_row, inc_row; + if(downward < 0) { + init_row = dest.end_row - 1; + test_row = dest.start_row - 1; + inc_row = -1; + } + else { + init_row = dest.start_row; + test_row = dest.end_row; + inc_row = +1; + } + + for(int row = init_row; row != test_row; row += inc_row) + memmove(getcell(screen, row, dest.start_col), + getcell(screen, row + downward, src.start_col), + cols * sizeof(ScreenCell)); + + return 1; +} + +static int moverect_user(VTermRect dest, VTermRect src, void *user) +{ + VTermScreen *screen = user; + + if(screen->callbacks && screen->callbacks->moverect) { + if(screen->damage_merge != VTERM_DAMAGE_SCROLL) + // Avoid an infinite loop + vterm_screen_flush_damage(screen); + + if((*screen->callbacks->moverect)(dest, src, screen->cbdata)) + return 1; + } + + damagerect(screen, dest); + + return 1; +} + +static int erase_internal(VTermRect rect, int selective, void *user) +{ + VTermScreen *screen = user; + + for(int row = rect.start_row; row < screen->state->rows && row < rect.end_row; row++) { + const VTermLineInfo *info = vterm_state_get_lineinfo(screen->state, row); + + for(int col = rect.start_col; col < rect.end_col; col++) { + ScreenCell *cell = getcell(screen, row, col); + + if(selective && cell->pen.protected_cell) + continue; + + cell->chars[0] = 0; + cell->pen = (ScreenPen){ + /* Only copy .fg and .bg; leave things like rv in reset state */ + .fg = screen->pen.fg, + .bg = screen->pen.bg, + }; + cell->pen.dwl = info->doublewidth; + cell->pen.dhl = info->doubleheight; + } + } + + return 1; +} + +static int erase_user(VTermRect rect, int selective, void *user) +{ + VTermScreen *screen = user; + + damagerect(screen, rect); + + return 1; +} + +static int erase(VTermRect rect, int selective, void *user) +{ + erase_internal(rect, selective, user); + return erase_user(rect, 0, user); +} + +static int scrollrect(VTermRect rect, int downward, int rightward, void *user) +{ + VTermScreen *screen = user; + + if(screen->damage_merge != VTERM_DAMAGE_SCROLL) { + vterm_scroll_rect(rect, downward, rightward, + moverect_internal, erase_internal, screen); + + vterm_screen_flush_damage(screen); + + vterm_scroll_rect(rect, downward, rightward, + moverect_user, erase_user, screen); + + return 1; + } + + if(screen->damaged.start_row != -1 && + !rect_intersects(&rect, &screen->damaged)) { + vterm_screen_flush_damage(screen); + } + + if(screen->pending_scrollrect.start_row == -1) { + screen->pending_scrollrect = rect; + screen->pending_scroll_downward = downward; + screen->pending_scroll_rightward = rightward; + } + else if(rect_equal(&screen->pending_scrollrect, &rect) && + ((screen->pending_scroll_downward == 0 && downward == 0) || + (screen->pending_scroll_rightward == 0 && rightward == 0))) { + screen->pending_scroll_downward += downward; + screen->pending_scroll_rightward += rightward; + } + else { + vterm_screen_flush_damage(screen); + + screen->pending_scrollrect = rect; + screen->pending_scroll_downward = downward; + screen->pending_scroll_rightward = rightward; + } + + vterm_scroll_rect(rect, downward, rightward, + moverect_internal, erase_internal, screen); + + if(screen->damaged.start_row == -1) + return 1; + + if(rect_contains(&rect, &screen->damaged)) { + /* Scroll region entirely contains the damage; just move it */ + vterm_rect_move(&screen->damaged, -downward, -rightward); + rect_clip(&screen->damaged, &rect); + } + /* There are a number of possible cases here, but lets restrict this to only + * the common case where we might actually gain some performance by + * optimising it. Namely, a vertical scroll that neatly cuts the damage + * region in half. + */ + else if(rect.start_col <= screen->damaged.start_col && + rect.end_col >= screen->damaged.end_col && + rightward == 0) { + if(screen->damaged.start_row >= rect.start_row && + screen->damaged.start_row < rect.end_row) { + screen->damaged.start_row -= downward; + if(screen->damaged.start_row < rect.start_row) + screen->damaged.start_row = rect.start_row; + if(screen->damaged.start_row > rect.end_row) + screen->damaged.start_row = rect.end_row; + } + if(screen->damaged.end_row >= rect.start_row && + screen->damaged.end_row < rect.end_row) { + screen->damaged.end_row -= downward; + if(screen->damaged.end_row < rect.start_row) + screen->damaged.end_row = rect.start_row; + if(screen->damaged.end_row > rect.end_row) + screen->damaged.end_row = rect.end_row; + } + } + else { + DEBUG_LOG("TODO: Just flush and redo damaged=" STRFrect " rect=" STRFrect "\n", + ARGSrect(screen->damaged), ARGSrect(rect)); + } + + return 1; +} + +static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user) +{ + VTermScreen *screen = user; + + if(screen->callbacks && screen->callbacks->movecursor) + return (*screen->callbacks->movecursor)(pos, oldpos, visible, screen->cbdata); + + return 0; +} + +static int setpenattr(VTermAttr attr, VTermValue *val, void *user) +{ + VTermScreen *screen = user; + + switch(attr) { + case VTERM_ATTR_BOLD: + screen->pen.bold = val->boolean; + return 1; + case VTERM_ATTR_UNDERLINE: + screen->pen.underline = val->number; + return 1; + case VTERM_ATTR_ITALIC: + screen->pen.italic = val->boolean; + return 1; + case VTERM_ATTR_BLINK: + screen->pen.blink = val->boolean; + return 1; + case VTERM_ATTR_REVERSE: + screen->pen.reverse = val->boolean; + return 1; + case VTERM_ATTR_CONCEAL: + screen->pen.conceal = val->boolean; + return 1; + case VTERM_ATTR_STRIKE: + screen->pen.strike = val->boolean; + return 1; + case VTERM_ATTR_FONT: + screen->pen.font = val->number; + return 1; + case VTERM_ATTR_FOREGROUND: + screen->pen.fg = val->color; + return 1; + case VTERM_ATTR_BACKGROUND: + screen->pen.bg = val->color; + return 1; + case VTERM_ATTR_SMALL: + screen->pen.small = val->boolean; + return 1; + case VTERM_ATTR_BASELINE: + screen->pen.baseline = val->number; + return 1; + + case VTERM_N_ATTRS: + return 0; + } + + return 0; +} + +static int settermprop(VTermProp prop, VTermValue *val, void *user) +{ + VTermScreen *screen = user; + + switch(prop) { + case VTERM_PROP_ALTSCREEN: + if(val->boolean && !screen->buffers[BUFIDX_ALTSCREEN]) + return 0; + + screen->buffer = val->boolean ? screen->buffers[BUFIDX_ALTSCREEN] : screen->buffers[BUFIDX_PRIMARY]; + /* only send a damage event on disable; because during enable there's an + * erase that sends a damage anyway + */ + if(!val->boolean) + damagescreen(screen); + break; + case VTERM_PROP_REVERSE: + screen->global_reverse = val->boolean; + damagescreen(screen); + break; + default: + ; /* ignore */ + } + + if(screen->callbacks && screen->callbacks->settermprop) + return (*screen->callbacks->settermprop)(prop, val, screen->cbdata); + + return 1; +} + +static int bell(void *user) +{ + VTermScreen *screen = user; + + if(screen->callbacks && screen->callbacks->bell) + return (*screen->callbacks->bell)(screen->cbdata); + + return 0; +} + +/* How many cells are non-blank + * Returns the position of the first blank cell in the trailing blank end */ +static int line_popcount(ScreenCell *buffer, int row, int rows, int cols) +{ + int col = cols - 1; + while(col >= 0 && buffer[row * cols + col].chars[0] == 0) + col--; + return col + 1; +} + +#define REFLOW (screen->reflow) + +static void resize_buffer(VTermScreen *screen, int bufidx, int new_rows, int new_cols, bool active, VTermStateFields *statefields) +{ + int old_rows = screen->rows; + int old_cols = screen->cols; + + ScreenCell *old_buffer = screen->buffers[bufidx]; + VTermLineInfo *old_lineinfo = statefields->lineinfos[bufidx]; + + ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * new_rows * new_cols); + VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt, sizeof(new_lineinfo[0]) * new_rows); + + int old_row = old_rows - 1; + int new_row = new_rows - 1; + + VTermPos old_cursor = statefields->pos; + VTermPos new_cursor = { -1, -1 }; + +#ifdef DEBUG_REFLOW + fprintf(stderr, "Resizing from %dx%d to %dx%d; cursor was at (%d,%d)\n", + old_cols, old_rows, new_cols, new_rows, old_cursor.col, old_cursor.row); +#endif + + /* Keep track of the final row that is knonw to be blank, so we know what + * spare space we have for scrolling into + */ + int final_blank_row = new_rows; + + while(old_row >= 0) { + int old_row_end = old_row; + /* TODO: Stop if dwl or dhl */ + while(REFLOW && old_lineinfo && old_row >= 0 && old_lineinfo[old_row].continuation) + old_row--; + int old_row_start = old_row; + + int width = 0; + for(int row = old_row_start; row <= old_row_end; row++) { + if(REFLOW && row < (old_rows - 1) && old_lineinfo[row + 1].continuation) + width += old_cols; + else + width += line_popcount(old_buffer, row, old_rows, old_cols); + } + + if(final_blank_row == (new_row + 1) && width == 0) + final_blank_row = new_row; + + int new_height = REFLOW + ? width ? (width + new_cols - 1) / new_cols : 1 + : 1; + + int new_row_end = new_row; + int new_row_start = new_row - new_height + 1; + + old_row = old_row_start; + int old_col = 0; + + int spare_rows = new_rows - final_blank_row; + + if(new_row_start < 0 && /* we'd fall off the top */ + spare_rows >= 0 && /* we actually have spare rows */ + (!active || new_cursor.row == -1 || (new_cursor.row - new_row_start) < new_rows)) + { + /* Attempt to scroll content down into the blank rows at the bottom to + * make it fit + */ + int downwards = -new_row_start; + if(downwards > spare_rows) + downwards = spare_rows; + int rowcount = new_rows - downwards; + +#ifdef DEBUG_REFLOW + fprintf(stderr, " scroll %d rows +%d downwards\n", rowcount, downwards); +#endif + + memmove(&new_buffer[downwards * new_cols], &new_buffer[0], rowcount * new_cols * sizeof(ScreenCell)); + memmove(&new_lineinfo[downwards], &new_lineinfo[0], rowcount * sizeof(new_lineinfo[0])); + + new_row += downwards; + new_row_start += downwards; + new_row_end += downwards; + + if(new_cursor.row >= 0) + new_cursor.row += downwards; + + final_blank_row += downwards; + } + +#ifdef DEBUG_REFLOW + fprintf(stderr, " rows [%d..%d] <- [%d..%d] width=%d\n", + new_row_start, new_row_end, old_row_start, old_row_end, width); +#endif + + if(new_row_start < 0) + break; + + for(new_row = new_row_start, old_row = old_row_start; new_row <= new_row_end; new_row++) { + int count = width >= new_cols ? new_cols : width; + width -= count; + + int new_col = 0; + + while(count) { + /* TODO: This could surely be done a lot faster by memcpy()'ing the entire range */ + new_buffer[new_row * new_cols + new_col] = old_buffer[old_row * old_cols + old_col]; + + if(old_cursor.row == old_row && old_cursor.col == old_col) + new_cursor.row = new_row, new_cursor.col = new_col; + + old_col++; + if(old_col == old_cols) { + old_row++; + + if(!REFLOW) { + new_col++; + break; + } + old_col = 0; + } + + new_col++; + count--; + } + + if(old_cursor.row == old_row && old_cursor.col >= old_col) { + new_cursor.row = new_row, new_cursor.col = (old_cursor.col - old_col + new_col); + if(new_cursor.col >= new_cols) + new_cursor.col = new_cols-1; + } + + while(new_col < new_cols) { + clearcell(screen, &new_buffer[new_row * new_cols + new_col]); + new_col++; + } + + new_lineinfo[new_row].continuation = (new_row > new_row_start); + } + + old_row = old_row_start - 1; + new_row = new_row_start - 1; + } + + if(old_cursor.row <= old_row) { + /* cursor would have moved entirely off the top of the screen; lets just + * bring it within range */ + new_cursor.row = 0, new_cursor.col = old_cursor.col; + if(new_cursor.col >= new_cols) + new_cursor.col = new_cols-1; + } + + /* We really expect the cursor position to be set by now */ + if(active && (new_cursor.row == -1 || new_cursor.col == -1)) { + fprintf(stderr, "screen_resize failed to update cursor position\n"); + abort(); + } + + if(old_row >= 0 && bufidx == BUFIDX_PRIMARY) { + /* Push spare lines to scrollback buffer */ + for(int row = 0; row <= old_row; row++) + sb_pushline_from_row(screen, row); + if(active) + statefields->pos.row -= (old_row + 1); + } + if(new_row >= 0 && bufidx == BUFIDX_PRIMARY && + screen->callbacks && screen->callbacks->sb_popline) { + /* Try to backfill rows by popping scrollback buffer */ + while(new_row >= 0) { + if(!(screen->callbacks->sb_popline(old_cols, screen->sb_buffer, screen->cbdata))) + break; + + VTermPos pos = { .row = new_row }; + for(pos.col = 0; pos.col < old_cols && pos.col < new_cols; pos.col += screen->sb_buffer[pos.col].width) { + VTermScreenCell *src = &screen->sb_buffer[pos.col]; + ScreenCell *dst = &new_buffer[pos.row * new_cols + pos.col]; + + for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) { + dst->chars[i] = src->chars[i]; + if(!src->chars[i]) + break; + } + + dst->pen.bold = src->attrs.bold; + dst->pen.underline = src->attrs.underline; + dst->pen.italic = src->attrs.italic; + dst->pen.blink = src->attrs.blink; + dst->pen.reverse = src->attrs.reverse ^ screen->global_reverse; + dst->pen.conceal = src->attrs.conceal; + dst->pen.strike = src->attrs.strike; + dst->pen.font = src->attrs.font; + dst->pen.small = src->attrs.small; + dst->pen.baseline = src->attrs.baseline; + + dst->pen.fg = src->fg; + dst->pen.bg = src->bg; + + if(src->width == 2 && pos.col < (new_cols-1)) + (dst + 1)->chars[0] = (uint32_t) -1; + } + for( ; pos.col < new_cols; pos.col++) + clearcell(screen, &new_buffer[pos.row * new_cols + pos.col]); + new_row--; + + if(active) + statefields->pos.row++; + } + } + if(new_row >= 0) { + /* Scroll new rows back up to the top and fill in blanks at the bottom */ + int moverows = new_rows - new_row - 1; + memmove(&new_buffer[0], &new_buffer[(new_row + 1) * new_cols], moverows * new_cols * sizeof(ScreenCell)); + memmove(&new_lineinfo[0], &new_lineinfo[new_row + 1], moverows * sizeof(new_lineinfo[0])); + + new_cursor.row -= (new_row + 1); + + for(new_row = moverows; new_row < new_rows; new_row++) { + for(int col = 0; col < new_cols; col++) + clearcell(screen, &new_buffer[new_row * new_cols + col]); + new_lineinfo[new_row] = (VTermLineInfo){ 0 }; + } + } + + vterm_allocator_free(screen->vt, old_buffer); + screen->buffers[bufidx] = new_buffer; + + vterm_allocator_free(screen->vt, old_lineinfo); + statefields->lineinfos[bufidx] = new_lineinfo; + + if(active) + statefields->pos = new_cursor; + + return; +} + +static int resize(int new_rows, int new_cols, VTermStateFields *fields, void *user) +{ + VTermScreen *screen = user; + + int altscreen_active = (screen->buffers[BUFIDX_ALTSCREEN] && screen->buffer == screen->buffers[BUFIDX_ALTSCREEN]); + + int old_rows = screen->rows; + int old_cols = screen->cols; + + if(new_cols > old_cols) { + /* Ensure that ->sb_buffer is large enough for a new or and old row */ + if(screen->sb_buffer) + vterm_allocator_free(screen->vt, screen->sb_buffer); + + screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols); + } + + resize_buffer(screen, 0, new_rows, new_cols, !altscreen_active, fields); + if(screen->buffers[BUFIDX_ALTSCREEN]) + resize_buffer(screen, 1, new_rows, new_cols, altscreen_active, fields); + else if(new_rows != old_rows) { + /* We don't need a full resize of the altscreen because it isn't enabled + * but we should at least keep the lineinfo the right size */ + vterm_allocator_free(screen->vt, fields->lineinfos[BUFIDX_ALTSCREEN]); + + VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt, sizeof(new_lineinfo[0]) * new_rows); + for(int row = 0; row < new_rows; row++) + new_lineinfo[row] = (VTermLineInfo){ 0 }; + + fields->lineinfos[BUFIDX_ALTSCREEN] = new_lineinfo; + } + + screen->buffer = altscreen_active ? screen->buffers[BUFIDX_ALTSCREEN] : screen->buffers[BUFIDX_PRIMARY]; + + screen->rows = new_rows; + screen->cols = new_cols; + + if(new_cols <= old_cols) { + if(screen->sb_buffer) + vterm_allocator_free(screen->vt, screen->sb_buffer); + + screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols); + } + + /* TODO: Maaaaybe we can optimise this if there's no reflow happening */ + damagescreen(screen); + + if(screen->callbacks && screen->callbacks->resize) + return (*screen->callbacks->resize)(new_rows, new_cols, screen->cbdata); + + return 1; +} + +static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user) +{ + VTermScreen *screen = user; + + if(newinfo->doublewidth != oldinfo->doublewidth || + newinfo->doubleheight != oldinfo->doubleheight) { + for(int col = 0; col < screen->cols; col++) { + ScreenCell *cell = getcell(screen, row, col); + cell->pen.dwl = newinfo->doublewidth; + cell->pen.dhl = newinfo->doubleheight; + } + + VTermRect rect = { + .start_row = row, + .end_row = row + 1, + .start_col = 0, + .end_col = newinfo->doublewidth ? screen->cols / 2 : screen->cols, + }; + damagerect(screen, rect); + + if(newinfo->doublewidth) { + rect.start_col = screen->cols / 2; + rect.end_col = screen->cols; + + erase_internal(rect, 0, user); + } + } + + return 1; +} + +static int sb_clear(void *user) { + VTermScreen *screen = user; + + if(screen->callbacks && screen->callbacks->sb_clear) + if((*screen->callbacks->sb_clear)(screen->cbdata)) + return 1; + + return 0; +} + +static VTermStateCallbacks state_cbs = { + .putglyph = &putglyph, + .movecursor = &movecursor, + .scrollrect = &scrollrect, + .erase = &erase, + .setpenattr = &setpenattr, + .settermprop = &settermprop, + .bell = &bell, + .resize = &resize, + .setlineinfo = &setlineinfo, + .sb_clear = &sb_clear, +}; + +static VTermScreen *screen_new(VTerm *vt) +{ + VTermState *state = vterm_obtain_state(vt); + if(!state) + return NULL; + + VTermScreen *screen = vterm_allocator_malloc(vt, sizeof(VTermScreen)); + int rows, cols; + + vterm_get_size(vt, &rows, &cols); + + screen->vt = vt; + screen->state = state; + + screen->damage_merge = VTERM_DAMAGE_CELL; + screen->damaged.start_row = -1; + screen->pending_scrollrect.start_row = -1; + + screen->rows = rows; + screen->cols = cols; + + screen->global_reverse = false; + screen->reflow = false; + + screen->callbacks = NULL; + screen->cbdata = NULL; + + screen->buffers[BUFIDX_PRIMARY] = alloc_buffer(screen, rows, cols); + + screen->buffer = screen->buffers[BUFIDX_PRIMARY]; + + screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * cols); + + vterm_state_set_callbacks(screen->state, &state_cbs, screen); + + return screen; +} + +INTERNAL void vterm_screen_free(VTermScreen *screen) +{ + vterm_allocator_free(screen->vt, screen->buffers[BUFIDX_PRIMARY]); + if(screen->buffers[BUFIDX_ALTSCREEN]) + vterm_allocator_free(screen->vt, screen->buffers[BUFIDX_ALTSCREEN]); + + vterm_allocator_free(screen->vt, screen->sb_buffer); + + vterm_allocator_free(screen->vt, screen); +} + +void vterm_screen_reset(VTermScreen *screen, int hard) +{ + screen->damaged.start_row = -1; + screen->pending_scrollrect.start_row = -1; + vterm_state_reset(screen->state, hard); + vterm_screen_flush_damage(screen); +} + +static size_t _get_chars(const VTermScreen *screen, const int utf8, void *buffer, size_t len, const VTermRect rect) +{ + size_t outpos = 0; + int padding = 0; + +#define PUT(c) \ + if(utf8) { \ + size_t thislen = utf8_seqlen(c); \ + if(buffer && outpos + thislen <= len) \ + outpos += fill_utf8((c), (char *)buffer + outpos); \ + else \ + outpos += thislen; \ + } \ + else { \ + if(buffer && outpos + 1 <= len) \ + ((uint32_t*)buffer)[outpos++] = (c); \ + else \ + outpos++; \ + } + + for(int row = rect.start_row; row < rect.end_row; row++) { + for(int col = rect.start_col; col < rect.end_col; col++) { + ScreenCell *cell = getcell(screen, row, col); + + if(cell->chars[0] == 0) + // Erased cell, might need a space + padding++; + else if(cell->chars[0] == (uint32_t)-1) + // Gap behind a double-width char, do nothing + ; + else { + while(padding) { + PUT(UNICODE_SPACE); + padding--; + } + for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) { + PUT(cell->chars[i]); + } + } + } + + if(row < rect.end_row - 1) { + PUT(UNICODE_LINEFEED); + padding = 0; + } + } + + return outpos; +} + +size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect) +{ + return _get_chars(screen, 0, chars, len, rect); +} + +size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect) +{ + return _get_chars(screen, 1, str, len, rect); +} + +/* Copy internal to external representation of a screen cell */ +int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell) +{ + ScreenCell *intcell = getcell(screen, pos.row, pos.col); + if(!intcell) + return 0; + + for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) { + cell->chars[i] = intcell->chars[i]; + if(!intcell->chars[i]) + break; + } + + cell->attrs.bold = intcell->pen.bold; + cell->attrs.underline = intcell->pen.underline; + cell->attrs.italic = intcell->pen.italic; + cell->attrs.blink = intcell->pen.blink; + cell->attrs.reverse = intcell->pen.reverse ^ screen->global_reverse; + cell->attrs.conceal = intcell->pen.conceal; + cell->attrs.strike = intcell->pen.strike; + cell->attrs.font = intcell->pen.font; + cell->attrs.small = intcell->pen.small; + cell->attrs.baseline = intcell->pen.baseline; + + cell->attrs.dwl = intcell->pen.dwl; + cell->attrs.dhl = intcell->pen.dhl; + + cell->fg = intcell->pen.fg; + cell->bg = intcell->pen.bg; + + if(pos.col < (screen->cols - 1) && + getcell(screen, pos.row, pos.col + 1)->chars[0] == (uint32_t)-1) + cell->width = 2; + else + cell->width = 1; + + return 1; +} + +int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos) +{ + /* This cell is EOL if this and every cell to the right is black */ + for(; pos.col < screen->cols; pos.col++) { + ScreenCell *cell = getcell(screen, pos.row, pos.col); + if(cell->chars[0] != 0) + return 0; + } + + return 1; +} + +VTermScreen *vterm_obtain_screen(VTerm *vt) +{ + if(vt->screen) + return vt->screen; + + VTermScreen *screen = screen_new(vt); + vt->screen = screen; + + return screen; +} + +void vterm_screen_enable_reflow(VTermScreen *screen, bool reflow) +{ + screen->reflow = reflow; +} + +#undef vterm_screen_set_reflow +void vterm_screen_set_reflow(VTermScreen *screen, bool reflow) +{ + vterm_screen_enable_reflow(screen, reflow); +} + +void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen) +{ + if(!screen->buffers[BUFIDX_ALTSCREEN] && altscreen) { + int rows, cols; + vterm_get_size(screen->vt, &rows, &cols); + + screen->buffers[BUFIDX_ALTSCREEN] = alloc_buffer(screen, rows, cols); + } +} + +void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user) +{ + screen->callbacks = callbacks; + screen->cbdata = user; +} + +void *vterm_screen_get_cbdata(VTermScreen *screen) +{ + return screen->cbdata; +} + +void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermStateFallbacks *fallbacks, void *user) +{ + vterm_state_set_unrecognised_fallbacks(screen->state, fallbacks, user); +} + +void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen) +{ + return vterm_state_get_unrecognised_fbdata(screen->state); +} + +void vterm_screen_flush_damage(VTermScreen *screen) +{ + if(screen->pending_scrollrect.start_row != -1) { + vterm_scroll_rect(screen->pending_scrollrect, screen->pending_scroll_downward, screen->pending_scroll_rightward, + moverect_user, erase_user, screen); + + screen->pending_scrollrect.start_row = -1; + } + + if(screen->damaged.start_row != -1) { + if(screen->callbacks && screen->callbacks->damage) + (*screen->callbacks->damage)(screen->damaged, screen->cbdata); + + screen->damaged.start_row = -1; + } +} + +void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size) +{ + vterm_screen_flush_damage(screen); + screen->damage_merge = size; +} + +static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b) +{ + if((attrs & VTERM_ATTR_BOLD_MASK) && (a->pen.bold != b->pen.bold)) + return 1; + if((attrs & VTERM_ATTR_UNDERLINE_MASK) && (a->pen.underline != b->pen.underline)) + return 1; + if((attrs & VTERM_ATTR_ITALIC_MASK) && (a->pen.italic != b->pen.italic)) + return 1; + if((attrs & VTERM_ATTR_BLINK_MASK) && (a->pen.blink != b->pen.blink)) + return 1; + if((attrs & VTERM_ATTR_REVERSE_MASK) && (a->pen.reverse != b->pen.reverse)) + return 1; + if((attrs & VTERM_ATTR_CONCEAL_MASK) && (a->pen.conceal != b->pen.conceal)) + return 1; + if((attrs & VTERM_ATTR_STRIKE_MASK) && (a->pen.strike != b->pen.strike)) + return 1; + if((attrs & VTERM_ATTR_FONT_MASK) && (a->pen.font != b->pen.font)) + return 1; + if((attrs & VTERM_ATTR_FOREGROUND_MASK) && !vterm_color_is_equal(&a->pen.fg, &b->pen.fg)) + return 1; + if((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_is_equal(&a->pen.bg, &b->pen.bg)) + return 1; + if((attrs & VTERM_ATTR_SMALL_MASK) && (a->pen.small != b->pen.small)) + return 1; + if((attrs & VTERM_ATTR_BASELINE_MASK) && (a->pen.baseline != b->pen.baseline)) + return 1; + + return 0; +} + +int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs) +{ + ScreenCell *target = getcell(screen, pos.row, pos.col); + + // TODO: bounds check + extent->start_row = pos.row; + extent->end_row = pos.row + 1; + + if(extent->start_col < 0) + extent->start_col = 0; + if(extent->end_col < 0) + extent->end_col = screen->cols; + + int col; + + for(col = pos.col - 1; col >= extent->start_col; col--) + if(attrs_differ(attrs, target, getcell(screen, pos.row, col))) + break; + extent->start_col = col + 1; + + for(col = pos.col + 1; col < extent->end_col; col++) + if(attrs_differ(attrs, target, getcell(screen, pos.row, col))) + break; + extent->end_col = col - 1; + + return 1; +} + +void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col) +{ + vterm_state_convert_color_to_rgb(screen->state, col); +} + +static void reset_default_colours(VTermScreen *screen, ScreenCell *buffer) +{ + for(int row = 0; row <= screen->rows - 1; row++) + for(int col = 0; col <= screen->cols - 1; col++) { + ScreenCell *cell = &buffer[row * screen->cols + col]; + if(VTERM_COLOR_IS_DEFAULT_FG(&cell->pen.fg)) + cell->pen.fg = screen->pen.fg; + if(VTERM_COLOR_IS_DEFAULT_BG(&cell->pen.bg)) + cell->pen.bg = screen->pen.bg; + } +} + +void vterm_screen_set_default_colors(VTermScreen *screen, const VTermColor *default_fg, const VTermColor *default_bg) +{ + vterm_state_set_default_colors(screen->state, default_fg, default_bg); + + if(default_fg && VTERM_COLOR_IS_DEFAULT_FG(&screen->pen.fg)) { + screen->pen.fg = *default_fg; + screen->pen.fg.type = (screen->pen.fg.type & ~VTERM_COLOR_DEFAULT_MASK) + | VTERM_COLOR_DEFAULT_FG; + } + + if(default_bg && VTERM_COLOR_IS_DEFAULT_BG(&screen->pen.bg)) { + screen->pen.bg = *default_bg; + screen->pen.bg.type = (screen->pen.bg.type & ~VTERM_COLOR_DEFAULT_MASK) + | VTERM_COLOR_DEFAULT_BG; + } + + reset_default_colours(screen, screen->buffers[0]); + if(screen->buffers[1]) + reset_default_colours(screen, screen->buffers[1]); +} diff --git a/src/libs/3rdparty/libvterm/src/state.c b/src/libs/3rdparty/libvterm/src/state.c new file mode 100644 index 00000000000..313e746e77c --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/state.c @@ -0,0 +1,2315 @@ +#include "vterm_internal.h" + +#include +#include + +#define strneq(a,b,n) (strncmp(a,b,n)==0) + +#if defined(DEBUG) && DEBUG > 1 +# define DEBUG_GLYPH_COMBINE +#endif + +/* Some convenient wrappers to make callback functions easier */ + +static void putglyph(VTermState *state, const uint32_t chars[], int width, VTermPos pos) +{ + VTermGlyphInfo info = { + .chars = chars, + .width = width, + .protected_cell = state->protected_cell, + .dwl = state->lineinfo[pos.row].doublewidth, + .dhl = state->lineinfo[pos.row].doubleheight, + }; + + if(state->callbacks && state->callbacks->putglyph) + if((*state->callbacks->putglyph)(&info, pos, state->cbdata)) + return; + + DEBUG_LOG("libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row); +} + +static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom) +{ + if(state->pos.col == oldpos->col && state->pos.row == oldpos->row) + return; + + if(cancel_phantom) + state->at_phantom = 0; + + if(state->callbacks && state->callbacks->movecursor) + if((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible, state->cbdata)) + return; +} + +static void erase(VTermState *state, VTermRect rect, int selective) +{ + if(rect.end_col == state->cols) { + /* If we're erasing the final cells of any lines, cancel the continuation + * marker on the subsequent line + */ + for(int row = rect.start_row + 1; row < rect.end_row + 1 && row < state->rows; row++) + state->lineinfo[row].continuation = 0; + } + + if(state->callbacks && state->callbacks->erase) + if((*state->callbacks->erase)(rect, selective, state->cbdata)) + return; +} + +static VTermState *vterm_state_new(VTerm *vt) +{ + VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState)); + + state->vt = vt; + + state->rows = vt->rows; + state->cols = vt->cols; + + state->mouse_col = 0; + state->mouse_row = 0; + state->mouse_buttons = 0; + + state->mouse_protocol = MOUSE_X10; + + state->callbacks = NULL; + state->cbdata = NULL; + + state->selection.callbacks = NULL; + state->selection.user = NULL; + state->selection.buffer = NULL; + + vterm_state_newpen(state); + + state->bold_is_highbright = 0; + + state->combine_chars_size = 16; + state->combine_chars = vterm_allocator_malloc(state->vt, state->combine_chars_size * sizeof(state->combine_chars[0])); + + state->tabstops = vterm_allocator_malloc(state->vt, (state->cols + 7) / 8); + + state->lineinfos[BUFIDX_PRIMARY] = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo)); + /* TODO: Make an 'enable' function */ + state->lineinfos[BUFIDX_ALTSCREEN] = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo)); + state->lineinfo = state->lineinfos[BUFIDX_PRIMARY]; + + state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u'); + if(*state->encoding_utf8.enc->init) + (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data); + + return state; +} + +INTERNAL void vterm_state_free(VTermState *state) +{ + vterm_allocator_free(state->vt, state->tabstops); + vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_PRIMARY]); + if(state->lineinfos[BUFIDX_ALTSCREEN]) + vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_ALTSCREEN]); + vterm_allocator_free(state->vt, state->combine_chars); + vterm_allocator_free(state->vt, state); +} + +static void scroll(VTermState *state, VTermRect rect, int downward, int rightward) +{ + if(!downward && !rightward) + return; + + int rows = rect.end_row - rect.start_row; + if(downward > rows) + downward = rows; + else if(downward < -rows) + downward = -rows; + + int cols = rect.end_col - rect.start_col; + if(rightward > cols) + rightward = cols; + else if(rightward < -cols) + rightward = -cols; + + // Update lineinfo if full line + if(rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) { + int height = rect.end_row - rect.start_row - abs(downward); + + if(downward > 0) { + memmove(state->lineinfo + rect.start_row, + state->lineinfo + rect.start_row + downward, + height * sizeof(state->lineinfo[0])); + for(int row = rect.end_row - downward; row < rect.end_row; row++) + state->lineinfo[row] = (VTermLineInfo){ 0 }; + } + else { + memmove(state->lineinfo + rect.start_row - downward, + state->lineinfo + rect.start_row, + height * sizeof(state->lineinfo[0])); + for(int row = rect.start_row; row < rect.start_row - downward; row++) + state->lineinfo[row] = (VTermLineInfo){ 0 }; + } + } + + if(state->callbacks && state->callbacks->scrollrect) + if((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata)) + return; + + if(state->callbacks) + vterm_scroll_rect(rect, downward, rightward, + state->callbacks->moverect, state->callbacks->erase, state->cbdata); +} + +static void linefeed(VTermState *state) +{ + if(state->pos.row == SCROLLREGION_BOTTOM(state) - 1) { + VTermRect rect = { + .start_row = state->scrollregion_top, + .end_row = SCROLLREGION_BOTTOM(state), + .start_col = SCROLLREGION_LEFT(state), + .end_col = SCROLLREGION_RIGHT(state), + }; + + scroll(state, rect, 1, 0); + } + else if(state->pos.row < state->rows-1) + state->pos.row++; +} + +static void grow_combine_buffer(VTermState *state) +{ + size_t new_size = state->combine_chars_size * 2; + uint32_t *new_chars = vterm_allocator_malloc(state->vt, new_size * sizeof(new_chars[0])); + + memcpy(new_chars, state->combine_chars, state->combine_chars_size * sizeof(new_chars[0])); + + vterm_allocator_free(state->vt, state->combine_chars); + + state->combine_chars = new_chars; + state->combine_chars_size = new_size; +} + +static void set_col_tabstop(VTermState *state, int col) +{ + unsigned char mask = 1 << (col & 7); + state->tabstops[col >> 3] |= mask; +} + +static void clear_col_tabstop(VTermState *state, int col) +{ + unsigned char mask = 1 << (col & 7); + state->tabstops[col >> 3] &= ~mask; +} + +static int is_col_tabstop(VTermState *state, int col) +{ + unsigned char mask = 1 << (col & 7); + return state->tabstops[col >> 3] & mask; +} + +static int is_cursor_in_scrollregion(const VTermState *state) +{ + if(state->pos.row < state->scrollregion_top || + state->pos.row >= SCROLLREGION_BOTTOM(state)) + return 0; + if(state->pos.col < SCROLLREGION_LEFT(state) || + state->pos.col >= SCROLLREGION_RIGHT(state)) + return 0; + + return 1; +} + +static void tab(VTermState *state, int count, int direction) +{ + while(count > 0) { + if(direction > 0) { + if(state->pos.col >= THISROWWIDTH(state)-1) + return; + + state->pos.col++; + } + else if(direction < 0) { + if(state->pos.col < 1) + return; + + state->pos.col--; + } + + if(is_col_tabstop(state, state->pos.col)) + count--; + } +} + +#define NO_FORCE 0 +#define FORCE 1 + +#define DWL_OFF 0 +#define DWL_ON 1 + +#define DHL_OFF 0 +#define DHL_TOP 1 +#define DHL_BOTTOM 2 + +static void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl) +{ + VTermLineInfo info = state->lineinfo[row]; + + if(dwl == DWL_OFF) + info.doublewidth = DWL_OFF; + else if(dwl == DWL_ON) + info.doublewidth = DWL_ON; + // else -1 to ignore + + if(dhl == DHL_OFF) + info.doubleheight = DHL_OFF; + else if(dhl == DHL_TOP) + info.doubleheight = DHL_TOP; + else if(dhl == DHL_BOTTOM) + info.doubleheight = DHL_BOTTOM; + + if((state->callbacks && + state->callbacks->setlineinfo && + (*state->callbacks->setlineinfo)(row, &info, state->lineinfo + row, state->cbdata)) + || force) + state->lineinfo[row] = info; +} + +static int on_text(const char bytes[], size_t len, void *user) +{ + VTermState *state = user; + + VTermPos oldpos = state->pos; + + uint32_t *codepoints = (uint32_t *)(state->vt->tmpbuffer); + size_t maxpoints = (state->vt->tmpbuffer_len) / sizeof(uint32_t); + + int npoints = 0; + size_t eaten = 0; + + VTermEncodingInstance *encoding = + state->gsingle_set ? &state->encoding[state->gsingle_set] : + !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] : + state->vt->mode.utf8 ? &state->encoding_utf8 : + &state->encoding[state->gr_set]; + + (*encoding->enc->decode)(encoding->enc, encoding->data, + codepoints, &npoints, state->gsingle_set ? 1 : maxpoints, + bytes, &eaten, len); + + /* There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet + * for even a single codepoint + */ + if(!npoints) + return eaten; + + if(state->gsingle_set && npoints) + state->gsingle_set = 0; + + int i = 0; + + /* This is a combining char. that needs to be merged with the previous + * glyph output */ + if(vterm_unicode_is_combining(codepoints[i])) { + /* See if the cursor has moved since */ + if(state->pos.row == state->combine_pos.row && state->pos.col == state->combine_pos.col + state->combine_width) { +#ifdef DEBUG_GLYPH_COMBINE + int printpos; + printf("DEBUG: COMBINING SPLIT GLYPH of chars {"); + for(printpos = 0; state->combine_chars[printpos]; printpos++) + printf("U+%04x ", state->combine_chars[printpos]); + printf("} + {"); +#endif + + /* Find where we need to append these combining chars */ + int saved_i = 0; + while(state->combine_chars[saved_i]) + saved_i++; + + /* Add extra ones */ + while(i < npoints && vterm_unicode_is_combining(codepoints[i])) { + if(saved_i >= state->combine_chars_size) + grow_combine_buffer(state); + state->combine_chars[saved_i++] = codepoints[i++]; + } + if(saved_i >= state->combine_chars_size) + grow_combine_buffer(state); + state->combine_chars[saved_i] = 0; + +#ifdef DEBUG_GLYPH_COMBINE + for(; state->combine_chars[printpos]; printpos++) + printf("U+%04x ", state->combine_chars[printpos]); + printf("}\n"); +#endif + + /* Now render it */ + putglyph(state, state->combine_chars, state->combine_width, state->combine_pos); + } + else { + DEBUG_LOG("libvterm: TODO: Skip over split char+combining\n"); + } + } + + for(; i < npoints; i++) { + // Try to find combining characters following this + int glyph_starts = i; + int glyph_ends; + for(glyph_ends = i + 1; + (glyph_ends < npoints) && (glyph_ends < glyph_starts + VTERM_MAX_CHARS_PER_CELL); + glyph_ends++) + if(!vterm_unicode_is_combining(codepoints[glyph_ends])) + break; + + int width = 0; + + uint32_t chars[VTERM_MAX_CHARS_PER_CELL + 1]; + + for( ; i < glyph_ends; i++) { + chars[i - glyph_starts] = codepoints[i]; + int this_width = vterm_unicode_width(codepoints[i]); +#ifdef DEBUG + if(this_width < 0) { + fprintf(stderr, "Text with negative-width codepoint U+%04x\n", codepoints[i]); + abort(); + } +#endif + width += this_width; + } + + while(i < npoints && vterm_unicode_is_combining(codepoints[i])) + i++; + + chars[glyph_ends - glyph_starts] = 0; + i--; + +#ifdef DEBUG_GLYPH_COMBINE + int printpos; + printf("DEBUG: COMBINED GLYPH of %d chars {", glyph_ends - glyph_starts); + for(printpos = 0; printpos < glyph_ends - glyph_starts; printpos++) + printf("U+%04x ", chars[printpos]); + printf("}, onscreen width %d\n", width); +#endif + + if(state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) { + linefeed(state); + state->pos.col = 0; + state->at_phantom = 0; + state->lineinfo[state->pos.row].continuation = 1; + } + + if(state->mode.insert) { + /* TODO: This will be a little inefficient for large bodies of text, as + * it'll have to 'ICH' effectively before every glyph. We should scan + * ahead and ICH as many times as required + */ + VTermRect rect = { + .start_row = state->pos.row, + .end_row = state->pos.row + 1, + .start_col = state->pos.col, + .end_col = THISROWWIDTH(state), + }; + scroll(state, rect, 0, -1); + } + + putglyph(state, chars, width, state->pos); + + if(i == npoints - 1) { + /* End of the buffer. Save the chars in case we have to combine with + * more on the next call */ + int save_i; + for(save_i = 0; chars[save_i]; save_i++) { + if(save_i >= state->combine_chars_size) + grow_combine_buffer(state); + state->combine_chars[save_i] = chars[save_i]; + } + if(save_i >= state->combine_chars_size) + grow_combine_buffer(state); + state->combine_chars[save_i] = 0; + state->combine_width = width; + state->combine_pos = state->pos; + } + + if(state->pos.col + width >= THISROWWIDTH(state)) { + if(state->mode.autowrap) + state->at_phantom = 1; + } + else { + state->pos.col += width; + } + } + + updatecursor(state, &oldpos, 0); + +#ifdef DEBUG + if(state->pos.row < 0 || state->pos.row >= state->rows || + state->pos.col < 0 || state->pos.col >= state->cols) { + fprintf(stderr, "Position out of bounds after text: (%d,%d)\n", + state->pos.row, state->pos.col); + abort(); + } +#endif + + return eaten; +} + +static int on_control(unsigned char control, void *user) +{ + VTermState *state = user; + + VTermPos oldpos = state->pos; + + switch(control) { + case 0x07: // BEL - ECMA-48 8.3.3 + if(state->callbacks && state->callbacks->bell) + (*state->callbacks->bell)(state->cbdata); + break; + + case 0x08: // BS - ECMA-48 8.3.5 + if(state->pos.col > 0) + state->pos.col--; + break; + + case 0x09: // HT - ECMA-48 8.3.60 + tab(state, 1, +1); + break; + + case 0x0a: // LF - ECMA-48 8.3.74 + case 0x0b: // VT + case 0x0c: // FF + linefeed(state); + if(state->mode.newline) + state->pos.col = 0; + break; + + case 0x0d: // CR - ECMA-48 8.3.15 + state->pos.col = 0; + break; + + case 0x0e: // LS1 - ECMA-48 8.3.76 + state->gl_set = 1; + break; + + case 0x0f: // LS0 - ECMA-48 8.3.75 + state->gl_set = 0; + break; + + case 0x84: // IND - DEPRECATED but implemented for completeness + linefeed(state); + break; + + case 0x85: // NEL - ECMA-48 8.3.86 + linefeed(state); + state->pos.col = 0; + break; + + case 0x88: // HTS - ECMA-48 8.3.62 + set_col_tabstop(state, state->pos.col); + break; + + case 0x8d: // RI - ECMA-48 8.3.104 + if(state->pos.row == state->scrollregion_top) { + VTermRect rect = { + .start_row = state->scrollregion_top, + .end_row = SCROLLREGION_BOTTOM(state), + .start_col = SCROLLREGION_LEFT(state), + .end_col = SCROLLREGION_RIGHT(state), + }; + + scroll(state, rect, -1, 0); + } + else if(state->pos.row > 0) + state->pos.row--; + break; + + case 0x8e: // SS2 - ECMA-48 8.3.141 + state->gsingle_set = 2; + break; + + case 0x8f: // SS3 - ECMA-48 8.3.142 + state->gsingle_set = 3; + break; + + default: + if(state->fallbacks && state->fallbacks->control) + if((*state->fallbacks->control)(control, state->fbdata)) + return 1; + + return 0; + } + + updatecursor(state, &oldpos, 1); + +#ifdef DEBUG + if(state->pos.row < 0 || state->pos.row >= state->rows || + state->pos.col < 0 || state->pos.col >= state->cols) { + fprintf(stderr, "Position out of bounds after Ctrl %02x: (%d,%d)\n", + control, state->pos.row, state->pos.col); + abort(); + } +#endif + + return 1; +} + +static int settermprop_bool(VTermState *state, VTermProp prop, int v) +{ + VTermValue val = { .boolean = v }; + return vterm_state_set_termprop(state, prop, &val); +} + +static int settermprop_int(VTermState *state, VTermProp prop, int v) +{ + VTermValue val = { .number = v }; + return vterm_state_set_termprop(state, prop, &val); +} + +static int settermprop_string(VTermState *state, VTermProp prop, VTermStringFragment frag) +{ + VTermValue val = { .string = frag }; + return vterm_state_set_termprop(state, prop, &val); +} + +static void savecursor(VTermState *state, int save) +{ + if(save) { + state->saved.pos = state->pos; + state->saved.mode.cursor_visible = state->mode.cursor_visible; + state->saved.mode.cursor_blink = state->mode.cursor_blink; + state->saved.mode.cursor_shape = state->mode.cursor_shape; + + vterm_state_savepen(state, 1); + } + else { + VTermPos oldpos = state->pos; + + state->pos = state->saved.pos; + + settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->saved.mode.cursor_visible); + settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->saved.mode.cursor_blink); + settermprop_int (state, VTERM_PROP_CURSORSHAPE, state->saved.mode.cursor_shape); + + vterm_state_savepen(state, 0); + + updatecursor(state, &oldpos, 1); + } +} + +static int on_escape(const char *bytes, size_t len, void *user) +{ + VTermState *state = user; + + /* Easier to decode this from the first byte, even though the final + * byte terminates it + */ + switch(bytes[0]) { + case ' ': + if(len != 2) + return 0; + + switch(bytes[1]) { + case 'F': // S7C1T + state->vt->mode.ctrl8bit = 0; + break; + + case 'G': // S8C1T + state->vt->mode.ctrl8bit = 1; + break; + + default: + return 0; + } + return 2; + + case '#': + if(len != 2) + return 0; + + switch(bytes[1]) { + case '3': // DECDHL top + if(state->mode.leftrightmargin) + break; + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_TOP); + break; + + case '4': // DECDHL bottom + if(state->mode.leftrightmargin) + break; + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_BOTTOM); + break; + + case '5': // DECSWL + if(state->mode.leftrightmargin) + break; + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_OFF, DHL_OFF); + break; + + case '6': // DECDWL + if(state->mode.leftrightmargin) + break; + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_OFF); + break; + + case '8': // DECALN + { + VTermPos pos; + uint32_t E[] = { 'E', 0 }; + for(pos.row = 0; pos.row < state->rows; pos.row++) + for(pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++) + putglyph(state, E, 1, pos); + break; + } + + default: + return 0; + } + return 2; + + case '(': case ')': case '*': case '+': // SCS + if(len != 2) + return 0; + + { + int setnum = bytes[0] - 0x28; + VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]); + + if(newenc) { + state->encoding[setnum].enc = newenc; + + if(newenc->init) + (*newenc->init)(newenc, state->encoding[setnum].data); + } + } + + return 2; + + case '7': // DECSC + savecursor(state, 1); + return 1; + + case '8': // DECRC + savecursor(state, 0); + return 1; + + case '<': // Ignored by VT100. Used in VT52 mode to switch up to VT100 + return 1; + + case '=': // DECKPAM + state->mode.keypad = 1; + return 1; + + case '>': // DECKPNM + state->mode.keypad = 0; + return 1; + + case 'c': // RIS - ECMA-48 8.3.105 + { + VTermPos oldpos = state->pos; + vterm_state_reset(state, 1); + if(state->callbacks && state->callbacks->movecursor) + (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible, state->cbdata); + return 1; + } + + case 'n': // LS2 - ECMA-48 8.3.78 + state->gl_set = 2; + return 1; + + case 'o': // LS3 - ECMA-48 8.3.80 + state->gl_set = 3; + return 1; + + case '~': // LS1R - ECMA-48 8.3.77 + state->gr_set = 1; + return 1; + + case '}': // LS2R - ECMA-48 8.3.79 + state->gr_set = 2; + return 1; + + case '|': // LS3R - ECMA-48 8.3.81 + state->gr_set = 3; + return 1; + + default: + return 0; + } +} + +static void set_mode(VTermState *state, int num, int val) +{ + switch(num) { + case 4: // IRM - ECMA-48 7.2.10 + state->mode.insert = val; + break; + + case 20: // LNM - ANSI X3.4-1977 + state->mode.newline = val; + break; + + default: + DEBUG_LOG("libvterm: Unknown mode %d\n", num); + return; + } +} + +static void set_dec_mode(VTermState *state, int num, int val) +{ + switch(num) { + case 1: + state->mode.cursor = val; + break; + + case 5: // DECSCNM - screen mode + settermprop_bool(state, VTERM_PROP_REVERSE, val); + break; + + case 6: // DECOM - origin mode + { + VTermPos oldpos = state->pos; + state->mode.origin = val; + state->pos.row = state->mode.origin ? state->scrollregion_top : 0; + state->pos.col = state->mode.origin ? SCROLLREGION_LEFT(state) : 0; + updatecursor(state, &oldpos, 1); + } + break; + + case 7: + state->mode.autowrap = val; + break; + + case 12: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, val); + break; + + case 25: + settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val); + break; + + case 69: // DECVSSM - vertical split screen mode + // DECLRMM - left/right margin mode + state->mode.leftrightmargin = val; + if(val) { + // Setting DECVSSM must clear doublewidth/doubleheight state of every line + for(int row = 0; row < state->rows; row++) + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + } + + break; + + case 1000: + case 1002: + case 1003: + settermprop_int(state, VTERM_PROP_MOUSE, + !val ? VTERM_PROP_MOUSE_NONE : + (num == 1000) ? VTERM_PROP_MOUSE_CLICK : + (num == 1002) ? VTERM_PROP_MOUSE_DRAG : + VTERM_PROP_MOUSE_MOVE); + break; + + case 1004: + state->mode.report_focus = val; + break; + + case 1005: + state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10; + break; + + case 1006: + state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10; + break; + + case 1015: + state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10; + break; + + case 1047: + settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); + break; + + case 1048: + savecursor(state, val); + break; + + case 1049: + settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); + savecursor(state, val); + break; + + case 2004: + state->mode.bracketpaste = val; + break; + + default: + DEBUG_LOG("libvterm: Unknown DEC mode %d\n", num); + return; + } +} + +static void request_dec_mode(VTermState *state, int num) +{ + int reply; + + switch(num) { + case 1: + reply = state->mode.cursor; + break; + + case 5: + reply = state->mode.screen; + break; + + case 6: + reply = state->mode.origin; + break; + + case 7: + reply = state->mode.autowrap; + break; + + case 12: + reply = state->mode.cursor_blink; + break; + + case 25: + reply = state->mode.cursor_visible; + break; + + case 69: + reply = state->mode.leftrightmargin; + break; + + case 1000: + reply = state->mouse_flags == MOUSE_WANT_CLICK; + break; + + case 1002: + reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_DRAG); + break; + + case 1003: + reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_MOVE); + break; + + case 1004: + reply = state->mode.report_focus; + break; + + case 1005: + reply = state->mouse_protocol == MOUSE_UTF8; + break; + + case 1006: + reply = state->mouse_protocol == MOUSE_SGR; + break; + + case 1015: + reply = state->mouse_protocol == MOUSE_RXVT; + break; + + case 1047: + reply = state->mode.alt_screen; + break; + + case 2004: + reply = state->mode.bracketpaste; + break; + + default: + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0); + return; + } + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, reply ? 1 : 2); +} + +static void request_version_string(VTermState *state) +{ + vterm_push_output_sprintf_str(state->vt, C1_DCS, true, ">|libvterm(%d.%d)", + VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR); +} + +static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user) +{ + VTermState *state = user; + int leader_byte = 0; + int intermed_byte = 0; + int cancel_phantom = 1; + + if(leader && leader[0]) { + if(leader[1]) // longer than 1 char + return 0; + + switch(leader[0]) { + case '?': + case '>': + leader_byte = leader[0]; + break; + default: + return 0; + } + } + + if(intermed && intermed[0]) { + if(intermed[1]) // longer than 1 char + return 0; + + switch(intermed[0]) { + case ' ': + case '"': + case '$': + case '\'': + intermed_byte = intermed[0]; + break; + default: + return 0; + } + } + + VTermPos oldpos = state->pos; + + // Some temporaries for later code + int count, val; + int row, col; + VTermRect rect; + int selective; + +#define LBOUND(v,min) if((v) < (min)) (v) = (min) +#define UBOUND(v,max) if((v) > (max)) (v) = (max) + +#define LEADER(l,b) ((l << 8) | b) +#define INTERMED(i,b) ((i << 16) | b) + + switch(intermed_byte << 16 | leader_byte << 8 | command) { + case 0x40: // ICH - ECMA-48 8.3.64 + count = CSI_ARG_COUNT(args[0]); + + if(!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; + if(state->mode.leftrightmargin) + rect.end_col = SCROLLREGION_RIGHT(state); + else + rect.end_col = THISROWWIDTH(state); + + scroll(state, rect, 0, -count); + + break; + + case 0x41: // CUU - ECMA-48 8.3.22 + count = CSI_ARG_COUNT(args[0]); + state->pos.row -= count; + state->at_phantom = 0; + break; + + case 0x42: // CUD - ECMA-48 8.3.19 + count = CSI_ARG_COUNT(args[0]); + state->pos.row += count; + state->at_phantom = 0; + break; + + case 0x43: // CUF - ECMA-48 8.3.20 + count = CSI_ARG_COUNT(args[0]); + state->pos.col += count; + state->at_phantom = 0; + break; + + case 0x44: // CUB - ECMA-48 8.3.18 + count = CSI_ARG_COUNT(args[0]); + state->pos.col -= count; + state->at_phantom = 0; + break; + + case 0x45: // CNL - ECMA-48 8.3.12 + count = CSI_ARG_COUNT(args[0]); + state->pos.col = 0; + state->pos.row += count; + state->at_phantom = 0; + break; + + case 0x46: // CPL - ECMA-48 8.3.13 + count = CSI_ARG_COUNT(args[0]); + state->pos.col = 0; + state->pos.row -= count; + state->at_phantom = 0; + break; + + case 0x47: // CHA - ECMA-48 8.3.9 + val = CSI_ARG_OR(args[0], 1); + state->pos.col = val-1; + state->at_phantom = 0; + break; + + case 0x48: // CUP - ECMA-48 8.3.21 + row = CSI_ARG_OR(args[0], 1); + col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); + // zero-based + state->pos.row = row-1; + state->pos.col = col-1; + if(state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + state->at_phantom = 0; + break; + + case 0x49: // CHT - ECMA-48 8.3.10 + count = CSI_ARG_COUNT(args[0]); + tab(state, count, +1); + break; + + case 0x4a: // ED - ECMA-48 8.3.39 + case LEADER('?', 0x4a): // DECSED - Selective Erase in Display + selective = (leader_byte == '?'); + switch(CSI_ARG(args[0])) { + case CSI_ARG_MISSING: + case 0: + rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; rect.end_col = state->cols; + if(rect.end_col > rect.start_col) + erase(state, rect, selective); + + rect.start_row = state->pos.row + 1; rect.end_row = state->rows; + rect.start_col = 0; + for(int row = rect.start_row; row < rect.end_row; row++) + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + if(rect.end_row > rect.start_row) + erase(state, rect, selective); + break; + + case 1: + rect.start_row = 0; rect.end_row = state->pos.row; + rect.start_col = 0; rect.end_col = state->cols; + for(int row = rect.start_row; row < rect.end_row; row++) + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + if(rect.end_col > rect.start_col) + erase(state, rect, selective); + + rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; + rect.end_col = state->pos.col + 1; + if(rect.end_row > rect.start_row) + erase(state, rect, selective); + break; + + case 2: + rect.start_row = 0; rect.end_row = state->rows; + rect.start_col = 0; rect.end_col = state->cols; + for(int row = rect.start_row; row < rect.end_row; row++) + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + erase(state, rect, selective); + break; + + case 3: + if(state->callbacks && state->callbacks->sb_clear) + if((*state->callbacks->sb_clear)(state->cbdata)) + return 1; + break; + } + break; + + case 0x4b: // EL - ECMA-48 8.3.41 + case LEADER('?', 0x4b): // DECSEL - Selective Erase in Line + selective = (leader_byte == '?'); + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + + switch(CSI_ARG(args[0])) { + case CSI_ARG_MISSING: + case 0: + rect.start_col = state->pos.col; rect.end_col = THISROWWIDTH(state); break; + case 1: + rect.start_col = 0; rect.end_col = state->pos.col + 1; break; + case 2: + rect.start_col = 0; rect.end_col = THISROWWIDTH(state); break; + default: + return 0; + } + + if(rect.end_col > rect.start_col) + erase(state, rect, selective); + + break; + + case 0x4c: // IL - ECMA-48 8.3.67 + count = CSI_ARG_COUNT(args[0]); + + if(!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->pos.row; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, -count, 0); + + break; + + case 0x4d: // DL - ECMA-48 8.3.32 + count = CSI_ARG_COUNT(args[0]); + + if(!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->pos.row; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, count, 0); + + break; + + case 0x50: // DCH - ECMA-48 8.3.26 + count = CSI_ARG_COUNT(args[0]); + + if(!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; + if(state->mode.leftrightmargin) + rect.end_col = SCROLLREGION_RIGHT(state); + else + rect.end_col = THISROWWIDTH(state); + + scroll(state, rect, 0, count); + + break; + + case 0x53: // SU - ECMA-48 8.3.147 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, count, 0); + + break; + + case 0x54: // SD - ECMA-48 8.3.113 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, -count, 0); + + break; + + case 0x58: // ECH - ECMA-48 8.3.38 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; + rect.end_col = state->pos.col + count; + UBOUND(rect.end_col, THISROWWIDTH(state)); + + erase(state, rect, 0); + break; + + case 0x5a: // CBT - ECMA-48 8.3.7 + count = CSI_ARG_COUNT(args[0]); + tab(state, count, -1); + break; + + case 0x60: // HPA - ECMA-48 8.3.57 + col = CSI_ARG_OR(args[0], 1); + state->pos.col = col-1; + state->at_phantom = 0; + break; + + case 0x61: // HPR - ECMA-48 8.3.59 + count = CSI_ARG_COUNT(args[0]); + state->pos.col += count; + state->at_phantom = 0; + break; + + case 0x62: { // REP - ECMA-48 8.3.103 + const int row_width = THISROWWIDTH(state); + count = CSI_ARG_COUNT(args[0]); + col = state->pos.col + count; + UBOUND(col, row_width); + while (state->pos.col < col) { + putglyph(state, state->combine_chars, state->combine_width, state->pos); + state->pos.col += state->combine_width; + } + if (state->pos.col + state->combine_width >= row_width) { + if (state->mode.autowrap) { + state->at_phantom = 1; + cancel_phantom = 0; + } + } + break; + } + + case 0x63: // DA - ECMA-48 8.3.24 + val = CSI_ARG_OR(args[0], 0); + if(val == 0) + // DEC VT100 response + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?1;2c"); + break; + + case LEADER('>', 0x63): // DEC secondary Device Attributes + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, ">%d;%d;%dc", 0, 100, 0); + break; + + case 0x64: // VPA - ECMA-48 8.3.158 + row = CSI_ARG_OR(args[0], 1); + state->pos.row = row-1; + if(state->mode.origin) + state->pos.row += state->scrollregion_top; + state->at_phantom = 0; + break; + + case 0x65: // VPR - ECMA-48 8.3.160 + count = CSI_ARG_COUNT(args[0]); + state->pos.row += count; + state->at_phantom = 0; + break; + + case 0x66: // HVP - ECMA-48 8.3.63 + row = CSI_ARG_OR(args[0], 1); + col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); + // zero-based + state->pos.row = row-1; + state->pos.col = col-1; + if(state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + state->at_phantom = 0; + break; + + case 0x67: // TBC - ECMA-48 8.3.154 + val = CSI_ARG_OR(args[0], 0); + + switch(val) { + case 0: + clear_col_tabstop(state, state->pos.col); + break; + case 3: + case 5: + for(col = 0; col < state->cols; col++) + clear_col_tabstop(state, col); + break; + case 1: + case 2: + case 4: + break; + /* TODO: 1, 2 and 4 aren't meaningful yet without line tab stops */ + default: + return 0; + } + break; + + case 0x68: // SM - ECMA-48 8.3.125 + if(!CSI_ARG_IS_MISSING(args[0])) + set_mode(state, CSI_ARG(args[0]), 1); + break; + + case LEADER('?', 0x68): // DEC private mode set + if(!CSI_ARG_IS_MISSING(args[0])) + set_dec_mode(state, CSI_ARG(args[0]), 1); + break; + + case 0x6a: // HPB - ECMA-48 8.3.58 + count = CSI_ARG_COUNT(args[0]); + state->pos.col -= count; + state->at_phantom = 0; + break; + + case 0x6b: // VPB - ECMA-48 8.3.159 + count = CSI_ARG_COUNT(args[0]); + state->pos.row -= count; + state->at_phantom = 0; + break; + + case 0x6c: // RM - ECMA-48 8.3.106 + if(!CSI_ARG_IS_MISSING(args[0])) + set_mode(state, CSI_ARG(args[0]), 0); + break; + + case LEADER('?', 0x6c): // DEC private mode reset + if(!CSI_ARG_IS_MISSING(args[0])) + set_dec_mode(state, CSI_ARG(args[0]), 0); + break; + + case 0x6d: // SGR - ECMA-48 8.3.117 + vterm_state_setpen(state, args, argcount); + break; + + case LEADER('?', 0x6d): // DECSGR + /* No actual DEC terminal recognised these, but some printers did. These + * are alternative ways to request subscript/superscript/off + */ + for(int argi = 0; argi < argcount; argi++) { + long arg; + switch(arg = CSI_ARG(args[argi])) { + case 4: // Superscript on + arg = 73; + vterm_state_setpen(state, &arg, 1); + break; + case 5: // Subscript on + arg = 74; + vterm_state_setpen(state, &arg, 1); + break; + case 24: // Super+subscript off + arg = 75; + vterm_state_setpen(state, &arg, 1); + break; + } + } + break; + + case 0x6e: // DSR - ECMA-48 8.3.35 + case LEADER('?', 0x6e): // DECDSR + val = CSI_ARG_OR(args[0], 0); + + { + char *qmark = (leader_byte == '?') ? "?" : ""; + + switch(val) { + case 0: case 1: case 2: case 3: case 4: + // ignore - these are replies + break; + case 5: + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s0n", qmark); + break; + case 6: // CPR - cursor position report + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s%d;%dR", qmark, state->pos.row + 1, state->pos.col + 1); + break; + } + } + break; + + + case LEADER('!', 0x70): // DECSTR - DEC soft terminal reset + vterm_state_reset(state, 0); + break; + + case LEADER('?', INTERMED('$', 0x70)): + request_dec_mode(state, CSI_ARG(args[0])); + break; + + case LEADER('>', 0x71): // XTVERSION - xterm query version string + request_version_string(state); + break; + + case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape + val = CSI_ARG_OR(args[0], 1); + + switch(val) { + case 0: case 1: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); + break; + case 2: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); + settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); + break; + case 3: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); + break; + case 4: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); + settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); + break; + case 5: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); + break; + case 6: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); + settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); + break; + } + + break; + + case INTERMED('"', 0x71): // DECSCA - DEC select character protection attribute + val = CSI_ARG_OR(args[0], 0); + + switch(val) { + case 0: case 2: + state->protected_cell = 0; + break; + case 1: + state->protected_cell = 1; + break; + } + + break; + + case 0x72: // DECSTBM - DEC custom + state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1; + state->scrollregion_bottom = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); + LBOUND(state->scrollregion_top, 0); + UBOUND(state->scrollregion_top, state->rows); + LBOUND(state->scrollregion_bottom, -1); + if(state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows) + state->scrollregion_bottom = -1; + else + UBOUND(state->scrollregion_bottom, state->rows); + + if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { + // Invalid + state->scrollregion_top = 0; + state->scrollregion_bottom = -1; + } + + // Setting the scrolling region restores the cursor to the home position + state->pos.row = 0; + state->pos.col = 0; + if(state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + + break; + + case 0x73: // DECSLRM - DEC custom + // Always allow setting these margins, just they won't take effect without DECVSSM + state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1; + state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); + LBOUND(state->scrollregion_left, 0); + UBOUND(state->scrollregion_left, state->cols); + LBOUND(state->scrollregion_right, -1); + if(state->scrollregion_left == 0 && state->scrollregion_right == state->cols) + state->scrollregion_right = -1; + else + UBOUND(state->scrollregion_right, state->cols); + + if(state->scrollregion_right > -1 && + state->scrollregion_right <= state->scrollregion_left) { + // Invalid + state->scrollregion_left = 0; + state->scrollregion_right = -1; + } + + // Setting the scrolling region restores the cursor to the home position + state->pos.row = 0; + state->pos.col = 0; + if(state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + + break; + + case INTERMED('\'', 0x7D): // DECIC + count = CSI_ARG_COUNT(args[0]); + + if(!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = state->pos.col; + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, 0, -count); + + break; + + case INTERMED('\'', 0x7E): // DECDC + count = CSI_ARG_COUNT(args[0]); + + if(!is_cursor_in_scrollregion(state)) + break; + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = state->pos.col; + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, 0, count); + + break; + + default: + if(state->fallbacks && state->fallbacks->csi) + if((*state->fallbacks->csi)(leader, args, argcount, intermed, command, state->fbdata)) + return 1; + + return 0; + } + + if(state->mode.origin) { + LBOUND(state->pos.row, state->scrollregion_top); + UBOUND(state->pos.row, SCROLLREGION_BOTTOM(state)-1); + LBOUND(state->pos.col, SCROLLREGION_LEFT(state)); + UBOUND(state->pos.col, SCROLLREGION_RIGHT(state)-1); + } + else { + LBOUND(state->pos.row, 0); + UBOUND(state->pos.row, state->rows-1); + LBOUND(state->pos.col, 0); + UBOUND(state->pos.col, THISROWWIDTH(state)-1); + } + + updatecursor(state, &oldpos, cancel_phantom); + +#ifdef DEBUG + if(state->pos.row < 0 || state->pos.row >= state->rows || + state->pos.col < 0 || state->pos.col >= state->cols) { + fprintf(stderr, "Position out of bounds after CSI %c: (%d,%d)\n", + command, state->pos.row, state->pos.col); + abort(); + } + + if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { + fprintf(stderr, "Scroll region height out of bounds after CSI %c: %d <= %d\n", + command, SCROLLREGION_BOTTOM(state), state->scrollregion_top); + abort(); + } + + if(SCROLLREGION_RIGHT(state) <= SCROLLREGION_LEFT(state)) { + fprintf(stderr, "Scroll region width out of bounds after CSI %c: %d <= %d\n", + command, SCROLLREGION_RIGHT(state), SCROLLREGION_LEFT(state)); + abort(); + } +#endif + + return 1; +} + +static char base64_one(uint8_t b) +{ + if(b < 26) + return 'A' + b; + else if(b < 52) + return 'a' + b - 26; + else if(b < 62) + return '0' + b - 52; + else if(b == 62) + return '+'; + else if(b == 63) + return '/'; + return 0; +} + +static uint8_t unbase64one(char c) +{ + if(c >= 'A' && c <= 'Z') + return c - 'A'; + else if(c >= 'a' && c <= 'z') + return c - 'a' + 26; + else if(c >= '0' && c <= '9') + return c - '0' + 52; + else if(c == '+') + return 62; + else if(c == '/') + return 63; + + return 0xFF; +} + +static void osc_selection(VTermState *state, VTermStringFragment frag) +{ + if(frag.initial) { + state->tmp.selection.mask = 0; + state->tmp.selection.state = SELECTION_INITIAL; + } + + while(!state->tmp.selection.state && frag.len) { + /* Parse selection parameter */ + switch(frag.str[0]) { + case 'c': + state->tmp.selection.mask |= VTERM_SELECTION_CLIPBOARD; + break; + case 'p': + state->tmp.selection.mask |= VTERM_SELECTION_PRIMARY; + break; + case 'q': + state->tmp.selection.mask |= VTERM_SELECTION_SECONDARY; + break; + case 's': + state->tmp.selection.mask |= VTERM_SELECTION_SELECT; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + state->tmp.selection.mask |= (VTERM_SELECTION_CUT0 << (frag.str[0] - '0')); + break; + + case ';': + state->tmp.selection.state = SELECTION_SELECTED; + if(!state->tmp.selection.mask) + state->tmp.selection.mask = VTERM_SELECTION_SELECT|VTERM_SELECTION_CUT0; + break; + } + + frag.str++; + frag.len--; + } + + if(!frag.len) + return; + + if(state->tmp.selection.state == SELECTION_SELECTED) { + if(frag.str[0] == '?') { + state->tmp.selection.state = SELECTION_QUERY; + } + else { + state->tmp.selection.state = SELECTION_SET_INITIAL; + state->tmp.selection.recvpartial = 0; + } + } + + if(state->tmp.selection.state == SELECTION_QUERY) { + if(state->selection.callbacks->query) + (*state->selection.callbacks->query)(state->tmp.selection.mask, state->selection.user); + return; + } + + if(state->selection.callbacks->set) { + size_t bufcur = 0; + char *buffer = state->selection.buffer; + + uint32_t x = 0; /* Current decoding value */ + int n = 0; /* Number of sextets consumed */ + + if(state->tmp.selection.recvpartial) { + n = state->tmp.selection.recvpartial >> 24; + x = state->tmp.selection.recvpartial & 0x03FFFF; /* could be up to 18 bits of state in here */ + + state->tmp.selection.recvpartial = 0; + } + + while((state->selection.buflen - bufcur) >= 3 && frag.len) { + if(frag.str[0] == '=') { + if(n == 2) { + buffer[0] = (x >> 4) & 0xFF; + buffer += 1, bufcur += 1; + } + if(n == 3) { + buffer[0] = (x >> 10) & 0xFF; + buffer[1] = (x >> 2) & 0xFF; + buffer += 2, bufcur += 2; + } + + while(frag.len && frag.str[0] == '=') + frag.str++, frag.len--; + + n = 0; + } + else { + uint8_t b = unbase64one(frag.str[0]); + if(b == 0xFF) { + DEBUG_LOG("base64decode bad input %02X\n", (uint8_t)frag.str[0]); + } + else { + x = (x << 6) | b; + n++; + } + frag.str++, frag.len--; + + if(n == 4) { + buffer[0] = (x >> 16) & 0xFF; + buffer[1] = (x >> 8) & 0xFF; + buffer[2] = (x >> 0) & 0xFF; + + buffer += 3, bufcur += 3; + x = 0; + n = 0; + } + } + + if(!frag.len || (state->selection.buflen - bufcur) < 3) { + if(bufcur) { + (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){ + .str = state->selection.buffer, + .len = bufcur, + .initial = state->tmp.selection.state == SELECTION_SET_INITIAL, + .final = frag.final, + }, state->selection.user); + state->tmp.selection.state = SELECTION_SET; + } + + buffer = state->selection.buffer; + bufcur = 0; + } + } + + if(n) + state->tmp.selection.recvpartial = (n << 24) | x; + } +} + +static int on_osc(int command, VTermStringFragment frag, void *user) +{ + VTermState *state = user; + + switch(command) { + case 0: + settermprop_string(state, VTERM_PROP_ICONNAME, frag); + settermprop_string(state, VTERM_PROP_TITLE, frag); + return 1; + + case 1: + settermprop_string(state, VTERM_PROP_ICONNAME, frag); + return 1; + + case 2: + settermprop_string(state, VTERM_PROP_TITLE, frag); + return 1; + + case 52: + if(state->selection.callbacks) + osc_selection(state, frag); + + return 1; + + default: + if(state->fallbacks && state->fallbacks->osc) + if((*state->fallbacks->osc)(command, frag, state->fbdata)) + return 1; + } + + return 0; +} + +static void request_status_string(VTermState *state, VTermStringFragment frag) +{ + VTerm *vt = state->vt; + + char *tmp = state->tmp.decrqss; + + if(frag.initial) + tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0; + + int i = 0; + while(i < sizeof(state->tmp.decrqss)-1 && tmp[i]) + i++; + while(i < sizeof(state->tmp.decrqss)-1 && frag.len--) + tmp[i++] = (frag.str++)[0]; + tmp[i] = 0; + + if(!frag.final) + return; + + switch(tmp[0] | tmp[1]<<8 | tmp[2]<<16) { + case 'm': { + // Query SGR + long args[20]; + int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0])); + size_t cur = 0; + + cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + vt->mode.ctrl8bit ? "\x90" "1$r" : ESC_S "P" "1$r"); // DCS 1$r ... + if(cur >= vt->tmpbuffer_len) + return; + + for(int argi = 0; argi < argc; argi++) { + cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + argi == argc - 1 ? "%ld" : + CSI_ARG_HAS_MORE(args[argi]) ? "%ld:" : + "%ld;", + CSI_ARG(args[argi])); + if(cur >= vt->tmpbuffer_len) + return; + } + + cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + vt->mode.ctrl8bit ? "m" "\x9C" : "m" ESC_S "\\"); // ... m ST + if(cur >= vt->tmpbuffer_len) + return; + + vterm_push_output_bytes(vt, vt->tmpbuffer, cur); + return; + } + + case 'r': + // Query DECSTBM + vterm_push_output_sprintf_str(vt, C1_DCS, true, + "1$r%d;%dr", state->scrollregion_top+1, SCROLLREGION_BOTTOM(state)); + return; + + case 's': + // Query DECSLRM + vterm_push_output_sprintf_str(vt, C1_DCS, true, + "1$r%d;%ds", SCROLLREGION_LEFT(state)+1, SCROLLREGION_RIGHT(state)); + return; + + case ' '|('q'<<8): { + // Query DECSCUSR + int reply = 2; + switch(state->mode.cursor_shape) { + case VTERM_PROP_CURSORSHAPE_BLOCK: reply = 2; break; + case VTERM_PROP_CURSORSHAPE_UNDERLINE: reply = 4; break; + case VTERM_PROP_CURSORSHAPE_BAR_LEFT: reply = 6; break; + } + if(state->mode.cursor_blink) + reply--; + vterm_push_output_sprintf_str(vt, C1_DCS, true, + "1$r%d q", reply); + return; + } + + case '\"'|('q'<<8): + // Query DECSCA + vterm_push_output_sprintf_str(vt, C1_DCS, true, + "1$r%d\"q", state->protected_cell ? 1 : 2); + return; + } + + vterm_push_output_sprintf_str(state->vt, C1_DCS, true, "0$r"); +} + +static int on_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user) +{ + VTermState *state = user; + + if(commandlen == 2 && strneq(command, "$q", 2)) { + request_status_string(state, frag); + return 1; + } + else if(state->fallbacks && state->fallbacks->dcs) + if((*state->fallbacks->dcs)(command, commandlen, frag, state->fbdata)) + return 1; + + DEBUG_LOG("libvterm: Unhandled DCS %.*s\n", (int)commandlen, command); + return 0; +} + +static int on_apc(VTermStringFragment frag, void *user) +{ + VTermState *state = user; + + if(state->fallbacks && state->fallbacks->apc) + if((*state->fallbacks->apc)(frag, state->fbdata)) + return 1; + + /* No DEBUG_LOG because all APCs are unhandled */ + return 0; +} + +static int on_pm(VTermStringFragment frag, void *user) +{ + VTermState *state = user; + + if(state->fallbacks && state->fallbacks->pm) + if((*state->fallbacks->pm)(frag, state->fbdata)) + return 1; + + /* No DEBUG_LOG because all PMs are unhandled */ + return 0; +} + +static int on_sos(VTermStringFragment frag, void *user) +{ + VTermState *state = user; + + if(state->fallbacks && state->fallbacks->sos) + if((*state->fallbacks->sos)(frag, state->fbdata)) + return 1; + + /* No DEBUG_LOG because all SOSs are unhandled */ + return 0; +} + +static int on_resize(int rows, int cols, void *user) +{ + VTermState *state = user; + VTermPos oldpos = state->pos; + + if(cols != state->cols) { + unsigned char *newtabstops = vterm_allocator_malloc(state->vt, (cols + 7) / 8); + + /* TODO: This can all be done much more efficiently bytewise */ + int col; + for(col = 0; col < state->cols && col < cols; col++) { + unsigned char mask = 1 << (col & 7); + if(state->tabstops[col >> 3] & mask) + newtabstops[col >> 3] |= mask; + else + newtabstops[col >> 3] &= ~mask; + } + + for( ; col < cols; col++) { + unsigned char mask = 1 << (col & 7); + if(col % 8 == 0) + newtabstops[col >> 3] |= mask; + else + newtabstops[col >> 3] &= ~mask; + } + + vterm_allocator_free(state->vt, state->tabstops); + state->tabstops = newtabstops; + } + + state->rows = rows; + state->cols = cols; + + if(state->scrollregion_bottom > -1) + UBOUND(state->scrollregion_bottom, state->rows); + if(state->scrollregion_right > -1) + UBOUND(state->scrollregion_right, state->cols); + + VTermStateFields fields = { + .pos = state->pos, + .lineinfos = { [0] = state->lineinfos[0], [1] = state->lineinfos[1] }, + }; + + if(state->callbacks && state->callbacks->resize) { + (*state->callbacks->resize)(rows, cols, &fields, state->cbdata); + state->pos = fields.pos; + + state->lineinfos[0] = fields.lineinfos[0]; + state->lineinfos[1] = fields.lineinfos[1]; + } + else { + if(rows != state->rows) { + for(int bufidx = BUFIDX_PRIMARY; bufidx <= BUFIDX_ALTSCREEN; bufidx++) { + VTermLineInfo *oldlineinfo = state->lineinfos[bufidx]; + if(!oldlineinfo) + continue; + + VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo)); + + int row; + for(row = 0; row < state->rows && row < rows; row++) { + newlineinfo[row] = oldlineinfo[row]; + } + + for( ; row < rows; row++) { + newlineinfo[row] = (VTermLineInfo){ + .doublewidth = 0, + }; + } + + vterm_allocator_free(state->vt, state->lineinfos[bufidx]); + state->lineinfos[bufidx] = newlineinfo; + } + } + } + + state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY]; + + if(state->at_phantom && state->pos.col < cols-1) { + state->at_phantom = 0; + state->pos.col++; + } + + if(state->pos.row < 0) + state->pos.row = 0; + if(state->pos.row >= rows) + state->pos.row = rows - 1; + if(state->pos.col < 0) + state->pos.col = 0; + if(state->pos.col >= cols) + state->pos.col = cols - 1; + + updatecursor(state, &oldpos, 1); + + return 1; +} + +static const VTermParserCallbacks parser_callbacks = { + .text = on_text, + .control = on_control, + .escape = on_escape, + .csi = on_csi, + .osc = on_osc, + .dcs = on_dcs, + .apc = on_apc, + .pm = on_pm, + .sos = on_sos, + .resize = on_resize, +}; + +VTermState *vterm_obtain_state(VTerm *vt) +{ + if(vt->state) + return vt->state; + + VTermState *state = vterm_state_new(vt); + vt->state = state; + + vterm_parser_set_callbacks(vt, &parser_callbacks, state); + + return state; +} + +void vterm_state_reset(VTermState *state, int hard) +{ + state->scrollregion_top = 0; + state->scrollregion_bottom = -1; + state->scrollregion_left = 0; + state->scrollregion_right = -1; + + state->mode.keypad = 0; + state->mode.cursor = 0; + state->mode.autowrap = 1; + state->mode.insert = 0; + state->mode.newline = 0; + state->mode.alt_screen = 0; + state->mode.origin = 0; + state->mode.leftrightmargin = 0; + state->mode.bracketpaste = 0; + state->mode.report_focus = 0; + + state->mouse_flags = 0; + + state->vt->mode.ctrl8bit = 0; + + for(int col = 0; col < state->cols; col++) + if(col % 8 == 0) + set_col_tabstop(state, col); + else + clear_col_tabstop(state, col); + + for(int row = 0; row < state->rows; row++) + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + + if(state->callbacks && state->callbacks->initpen) + (*state->callbacks->initpen)(state->cbdata); + + vterm_state_resetpen(state); + + VTermEncoding *default_enc = state->vt->mode.utf8 ? + vterm_lookup_encoding(ENC_UTF8, 'u') : + vterm_lookup_encoding(ENC_SINGLE_94, 'B'); + + for(int i = 0; i < 4; i++) { + state->encoding[i].enc = default_enc; + if(default_enc->init) + (*default_enc->init)(default_enc, state->encoding[i].data); + } + + state->gl_set = 0; + state->gr_set = 1; + state->gsingle_set = 0; + + state->protected_cell = 0; + + // Initialise the props + settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, 1); + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); + + if(hard) { + state->pos.row = 0; + state->pos.col = 0; + state->at_phantom = 0; + + VTermRect rect = { 0, state->rows, 0, state->cols }; + erase(state, rect, 0); + } +} + +void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos) +{ + *cursorpos = state->pos; +} + +void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user) +{ + if(callbacks) { + state->callbacks = callbacks; + state->cbdata = user; + + if(state->callbacks && state->callbacks->initpen) + (*state->callbacks->initpen)(state->cbdata); + } + else { + state->callbacks = NULL; + state->cbdata = NULL; + } +} + +void *vterm_state_get_cbdata(VTermState *state) +{ + return state->cbdata; +} + +void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *fallbacks, void *user) +{ + if(fallbacks) { + state->fallbacks = fallbacks; + state->fbdata = user; + } + else { + state->fallbacks = NULL; + state->fbdata = NULL; + } +} + +void *vterm_state_get_unrecognised_fbdata(VTermState *state) +{ + return state->fbdata; +} + +int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val) +{ + /* Only store the new value of the property if usercode said it was happy. + * This is especially important for altscreen switching */ + if(state->callbacks && state->callbacks->settermprop) + if(!(*state->callbacks->settermprop)(prop, val, state->cbdata)) + return 0; + + switch(prop) { + case VTERM_PROP_TITLE: + case VTERM_PROP_ICONNAME: + // we don't store these, just transparently pass through + return 1; + case VTERM_PROP_CURSORVISIBLE: + state->mode.cursor_visible = val->boolean; + return 1; + case VTERM_PROP_CURSORBLINK: + state->mode.cursor_blink = val->boolean; + return 1; + case VTERM_PROP_CURSORSHAPE: + state->mode.cursor_shape = val->number; + return 1; + case VTERM_PROP_REVERSE: + state->mode.screen = val->boolean; + return 1; + case VTERM_PROP_ALTSCREEN: + state->mode.alt_screen = val->boolean; + state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY]; + if(state->mode.alt_screen) { + VTermRect rect = { + .start_row = 0, + .start_col = 0, + .end_row = state->rows, + .end_col = state->cols, + }; + erase(state, rect, 0); + } + return 1; + case VTERM_PROP_MOUSE: + state->mouse_flags = 0; + if(val->number) + state->mouse_flags |= MOUSE_WANT_CLICK; + if(val->number == VTERM_PROP_MOUSE_DRAG) + state->mouse_flags |= MOUSE_WANT_DRAG; + if(val->number == VTERM_PROP_MOUSE_MOVE) + state->mouse_flags |= MOUSE_WANT_MOVE; + return 1; + + case VTERM_N_PROPS: + return 0; + } + + return 0; +} + +void vterm_state_focus_in(VTermState *state) +{ + if(state->mode.report_focus) + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "I"); +} + +void vterm_state_focus_out(VTermState *state) +{ + if(state->mode.report_focus) + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "O"); +} + +const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row) +{ + return state->lineinfo + row; +} + +void vterm_state_set_selection_callbacks(VTermState *state, const VTermSelectionCallbacks *callbacks, void *user, + char *buffer, size_t buflen) +{ + if(buflen && !buffer) + buffer = vterm_allocator_malloc(state->vt, buflen); + + state->selection.callbacks = callbacks; + state->selection.user = user; + state->selection.buffer = buffer; + state->selection.buflen = buflen; +} + +void vterm_state_send_selection(VTermState *state, VTermSelectionMask mask, VTermStringFragment frag) +{ + VTerm *vt = state->vt; + + if(frag.initial) { + /* TODO: support sending more than one mask bit */ + const static char selection_chars[] = "cpqs"; + int idx; + for(idx = 0; idx < 4; idx++) + if(mask & (1 << idx)) + break; + + vterm_push_output_sprintf_str(vt, C1_OSC, false, "52;%c;", selection_chars[idx]); + + state->tmp.selection.sendpartial = 0; + } + + if(frag.len) { + size_t bufcur = 0; + char *buffer = state->selection.buffer; + + uint32_t x = 0; + int n = 0; + + if(state->tmp.selection.sendpartial) { + n = state->tmp.selection.sendpartial >> 24; + x = state->tmp.selection.sendpartial & 0xFFFFFF; + + state->tmp.selection.sendpartial = 0; + } + + while((state->selection.buflen - bufcur) >= 4 && frag.len) { + x = (x << 8) | frag.str[0]; + n++; + frag.str++, frag.len--; + + if(n == 3) { + buffer[0] = base64_one((x >> 18) & 0x3F); + buffer[1] = base64_one((x >> 12) & 0x3F); + buffer[2] = base64_one((x >> 6) & 0x3F); + buffer[3] = base64_one((x >> 0) & 0x3F); + + buffer += 4, bufcur += 4; + x = 0; + n = 0; + } + + if(!frag.len || (state->selection.buflen - bufcur) < 4) { + if(bufcur) + vterm_push_output_bytes(vt, state->selection.buffer, bufcur); + + buffer = state->selection.buffer; + bufcur = 0; + } + } + + if(n) + state->tmp.selection.sendpartial = (n << 24) | x; + } + + if(frag.final) { + if(state->tmp.selection.sendpartial) { + int n = state->tmp.selection.sendpartial >> 24; + uint32_t x = state->tmp.selection.sendpartial & 0xFFFFFF; + char *buffer = state->selection.buffer; + + /* n is either 1 or 2 now */ + x <<= (n == 1) ? 16 : 8; + + buffer[0] = base64_one((x >> 18) & 0x3F); + buffer[1] = base64_one((x >> 12) & 0x3F); + buffer[2] = (n == 1) ? '=' : base64_one((x >> 6) & 0x3F); + buffer[3] = '='; + + vterm_push_output_sprintf_str(vt, 0, true, "%.*s", 4, buffer); + } + else + vterm_push_output_sprintf_str(vt, 0, true, ""); + } +} diff --git a/src/libs/3rdparty/libvterm/src/unicode.c b/src/libs/3rdparty/libvterm/src/unicode.c new file mode 100644 index 00000000000..269244ff6b9 --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/unicode.c @@ -0,0 +1,313 @@ +#include "vterm_internal.h" + +// ### The following from http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c +// With modifications: +// made functions static +// moved 'combining' table to file scope, so other functions can see it +// ################################################################### + +/* + * This is an implementation of wcwidth() and wcswidth() (defined in + * IEEE Std 1002.1-2001) for Unicode. + * + * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html + * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html + * + * In fixed-width output devices, Latin characters all occupy a single + * "cell" position of equal width, whereas ideographic CJK characters + * occupy two such cells. Interoperability between terminal-line + * applications and (teletype-style) character terminals using the + * UTF-8 encoding requires agreement on which character should advance + * the cursor by how many cell positions. No established formal + * standards exist at present on which Unicode character shall occupy + * how many cell positions on character terminals. These routines are + * a first attempt of defining such behavior based on simple rules + * applied to data provided by the Unicode Consortium. + * + * For some graphical characters, the Unicode standard explicitly + * defines a character-cell width via the definition of the East Asian + * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes. + * In all these cases, there is no ambiguity about which width a + * terminal shall use. For characters in the East Asian Ambiguous (A) + * class, the width choice depends purely on a preference of backward + * compatibility with either historic CJK or Western practice. + * Choosing single-width for these characters is easy to justify as + * the appropriate long-term solution, as the CJK practice of + * displaying these characters as double-width comes from historic + * implementation simplicity (8-bit encoded characters were displayed + * single-width and 16-bit ones double-width, even for Greek, + * Cyrillic, etc.) and not any typographic considerations. + * + * Much less clear is the choice of width for the Not East Asian + * (Neutral) class. Existing practice does not dictate a width for any + * of these characters. It would nevertheless make sense + * typographically to allocate two character cells to characters such + * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be + * represented adequately with a single-width glyph. The following + * routines at present merely assign a single-cell width to all + * neutral characters, in the interest of simplicity. This is not + * entirely satisfactory and should be reconsidered before + * establishing a formal standard in this area. At the moment, the + * decision which Not East Asian (Neutral) characters should be + * represented by double-width glyphs cannot yet be answered by + * applying a simple rule from the Unicode database content. Setting + * up a proper standard for the behavior of UTF-8 character terminals + * will require a careful analysis not only of each Unicode character, + * but also of each presentation form, something the author of these + * routines has avoided to do so far. + * + * http://www.unicode.org/unicode/reports/tr11/ + * + * Markus Kuhn -- 2007-05-26 (Unicode 5.0) + * + * Permission to use, copy, modify, and distribute this software + * for any purpose and without fee is hereby granted. The author + * disclaims all warranties with regard to this software. + * + * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c + */ + +struct interval { + int first; + int last; +}; + +/* sorted list of non-overlapping intervals of non-spacing characters */ +/* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ +static const struct interval combining[] = { + { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, + { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, + { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, + { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, + { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, + { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, + { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, + { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, + { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, + { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, + { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, + { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, + { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, + { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, + { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, + { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, + { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, + { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, + { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, + { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, + { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, + { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, + { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, + { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, + { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, + { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, + { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, + { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, + { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, + { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, + { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, + { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, + { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, + { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, + { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, + { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, + { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, + { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, + { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, + { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F }, + { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, + { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, + { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, + { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F }, + { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 }, + { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, + { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, + { 0xE0100, 0xE01EF } +}; + + +/* auxiliary function for binary search in interval table */ +static int bisearch(uint32_t ucs, const struct interval *table, int max) { + int min = 0; + int mid; + + if (ucs < table[0].first || ucs > table[max].last) + return 0; + while (max >= min) { + mid = (min + max) / 2; + if (ucs > table[mid].last) + min = mid + 1; + else if (ucs < table[mid].first) + max = mid - 1; + else + return 1; + } + + return 0; +} + + +/* The following two functions define the column width of an ISO 10646 + * character as follows: + * + * - The null character (U+0000) has a column width of 0. + * + * - Other C0/C1 control characters and DEL will lead to a return + * value of -1. + * + * - Non-spacing and enclosing combining characters (general + * category code Mn or Me in the Unicode database) have a + * column width of 0. + * + * - SOFT HYPHEN (U+00AD) has a column width of 1. + * + * - Other format characters (general category code Cf in the Unicode + * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. + * + * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) + * have a column width of 0. + * + * - Spacing characters in the East Asian Wide (W) or East Asian + * Full-width (F) category as defined in Unicode Technical + * Report #11 have a column width of 2. + * + * - All remaining characters (including all printable + * ISO 8859-1 and WGL4 characters, Unicode control characters, + * etc.) have a column width of 1. + * + * This implementation assumes that uint32_t characters are encoded + * in ISO 10646. + */ + + +static int mk_wcwidth(uint32_t ucs) +{ + /* test for 8-bit control characters */ + if (ucs == 0) + return 0; + if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) + return -1; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, combining, + sizeof(combining) / sizeof(struct interval) - 1)) + return 0; + + /* if we arrive here, ucs is not a combining or C0/C1 control character */ + + return 1 + + (ucs >= 0x1100 && + (ucs <= 0x115f || /* Hangul Jamo init. consonants */ + ucs == 0x2329 || ucs == 0x232a || + (ucs >= 0x2e80 && ucs <= 0xa4cf && + ucs != 0x303f) || /* CJK ... Yi */ + (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ + (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ + (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */ + (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ + (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ + (ucs >= 0xffe0 && ucs <= 0xffe6) || + (ucs >= 0x20000 && ucs <= 0x2fffd) || + (ucs >= 0x30000 && ucs <= 0x3fffd))); +} + + +#ifdef USE_MK_WCWIDTH_CJK + +/* + * The following functions are the same as mk_wcwidth() and + * mk_wcswidth(), except that spacing characters in the East Asian + * Ambiguous (A) category as defined in Unicode Technical Report #11 + * have a column width of 2. This variant might be useful for users of + * CJK legacy encodings who want to migrate to UCS without changing + * the traditional terminal character-width behaviour. It is not + * otherwise recommended for general use. + */ +static int mk_wcwidth_cjk(uint32_t ucs) +{ + /* sorted list of non-overlapping intervals of East Asian Ambiguous + * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */ + static const struct interval ambiguous[] = { + { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 }, + { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 }, + { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 }, + { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 }, + { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED }, + { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA }, + { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 }, + { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B }, + { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 }, + { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 }, + { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 }, + { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE }, + { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 }, + { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA }, + { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 }, + { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB }, + { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB }, + { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 }, + { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 }, + { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 }, + { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 }, + { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 }, + { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 }, + { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 }, + { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC }, + { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 }, + { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 }, + { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 }, + { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 }, + { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 }, + { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 }, + { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B }, + { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 }, + { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 }, + { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E }, + { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 }, + { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 }, + { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F }, + { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 }, + { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF }, + { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B }, + { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 }, + { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 }, + { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 }, + { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 }, + { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 }, + { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 }, + { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 }, + { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 }, + { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F }, + { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF }, + { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD } + }; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, ambiguous, + sizeof(ambiguous) / sizeof(struct interval) - 1)) + return 2; + + return mk_wcwidth(ucs); +} + +#endif + +// ################################ +// ### The rest added by Paul Evans + +static const struct interval fullwidth[] = { +#include "fullwidth.inc" +}; + +INTERNAL int vterm_unicode_width(uint32_t codepoint) +{ + if(bisearch(codepoint, fullwidth, sizeof(fullwidth) / sizeof(fullwidth[0]) - 1)) + return 2; + + return mk_wcwidth(codepoint); +} + +INTERNAL int vterm_unicode_is_combining(uint32_t codepoint) +{ + return bisearch(codepoint, combining, sizeof(combining) / sizeof(struct interval) - 1); +} diff --git a/src/libs/3rdparty/libvterm/src/utf8.h b/src/libs/3rdparty/libvterm/src/utf8.h new file mode 100644 index 00000000000..9a336d357ff --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/utf8.h @@ -0,0 +1,39 @@ +/* The following functions copied and adapted from libtermkey + * + * http://www.leonerd.org.uk/code/libtermkey/ + */ +static inline unsigned int utf8_seqlen(long codepoint) +{ + if(codepoint < 0x0000080) return 1; + if(codepoint < 0x0000800) return 2; + if(codepoint < 0x0010000) return 3; + if(codepoint < 0x0200000) return 4; + if(codepoint < 0x4000000) return 5; + return 6; +} + +/* Does NOT NUL-terminate the buffer */ +static int fill_utf8(long codepoint, char *str) +{ + int nbytes = utf8_seqlen(codepoint); + + // This is easier done backwards + int b = nbytes; + while(b > 1) { + b--; + str[b] = 0x80 | (codepoint & 0x3f); + codepoint >>= 6; + } + + switch(nbytes) { + case 1: str[0] = (codepoint & 0x7f); break; + case 2: str[0] = 0xc0 | (codepoint & 0x1f); break; + case 3: str[0] = 0xe0 | (codepoint & 0x0f); break; + case 4: str[0] = 0xf0 | (codepoint & 0x07); break; + case 5: str[0] = 0xf8 | (codepoint & 0x03); break; + case 6: str[0] = 0xfc | (codepoint & 0x01); break; + } + + return nbytes; +} +/* end copy */ diff --git a/src/libs/3rdparty/libvterm/src/vterm.c b/src/libs/3rdparty/libvterm/src/vterm.c new file mode 100644 index 00000000000..0997887f5fb --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/vterm.c @@ -0,0 +1,429 @@ +#include "vterm_internal.h" + +#include +#include +#include +#include + +/***************** + * API functions * + *****************/ + +static void *default_malloc(size_t size, void *allocdata) +{ + void *ptr = malloc(size); + if(ptr) + memset(ptr, 0, size); + return ptr; +} + +static void default_free(void *ptr, void *allocdata) +{ + free(ptr); +} + +static VTermAllocatorFunctions default_allocator = { + .malloc = &default_malloc, + .free = &default_free, +}; + +VTerm *vterm_new(int rows, int cols) +{ + return vterm_build(&(const struct VTermBuilder){ + .rows = rows, + .cols = cols, + }); +} + +VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata) +{ + return vterm_build(&(const struct VTermBuilder){ + .rows = rows, + .cols = cols, + .allocator = funcs, + .allocdata = allocdata, + }); +} + +/* A handy macro for defaulting values out of builder fields */ +#define DEFAULT(v, def) ((v) ? (v) : (def)) + +VTerm *vterm_build(const struct VTermBuilder *builder) +{ + const VTermAllocatorFunctions *allocator = DEFAULT(builder->allocator, &default_allocator); + + /* Need to bootstrap using the allocator function directly */ + VTerm *vt = (*allocator->malloc)(sizeof(VTerm), builder->allocdata); + + vt->allocator = allocator; + vt->allocdata = builder->allocdata; + + vt->rows = builder->rows; + vt->cols = builder->cols; + + vt->parser.state = NORMAL; + + vt->parser.callbacks = NULL; + vt->parser.cbdata = NULL; + + vt->parser.emit_nul = false; + + vt->outfunc = NULL; + vt->outdata = NULL; + + vt->outbuffer_len = DEFAULT(builder->outbuffer_len, 4096); + vt->outbuffer_cur = 0; + vt->outbuffer = vterm_allocator_malloc(vt, vt->outbuffer_len); + + vt->tmpbuffer_len = DEFAULT(builder->tmpbuffer_len, 4096); + vt->tmpbuffer = vterm_allocator_malloc(vt, vt->tmpbuffer_len); + + return vt; +} + +void vterm_free(VTerm *vt) +{ + if(vt->screen) + vterm_screen_free(vt->screen); + + if(vt->state) + vterm_state_free(vt->state); + + vterm_allocator_free(vt, vt->outbuffer); + vterm_allocator_free(vt, vt->tmpbuffer); + + vterm_allocator_free(vt, vt); +} + +INTERNAL void *vterm_allocator_malloc(VTerm *vt, size_t size) +{ + return (*vt->allocator->malloc)(size, vt->allocdata); +} + +INTERNAL void vterm_allocator_free(VTerm *vt, void *ptr) +{ + (*vt->allocator->free)(ptr, vt->allocdata); +} + +void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp) +{ + if(rowsp) + *rowsp = vt->rows; + if(colsp) + *colsp = vt->cols; +} + +void vterm_set_size(VTerm *vt, int rows, int cols) +{ + if(rows < 1 || cols < 1) + return; + + vt->rows = rows; + vt->cols = cols; + + if(vt->parser.callbacks && vt->parser.callbacks->resize) + (*vt->parser.callbacks->resize)(rows, cols, vt->parser.cbdata); +} + +int vterm_get_utf8(const VTerm *vt) +{ + return vt->mode.utf8; +} + +void vterm_set_utf8(VTerm *vt, int is_utf8) +{ + vt->mode.utf8 = is_utf8; +} + +void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user) +{ + vt->outfunc = func; + vt->outdata = user; +} + +INTERNAL void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len) +{ + if(vt->outfunc) { + (vt->outfunc)(bytes, len, vt->outdata); + return; + } + + if(len > vt->outbuffer_len - vt->outbuffer_cur) + return; + + memcpy(vt->outbuffer + vt->outbuffer_cur, bytes, len); + vt->outbuffer_cur += len; +} + +INTERNAL void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args) +{ + size_t len = vsnprintf(vt->tmpbuffer, vt->tmpbuffer_len, + format, args); + + vterm_push_output_bytes(vt, vt->tmpbuffer, len); +} + +INTERNAL void vterm_push_output_sprintf(VTerm *vt, const char *format, ...) +{ + va_list args; + va_start(args, format); + vterm_push_output_vsprintf(vt, format, args); + va_end(args); +} + +INTERNAL void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...) +{ + size_t cur; + + if(ctrl >= 0x80 && !vt->mode.ctrl8bit) + cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, + ESC_S "%c", ctrl - 0x40); + else + cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, + "%c", ctrl); + + if(cur >= vt->tmpbuffer_len) + return; + + va_list args; + va_start(args, fmt); + cur += vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + fmt, args); + va_end(args); + + if(cur >= vt->tmpbuffer_len) + return; + + vterm_push_output_bytes(vt, vt->tmpbuffer, cur); +} + +INTERNAL void vterm_push_output_sprintf_str(VTerm *vt, unsigned char ctrl, bool term, const char *fmt, ...) +{ + size_t cur = 0; + + if(ctrl) { + if(ctrl >= 0x80 && !vt->mode.ctrl8bit) + cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, + ESC_S "%c", ctrl - 0x40); + else + cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, + "%c", ctrl); + + if(cur >= vt->tmpbuffer_len) + return; + } + + va_list args; + va_start(args, fmt); + cur += vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + fmt, args); + va_end(args); + + if(cur >= vt->tmpbuffer_len) + return; + + if(term) { + cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + vt->mode.ctrl8bit ? "\x9C" : ESC_S "\\"); // ST + + if(cur >= vt->tmpbuffer_len) + return; + } + + vterm_push_output_bytes(vt, vt->tmpbuffer, cur); +} + +size_t vterm_output_get_buffer_size(const VTerm *vt) +{ + return vt->outbuffer_len; +} + +size_t vterm_output_get_buffer_current(const VTerm *vt) +{ + return vt->outbuffer_cur; +} + +size_t vterm_output_get_buffer_remaining(const VTerm *vt) +{ + return vt->outbuffer_len - vt->outbuffer_cur; +} + +size_t vterm_output_read(VTerm *vt, char *buffer, size_t len) +{ + if(len > vt->outbuffer_cur) + len = vt->outbuffer_cur; + + memcpy(buffer, vt->outbuffer, len); + + if(len < vt->outbuffer_cur) + memmove(vt->outbuffer, vt->outbuffer + len, vt->outbuffer_cur - len); + + vt->outbuffer_cur -= len; + + return len; +} + +VTermValueType vterm_get_attr_type(VTermAttr attr) +{ + switch(attr) { + case VTERM_ATTR_BOLD: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_UNDERLINE: return VTERM_VALUETYPE_INT; + case VTERM_ATTR_ITALIC: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_BLINK: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_REVERSE: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_CONCEAL: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_STRIKE: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_FONT: return VTERM_VALUETYPE_INT; + case VTERM_ATTR_FOREGROUND: return VTERM_VALUETYPE_COLOR; + case VTERM_ATTR_BACKGROUND: return VTERM_VALUETYPE_COLOR; + case VTERM_ATTR_SMALL: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_BASELINE: return VTERM_VALUETYPE_INT; + + case VTERM_N_ATTRS: return 0; + } + return 0; /* UNREACHABLE */ +} + +VTermValueType vterm_get_prop_type(VTermProp prop) +{ + switch(prop) { + case VTERM_PROP_CURSORVISIBLE: return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_CURSORBLINK: return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_ALTSCREEN: return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_TITLE: return VTERM_VALUETYPE_STRING; + case VTERM_PROP_ICONNAME: return VTERM_VALUETYPE_STRING; + case VTERM_PROP_REVERSE: return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_CURSORSHAPE: return VTERM_VALUETYPE_INT; + case VTERM_PROP_MOUSE: return VTERM_VALUETYPE_INT; + + case VTERM_N_PROPS: return 0; + } + return 0; /* UNREACHABLE */ +} + +void vterm_scroll_rect(VTermRect rect, + int downward, + int rightward, + int (*moverect)(VTermRect src, VTermRect dest, void *user), + int (*eraserect)(VTermRect rect, int selective, void *user), + void *user) +{ + VTermRect src; + VTermRect dest; + + if(abs(downward) >= rect.end_row - rect.start_row || + abs(rightward) >= rect.end_col - rect.start_col) { + /* Scroll more than area; just erase the lot */ + (*eraserect)(rect, 0, user); + return; + } + + if(rightward >= 0) { + /* rect: [XXX................] + * src: [----------------] + * dest: [----------------] + */ + dest.start_col = rect.start_col; + dest.end_col = rect.end_col - rightward; + src.start_col = rect.start_col + rightward; + src.end_col = rect.end_col; + } + else { + /* rect: [................XXX] + * src: [----------------] + * dest: [----------------] + */ + int leftward = -rightward; + dest.start_col = rect.start_col + leftward; + dest.end_col = rect.end_col; + src.start_col = rect.start_col; + src.end_col = rect.end_col - leftward; + } + + if(downward >= 0) { + dest.start_row = rect.start_row; + dest.end_row = rect.end_row - downward; + src.start_row = rect.start_row + downward; + src.end_row = rect.end_row; + } + else { + int upward = -downward; + dest.start_row = rect.start_row + upward; + dest.end_row = rect.end_row; + src.start_row = rect.start_row; + src.end_row = rect.end_row - upward; + } + + if(moverect) + (*moverect)(dest, src, user); + + if(downward > 0) + rect.start_row = rect.end_row - downward; + else if(downward < 0) + rect.end_row = rect.start_row - downward; + + if(rightward > 0) + rect.start_col = rect.end_col - rightward; + else if(rightward < 0) + rect.end_col = rect.start_col - rightward; + + (*eraserect)(rect, 0, user); +} + +void vterm_copy_cells(VTermRect dest, + VTermRect src, + void (*copycell)(VTermPos dest, VTermPos src, void *user), + void *user) +{ + int downward = src.start_row - dest.start_row; + int rightward = src.start_col - dest.start_col; + + int init_row, test_row, init_col, test_col; + int inc_row, inc_col; + + if(downward < 0) { + init_row = dest.end_row - 1; + test_row = dest.start_row - 1; + inc_row = -1; + } + else /* downward >= 0 */ { + init_row = dest.start_row; + test_row = dest.end_row; + inc_row = +1; + } + + if(rightward < 0) { + init_col = dest.end_col - 1; + test_col = dest.start_col - 1; + inc_col = -1; + } + else /* rightward >= 0 */ { + init_col = dest.start_col; + test_col = dest.end_col; + inc_col = +1; + } + + VTermPos pos; + for(pos.row = init_row; pos.row != test_row; pos.row += inc_row) + for(pos.col = init_col; pos.col != test_col; pos.col += inc_col) { + VTermPos srcpos = { pos.row + downward, pos.col + rightward }; + (*copycell)(pos, srcpos, user); + } +} + +void vterm_check_version(int major, int minor) +{ + if(major != VTERM_VERSION_MAJOR) { + fprintf(stderr, "libvterm major version mismatch; %d (wants) != %d (library)\n", + major, VTERM_VERSION_MAJOR); + exit(1); + } + + if(minor > VTERM_VERSION_MINOR) { + fprintf(stderr, "libvterm minor version mismatch; %d (wants) > %d (library)\n", + minor, VTERM_VERSION_MINOR); + exit(1); + } + + // Happy +} diff --git a/src/libs/3rdparty/libvterm/src/vterm_internal.h b/src/libs/3rdparty/libvterm/src/vterm_internal.h new file mode 100644 index 00000000000..ad61bff8b0f --- /dev/null +++ b/src/libs/3rdparty/libvterm/src/vterm_internal.h @@ -0,0 +1,296 @@ +#ifndef __VTERM_INTERNAL_H__ +#define __VTERM_INTERNAL_H__ + +#include "vterm.h" + +#include + +#if defined(__GNUC__) +# define INTERNAL __attribute__((visibility("internal"))) +#else +# define INTERNAL +#endif + +#ifdef DEBUG +# define DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__) +#else +# define DEBUG_LOG(...) +#endif + +#define ESC_S "\x1b" + +#define INTERMED_MAX 16 + +#define CSI_ARGS_MAX 16 +#define CSI_LEADER_MAX 16 + +#define BUFIDX_PRIMARY 0 +#define BUFIDX_ALTSCREEN 1 + +typedef struct VTermEncoding VTermEncoding; + +typedef struct { + VTermEncoding *enc; + + // This size should be increased if required by other stateful encodings + char data[4*sizeof(uint32_t)]; +} VTermEncodingInstance; + +struct VTermPen +{ + VTermColor fg; + VTermColor bg; + unsigned int bold:1; + unsigned int underline:3; + unsigned int italic:1; + unsigned int blink:1; + unsigned int reverse:1; + unsigned int conceal:1; + unsigned int strike:1; + unsigned int font:4; /* To store 0-9 */ + unsigned int small:1; + unsigned int baseline:2; +}; + +struct VTermState +{ + VTerm *vt; + + const VTermStateCallbacks *callbacks; + void *cbdata; + + const VTermStateFallbacks *fallbacks; + void *fbdata; + + int rows; + int cols; + + /* Current cursor position */ + VTermPos pos; + + int at_phantom; /* True if we're on the "81st" phantom column to defer a wraparound */ + + int scrollregion_top; + int scrollregion_bottom; /* -1 means unbounded */ +#define SCROLLREGION_BOTTOM(state) ((state)->scrollregion_bottom > -1 ? (state)->scrollregion_bottom : (state)->rows) + int scrollregion_left; +#define SCROLLREGION_LEFT(state) ((state)->mode.leftrightmargin ? (state)->scrollregion_left : 0) + int scrollregion_right; /* -1 means unbounded */ +#define SCROLLREGION_RIGHT(state) ((state)->mode.leftrightmargin && (state)->scrollregion_right > -1 ? (state)->scrollregion_right : (state)->cols) + + /* Bitvector of tab stops */ + unsigned char *tabstops; + + /* Primary and Altscreen; lineinfos[1] is lazily allocated as needed */ + VTermLineInfo *lineinfos[2]; + + /* lineinfo will == lineinfos[0] or lineinfos[1], depending on altscreen */ + VTermLineInfo *lineinfo; +#define ROWWIDTH(state,row) ((state)->lineinfo[(row)].doublewidth ? ((state)->cols / 2) : (state)->cols) +#define THISROWWIDTH(state) ROWWIDTH(state, (state)->pos.row) + + /* Mouse state */ + int mouse_col, mouse_row; + int mouse_buttons; + int mouse_flags; +#define MOUSE_WANT_CLICK 0x01 +#define MOUSE_WANT_DRAG 0x02 +#define MOUSE_WANT_MOVE 0x04 + + enum { MOUSE_X10, MOUSE_UTF8, MOUSE_SGR, MOUSE_RXVT } mouse_protocol; + + /* Last glyph output, for Unicode recombining purposes */ + uint32_t *combine_chars; + size_t combine_chars_size; // Number of ELEMENTS in the above + int combine_width; // The width of the glyph above + VTermPos combine_pos; // Position before movement + + struct { + unsigned int keypad:1; + unsigned int cursor:1; + unsigned int autowrap:1; + unsigned int insert:1; + unsigned int newline:1; + unsigned int cursor_visible:1; + unsigned int cursor_blink:1; + unsigned int cursor_shape:2; + unsigned int alt_screen:1; + unsigned int origin:1; + unsigned int screen:1; + unsigned int leftrightmargin:1; + unsigned int bracketpaste:1; + unsigned int report_focus:1; + } mode; + + VTermEncodingInstance encoding[4], encoding_utf8; + int gl_set, gr_set, gsingle_set; + + struct VTermPen pen; + + VTermColor default_fg; + VTermColor default_bg; + VTermColor colors[16]; // Store the 8 ANSI and the 8 ANSI high-brights only + + int bold_is_highbright; + + unsigned int protected_cell : 1; + + /* Saved state under DEC mode 1048/1049 */ + struct { + VTermPos pos; + struct VTermPen pen; + + struct { + unsigned int cursor_visible:1; + unsigned int cursor_blink:1; + unsigned int cursor_shape:2; + } mode; + } saved; + + /* Temporary state for DECRQSS parsing */ + union { + char decrqss[4]; + struct { + uint16_t mask; + enum { + SELECTION_INITIAL, + SELECTION_SELECTED, + SELECTION_QUERY, + SELECTION_SET_INITIAL, + SELECTION_SET, + } state : 8; + uint32_t recvpartial; + uint32_t sendpartial; + } selection; + } tmp; + + struct { + const VTermSelectionCallbacks *callbacks; + void *user; + char *buffer; + size_t buflen; + } selection; +}; + +struct VTerm +{ + const VTermAllocatorFunctions *allocator; + void *allocdata; + + int rows; + int cols; + + struct { + unsigned int utf8:1; + unsigned int ctrl8bit:1; + } mode; + + struct { + enum VTermParserState { + NORMAL, + CSI_LEADER, + CSI_ARGS, + CSI_INTERMED, + DCS_COMMAND, + /* below here are the "string states" */ + OSC_COMMAND, + OSC, + DCS, + APC, + PM, + SOS, + } state; + + bool in_esc : 1; + + int intermedlen; + char intermed[INTERMED_MAX]; + + union { + struct { + int leaderlen; + char leader[CSI_LEADER_MAX]; + + int argi; + long args[CSI_ARGS_MAX]; + } csi; + struct { + int command; + } osc; + struct { + int commandlen; + char command[CSI_LEADER_MAX]; + } dcs; + } v; + + const VTermParserCallbacks *callbacks; + void *cbdata; + + bool string_initial; + + bool emit_nul; + } parser; + + /* len == malloc()ed size; cur == number of valid bytes */ + + VTermOutputCallback *outfunc; + void *outdata; + + char *outbuffer; + size_t outbuffer_len; + size_t outbuffer_cur; + + char *tmpbuffer; + size_t tmpbuffer_len; + + VTermState *state; + VTermScreen *screen; +}; + +struct VTermEncoding { + void (*init) (VTermEncoding *enc, void *data); + void (*decode)(VTermEncoding *enc, void *data, + uint32_t cp[], int *cpi, int cplen, + const char bytes[], size_t *pos, size_t len); +}; + +typedef enum { + ENC_UTF8, + ENC_SINGLE_94 +} VTermEncodingType; + +void *vterm_allocator_malloc(VTerm *vt, size_t size); +void vterm_allocator_free(VTerm *vt, void *ptr); + +void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len); +void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args); +void vterm_push_output_sprintf(VTerm *vt, const char *format, ...); +void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...); +void vterm_push_output_sprintf_str(VTerm *vt, unsigned char ctrl, bool term, const char *fmt, ...); + +void vterm_state_free(VTermState *state); + +void vterm_state_newpen(VTermState *state); +void vterm_state_resetpen(VTermState *state); +void vterm_state_setpen(VTermState *state, const long args[], int argcount); +int vterm_state_getpen(VTermState *state, long args[], int argcount); +void vterm_state_savepen(VTermState *state, int save); + +enum { + C1_SS3 = 0x8f, + C1_DCS = 0x90, + C1_CSI = 0x9b, + C1_ST = 0x9c, + C1_OSC = 0x9d, +}; + +void vterm_state_push_output_sprintf_CSI(VTermState *vts, const char *format, ...); + +void vterm_screen_free(VTermScreen *screen); + +VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation); + +int vterm_unicode_width(uint32_t codepoint); +int vterm_unicode_is_combining(uint32_t codepoint); + +#endif diff --git a/src/libs/3rdparty/libvterm/vterm.pc.in b/src/libs/3rdparty/libvterm/vterm.pc.in new file mode 100644 index 00000000000..681a270d513 --- /dev/null +++ b/src/libs/3rdparty/libvterm/vterm.pc.in @@ -0,0 +1,8 @@ +libdir=@LIBDIR@ +includedir=@INCDIR@ + +Name: vterm +Description: Abstract VT220/Xterm/ECMA-48 emulation library +Version: 0.3.1 +Libs: -L${libdir} -lvterm +Cflags: -I${includedir} diff --git a/src/libs/3rdparty/libvterm/vterm.qbs b/src/libs/3rdparty/libvterm/vterm.qbs new file mode 100644 index 00000000000..18ccb638aab --- /dev/null +++ b/src/libs/3rdparty/libvterm/vterm.qbs @@ -0,0 +1,34 @@ +Project { + QtcLibrary { + name: "vterm" + type: "staticlibrary" + + Depends { name: "cpp" } + cpp.includePaths: base.concat("include") + cpp.warningLevel: "none" + + Group { + prefix: "src/" + files: [ + "encoding.c", + "fullwidth.inc", + "keyboard.c", + "mouse.c", + "parser.c", + "pen.c", + "rect.h", + "screen.c", + "state.c", + "unicode.c", + "utf8.h", + "vterm.c", + "vterm_internal.h", + ] + } + + Export { + Depends { name: "cpp" } + cpp.includePaths: base.concat("include") + } + } +} diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/syntaxhighlighter.cpp b/src/libs/3rdparty/syntax-highlighting/src/lib/syntaxhighlighter.cpp index 41551e96da5..4754da22c61 100644 --- a/src/libs/3rdparty/syntax-highlighting/src/lib/syntaxhighlighter.cpp +++ b/src/libs/3rdparty/syntax-highlighting/src/lib/syntaxhighlighter.cpp @@ -179,7 +179,7 @@ void SyntaxHighlighter::applyFolding(int offset, int length, FoldingRegion regio { Q_UNUSED(offset); Q_UNUSED(length); - Q_D(SyntaxHighlighter); + [[maybe_unused]] Q_D(SyntaxHighlighter); if (region.type() == FoldingRegion::Begin) { d->foldingRegions.push_back(region); diff --git a/src/libs/3rdparty/winpty/.gitattributes b/src/libs/3rdparty/winpty/.gitattributes new file mode 100644 index 00000000000..36d4c60f1a7 --- /dev/null +++ b/src/libs/3rdparty/winpty/.gitattributes @@ -0,0 +1,19 @@ +* text=auto +*.bat text eol=crlf +*.c text +*.cc text +*.gyp text +*.gypi text +*.h text +*.ps1 text eol=crlf +*.rst text +*.sh text +*.txt text +.gitignore text +.gitattributes text +Makefile text +configure text + +*.sh eol=lf +configure eol=lf +VERSION.txt eol=lf diff --git a/src/libs/3rdparty/winpty/.gitignore b/src/libs/3rdparty/winpty/.gitignore new file mode 100644 index 00000000000..68c6b47fb3d --- /dev/null +++ b/src/libs/3rdparty/winpty/.gitignore @@ -0,0 +1,16 @@ +*.sln +*.suo +*.vcxproj +*.vcxproj.filters +*.pyc +winpty.sdf +winpty.opensdf +/config.mk +/build +/build-gyp +/build-libpty +/ship/packages +/ship/tmp +/src/Default +/src/Release +/src/gen diff --git a/src/libs/3rdparty/winpty/CMakeLists.txt b/src/libs/3rdparty/winpty/CMakeLists.txt new file mode 100644 index 00000000000..febd4f0ab6f --- /dev/null +++ b/src/libs/3rdparty/winpty/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(src) diff --git a/src/libs/3rdparty/winpty/LICENSE b/src/libs/3rdparty/winpty/LICENSE new file mode 100644 index 00000000000..246fbe01135 --- /dev/null +++ b/src/libs/3rdparty/winpty/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2011-2016 Ryan Prichard + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/src/libs/3rdparty/winpty/README.md b/src/libs/3rdparty/winpty/README.md new file mode 100644 index 00000000000..bc8e7d6e5f4 --- /dev/null +++ b/src/libs/3rdparty/winpty/README.md @@ -0,0 +1,151 @@ +# winpty + +[![Build Status](https://ci.appveyor.com/api/projects/status/69tb9gylsph1ee1x/branch/master?svg=true)](https://ci.appveyor.com/project/rprichard/winpty/branch/master) + +winpty is a Windows software package providing an interface similar to a Unix +pty-master for communicating with Windows console programs. The package +consists of a library (libwinpty) and a tool for Cygwin and MSYS for running +Windows console programs in a Cygwin/MSYS pty. + +The software works by starting the `winpty-agent.exe` process with a new, +hidden console window, which bridges between the console API and terminal +input/output escape codes. It polls the hidden console's screen buffer for +changes and generates a corresponding stream of output. + +The Unix adapter allows running Windows console programs (e.g. CMD, PowerShell, +IronPython, etc.) under `mintty` or Cygwin's `sshd` with +properly-functioning input (e.g. arrow and function keys) and output (e.g. line +buffering). The library could be also useful for writing a non-Cygwin SSH +server. + +## Supported Windows versions + +winpty runs on Windows XP through Windows 10, including server versions. It +can be compiled into either 32-bit or 64-bit binaries. + +## Cygwin/MSYS adapter (`winpty.exe`) + +### Prerequisites + +You need the following to build winpty: + +* A Cygwin or MSYS installation +* GNU make +* A MinGW g++ toolchain capable of compiling C++11 code to build `winpty.dll` + and `winpty-agent.exe` +* A g++ toolchain targeting Cygwin or MSYS to build `winpty.exe` + +Winpty requires two g++ toolchains as it is split into two parts. The +`winpty.dll` and `winpty-agent.exe` binaries interface with the native +Windows command prompt window so they are compiled with the native MinGW +toolchain. The `winpty.exe` binary interfaces with the MSYS/Cygwin terminal so +it is compiled with the MSYS/Cygwin toolchain. + +MinGW appears to be split into two distributions -- MinGW (creates 32-bit +binaries) and MinGW-w64 (creates both 32-bit and 64-bit binaries). Either +one is generally acceptable. + +#### Cygwin packages + +The default g++ compiler for Cygwin targets Cygwin itself, but Cygwin also +packages MinGW-w64 compilers. As of this writing, the necessary packages are: + +* Either `mingw64-i686-gcc-g++` or `mingw64-x86_64-gcc-g++`. Select the + appropriate compiler for your CPU architecture. +* `gcc-g++` +* `make` + +As of this writing (2016-01-23), only the MinGW-w64 compiler is acceptable. +The MinGW compiler (e.g. from the `mingw-gcc-g++` package) is no longer +maintained and is too buggy. + +#### MSYS packages + +For the original MSYS, use the `mingw-get` tool (MinGW Installation Manager), +and select at least these components: + +* `mingw-developer-toolkit` +* `mingw32-base` +* `mingw32-gcc-g++` +* `msys-base` +* `msys-system-builder` + +When running `./configure`, make sure that `mingw32-g++` is in your +`PATH`. It will be in the `C:\MinGW\bin` directory. + +#### MSYS2 packages + +For MSYS2, use `pacman` and install at least these packages: + +* `msys/gcc` +* `mingw32/mingw-w64-i686-gcc` or `mingw64/mingw-w64-x86_64-gcc`. Select + the appropriate compiler for your CPU architecture. +* `make` + +MSYS2 provides three start menu shortcuts for starting MSYS2: + +* MinGW-w64 Win32 Shell +* MinGW-w64 Win64 Shell +* MSYS2 Shell + +To build winpty, use the MinGW-w64 {Win32,Win64} shortcut of the architecture +matching MSYS2. These shortcuts will put the g++ compiler from the +`{mingw32,mingw64}/mingw-w64-{i686,x86_64}-gcc` packages into the `PATH`. + +Alternatively, instead of installing `mingw32/mingw-w64-i686-gcc` or +`mingw64/mingw-w64-x86_64-gcc`, install the `mingw-w64-cross-gcc` and +`mingw-w64-cross-crt-git` packages. These packages install cross-compilers +into `/opt/bin`, and then any of the three shortcuts will work. + +### Building the Unix adapter + +In the project directory, run `./configure`, then `make`, then `make install`. +By default, winpty is installed into `/usr/local`. Pass `PREFIX=` to +`make install` to override this default. + +### Using the Unix adapter + +To run a Windows console program in `mintty` or Cygwin `sshd`, prepend +`winpty` to the command-line: + + $ winpty powershell + Windows PowerShell + Copyright (C) 2009 Microsoft Corporation. All rights reserved. + + PS C:\rprichard\proj\winpty> 10 + 20 + 30 + PS C:\rprichard\proj\winpty> exit + +## Embedding winpty / MSVC compilation + +See `src/include/winpty.h` for the prototypes of functions exported by +`winpty.dll`. + +Only the `winpty.exe` binary uses Cygwin; all the other binaries work without +it and can be compiled with either MinGW or MSVC. To compile using MSVC, +download gyp and run `gyp -I configurations.gypi` in the `src` subdirectory. +This will generate a `winpty.sln` and associated project files. See the +`src/winpty.gyp` and `src/configurations.gypi` files for notes on dealing with +MSVC versions and different architectures. + +Compiling winpty with MSVC currently requires MSVC 2013 or newer. + +## Debugging winpty + +winpty comes with a tool for collecting timestamped debugging output. To use +it: + +1. Run `winpty-debugserver.exe` on the same computer as winpty. +2. Set the `WINPTY_DEBUG` environment variable to `trace` for the + `winpty.exe` process and/or the process using `libwinpty.dll`. + +winpty also recognizes a `WINPTY_SHOW_CONSOLE` environment variable. Set it +to 1 to prevent winpty from hiding the console window. + +## Copyright + +This project is distributed under the MIT license (see the `LICENSE` file in +the project root). + +By submitting a pull request for this project, you agree to license your +contribution under the MIT license to this project. diff --git a/src/libs/3rdparty/winpty/RELEASES.md b/src/libs/3rdparty/winpty/RELEASES.md new file mode 100644 index 00000000000..768cdf90e36 --- /dev/null +++ b/src/libs/3rdparty/winpty/RELEASES.md @@ -0,0 +1,280 @@ +# Next Version + +Input handling changes: + + * Improve Ctrl-C handling with programs that use unprocessed input. (e.g. + Ctrl-C now cancels input with PowerShell on Windows 10.) + [#116](https://github.com/rprichard/winpty/issues/116) + * Fix a theoretical issue with input event ordering. + [#117](https://github.com/rprichard/winpty/issues/117) + * Ctrl/Shift+{Arrow,Home,End} keys now work with IntelliJ. + [#118](https://github.com/rprichard/winpty/issues/118) + +# Version 0.4.3 (2017-05-17) + +Input handling changes: + + * winpty sets `ENHANCED_KEY` for arrow and navigation keys. This fixes an + issue with the Ruby REPL. + [#99](https://github.com/rprichard/winpty/issues/99) + * AltGr keys are handled better now. + [#109](https://github.com/rprichard/winpty/issues/109) + * In `ENABLE_VIRTUAL_TERMINAL_INPUT` mode, when typing Home/End with a + modifier (e.g. Ctrl), winpty now generates an H/F escape sequence like + `^[[1;5F` rather than a 1/4 escape like `^[[4;5~`. + [#114](https://github.com/rprichard/winpty/issues/114) + +Resizing and scraping fixes: + + * winpty now synthesizes a `WINDOW_BUFFER_SIZE_EVENT` event after resizing + the console to better propagate window size changes to console programs. + In particular, this affects WSL and Cygwin. + [#110](https://github.com/rprichard/winpty/issues/110) + * Better handling of resizing for certain full-screen programs, like + WSL less. + [#112](https://github.com/rprichard/winpty/issues/112) + * Hide the cursor if it's currently outside the console window. This change + fixes an issue with Far Manager. + [#113](https://github.com/rprichard/winpty/issues/113) + * winpty now avoids using console fonts smaller than 5px high to improve + half-vs-full-width character handling. See + https://github.com/Microsoft/vscode/issues/19665. + [b4db322010](https://github.com/rprichard/winpty/commit/b4db322010d2d897e6c496fefc4f0ecc9b84c2f3) + +Cygwin/MSYS adapter fix: + + * The way the `winpty` Cygwin/MSYS2 adapter searches for the program to + launch changed. It now resolves symlinks and searches the PATH explicitly. + [#81](https://github.com/rprichard/winpty/issues/81) + [#98](https://github.com/rprichard/winpty/issues/98) + +This release does not include binaries for the old MSYS1 project anymore. +MSYS2 will continue to be supported. See +https://github.com/rprichard/winpty/issues/97. + +# Version 0.4.2 (2017-01-18) + +This release improves WSL support (i.e. Bash-on-Windows): + + * winpty generates more correct input escape sequences for WSL programs that + enable an alternate input mode using DECCKM. This bug affected arrow keys + and Home/End in WSL programs such as `vim`, `mc`, and `less`. + [#90](https://github.com/rprichard/winpty/issues/90) + * winpty now recognizes the `COMMON_LVB_REVERSE_VIDEO` and + `COMMON_LVB_UNDERSCORE` text attributes. The Windows console uses these + attributes to implement the SGR.4(Underline) and SGR.7(Negative) modes in + its VT handling. This change affects WSL pager status bars, man pages, etc. + +The build system no longer has a "version suffix" mechanism, so passing +`VERSION_SUFFIX=` to make or `-D VERSION_SUFFIX=` to gyp now +has no effect. AFAIK, the mechanism was never used publicly. +[67a34b6c03](https://github.com/rprichard/winpty/commit/67a34b6c03557a5c2e0a2bdd502c2210921d8f3e) + +# Version 0.4.1 (2017-01-03) + +Bug fixes: + + * This version fixes a bug where the `winpty-agent.exe` process could read + past the end of a buffer. + [#94](https://github.com/rprichard/winpty/issues/94) + +# Version 0.4.0 (2016-06-28) + +The winpty library has a new API that should be easier for embedding. +[880c00c69e](https://github.com/rprichard/winpty/commit/880c00c69eeca73643ddb576f02c5badbec81f56) + +User-visible changes: + + * winpty now automatically puts the terminal into mouse mode when it detects + that the console has left QuickEdit mode. The `--mouse` option still forces + the terminal into mouse mode. In principle, an option could be added to + suppress terminal mode, but hopefully it won't be necessary. There is a + script in the `misc` subdirectory, `misc/ConinMode.ps1`, that can change + the QuickEdit mode from the command-line. + * winpty now passes keyboard escapes to `bash.exe` in the Windows Subsystem + for Linux. + [#82](https://github.com/rprichard/winpty/issues/82) + +Bug fixes: + + * By default, `winpty.dll` avoids calling `SetProcessWindowStation` within + the calling process. + [#58](https://github.com/rprichard/winpty/issues/58) + * Fixed an uninitialized memory bug that could have crashed winpty. + [#80](https://github.com/rprichard/winpty/issues/80) + * winpty now works better with very large and very small terminal windows. + It resizes the console font according to the number of columns. + [#61](https://github.com/rprichard/winpty/issues/61) + * winpty no longer uses Mark to freeze the console on Windows 10. The Mark + command could interfere with the cursor position, corrupting the data in + the screen buffer. + [#79](https://github.com/rprichard/winpty/issues/79) + +# Version 0.3.0 (2016-05-20) + +User-visible changes: + + * The UNIX adapter is renamed from `console.exe` to `winpty.exe` to be + consistent with MSYS2. The name `winpty.exe` is less likely to conflict + with another program and is easier to search for online (e.g. for someone + unfamiliar with winpty). + * The UNIX adapter now clears the `TERM` variable. + [#43](https://github.com/rprichard/winpty/issues/43) + * An escape character appearing in a console screen buffer cell is converted + to a '?'. + [#47](https://github.com/rprichard/winpty/issues/47) + +Bug fixes: + + * A major bug affecting XP users was fixed. + [#67](https://github.com/rprichard/winpty/issues/67) + * Fixed an incompatibility with ConEmu where winpty hung if ConEmu's + "Process 'start'" feature was enabled. + [#70](https://github.com/rprichard/winpty/issues/70) + * Fixed a bug where `cmd.exe` sometimes printed the message, + `Not enough storage is available to process this command.`. + [#74](https://github.com/rprichard/winpty/issues/74) + +Many changes internally: + + * The codebase is switched from C++03 to C++11 and uses exceptions internally. + No exceptions are thrown across the C APIs defined in `winpty.h`. + * This version drops support for the original MinGW compiler packaged with + Cygwin (`i686-pc-mingw32-g++`). The MinGW-w64 compiler is still supported, + as is the MinGW distributed at mingw.org. Compiling with MSVC now requires + MSVC 2013 or newer. Windows XP is still supported. + [ec3eae8df5](https://github.com/rprichard/winpty/commit/ec3eae8df5bbbb36d7628d168b0815638d122f37) + * Pipe security is improved. winpty works harder to produce unique pipe names + and includes a random component in the name. winpty secures pipes with a + DACL that prevents arbitrary users from connecting to its pipes. winpty now + passes `PIPE_REJECT_REMOTE_CLIENTS` on Vista and up, and it verifies that + the pipe client PID is correct, again on Vista and up. When connecting to a + named pipe, winpty uses the `SECURITY_IDENTIFICATION` flag to restrict + impersonation. Previous versions *should* still be secure. + * `winpty-debugserver.exe` now has an `--everyone` flag that allows capturing + debug output from other users. + * The code now compiles cleanly with MSVC's "Security Development Lifecycle" + (`/SDL`) checks enabled. + +# Version 0.2.2 (2016-02-25) + +Minor bug fixes and enhancements: + + * Fix a bug that generated spurious mouse input records when an incomplete + mouse escape sequence was seen. + * Fix a buffer overflow bug in `winpty-debugserver.exe` affecting messages of + exactly 4096 bytes. + * For MSVC builds, add a `src/configurations.gypi` file that can be included + on the gyp command-line to enable 32-bit and 64-bit builds. + * `winpty-agent --show-input` mode: Flush stdout after each line. + * Makefile builds: generate a `build/winpty.lib` import library to accompany + `build/winpty.dll`. + +# Version 0.2.1 (2015-12-19) + + * The main project source was moved into a `src` directory for better code + organization and to fix + [#51](https://github.com/rprichard/winpty/issues/51). + * winpty recognizes many more escape sequences, including: + * putty/rxvt's F1-F4 keys + [#40](https://github.com/rprichard/winpty/issues/40) + * the Linux virtual console's F1-F5 keys + * the "application numpad" keys (e.g. enabled with DECPAM) + * Fixed handling of Shift-Alt-O and Alt-[. + * Added support for mouse input. The UNIX adapter has a `--mouse` argument + that puts the terminal into mouse mode, but the agent recognizes mouse + input even without the argument. The agent recognizes double-clicks using + Windows' double-click interval setting (i.e. GetDoubleClickTime). + [#57](https://github.com/rprichard/winpty/issues/57) + +Changes to debugging interfaces: + + * The `WINPTY_DEBUG` variable is now a comma-separated list. The old + behavior (i.e. tracing) is enabled with `WINPTY_DEBUG=trace`. + * The UNIX adapter program now has a `--showkey` argument that dumps input + bytes. + * The `winpty-agent.exe` program has a `--show-input` argument that dumps + `INPUT_RECORD` records. (It omits mouse events unless `--with-mouse` is + also specified.) The agent also responds to `WINPTY_DEBUG=trace,input`, + which logs input bytes and synthesized console events, and it responds to + `WINPTY_DEBUG=trace,dump_input_map`, which dumps the internal table of + escape sequences. + +# Version 0.2.0 (2015-11-13) + +No changes to the API, but many small changes to the implementation. The big +changes include: + + * Support for 64-bit Cygwin and MSYS2 + * Support for Windows 10 + * Better Unicode support (especially East Asian languages) + +Details: + + * The `configure` script recognizes 64-bit Cygwin and MSYS2 environments and + selects the appropriate compiler. + * winpty works much better with the upgraded console in Windows 10. The + `conhost.exe` hang can still occur, but only with certain programs, and + is much less likely to occur. With the new console, use Mark instead of + SelectAll, for better performance. + [#31](https://github.com/rprichard/winpty/issues/31) + [#30](https://github.com/rprichard/winpty/issues/30) + [#53](https://github.com/rprichard/winpty/issues/53) + * The UNIX adapter now calls `setlocale(LC_ALL, "")` to set the locale. + * Improved Unicode support. When a console is started with an East Asian code + page, winpty now chooses an East Asian font rather than Consolas / Lucida + Console. Selecting the right font helps synchronize character widths + between the console and terminal. (It's not perfect, though.) + [#41](https://github.com/rprichard/winpty/issues/41) + * winpty now more-or-less works with programs that change the screen buffer + or resize the original screen buffer. If the screen buffer height changes, + winpty switches to a "direct mode", where it makes no effort to track + scrolling. In direct mode, it merely syncs snapshots of the console to the + terminal. Caveats: + * Changing the screen buffer (i.e. `SetConsoleActiveScreenBuffer`) + breaks winpty on Windows 7. This problem can eventually be mitigated, + but never completely fixed, due to Windows 7 bugginess. + * Resizing the original screen buffer can hang `conhost.exe` on Windows 10. + Enabling the legacy console is a workaround. + * If a program changes the screen buffer and then exits, relying on the OS + to restore the original screen buffer, that restoration probably will not + happen with winpty. winpty's behavior can probably be improved here. + * Improved color handling: + * DkGray-on-Black text was previously hiddenly completely. Now it is + output as DkGray, with a fallback to LtGray on terminals that don't + recognize the intense colors. + [#39](https://github.com/rprichard/winpty/issues/39). + * The console is always initialized to LtGray-on-Black, regardless of the + user setting, which matches the console color heuristic, which translates + LtGray-on-Black to "reset SGR parameters." + * Shift-Tab is recognized correctly now. + [#19](https://github.com/rprichard/winpty/issues/19) + * Add a `--version` argument to `winpty-agent.exe` and the UNIX adapter. The + argument reports the nominal version (i.e. the `VERSION.txt`) file, with a + "VERSION_SUFFIX" appended (defaulted to `-dev`), and a git commit hash, if + the `git` command successfully reports a hash during the build. The `git` + command is invoked by either `make` or `gyp`. + * The agent now combines `ReadConsoleOutputW` calls when it polls the console + buffer for changes, which may slightly reduce its CPU overhead. + [#44](https://github.com/rprichard/winpty/issues/44). + * A `gyp` file is added to help compile with MSVC. + * The code can now be compiled as C++11 code, though it isn't by default. + [bde8922e08](https://github.com/rprichard/winpty/commit/bde8922e08c3638e01ecc7b581b676c314163e3c) + * If winpty can't create a new window station, it charges ahead rather than + aborting. This situation might happen if winpty were started from an SSH + session. + * Debugging improvements: + * `WINPTYDBG` is renamed to `WINPTY_DEBUG`, and a new `WINPTY_SHOW_CONSOLE` + variable keeps the underlying console visible. + * A `winpty-debugserver.exe` program is built and shipped by default. It + collects the trace output enabled with `WINPTY_DEBUG`. + * The `Makefile` build of winpty now compiles `winpty-agent.exe` and + `winpty.dll` with -O2. + +# Version 0.1.1 (2012-07-28) + +Minor bugfix release. + +# Version 0.1 (2012-04-17) + +Initial release. diff --git a/src/libs/3rdparty/winpty/VERSION.txt b/src/libs/3rdparty/winpty/VERSION.txt new file mode 100644 index 00000000000..5d47ff8c45a --- /dev/null +++ b/src/libs/3rdparty/winpty/VERSION.txt @@ -0,0 +1 @@ +0.4.4-dev diff --git a/src/libs/3rdparty/winpty/appveyor.yml b/src/libs/3rdparty/winpty/appveyor.yml new file mode 100644 index 00000000000..a9e8726fc13 --- /dev/null +++ b/src/libs/3rdparty/winpty/appveyor.yml @@ -0,0 +1,16 @@ +image: Visual Studio 2015 + +init: + - C:\msys64\usr\bin\bash --login -c "pacman -S --needed --noconfirm --noprogressbar msys/make msys/tar msys/gcc mingw-w64-cross-toolchain" + - C:\cygwin\setup-x86 -q -P mingw64-i686-gcc-g++,mingw64-x86_64-gcc-g++,make + - C:\cygwin64\setup-x86_64 -q -P mingw64-i686-gcc-g++,mingw64-x86_64-gcc-g++,make + +build_script: + - C:\Python27-x64\python.exe ship\ship.py --kind msys2 --arch x64 --syspath C:\msys64 + - C:\Python27-x64\python.exe ship\ship.py --kind cygwin --arch ia32 --syspath C:\cygwin + - C:\Python27-x64\python.exe ship\ship.py --kind cygwin --arch x64 --syspath C:\cygwin64 + - C:\Python27-x64\python.exe ship\make_msvc_package.py + +artifacts: + - path: ship\packages\*.tar.gz + - path: ship\packages\*.zip diff --git a/src/libs/3rdparty/winpty/configure b/src/libs/3rdparty/winpty/configure new file mode 100644 index 00000000000..6d37d65b091 --- /dev/null +++ b/src/libs/3rdparty/winpty/configure @@ -0,0 +1,167 @@ +#!/bin/bash +# +# Copyright (c) 2011-2015 Ryan Prichard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +# +# findTool(desc, commandList) +# +# Searches commandLine for the first command in the PATH and returns it. +# Prints an error and aborts the script if no match is found. +# +FINDTOOL_OUT="" +function findTool { + DESC=$1 + OPTIONS=$2 + for CMD in ${OPTIONS}; do + if (which $CMD &>/dev/null) then + echo "Found $DESC: $CMD" + FINDTOOL_OUT="$CMD" + return + fi + done + echo "Error: could not find $DESC. One of these should be in your PATH:" + for CMD in ${OPTIONS}; do + echo " * $CMD" + done + exit 1 +} + +IS_CYGWIN=0 +IS_MSYS1=0 +IS_MSYS2=0 + +# Link parts of the Cygwin binary statically to aid in redistribution? The +# binary still links dynamically against the main DLL. The MinGW binaries are +# also statically linked and therefore depend only on Windows DLLs. I started +# linking the Cygwin/MSYS binary statically, because G++ 4.7 changed the +# Windows C++ ABI. +UNIX_LDFLAGS_STATIC='-static -static-libgcc -static-libstdc++' + +# Detect the environment -- Cygwin or MSYS. +case $(uname -s) in + CYGWIN*) + echo 'uname -s identifies a Cygwin environment.' + IS_CYGWIN=1 + case $(uname -m) in + i686) + echo 'uname -m identifies an i686 environment.' + UNIX_CXX=i686-pc-cygwin-g++ + MINGW_CXX=i686-w64-mingw32-g++ + ;; + x86_64) + echo 'uname -m identifies an x86_64 environment.' + UNIX_CXX=x86_64-pc-cygwin-g++ + MINGW_CXX=x86_64-w64-mingw32-g++ + ;; + *) + echo 'Error: uname -m did not match either i686 or x86_64.' + exit 1 + ;; + esac + ;; + MSYS*|MINGW*) + # MSYS2 notes: + # - MSYS2 offers two shortcuts to open an environment: + # - MinGW-w64 Win32 Shell. This env reports a `uname -s` of + # MINGW32_NT-6.1 on 32-bit Win7. The MinGW-w64 compiler + # (i686-w64-mingw32-g++.exe) is in the PATH. + # - MSYS2 Shell. `uname -s` instead reports MSYS_NT-6.1. + # The i686-w64-mingw32-g++ compiler is not in the PATH. + # - MSYS2 appears to use MinGW-w64, not the older mingw.org. + # MSYS notes: + # - `uname -s` is always MINGW32_NT-6.1 on Win7. + echo 'uname -s identifies an MSYS/MSYS2 environment.' + case $(uname -m) in + i686) + echo 'uname -m identifies an i686 environment.' + UNIX_CXX=i686-pc-msys-g++ + if echo "$(uname -r)" | grep '^1[.]' > /dev/null; then + # The MSYS-targeting compiler for the original 32-bit-only + # MSYS does not recognize the -static-libstdc++ flag, and + # it does not work with -static, because it tries to link + # statically with the core MSYS library and fails. + # + # Distinguish between the two using the major version + # number of `uname -r`: + # + # MSYS uname -r: 1.0.18(0.48/3/2) + # MSYS2 uname -r: 2.0.0(0.284/5/3) + # + # This is suboptimal because MSYS2 is not actually the + # second version of MSYS--it's a brand-new fork of Cygwin. + # + IS_MSYS1=1 + UNIX_LDFLAGS_STATIC= + MINGW_CXX=mingw32-g++ + else + IS_MSYS2=1 + MINGW_CXX=i686-w64-mingw32-g++.exe + fi + ;; + x86_64) + echo 'uname -m identifies an x86_64 environment.' + IS_MSYS2=1 + UNIX_CXX=x86_64-pc-msys-g++ + MINGW_CXX=x86_64-w64-mingw32-g++ + ;; + *) + echo 'Error: uname -m did not match either i686 or x86_64.' + exit 1 + ;; + esac + ;; + *) + echo 'Error: uname -s did not match either CYGWIN* or MINGW*.' + exit 1 + ;; +esac + +# Search the PATH and pick the first match. +findTool "Cygwin/MSYS G++ compiler" "$UNIX_CXX" +UNIX_CXX=$FINDTOOL_OUT +findTool "MinGW G++ compiler" "$MINGW_CXX" +MINGW_CXX=$FINDTOOL_OUT + +# Write config files. +echo Writing config.mk +echo UNIX_CXX=$UNIX_CXX > config.mk +echo UNIX_LDFLAGS_STATIC=$UNIX_LDFLAGS_STATIC >> config.mk +echo MINGW_CXX=$MINGW_CXX >> config.mk + +if test $IS_MSYS1 = 1; then + echo UNIX_CXXFLAGS += -DWINPTY_TARGET_MSYS1 >> config.mk + # The MSYS1 MinGW compiler has a bug that prevents inclusion of algorithm + # and math.h in normal C++11 mode. The workaround is to enable the gnu++11 + # mode instead. The bug was fixed on 2015-07-31, but as of 2016-02-26, the + # fix apparently hasn't been released. See + # http://ehc.ac/p/mingw/bugs/2250/. + echo MINGW_ENABLE_CXX11_FLAG := -std=gnu++11 >> config.mk +fi + +if test -d .git -a -f .git/HEAD -a -f .git/index && git rev-parse HEAD >&/dev/null; then + echo "Commit info: git" + echo 'COMMIT_HASH = $(shell git rev-parse HEAD)' >> config.mk + echo 'COMMIT_HASH_DEP := config.mk .git/HEAD .git/index' >> config.mk +else + echo "Commit info: none" + echo 'COMMIT_HASH := none' >> config.mk + echo 'COMMIT_HASH_DEP := config.mk' >> config.mk +fi diff --git a/src/libs/3rdparty/winpty/misc/.gitignore b/src/libs/3rdparty/winpty/misc/.gitignore new file mode 100644 index 00000000000..23751645fa1 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/.gitignore @@ -0,0 +1,2 @@ +*.exe +UnixEcho \ No newline at end of file diff --git a/src/libs/3rdparty/winpty/misc/BufferResizeTests.cc b/src/libs/3rdparty/winpty/misc/BufferResizeTests.cc new file mode 100644 index 00000000000..a5bb074826f --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/BufferResizeTests.cc @@ -0,0 +1,90 @@ +#include +#include + +#include "TestUtil.cc" + +void dumpInfoToTrace() { + CONSOLE_SCREEN_BUFFER_INFO info; + assert(GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info)); + trace("win=(%d,%d,%d,%d)", + (int)info.srWindow.Left, + (int)info.srWindow.Top, + (int)info.srWindow.Right, + (int)info.srWindow.Bottom); + trace("buf=(%d,%d)", + (int)info.dwSize.X, + (int)info.dwSize.Y); + trace("cur=(%d,%d)", + (int)info.dwCursorPosition.X, + (int)info.dwCursorPosition.Y); +} + +int main(int argc, char *argv[]) { + if (argc == 1) { + startChildProcess(L"CHILD"); + return 0; + } + + setWindowPos(0, 0, 1, 1); + + if (false) { + // Reducing the buffer height can move the window up. + setBufferSize(80, 25); + setWindowPos(0, 20, 80, 5); + Sleep(2000); + setBufferSize(80, 10); + } + + if (false) { + // Reducing the buffer height moves the window up and the buffer + // contents up too. + setBufferSize(80, 25); + setWindowPos(0, 20, 80, 5); + setCursorPos(0, 20); + printf("TEST1\nTEST2\nTEST3\nTEST4\n"); + fflush(stdout); + Sleep(2000); + setBufferSize(80, 10); + } + + if (false) { + // Reducing the buffer width can move the window left. + setBufferSize(80, 25); + setWindowPos(40, 0, 40, 25); + Sleep(2000); + setBufferSize(60, 25); + } + + if (false) { + // Sometimes the buffer contents are shifted up; sometimes they're + // shifted down. It seems to depend on the cursor position? + + // setBufferSize(80, 25); + // setWindowPos(0, 20, 80, 5); + // setCursorPos(0, 20); + // printf("TESTa\nTESTb\nTESTc\nTESTd\nTESTe"); + // fflush(stdout); + // setCursorPos(0, 0); + // printf("TEST1\nTEST2\nTEST3\nTEST4\nTEST5"); + // fflush(stdout); + // setCursorPos(0, 24); + // Sleep(5000); + // setBufferSize(80, 24); + + setBufferSize(80, 20); + setWindowPos(0, 10, 80, 10); + setCursorPos(0, 18); + + printf("TEST1\nTEST2"); + fflush(stdout); + setCursorPos(0, 18); + + Sleep(2000); + setBufferSize(80, 18); + } + + dumpInfoToTrace(); + Sleep(30000); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/ChangeScreenBuffer.cc b/src/libs/3rdparty/winpty/misc/ChangeScreenBuffer.cc new file mode 100644 index 00000000000..701a2cb4a3c --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/ChangeScreenBuffer.cc @@ -0,0 +1,53 @@ +// A test program for CreateConsoleScreenBuffer / SetConsoleActiveScreenBuffer +// + +#include +#include +#include +#include +#include + +#include "TestUtil.cc" + +int main() +{ + HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE); + HANDLE childBuffer = CreateConsoleScreenBuffer( + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, CONSOLE_TEXTMODE_BUFFER, NULL); + + SetConsoleActiveScreenBuffer(childBuffer); + + while (true) { + char buf[1024]; + CONSOLE_SCREEN_BUFFER_INFO info; + + assert(GetConsoleScreenBufferInfo(origBuffer, &info)); + trace("child.size=(%d,%d)", (int)info.dwSize.X, (int)info.dwSize.Y); + trace("child.cursor=(%d,%d)", (int)info.dwCursorPosition.X, (int)info.dwCursorPosition.Y); + trace("child.window=(%d,%d,%d,%d)", + (int)info.srWindow.Left, (int)info.srWindow.Top, + (int)info.srWindow.Right, (int)info.srWindow.Bottom); + trace("child.maxSize=(%d,%d)", (int)info.dwMaximumWindowSize.X, (int)info.dwMaximumWindowSize.Y); + + int ch = getch(); + sprintf(buf, "%02x\n", ch); + DWORD actual = 0; + WriteFile(childBuffer, buf, strlen(buf), &actual, NULL); + if (ch == 0x1b/*ESC*/ || ch == 0x03/*CTRL-C*/) + break; + + if (ch == 'b') { + setBufferSize(origBuffer, 40, 25); + } else if (ch == 'w') { + setWindowPos(origBuffer, 1, 1, 38, 23); + } else if (ch == 'c') { + setCursorPos(origBuffer, 10, 10); + } + } + + SetConsoleActiveScreenBuffer(origBuffer); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/ClearConsole.cc b/src/libs/3rdparty/winpty/misc/ClearConsole.cc new file mode 100644 index 00000000000..f95f8c84caa --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/ClearConsole.cc @@ -0,0 +1,72 @@ +/* + * Demonstrates that console clearing sets each cell's character to SP, not + * NUL, and it sets the attribute of each cell to the current text attribute. + * + * This confirms the MSDN instruction in the "Clearing the Screen" article. + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms682022(v=vs.85).aspx + * It advises using GetConsoleScreenBufferInfo to get the current text + * attribute, then FillConsoleOutputCharacter and FillConsoleOutputAttribute to + * write to the console buffer. + */ + +#include + +#include +#include +#include + +#include "TestUtil.cc" + +int main(int argc, char *argv[]) { + if (argc == 1) { + startChildProcess(L"CHILD"); + return 0; + } + + const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE); + + SetConsoleTextAttribute(conout, 0x24); + system("cls"); + + setWindowPos(0, 0, 1, 1); + setBufferSize(80, 25); + setWindowPos(0, 0, 80, 25); + + CHAR_INFO buf; + COORD bufSize = { 1, 1 }; + COORD bufCoord = { 0, 0 }; + SMALL_RECT rect = { 5, 5, 5, 5 }; + BOOL ret; + DWORD actual; + COORD writeCoord = { 5, 5 }; + + // After cls, each cell's character is a space, and its attributes are the + // default text attributes. + ret = ReadConsoleOutputW(conout, &buf, bufSize, bufCoord, &rect); + assert(ret && buf.Char.UnicodeChar == L' ' && buf.Attributes == 0x24); + + // Nevertheless, it is possible to change a cell to NUL. + ret = FillConsoleOutputCharacterW(conout, L'\0', 1, writeCoord, &actual); + assert(ret && actual == 1); + ret = ReadConsoleOutputW(conout, &buf, bufSize, bufCoord, &rect); + assert(ret && buf.Char.UnicodeChar == L'\0' && buf.Attributes == 0x24); + + // As well as a 0 attribute. (As one would expect, the cell is + // black-on-black.) + ret = FillConsoleOutputAttribute(conout, 0, 1, writeCoord, &actual); + assert(ret && actual == 1); + ret = ReadConsoleOutputW(conout, &buf, bufSize, bufCoord, &rect); + assert(ret && buf.Char.UnicodeChar == L'\0' && buf.Attributes == 0); + ret = FillConsoleOutputCharacterW(conout, L'X', 1, writeCoord, &actual); + assert(ret && actual == 1); + ret = ReadConsoleOutputW(conout, &buf, bufSize, bufCoord, &rect); + assert(ret && buf.Char.UnicodeChar == L'X' && buf.Attributes == 0); + + // The 'X' is invisible. + countDown(3); + + ret = FillConsoleOutputAttribute(conout, 0x42, 1, writeCoord, &actual); + assert(ret && actual == 1); + + countDown(5); +} diff --git a/src/libs/3rdparty/winpty/misc/ConinMode.cc b/src/libs/3rdparty/winpty/misc/ConinMode.cc new file mode 100644 index 00000000000..1e1428d8b0c --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/ConinMode.cc @@ -0,0 +1,117 @@ +#include + +#include +#include +#include + +#include +#include + +static HANDLE getConin() { + HANDLE conin = GetStdHandle(STD_INPUT_HANDLE); + if (conin == INVALID_HANDLE_VALUE) { + fprintf(stderr, "error: cannot get stdin\n"); + exit(1); + } + return conin; +} + +static DWORD getConsoleMode() { + DWORD mode = 0; + if (!GetConsoleMode(getConin(), &mode)) { + fprintf(stderr, "error: GetConsoleMode failed (is stdin a console?)\n"); + exit(1); + } + return mode; +} + +static void setConsoleMode(DWORD mode) { + if (!SetConsoleMode(getConin(), mode)) { + fprintf(stderr, "error: SetConsoleMode failed (is stdin a console?)\n"); + exit(1); + } +} + +static long parseInt(const std::string &s) { + errno = 0; + char *endptr = nullptr; + long result = strtol(s.c_str(), &endptr, 0); + if (errno != 0 || !endptr || *endptr != '\0') { + fprintf(stderr, "error: could not parse integral argument '%s'\n", s.c_str()); + exit(1); + } + return result; +} + +static void usage() { + printf("Usage: ConinMode [verb] [options]\n"); + printf("Verbs:\n"); + printf(" [info] Dumps info about mode flags.\n"); + printf(" get Prints the mode DWORD.\n"); + printf(" set VALUE Sets the mode to VALUE, which can be decimal, hex, or octal.\n"); + printf(" set VALUE MASK\n"); + printf(" Same as `set VALUE`, but only alters the bits in MASK.\n"); + exit(1); +} + +struct { + const char *name; + DWORD value; +} kInputFlags[] = { + "ENABLE_PROCESSED_INPUT", ENABLE_PROCESSED_INPUT, // 0x0001 + "ENABLE_LINE_INPUT", ENABLE_LINE_INPUT, // 0x0002 + "ENABLE_ECHO_INPUT", ENABLE_ECHO_INPUT, // 0x0004 + "ENABLE_WINDOW_INPUT", ENABLE_WINDOW_INPUT, // 0x0008 + "ENABLE_MOUSE_INPUT", ENABLE_MOUSE_INPUT, // 0x0010 + "ENABLE_INSERT_MODE", ENABLE_INSERT_MODE, // 0x0020 + "ENABLE_QUICK_EDIT_MODE", ENABLE_QUICK_EDIT_MODE, // 0x0040 + "ENABLE_EXTENDED_FLAGS", ENABLE_EXTENDED_FLAGS, // 0x0080 + "ENABLE_VIRTUAL_TERMINAL_INPUT", 0x0200/*ENABLE_VIRTUAL_TERMINAL_INPUT*/, // 0x0200 +}; + +int main(int argc, char *argv[]) { + std::vector args; + for (size_t i = 1; i < argc; ++i) { + args.push_back(argv[i]); + } + + if (args.empty() || args.size() == 1 && args[0] == "info") { + DWORD mode = getConsoleMode(); + printf("mode: 0x%lx\n", mode); + for (const auto &flag : kInputFlags) { + printf("%-29s 0x%04lx %s\n", flag.name, flag.value, flag.value & mode ? "ON" : "off"); + mode &= ~flag.value; + } + for (int i = 0; i < 32; ++i) { + if (mode & (1u << i)) { + printf("Unrecognized flag: %04x\n", (1u << i)); + } + } + return 0; + } + + const auto verb = args[0]; + + if (verb == "set") { + if (args.size() == 2) { + const DWORD newMode = parseInt(args[1]); + setConsoleMode(newMode); + } else if (args.size() == 3) { + const DWORD mode = parseInt(args[1]); + const DWORD mask = parseInt(args[2]); + const int newMode = (getConsoleMode() & ~mask) | (mode & mask); + setConsoleMode(newMode); + } else { + usage(); + } + } else if (verb == "get") { + if (args.size() != 1) { + usage(); + } + printf("0x%lx\n", getConsoleMode()); + } else { + usage(); + } + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/ConinMode.ps1 b/src/libs/3rdparty/winpty/misc/ConinMode.ps1 new file mode 100644 index 00000000000..ecfe8f039e4 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/ConinMode.ps1 @@ -0,0 +1,116 @@ +# +# PowerShell script for controlling the console QuickEdit and InsertMode flags. +# +# Turn QuickEdit off to interact with mouse-driven console programs. +# +# Usage: +# +# powershell .\ConinMode.ps1 [Options] +# +# Options: +# -QuickEdit [on/off] +# -InsertMode [on/off] +# -Mode [integer] +# + +param ( + [ValidateSet("on", "off")][string] $QuickEdit, + [ValidateSet("on", "off")][string] $InsertMode, + [int] $Mode +) + +$signature = @' +[DllImport("kernel32.dll", SetLastError = true)] +public static extern IntPtr GetStdHandle(int nStdHandle); + +[DllImport("kernel32.dll", SetLastError = true)] +public static extern uint GetConsoleMode( + IntPtr hConsoleHandle, + out uint lpMode); + +[DllImport("kernel32.dll", SetLastError = true)] +public static extern uint SetConsoleMode( + IntPtr hConsoleHandle, + uint dwMode); + +public const int STD_INPUT_HANDLE = -10; +public const int ENABLE_INSERT_MODE = 0x0020; +public const int ENABLE_QUICK_EDIT_MODE = 0x0040; +public const int ENABLE_EXTENDED_FLAGS = 0x0080; +'@ + +$WinAPI = Add-Type -MemberDefinition $signature ` + -Name WinAPI -Namespace ConinModeScript ` + -PassThru + +function GetConIn { + $ret = $WinAPI::GetStdHandle($WinAPI::STD_INPUT_HANDLE) + if ($ret -eq -1) { + throw "error: cannot get stdin" + } + return $ret +} + +function GetConsoleMode { + $conin = GetConIn + $mode = 0 + $ret = $WinAPI::GetConsoleMode($conin, [ref]$mode) + if ($ret -eq 0) { + throw "GetConsoleMode failed (is stdin a console?)" + } + return $mode +} + +function SetConsoleMode($mode) { + $conin = GetConIn + $ret = $WinAPI::SetConsoleMode($conin, $mode) + if ($ret -eq 0) { + throw "SetConsoleMode failed (is stdin a console?)" + } +} + +$oldMode = GetConsoleMode +$newMode = $oldMode +$doingSomething = $false + +if ($PSBoundParameters.ContainsKey("Mode")) { + $newMode = $Mode + $doingSomething = $true +} + +if ($QuickEdit + $InsertMode -ne "") { + if (!($newMode -band $WinAPI::ENABLE_EXTENDED_FLAGS)) { + # We can't enable an extended flag without overwriting the existing + # QuickEdit/InsertMode flags. AFAICT, there is no way to query their + # existing values, so at least we can choose sensible defaults. + $newMode = $newMode -bor $WinAPI::ENABLE_EXTENDED_FLAGS + $newMode = $newMode -bor $WinAPI::ENABLE_QUICK_EDIT_MODE + $newMode = $newMode -bor $WinAPI::ENABLE_INSERT_MODE + $doingSomething = $true + } +} + +if ($QuickEdit -eq "on") { + $newMode = $newMode -bor $WinAPI::ENABLE_QUICK_EDIT_MODE + $doingSomething = $true +} elseif ($QuickEdit -eq "off") { + $newMode = $newMode -band (-bnot $WinAPI::ENABLE_QUICK_EDIT_MODE) + $doingSomething = $true +} + +if ($InsertMode -eq "on") { + $newMode = $newMode -bor $WinAPI::ENABLE_INSERT_MODE + $doingSomething = $true +} elseif ($InsertMode -eq "off") { + $newMode = $newMode -band (-bnot $WinAPI::ENABLE_INSERT_MODE) + $doingSomething = $true +} + +if ($doingSomething) { + echo "old mode: $oldMode" + SetConsoleMode $newMode + $newMode = GetConsoleMode + echo "new mode: $newMode" +} else { + echo "mode: $oldMode" +} diff --git a/src/libs/3rdparty/winpty/misc/ConoutMode.cc b/src/libs/3rdparty/winpty/misc/ConoutMode.cc new file mode 100644 index 00000000000..100e0c7bea9 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/ConoutMode.cc @@ -0,0 +1,113 @@ +#include + +#include +#include +#include + +#include +#include + +static HANDLE getConout() { + HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE); + if (conout == INVALID_HANDLE_VALUE) { + fprintf(stderr, "error: cannot get stdout\n"); + exit(1); + } + return conout; +} + +static DWORD getConsoleMode() { + DWORD mode = 0; + if (!GetConsoleMode(getConout(), &mode)) { + fprintf(stderr, "error: GetConsoleMode failed (is stdout a console?)\n"); + exit(1); + } + return mode; +} + +static void setConsoleMode(DWORD mode) { + if (!SetConsoleMode(getConout(), mode)) { + fprintf(stderr, "error: SetConsoleMode failed (is stdout a console?)\n"); + exit(1); + } +} + +static long parseInt(const std::string &s) { + errno = 0; + char *endptr = nullptr; + long result = strtol(s.c_str(), &endptr, 0); + if (errno != 0 || !endptr || *endptr != '\0') { + fprintf(stderr, "error: could not parse integral argument '%s'\n", s.c_str()); + exit(1); + } + return result; +} + +static void usage() { + printf("Usage: ConoutMode [verb] [options]\n"); + printf("Verbs:\n"); + printf(" [info] Dumps info about mode flags.\n"); + printf(" get Prints the mode DWORD.\n"); + printf(" set VALUE Sets the mode to VALUE, which can be decimal, hex, or octal.\n"); + printf(" set VALUE MASK\n"); + printf(" Same as `set VALUE`, but only alters the bits in MASK.\n"); + exit(1); +} + +struct { + const char *name; + DWORD value; +} kOutputFlags[] = { + "ENABLE_PROCESSED_OUTPUT", ENABLE_PROCESSED_OUTPUT, // 0x0001 + "ENABLE_WRAP_AT_EOL_OUTPUT", ENABLE_WRAP_AT_EOL_OUTPUT, // 0x0002 + "ENABLE_VIRTUAL_TERMINAL_PROCESSING", 0x0004/*ENABLE_VIRTUAL_TERMINAL_PROCESSING*/, // 0x0004 + "DISABLE_NEWLINE_AUTO_RETURN", 0x0008/*DISABLE_NEWLINE_AUTO_RETURN*/, // 0x0008 + "ENABLE_LVB_GRID_WORLDWIDE", 0x0010/*ENABLE_LVB_GRID_WORLDWIDE*/, //0x0010 +}; + +int main(int argc, char *argv[]) { + std::vector args; + for (size_t i = 1; i < argc; ++i) { + args.push_back(argv[i]); + } + + if (args.empty() || args.size() == 1 && args[0] == "info") { + DWORD mode = getConsoleMode(); + printf("mode: 0x%lx\n", mode); + for (const auto &flag : kOutputFlags) { + printf("%-34s 0x%04lx %s\n", flag.name, flag.value, flag.value & mode ? "ON" : "off"); + mode &= ~flag.value; + } + for (int i = 0; i < 32; ++i) { + if (mode & (1u << i)) { + printf("Unrecognized flag: %04x\n", (1u << i)); + } + } + return 0; + } + + const auto verb = args[0]; + + if (verb == "set") { + if (args.size() == 2) { + const DWORD newMode = parseInt(args[1]); + setConsoleMode(newMode); + } else if (args.size() == 3) { + const DWORD mode = parseInt(args[1]); + const DWORD mask = parseInt(args[2]); + const int newMode = (getConsoleMode() & ~mask) | (mode & mask); + setConsoleMode(newMode); + } else { + usage(); + } + } else if (verb == "get") { + if (args.size() != 1) { + usage(); + } + printf("0x%lx\n", getConsoleMode()); + } else { + usage(); + } + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/DebugClient.py b/src/libs/3rdparty/winpty/misc/DebugClient.py new file mode 100644 index 00000000000..cd12df8924a --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/DebugClient.py @@ -0,0 +1,42 @@ +#!python +# Run with native CPython. Needs pywin32 extensions. + +# Copyright (c) 2011-2012 Ryan Prichard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +import winerror +import win32pipe +import win32file +import win32api +import sys +import pywintypes +import time + +if len(sys.argv) != 2: + print("Usage: %s message" % sys.argv[0]) + sys.exit(1) + +message = "[%05.3f %s]: %s" % (time.time() % 100000, sys.argv[0], sys.argv[1]) + +win32pipe.CallNamedPipe( + "\\\\.\\pipe\\DebugServer", + message.encode(), + 16, + win32pipe.NMPWAIT_WAIT_FOREVER) diff --git a/src/libs/3rdparty/winpty/misc/DebugServer.py b/src/libs/3rdparty/winpty/misc/DebugServer.py new file mode 100644 index 00000000000..3fc068bae70 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/DebugServer.py @@ -0,0 +1,63 @@ +#!python +# +# Run with native CPython. Needs pywin32 extensions. + +# Copyright (c) 2011-2012 Ryan Prichard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +import win32pipe +import win32api +import win32file +import time +import threading +import sys + +# A message may not be larger than this size. +MSG_SIZE=4096 + +serverPipe = win32pipe.CreateNamedPipe( + "\\\\.\\pipe\\DebugServer", + win32pipe.PIPE_ACCESS_DUPLEX, + win32pipe.PIPE_TYPE_MESSAGE | win32pipe.PIPE_READMODE_MESSAGE, + win32pipe.PIPE_UNLIMITED_INSTANCES, + MSG_SIZE, + MSG_SIZE, + 10 * 1000, + None) +while True: + win32pipe.ConnectNamedPipe(serverPipe, None) + (ret, data) = win32file.ReadFile(serverPipe, MSG_SIZE) + print(data.decode()) + sys.stdout.flush() + + # The client uses CallNamedPipe to send its message. CallNamedPipe waits + # for a reply message. If I send a reply, however, using WriteFile, then + # sometimes WriteFile fails with: + # pywintypes.error: (232, 'WriteFile', 'The pipe is being closed.') + # I can't figure out how to write a strictly correct pipe server, but if + # I comment out the WriteFile line, then everything seems to work. I + # think the DisconnectNamedPipe call aborts the client's CallNamedPipe + # call normally. + + try: + win32file.WriteFile(serverPipe, b'OK') + except: + pass + win32pipe.DisconnectNamedPipe(serverPipe) diff --git a/src/libs/3rdparty/winpty/misc/DumpLines.py b/src/libs/3rdparty/winpty/misc/DumpLines.py new file mode 100644 index 00000000000..40049961b5c --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/DumpLines.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +import sys + +for i in range(1, int(sys.argv[1]) + 1): + print i, "X" * 78 diff --git a/src/libs/3rdparty/winpty/misc/EnableExtendedFlags.txt b/src/libs/3rdparty/winpty/misc/EnableExtendedFlags.txt new file mode 100644 index 00000000000..37914dac268 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/EnableExtendedFlags.txt @@ -0,0 +1,46 @@ +Note regarding ENABLE_EXTENDED_FLAGS (2016-05-30) + +There is a complicated interaction between the ENABLE_EXTENDED_FLAGS flag +and the ENABLE_QUICK_EDIT_MODE and ENABLE_INSERT_MODE flags (presumably for +backwards compatibility?). I studied the behavior on Windows 7 and Windows +10, with both the old and new consoles, and I didn't see any differences +between versions. Here's what I seemed to observe: + + - The console has three flags internally: + - QuickEdit + - InsertMode + - ExtendedFlags + + - SetConsoleMode psuedocode: + void SetConsoleMode(..., DWORD mode) { + ExtendedFlags = (mode & (ENABLE_EXTENDED_FLAGS + | ENABLE_QUICK_EDIT_MODE + | ENABLE_INSERT_MODE )) != 0; + if (ExtendedFlags) { + QuickEdit = (mode & ENABLE_QUICK_EDIT_MODE) != 0; + InsertMode = (mode & ENABLE_INSERT_MODE) != 0; + } + } + + - Setting QuickEdit or InsertMode from the properties dialog GUI does not + affect the ExtendedFlags setting -- it simply toggles the one flag. + + - GetConsoleMode psuedocode: + GetConsoleMode(..., DWORD *result) { + if (ExtendedFlags) { + *result |= ENABLE_EXTENDED_FLAGS; + if (QuickEdit) { *result |= ENABLE_QUICK_EDIT_MODE; } + if (InsertMode) { *result |= ENABLE_INSERT_MODE; } + } + } + +Effectively, the ExtendedFlags flags controls whether the other two flags +are visible/controlled by the user application. If they aren't visible, +though, there is no way for the user application to make them visible, +except by overwriting their values! Calling SetConsoleMode with just +ENABLE_EXTENDED_FLAGS would clear the extended flags we want to read. + +Consequently, if a program temporarily alters the QuickEdit flag (e.g. to +enable mouse input), it cannot restore the original values of the QuickEdit +and InsertMode flags, UNLESS every other console program cooperates by +keeping the ExtendedFlags flag set. diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Consolas.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Consolas.txt new file mode 100644 index 00000000000..067bd3824a8 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Consolas.txt @@ -0,0 +1,528 @@ +================================== +Code Page 437, Consolas font +================================== + +Options: -face "Consolas" -family 0x36 +Chars: A2 A3 2014 3044 30FC 4000 + +FontSurvey "-face \"Consolas\" -family 0x36" + +Windows 7 +--------- + +Size 1: 1,3 BAD (HHHHHH) +Size 2: 1,2 BAD (HHHHHH) +Size 3: 1,3 BAD (HHHHHH) +Size 4: 2,4 BAD (HHHHHH) +Size 5: 2,5 BAD (HHHHHH) +Size 6: 3,6 BAD (HHHHHH) +Size 7: 3,6 BAD (HHHHHH) +Size 8: 4,8 BAD (HHHHHH) +Size 9: 4,9 BAD (HHHHHH) +Size 10: 5,10 BAD (HHHHHH) +Size 11: 5,11 BAD (HHHHHH) +Size 12: 6,12 BAD (HHHHHH) +Size 13: 6,13 BAD (HHHHHH) +Size 14: 7,14 BAD (HHHHHH) +Size 15: 7,15 BAD (HHHHHH) +Size 16: 8,16 BAD (HHHHHH) +Size 17: 8,17 BAD (HHHHHH) +Size 18: 8,18 BAD (HHHHHH) +Size 19: 9,19 BAD (HHHHHH) +Size 20: 9,20 BAD (HHHHHH) +Size 21: 10,22 BAD (HHHHHH) +Size 22: 10,22 BAD (HHHHHH) +Size 23: 11,23 BAD (HHHHHH) +Size 24: 11,24 BAD (HHHHHH) +Size 25: 12,25 BAD (HHHHHH) +Size 26: 12,26 BAD (HHHHHH) +Size 27: 13,27 BAD (HHHHHH) +Size 28: 13,28 BAD (HHHHHH) +Size 29: 14,29 BAD (HHHHHH) +Size 30: 14,30 BAD (HHHHHH) +Size 31: 15,31 BAD (HHHHHH) +Size 32: 15,32 BAD (HHHHHH) +Size 33: 15,33 BAD (HHHHHH) +Size 34: 16,34 BAD (HHHHHH) +Size 35: 16,36 BAD (HHHHHH) +Size 36: 17,36 BAD (HHHHHH) +Size 37: 17,37 BAD (HHHHHH) +Size 38: 18,38 BAD (HHHHHH) +Size 39: 18,39 BAD (HHHHHH) +Size 40: 19,40 BAD (HHHHHH) +Size 41: 19,41 BAD (HHHHHH) +Size 42: 20,42 BAD (HHHHHH) +Size 43: 20,43 BAD (HHHHHH) +Size 44: 21,44 BAD (HHHHHH) +Size 45: 21,45 BAD (HHHHHH) +Size 46: 22,46 BAD (HHHHHH) +Size 47: 22,47 BAD (HHHHHH) +Size 48: 23,48 BAD (HHHHHH) +Size 49: 23,49 BAD (HHHHHH) +Size 50: 23,50 BAD (HHHHHH) +Size 51: 24,51 BAD (HHHHHH) +Size 52: 24,52 BAD (HHHHHH) +Size 53: 25,53 BAD (HHHHHH) +Size 54: 25,54 BAD (HHHHHH) +Size 55: 26,55 BAD (HHHHHH) +Size 56: 26,56 BAD (HHHHHH) +Size 57: 27,57 BAD (HHHHHH) +Size 58: 27,58 BAD (HHHHHH) +Size 59: 28,59 BAD (HHHHHH) +Size 60: 28,60 BAD (HHHHHH) +Size 61: 29,61 BAD (HHHHHH) +Size 62: 29,62 BAD (HHHHHH) +Size 63: 30,64 BAD (HHHHHH) +Size 64: 30,64 BAD (HHHHHH) +Size 65: 31,65 BAD (HHHHHH) +Size 66: 31,66 BAD (HHHHHH) +Size 67: 31,67 BAD (HHHHHH) +Size 68: 32,68 BAD (HHHHHH) +Size 69: 32,69 BAD (HHHHHH) +Size 70: 33,70 BAD (HHHHHH) +Size 71: 33,71 BAD (HHHHHH) +Size 72: 34,72 BAD (HHHHHH) +Size 73: 34,73 BAD (HHHHHH) +Size 74: 35,74 BAD (HHHHHH) +Size 75: 35,75 BAD (HHHHHH) +Size 76: 36,76 BAD (HHHHHH) +Size 77: 36,77 BAD (HHHHHH) +Size 78: 37,78 BAD (HHHHHH) +Size 79: 37,79 BAD (HHHHHH) +Size 80: 38,80 BAD (HHHHHH) +Size 81: 38,81 BAD (HHHHHH) +Size 82: 39,82 BAD (HHHHHH) +Size 83: 39,83 BAD (HHHHHH) +Size 84: 39,84 BAD (HHHHHH) +Size 85: 40,85 BAD (HHHHHH) +Size 86: 40,86 BAD (HHHHHH) +Size 87: 41,87 BAD (HHHHHH) +Size 88: 41,88 BAD (HHHHHH) +Size 89: 42,89 BAD (HHHHHH) +Size 90: 42,90 BAD (HHHHHH) +Size 91: 43,91 BAD (HHHHHH) +Size 92: 43,92 BAD (HHHHHH) +Size 93: 44,93 BAD (HHHHHH) +Size 94: 44,94 BAD (HHHHHH) +Size 95: 45,95 BAD (HHHHHH) +Size 96: 45,96 BAD (HHHHHH) +Size 97: 46,97 BAD (HHHHHH) +Size 98: 46,98 BAD (HHHHHH) +Size 99: 46,99 BAD (HHHHHH) +Size 100: 47,100 BAD (HHHHHH) + +Windows 8 +--------- + +Size 1: 1,3 BAD (HHHHHH) +Size 2: 1,2 BAD (HHHHHH) +Size 3: 1,3 BAD (HHHHHH) +Size 4: 2,4 BAD (HHHHHH) +Size 5: 2,5 BAD (HHHHHH) +Size 6: 3,6 BAD (HHHHHH) +Size 7: 3,6 BAD (HHHHHH) +Size 8: 4,8 BAD (HHHHHH) +Size 9: 4,9 BAD (HHHHHH) +Size 10: 5,10 BAD (HHHHHH) +Size 11: 5,11 BAD (HHHHHH) +Size 12: 6,12 BAD (HHHHHH) +Size 13: 6,13 BAD (HHHHHH) +Size 14: 7,14 BAD (HHHHHH) +Size 15: 7,15 BAD (HHHHHH) +Size 16: 8,16 BAD (HHHHHH) +Size 17: 8,17 BAD (HHHHHH) +Size 18: 8,18 BAD (HHHHHH) +Size 19: 9,19 BAD (HHHHHH) +Size 20: 9,20 BAD (HHHHHH) +Size 21: 10,22 BAD (HHHHHH) +Size 22: 10,22 BAD (HHHHHH) +Size 23: 11,23 BAD (HHHHHH) +Size 24: 11,24 BAD (HHHHHH) +Size 25: 12,25 BAD (HHHHHH) +Size 26: 12,26 BAD (HHHHHH) +Size 27: 13,27 BAD (HHHHHH) +Size 28: 13,28 BAD (HHHHHH) +Size 29: 14,29 BAD (HHHHHH) +Size 30: 14,30 BAD (HHHHHH) +Size 31: 15,31 BAD (HHHHHH) +Size 32: 15,32 BAD (HHHHHH) +Size 33: 15,33 BAD (HHHHHH) +Size 34: 16,34 BAD (HHHHHH) +Size 35: 16,36 BAD (HHHHHH) +Size 36: 17,36 BAD (HHHHHH) +Size 37: 17,37 BAD (HHHHHH) +Size 38: 18,38 BAD (HHHHHH) +Size 39: 18,39 BAD (HHHHHH) +Size 40: 19,40 BAD (HHHHHH) +Size 41: 19,41 BAD (HHHHHH) +Size 42: 20,42 BAD (HHHHHH) +Size 43: 20,43 BAD (HHHHHH) +Size 44: 21,44 BAD (HHHHHH) +Size 45: 21,45 BAD (HHHHHH) +Size 46: 22,46 BAD (HHHHHH) +Size 47: 22,47 BAD (HHHHHH) +Size 48: 23,48 BAD (HHHHHH) +Size 49: 23,49 BAD (HHHHHH) +Size 50: 23,50 BAD (HHHHHH) +Size 51: 24,51 BAD (HHHHHH) +Size 52: 24,52 BAD (HHHHHH) +Size 53: 25,53 BAD (HHHHHH) +Size 54: 25,54 BAD (HHHHHH) +Size 55: 26,55 BAD (HHHHHH) +Size 56: 26,56 BAD (HHHHHH) +Size 57: 27,57 BAD (HHHHHH) +Size 58: 27,58 BAD (HHHHHH) +Size 59: 28,59 BAD (HHHHHH) +Size 60: 28,60 BAD (HHHHHH) +Size 61: 29,61 BAD (HHHHHH) +Size 62: 29,62 BAD (HHHHHH) +Size 63: 30,64 BAD (HHHHHH) +Size 64: 30,64 BAD (HHHHHH) +Size 65: 31,65 BAD (HHHHHH) +Size 66: 31,66 BAD (HHHHHH) +Size 67: 31,67 BAD (HHHHHH) +Size 68: 32,68 BAD (HHHHHH) +Size 69: 32,69 BAD (HHHHHH) +Size 70: 33,70 BAD (HHHHHH) +Size 71: 33,71 BAD (HHHHHH) +Size 72: 34,72 BAD (HHHHHH) +Size 73: 34,73 BAD (HHHHHH) +Size 74: 35,74 BAD (HHHHHH) +Size 75: 35,75 BAD (HHHHHH) +Size 76: 36,76 BAD (HHHHHH) +Size 77: 36,77 BAD (HHHHHH) +Size 78: 37,78 BAD (HHHHHH) +Size 79: 37,79 BAD (HHHHHH) +Size 80: 38,80 BAD (HHHHHH) +Size 81: 38,81 BAD (HHHHHH) +Size 82: 39,82 BAD (HHHHHH) +Size 83: 39,83 BAD (HHHHHH) +Size 84: 39,84 BAD (HHHHHH) +Size 85: 40,85 BAD (HHHHHH) +Size 86: 40,86 BAD (HHHHHH) +Size 87: 41,87 BAD (HHHHHH) +Size 88: 41,88 BAD (HHHHHH) +Size 89: 42,89 BAD (HHHHHH) +Size 90: 42,90 BAD (HHHHHH) +Size 91: 43,91 BAD (HHHHHH) +Size 92: 43,92 BAD (HHHHHH) +Size 93: 44,93 BAD (HHHHHH) +Size 94: 44,94 BAD (HHHHHH) +Size 95: 45,95 BAD (HHHHHH) +Size 96: 45,96 BAD (HHHHHH) +Size 97: 46,97 BAD (HHHHHH) +Size 98: 46,98 BAD (HHHHHH) +Size 99: 46,99 BAD (HHHHHH) +Size 100: 47,100 BAD (HHHHHH) + +Windows 8.1 +----------- + +Size 1: 1,3 BAD (HHHHHH) +Size 2: 1,2 BAD (HHHHHH) +Size 3: 1,3 BAD (HHHHHH) +Size 4: 2,4 BAD (HHHHHH) +Size 5: 2,5 BAD (HHHHHH) +Size 6: 3,6 BAD (HHHHHH) +Size 7: 3,6 BAD (HHHHHH) +Size 8: 4,8 BAD (HHHHHH) +Size 9: 4,9 BAD (HHHHHH) +Size 10: 5,10 BAD (HHHHHH) +Size 11: 5,11 BAD (HHHHHH) +Size 12: 6,12 BAD (HHHHHH) +Size 13: 6,13 BAD (HHHHHH) +Size 14: 7,14 BAD (HHHHHH) +Size 15: 7,15 BAD (HHHHHH) +Size 16: 8,16 BAD (HHHHHH) +Size 17: 8,17 BAD (HHHHHH) +Size 18: 8,18 BAD (HHHHHH) +Size 19: 9,19 BAD (HHHHHH) +Size 20: 9,20 BAD (HHHHHH) +Size 21: 10,22 BAD (HHHHHH) +Size 22: 10,22 BAD (HHHHHH) +Size 23: 11,23 BAD (HHHHHH) +Size 24: 11,24 BAD (HHHHHH) +Size 25: 12,25 BAD (HHHHHH) +Size 26: 12,26 BAD (HHHHHH) +Size 27: 13,27 BAD (HHHHHH) +Size 28: 13,28 BAD (HHHHHH) +Size 29: 14,29 BAD (HHHHHH) +Size 30: 14,30 BAD (HHHHHH) +Size 31: 15,31 BAD (HHHHHH) +Size 32: 15,32 BAD (HHHHHH) +Size 33: 15,33 BAD (HHHHHH) +Size 34: 16,34 BAD (HHHHHH) +Size 35: 16,36 BAD (HHHHHH) +Size 36: 17,36 BAD (HHHHHH) +Size 37: 17,37 BAD (HHHHHH) +Size 38: 18,38 BAD (HHHHHH) +Size 39: 18,39 BAD (HHHHHH) +Size 40: 19,40 BAD (HHHHHH) +Size 41: 19,41 BAD (HHHHHH) +Size 42: 20,42 BAD (HHHHHH) +Size 43: 20,43 BAD (HHHHHH) +Size 44: 21,44 BAD (HHHHHH) +Size 45: 21,45 BAD (HHHHHH) +Size 46: 22,46 BAD (HHHHHH) +Size 47: 22,47 BAD (HHHHHH) +Size 48: 23,48 BAD (HHHHHH) +Size 49: 23,49 BAD (HHHHHH) +Size 50: 23,50 BAD (HHHHHH) +Size 51: 24,51 BAD (HHHHHH) +Size 52: 24,52 BAD (HHHHHH) +Size 53: 25,53 BAD (HHHHHH) +Size 54: 25,54 BAD (HHHHHH) +Size 55: 26,55 BAD (HHHHHH) +Size 56: 26,56 BAD (HHHHHH) +Size 57: 27,57 BAD (HHHHHH) +Size 58: 27,58 BAD (HHHHHH) +Size 59: 28,59 BAD (HHHHHH) +Size 60: 28,60 BAD (HHHHHH) +Size 61: 29,61 BAD (HHHHHH) +Size 62: 29,62 BAD (HHHHHH) +Size 63: 30,64 BAD (HHHHHH) +Size 64: 30,64 BAD (HHHHHH) +Size 65: 31,65 BAD (HHHHHH) +Size 66: 31,66 BAD (HHHHHH) +Size 67: 31,67 BAD (HHHHHH) +Size 68: 32,68 BAD (HHHHHH) +Size 69: 32,69 BAD (HHHHHH) +Size 70: 33,70 BAD (HHHHHH) +Size 71: 33,71 BAD (HHHHHH) +Size 72: 34,72 BAD (HHHHHH) +Size 73: 34,73 BAD (HHHHHH) +Size 74: 35,74 BAD (HHHHHH) +Size 75: 35,75 BAD (HHHHHH) +Size 76: 36,76 BAD (HHHHHH) +Size 77: 36,77 BAD (HHHHHH) +Size 78: 37,78 BAD (HHHHHH) +Size 79: 37,79 BAD (HHHHHH) +Size 80: 38,80 BAD (HHHHHH) +Size 81: 38,81 BAD (HHHHHH) +Size 82: 39,82 BAD (HHHHHH) +Size 83: 39,83 BAD (HHHHHH) +Size 84: 39,84 BAD (HHHHHH) +Size 85: 40,85 BAD (HHHHHH) +Size 86: 40,86 BAD (HHHHHH) +Size 87: 41,87 BAD (HHHHHH) +Size 88: 41,88 BAD (HHHHHH) +Size 89: 42,89 BAD (HHHHHH) +Size 90: 42,90 BAD (HHHHHH) +Size 91: 43,91 BAD (HHHHHH) +Size 92: 43,92 BAD (HHHHHH) +Size 93: 44,93 BAD (HHHHHH) +Size 94: 44,94 BAD (HHHHHH) +Size 95: 45,95 BAD (HHHHHH) +Size 96: 45,96 BAD (HHHHHH) +Size 97: 46,97 BAD (HHHHHH) +Size 98: 46,98 BAD (HHHHHH) +Size 99: 46,99 BAD (HHHHHH) +Size 100: 47,100 BAD (HHHHHH) + +Windows 10 14342 Old Console +---------------------------- + +Size 1: 1,3 BAD (HHHHHH) +Size 2: 1,2 BAD (HHHHHH) +Size 3: 1,3 BAD (HHHHHH) +Size 4: 2,4 BAD (HHHHHH) +Size 5: 2,5 BAD (HHHHHH) +Size 6: 3,6 BAD (HHHHHH) +Size 7: 3,6 BAD (HHHHHH) +Size 8: 4,8 BAD (HHHHHH) +Size 9: 4,9 BAD (HHHHHH) +Size 10: 5,10 BAD (HHHHHH) +Size 11: 5,11 BAD (HHHHHH) +Size 12: 6,12 BAD (HHHHHH) +Size 13: 6,13 BAD (HHHHHH) +Size 14: 7,14 BAD (HHHHHH) +Size 15: 7,15 BAD (HHHHHH) +Size 16: 8,16 BAD (HHHHHH) +Size 17: 8,17 BAD (HHHHHH) +Size 18: 8,18 BAD (HHHHHH) +Size 19: 9,19 BAD (HHHHHH) +Size 20: 9,20 BAD (HHHHHH) +Size 21: 10,22 BAD (HHHHHH) +Size 22: 10,22 BAD (HHHHHH) +Size 23: 11,23 BAD (HHHHHH) +Size 24: 11,24 BAD (HHHHHH) +Size 25: 12,25 BAD (HHHHHH) +Size 26: 12,26 BAD (HHHHHH) +Size 27: 13,27 BAD (HHHHHH) +Size 28: 13,28 BAD (HHHHHH) +Size 29: 14,29 BAD (HHHHHH) +Size 30: 14,30 BAD (HHHHHH) +Size 31: 15,31 BAD (HHHHHH) +Size 32: 15,32 BAD (HHHHHH) +Size 33: 15,33 BAD (HHHHHH) +Size 34: 16,34 BAD (HHHHHH) +Size 35: 16,36 BAD (HHHHHH) +Size 36: 17,36 BAD (HHHHHH) +Size 37: 17,37 BAD (HHHHHH) +Size 38: 18,38 BAD (HHHHHH) +Size 39: 18,39 BAD (HHHHHH) +Size 40: 19,40 BAD (HHHHHH) +Size 41: 19,41 BAD (HHHHHH) +Size 42: 20,42 BAD (HHHHHH) +Size 43: 20,43 BAD (HHHHHH) +Size 44: 21,44 BAD (HHHHHH) +Size 45: 21,45 BAD (HHHHHH) +Size 46: 22,46 BAD (HHHHHH) +Size 47: 22,47 BAD (HHHHHH) +Size 48: 23,48 BAD (HHHHHH) +Size 49: 23,49 BAD (HHHHHH) +Size 50: 23,50 BAD (HHHHHH) +Size 51: 24,51 BAD (HHHHHH) +Size 52: 24,52 BAD (HHHHHH) +Size 53: 25,53 BAD (HHHHHH) +Size 54: 25,54 BAD (HHHHHH) +Size 55: 26,55 BAD (HHHHHH) +Size 56: 26,56 BAD (HHHHHH) +Size 57: 27,57 BAD (HHHHHH) +Size 58: 27,58 BAD (HHHHHH) +Size 59: 28,59 BAD (HHHHHH) +Size 60: 28,60 BAD (HHHHHH) +Size 61: 29,61 BAD (HHHHHH) +Size 62: 29,62 BAD (HHHHHH) +Size 63: 30,64 BAD (HHHHHH) +Size 64: 30,64 BAD (HHHHHH) +Size 65: 31,65 BAD (HHHHHH) +Size 66: 31,66 BAD (HHHHHH) +Size 67: 31,67 BAD (HHHHHH) +Size 68: 32,68 BAD (HHHHHH) +Size 69: 32,69 BAD (HHHHHH) +Size 70: 33,70 BAD (HHHHHH) +Size 71: 33,71 BAD (HHHHHH) +Size 72: 34,72 BAD (HHHHHH) +Size 73: 34,73 BAD (HHHHHH) +Size 74: 35,74 BAD (HHHHHH) +Size 75: 35,75 BAD (HHHHHH) +Size 76: 36,76 BAD (HHHHHH) +Size 77: 36,77 BAD (HHHHHH) +Size 78: 37,78 BAD (HHHHHH) +Size 79: 37,79 BAD (HHHHHH) +Size 80: 38,80 BAD (HHHHHH) +Size 81: 38,81 BAD (HHHHHH) +Size 82: 39,82 BAD (HHHHHH) +Size 83: 39,83 BAD (HHHHHH) +Size 84: 39,84 BAD (HHHHHH) +Size 85: 40,85 BAD (HHHHHH) +Size 86: 40,86 BAD (HHHHHH) +Size 87: 41,87 BAD (HHHHHH) +Size 88: 41,88 BAD (HHHHHH) +Size 89: 42,89 BAD (HHHHHH) +Size 90: 42,90 BAD (HHHHHH) +Size 91: 43,91 BAD (HHHHHH) +Size 92: 43,92 BAD (HHHHHH) +Size 93: 44,93 BAD (HHHHHH) +Size 94: 44,94 BAD (HHHHHH) +Size 95: 45,95 BAD (HHHHHH) +Size 96: 45,96 BAD (HHHHHH) +Size 97: 46,97 BAD (HHHHHH) +Size 98: 46,98 BAD (HHHHHH) +Size 99: 46,99 BAD (HHHHHH) +Size 100: 47,100 BAD (HHHHHH) + +Windows 10 14342 New Console +---------------------------- + +Size 1: 1,1 BAD (HHHHHH) +Size 2: 1,2 BAD (HHHHHH) +Size 3: 1,3 BAD (HHHHHH) +Size 4: 2,4 BAD (HHHHHH) +Size 5: 2,5 BAD (HHHHHH) +Size 6: 3,6 BAD (HHHHHH) +Size 7: 3,7 BAD (HHHHHH) +Size 8: 4,8 BAD (HHHHHH) +Size 9: 4,9 BAD (HHHHHH) +Size 10: 5,10 BAD (HHHHHH) +Size 11: 5,11 BAD (HHHHHH) +Size 12: 6,12 BAD (HHHHHH) +Size 13: 6,13 BAD (HHHHHH) +Size 14: 7,14 BAD (HHHHHH) +Size 15: 7,15 BAD (HHHHHH) +Size 16: 8,16 BAD (HHHHHH) +Size 17: 8,17 BAD (HHHHHH) +Size 18: 8,18 BAD (HHHHHH) +Size 19: 9,19 BAD (HHHHHH) +Size 20: 9,20 BAD (HHHHHH) +Size 21: 10,21 BAD (HHHHHH) +Size 22: 10,22 BAD (HHHHHH) +Size 23: 11,23 BAD (HHHHHH) +Size 24: 11,24 BAD (HHHHHH) +Size 25: 12,25 BAD (HHHHHH) +Size 26: 12,26 BAD (HHHHHH) +Size 27: 13,27 BAD (HHHHHH) +Size 28: 13,28 BAD (HHHHHH) +Size 29: 14,29 BAD (HHHHHH) +Size 30: 14,30 BAD (HHHHHH) +Size 31: 15,31 BAD (HHHHHH) +Size 32: 15,32 BAD (HHHHHH) +Size 33: 15,33 BAD (HHHHHH) +Size 34: 16,34 BAD (HHHHHH) +Size 35: 16,35 BAD (HHHHHH) +Size 36: 17,36 BAD (HHHHHH) +Size 37: 17,37 BAD (HHHHHH) +Size 38: 18,38 BAD (HHHHHH) +Size 39: 18,39 BAD (HHHHHH) +Size 40: 19,40 BAD (HHHHHH) +Size 41: 19,41 BAD (HHHHHH) +Size 42: 20,42 BAD (HHHHHH) +Size 43: 20,43 BAD (HHHHHH) +Size 44: 21,44 BAD (HHHHHH) +Size 45: 21,45 BAD (HHHHHH) +Size 46: 22,46 BAD (HHHHHH) +Size 47: 22,47 BAD (HHHHHH) +Size 48: 23,48 BAD (HHHHHH) +Size 49: 23,49 BAD (HHHHHH) +Size 50: 23,50 BAD (HHHHHH) +Size 51: 24,51 BAD (HHHHHH) +Size 52: 24,52 BAD (HHHHHH) +Size 53: 25,53 BAD (HHHHHH) +Size 54: 25,54 BAD (HHHHHH) +Size 55: 26,55 BAD (HHHHHH) +Size 56: 26,56 BAD (HHHHHH) +Size 57: 27,57 BAD (HHHHHH) +Size 58: 27,58 BAD (HHHHHH) +Size 59: 28,59 BAD (HHHHHH) +Size 60: 28,60 BAD (HHHHHH) +Size 61: 29,61 BAD (HHHHHH) +Size 62: 29,62 BAD (HHHHHH) +Size 63: 30,63 BAD (HHHHHH) +Size 64: 30,64 BAD (HHHHHH) +Size 65: 31,65 BAD (HHHHHH) +Size 66: 31,66 BAD (HHHHHH) +Size 67: 31,67 BAD (HHHHHH) +Size 68: 32,68 BAD (HHHHHH) +Size 69: 32,69 BAD (HHHHHH) +Size 70: 33,70 BAD (HHHHHH) +Size 71: 33,71 BAD (HHHHHH) +Size 72: 34,72 BAD (HHHHHH) +Size 73: 34,73 BAD (HHHHHH) +Size 74: 35,74 BAD (HHHHHH) +Size 75: 35,75 BAD (HHHHHH) +Size 76: 36,76 BAD (HHHHHH) +Size 77: 36,77 BAD (HHHHHH) +Size 78: 37,78 BAD (HHHHHH) +Size 79: 37,79 BAD (HHHHHH) +Size 80: 38,80 BAD (HHHHHH) +Size 81: 38,81 BAD (HHHHHH) +Size 82: 39,82 BAD (HHHHHH) +Size 83: 39,83 BAD (HHHHHH) +Size 84: 39,84 BAD (HHHHHH) +Size 85: 40,85 BAD (HHHHHH) +Size 86: 40,86 BAD (HHHHHH) +Size 87: 41,87 BAD (HHHHHH) +Size 88: 41,88 BAD (HHHHHH) +Size 89: 42,89 BAD (HHHHHH) +Size 90: 42,90 BAD (HHHHHH) +Size 91: 43,91 BAD (HHHHHH) +Size 92: 43,92 BAD (HHHHHH) +Size 93: 44,93 BAD (HHHHHH) +Size 94: 44,94 BAD (HHHHHH) +Size 95: 45,95 BAD (HHHHHH) +Size 96: 45,96 BAD (HHHHHH) +Size 97: 46,97 BAD (HHHHHH) +Size 98: 46,98 BAD (HHHHHH) +Size 99: 46,99 BAD (HHHHHH) +Size 100: 47,100 BAD (HHHHHH) diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Lucida.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Lucida.txt new file mode 100644 index 00000000000..0eed93ad989 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Lucida.txt @@ -0,0 +1,633 @@ +================================== +Code Page 437, Lucida Console font +================================== + +Options: -face "Lucida Console" -family 0x36 +Chars: A2 A3 2014 3044 30FC 4000 + +FontSurvey "-face \"Lucida Console\" -family 0x36" + +Vista +----- + +Size 1: 1,2 BAD (HHHHHH) +Size 2: 1,2 BAD (HHHHHH) +Size 3: 2,3 BAD (HHHHHH) +Size 4: 2,4 BAD (HHHHHH) +Size 5: 3,5 BAD (HHHHHH) +Size 6: 4,6 BAD (HHHHHH) +Size 7: 4,7 BAD (HHHHHH) +Size 8: 5,8 BAD (HHHHHH) +Size 9: 5,9 BAD (HHHHHH) +Size 10: 6,10 BAD (HHHHHH) +Size 11: 7,11 BAD (HHHHHH) +Size 12: 7,12 BAD (HHHHHH) +Size 13: 8,13 BAD (HHHHHH) +Size 14: 8,14 BAD (HHHHHH) +Size 15: 9,15 BAD (HHHHHH) +Size 16: 10,16 BAD (HHHHHH) +Size 17: 10,17 BAD (HHHHHH) +Size 18: 11,18 BAD (HHHHHH) +Size 19: 11,19 BAD (HHHHHH) +Size 20: 12,20 BAD (HHHHHH) +Size 21: 13,21 BAD (HHHHHH) +Size 22: 13,22 BAD (HHHHHH) +Size 23: 14,23 BAD (HHHHHH) +Size 24: 14,24 BAD (HHHHHH) +Size 25: 15,25 BAD (HHHHHH) +Size 26: 16,26 BAD (HHHHHH) +Size 27: 16,27 BAD (HHHHHH) +Size 28: 17,28 BAD (HHHHHH) +Size 29: 17,29 BAD (HHHHHH) +Size 30: 18,30 BAD (HHHHHH) +Size 31: 19,31 BAD (HHHHHH) +Size 32: 19,32 BAD (HHHHHH) +Size 33: 20,33 BAD (HHHHHH) +Size 34: 20,34 BAD (HHHHHH) +Size 35: 21,35 BAD (HHHHHH) +Size 36: 22,36 BAD (HHHHHH) +Size 37: 22,37 BAD (HHHHHH) +Size 38: 23,38 BAD (HHHHHH) +Size 39: 23,39 BAD (HHHHHH) +Size 40: 24,40 BAD (HHHHHH) +Size 41: 25,41 BAD (HHHHHH) +Size 42: 25,42 BAD (HHHHHH) +Size 43: 26,43 BAD (HHHHHH) +Size 44: 27,44 BAD (HHHHHH) +Size 45: 27,45 BAD (HHHHHH) +Size 46: 28,46 BAD (HHHHHH) +Size 47: 28,47 BAD (HHHHHH) +Size 48: 29,48 BAD (HHHHHH) +Size 49: 30,49 BAD (HHHHHH) +Size 50: 30,50 BAD (HHHHHH) +Size 51: 31,51 BAD (HHHHHH) +Size 52: 31,52 BAD (HHHHHH) +Size 53: 32,53 BAD (HHHHHH) +Size 54: 33,54 BAD (HHHHHH) +Size 55: 33,55 BAD (HHHHHH) +Size 56: 34,56 BAD (HHHHHH) +Size 57: 34,57 BAD (HHHHHH) +Size 58: 35,58 BAD (HHHHHH) +Size 59: 36,59 BAD (HHHHHH) +Size 60: 36,60 BAD (HHHHHH) +Size 61: 37,61 BAD (HHHHHH) +Size 62: 37,62 BAD (HHHHHH) +Size 63: 38,63 BAD (HHHHHH) +Size 64: 39,65 BAD (HHHHHH) +Size 65: 39,65 BAD (HHHHHH) +Size 66: 40,66 BAD (HHHHHH) +Size 67: 40,67 BAD (HHHHHH) +Size 68: 41,68 BAD (HHHHHH) +Size 69: 42,69 BAD (HHHHHH) +Size 70: 42,70 BAD (HHHHHH) +Size 71: 43,71 BAD (HHHHHH) +Size 72: 43,72 BAD (HHHHHH) +Size 73: 44,73 BAD (HHHHHH) +Size 74: 45,74 BAD (HHHHHH) +Size 75: 45,75 BAD (HHHHHH) +Size 76: 46,76 BAD (HHHHHH) +Size 77: 46,77 BAD (HHHHHH) +Size 78: 47,78 BAD (HHHHHH) +Size 79: 48,79 BAD (HHHHHH) +Size 80: 48,80 BAD (HHHHHH) +Size 81: 49,81 BAD (HHHHHH) +Size 82: 49,82 BAD (HHHHHH) +Size 83: 50,83 BAD (HHHHHH) +Size 84: 51,84 BAD (HHHHHH) +Size 85: 51,85 BAD (HHHHHH) +Size 86: 52,86 BAD (HHHHHH) +Size 87: 52,87 BAD (HHHHHH) +Size 88: 53,88 BAD (HHHHHH) +Size 89: 54,89 BAD (HHHHHH) +Size 90: 54,90 BAD (HHHHHH) +Size 91: 55,91 BAD (HHHHHH) +Size 92: 55,92 BAD (HHHHHH) +Size 93: 56,93 BAD (HHHHHH) +Size 94: 57,94 BAD (HHHHHH) +Size 95: 57,95 BAD (HHHHHH) +Size 96: 58,96 BAD (HHHHHH) +Size 97: 58,97 BAD (HHHHHH) +Size 98: 59,98 BAD (HHHHHH) +Size 99: 60,99 BAD (HHHHHH) +Size 100: 60,100 BAD (HHHHHH) + + +Windows 7 +--------- + +Size 1: 1,2 BAD (HHHHHH) +Size 2: 1,2 BAD (HHHHHH) +Size 3: 2,3 BAD (HHHHHH) +Size 4: 2,4 BAD (HHHHHH) +Size 5: 3,5 BAD (HHHHHH) +Size 6: 4,6 BAD (HHHHHH) +Size 7: 4,7 BAD (HHHHHH) +Size 8: 5,8 BAD (HHHHHH) +Size 9: 5,9 BAD (HHHHHH) +Size 10: 6,10 BAD (HHHHHH) +Size 11: 7,11 BAD (HHHHHH) +Size 12: 7,12 BAD (HHHHHH) +Size 13: 8,13 BAD (HHHHHH) +Size 14: 8,14 BAD (HHHHHH) +Size 15: 9,15 BAD (HHHHHH) +Size 16: 10,16 BAD (HHHHHH) +Size 17: 10,17 BAD (HHHHHH) +Size 18: 11,18 BAD (HHHHHH) +Size 19: 11,19 BAD (HHHHHH) +Size 20: 12,20 BAD (HHHHHH) +Size 21: 13,21 BAD (HHHHHH) +Size 22: 13,22 BAD (HHHHHH) +Size 23: 14,23 BAD (HHHHHH) +Size 24: 14,24 BAD (HHHHHH) +Size 25: 15,25 BAD (HHHHHH) +Size 26: 16,26 BAD (HHHHHH) +Size 27: 16,27 BAD (HHHHHH) +Size 28: 17,28 BAD (HHHHHH) +Size 29: 17,29 BAD (HHHHHH) +Size 30: 18,30 BAD (HHHHHH) +Size 31: 19,31 BAD (HHHHHH) +Size 32: 19,32 BAD (HHHHHH) +Size 33: 20,33 BAD (HHHHHH) +Size 34: 20,34 BAD (HHHHHH) +Size 35: 21,35 BAD (HHHHHH) +Size 36: 22,36 BAD (HHHHHH) +Size 37: 22,37 BAD (HHHHHH) +Size 38: 23,38 BAD (HHHHHH) +Size 39: 23,39 BAD (HHHHHH) +Size 40: 24,40 BAD (HHHHHH) +Size 41: 25,41 BAD (HHHHHH) +Size 42: 25,42 BAD (HHHHHH) +Size 43: 26,43 BAD (HHHHHH) +Size 44: 27,44 BAD (HHHHHH) +Size 45: 27,45 BAD (HHHHHH) +Size 46: 28,46 BAD (HHHHHH) +Size 47: 28,47 BAD (HHHHHH) +Size 48: 29,48 BAD (HHHHHH) +Size 49: 30,49 BAD (HHHHHH) +Size 50: 30,50 BAD (HHHHHH) +Size 51: 31,51 BAD (HHHHHH) +Size 52: 31,52 BAD (HHHHHH) +Size 53: 32,53 BAD (HHHHHH) +Size 54: 33,54 BAD (HHHHHH) +Size 55: 33,55 BAD (HHHHHH) +Size 56: 34,56 BAD (HHHHHH) +Size 57: 34,57 BAD (HHHHHH) +Size 58: 35,58 BAD (HHHHHH) +Size 59: 36,59 BAD (HHHHHH) +Size 60: 36,60 BAD (HHHHHH) +Size 61: 37,61 BAD (HHHHHH) +Size 62: 37,62 BAD (HHHHHH) +Size 63: 38,63 BAD (HHHHHH) +Size 64: 39,65 BAD (HHHHHH) +Size 65: 39,65 BAD (HHHHHH) +Size 66: 40,66 BAD (HHHHHH) +Size 67: 40,67 BAD (HHHHHH) +Size 68: 41,68 BAD (HHHHHH) +Size 69: 42,69 BAD (HHHHHH) +Size 70: 42,70 BAD (HHHHHH) +Size 71: 43,71 BAD (HHHHHH) +Size 72: 43,72 BAD (HHHHHH) +Size 73: 44,73 BAD (HHHHHH) +Size 74: 45,74 BAD (HHHHHH) +Size 75: 45,75 BAD (HHHHHH) +Size 76: 46,76 BAD (HHHHHH) +Size 77: 46,77 BAD (HHHHHH) +Size 78: 47,78 BAD (HHHHHH) +Size 79: 48,79 BAD (HHHHHH) +Size 80: 48,80 BAD (HHHHHH) +Size 81: 49,81 BAD (HHHHHH) +Size 82: 49,82 BAD (HHHHHH) +Size 83: 50,83 BAD (HHHHHH) +Size 84: 51,84 BAD (HHHHHH) +Size 85: 51,85 BAD (HHHHHH) +Size 86: 52,86 BAD (HHHHHH) +Size 87: 52,87 BAD (HHHHHH) +Size 88: 53,88 BAD (HHHHHH) +Size 89: 54,89 BAD (HHHHHH) +Size 90: 54,90 BAD (HHHHHH) +Size 91: 55,91 BAD (HHHHHH) +Size 92: 55,92 BAD (HHHHHH) +Size 93: 56,93 BAD (HHHHHH) +Size 94: 57,94 BAD (HHHHHH) +Size 95: 57,95 BAD (HHHHHH) +Size 96: 58,96 BAD (HHHHHH) +Size 97: 58,97 BAD (HHHHHH) +Size 98: 59,98 BAD (HHHHHH) +Size 99: 60,99 BAD (HHHHHH) +Size 100: 60,100 BAD (HHHHHH) + +Windows 8 +--------- + +Size 1: 1,2 BAD (HHHHHH) +Size 2: 1,2 BAD (HHHHHH) +Size 3: 2,3 BAD (HHHHHH) +Size 4: 2,4 BAD (HHHHHH) +Size 5: 3,5 BAD (HHHHHH) +Size 6: 4,6 BAD (HHHHHH) +Size 7: 4,7 BAD (HHHHHH) +Size 8: 5,8 BAD (HHHHHH) +Size 9: 5,9 BAD (HHHHHH) +Size 10: 6,10 BAD (HHHHHH) +Size 11: 7,11 BAD (HHHHHH) +Size 12: 7,12 BAD (HHHHHH) +Size 13: 8,13 BAD (HHHHHH) +Size 14: 8,14 BAD (HHHHHH) +Size 15: 9,15 BAD (HHHHHH) +Size 16: 10,16 BAD (HHHHHH) +Size 17: 10,17 BAD (HHHHHH) +Size 18: 11,18 BAD (HHHHHH) +Size 19: 11,19 BAD (HHHHHH) +Size 20: 12,20 BAD (HHHHHH) +Size 21: 13,21 BAD (HHHHHH) +Size 22: 13,22 BAD (HHHHHH) +Size 23: 14,23 BAD (HHHHHH) +Size 24: 14,24 BAD (HHHHHH) +Size 25: 15,25 BAD (HHHHHH) +Size 26: 16,26 BAD (HHHHHH) +Size 27: 16,27 BAD (HHHHHH) +Size 28: 17,28 BAD (HHHHHH) +Size 29: 17,29 BAD (HHHHHH) +Size 30: 18,30 BAD (HHHHHH) +Size 31: 19,31 BAD (HHHHHH) +Size 32: 19,32 BAD (HHHHHH) +Size 33: 20,33 BAD (HHHHHH) +Size 34: 20,34 BAD (HHHHHH) +Size 35: 21,35 BAD (HHHHHH) +Size 36: 22,36 BAD (HHHHHH) +Size 37: 22,37 BAD (HHHHHH) +Size 38: 23,38 BAD (HHHHHH) +Size 39: 23,39 BAD (HHHHHH) +Size 40: 24,40 BAD (HHHHHH) +Size 41: 25,41 BAD (HHHHHH) +Size 42: 25,42 BAD (HHHHHH) +Size 43: 26,43 BAD (HHHHHH) +Size 44: 27,44 BAD (HHHHHH) +Size 45: 27,45 BAD (HHHHHH) +Size 46: 28,46 BAD (HHHHHH) +Size 47: 28,47 BAD (HHHHHH) +Size 48: 29,48 BAD (HHHHHH) +Size 49: 30,49 BAD (HHHHHH) +Size 50: 30,50 BAD (HHHHHH) +Size 51: 31,51 BAD (HHHHHH) +Size 52: 31,52 BAD (HHHHHH) +Size 53: 32,53 BAD (HHHHHH) +Size 54: 33,54 BAD (HHHHHH) +Size 55: 33,55 BAD (HHHHHH) +Size 56: 34,56 BAD (HHHHHH) +Size 57: 34,57 BAD (HHHHHH) +Size 58: 35,58 BAD (HHHHHH) +Size 59: 36,59 BAD (HHHHHH) +Size 60: 36,60 BAD (HHHHHH) +Size 61: 37,61 BAD (HHHHHH) +Size 62: 37,62 BAD (HHHHHH) +Size 63: 38,63 BAD (HHHHHH) +Size 64: 39,65 BAD (HHHHHH) +Size 65: 39,65 BAD (HHHHHH) +Size 66: 40,66 BAD (HHHHHH) +Size 67: 40,67 BAD (HHHHHH) +Size 68: 41,68 BAD (HHHHHH) +Size 69: 42,69 BAD (HHHHHH) +Size 70: 42,70 BAD (HHHHHH) +Size 71: 43,71 BAD (HHHHHH) +Size 72: 43,72 BAD (HHHHHH) +Size 73: 44,73 BAD (HHHHHH) +Size 74: 45,74 BAD (HHHHHH) +Size 75: 45,75 BAD (HHHHHH) +Size 76: 46,76 BAD (HHHHHH) +Size 77: 46,77 BAD (HHHHHH) +Size 78: 47,78 BAD (HHHHHH) +Size 79: 48,79 BAD (HHHHHH) +Size 80: 48,80 BAD (HHHHHH) +Size 81: 49,81 BAD (HHHHHH) +Size 82: 49,82 BAD (HHHHHH) +Size 83: 50,83 BAD (HHHHHH) +Size 84: 51,84 BAD (HHHHHH) +Size 85: 51,85 BAD (HHHHHH) +Size 86: 52,86 BAD (HHHHHH) +Size 87: 52,87 BAD (HHHHHH) +Size 88: 53,88 BAD (HHHHHH) +Size 89: 54,89 BAD (HHHHHH) +Size 90: 54,90 BAD (HHHHHH) +Size 91: 55,91 BAD (HHHHHH) +Size 92: 55,92 BAD (HHHHHH) +Size 93: 56,93 BAD (HHHHHH) +Size 94: 57,94 BAD (HHHHHH) +Size 95: 57,95 BAD (HHHHHH) +Size 96: 58,96 BAD (HHHHHH) +Size 97: 58,97 BAD (HHHHHH) +Size 98: 59,98 BAD (HHHHHH) +Size 99: 60,99 BAD (HHHHHH) +Size 100: 60,100 BAD (HHHHHH) + +Windows 8.1 +----------- + +Size 1: 1,2 BAD (HHHHHH) +Size 2: 1,2 BAD (HHHHHH) +Size 3: 2,3 BAD (HHHHHH) +Size 4: 2,4 BAD (HHHHHH) +Size 5: 3,5 BAD (HHHHHH) +Size 6: 4,6 BAD (HHHHHH) +Size 7: 4,7 BAD (HHHHHH) +Size 8: 5,8 BAD (HHHHHH) +Size 9: 5,9 BAD (HHHHHH) +Size 10: 6,10 BAD (HHHHHH) +Size 11: 7,11 BAD (HHHHHH) +Size 12: 7,12 BAD (HHHHHH) +Size 13: 8,13 BAD (HHHHHH) +Size 14: 8,14 BAD (HHHHHH) +Size 15: 9,15 BAD (HHHHHH) +Size 16: 10,16 BAD (HHHHHH) +Size 17: 10,17 BAD (HHHHHH) +Size 18: 11,18 BAD (HHHHHH) +Size 19: 11,19 BAD (HHHHHH) +Size 20: 12,20 BAD (HHHHHH) +Size 21: 13,21 BAD (HHHHHH) +Size 22: 13,22 BAD (HHHHHH) +Size 23: 14,23 BAD (HHHHHH) +Size 24: 14,24 BAD (HHHHHH) +Size 25: 15,25 BAD (HHHHHH) +Size 26: 16,26 BAD (HHHHHH) +Size 27: 16,27 BAD (HHHHHH) +Size 28: 17,28 BAD (HHHHHH) +Size 29: 17,29 BAD (HHHHHH) +Size 30: 18,30 BAD (HHHHHH) +Size 31: 19,31 BAD (HHHHHH) +Size 32: 19,32 BAD (HHHHHH) +Size 33: 20,33 BAD (HHHHHH) +Size 34: 20,34 BAD (HHHHHH) +Size 35: 21,35 BAD (HHHHHH) +Size 36: 22,36 BAD (HHHHHH) +Size 37: 22,37 BAD (HHHHHH) +Size 38: 23,38 BAD (HHHHHH) +Size 39: 23,39 BAD (HHHHHH) +Size 40: 24,40 BAD (HHHHHH) +Size 41: 25,41 BAD (HHHHHH) +Size 42: 25,42 BAD (HHHHHH) +Size 43: 26,43 BAD (HHHHHH) +Size 44: 27,44 BAD (HHHHHH) +Size 45: 27,45 BAD (HHHHHH) +Size 46: 28,46 BAD (HHHHHH) +Size 47: 28,47 BAD (HHHHHH) +Size 48: 29,48 BAD (HHHHHH) +Size 49: 30,49 BAD (HHHHHH) +Size 50: 30,50 BAD (HHHHHH) +Size 51: 31,51 BAD (HHHHHH) +Size 52: 31,52 BAD (HHHHHH) +Size 53: 32,53 BAD (HHHHHH) +Size 54: 33,54 BAD (HHHHHH) +Size 55: 33,55 BAD (HHHHHH) +Size 56: 34,56 BAD (HHHHHH) +Size 57: 34,57 BAD (HHHHHH) +Size 58: 35,58 BAD (HHHHHH) +Size 59: 36,59 BAD (HHHHHH) +Size 60: 36,60 BAD (HHHHHH) +Size 61: 37,61 BAD (HHHHHH) +Size 62: 37,62 BAD (HHHHHH) +Size 63: 38,63 BAD (HHHHHH) +Size 64: 39,65 BAD (HHHHHH) +Size 65: 39,65 BAD (HHHHHH) +Size 66: 40,66 BAD (HHHHHH) +Size 67: 40,67 BAD (HHHHHH) +Size 68: 41,68 BAD (HHHHHH) +Size 69: 42,69 BAD (HHHHHH) +Size 70: 42,70 BAD (HHHHHH) +Size 71: 43,71 BAD (HHHHHH) +Size 72: 43,72 BAD (HHHHHH) +Size 73: 44,73 BAD (HHHHHH) +Size 74: 45,74 BAD (HHHHHH) +Size 75: 45,75 BAD (HHHHHH) +Size 76: 46,76 BAD (HHHHHH) +Size 77: 46,77 BAD (HHHHHH) +Size 78: 47,78 BAD (HHHHHH) +Size 79: 48,79 BAD (HHHHHH) +Size 80: 48,80 BAD (HHHHHH) +Size 81: 49,81 BAD (HHHHHH) +Size 82: 49,82 BAD (HHHHHH) +Size 83: 50,83 BAD (HHHHHH) +Size 84: 51,84 BAD (HHHHHH) +Size 85: 51,85 BAD (HHHHHH) +Size 86: 52,86 BAD (HHHHHH) +Size 87: 52,87 BAD (HHHHHH) +Size 88: 53,88 BAD (HHHHHH) +Size 89: 54,89 BAD (HHHHHH) +Size 90: 54,90 BAD (HHHHHH) +Size 91: 55,91 BAD (HHHHHH) +Size 92: 55,92 BAD (HHHHHH) +Size 93: 56,93 BAD (HHHHHH) +Size 94: 57,94 BAD (HHHHHH) +Size 95: 57,95 BAD (HHHHHH) +Size 96: 58,96 BAD (HHHHHH) +Size 97: 58,97 BAD (HHHHHH) +Size 98: 59,98 BAD (HHHHHH) +Size 99: 60,99 BAD (HHHHHH) +Size 100: 60,100 BAD (HHHHHH) + +Windows 10 14342 Old Console +---------------------------- + +Size 1: 1,2 BAD (HHHHHH) +Size 2: 1,2 BAD (HHHHHH) +Size 3: 2,3 BAD (HHHHHH) +Size 4: 2,4 BAD (HHHHHH) +Size 5: 3,5 BAD (HHHHHH) +Size 6: 4,6 BAD (HHHHHH) +Size 7: 4,7 BAD (HHHHHH) +Size 8: 5,8 BAD (HHHHHH) +Size 9: 5,9 BAD (HHHHHH) +Size 10: 6,10 BAD (HHHHHH) +Size 11: 7,11 BAD (HHHHHH) +Size 12: 7,12 BAD (HHHHHH) +Size 13: 8,13 BAD (HHHHHH) +Size 14: 8,14 BAD (HHHHHH) +Size 15: 9,15 BAD (HHHHHH) +Size 16: 10,16 BAD (HHHHHH) +Size 17: 10,17 BAD (HHHHHH) +Size 18: 11,18 BAD (HHHHHH) +Size 19: 11,19 BAD (HHHHHH) +Size 20: 12,20 BAD (HHHHHH) +Size 21: 13,21 BAD (HHHHHH) +Size 22: 13,22 BAD (HHHHHH) +Size 23: 14,23 BAD (HHHHHH) +Size 24: 14,24 BAD (HHHHHH) +Size 25: 15,25 BAD (HHHHHH) +Size 26: 16,26 BAD (HHHHHH) +Size 27: 16,27 BAD (HHHHHH) +Size 28: 17,28 BAD (HHHHHH) +Size 29: 17,29 BAD (HHHHHH) +Size 30: 18,30 BAD (HHHHHH) +Size 31: 19,31 BAD (HHHHHH) +Size 32: 19,32 BAD (HHHHHH) +Size 33: 20,33 BAD (HHHHHH) +Size 34: 20,34 BAD (HHHHHH) +Size 35: 21,35 BAD (HHHHHH) +Size 36: 22,36 BAD (HHHHHH) +Size 37: 22,37 BAD (HHHHHH) +Size 38: 23,38 BAD (HHHHHH) +Size 39: 23,39 BAD (HHHHHH) +Size 40: 24,40 BAD (HHHHHH) +Size 41: 25,41 BAD (HHHHHH) +Size 42: 25,42 BAD (HHHHHH) +Size 43: 26,43 BAD (HHHHHH) +Size 44: 27,44 BAD (HHHHHH) +Size 45: 27,45 BAD (HHHHHH) +Size 46: 28,46 BAD (HHHHHH) +Size 47: 28,47 BAD (HHHHHH) +Size 48: 29,48 BAD (HHHHHH) +Size 49: 30,49 BAD (HHHHHH) +Size 50: 30,50 BAD (HHHHHH) +Size 51: 31,51 BAD (HHHHHH) +Size 52: 31,52 BAD (HHHHHH) +Size 53: 32,53 BAD (HHHHHH) +Size 54: 33,54 BAD (HHHHHH) +Size 55: 33,55 BAD (HHHHHH) +Size 56: 34,56 BAD (HHHHHH) +Size 57: 34,57 BAD (HHHHHH) +Size 58: 35,58 BAD (HHHHHH) +Size 59: 36,59 BAD (HHHHHH) +Size 60: 36,60 BAD (HHHHHH) +Size 61: 37,61 BAD (HHHHHH) +Size 62: 37,62 BAD (HHHHHH) +Size 63: 38,63 BAD (HHHHHH) +Size 64: 39,65 BAD (HHHHHH) +Size 65: 39,65 BAD (HHHHHH) +Size 66: 40,66 BAD (HHHHHH) +Size 67: 40,67 BAD (HHHHHH) +Size 68: 41,68 BAD (HHHHHH) +Size 69: 42,69 BAD (HHHHHH) +Size 70: 42,70 BAD (HHHHHH) +Size 71: 43,71 BAD (HHHHHH) +Size 72: 43,72 BAD (HHHHHH) +Size 73: 44,73 BAD (HHHHHH) +Size 74: 45,74 BAD (HHHHHH) +Size 75: 45,75 BAD (HHHHHH) +Size 76: 46,76 BAD (HHHHHH) +Size 77: 46,77 BAD (HHHHHH) +Size 78: 47,78 BAD (HHHHHH) +Size 79: 48,79 BAD (HHHHHH) +Size 80: 48,80 BAD (HHHHHH) +Size 81: 49,81 BAD (HHHHHH) +Size 82: 49,82 BAD (HHHHHH) +Size 83: 50,83 BAD (HHHHHH) +Size 84: 51,84 BAD (HHHHHH) +Size 85: 51,85 BAD (HHHHHH) +Size 86: 52,86 BAD (HHHHHH) +Size 87: 52,87 BAD (HHHHHH) +Size 88: 53,88 BAD (HHHHHH) +Size 89: 54,89 BAD (HHHHHH) +Size 90: 54,90 BAD (HHHHHH) +Size 91: 55,91 BAD (HHHHHH) +Size 92: 55,92 BAD (HHHHHH) +Size 93: 56,93 BAD (HHHHHH) +Size 94: 57,94 BAD (HHHHHH) +Size 95: 57,95 BAD (HHHHHH) +Size 96: 58,96 BAD (HHHHHH) +Size 97: 58,97 BAD (HHHHHH) +Size 98: 59,98 BAD (HHHHHH) +Size 99: 60,99 BAD (HHHHHH) +Size 100: 60,100 BAD (HHHHHH) + +Windows 10 14342 New Console +---------------------------- + +Size 1: 1,1 BAD (HHHHHH) +Size 2: 1,2 BAD (HHHHHH) +Size 3: 2,3 BAD (HHHHHH) +Size 4: 2,4 BAD (HHHHHH) +Size 5: 3,5 BAD (HHHHHH) +Size 6: 4,6 BAD (HHHHHH) +Size 7: 4,7 BAD (HHHHHH) +Size 8: 5,8 BAD (HHHHHH) +Size 9: 5,9 BAD (HHHHHH) +Size 10: 6,10 BAD (HHHHHH) +Size 11: 7,11 BAD (HHHHHH) +Size 12: 7,12 BAD (HHHHHH) +Size 13: 8,13 BAD (HHHHHH) +Size 14: 8,14 BAD (HHHHHH) +Size 15: 9,15 BAD (HHHHHH) +Size 16: 10,16 BAD (HHHHHH) +Size 17: 10,17 BAD (HHHHHH) +Size 18: 11,18 BAD (HHHHHH) +Size 19: 11,19 BAD (HHHHHH) +Size 20: 12,20 BAD (HHHHHH) +Size 21: 13,21 BAD (HHHHHH) +Size 22: 13,22 BAD (HHHHHH) +Size 23: 14,23 BAD (HHHHHH) +Size 24: 14,24 BAD (HHHHHH) +Size 25: 15,25 BAD (HHHHHH) +Size 26: 16,26 BAD (HHHHHH) +Size 27: 16,27 BAD (HHHHHH) +Size 28: 17,28 BAD (HHHHHH) +Size 29: 17,29 BAD (HHHHHH) +Size 30: 18,30 BAD (HHHHHH) +Size 31: 19,31 BAD (HHHHHH) +Size 32: 19,32 BAD (HHHHHH) +Size 33: 20,33 BAD (HHHHHH) +Size 34: 20,34 BAD (HHHHHH) +Size 35: 21,35 BAD (HHHHHH) +Size 36: 22,36 BAD (HHHHHH) +Size 37: 22,37 BAD (HHHHHH) +Size 38: 23,38 BAD (HHHHHH) +Size 39: 23,39 BAD (HHHHHH) +Size 40: 24,40 BAD (HHHHHH) +Size 41: 25,41 BAD (HHHHHH) +Size 42: 25,42 BAD (HHHHHH) +Size 43: 26,43 BAD (HHHHHH) +Size 44: 27,44 BAD (HHHHHH) +Size 45: 27,45 BAD (HHHHHH) +Size 46: 28,46 BAD (HHHHHH) +Size 47: 28,47 BAD (HHHHHH) +Size 48: 29,48 BAD (HHHHHH) +Size 49: 30,49 BAD (HHHHHH) +Size 50: 30,50 BAD (HHHHHH) +Size 51: 31,51 BAD (HHHHHH) +Size 52: 31,52 BAD (HHHHHH) +Size 53: 32,53 BAD (HHHHHH) +Size 54: 33,54 BAD (HHHHHH) +Size 55: 33,55 BAD (HHHHHH) +Size 56: 34,56 BAD (HHHHHH) +Size 57: 34,57 BAD (HHHHHH) +Size 58: 35,58 BAD (HHHHHH) +Size 59: 36,59 BAD (HHHHHH) +Size 60: 36,60 BAD (HHHHHH) +Size 61: 37,61 BAD (HHHHHH) +Size 62: 37,62 BAD (HHHHHH) +Size 63: 38,63 BAD (HHHHHH) +Size 64: 39,64 BAD (HHHHHH) +Size 65: 39,65 BAD (HHHHHH) +Size 66: 40,66 BAD (HHHHHH) +Size 67: 40,67 BAD (HHHHHH) +Size 68: 41,68 BAD (HHHHHH) +Size 69: 42,69 BAD (HHHHHH) +Size 70: 42,70 BAD (HHHHHH) +Size 71: 43,71 BAD (HHHHHH) +Size 72: 43,72 BAD (HHHHHH) +Size 73: 44,73 BAD (HHHHHH) +Size 74: 45,74 BAD (HHHHHH) +Size 75: 45,75 BAD (HHHHHH) +Size 76: 46,76 BAD (HHHHHH) +Size 77: 46,77 BAD (HHHHHH) +Size 78: 47,78 BAD (HHHHHH) +Size 79: 48,79 BAD (HHHHHH) +Size 80: 48,80 BAD (HHHHHH) +Size 81: 49,81 BAD (HHHHHH) +Size 82: 49,82 BAD (HHHHHH) +Size 83: 50,83 BAD (HHHHHH) +Size 84: 51,84 BAD (HHHHHH) +Size 85: 51,85 BAD (HHHHHH) +Size 86: 52,86 BAD (HHHHHH) +Size 87: 52,87 BAD (HHHHHH) +Size 88: 53,88 BAD (HHHHHH) +Size 89: 54,89 BAD (HHHHHH) +Size 90: 54,90 BAD (HHHHHH) +Size 91: 55,91 BAD (HHHHHH) +Size 92: 55,92 BAD (HHHHHH) +Size 93: 56,93 BAD (HHHHHH) +Size 94: 57,94 BAD (HHHHHH) +Size 95: 57,95 BAD (HHHHHH) +Size 96: 58,96 BAD (HHHHHH) +Size 97: 58,97 BAD (HHHHHH) +Size 98: 59,98 BAD (HHHHHH) +Size 99: 60,99 BAD (HHHHHH) +Size 100: 60,100 BAD (HHHHHH) diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP932.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP932.txt new file mode 100644 index 00000000000..ed3637eac1f --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP932.txt @@ -0,0 +1,630 @@ +======================================= +Code Page 932, Japanese, MS Gothic font +======================================= + +Options: -face-gothic -family 0x36 +Chars: A2 A3 2014 3044 30FC 4000 + +Vista +----- + +Size 1: 1,2 OK (HHHFFF) +Size 2: 1,2 OK (HHHFFF) +Size 3: 2,3 BAD (FFFFHH) +Size 4: 2,4 OK (HHHFFF) +Size 5: 3,5 OK (HHHFFF) +Size 6: 3,6 OK (HHHFFF) +Size 7: 4,7 OK (HHHFFF) +Size 8: 4,8 OK (HHHFFF) +Size 9: 5,9 OK (HHHFFF) +Size 10: 5,10 OK (HHHFFF) +Size 11: 6,11 OK (HHHFFF) +Size 12: 6,12 OK (HHHFFF) +Size 13: 7,13 OK (HHHFFF) +Size 14: 7,14 BAD (HHHFHH) +Size 15: 8,15 OK (HHHFFF) +Size 16: 8,16 BAD (HHHFHH) +Size 17: 9,17 OK (HHHFFF) +Size 18: 9,18 BAD (HHHFHH) +Size 19: 10,19 OK (HHHFFF) +Size 20: 10,20 BAD (HHHFHH) +Size 21: 11,21 OK (HHHFFF) +Size 22: 11,22 BAD (HHHFHH) +Size 23: 12,23 BAD (HHHFHH) +Size 24: 12,24 BAD (HHHFHH) +Size 25: 13,25 BAD (HHHFHH) +Size 26: 13,26 BAD (HHHFHH) +Size 27: 14,27 BAD (HHHFHH) +Size 28: 14,28 BAD (HHHFHH) +Size 29: 15,29 BAD (HHHFHH) +Size 30: 15,30 BAD (HHHFHH) +Size 31: 16,31 BAD (HHHFHH) +Size 32: 16,33 BAD (HHHFHH) +Size 33: 17,33 BAD (HHHFHH) +Size 34: 17,34 BAD (HHHFHH) +Size 35: 18,35 BAD (HHHFHH) +Size 36: 18,36 BAD (HHHFHH) +Size 37: 19,37 BAD (HHHFHH) +Size 38: 19,38 BAD (HHHFHH) +Size 39: 20,39 BAD (HHHFHH) +Size 40: 20,40 BAD (HHHFHH) +Size 41: 21,41 BAD (HHHFHH) +Size 42: 21,42 BAD (HHHFHH) +Size 43: 22,43 BAD (HHHFHH) +Size 44: 22,44 BAD (HHHFHH) +Size 45: 23,45 BAD (HHHFHH) +Size 46: 23,46 BAD (HHHFHH) +Size 47: 24,47 BAD (HHHFHH) +Size 48: 24,48 BAD (HHHFHH) +Size 49: 25,49 BAD (HHHFHH) +Size 50: 25,50 BAD (HHHFHH) +Size 51: 26,51 BAD (HHHFHH) +Size 52: 26,52 BAD (HHHFHH) +Size 53: 27,53 BAD (HHHFHH) +Size 54: 27,54 BAD (HHHFHH) +Size 55: 28,55 BAD (HHHFHH) +Size 56: 28,56 BAD (HHHFHH) +Size 57: 29,57 BAD (HHHFHH) +Size 58: 29,58 BAD (HHHFHH) +Size 59: 30,59 BAD (HHHFHH) +Size 60: 30,60 BAD (HHHFHH) +Size 61: 31,61 BAD (HHHFHH) +Size 62: 31,62 BAD (HHHFHH) +Size 63: 32,63 BAD (HHHFHH) +Size 64: 32,64 BAD (HHHFHH) +Size 65: 33,65 BAD (HHHFHH) +Size 66: 33,66 BAD (HHHFHH) +Size 67: 34,67 BAD (HHHFHH) +Size 68: 34,68 BAD (HHHFHH) +Size 69: 35,69 BAD (HHHFHH) +Size 70: 35,70 BAD (HHHFHH) +Size 71: 36,71 BAD (HHHFHH) +Size 72: 36,72 BAD (HHHFHH) +Size 73: 37,73 BAD (HHHFHH) +Size 74: 37,74 BAD (HHHFHH) +Size 75: 38,75 BAD (HHHFHH) +Size 76: 38,76 BAD (HHHFHH) +Size 77: 39,77 BAD (HHHFHH) +Size 78: 39,78 BAD (HHHFHH) +Size 79: 40,79 BAD (HHHFHH) +Size 80: 40,80 BAD (HHHFHH) +Size 81: 41,81 BAD (HHHFHH) +Size 82: 41,82 BAD (HHHFHH) +Size 83: 42,83 BAD (HHHFHH) +Size 84: 42,84 BAD (HHHFHH) +Size 85: 43,85 BAD (HHHFHH) +Size 86: 43,86 BAD (HHHFHH) +Size 87: 44,87 BAD (HHHFHH) +Size 88: 44,88 BAD (HHHFHH) +Size 89: 45,89 BAD (HHHFHH) +Size 90: 45,90 BAD (HHHFHH) +Size 91: 46,91 BAD (HHHFHH) +Size 92: 46,92 BAD (HHHFHH) +Size 93: 47,93 BAD (HHHFHH) +Size 94: 47,94 BAD (HHHFHH) +Size 95: 48,95 BAD (HHHFHH) +Size 96: 48,97 BAD (HHHFHH) +Size 97: 49,97 BAD (HHHFHH) +Size 98: 49,98 BAD (HHHFHH) +Size 99: 50,99 BAD (HHHFHH) +Size 100: 50,100 BAD (HHHFHH) + +Windows 7 +--------- + +Size 1: 1,2 OK (HHHFFF) +Size 2: 1,2 OK (HHHFFF) +Size 3: 2,3 BAD (FFFFHH) +Size 4: 2,4 OK (HHHFFF) +Size 5: 3,5 OK (HHHFFF) +Size 6: 3,6 OK (HHHFFF) +Size 7: 4,7 OK (HHHFFF) +Size 8: 4,8 OK (HHHFFF) +Size 9: 5,9 OK (HHHFFF) +Size 10: 5,10 OK (HHHFFF) +Size 11: 6,11 OK (HHHFFF) +Size 12: 6,12 OK (HHHFFF) +Size 13: 7,13 OK (HHHFFF) +Size 14: 7,14 BAD (FFFFFF) +Size 15: 8,15 OK (HHHFFF) +Size 16: 8,16 BAD (FFFFFF) +Size 17: 9,17 OK (HHHFFF) +Size 18: 9,18 BAD (FFFFFF) +Size 19: 10,19 OK (HHHFFF) +Size 20: 10,20 BAD (FFFFFF) +Size 21: 11,21 OK (HHHFFF) +Size 22: 11,22 BAD (FFFFFF) +Size 23: 12,23 BAD (FFFFFF) +Size 24: 12,24 BAD (FFFFFF) +Size 25: 13,25 BAD (FFFFFF) +Size 26: 13,26 BAD (FFFFFF) +Size 27: 14,27 BAD (FFFFFF) +Size 28: 14,28 BAD (FFFFFF) +Size 29: 15,29 BAD (FFFFFF) +Size 30: 15,30 BAD (FFFFFF) +Size 31: 16,31 BAD (FFFFFF) +Size 32: 16,33 BAD (FFFFFF) +Size 33: 17,33 BAD (FFFFFF) +Size 34: 17,34 BAD (FFFFFF) +Size 35: 18,35 BAD (FFFFFF) +Size 36: 18,36 BAD (FFFFFF) +Size 37: 19,37 BAD (FFFFFF) +Size 38: 19,38 BAD (FFFFFF) +Size 39: 20,39 BAD (FFFFFF) +Size 40: 20,40 BAD (FFFFFF) +Size 41: 21,41 BAD (FFFFFF) +Size 42: 21,42 BAD (FFFFFF) +Size 43: 22,43 BAD (FFFFFF) +Size 44: 22,44 BAD (FFFFFF) +Size 45: 23,45 BAD (FFFFFF) +Size 46: 23,46 BAD (FFFFFF) +Size 47: 24,47 BAD (FFFFFF) +Size 48: 24,48 BAD (FFFFFF) +Size 49: 25,49 BAD (FFFFFF) +Size 50: 25,50 BAD (FFFFFF) +Size 51: 26,51 BAD (FFFFFF) +Size 52: 26,52 BAD (FFFFFF) +Size 53: 27,53 BAD (FFFFFF) +Size 54: 27,54 BAD (FFFFFF) +Size 55: 28,55 BAD (FFFFFF) +Size 56: 28,56 BAD (FFFFFF) +Size 57: 29,57 BAD (FFFFFF) +Size 58: 29,58 BAD (FFFFFF) +Size 59: 30,59 BAD (FFFFFF) +Size 60: 30,60 BAD (FFFFFF) +Size 61: 31,61 BAD (FFFFFF) +Size 62: 31,62 BAD (FFFFFF) +Size 63: 32,63 BAD (FFFFFF) +Size 64: 32,64 BAD (FFFFFF) +Size 65: 33,65 BAD (FFFFFF) +Size 66: 33,66 BAD (FFFFFF) +Size 67: 34,67 BAD (FFFFFF) +Size 68: 34,68 BAD (FFFFFF) +Size 69: 35,69 BAD (FFFFFF) +Size 70: 35,70 BAD (FFFFFF) +Size 71: 36,71 BAD (FFFFFF) +Size 72: 36,72 BAD (FFFFFF) +Size 73: 37,73 BAD (FFFFFF) +Size 74: 37,74 BAD (FFFFFF) +Size 75: 38,75 BAD (FFFFFF) +Size 76: 38,76 BAD (FFFFFF) +Size 77: 39,77 BAD (FFFFFF) +Size 78: 39,78 BAD (FFFFFF) +Size 79: 40,79 BAD (FFFFFF) +Size 80: 40,80 BAD (FFFFFF) +Size 81: 41,81 BAD (FFFFFF) +Size 82: 41,82 BAD (FFFFFF) +Size 83: 42,83 BAD (FFFFFF) +Size 84: 42,84 BAD (FFFFFF) +Size 85: 43,85 BAD (FFFFFF) +Size 86: 43,86 BAD (FFFFFF) +Size 87: 44,87 BAD (FFFFFF) +Size 88: 44,88 BAD (FFFFFF) +Size 89: 45,89 BAD (FFFFFF) +Size 90: 45,90 BAD (FFFFFF) +Size 91: 46,91 BAD (FFFFFF) +Size 92: 46,92 BAD (FFFFFF) +Size 93: 47,93 BAD (FFFFFF) +Size 94: 47,94 BAD (FFFFFF) +Size 95: 48,95 BAD (FFFFFF) +Size 96: 48,97 BAD (FFFFFF) +Size 97: 49,97 BAD (FFFFFF) +Size 98: 49,98 BAD (FFFFFF) +Size 99: 50,99 BAD (FFFFFF) +Size 100: 50,100 BAD (FFFFFF) + +Windows 8 +--------- + +Size 1: 1,2 BAD (FFFFHH) +Size 2: 1,2 BAD (FFFFHH) +Size 3: 2,3 BAD (FFFFFF) +Size 4: 2,4 BAD (FFFFHH) +Size 5: 3,5 BAD (FFFFFF) +Size 6: 3,6 BAD (FFFFHH) +Size 7: 4,7 BAD (FFFFFF) +Size 8: 4,8 BAD (FFFFHH) +Size 9: 5,9 BAD (FFFFFF) +Size 10: 5,10 BAD (FFFFHH) +Size 11: 6,11 BAD (FFFFFF) +Size 12: 6,12 BAD (FFFFHH) +Size 13: 7,13 BAD (FFFFFF) +Size 14: 7,14 BAD (FFFFHH) +Size 15: 8,15 BAD (FFFFFF) +Size 16: 8,16 BAD (FFFFHH) +Size 17: 9,17 BAD (FFFFFF) +Size 18: 9,18 BAD (FFFFHH) +Size 19: 10,19 BAD (FFFFFF) +Size 20: 10,20 BAD (FFFFFF) +Size 21: 11,21 BAD (FFFFFF) +Size 22: 11,22 BAD (FFFFFF) +Size 23: 12,23 BAD (FFFFFF) +Size 24: 12,24 BAD (FFFFFF) +Size 25: 13,25 BAD (FFFFFF) +Size 26: 13,26 BAD (FFFFFF) +Size 27: 14,27 BAD (FFFFFF) +Size 28: 14,28 BAD (FFFFFF) +Size 29: 15,29 BAD (FFFFFF) +Size 30: 15,30 BAD (FFFFFF) +Size 31: 16,31 BAD (FFFFFF) +Size 32: 16,33 BAD (FFFFFF) +Size 33: 17,33 BAD (FFFFFF) +Size 34: 17,34 BAD (FFFFFF) +Size 35: 18,35 BAD (FFFFFF) +Size 36: 18,36 BAD (FFFFFF) +Size 37: 19,37 BAD (FFFFFF) +Size 38: 19,38 BAD (FFFFFF) +Size 39: 20,39 BAD (FFFFFF) +Size 40: 20,40 BAD (FFFFFF) +Size 41: 21,41 BAD (FFFFFF) +Size 42: 21,42 BAD (FFFFFF) +Size 43: 22,43 BAD (FFFFFF) +Size 44: 22,44 BAD (FFFFFF) +Size 45: 23,45 BAD (FFFFFF) +Size 46: 23,46 BAD (FFFFFF) +Size 47: 24,47 BAD (FFFFFF) +Size 48: 24,48 BAD (FFFFFF) +Size 49: 25,49 BAD (FFFFFF) +Size 50: 25,50 BAD (FFFFFF) +Size 51: 26,51 BAD (FFFFFF) +Size 52: 26,52 BAD (FFFFFF) +Size 53: 27,53 BAD (FFFFFF) +Size 54: 27,54 BAD (FFFFFF) +Size 55: 28,55 BAD (FFFFFF) +Size 56: 28,56 BAD (FFFFFF) +Size 57: 29,57 BAD (FFFFFF) +Size 58: 29,58 BAD (FFFFFF) +Size 59: 30,59 BAD (FFFFFF) +Size 60: 30,60 BAD (FFFFFF) +Size 61: 31,61 BAD (FFFFFF) +Size 62: 31,62 BAD (FFFFFF) +Size 63: 32,63 BAD (FFFFFF) +Size 64: 32,64 BAD (FFFFFF) +Size 65: 33,65 BAD (FFFFFF) +Size 66: 33,66 BAD (FFFFFF) +Size 67: 34,67 BAD (FFFFFF) +Size 68: 34,68 BAD (FFFFFF) +Size 69: 35,69 BAD (FFFFFF) +Size 70: 35,70 BAD (FFFFFF) +Size 71: 36,71 BAD (FFFFFF) +Size 72: 36,72 BAD (FFFFFF) +Size 73: 37,73 BAD (FFFFFF) +Size 74: 37,74 BAD (FFFFFF) +Size 75: 38,75 BAD (FFFFFF) +Size 76: 38,76 BAD (FFFFFF) +Size 77: 39,77 BAD (FFFFFF) +Size 78: 39,78 BAD (FFFFFF) +Size 79: 40,79 BAD (FFFFFF) +Size 80: 40,80 BAD (FFFFFF) +Size 81: 41,81 BAD (FFFFFF) +Size 82: 41,82 BAD (FFFFFF) +Size 83: 42,83 BAD (FFFFFF) +Size 84: 42,84 BAD (FFFFFF) +Size 85: 43,85 BAD (FFFFFF) +Size 86: 43,86 BAD (FFFFFF) +Size 87: 44,87 BAD (FFFFFF) +Size 88: 44,88 BAD (FFFFFF) +Size 89: 45,89 BAD (FFFFFF) +Size 90: 45,90 BAD (FFFFFF) +Size 91: 46,91 BAD (FFFFFF) +Size 92: 46,92 BAD (FFFFFF) +Size 93: 47,93 BAD (FFFFFF) +Size 94: 47,94 BAD (FFFFFF) +Size 95: 48,95 BAD (FFFFFF) +Size 96: 48,97 BAD (FFFFFF) +Size 97: 49,97 BAD (FFFFFF) +Size 98: 49,98 BAD (FFFFFF) +Size 99: 50,99 BAD (FFFFFF) +Size 100: 50,100 BAD (FFFFFF) + +Windows 8.1 +----------- + +Size 1: 1,2 BAD (FFFFHH) +Size 2: 1,2 BAD (FFFFHH) +Size 3: 2,3 BAD (FFFFFF) +Size 4: 2,4 BAD (FFFFHH) +Size 5: 3,5 BAD (FFFFFF) +Size 6: 3,6 BAD (FFFFHH) +Size 7: 4,7 BAD (FFFFFF) +Size 8: 4,8 BAD (FFFFHH) +Size 9: 5,9 BAD (FFFFFF) +Size 10: 5,10 BAD (FFFFHH) +Size 11: 6,11 BAD (FFFFFF) +Size 12: 6,12 BAD (FFFFHH) +Size 13: 7,13 BAD (FFFFFF) +Size 14: 7,14 BAD (FFFFHH) +Size 15: 8,15 BAD (FFFFFF) +Size 16: 8,16 BAD (FFFFHH) +Size 17: 9,17 BAD (FFFFFF) +Size 18: 9,18 BAD (FFFFHH) +Size 19: 10,19 BAD (FFFFFF) +Size 20: 10,20 BAD (FFFFFF) +Size 21: 11,21 BAD (FFFFFF) +Size 22: 11,22 BAD (FFFFFF) +Size 23: 12,23 BAD (FFFFFF) +Size 24: 12,24 BAD (FFFFFF) +Size 25: 13,25 BAD (FFFFFF) +Size 26: 13,26 BAD (FFFFFF) +Size 27: 14,27 BAD (FFFFFF) +Size 28: 14,28 BAD (FFFFFF) +Size 29: 15,29 BAD (FFFFFF) +Size 30: 15,30 BAD (FFFFFF) +Size 31: 16,31 BAD (FFFFFF) +Size 32: 16,33 BAD (FFFFFF) +Size 33: 17,33 BAD (FFFFFF) +Size 34: 17,34 BAD (FFFFFF) +Size 35: 18,35 BAD (FFFFFF) +Size 36: 18,36 BAD (FFFFFF) +Size 37: 19,37 BAD (FFFFFF) +Size 38: 19,38 BAD (FFFFFF) +Size 39: 20,39 BAD (FFFFFF) +Size 40: 20,40 BAD (FFFFFF) +Size 41: 21,41 BAD (FFFFFF) +Size 42: 21,42 BAD (FFFFFF) +Size 43: 22,43 BAD (FFFFFF) +Size 44: 22,44 BAD (FFFFFF) +Size 45: 23,45 BAD (FFFFFF) +Size 46: 23,46 BAD (FFFFFF) +Size 47: 24,47 BAD (FFFFFF) +Size 48: 24,48 BAD (FFFFFF) +Size 49: 25,49 BAD (FFFFFF) +Size 50: 25,50 BAD (FFFFFF) +Size 51: 26,51 BAD (FFFFFF) +Size 52: 26,52 BAD (FFFFFF) +Size 53: 27,53 BAD (FFFFFF) +Size 54: 27,54 BAD (FFFFFF) +Size 55: 28,55 BAD (FFFFFF) +Size 56: 28,56 BAD (FFFFFF) +Size 57: 29,57 BAD (FFFFFF) +Size 58: 29,58 BAD (FFFFFF) +Size 59: 30,59 BAD (FFFFFF) +Size 60: 30,60 BAD (FFFFFF) +Size 61: 31,61 BAD (FFFFFF) +Size 62: 31,62 BAD (FFFFFF) +Size 63: 32,63 BAD (FFFFFF) +Size 64: 32,64 BAD (FFFFFF) +Size 65: 33,65 BAD (FFFFFF) +Size 66: 33,66 BAD (FFFFFF) +Size 67: 34,67 BAD (FFFFFF) +Size 68: 34,68 BAD (FFFFFF) +Size 69: 35,69 BAD (FFFFFF) +Size 70: 35,70 BAD (FFFFFF) +Size 71: 36,71 BAD (FFFFFF) +Size 72: 36,72 BAD (FFFFFF) +Size 73: 37,73 BAD (FFFFFF) +Size 74: 37,74 BAD (FFFFFF) +Size 75: 38,75 BAD (FFFFFF) +Size 76: 38,76 BAD (FFFFFF) +Size 77: 39,77 BAD (FFFFFF) +Size 78: 39,78 BAD (FFFFFF) +Size 79: 40,79 BAD (FFFFFF) +Size 80: 40,80 BAD (FFFFFF) +Size 81: 41,81 BAD (FFFFFF) +Size 82: 41,82 BAD (FFFFFF) +Size 83: 42,83 BAD (FFFFFF) +Size 84: 42,84 BAD (FFFFFF) +Size 85: 43,85 BAD (FFFFFF) +Size 86: 43,86 BAD (FFFFFF) +Size 87: 44,87 BAD (FFFFFF) +Size 88: 44,88 BAD (FFFFFF) +Size 89: 45,89 BAD (FFFFFF) +Size 90: 45,90 BAD (FFFFFF) +Size 91: 46,91 BAD (FFFFFF) +Size 92: 46,92 BAD (FFFFFF) +Size 93: 47,93 BAD (FFFFFF) +Size 94: 47,94 BAD (FFFFFF) +Size 95: 48,95 BAD (FFFFFF) +Size 96: 48,97 BAD (FFFFFF) +Size 97: 49,97 BAD (FFFFFF) +Size 98: 49,98 BAD (FFFFFF) +Size 99: 50,99 BAD (FFFFFF) +Size 100: 50,100 BAD (FFFFFF) + +Windows 10 14342 Old Console +---------------------------- + +Size 1: 1,2 BAD (FFFFHH) +Size 2: 1,2 BAD (FFFFHH) +Size 3: 2,3 BAD (FFFFFF) +Size 4: 2,4 BAD (FFFFHH) +Size 5: 3,5 BAD (FFFFFF) +Size 6: 3,6 BAD (FFFFHH) +Size 7: 4,7 BAD (FFFFFF) +Size 8: 4,8 BAD (FFFFHH) +Size 9: 5,9 BAD (FFFFFF) +Size 10: 5,10 BAD (FFFFHH) +Size 11: 6,11 BAD (FFFFFF) +Size 12: 6,12 BAD (FFFFHH) +Size 13: 7,13 BAD (FFFFFF) +Size 14: 7,14 BAD (FFFFHH) +Size 15: 8,15 BAD (FFFFFF) +Size 16: 8,16 BAD (FFFFHH) +Size 17: 9,17 BAD (FFFFFF) +Size 18: 9,18 BAD (FFFFHH) +Size 19: 10,19 BAD (FFFFFF) +Size 20: 10,20 BAD (FFFFFF) +Size 21: 11,21 BAD (FFFFFF) +Size 22: 11,22 BAD (FFFFFF) +Size 23: 12,23 BAD (FFFFFF) +Size 24: 12,24 BAD (FFFFFF) +Size 25: 13,25 BAD (FFFFFF) +Size 26: 13,26 BAD (FFFFFF) +Size 27: 14,27 BAD (FFFFFF) +Size 28: 14,28 BAD (FFFFFF) +Size 29: 15,29 BAD (FFFFFF) +Size 30: 15,30 BAD (FFFFFF) +Size 31: 16,31 BAD (FFFFFF) +Size 32: 16,33 BAD (FFFFFF) +Size 33: 17,33 BAD (FFFFFF) +Size 34: 17,34 BAD (FFFFFF) +Size 35: 18,35 BAD (FFFFFF) +Size 36: 18,36 BAD (FFFFFF) +Size 37: 19,37 BAD (FFFFFF) +Size 38: 19,38 BAD (FFFFFF) +Size 39: 20,39 BAD (FFFFFF) +Size 40: 20,40 BAD (FFFFFF) +Size 41: 21,41 BAD (FFFFFF) +Size 42: 21,42 BAD (FFFFFF) +Size 43: 22,43 BAD (FFFFFF) +Size 44: 22,44 BAD (FFFFFF) +Size 45: 23,45 BAD (FFFFFF) +Size 46: 23,46 BAD (FFFFFF) +Size 47: 24,47 BAD (FFFFFF) +Size 48: 24,48 BAD (FFFFFF) +Size 49: 25,49 BAD (FFFFFF) +Size 50: 25,50 BAD (FFFFFF) +Size 51: 26,51 BAD (FFFFFF) +Size 52: 26,52 BAD (FFFFFF) +Size 53: 27,53 BAD (FFFFFF) +Size 54: 27,54 BAD (FFFFFF) +Size 55: 28,55 BAD (FFFFFF) +Size 56: 28,56 BAD (FFFFFF) +Size 57: 29,57 BAD (FFFFFF) +Size 58: 29,58 BAD (FFFFFF) +Size 59: 30,59 BAD (FFFFFF) +Size 60: 30,60 BAD (FFFFFF) +Size 61: 31,61 BAD (FFFFFF) +Size 62: 31,62 BAD (FFFFFF) +Size 63: 32,63 BAD (FFFFFF) +Size 64: 32,64 BAD (FFFFFF) +Size 65: 33,65 BAD (FFFFFF) +Size 66: 33,66 BAD (FFFFFF) +Size 67: 34,67 BAD (FFFFFF) +Size 68: 34,68 BAD (FFFFFF) +Size 69: 35,69 BAD (FFFFFF) +Size 70: 35,70 BAD (FFFFFF) +Size 71: 36,71 BAD (FFFFFF) +Size 72: 36,72 BAD (FFFFFF) +Size 73: 37,73 BAD (FFFFFF) +Size 74: 37,74 BAD (FFFFFF) +Size 75: 38,75 BAD (FFFFFF) +Size 76: 38,76 BAD (FFFFFF) +Size 77: 39,77 BAD (FFFFFF) +Size 78: 39,78 BAD (FFFFFF) +Size 79: 40,79 BAD (FFFFFF) +Size 80: 40,80 BAD (FFFFFF) +Size 81: 41,81 BAD (FFFFFF) +Size 82: 41,82 BAD (FFFFFF) +Size 83: 42,83 BAD (FFFFFF) +Size 84: 42,84 BAD (FFFFFF) +Size 85: 43,85 BAD (FFFFFF) +Size 86: 43,86 BAD (FFFFFF) +Size 87: 44,87 BAD (FFFFFF) +Size 88: 44,88 BAD (FFFFFF) +Size 89: 45,89 BAD (FFFFFF) +Size 90: 45,90 BAD (FFFFFF) +Size 91: 46,91 BAD (FFFFFF) +Size 92: 46,92 BAD (FFFFFF) +Size 93: 47,93 BAD (FFFFFF) +Size 94: 47,94 BAD (FFFFFF) +Size 95: 48,95 BAD (FFFFFF) +Size 96: 48,97 BAD (FFFFFF) +Size 97: 49,97 BAD (FFFFFF) +Size 98: 49,98 BAD (FFFFFF) +Size 99: 50,99 BAD (FFFFFF) +Size 100: 50,100 BAD (FFFFFF) + +Windows 10 14342 New Console +---------------------------- + +Size 1: 1,1 OK (HHHFFF) +Size 2: 1,2 OK (HHHFFF) +Size 3: 2,3 OK (HHHFFF) +Size 4: 2,4 OK (HHHFFF) +Size 5: 3,5 OK (HHHFFF) +Size 6: 3,6 OK (HHHFFF) +Size 7: 4,7 OK (HHHFFF) +Size 8: 4,8 OK (HHHFFF) +Size 9: 5,9 OK (HHHFFF) +Size 10: 5,10 OK (HHHFFF) +Size 11: 6,11 OK (HHHFFF) +Size 12: 6,12 OK (HHHFFF) +Size 13: 7,13 OK (HHHFFF) +Size 14: 7,14 OK (HHHFFF) +Size 15: 8,15 OK (HHHFFF) +Size 16: 8,16 OK (HHHFFF) +Size 17: 9,17 OK (HHHFFF) +Size 18: 9,18 OK (HHHFFF) +Size 19: 10,19 OK (HHHFFF) +Size 20: 10,20 OK (HHHFFF) +Size 21: 11,21 OK (HHHFFF) +Size 22: 11,22 OK (HHHFFF) +Size 23: 12,23 OK (HHHFFF) +Size 24: 12,24 OK (HHHFFF) +Size 25: 13,25 OK (HHHFFF) +Size 26: 13,26 OK (HHHFFF) +Size 27: 14,27 OK (HHHFFF) +Size 28: 14,28 OK (HHHFFF) +Size 29: 15,29 OK (HHHFFF) +Size 30: 15,30 OK (HHHFFF) +Size 31: 16,31 OK (HHHFFF) +Size 32: 16,32 OK (HHHFFF) +Size 33: 17,33 OK (HHHFFF) +Size 34: 17,34 OK (HHHFFF) +Size 35: 18,35 OK (HHHFFF) +Size 36: 18,36 OK (HHHFFF) +Size 37: 19,37 OK (HHHFFF) +Size 38: 19,38 OK (HHHFFF) +Size 39: 20,39 OK (HHHFFF) +Size 40: 20,40 OK (HHHFFF) +Size 41: 21,41 OK (HHHFFF) +Size 42: 21,42 OK (HHHFFF) +Size 43: 22,43 OK (HHHFFF) +Size 44: 22,44 OK (HHHFFF) +Size 45: 23,45 OK (HHHFFF) +Size 46: 23,46 OK (HHHFFF) +Size 47: 24,47 OK (HHHFFF) +Size 48: 24,48 OK (HHHFFF) +Size 49: 25,49 OK (HHHFFF) +Size 50: 25,50 OK (HHHFFF) +Size 51: 26,51 OK (HHHFFF) +Size 52: 26,52 OK (HHHFFF) +Size 53: 27,53 OK (HHHFFF) +Size 54: 27,54 OK (HHHFFF) +Size 55: 28,55 OK (HHHFFF) +Size 56: 28,56 OK (HHHFFF) +Size 57: 29,57 OK (HHHFFF) +Size 58: 29,58 OK (HHHFFF) +Size 59: 30,59 OK (HHHFFF) +Size 60: 30,60 OK (HHHFFF) +Size 61: 31,61 OK (HHHFFF) +Size 62: 31,62 OK (HHHFFF) +Size 63: 32,63 OK (HHHFFF) +Size 64: 32,64 OK (HHHFFF) +Size 65: 33,65 OK (HHHFFF) +Size 66: 33,66 OK (HHHFFF) +Size 67: 34,67 OK (HHHFFF) +Size 68: 34,68 OK (HHHFFF) +Size 69: 35,69 OK (HHHFFF) +Size 70: 35,70 OK (HHHFFF) +Size 71: 36,71 OK (HHHFFF) +Size 72: 36,72 OK (HHHFFF) +Size 73: 37,73 OK (HHHFFF) +Size 74: 37,74 OK (HHHFFF) +Size 75: 38,75 OK (HHHFFF) +Size 76: 38,76 OK (HHHFFF) +Size 77: 39,77 OK (HHHFFF) +Size 78: 39,78 OK (HHHFFF) +Size 79: 40,79 OK (HHHFFF) +Size 80: 40,80 OK (HHHFFF) +Size 81: 41,81 OK (HHHFFF) +Size 82: 41,82 OK (HHHFFF) +Size 83: 42,83 OK (HHHFFF) +Size 84: 42,84 OK (HHHFFF) +Size 85: 43,85 OK (HHHFFF) +Size 86: 43,86 OK (HHHFFF) +Size 87: 44,87 OK (HHHFFF) +Size 88: 44,88 OK (HHHFFF) +Size 89: 45,89 OK (HHHFFF) +Size 90: 45,90 OK (HHHFFF) +Size 91: 46,91 OK (HHHFFF) +Size 92: 46,92 OK (HHHFFF) +Size 93: 47,93 OK (HHHFFF) +Size 94: 47,94 OK (HHHFFF) +Size 95: 48,95 OK (HHHFFF) +Size 96: 48,96 OK (HHHFFF) +Size 97: 49,97 OK (HHHFFF) +Size 98: 49,98 OK (HHHFFF) +Size 99: 50,99 OK (HHHFFF) +Size 100: 50,100 OK (HHHFFF) diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP936.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP936.txt new file mode 100644 index 00000000000..43210dac518 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP936.txt @@ -0,0 +1,630 @@ +========================================================== +Code Page 936, Chinese Simplified (China/PRC), SimSun font +========================================================== + +Options: -face-simsun -family 0x36 +Chars: A2 A3 2014 3044 30FC 4000 + +Vista +----- + +Size 1: 1,2 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,3 BAD (FFHFHH) +Size 4: 2,5 GOOD (HHFFFF) +Size 5: 3,6 BAD (HHHFHH) +Size 6: 3,7 GOOD (HHFFFF) +Size 7: 4,8 BAD (HHHFHH) +Size 8: 4,9 GOOD (HHFFFF) +Size 9: 5,10 BAD (HHHFHH) +Size 10: 5,11 GOOD (HHFFFF) +Size 11: 6,13 BAD (HHHFHH) +Size 12: 6,14 GOOD (HHFFFF) +Size 13: 7,15 BAD (HHHFHH) +Size 14: 7,16 GOOD (HHFFFF) +Size 15: 8,17 BAD (HHHFHH) +Size 16: 8,18 GOOD (HHFFFF) +Size 17: 9,19 BAD (HHHFHH) +Size 18: 9,21 GOOD (HHFFFF) +Size 19: 10,22 BAD (HHHFHH) +Size 20: 10,23 GOOD (HHFFFF) +Size 21: 11,24 BAD (HHHFHH) +Size 22: 11,25 GOOD (HHFFFF) +Size 23: 12,26 BAD (HHHFHH) +Size 24: 12,27 GOOD (HHFFFF) +Size 25: 13,29 BAD (HHHFHH) +Size 26: 13,30 GOOD (HHFFFF) +Size 27: 14,31 BAD (HHHFHH) +Size 28: 14,32 GOOD (HHFFFF) +Size 29: 15,33 BAD (HHHFHH) +Size 30: 15,34 GOOD (HHFFFF) +Size 31: 16,35 BAD (HHHFHH) +Size 32: 16,38 GOOD (HHFFFF) +Size 33: 17,38 BAD (HHHFHH) +Size 34: 17,39 GOOD (HHFFFF) +Size 35: 18,40 BAD (HHHFHH) +Size 36: 18,41 GOOD (HHFFFF) +Size 37: 19,42 BAD (HHHFHH) +Size 38: 19,43 GOOD (HHFFFF) +Size 39: 20,44 BAD (HHHFHH) +Size 40: 20,46 GOOD (HHFFFF) +Size 41: 21,47 BAD (HHHFHH) +Size 42: 21,48 GOOD (HHFFFF) +Size 43: 22,49 BAD (HHHFHH) +Size 44: 22,50 GOOD (HHFFFF) +Size 45: 23,51 BAD (HHHFHH) +Size 46: 23,52 GOOD (HHFFFF) +Size 47: 24,54 BAD (HHHFHH) +Size 48: 24,55 GOOD (HHFFFF) +Size 49: 25,56 BAD (HHHFHH) +Size 50: 25,57 GOOD (HHFFFF) +Size 51: 26,58 BAD (HHHFHH) +Size 52: 26,59 GOOD (HHFFFF) +Size 53: 27,60 BAD (HHHFHH) +Size 54: 27,62 GOOD (HHFFFF) +Size 55: 28,63 BAD (HHHFHH) +Size 56: 28,64 GOOD (HHFFFF) +Size 57: 29,65 BAD (HHHFHH) +Size 58: 29,66 GOOD (HHFFFF) +Size 59: 30,67 BAD (HHHFHH) +Size 60: 30,68 GOOD (HHFFFF) +Size 61: 31,70 BAD (HHHFHH) +Size 62: 31,71 GOOD (HHFFFF) +Size 63: 32,72 BAD (HHHFHH) +Size 64: 32,73 GOOD (HHFFFF) +Size 65: 33,74 GOOD (HHFFFF) +Size 66: 33,75 GOOD (HHFFFF) +Size 67: 34,76 GOOD (HHFFFF) +Size 68: 34,78 GOOD (HHFFFF) +Size 69: 35,79 GOOD (HHFFFF) +Size 70: 35,80 GOOD (HHFFFF) +Size 71: 36,81 GOOD (HHFFFF) +Size 72: 36,82 GOOD (HHFFFF) +Size 73: 37,83 GOOD (HHFFFF) +Size 74: 37,84 GOOD (HHFFFF) +Size 75: 38,86 GOOD (HHFFFF) +Size 76: 38,87 GOOD (HHFFFF) +Size 77: 39,88 GOOD (HHFFFF) +Size 78: 39,89 GOOD (HHFFFF) +Size 79: 40,90 GOOD (HHFFFF) +Size 80: 40,91 GOOD (HHFFFF) +Size 81: 41,92 GOOD (HHFFFF) +Size 82: 41,94 GOOD (HHFFFF) +Size 83: 42,95 GOOD (HHFFFF) +Size 84: 42,96 GOOD (HHFFFF) +Size 85: 43,97 GOOD (HHFFFF) +Size 86: 43,98 GOOD (HHFFFF) +Size 87: 44,99 GOOD (HHFFFF) +Size 88: 44,100 GOOD (HHFFFF) +Size 89: 45,102 GOOD (HHFFFF) +Size 90: 45,103 GOOD (HHFFFF) +Size 91: 46,104 GOOD (HHFFFF) +Size 92: 46,105 GOOD (HHFFFF) +Size 93: 47,106 GOOD (HHFFFF) +Size 94: 47,107 GOOD (HHFFFF) +Size 95: 48,108 GOOD (HHFFFF) +Size 96: 48,111 GOOD (HHFFFF) +Size 97: 49,111 GOOD (HHFFFF) +Size 98: 49,112 GOOD (HHFFFF) +Size 99: 50,113 GOOD (HHFFFF) +Size 100: 50,114 GOOD (HHFFFF) + +Windows 7 +--------- + +Size 1: 1,2 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,3 BAD (FFHFHH) +Size 4: 2,5 GOOD (HHFFFF) +Size 5: 3,6 BAD (FFHFHH) +Size 6: 3,7 GOOD (HHFFFF) +Size 7: 4,8 BAD (FFHFHH) +Size 8: 4,9 GOOD (HHFFFF) +Size 9: 5,10 BAD (FFHFHH) +Size 10: 5,11 GOOD (HHFFFF) +Size 11: 6,13 BAD (FFHFHH) +Size 12: 6,14 GOOD (HHFFFF) +Size 13: 7,15 BAD (FFHFHH) +Size 14: 7,16 GOOD (HHFFFF) +Size 15: 8,17 BAD (FFHFHH) +Size 16: 8,18 GOOD (HHFFFF) +Size 17: 9,19 BAD (FFHFHH) +Size 18: 9,21 GOOD (HHFFFF) +Size 19: 10,22 BAD (FFHFHH) +Size 20: 10,23 GOOD (HHFFFF) +Size 21: 11,24 BAD (FFHFHH) +Size 22: 11,25 GOOD (HHFFFF) +Size 23: 12,26 BAD (FFHFHH) +Size 24: 12,27 GOOD (HHFFFF) +Size 25: 13,29 BAD (FFHFHH) +Size 26: 13,30 GOOD (HHFFFF) +Size 27: 14,31 BAD (FFHFHH) +Size 28: 14,32 GOOD (HHFFFF) +Size 29: 15,33 BAD (FFHFHH) +Size 30: 15,34 GOOD (HHFFFF) +Size 31: 16,35 BAD (FFHFHH) +Size 32: 16,38 GOOD (HHFFFF) +Size 33: 17,38 BAD (FFHFHH) +Size 34: 17,39 GOOD (HHFFFF) +Size 35: 18,40 BAD (FFHFHH) +Size 36: 18,41 GOOD (HHFFFF) +Size 37: 19,42 BAD (FFHFHH) +Size 38: 19,43 GOOD (HHFFFF) +Size 39: 20,44 BAD (FFHFHH) +Size 40: 20,46 GOOD (HHFFFF) +Size 41: 21,47 BAD (FFHFHH) +Size 42: 21,48 GOOD (HHFFFF) +Size 43: 22,49 BAD (FFHFHH) +Size 44: 22,50 GOOD (HHFFFF) +Size 45: 23,51 BAD (FFHFHH) +Size 46: 23,52 GOOD (HHFFFF) +Size 47: 24,54 BAD (FFHFHH) +Size 48: 24,55 GOOD (HHFFFF) +Size 49: 25,56 BAD (FFHFHH) +Size 50: 25,57 GOOD (HHFFFF) +Size 51: 26,58 BAD (FFHFHH) +Size 52: 26,59 GOOD (HHFFFF) +Size 53: 27,60 BAD (FFHFHH) +Size 54: 27,62 GOOD (HHFFFF) +Size 55: 28,63 BAD (FFHFHH) +Size 56: 28,64 GOOD (HHFFFF) +Size 57: 29,65 BAD (FFHFHH) +Size 58: 29,66 GOOD (HHFFFF) +Size 59: 30,67 BAD (FFHFHH) +Size 60: 30,68 GOOD (HHFFFF) +Size 61: 31,70 BAD (FFHFHH) +Size 62: 31,71 GOOD (HHFFFF) +Size 63: 32,72 BAD (FFHFHH) +Size 64: 32,73 GOOD (HHFFFF) +Size 65: 33,74 GOOD (HHFFFF) +Size 66: 33,75 GOOD (HHFFFF) +Size 67: 34,76 GOOD (HHFFFF) +Size 68: 34,78 GOOD (HHFFFF) +Size 69: 35,79 GOOD (HHFFFF) +Size 70: 35,80 GOOD (HHFFFF) +Size 71: 36,81 GOOD (HHFFFF) +Size 72: 36,82 GOOD (HHFFFF) +Size 73: 37,83 GOOD (HHFFFF) +Size 74: 37,84 GOOD (HHFFFF) +Size 75: 38,86 GOOD (HHFFFF) +Size 76: 38,87 GOOD (HHFFFF) +Size 77: 39,88 GOOD (HHFFFF) +Size 78: 39,89 GOOD (HHFFFF) +Size 79: 40,90 GOOD (HHFFFF) +Size 80: 40,91 GOOD (HHFFFF) +Size 81: 41,92 GOOD (HHFFFF) +Size 82: 41,94 GOOD (HHFFFF) +Size 83: 42,95 GOOD (HHFFFF) +Size 84: 42,96 GOOD (HHFFFF) +Size 85: 43,97 GOOD (HHFFFF) +Size 86: 43,98 GOOD (HHFFFF) +Size 87: 44,99 GOOD (HHFFFF) +Size 88: 44,100 GOOD (HHFFFF) +Size 89: 45,102 GOOD (HHFFFF) +Size 90: 45,103 GOOD (HHFFFF) +Size 91: 46,104 GOOD (HHFFFF) +Size 92: 46,105 GOOD (HHFFFF) +Size 93: 47,106 GOOD (HHFFFF) +Size 94: 47,107 GOOD (HHFFFF) +Size 95: 48,108 GOOD (HHFFFF) +Size 96: 48,111 GOOD (HHFFFF) +Size 97: 49,111 GOOD (HHFFFF) +Size 98: 49,112 GOOD (HHFFFF) +Size 99: 50,113 GOOD (HHFFFF) +Size 100: 50,114 GOOD (HHFFFF) + +Windows 8 +--------- + +Size 1: 1,2 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,3 BAD (FFHFHH) +Size 4: 2,5 GOOD (HHFFFF) +Size 5: 3,6 BAD (FFHFHH) +Size 6: 3,7 GOOD (HHFFFF) +Size 7: 4,8 BAD (FFHFHH) +Size 8: 4,9 GOOD (HHFFFF) +Size 9: 5,10 BAD (FFHFHH) +Size 10: 5,11 GOOD (HHFFFF) +Size 11: 6,13 BAD (FFHFHH) +Size 12: 6,14 GOOD (HHFFFF) +Size 13: 7,15 BAD (FFHFHH) +Size 14: 7,16 GOOD (HHFFFF) +Size 15: 8,17 BAD (FFHFHH) +Size 16: 8,18 GOOD (HHFFFF) +Size 17: 9,19 BAD (FFHFHH) +Size 18: 9,21 GOOD (HHFFFF) +Size 19: 10,22 BAD (FFHFHH) +Size 20: 10,23 GOOD (HHFFFF) +Size 21: 11,24 BAD (FFHFHH) +Size 22: 11,25 GOOD (HHFFFF) +Size 23: 12,26 BAD (FFHFHH) +Size 24: 12,27 GOOD (HHFFFF) +Size 25: 13,29 BAD (FFHFHH) +Size 26: 13,30 GOOD (HHFFFF) +Size 27: 14,31 BAD (FFHFHH) +Size 28: 14,32 GOOD (HHFFFF) +Size 29: 15,33 BAD (FFHFHH) +Size 30: 15,34 GOOD (HHFFFF) +Size 31: 16,35 BAD (FFHFHH) +Size 32: 16,38 GOOD (HHFFFF) +Size 33: 17,38 BAD (FFHFHH) +Size 34: 17,39 GOOD (HHFFFF) +Size 35: 18,40 BAD (FFHFHH) +Size 36: 18,41 GOOD (HHFFFF) +Size 37: 19,42 BAD (FFHFHH) +Size 38: 19,43 GOOD (HHFFFF) +Size 39: 20,44 BAD (FFHFHH) +Size 40: 20,46 GOOD (HHFFFF) +Size 41: 21,47 BAD (FFHFHH) +Size 42: 21,48 GOOD (HHFFFF) +Size 43: 22,49 BAD (FFHFHH) +Size 44: 22,50 GOOD (HHFFFF) +Size 45: 23,51 BAD (FFHFHH) +Size 46: 23,52 GOOD (HHFFFF) +Size 47: 24,54 BAD (FFHFHH) +Size 48: 24,55 GOOD (HHFFFF) +Size 49: 25,56 BAD (FFHFHH) +Size 50: 25,57 GOOD (HHFFFF) +Size 51: 26,58 BAD (FFHFHH) +Size 52: 26,59 GOOD (HHFFFF) +Size 53: 27,60 BAD (FFHFHH) +Size 54: 27,62 GOOD (HHFFFF) +Size 55: 28,63 BAD (FFHFHH) +Size 56: 28,64 GOOD (HHFFFF) +Size 57: 29,65 BAD (FFHFHH) +Size 58: 29,66 GOOD (HHFFFF) +Size 59: 30,67 BAD (FFHFHH) +Size 60: 30,68 GOOD (HHFFFF) +Size 61: 31,70 BAD (FFHFHH) +Size 62: 31,71 GOOD (HHFFFF) +Size 63: 32,72 BAD (FFHFHH) +Size 64: 32,73 GOOD (HHFFFF) +Size 65: 33,74 GOOD (HHFFFF) +Size 66: 33,75 GOOD (HHFFFF) +Size 67: 34,76 GOOD (HHFFFF) +Size 68: 34,78 GOOD (HHFFFF) +Size 69: 35,79 GOOD (HHFFFF) +Size 70: 35,80 GOOD (HHFFFF) +Size 71: 36,81 GOOD (HHFFFF) +Size 72: 36,82 GOOD (HHFFFF) +Size 73: 37,83 GOOD (HHFFFF) +Size 74: 37,84 GOOD (HHFFFF) +Size 75: 38,86 GOOD (HHFFFF) +Size 76: 38,87 GOOD (HHFFFF) +Size 77: 39,88 GOOD (HHFFFF) +Size 78: 39,89 GOOD (HHFFFF) +Size 79: 40,90 GOOD (HHFFFF) +Size 80: 40,91 GOOD (HHFFFF) +Size 81: 41,92 GOOD (HHFFFF) +Size 82: 41,94 GOOD (HHFFFF) +Size 83: 42,95 GOOD (HHFFFF) +Size 84: 42,96 GOOD (HHFFFF) +Size 85: 43,97 GOOD (HHFFFF) +Size 86: 43,98 GOOD (HHFFFF) +Size 87: 44,99 GOOD (HHFFFF) +Size 88: 44,100 GOOD (HHFFFF) +Size 89: 45,102 GOOD (HHFFFF) +Size 90: 45,103 GOOD (HHFFFF) +Size 91: 46,104 GOOD (HHFFFF) +Size 92: 46,105 GOOD (HHFFFF) +Size 93: 47,106 GOOD (HHFFFF) +Size 94: 47,107 GOOD (HHFFFF) +Size 95: 48,108 GOOD (HHFFFF) +Size 96: 48,111 GOOD (HHFFFF) +Size 97: 49,111 GOOD (HHFFFF) +Size 98: 49,112 GOOD (HHFFFF) +Size 99: 50,113 GOOD (HHFFFF) +Size 100: 50,114 GOOD (HHFFFF) + +Windows 8.1 +----------- + +Size 1: 1,2 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,3 BAD (FFHFHH) +Size 4: 2,5 GOOD (HHFFFF) +Size 5: 3,6 BAD (FFHFHH) +Size 6: 3,7 GOOD (HHFFFF) +Size 7: 4,8 BAD (FFHFHH) +Size 8: 4,9 GOOD (HHFFFF) +Size 9: 5,10 BAD (FFHFHH) +Size 10: 5,11 GOOD (HHFFFF) +Size 11: 6,13 BAD (FFHFHH) +Size 12: 6,14 GOOD (HHFFFF) +Size 13: 7,15 BAD (FFHFHH) +Size 14: 7,16 GOOD (HHFFFF) +Size 15: 8,17 BAD (FFHFHH) +Size 16: 8,18 GOOD (HHFFFF) +Size 17: 9,19 BAD (FFHFHH) +Size 18: 9,21 GOOD (HHFFFF) +Size 19: 10,22 BAD (FFHFHH) +Size 20: 10,23 GOOD (HHFFFF) +Size 21: 11,24 BAD (FFHFHH) +Size 22: 11,25 GOOD (HHFFFF) +Size 23: 12,26 BAD (FFHFHH) +Size 24: 12,27 GOOD (HHFFFF) +Size 25: 13,29 BAD (FFHFHH) +Size 26: 13,30 GOOD (HHFFFF) +Size 27: 14,31 BAD (FFHFHH) +Size 28: 14,32 GOOD (HHFFFF) +Size 29: 15,33 BAD (FFHFHH) +Size 30: 15,34 GOOD (HHFFFF) +Size 31: 16,35 BAD (FFHFHH) +Size 32: 16,38 GOOD (HHFFFF) +Size 33: 17,38 BAD (FFHFHH) +Size 34: 17,39 GOOD (HHFFFF) +Size 35: 18,40 BAD (FFHFHH) +Size 36: 18,41 GOOD (HHFFFF) +Size 37: 19,42 BAD (FFHFHH) +Size 38: 19,43 GOOD (HHFFFF) +Size 39: 20,44 BAD (FFHFHH) +Size 40: 20,46 GOOD (HHFFFF) +Size 41: 21,47 BAD (FFHFHH) +Size 42: 21,48 GOOD (HHFFFF) +Size 43: 22,49 BAD (FFHFHH) +Size 44: 22,50 GOOD (HHFFFF) +Size 45: 23,51 BAD (FFHFHH) +Size 46: 23,52 GOOD (HHFFFF) +Size 47: 24,54 BAD (FFHFHH) +Size 48: 24,55 GOOD (HHFFFF) +Size 49: 25,56 BAD (FFHFHH) +Size 50: 25,57 GOOD (HHFFFF) +Size 51: 26,58 BAD (FFHFHH) +Size 52: 26,59 GOOD (HHFFFF) +Size 53: 27,60 BAD (FFHFHH) +Size 54: 27,62 GOOD (HHFFFF) +Size 55: 28,63 BAD (FFHFHH) +Size 56: 28,64 GOOD (HHFFFF) +Size 57: 29,65 BAD (FFHFHH) +Size 58: 29,66 GOOD (HHFFFF) +Size 59: 30,67 BAD (FFHFHH) +Size 60: 30,68 GOOD (HHFFFF) +Size 61: 31,70 BAD (FFHFHH) +Size 62: 31,71 GOOD (HHFFFF) +Size 63: 32,72 BAD (FFHFHH) +Size 64: 32,73 GOOD (HHFFFF) +Size 65: 33,74 GOOD (HHFFFF) +Size 66: 33,75 GOOD (HHFFFF) +Size 67: 34,76 GOOD (HHFFFF) +Size 68: 34,78 GOOD (HHFFFF) +Size 69: 35,79 GOOD (HHFFFF) +Size 70: 35,80 GOOD (HHFFFF) +Size 71: 36,81 GOOD (HHFFFF) +Size 72: 36,82 GOOD (HHFFFF) +Size 73: 37,83 GOOD (HHFFFF) +Size 74: 37,84 GOOD (HHFFFF) +Size 75: 38,86 GOOD (HHFFFF) +Size 76: 38,87 GOOD (HHFFFF) +Size 77: 39,88 GOOD (HHFFFF) +Size 78: 39,89 GOOD (HHFFFF) +Size 79: 40,90 GOOD (HHFFFF) +Size 80: 40,91 GOOD (HHFFFF) +Size 81: 41,92 GOOD (HHFFFF) +Size 82: 41,94 GOOD (HHFFFF) +Size 83: 42,95 GOOD (HHFFFF) +Size 84: 42,96 GOOD (HHFFFF) +Size 85: 43,97 GOOD (HHFFFF) +Size 86: 43,98 GOOD (HHFFFF) +Size 87: 44,99 GOOD (HHFFFF) +Size 88: 44,100 GOOD (HHFFFF) +Size 89: 45,102 GOOD (HHFFFF) +Size 90: 45,103 GOOD (HHFFFF) +Size 91: 46,104 GOOD (HHFFFF) +Size 92: 46,105 GOOD (HHFFFF) +Size 93: 47,106 GOOD (HHFFFF) +Size 94: 47,107 GOOD (HHFFFF) +Size 95: 48,108 GOOD (HHFFFF) +Size 96: 48,111 GOOD (HHFFFF) +Size 97: 49,111 GOOD (HHFFFF) +Size 98: 49,112 GOOD (HHFFFF) +Size 99: 50,113 GOOD (HHFFFF) +Size 100: 50,114 GOOD (HHFFFF) + +Windows 10 14342 Old Console +---------------------------- + +Size 1: 1,2 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,3 BAD (FFHFHH) +Size 4: 2,5 GOOD (HHFFFF) +Size 5: 3,6 BAD (FFHFHH) +Size 6: 3,7 GOOD (HHFFFF) +Size 7: 4,8 BAD (FFHFHH) +Size 8: 4,9 GOOD (HHFFFF) +Size 9: 5,10 BAD (FFHFHH) +Size 10: 5,11 GOOD (HHFFFF) +Size 11: 6,13 BAD (FFHFHH) +Size 12: 6,14 GOOD (HHFFFF) +Size 13: 7,15 BAD (FFHFHH) +Size 14: 7,16 GOOD (HHFFFF) +Size 15: 8,17 BAD (FFHFHH) +Size 16: 8,18 GOOD (HHFFFF) +Size 17: 9,19 BAD (FFHFHH) +Size 18: 9,21 GOOD (HHFFFF) +Size 19: 10,22 BAD (FFHFHH) +Size 20: 10,23 GOOD (HHFFFF) +Size 21: 11,24 BAD (FFHFHH) +Size 22: 11,25 GOOD (HHFFFF) +Size 23: 12,26 BAD (FFHFHH) +Size 24: 12,27 GOOD (HHFFFF) +Size 25: 13,29 BAD (FFHFHH) +Size 26: 13,30 GOOD (HHFFFF) +Size 27: 14,31 BAD (FFHFHH) +Size 28: 14,32 GOOD (HHFFFF) +Size 29: 15,33 BAD (FFHFHH) +Size 30: 15,34 GOOD (HHFFFF) +Size 31: 16,35 BAD (FFHFHH) +Size 32: 16,38 GOOD (HHFFFF) +Size 33: 17,38 BAD (FFHFHH) +Size 34: 17,39 GOOD (HHFFFF) +Size 35: 18,40 BAD (FFHFHH) +Size 36: 18,41 GOOD (HHFFFF) +Size 37: 19,42 BAD (FFHFHH) +Size 38: 19,43 GOOD (HHFFFF) +Size 39: 20,44 BAD (FFHFHH) +Size 40: 20,46 GOOD (HHFFFF) +Size 41: 21,47 BAD (FFHFHH) +Size 42: 21,48 GOOD (HHFFFF) +Size 43: 22,49 BAD (FFHFHH) +Size 44: 22,50 GOOD (HHFFFF) +Size 45: 23,51 BAD (FFHFHH) +Size 46: 23,52 GOOD (HHFFFF) +Size 47: 24,54 BAD (FFHFHH) +Size 48: 24,55 GOOD (HHFFFF) +Size 49: 25,56 BAD (FFHFHH) +Size 50: 25,57 GOOD (HHFFFF) +Size 51: 26,58 BAD (FFHFHH) +Size 52: 26,59 GOOD (HHFFFF) +Size 53: 27,60 BAD (FFHFHH) +Size 54: 27,62 GOOD (HHFFFF) +Size 55: 28,63 BAD (FFHFHH) +Size 56: 28,64 GOOD (HHFFFF) +Size 57: 29,65 BAD (FFHFHH) +Size 58: 29,66 GOOD (HHFFFF) +Size 59: 30,67 BAD (FFHFHH) +Size 60: 30,68 GOOD (HHFFFF) +Size 61: 31,70 BAD (FFHFHH) +Size 62: 31,71 GOOD (HHFFFF) +Size 63: 32,72 BAD (FFHFHH) +Size 64: 32,73 GOOD (HHFFFF) +Size 65: 33,74 GOOD (HHFFFF) +Size 66: 33,75 GOOD (HHFFFF) +Size 67: 34,76 GOOD (HHFFFF) +Size 68: 34,78 GOOD (HHFFFF) +Size 69: 35,79 GOOD (HHFFFF) +Size 70: 35,80 GOOD (HHFFFF) +Size 71: 36,81 GOOD (HHFFFF) +Size 72: 36,82 GOOD (HHFFFF) +Size 73: 37,83 GOOD (HHFFFF) +Size 74: 37,84 GOOD (HHFFFF) +Size 75: 38,86 GOOD (HHFFFF) +Size 76: 38,87 GOOD (HHFFFF) +Size 77: 39,88 GOOD (HHFFFF) +Size 78: 39,89 GOOD (HHFFFF) +Size 79: 40,90 GOOD (HHFFFF) +Size 80: 40,91 GOOD (HHFFFF) +Size 81: 41,92 GOOD (HHFFFF) +Size 82: 41,94 GOOD (HHFFFF) +Size 83: 42,95 GOOD (HHFFFF) +Size 84: 42,96 GOOD (HHFFFF) +Size 85: 43,97 GOOD (HHFFFF) +Size 86: 43,98 GOOD (HHFFFF) +Size 87: 44,99 GOOD (HHFFFF) +Size 88: 44,100 GOOD (HHFFFF) +Size 89: 45,102 GOOD (HHFFFF) +Size 90: 45,103 GOOD (HHFFFF) +Size 91: 46,104 GOOD (HHFFFF) +Size 92: 46,105 GOOD (HHFFFF) +Size 93: 47,106 GOOD (HHFFFF) +Size 94: 47,107 GOOD (HHFFFF) +Size 95: 48,108 GOOD (HHFFFF) +Size 96: 48,111 GOOD (HHFFFF) +Size 97: 49,111 GOOD (HHFFFF) +Size 98: 49,112 GOOD (HHFFFF) +Size 99: 50,113 GOOD (HHFFFF) +Size 100: 50,114 GOOD (HHFFFF) + +Windows 10 14342 New Console +---------------------------- + +Size 1: 1,1 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,3 GOOD (HHFFFF) +Size 4: 2,4 GOOD (HHFFFF) +Size 5: 3,5 GOOD (HHFFFF) +Size 6: 3,6 GOOD (HHFFFF) +Size 7: 4,7 GOOD (HHFFFF) +Size 8: 4,8 GOOD (HHFFFF) +Size 9: 5,9 GOOD (HHFFFF) +Size 10: 5,10 GOOD (HHFFFF) +Size 11: 6,11 GOOD (HHFFFF) +Size 12: 6,12 GOOD (HHFFFF) +Size 13: 7,13 GOOD (HHFFFF) +Size 14: 7,14 GOOD (HHFFFF) +Size 15: 8,15 GOOD (HHFFFF) +Size 16: 8,16 GOOD (HHFFFF) +Size 17: 9,17 GOOD (HHFFFF) +Size 18: 9,18 GOOD (HHFFFF) +Size 19: 10,19 GOOD (HHFFFF) +Size 20: 10,20 GOOD (HHFFFF) +Size 21: 11,21 GOOD (HHFFFF) +Size 22: 11,22 GOOD (HHFFFF) +Size 23: 12,23 GOOD (HHFFFF) +Size 24: 12,24 GOOD (HHFFFF) +Size 25: 13,25 GOOD (HHFFFF) +Size 26: 13,26 GOOD (HHFFFF) +Size 27: 14,27 GOOD (HHFFFF) +Size 28: 14,28 GOOD (HHFFFF) +Size 29: 15,29 GOOD (HHFFFF) +Size 30: 15,30 GOOD (HHFFFF) +Size 31: 16,31 GOOD (HHFFFF) +Size 32: 16,32 GOOD (HHFFFF) +Size 33: 17,33 GOOD (HHFFFF) +Size 34: 17,34 GOOD (HHFFFF) +Size 35: 18,35 GOOD (HHFFFF) +Size 36: 18,36 GOOD (HHFFFF) +Size 37: 19,37 GOOD (HHFFFF) +Size 38: 19,38 GOOD (HHFFFF) +Size 39: 20,39 GOOD (HHFFFF) +Size 40: 20,40 GOOD (HHFFFF) +Size 41: 21,41 GOOD (HHFFFF) +Size 42: 21,42 GOOD (HHFFFF) +Size 43: 22,43 GOOD (HHFFFF) +Size 44: 22,44 GOOD (HHFFFF) +Size 45: 23,45 GOOD (HHFFFF) +Size 46: 23,46 GOOD (HHFFFF) +Size 47: 24,47 GOOD (HHFFFF) +Size 48: 24,48 GOOD (HHFFFF) +Size 49: 25,49 GOOD (HHFFFF) +Size 50: 25,50 GOOD (HHFFFF) +Size 51: 26,51 GOOD (HHFFFF) +Size 52: 26,52 GOOD (HHFFFF) +Size 53: 27,53 GOOD (HHFFFF) +Size 54: 27,54 GOOD (HHFFFF) +Size 55: 28,55 GOOD (HHFFFF) +Size 56: 28,56 GOOD (HHFFFF) +Size 57: 29,57 GOOD (HHFFFF) +Size 58: 29,58 GOOD (HHFFFF) +Size 59: 30,59 GOOD (HHFFFF) +Size 60: 30,60 GOOD (HHFFFF) +Size 61: 31,61 GOOD (HHFFFF) +Size 62: 31,62 GOOD (HHFFFF) +Size 63: 32,63 GOOD (HHFFFF) +Size 64: 32,64 GOOD (HHFFFF) +Size 65: 33,65 GOOD (HHFFFF) +Size 66: 33,66 GOOD (HHFFFF) +Size 67: 34,67 GOOD (HHFFFF) +Size 68: 34,68 GOOD (HHFFFF) +Size 69: 35,69 GOOD (HHFFFF) +Size 70: 35,70 GOOD (HHFFFF) +Size 71: 36,71 GOOD (HHFFFF) +Size 72: 36,72 GOOD (HHFFFF) +Size 73: 37,73 GOOD (HHFFFF) +Size 74: 37,74 GOOD (HHFFFF) +Size 75: 38,75 GOOD (HHFFFF) +Size 76: 38,76 GOOD (HHFFFF) +Size 77: 39,77 GOOD (HHFFFF) +Size 78: 39,78 GOOD (HHFFFF) +Size 79: 40,79 GOOD (HHFFFF) +Size 80: 40,80 GOOD (HHFFFF) +Size 81: 41,81 GOOD (HHFFFF) +Size 82: 41,82 GOOD (HHFFFF) +Size 83: 42,83 GOOD (HHFFFF) +Size 84: 42,84 GOOD (HHFFFF) +Size 85: 43,85 GOOD (HHFFFF) +Size 86: 43,86 GOOD (HHFFFF) +Size 87: 44,87 GOOD (HHFFFF) +Size 88: 44,88 GOOD (HHFFFF) +Size 89: 45,89 GOOD (HHFFFF) +Size 90: 45,90 GOOD (HHFFFF) +Size 91: 46,91 GOOD (HHFFFF) +Size 92: 46,92 GOOD (HHFFFF) +Size 93: 47,93 GOOD (HHFFFF) +Size 94: 47,94 GOOD (HHFFFF) +Size 95: 48,95 GOOD (HHFFFF) +Size 96: 48,96 GOOD (HHFFFF) +Size 97: 49,97 GOOD (HHFFFF) +Size 98: 49,98 GOOD (HHFFFF) +Size 99: 50,99 GOOD (HHFFFF) +Size 100: 50,100 GOOD (HHFFFF) diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP949.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP949.txt new file mode 100644 index 00000000000..2f0ea1e7c2e --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP949.txt @@ -0,0 +1,630 @@ +===================================== +Code Page 949, Korean, GulimChe font +===================================== + +Options: -face-gulimche -family 0x36 +Chars: A2 A3 2014 3044 30FC 4000 + +Vista +----- + +Size 1: 1,2 OK (HHHFFF) +Size 2: 1,2 OK (HHHFFF) +Size 3: 2,3 BAD (FFFFHH) +Size 4: 2,5 OK (HHHFFF) +Size 5: 3,6 BAD (HHHFHH) +Size 6: 3,7 OK (HHHFFF) +Size 7: 4,8 BAD (HHHFHH) +Size 8: 4,9 OK (HHHFFF) +Size 9: 5,10 BAD (HHHFHH) +Size 10: 5,11 OK (HHHFFF) +Size 11: 6,13 BAD (HHHFHH) +Size 12: 6,14 OK (HHHFFF) +Size 13: 7,15 BAD (HHHFHH) +Size 14: 7,16 OK (HHHFFF) +Size 15: 8,17 BAD (HHHFHH) +Size 16: 8,18 OK (HHHFFF) +Size 17: 9,20 BAD (HHHFHH) +Size 18: 9,21 OK (HHHFFF) +Size 19: 10,22 BAD (HHHFHH) +Size 20: 10,23 OK (HHHFFF) +Size 21: 11,24 BAD (HHHFHH) +Size 22: 11,25 OK (HHHFFF) +Size 23: 12,26 BAD (HHHFHH) +Size 24: 12,28 OK (HHHFFF) +Size 25: 13,29 BAD (HHHFHH) +Size 26: 13,30 OK (HHHFFF) +Size 27: 14,31 BAD (HHHFHH) +Size 28: 14,32 OK (HHHFFF) +Size 29: 15,33 BAD (HHHFHH) +Size 30: 15,34 OK (HHHFFF) +Size 31: 16,36 BAD (HHHFHH) +Size 32: 16,37 OK (HHHFFF) +Size 33: 17,38 BAD (HHHFHH) +Size 34: 17,39 OK (HHHFFF) +Size 35: 18,40 BAD (HHHFHH) +Size 36: 18,41 OK (HHHFFF) +Size 37: 19,42 BAD (HHHFHH) +Size 38: 19,44 OK (HHHFFF) +Size 39: 20,45 BAD (HHHFHH) +Size 40: 20,46 OK (HHHFFF) +Size 41: 21,47 BAD (HHHFHH) +Size 42: 21,48 OK (HHHFFF) +Size 43: 22,49 BAD (HHHFHH) +Size 44: 22,51 OK (HHHFFF) +Size 45: 23,52 BAD (HHHFHH) +Size 46: 23,53 OK (HHHFFF) +Size 47: 24,54 BAD (HHHFHH) +Size 48: 24,55 OK (HHHFFF) +Size 49: 25,56 BAD (HHHFHH) +Size 50: 25,57 OK (HHHFFF) +Size 51: 26,59 BAD (HHHFHH) +Size 52: 26,60 OK (HHHFFF) +Size 53: 27,61 BAD (HHHFHH) +Size 54: 27,62 OK (HHHFFF) +Size 55: 28,63 BAD (HHHFHH) +Size 56: 28,64 OK (HHHFFF) +Size 57: 29,65 BAD (HHHFHH) +Size 58: 29,67 OK (HHHFFF) +Size 59: 30,68 BAD (HHHFHH) +Size 60: 30,69 OK (HHHFFF) +Size 61: 31,70 BAD (HHHFHH) +Size 62: 31,71 OK (HHHFFF) +Size 63: 32,72 BAD (HHHFHH) +Size 64: 32,74 OK (HHHFFF) +Size 65: 33,75 BAD (HHHFHH) +Size 66: 33,76 OK (HHHFFF) +Size 67: 34,77 BAD (HHHFHH) +Size 68: 34,78 OK (HHHFFF) +Size 69: 35,79 BAD (HHHFHH) +Size 70: 35,80 OK (HHHFFF) +Size 71: 36,82 BAD (HHHFHH) +Size 72: 36,83 OK (HHHFFF) +Size 73: 37,84 BAD (HHHFHH) +Size 74: 37,85 OK (HHHFFF) +Size 75: 38,86 BAD (HHHFHH) +Size 76: 38,87 OK (HHHFFF) +Size 77: 39,88 BAD (HHHFHH) +Size 78: 39,90 OK (HHHFFF) +Size 79: 40,91 BAD (HHHFHH) +Size 80: 40,92 OK (HHHFFF) +Size 81: 41,93 BAD (HHHFHH) +Size 82: 41,94 OK (HHHFFF) +Size 83: 42,95 BAD (HHHFHH) +Size 84: 42,96 OK (HHHFFF) +Size 85: 43,98 BAD (HHHFHH) +Size 86: 43,99 OK (HHHFFF) +Size 87: 44,100 BAD (HHHFHH) +Size 88: 44,101 OK (HHHFFF) +Size 89: 45,102 BAD (HHHFHH) +Size 90: 45,103 OK (HHHFFF) +Size 91: 46,105 BAD (HHHFHH) +Size 92: 46,106 OK (HHHFFF) +Size 93: 47,107 BAD (HHHFHH) +Size 94: 47,108 OK (HHHFFF) +Size 95: 48,109 BAD (HHHFHH) +Size 96: 48,110 OK (HHHFFF) +Size 97: 49,111 BAD (HHHFHH) +Size 98: 49,113 OK (HHHFFF) +Size 99: 50,114 BAD (HHHFHH) +Size 100: 50,115 OK (HHHFFF) + +Windows 7 +--------- + +Size 1: 1,2 OK (HHHFFF) +Size 2: 1,2 OK (HHHFFF) +Size 3: 2,3 BAD (FFFFHH) +Size 4: 2,5 OK (HHHFFF) +Size 5: 3,6 BAD (FFFFHH) +Size 6: 3,7 OK (HHHFFF) +Size 7: 4,8 BAD (FFFFHH) +Size 8: 4,9 OK (HHHFFF) +Size 9: 5,10 BAD (FFFFHH) +Size 10: 5,11 OK (HHHFFF) +Size 11: 6,13 BAD (FFFFHH) +Size 12: 6,14 OK (HHHFFF) +Size 13: 7,15 BAD (FFFFHH) +Size 14: 7,16 OK (HHHFFF) +Size 15: 8,17 BAD (FFFFHH) +Size 16: 8,18 OK (HHHFFF) +Size 17: 9,20 BAD (FFFFHH) +Size 18: 9,21 OK (HHHFFF) +Size 19: 10,22 BAD (FFFFHH) +Size 20: 10,23 OK (HHHFFF) +Size 21: 11,24 BAD (FFFFHH) +Size 22: 11,25 OK (HHHFFF) +Size 23: 12,26 BAD (FFFFHH) +Size 24: 12,28 OK (HHHFFF) +Size 25: 13,29 BAD (FFFFHH) +Size 26: 13,30 OK (HHHFFF) +Size 27: 14,31 BAD (FFFFHH) +Size 28: 14,32 OK (HHHFFF) +Size 29: 15,33 BAD (FFFFHH) +Size 30: 15,34 OK (HHHFFF) +Size 31: 16,36 BAD (FFFFHH) +Size 32: 16,37 OK (HHHFFF) +Size 33: 17,38 BAD (FFFFHH) +Size 34: 17,39 OK (HHHFFF) +Size 35: 18,40 BAD (FFFFHH) +Size 36: 18,41 OK (HHHFFF) +Size 37: 19,42 BAD (FFFFHH) +Size 38: 19,44 OK (HHHFFF) +Size 39: 20,45 BAD (FFFFHH) +Size 40: 20,46 OK (HHHFFF) +Size 41: 21,47 BAD (FFFFHH) +Size 42: 21,48 OK (HHHFFF) +Size 43: 22,49 BAD (FFFFHH) +Size 44: 22,51 OK (HHHFFF) +Size 45: 23,52 BAD (FFFFHH) +Size 46: 23,53 OK (HHHFFF) +Size 47: 24,54 BAD (FFFFHH) +Size 48: 24,55 OK (HHHFFF) +Size 49: 25,56 BAD (FFFFHH) +Size 50: 25,57 OK (HHHFFF) +Size 51: 26,59 BAD (FFFFHH) +Size 52: 26,60 OK (HHHFFF) +Size 53: 27,61 BAD (FFFFHH) +Size 54: 27,62 OK (HHHFFF) +Size 55: 28,63 BAD (FFFFHH) +Size 56: 28,64 OK (HHHFFF) +Size 57: 29,65 BAD (FFFFHH) +Size 58: 29,67 OK (HHHFFF) +Size 59: 30,68 BAD (FFFFHH) +Size 60: 30,69 OK (HHHFFF) +Size 61: 31,70 BAD (FFFFHH) +Size 62: 31,71 OK (HHHFFF) +Size 63: 32,72 BAD (FFFFHH) +Size 64: 32,74 OK (HHHFFF) +Size 65: 33,75 BAD (FFFFHH) +Size 66: 33,76 OK (HHHFFF) +Size 67: 34,77 BAD (FFFFHH) +Size 68: 34,78 OK (HHHFFF) +Size 69: 35,79 BAD (FFFFHH) +Size 70: 35,80 OK (HHHFFF) +Size 71: 36,82 BAD (FFFFHH) +Size 72: 36,83 OK (HHHFFF) +Size 73: 37,84 BAD (FFFFHH) +Size 74: 37,85 OK (HHHFFF) +Size 75: 38,86 BAD (FFFFHH) +Size 76: 38,87 OK (HHHFFF) +Size 77: 39,88 BAD (FFFFHH) +Size 78: 39,90 OK (HHHFFF) +Size 79: 40,91 BAD (FFFFHH) +Size 80: 40,92 OK (HHHFFF) +Size 81: 41,93 BAD (FFFFHH) +Size 82: 41,94 OK (HHHFFF) +Size 83: 42,95 BAD (FFFFHH) +Size 84: 42,96 OK (HHHFFF) +Size 85: 43,98 BAD (FFFFHH) +Size 86: 43,99 OK (HHHFFF) +Size 87: 44,100 BAD (FFFFHH) +Size 88: 44,101 OK (HHHFFF) +Size 89: 45,102 BAD (FFFFHH) +Size 90: 45,103 OK (HHHFFF) +Size 91: 46,105 BAD (FFFFHH) +Size 92: 46,106 OK (HHHFFF) +Size 93: 47,107 BAD (FFFFHH) +Size 94: 47,108 OK (HHHFFF) +Size 95: 48,109 BAD (FFFFHH) +Size 96: 48,110 OK (HHHFFF) +Size 97: 49,111 BAD (FFFFHH) +Size 98: 49,113 OK (HHHFFF) +Size 99: 50,114 BAD (FFFFHH) +Size 100: 50,115 OK (HHHFFF) + +Windows 8 +--------- + +Size 1: 1,2 OK (HHHFFF) +Size 2: 1,2 OK (HHHFFF) +Size 3: 2,3 BAD (FFFFHH) +Size 4: 2,5 OK (HHHFFF) +Size 5: 3,6 BAD (FFFFHH) +Size 6: 3,7 OK (HHHFFF) +Size 7: 4,8 BAD (FFFFHH) +Size 8: 4,9 OK (HHHFFF) +Size 9: 5,10 BAD (FFFFHH) +Size 10: 5,11 OK (HHHFFF) +Size 11: 6,13 BAD (FFFFHH) +Size 12: 6,14 OK (HHHFFF) +Size 13: 7,15 BAD (FFFFHH) +Size 14: 7,16 OK (HHHFFF) +Size 15: 8,17 BAD (FFFFHH) +Size 16: 8,18 OK (HHHFFF) +Size 17: 9,20 BAD (FFFFHH) +Size 18: 9,21 OK (HHHFFF) +Size 19: 10,22 BAD (FFFFHH) +Size 20: 10,23 OK (HHHFFF) +Size 21: 11,24 BAD (FFFFHH) +Size 22: 11,25 OK (HHHFFF) +Size 23: 12,26 BAD (FFFFHH) +Size 24: 12,28 OK (HHHFFF) +Size 25: 13,29 BAD (FFFFHH) +Size 26: 13,30 OK (HHHFFF) +Size 27: 14,31 BAD (FFFFHH) +Size 28: 14,32 OK (HHHFFF) +Size 29: 15,33 BAD (FFFFHH) +Size 30: 15,34 OK (HHHFFF) +Size 31: 16,36 BAD (FFFFHH) +Size 32: 16,37 OK (HHHFFF) +Size 33: 17,38 BAD (FFFFHH) +Size 34: 17,39 OK (HHHFFF) +Size 35: 18,40 BAD (FFFFHH) +Size 36: 18,41 OK (HHHFFF) +Size 37: 19,42 BAD (FFFFHH) +Size 38: 19,44 OK (HHHFFF) +Size 39: 20,45 BAD (FFFFHH) +Size 40: 20,46 OK (HHHFFF) +Size 41: 21,47 BAD (FFFFHH) +Size 42: 21,48 OK (HHHFFF) +Size 43: 22,49 BAD (FFFFHH) +Size 44: 22,51 OK (HHHFFF) +Size 45: 23,52 BAD (FFFFHH) +Size 46: 23,53 OK (HHHFFF) +Size 47: 24,54 BAD (FFFFHH) +Size 48: 24,55 OK (HHHFFF) +Size 49: 25,56 BAD (FFFFHH) +Size 50: 25,57 OK (HHHFFF) +Size 51: 26,59 BAD (FFFFHH) +Size 52: 26,60 OK (HHHFFF) +Size 53: 27,61 BAD (FFFFHH) +Size 54: 27,62 OK (HHHFFF) +Size 55: 28,63 BAD (FFFFHH) +Size 56: 28,64 OK (HHHFFF) +Size 57: 29,65 BAD (FFFFHH) +Size 58: 29,67 OK (HHHFFF) +Size 59: 30,68 BAD (FFFFHH) +Size 60: 30,69 OK (HHHFFF) +Size 61: 31,70 BAD (FFFFHH) +Size 62: 31,71 OK (HHHFFF) +Size 63: 32,72 BAD (FFFFHH) +Size 64: 32,74 OK (HHHFFF) +Size 65: 33,75 BAD (FFFFHH) +Size 66: 33,76 OK (HHHFFF) +Size 67: 34,77 BAD (FFFFHH) +Size 68: 34,78 OK (HHHFFF) +Size 69: 35,79 BAD (FFFFHH) +Size 70: 35,80 OK (HHHFFF) +Size 71: 36,82 BAD (FFFFHH) +Size 72: 36,83 OK (HHHFFF) +Size 73: 37,84 BAD (FFFFHH) +Size 74: 37,85 OK (HHHFFF) +Size 75: 38,86 BAD (FFFFHH) +Size 76: 38,87 OK (HHHFFF) +Size 77: 39,88 BAD (FFFFHH) +Size 78: 39,90 OK (HHHFFF) +Size 79: 40,91 BAD (FFFFHH) +Size 80: 40,92 OK (HHHFFF) +Size 81: 41,93 BAD (FFFFHH) +Size 82: 41,94 OK (HHHFFF) +Size 83: 42,95 BAD (FFFFHH) +Size 84: 42,96 OK (HHHFFF) +Size 85: 43,98 BAD (FFFFHH) +Size 86: 43,99 OK (HHHFFF) +Size 87: 44,100 BAD (FFFFHH) +Size 88: 44,101 OK (HHHFFF) +Size 89: 45,102 BAD (FFFFHH) +Size 90: 45,103 OK (HHHFFF) +Size 91: 46,105 BAD (FFFFHH) +Size 92: 46,106 OK (HHHFFF) +Size 93: 47,107 BAD (FFFFHH) +Size 94: 47,108 OK (HHHFFF) +Size 95: 48,109 BAD (FFFFHH) +Size 96: 48,110 OK (HHHFFF) +Size 97: 49,111 BAD (FFFFHH) +Size 98: 49,113 OK (HHHFFF) +Size 99: 50,114 BAD (FFFFHH) +Size 100: 50,115 OK (HHHFFF) + +Windows 8.1 +----------- + +Size 1: 1,2 OK (HHHFFF) +Size 2: 1,2 OK (HHHFFF) +Size 3: 2,3 BAD (FFFFHH) +Size 4: 2,5 OK (HHHFFF) +Size 5: 3,6 BAD (FFFFHH) +Size 6: 3,7 OK (HHHFFF) +Size 7: 4,8 BAD (FFFFHH) +Size 8: 4,9 OK (HHHFFF) +Size 9: 5,10 BAD (FFFFHH) +Size 10: 5,11 OK (HHHFFF) +Size 11: 6,13 BAD (FFFFHH) +Size 12: 6,14 OK (HHHFFF) +Size 13: 7,15 BAD (FFFFHH) +Size 14: 7,16 OK (HHHFFF) +Size 15: 8,17 BAD (FFFFHH) +Size 16: 8,18 OK (HHHFFF) +Size 17: 9,20 BAD (FFFFHH) +Size 18: 9,21 OK (HHHFFF) +Size 19: 10,22 BAD (FFFFHH) +Size 20: 10,23 OK (HHHFFF) +Size 21: 11,24 BAD (FFFFHH) +Size 22: 11,25 OK (HHHFFF) +Size 23: 12,26 BAD (FFFFHH) +Size 24: 12,28 OK (HHHFFF) +Size 25: 13,29 BAD (FFFFHH) +Size 26: 13,30 OK (HHHFFF) +Size 27: 14,31 BAD (FFFFHH) +Size 28: 14,32 OK (HHHFFF) +Size 29: 15,33 BAD (FFFFHH) +Size 30: 15,34 OK (HHHFFF) +Size 31: 16,36 BAD (FFFFHH) +Size 32: 16,37 OK (HHHFFF) +Size 33: 17,38 BAD (FFFFHH) +Size 34: 17,39 OK (HHHFFF) +Size 35: 18,40 BAD (FFFFHH) +Size 36: 18,41 OK (HHHFFF) +Size 37: 19,42 BAD (FFFFHH) +Size 38: 19,44 OK (HHHFFF) +Size 39: 20,45 BAD (FFFFHH) +Size 40: 20,46 OK (HHHFFF) +Size 41: 21,47 BAD (FFFFHH) +Size 42: 21,48 OK (HHHFFF) +Size 43: 22,49 BAD (FFFFHH) +Size 44: 22,51 OK (HHHFFF) +Size 45: 23,52 BAD (FFFFHH) +Size 46: 23,53 OK (HHHFFF) +Size 47: 24,54 BAD (FFFFHH) +Size 48: 24,55 OK (HHHFFF) +Size 49: 25,56 BAD (FFFFHH) +Size 50: 25,57 OK (HHHFFF) +Size 51: 26,59 BAD (FFFFHH) +Size 52: 26,60 OK (HHHFFF) +Size 53: 27,61 BAD (FFFFHH) +Size 54: 27,62 OK (HHHFFF) +Size 55: 28,63 BAD (FFFFHH) +Size 56: 28,64 OK (HHHFFF) +Size 57: 29,65 BAD (FFFFHH) +Size 58: 29,67 OK (HHHFFF) +Size 59: 30,68 BAD (FFFFHH) +Size 60: 30,69 OK (HHHFFF) +Size 61: 31,70 BAD (FFFFHH) +Size 62: 31,71 OK (HHHFFF) +Size 63: 32,72 BAD (FFFFHH) +Size 64: 32,74 OK (HHHFFF) +Size 65: 33,75 BAD (FFFFHH) +Size 66: 33,76 OK (HHHFFF) +Size 67: 34,77 BAD (FFFFHH) +Size 68: 34,78 OK (HHHFFF) +Size 69: 35,79 BAD (FFFFHH) +Size 70: 35,80 OK (HHHFFF) +Size 71: 36,82 BAD (FFFFHH) +Size 72: 36,83 OK (HHHFFF) +Size 73: 37,84 BAD (FFFFHH) +Size 74: 37,85 OK (HHHFFF) +Size 75: 38,86 BAD (FFFFHH) +Size 76: 38,87 OK (HHHFFF) +Size 77: 39,88 BAD (FFFFHH) +Size 78: 39,90 OK (HHHFFF) +Size 79: 40,91 BAD (FFFFHH) +Size 80: 40,92 OK (HHHFFF) +Size 81: 41,93 BAD (FFFFHH) +Size 82: 41,94 OK (HHHFFF) +Size 83: 42,95 BAD (FFFFHH) +Size 84: 42,96 OK (HHHFFF) +Size 85: 43,98 BAD (FFFFHH) +Size 86: 43,99 OK (HHHFFF) +Size 87: 44,100 BAD (FFFFHH) +Size 88: 44,101 OK (HHHFFF) +Size 89: 45,102 BAD (FFFFHH) +Size 90: 45,103 OK (HHHFFF) +Size 91: 46,105 BAD (FFFFHH) +Size 92: 46,106 OK (HHHFFF) +Size 93: 47,107 BAD (FFFFHH) +Size 94: 47,108 OK (HHHFFF) +Size 95: 48,109 BAD (FFFFHH) +Size 96: 48,110 OK (HHHFFF) +Size 97: 49,111 BAD (FFFFHH) +Size 98: 49,113 OK (HHHFFF) +Size 99: 50,114 BAD (FFFFHH) +Size 100: 50,115 OK (HHHFFF) + +Windows 10 14342 Old Console +---------------------------- + +Size 1: 1,2 OK (HHHFFF) +Size 2: 1,2 OK (HHHFFF) +Size 3: 2,3 BAD (FFFFHH) +Size 4: 2,5 OK (HHHFFF) +Size 5: 3,6 BAD (FFFFHH) +Size 6: 3,7 OK (HHHFFF) +Size 7: 4,8 BAD (FFFFHH) +Size 8: 4,9 OK (HHHFFF) +Size 9: 5,10 BAD (FFFFHH) +Size 10: 5,11 OK (HHHFFF) +Size 11: 6,13 BAD (FFFFHH) +Size 12: 6,14 OK (HHHFFF) +Size 13: 7,15 BAD (FFFFHH) +Size 14: 7,16 OK (HHHFFF) +Size 15: 8,17 BAD (FFFFHH) +Size 16: 8,18 OK (HHHFFF) +Size 17: 9,20 BAD (FFFFHH) +Size 18: 9,21 OK (HHHFFF) +Size 19: 10,22 BAD (FFFFHH) +Size 20: 10,23 OK (HHHFFF) +Size 21: 11,24 BAD (FFFFHH) +Size 22: 11,25 OK (HHHFFF) +Size 23: 12,26 BAD (FFFFHH) +Size 24: 12,28 OK (HHHFFF) +Size 25: 13,29 BAD (FFFFHH) +Size 26: 13,30 OK (HHHFFF) +Size 27: 14,31 BAD (FFFFHH) +Size 28: 14,32 OK (HHHFFF) +Size 29: 15,33 BAD (FFFFHH) +Size 30: 15,34 OK (HHHFFF) +Size 31: 16,36 BAD (FFFFHH) +Size 32: 16,37 OK (HHHFFF) +Size 33: 17,38 BAD (FFFFHH) +Size 34: 17,39 OK (HHHFFF) +Size 35: 18,40 BAD (FFFFHH) +Size 36: 18,41 OK (HHHFFF) +Size 37: 19,42 BAD (FFFFHH) +Size 38: 19,44 OK (HHHFFF) +Size 39: 20,45 BAD (FFFFHH) +Size 40: 20,46 OK (HHHFFF) +Size 41: 21,47 BAD (FFFFHH) +Size 42: 21,48 OK (HHHFFF) +Size 43: 22,49 BAD (FFFFHH) +Size 44: 22,51 OK (HHHFFF) +Size 45: 23,52 BAD (FFFFHH) +Size 46: 23,53 OK (HHHFFF) +Size 47: 24,54 BAD (FFFFHH) +Size 48: 24,55 OK (HHHFFF) +Size 49: 25,56 BAD (FFFFHH) +Size 50: 25,57 OK (HHHFFF) +Size 51: 26,59 BAD (FFFFHH) +Size 52: 26,60 OK (HHHFFF) +Size 53: 27,61 BAD (FFFFHH) +Size 54: 27,62 OK (HHHFFF) +Size 55: 28,63 BAD (FFFFHH) +Size 56: 28,64 OK (HHHFFF) +Size 57: 29,65 BAD (FFFFHH) +Size 58: 29,67 OK (HHHFFF) +Size 59: 30,68 BAD (FFFFHH) +Size 60: 30,69 OK (HHHFFF) +Size 61: 31,70 BAD (FFFFHH) +Size 62: 31,71 OK (HHHFFF) +Size 63: 32,72 BAD (FFFFHH) +Size 64: 32,74 OK (HHHFFF) +Size 65: 33,75 BAD (FFFFHH) +Size 66: 33,76 OK (HHHFFF) +Size 67: 34,77 BAD (FFFFHH) +Size 68: 34,78 OK (HHHFFF) +Size 69: 35,79 BAD (FFFFHH) +Size 70: 35,80 OK (HHHFFF) +Size 71: 36,82 BAD (FFFFHH) +Size 72: 36,83 OK (HHHFFF) +Size 73: 37,84 BAD (FFFFHH) +Size 74: 37,85 OK (HHHFFF) +Size 75: 38,86 BAD (FFFFHH) +Size 76: 38,87 OK (HHHFFF) +Size 77: 39,88 BAD (FFFFHH) +Size 78: 39,90 OK (HHHFFF) +Size 79: 40,91 BAD (FFFFHH) +Size 80: 40,92 OK (HHHFFF) +Size 81: 41,93 BAD (FFFFHH) +Size 82: 41,94 OK (HHHFFF) +Size 83: 42,95 BAD (FFFFHH) +Size 84: 42,96 OK (HHHFFF) +Size 85: 43,98 BAD (FFFFHH) +Size 86: 43,99 OK (HHHFFF) +Size 87: 44,100 BAD (FFFFHH) +Size 88: 44,101 OK (HHHFFF) +Size 89: 45,102 BAD (FFFFHH) +Size 90: 45,103 OK (HHHFFF) +Size 91: 46,105 BAD (FFFFHH) +Size 92: 46,106 OK (HHHFFF) +Size 93: 47,107 BAD (FFFFHH) +Size 94: 47,108 OK (HHHFFF) +Size 95: 48,109 BAD (FFFFHH) +Size 96: 48,110 OK (HHHFFF) +Size 97: 49,111 BAD (FFFFHH) +Size 98: 49,113 OK (HHHFFF) +Size 99: 50,114 BAD (FFFFHH) +Size 100: 50,115 OK (HHHFFF) + +Windows 10 14342 New Console +---------------------------- + +Size 1: 1,1 OK (HHHFFF) +Size 2: 1,2 OK (HHHFFF) +Size 3: 2,3 OK (HHHFFF) +Size 4: 2,4 OK (HHHFFF) +Size 5: 3,5 OK (HHHFFF) +Size 6: 3,6 OK (HHHFFF) +Size 7: 4,7 OK (HHHFFF) +Size 8: 4,8 OK (HHHFFF) +Size 9: 5,9 OK (HHHFFF) +Size 10: 5,10 OK (HHHFFF) +Size 11: 6,11 OK (HHHFFF) +Size 12: 6,12 OK (HHHFFF) +Size 13: 7,13 OK (HHHFFF) +Size 14: 7,14 OK (HHHFFF) +Size 15: 8,15 OK (HHHFFF) +Size 16: 8,16 OK (HHHFFF) +Size 17: 9,17 OK (HHHFFF) +Size 18: 9,18 OK (HHHFFF) +Size 19: 10,19 OK (HHHFFF) +Size 20: 10,20 OK (HHHFFF) +Size 21: 11,21 OK (HHHFFF) +Size 22: 11,22 OK (HHHFFF) +Size 23: 12,23 OK (HHHFFF) +Size 24: 12,24 OK (HHHFFF) +Size 25: 13,25 OK (HHHFFF) +Size 26: 13,26 OK (HHHFFF) +Size 27: 14,27 OK (HHHFFF) +Size 28: 14,28 OK (HHHFFF) +Size 29: 15,29 OK (HHHFFF) +Size 30: 15,30 OK (HHHFFF) +Size 31: 16,31 OK (HHHFFF) +Size 32: 16,32 OK (HHHFFF) +Size 33: 17,33 OK (HHHFFF) +Size 34: 17,34 OK (HHHFFF) +Size 35: 18,35 OK (HHHFFF) +Size 36: 18,36 OK (HHHFFF) +Size 37: 19,37 OK (HHHFFF) +Size 38: 19,38 OK (HHHFFF) +Size 39: 20,39 OK (HHHFFF) +Size 40: 20,40 OK (HHHFFF) +Size 41: 21,41 OK (HHHFFF) +Size 42: 21,42 OK (HHHFFF) +Size 43: 22,43 OK (HHHFFF) +Size 44: 22,44 OK (HHHFFF) +Size 45: 23,45 OK (HHHFFF) +Size 46: 23,46 OK (HHHFFF) +Size 47: 24,47 OK (HHHFFF) +Size 48: 24,48 OK (HHHFFF) +Size 49: 25,49 OK (HHHFFF) +Size 50: 25,50 OK (HHHFFF) +Size 51: 26,51 OK (HHHFFF) +Size 52: 26,52 OK (HHHFFF) +Size 53: 27,53 OK (HHHFFF) +Size 54: 27,54 OK (HHHFFF) +Size 55: 28,55 OK (HHHFFF) +Size 56: 28,56 OK (HHHFFF) +Size 57: 29,57 OK (HHHFFF) +Size 58: 29,58 OK (HHHFFF) +Size 59: 30,59 OK (HHHFFF) +Size 60: 30,60 OK (HHHFFF) +Size 61: 31,61 OK (HHHFFF) +Size 62: 31,62 OK (HHHFFF) +Size 63: 32,63 OK (HHHFFF) +Size 64: 32,64 OK (HHHFFF) +Size 65: 33,65 OK (HHHFFF) +Size 66: 33,66 OK (HHHFFF) +Size 67: 34,67 OK (HHHFFF) +Size 68: 34,68 OK (HHHFFF) +Size 69: 35,69 OK (HHHFFF) +Size 70: 35,70 OK (HHHFFF) +Size 71: 36,71 OK (HHHFFF) +Size 72: 36,72 OK (HHHFFF) +Size 73: 37,73 OK (HHHFFF) +Size 74: 37,74 OK (HHHFFF) +Size 75: 38,75 OK (HHHFFF) +Size 76: 38,76 OK (HHHFFF) +Size 77: 39,77 OK (HHHFFF) +Size 78: 39,78 OK (HHHFFF) +Size 79: 40,79 OK (HHHFFF) +Size 80: 40,80 OK (HHHFFF) +Size 81: 41,81 OK (HHHFFF) +Size 82: 41,82 OK (HHHFFF) +Size 83: 42,83 OK (HHHFFF) +Size 84: 42,84 OK (HHHFFF) +Size 85: 43,85 OK (HHHFFF) +Size 86: 43,86 OK (HHHFFF) +Size 87: 44,87 OK (HHHFFF) +Size 88: 44,88 OK (HHHFFF) +Size 89: 45,89 OK (HHHFFF) +Size 90: 45,90 OK (HHHFFF) +Size 91: 46,91 OK (HHHFFF) +Size 92: 46,92 OK (HHHFFF) +Size 93: 47,93 OK (HHHFFF) +Size 94: 47,94 OK (HHHFFF) +Size 95: 48,95 OK (HHHFFF) +Size 96: 48,96 OK (HHHFFF) +Size 97: 49,97 OK (HHHFFF) +Size 98: 49,98 OK (HHHFFF) +Size 99: 50,99 OK (HHHFFF) +Size 100: 50,100 OK (HHHFFF) diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP950.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP950.txt new file mode 100644 index 00000000000..0dbade504db --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP950.txt @@ -0,0 +1,630 @@ +=========================================================== +Code Page 950, Chinese Traditional (Taiwan), MingLight font +=========================================================== + +Options: -face-minglight -family 0x36 +Chars: A2 A3 2014 3044 30FC 4000 + +Vista +----- + +Size 1: 1,2 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,4 BAD (FFHFHH) +Size 4: 2,5 GOOD (HHFFFF) +Size 5: 3,6 BAD (HHHFHH) +Size 6: 3,7 GOOD (HHFFFF) +Size 7: 4,8 BAD (HHHFHH) +Size 8: 4,10 GOOD (HHFFFF) +Size 9: 5,11 BAD (HHHFHH) +Size 10: 5,12 GOOD (HHFFFF) +Size 11: 6,13 BAD (HHHFHH) +Size 12: 6,14 GOOD (HHFFFF) +Size 13: 7,16 BAD (HHHFHH) +Size 14: 7,17 GOOD (HHFFFF) +Size 15: 8,18 BAD (HHHFHH) +Size 16: 8,19 GOOD (HHFFFF) +Size 17: 9,20 BAD (HHHFHH) +Size 18: 9,22 GOOD (HHFFFF) +Size 19: 10,23 BAD (HHHFHH) +Size 20: 10,24 GOOD (HHFFFF) +Size 21: 11,25 BAD (HHHFHH) +Size 22: 11,26 GOOD (HHFFFF) +Size 23: 12,28 BAD (HHHFHH) +Size 24: 12,29 GOOD (HHFFFF) +Size 25: 13,30 BAD (HHHFHH) +Size 26: 13,31 GOOD (HHFFFF) +Size 27: 14,32 BAD (HHHFHH) +Size 28: 14,34 GOOD (HHFFFF) +Size 29: 15,35 BAD (HHHFHH) +Size 30: 15,36 GOOD (HHFFFF) +Size 31: 16,37 BAD (HHHFHH) +Size 32: 16,38 GOOD (HHFFFF) +Size 33: 17,40 BAD (HHHFHH) +Size 34: 17,41 GOOD (HHFFFF) +Size 35: 18,42 BAD (HHHFHH) +Size 36: 18,43 GOOD (HHFFFF) +Size 37: 19,44 BAD (HHHFHH) +Size 38: 19,46 GOOD (HHFFFF) +Size 39: 20,47 BAD (HHHFHH) +Size 40: 20,48 GOOD (HHFFFF) +Size 41: 21,49 BAD (HHHFHH) +Size 42: 21,50 GOOD (HHFFFF) +Size 43: 22,52 BAD (HHHFHH) +Size 44: 22,53 GOOD (HHFFFF) +Size 45: 23,54 BAD (HHHFHH) +Size 46: 23,55 GOOD (HHFFFF) +Size 47: 24,56 BAD (HHHFHH) +Size 48: 24,58 GOOD (HHFFFF) +Size 49: 25,59 BAD (HHHFHH) +Size 50: 25,60 GOOD (HHFFFF) +Size 51: 26,61 BAD (HHHFHH) +Size 52: 26,62 GOOD (HHFFFF) +Size 53: 27,64 BAD (HHHFHH) +Size 54: 27,65 GOOD (HHFFFF) +Size 55: 28,66 BAD (HHHFHH) +Size 56: 28,67 GOOD (HHFFFF) +Size 57: 29,68 BAD (HHHFHH) +Size 58: 29,70 GOOD (HHFFFF) +Size 59: 30,71 BAD (HHHFHH) +Size 60: 30,72 GOOD (HHFFFF) +Size 61: 31,73 BAD (HHHFHH) +Size 62: 31,74 GOOD (HHFFFF) +Size 63: 32,76 BAD (HHHFHH) +Size 64: 32,77 GOOD (HHFFFF) +Size 65: 33,78 BAD (HHHFHH) +Size 66: 33,79 GOOD (HHFFFF) +Size 67: 34,80 BAD (HHHFHH) +Size 68: 34,82 GOOD (HHFFFF) +Size 69: 35,83 BAD (HHHFHH) +Size 70: 35,84 GOOD (HHFFFF) +Size 71: 36,85 BAD (HHHFHH) +Size 72: 36,86 GOOD (HHFFFF) +Size 73: 37,88 BAD (HHHFHH) +Size 74: 37,89 GOOD (HHFFFF) +Size 75: 38,90 BAD (HHHFHH) +Size 76: 38,91 GOOD (HHFFFF) +Size 77: 39,92 BAD (HHHFHH) +Size 78: 39,94 GOOD (HHFFFF) +Size 79: 40,95 BAD (HHHFHH) +Size 80: 40,96 GOOD (HHFFFF) +Size 81: 41,97 BAD (HHHFHH) +Size 82: 41,98 GOOD (HHFFFF) +Size 83: 42,100 BAD (HHHFHH) +Size 84: 42,101 GOOD (HHFFFF) +Size 85: 43,102 BAD (HHHFHH) +Size 86: 43,103 GOOD (HHFFFF) +Size 87: 44,104 BAD (HHHFHH) +Size 88: 44,106 GOOD (HHFFFF) +Size 89: 45,107 BAD (HHHFHH) +Size 90: 45,108 GOOD (HHFFFF) +Size 91: 46,109 BAD (HHHFHH) +Size 92: 46,110 GOOD (HHFFFF) +Size 93: 47,112 BAD (HHHFHH) +Size 94: 47,113 GOOD (HHFFFF) +Size 95: 48,114 BAD (HHHFHH) +Size 96: 48,115 GOOD (HHFFFF) +Size 97: 49,116 BAD (HHHFHH) +Size 98: 49,118 GOOD (HHFFFF) +Size 99: 50,119 BAD (HHHFHH) +Size 100: 50,120 GOOD (HHFFFF) + +Windows 7 +--------- + +Size 1: 1,2 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,4 BAD (FFHFHH) +Size 4: 2,5 GOOD (HHFFFF) +Size 5: 3,6 BAD (FFHFHH) +Size 6: 3,7 GOOD (HHFFFF) +Size 7: 4,8 BAD (FFHFHH) +Size 8: 4,10 GOOD (HHFFFF) +Size 9: 5,11 BAD (FFHFHH) +Size 10: 5,12 GOOD (HHFFFF) +Size 11: 6,13 BAD (FFHFHH) +Size 12: 6,14 GOOD (HHFFFF) +Size 13: 7,16 BAD (FFHFHH) +Size 14: 7,17 GOOD (HHFFFF) +Size 15: 8,18 BAD (FFHFHH) +Size 16: 8,19 GOOD (HHFFFF) +Size 17: 9,20 BAD (FFHFHH) +Size 18: 9,22 GOOD (HHFFFF) +Size 19: 10,23 BAD (FFHFHH) +Size 20: 10,24 GOOD (HHFFFF) +Size 21: 11,25 BAD (FFHFHH) +Size 22: 11,26 GOOD (HHFFFF) +Size 23: 12,28 BAD (FFHFHH) +Size 24: 12,29 GOOD (HHFFFF) +Size 25: 13,30 BAD (FFHFHH) +Size 26: 13,31 GOOD (HHFFFF) +Size 27: 14,32 BAD (FFHFHH) +Size 28: 14,34 GOOD (HHFFFF) +Size 29: 15,35 BAD (FFHFHH) +Size 30: 15,36 GOOD (HHFFFF) +Size 31: 16,37 BAD (FFHFHH) +Size 32: 16,38 GOOD (HHFFFF) +Size 33: 17,40 BAD (FFHFHH) +Size 34: 17,41 GOOD (HHFFFF) +Size 35: 18,42 BAD (FFHFHH) +Size 36: 18,43 GOOD (HHFFFF) +Size 37: 19,44 BAD (FFHFHH) +Size 38: 19,46 GOOD (HHFFFF) +Size 39: 20,47 BAD (FFHFHH) +Size 40: 20,48 GOOD (HHFFFF) +Size 41: 21,49 BAD (FFHFHH) +Size 42: 21,50 GOOD (HHFFFF) +Size 43: 22,52 BAD (FFHFHH) +Size 44: 22,53 GOOD (HHFFFF) +Size 45: 23,54 BAD (FFHFHH) +Size 46: 23,55 GOOD (HHFFFF) +Size 47: 24,56 BAD (FFHFHH) +Size 48: 24,58 GOOD (HHFFFF) +Size 49: 25,59 BAD (FFHFHH) +Size 50: 25,60 GOOD (HHFFFF) +Size 51: 26,61 BAD (FFHFHH) +Size 52: 26,62 GOOD (HHFFFF) +Size 53: 27,64 BAD (FFHFHH) +Size 54: 27,65 GOOD (HHFFFF) +Size 55: 28,66 BAD (FFHFHH) +Size 56: 28,67 GOOD (HHFFFF) +Size 57: 29,68 BAD (FFHFHH) +Size 58: 29,70 GOOD (HHFFFF) +Size 59: 30,71 BAD (FFHFHH) +Size 60: 30,72 GOOD (HHFFFF) +Size 61: 31,73 BAD (FFHFHH) +Size 62: 31,74 GOOD (HHFFFF) +Size 63: 32,76 BAD (FFHFHH) +Size 64: 32,77 GOOD (HHFFFF) +Size 65: 33,78 BAD (FFHFHH) +Size 66: 33,79 GOOD (HHFFFF) +Size 67: 34,80 BAD (FFHFHH) +Size 68: 34,82 GOOD (HHFFFF) +Size 69: 35,83 BAD (FFHFHH) +Size 70: 35,84 GOOD (HHFFFF) +Size 71: 36,85 BAD (FFHFHH) +Size 72: 36,86 GOOD (HHFFFF) +Size 73: 37,88 BAD (FFHFHH) +Size 74: 37,89 GOOD (HHFFFF) +Size 75: 38,90 BAD (FFHFHH) +Size 76: 38,91 GOOD (HHFFFF) +Size 77: 39,92 BAD (FFHFHH) +Size 78: 39,94 GOOD (HHFFFF) +Size 79: 40,95 BAD (FFHFHH) +Size 80: 40,96 GOOD (HHFFFF) +Size 81: 41,97 BAD (FFHFHH) +Size 82: 41,98 GOOD (HHFFFF) +Size 83: 42,100 BAD (FFHFHH) +Size 84: 42,101 GOOD (HHFFFF) +Size 85: 43,102 BAD (FFHFHH) +Size 86: 43,103 GOOD (HHFFFF) +Size 87: 44,104 BAD (FFHFHH) +Size 88: 44,106 GOOD (HHFFFF) +Size 89: 45,107 BAD (FFHFHH) +Size 90: 45,108 GOOD (HHFFFF) +Size 91: 46,109 BAD (FFHFHH) +Size 92: 46,110 GOOD (HHFFFF) +Size 93: 47,112 BAD (FFHFHH) +Size 94: 47,113 GOOD (HHFFFF) +Size 95: 48,114 BAD (FFHFHH) +Size 96: 48,115 GOOD (HHFFFF) +Size 97: 49,116 BAD (FFHFHH) +Size 98: 49,118 GOOD (HHFFFF) +Size 99: 50,119 BAD (FFHFHH) +Size 100: 50,120 GOOD (HHFFFF) + +Windows 8 +--------- + +Size 1: 1,2 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,4 BAD (FFHFHH) +Size 4: 2,5 GOOD (HHFFFF) +Size 5: 3,6 BAD (FFHFHH) +Size 6: 3,7 GOOD (HHFFFF) +Size 7: 4,8 BAD (FFHFHH) +Size 8: 4,10 GOOD (HHFFFF) +Size 9: 5,11 BAD (FFHFHH) +Size 10: 5,12 GOOD (HHFFFF) +Size 11: 6,13 BAD (FFHFHH) +Size 12: 6,14 GOOD (HHFFFF) +Size 13: 7,16 BAD (FFHFHH) +Size 14: 7,17 GOOD (HHFFFF) +Size 15: 8,18 BAD (FFHFHH) +Size 16: 8,19 GOOD (HHFFFF) +Size 17: 9,20 BAD (FFHFHH) +Size 18: 9,22 GOOD (HHFFFF) +Size 19: 10,23 BAD (FFHFHH) +Size 20: 10,24 GOOD (HHFFFF) +Size 21: 11,25 BAD (FFHFHH) +Size 22: 11,26 GOOD (HHFFFF) +Size 23: 12,28 BAD (FFHFHH) +Size 24: 12,29 GOOD (HHFFFF) +Size 25: 13,30 BAD (FFHFHH) +Size 26: 13,31 GOOD (HHFFFF) +Size 27: 14,32 BAD (FFHFHH) +Size 28: 14,34 GOOD (HHFFFF) +Size 29: 15,35 BAD (FFHFHH) +Size 30: 15,36 GOOD (HHFFFF) +Size 31: 16,37 BAD (FFHFHH) +Size 32: 16,38 GOOD (HHFFFF) +Size 33: 17,40 BAD (FFHFHH) +Size 34: 17,41 GOOD (HHFFFF) +Size 35: 18,42 BAD (FFHFHH) +Size 36: 18,43 GOOD (HHFFFF) +Size 37: 19,44 BAD (FFHFHH) +Size 38: 19,46 GOOD (HHFFFF) +Size 39: 20,47 BAD (FFHFHH) +Size 40: 20,48 GOOD (HHFFFF) +Size 41: 21,49 BAD (FFHFHH) +Size 42: 21,50 GOOD (HHFFFF) +Size 43: 22,52 BAD (FFHFHH) +Size 44: 22,53 GOOD (HHFFFF) +Size 45: 23,54 BAD (FFHFHH) +Size 46: 23,55 GOOD (HHFFFF) +Size 47: 24,56 BAD (FFHFHH) +Size 48: 24,58 GOOD (HHFFFF) +Size 49: 25,59 BAD (FFHFHH) +Size 50: 25,60 GOOD (HHFFFF) +Size 51: 26,61 BAD (FFHFHH) +Size 52: 26,62 GOOD (HHFFFF) +Size 53: 27,64 BAD (FFHFHH) +Size 54: 27,65 GOOD (HHFFFF) +Size 55: 28,66 BAD (FFHFHH) +Size 56: 28,67 GOOD (HHFFFF) +Size 57: 29,68 BAD (FFHFHH) +Size 58: 29,70 GOOD (HHFFFF) +Size 59: 30,71 BAD (FFHFHH) +Size 60: 30,72 GOOD (HHFFFF) +Size 61: 31,73 BAD (FFHFHH) +Size 62: 31,74 GOOD (HHFFFF) +Size 63: 32,76 BAD (FFHFHH) +Size 64: 32,77 GOOD (HHFFFF) +Size 65: 33,78 BAD (FFHFHH) +Size 66: 33,79 GOOD (HHFFFF) +Size 67: 34,80 BAD (FFHFHH) +Size 68: 34,82 GOOD (HHFFFF) +Size 69: 35,83 BAD (FFHFHH) +Size 70: 35,84 GOOD (HHFFFF) +Size 71: 36,85 BAD (FFHFHH) +Size 72: 36,86 GOOD (HHFFFF) +Size 73: 37,88 BAD (FFHFHH) +Size 74: 37,89 GOOD (HHFFFF) +Size 75: 38,90 BAD (FFHFHH) +Size 76: 38,91 GOOD (HHFFFF) +Size 77: 39,92 BAD (FFHFHH) +Size 78: 39,94 GOOD (HHFFFF) +Size 79: 40,95 BAD (FFHFHH) +Size 80: 40,96 GOOD (HHFFFF) +Size 81: 41,97 BAD (FFHFHH) +Size 82: 41,98 GOOD (HHFFFF) +Size 83: 42,100 BAD (FFHFHH) +Size 84: 42,101 GOOD (HHFFFF) +Size 85: 43,102 BAD (FFHFHH) +Size 86: 43,103 GOOD (HHFFFF) +Size 87: 44,104 BAD (FFHFHH) +Size 88: 44,106 GOOD (HHFFFF) +Size 89: 45,107 BAD (FFHFHH) +Size 90: 45,108 GOOD (HHFFFF) +Size 91: 46,109 BAD (FFHFHH) +Size 92: 46,110 GOOD (HHFFFF) +Size 93: 47,112 BAD (FFHFHH) +Size 94: 47,113 GOOD (HHFFFF) +Size 95: 48,114 BAD (FFHFHH) +Size 96: 48,115 GOOD (HHFFFF) +Size 97: 49,116 BAD (FFHFHH) +Size 98: 49,118 GOOD (HHFFFF) +Size 99: 50,119 BAD (FFHFHH) +Size 100: 50,120 GOOD (HHFFFF) + +Windows 8.1 +----------- + +Size 1: 1,2 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,4 BAD (FFHFHH) +Size 4: 2,5 GOOD (HHFFFF) +Size 5: 3,6 BAD (FFHFHH) +Size 6: 3,7 GOOD (HHFFFF) +Size 7: 4,8 BAD (FFHFHH) +Size 8: 4,10 GOOD (HHFFFF) +Size 9: 5,11 BAD (FFHFHH) +Size 10: 5,12 GOOD (HHFFFF) +Size 11: 6,13 BAD (FFHFHH) +Size 12: 6,14 GOOD (HHFFFF) +Size 13: 7,16 BAD (FFHFHH) +Size 14: 7,17 GOOD (HHFFFF) +Size 15: 8,18 BAD (FFHFHH) +Size 16: 8,19 GOOD (HHFFFF) +Size 17: 9,20 BAD (FFHFHH) +Size 18: 9,22 GOOD (HHFFFF) +Size 19: 10,23 BAD (FFHFHH) +Size 20: 10,24 GOOD (HHFFFF) +Size 21: 11,25 BAD (FFHFHH) +Size 22: 11,26 GOOD (HHFFFF) +Size 23: 12,28 BAD (FFHFHH) +Size 24: 12,29 GOOD (HHFFFF) +Size 25: 13,30 BAD (FFHFHH) +Size 26: 13,31 GOOD (HHFFFF) +Size 27: 14,32 BAD (FFHFHH) +Size 28: 14,34 GOOD (HHFFFF) +Size 29: 15,35 BAD (FFHFHH) +Size 30: 15,36 GOOD (HHFFFF) +Size 31: 16,37 BAD (FFHFHH) +Size 32: 16,38 GOOD (HHFFFF) +Size 33: 17,40 BAD (FFHFHH) +Size 34: 17,41 GOOD (HHFFFF) +Size 35: 18,42 BAD (FFHFHH) +Size 36: 18,43 GOOD (HHFFFF) +Size 37: 19,44 BAD (FFHFHH) +Size 38: 19,46 GOOD (HHFFFF) +Size 39: 20,47 BAD (FFHFHH) +Size 40: 20,48 GOOD (HHFFFF) +Size 41: 21,49 BAD (FFHFHH) +Size 42: 21,50 GOOD (HHFFFF) +Size 43: 22,52 BAD (FFHFHH) +Size 44: 22,53 GOOD (HHFFFF) +Size 45: 23,54 BAD (FFHFHH) +Size 46: 23,55 GOOD (HHFFFF) +Size 47: 24,56 BAD (FFHFHH) +Size 48: 24,58 GOOD (HHFFFF) +Size 49: 25,59 BAD (FFHFHH) +Size 50: 25,60 GOOD (HHFFFF) +Size 51: 26,61 BAD (FFHFHH) +Size 52: 26,62 GOOD (HHFFFF) +Size 53: 27,64 BAD (FFHFHH) +Size 54: 27,65 GOOD (HHFFFF) +Size 55: 28,66 BAD (FFHFHH) +Size 56: 28,67 GOOD (HHFFFF) +Size 57: 29,68 BAD (FFHFHH) +Size 58: 29,70 GOOD (HHFFFF) +Size 59: 30,71 BAD (FFHFHH) +Size 60: 30,72 GOOD (HHFFFF) +Size 61: 31,73 BAD (FFHFHH) +Size 62: 31,74 GOOD (HHFFFF) +Size 63: 32,76 BAD (FFHFHH) +Size 64: 32,77 GOOD (HHFFFF) +Size 65: 33,78 BAD (FFHFHH) +Size 66: 33,79 GOOD (HHFFFF) +Size 67: 34,80 BAD (FFHFHH) +Size 68: 34,82 GOOD (HHFFFF) +Size 69: 35,83 BAD (FFHFHH) +Size 70: 35,84 GOOD (HHFFFF) +Size 71: 36,85 BAD (FFHFHH) +Size 72: 36,86 GOOD (HHFFFF) +Size 73: 37,88 BAD (FFHFHH) +Size 74: 37,89 GOOD (HHFFFF) +Size 75: 38,90 BAD (FFHFHH) +Size 76: 38,91 GOOD (HHFFFF) +Size 77: 39,92 BAD (FFHFHH) +Size 78: 39,94 GOOD (HHFFFF) +Size 79: 40,95 BAD (FFHFHH) +Size 80: 40,96 GOOD (HHFFFF) +Size 81: 41,97 BAD (FFHFHH) +Size 82: 41,98 GOOD (HHFFFF) +Size 83: 42,100 BAD (FFHFHH) +Size 84: 42,101 GOOD (HHFFFF) +Size 85: 43,102 BAD (FFHFHH) +Size 86: 43,103 GOOD (HHFFFF) +Size 87: 44,104 BAD (FFHFHH) +Size 88: 44,106 GOOD (HHFFFF) +Size 89: 45,107 BAD (FFHFHH) +Size 90: 45,108 GOOD (HHFFFF) +Size 91: 46,109 BAD (FFHFHH) +Size 92: 46,110 GOOD (HHFFFF) +Size 93: 47,112 BAD (FFHFHH) +Size 94: 47,113 GOOD (HHFFFF) +Size 95: 48,114 BAD (FFHFHH) +Size 96: 48,115 GOOD (HHFFFF) +Size 97: 49,116 BAD (FFHFHH) +Size 98: 49,118 GOOD (HHFFFF) +Size 99: 50,119 BAD (FFHFHH) +Size 100: 50,120 GOOD (HHFFFF) + +Windows 10 14342 Old Console +---------------------------- + +Size 1: 1,2 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,4 BAD (FFHFHH) +Size 4: 2,5 GOOD (HHFFFF) +Size 5: 3,6 BAD (FFHFHH) +Size 6: 3,7 GOOD (HHFFFF) +Size 7: 4,8 BAD (FFHFHH) +Size 8: 4,10 GOOD (HHFFFF) +Size 9: 5,11 BAD (FFHFHH) +Size 10: 5,12 GOOD (HHFFFF) +Size 11: 6,13 BAD (FFHFHH) +Size 12: 6,14 GOOD (HHFFFF) +Size 13: 7,16 BAD (FFHFHH) +Size 14: 7,17 GOOD (HHFFFF) +Size 15: 8,18 BAD (FFHFHH) +Size 16: 8,19 GOOD (HHFFFF) +Size 17: 9,20 BAD (FFHFHH) +Size 18: 9,22 GOOD (HHFFFF) +Size 19: 10,23 BAD (FFHFHH) +Size 20: 10,24 GOOD (HHFFFF) +Size 21: 11,25 BAD (FFHFHH) +Size 22: 11,26 GOOD (HHFFFF) +Size 23: 12,28 BAD (FFHFHH) +Size 24: 12,29 GOOD (HHFFFF) +Size 25: 13,30 BAD (FFHFHH) +Size 26: 13,31 GOOD (HHFFFF) +Size 27: 14,32 BAD (FFHFHH) +Size 28: 14,34 GOOD (HHFFFF) +Size 29: 15,35 BAD (FFHFHH) +Size 30: 15,36 GOOD (HHFFFF) +Size 31: 16,37 BAD (FFHFHH) +Size 32: 16,38 GOOD (HHFFFF) +Size 33: 17,40 BAD (FFHFHH) +Size 34: 17,41 GOOD (HHFFFF) +Size 35: 18,42 BAD (FFHFHH) +Size 36: 18,43 GOOD (HHFFFF) +Size 37: 19,44 BAD (FFHFHH) +Size 38: 19,46 GOOD (HHFFFF) +Size 39: 20,47 BAD (FFHFHH) +Size 40: 20,48 GOOD (HHFFFF) +Size 41: 21,49 BAD (FFHFHH) +Size 42: 21,50 GOOD (HHFFFF) +Size 43: 22,52 BAD (FFHFHH) +Size 44: 22,53 GOOD (HHFFFF) +Size 45: 23,54 BAD (FFHFHH) +Size 46: 23,55 GOOD (HHFFFF) +Size 47: 24,56 BAD (FFHFHH) +Size 48: 24,58 GOOD (HHFFFF) +Size 49: 25,59 BAD (FFHFHH) +Size 50: 25,60 GOOD (HHFFFF) +Size 51: 26,61 BAD (FFHFHH) +Size 52: 26,62 GOOD (HHFFFF) +Size 53: 27,64 BAD (FFHFHH) +Size 54: 27,65 GOOD (HHFFFF) +Size 55: 28,66 BAD (FFHFHH) +Size 56: 28,67 GOOD (HHFFFF) +Size 57: 29,68 BAD (FFHFHH) +Size 58: 29,70 GOOD (HHFFFF) +Size 59: 30,71 BAD (FFHFHH) +Size 60: 30,72 GOOD (HHFFFF) +Size 61: 31,73 BAD (FFHFHH) +Size 62: 31,74 GOOD (HHFFFF) +Size 63: 32,76 BAD (FFHFHH) +Size 64: 32,77 GOOD (HHFFFF) +Size 65: 33,78 BAD (FFHFHH) +Size 66: 33,79 GOOD (HHFFFF) +Size 67: 34,80 BAD (FFHFHH) +Size 68: 34,82 GOOD (HHFFFF) +Size 69: 35,83 BAD (FFHFHH) +Size 70: 35,84 GOOD (HHFFFF) +Size 71: 36,85 BAD (FFHFHH) +Size 72: 36,86 GOOD (HHFFFF) +Size 73: 37,88 BAD (FFHFHH) +Size 74: 37,89 GOOD (HHFFFF) +Size 75: 38,90 BAD (FFHFHH) +Size 76: 38,91 GOOD (HHFFFF) +Size 77: 39,92 BAD (FFHFHH) +Size 78: 39,94 GOOD (HHFFFF) +Size 79: 40,95 BAD (FFHFHH) +Size 80: 40,96 GOOD (HHFFFF) +Size 81: 41,97 BAD (FFHFHH) +Size 82: 41,98 GOOD (HHFFFF) +Size 83: 42,100 BAD (FFHFHH) +Size 84: 42,101 GOOD (HHFFFF) +Size 85: 43,102 BAD (FFHFHH) +Size 86: 43,103 GOOD (HHFFFF) +Size 87: 44,104 BAD (FFHFHH) +Size 88: 44,106 GOOD (HHFFFF) +Size 89: 45,107 BAD (FFHFHH) +Size 90: 45,108 GOOD (HHFFFF) +Size 91: 46,109 BAD (FFHFHH) +Size 92: 46,110 GOOD (HHFFFF) +Size 93: 47,112 BAD (FFHFHH) +Size 94: 47,113 GOOD (HHFFFF) +Size 95: 48,114 BAD (FFHFHH) +Size 96: 48,115 GOOD (HHFFFF) +Size 97: 49,116 BAD (FFHFHH) +Size 98: 49,118 GOOD (HHFFFF) +Size 99: 50,119 BAD (FFHFHH) +Size 100: 50,120 GOOD (HHFFFF) + +Windows 10 14342 New Console +---------------------------- + +Size 1: 1,1 GOOD (HHFFFF) +Size 2: 1,2 GOOD (HHFFFF) +Size 3: 2,3 GOOD (HHFFFF) +Size 4: 2,4 GOOD (HHFFFF) +Size 5: 3,5 GOOD (HHFFFF) +Size 6: 3,6 GOOD (HHFFFF) +Size 7: 4,7 GOOD (HHFFFF) +Size 8: 4,8 GOOD (HHFFFF) +Size 9: 5,9 GOOD (HHFFFF) +Size 10: 5,10 GOOD (HHFFFF) +Size 11: 6,11 GOOD (HHFFFF) +Size 12: 6,12 GOOD (HHFFFF) +Size 13: 7,13 GOOD (HHFFFF) +Size 14: 7,14 GOOD (HHFFFF) +Size 15: 8,15 GOOD (HHFFFF) +Size 16: 8,16 GOOD (HHFFFF) +Size 17: 9,17 GOOD (HHFFFF) +Size 18: 9,18 GOOD (HHFFFF) +Size 19: 10,19 GOOD (HHFFFF) +Size 20: 10,20 GOOD (HHFFFF) +Size 21: 11,21 GOOD (HHFFFF) +Size 22: 11,22 GOOD (HHFFFF) +Size 23: 12,23 GOOD (HHFFFF) +Size 24: 12,24 GOOD (HHFFFF) +Size 25: 13,25 GOOD (HHFFFF) +Size 26: 13,26 GOOD (HHFFFF) +Size 27: 14,27 GOOD (HHFFFF) +Size 28: 14,28 GOOD (HHFFFF) +Size 29: 15,29 GOOD (HHFFFF) +Size 30: 15,30 GOOD (HHFFFF) +Size 31: 16,31 GOOD (HHFFFF) +Size 32: 16,32 GOOD (HHFFFF) +Size 33: 17,33 GOOD (HHFFFF) +Size 34: 17,34 GOOD (HHFFFF) +Size 35: 18,35 GOOD (HHFFFF) +Size 36: 18,36 GOOD (HHFFFF) +Size 37: 19,37 GOOD (HHFFFF) +Size 38: 19,38 GOOD (HHFFFF) +Size 39: 20,39 GOOD (HHFFFF) +Size 40: 20,40 GOOD (HHFFFF) +Size 41: 21,41 GOOD (HHFFFF) +Size 42: 21,42 GOOD (HHFFFF) +Size 43: 22,43 GOOD (HHFFFF) +Size 44: 22,44 GOOD (HHFFFF) +Size 45: 23,45 GOOD (HHFFFF) +Size 46: 23,46 GOOD (HHFFFF) +Size 47: 24,47 GOOD (HHFFFF) +Size 48: 24,48 GOOD (HHFFFF) +Size 49: 25,49 GOOD (HHFFFF) +Size 50: 25,50 GOOD (HHFFFF) +Size 51: 26,51 GOOD (HHFFFF) +Size 52: 26,52 GOOD (HHFFFF) +Size 53: 27,53 GOOD (HHFFFF) +Size 54: 27,54 GOOD (HHFFFF) +Size 55: 28,55 GOOD (HHFFFF) +Size 56: 28,56 GOOD (HHFFFF) +Size 57: 29,57 GOOD (HHFFFF) +Size 58: 29,58 GOOD (HHFFFF) +Size 59: 30,59 GOOD (HHFFFF) +Size 60: 30,60 GOOD (HHFFFF) +Size 61: 31,61 GOOD (HHFFFF) +Size 62: 31,62 GOOD (HHFFFF) +Size 63: 32,63 GOOD (HHFFFF) +Size 64: 32,64 GOOD (HHFFFF) +Size 65: 33,65 GOOD (HHFFFF) +Size 66: 33,66 GOOD (HHFFFF) +Size 67: 34,67 GOOD (HHFFFF) +Size 68: 34,68 GOOD (HHFFFF) +Size 69: 35,69 GOOD (HHFFFF) +Size 70: 35,70 GOOD (HHFFFF) +Size 71: 36,71 GOOD (HHFFFF) +Size 72: 36,72 GOOD (HHFFFF) +Size 73: 37,73 GOOD (HHFFFF) +Size 74: 37,74 GOOD (HHFFFF) +Size 75: 38,75 GOOD (HHFFFF) +Size 76: 38,76 GOOD (HHFFFF) +Size 77: 39,77 GOOD (HHFFFF) +Size 78: 39,78 GOOD (HHFFFF) +Size 79: 40,79 GOOD (HHFFFF) +Size 80: 40,80 GOOD (HHFFFF) +Size 81: 41,81 GOOD (HHFFFF) +Size 82: 41,82 GOOD (HHFFFF) +Size 83: 42,83 GOOD (HHFFFF) +Size 84: 42,84 GOOD (HHFFFF) +Size 85: 43,85 GOOD (HHFFFF) +Size 86: 43,86 GOOD (HHFFFF) +Size 87: 44,87 GOOD (HHFFFF) +Size 88: 44,88 GOOD (HHFFFF) +Size 89: 45,89 GOOD (HHFFFF) +Size 90: 45,90 GOOD (HHFFFF) +Size 91: 46,91 GOOD (HHFFFF) +Size 92: 46,92 GOOD (HHFFFF) +Size 93: 47,93 GOOD (HHFFFF) +Size 94: 47,94 GOOD (HHFFFF) +Size 95: 48,95 GOOD (HHFFFF) +Size 96: 48,96 GOOD (HHFFFF) +Size 97: 49,97 GOOD (HHFFFF) +Size 98: 49,98 GOOD (HHFFFF) +Size 99: 50,99 GOOD (HHFFFF) +Size 100: 50,100 GOOD (HHFFFF) diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/MinimumWindowWidths.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/MinimumWindowWidths.txt new file mode 100644 index 00000000000..d5261d8db39 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/MinimumWindowWidths.txt @@ -0,0 +1,16 @@ +The narrowest allowed console window, in pixels, on a conventional (~96dpi) +monitor: + +(mode con: cols=40 lines=40) && SetFont.exe -face "Lucida Console" -h 1 && (ping -n 4 127.0.0.1 > NUL) && cls && GetConsolePos.exe && SetFont.exe -face "Lucida Console" -h 12 + +(mode con: cols=40 lines=40) && SetFont.exe -face "Lucida Console" -h 16 && (ping -n 4 127.0.0.1 > NUL) && cls && GetConsolePos.exe && SetFont.exe -face "Lucida Console" -h 12 + + sz1:px sz1:col sz16:px sz16:col +Vista: 124 104 137 10 +Windows 7: 132 112 147 11 +Windows 8: 140 120 147 11 +Windows 8.1: 140 120 147 11 +Windows 10 OLD: 136 116 147 11 +Windows 10 NEW: 136 103 136 10 + +I used build 14342 to test Windows 10. diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/Results.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/Results.txt new file mode 100644 index 00000000000..15a825cb51b --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/Results.txt @@ -0,0 +1,4 @@ +As before, avoid odd sizes in favor of even sizes. + +It's curious that the Japanese font is handled so poorly, especially with +Windows 8 and later. diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/Windows10SetFontBugginess.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/Windows10SetFontBugginess.txt new file mode 100644 index 00000000000..fef397a1e34 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/Windows10SetFontBugginess.txt @@ -0,0 +1,144 @@ +Issues: + + - Starting with the 14342 build, changing the font using + SetCurrentConsoleFontEx does not affect the window size. e.g. The content + itself will resize/redraw, but the window neither shrinks nor expands. + Presumably this is an oversight? It's almost a convenience; if a program + is going to resize the window anyway, then it's nice that the window size + contraints don't get in the way. Ordinarily, changing the font doesn't just + change the window size in pixels--it can also change the size as measured in + rows and columns. + + - (Aside: in the 14342 build, there is also a bug with wmic.exe. Open a console + with more than 300 lines of screen buffer, then fill those lines with, e.g., + dir /s. Then run wmic.exe. You won't be able to see the wmic.exe prompt. + If you query the screen buffer info somehow, you'll notice that the srWindow + is not contained within the dwSize. This breaks winpty's scraping, because + it's invalid.) + + - In build 14316, with the Japanese locale, with the 437 code page, attempting + to set the Consolas font instead sets the Terminal (raster) font. It seems + to pick an appropriate vertical size. + + - It seems necessary to specify "-family 0x36" for maximum reliability. + Setting the family to 0 almost always works, and specifying just -tt rarely + works. + +Win7 + English locale / 437 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead + SetFont.exe -face Consolas -h 16 -family 0x36 works + Japanese locale / 932 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead + SetFont.exe -face Consolas -h 16 -family 0x36 works + Japanese locale / 437 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt unreliable + SetFont.exe -face Consolas -h 16 -family 0x36 works + +Win10 Build 10586 + New console + Japanese locale / 437 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt selects Terminal instead + SetFont.exe -face Consolas -h 16 -family 0x36 works + +Win10 Build 14316 + Old console + English locale / 437 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead + SetFont.exe -face Consolas -h 16 -family 0x36 works + Japanese locale / 932 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead + SetFont.exe -face Consolas -h 16 -family 0x36 works + Japanese locale / 437 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt selected very small Consolas font + SetFont.exe -face Consolas -h 16 -family 0x36 works + New console + English locale / 437 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt works + SetFont.exe -face Consolas -h 16 -family 0x36 works + Japanese locale / 932 code page: + SetFont.exe -face Consolas -h 16 selects gothic instead + SetFont.exe -face Consolas -h 16 -tt selects gothic instead + SetFont.exe -face Consolas -h 16 -family 0x36 selects gothic instead + Japanese locale / 437 code page: + SetFont.exe -face Consolas -h 16 selects Terminal font instead + SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead + SetFont.exe -face Consolas -h 16 -family 0x36(*) selects Terminal font instead + +Win10 Build 14342 + Old Console + English locale / 437 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead + SetFont.exe -face Consolas -h 16 -family 0x36 works + Japanese locale / 932 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead + SetFont.exe -face Consolas -h 16 -family 0x36 works + Japanese locale / 437 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead + SetFont.exe -face Consolas -h 16 -family 0x36 works + New console + English locale / 437 code page: + SetFont.exe -face Consolas -h 16 works + SetFont.exe -face Consolas -h 16 -tt works + SetFont.exe -face Consolas -h 16 -family 0x36 works + Japanese locale / 932 code page: + SetFont.exe -face Consolas -h 16 selects gothic instead + SetFont.exe -face Consolas -h 16 -tt selects gothic instead + SetFont.exe -face Consolas -h 16 -family 0x36 selects gothic instead + Japanese locale / 437 code page: + SetFont.exe -face Consolas -h 16 selects Terminal font instead + SetFont.exe -face Consolas -h 16 -tt works + SetFont.exe -face Consolas -h 16 -family 0x36 works + +(*) I was trying to figure out whether the inconsistency was at when I stumbled +onto this completely unexpected bug. Here's more detail: + + F:\>SetFont.exe -face Consolas -h 16 -family 0x36 -weight normal -w 8 + Setting to: nFont=0 dwFontSize=(8,16) FontFamily=0x36 FontWeight=400 FaceName="Consolas" + SetCurrentConsoleFontEx returned 1 + + F:\>GetFont.exe + largestConsoleWindowSize=(96,50) + maxWnd=0: nFont=0 dwFontSize=(12,16) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C) + maxWnd=1: nFont=0 dwFontSize=(96,25) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C) + 00-00: 12x16 + GetNumberOfConsoleFonts returned 0 + CP=437 OutputCP=437 + + F:\>SetFont.exe -face "Lucida Console" -h 16 -family 0x36 -weight normal + Setting to: nFont=0 dwFontSize=(0,16) FontFamily=0x36 FontWeight=400 FaceName="Lucida Console" + SetCurrentConsoleFontEx returned 1 + + F:\>GetFont.exe + largestConsoleWindowSize=(96,50) + maxWnd=0: nFont=0 dwFontSize=(12,16) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C) + maxWnd=1: nFont=0 dwFontSize=(96,25) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C) + 00-00: 12x16 + GetNumberOfConsoleFonts returned 0 + CP=437 OutputCP=437 + + F:\>SetFont.exe -face "Lucida Console" -h 12 -family 0x36 -weight normal + Setting to: nFont=0 dwFontSize=(0,12) FontFamily=0x36 FontWeight=400 FaceName="Lucida Console" + SetCurrentConsoleFontEx returned 1 + + F:\>GetFont.exe + largestConsoleWindowSize=(230,66) + maxWnd=0: nFont=0 dwFontSize=(5,12) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C) + maxWnd=1: nFont=0 dwFontSize=(116,36) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C) + 00-00: 5x12 + GetNumberOfConsoleFonts returned 0 + CP=437 OutputCP=437 + +Even attempting to set to a Lucida Console / Consolas font from the Console +properties dialog fails. diff --git a/src/libs/3rdparty/winpty/misc/FontSurvey.cc b/src/libs/3rdparty/winpty/misc/FontSurvey.cc new file mode 100644 index 00000000000..254bcc81a60 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/FontSurvey.cc @@ -0,0 +1,100 @@ +#include + +#include +#include +#include + +#include + +#include "TestUtil.cc" + +#define COUNT_OF(array) (sizeof(array) / sizeof((array)[0])) + +// See https://en.wikipedia.org/wiki/List_of_CJK_fonts +const wchar_t kMSGothic[] = { 0xff2d, 0xff33, 0x0020, 0x30b4, 0x30b7, 0x30c3, 0x30af, 0 }; // Japanese +const wchar_t kNSimSun[] = { 0x65b0, 0x5b8b, 0x4f53, 0 }; // Simplified Chinese +const wchar_t kMingLight[] = { 0x7d30, 0x660e, 0x9ad4, 0 }; // Traditional Chinese +const wchar_t kGulimChe[] = { 0xad74, 0xb9bc, 0xccb4, 0 }; // Korean + +std::vector condense(const std::vector &buf) { + std::vector ret; + size_t i = 0; + while (i < buf.size()) { + if (buf[i].Char.UnicodeChar == L' ' && + ((buf[i].Attributes & 0x300) == 0)) { + // end of line + break; + } else if (i + 1 < buf.size() && + ((buf[i].Attributes & 0x300) == 0x100) && + ((buf[i + 1].Attributes & 0x300) == 0x200) && + buf[i].Char.UnicodeChar != L' ' && + buf[i].Char.UnicodeChar == buf[i + 1].Char.UnicodeChar) { + // double-width + ret.push_back(true); + i += 2; + } else if ((buf[i].Attributes & 0x300) == 0) { + // single-width + ret.push_back(false); + i++; + } else { + ASSERT(false && "unexpected output"); + } + } + return ret; +} + +int main(int argc, char *argv[]) { + if (argc != 2) { + printf("Usage: %s \"arguments for SetFont.exe\"\n", argv[0]); + return 1; + } + + const char *setFontArgs = argv[1]; + + const wchar_t testLine[] = { 0xA2, 0xA3, 0x2014, 0x3044, 0x30FC, 0x4000, 0 }; + const HANDLE conout = openConout(); + + char setFontCmd[1024]; + for (int h = 1; h <= 100; ++h) { + sprintf(setFontCmd, ".\\SetFont.exe %s -h %d && cls", setFontArgs, h); + system(setFontCmd); + + CONSOLE_FONT_INFOEX infoex = {}; + infoex.cbSize = sizeof(infoex); + BOOL success = GetCurrentConsoleFontEx(conout, FALSE, &infoex); + ASSERT(success && "GetCurrentConsoleFontEx failed"); + + DWORD actual = 0; + success = WriteConsoleW(conout, testLine, wcslen(testLine), &actual, nullptr); + ASSERT(success && actual == wcslen(testLine)); + + std::vector readBuf(14); + const SMALL_RECT readRegion = {0, 0, static_cast(readBuf.size() - 1), 0}; + SMALL_RECT readRegion2 = readRegion; + success = ReadConsoleOutputW( + conout, readBuf.data(), + {static_cast(readBuf.size()), 1}, + {0, 0}, + &readRegion2); + ASSERT(success && !memcmp(&readRegion, &readRegion2, sizeof(readRegion))); + + const auto widths = condense(readBuf); + std::string widthsStr; + for (bool width : widths) { + widthsStr.append(width ? "F" : "H"); + } + char size[16]; + sprintf(size, "%d,%d", infoex.dwFontSize.X, infoex.dwFontSize.Y); + const char *status = ""; + if (widthsStr == "HHFFFF") { + status = "GOOD"; + } else if (widthsStr == "HHHFFF") { + status = "OK"; + } else { + status = "BAD"; + } + trace("Size %3d: %-7s %-4s (%s)", h, size, status, widthsStr.c_str()); + } + sprintf(setFontCmd, ".\\SetFont.exe %s -h 14", setFontArgs); + system(setFontCmd); +} diff --git a/src/libs/3rdparty/winpty/misc/FormatChar.h b/src/libs/3rdparty/winpty/misc/FormatChar.h new file mode 100644 index 00000000000..aade488f9e2 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/FormatChar.h @@ -0,0 +1,21 @@ +#include +#include +#include + +static inline void formatChar(char *str, char ch) +{ + // Print some common control codes. + switch (ch) { + case '\r': strcpy(str, "CR "); break; + case '\n': strcpy(str, "LF "); break; + case ' ': strcpy(str, "SP "); break; + case 27: strcpy(str, "^[ "); break; + case 3: strcpy(str, "^C "); break; + default: + if (isgraph(ch)) + sprintf(str, "%c ", ch); + else + sprintf(str, "%02x ", ch); + break; + } +} diff --git a/src/libs/3rdparty/winpty/misc/FreezePerfTest.cc b/src/libs/3rdparty/winpty/misc/FreezePerfTest.cc new file mode 100644 index 00000000000..2c0b0086a14 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/FreezePerfTest.cc @@ -0,0 +1,62 @@ +#include + +#include "TestUtil.cc" + +const int SC_CONSOLE_MARK = 0xFFF2; +const int SC_CONSOLE_SELECT_ALL = 0xFFF5; + +int main(int argc, char *argv[0]) { + + if (argc != 2) { + printf("Usage: %s (mark|selectall|read)\n", argv[0]); + return 1; + } + + enum class Test { Mark, SelectAll, Read } test; + if (!strcmp(argv[1], "mark")) { + test = Test::Mark; + } else if (!strcmp(argv[1], "selectall")) { + test = Test::SelectAll; + } else if (!strcmp(argv[1], "read")) { + test = Test::Read; + } else { + printf("Invalid test: %s\n", argv[1]); + return 1; + } + + HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE); + TimeMeasurement tm; + HWND hwnd = GetConsoleWindow(); + + setWindowPos(0, 0, 1, 1); + setBufferSize(100, 3000); + system("cls"); + setWindowPos(0, 2975, 100, 25); + setCursorPos(0, 2999); + + ShowWindow(hwnd, SW_HIDE); + + for (int i = 0; i < 1000; ++i) { + // CONSOLE_SCREEN_BUFFER_INFO info = {}; + // GetConsoleScreenBufferInfo(conout, &info); + + if (test == Test::Mark) { + SendMessage(hwnd, WM_SYSCOMMAND, SC_CONSOLE_MARK, 0); + SendMessage(hwnd, WM_CHAR, 27, 0x00010001); + } else if (test == Test::SelectAll) { + SendMessage(hwnd, WM_SYSCOMMAND, SC_CONSOLE_SELECT_ALL, 0); + SendMessage(hwnd, WM_CHAR, 27, 0x00010001); + } else if (test == Test::Read) { + static CHAR_INFO buffer[100 * 3000]; + const SMALL_RECT readRegion = {0, 0, 99, 2999}; + SMALL_RECT tmp = readRegion; + BOOL ret = ReadConsoleOutput(conout, buffer, {100, 3000}, {0, 0}, &tmp); + ASSERT(ret && !memcmp(&tmp, &readRegion, sizeof(tmp))); + } + } + + ShowWindow(hwnd, SW_SHOW); + + printf("elapsed: %f\n", tm.elapsed()); + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/GetCh.cc b/src/libs/3rdparty/winpty/misc/GetCh.cc new file mode 100644 index 00000000000..cd6ed1943ad --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/GetCh.cc @@ -0,0 +1,20 @@ +#include +#include +#include + +int main() { + printf("\nPress any keys -- Ctrl-D exits\n\n"); + + while (true) { + const int ch = getch(); + printf("0x%x", ch); + if (isgraph(ch)) { + printf(" '%c'", ch); + } + printf("\n"); + if (ch == 0x4) { // Ctrl-D + break; + } + } + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/GetConsolePos.cc b/src/libs/3rdparty/winpty/misc/GetConsolePos.cc new file mode 100644 index 00000000000..1f3cc5316f1 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/GetConsolePos.cc @@ -0,0 +1,41 @@ +#include + +#include + +#include "TestUtil.cc" + +int main() { + const HANDLE conout = openConout(); + + CONSOLE_SCREEN_BUFFER_INFO info = {}; + BOOL ret = GetConsoleScreenBufferInfo(conout, &info); + ASSERT(ret && "GetConsoleScreenBufferInfo failed"); + + trace("cursor=%d,%d", info.dwCursorPosition.X, info.dwCursorPosition.Y); + printf("cursor=%d,%d\n", info.dwCursorPosition.X, info.dwCursorPosition.Y); + + trace("srWindow={L=%d,T=%d,R=%d,B=%d}", info.srWindow.Left, info.srWindow.Top, info.srWindow.Right, info.srWindow.Bottom); + printf("srWindow={L=%d,T=%d,R=%d,B=%d}\n", info.srWindow.Left, info.srWindow.Top, info.srWindow.Right, info.srWindow.Bottom); + + trace("dwSize=%d,%d", info.dwSize.X, info.dwSize.Y); + printf("dwSize=%d,%d\n", info.dwSize.X, info.dwSize.Y); + + const HWND hwnd = GetConsoleWindow(); + if (hwnd != NULL) { + RECT r = {}; + if (GetWindowRect(hwnd, &r)) { + const int w = r.right - r.left; + const int h = r.bottom - r.top; + trace("hwnd: pos=(%d,%d) size=(%d,%d)", r.left, r.top, w, h); + printf("hwnd: pos=(%d,%d) size=(%d,%d)\n", r.left, r.top, w, h); + } else { + trace("GetWindowRect failed"); + printf("GetWindowRect failed\n"); + } + } else { + trace("GetConsoleWindow returned NULL"); + printf("GetConsoleWindow returned NULL\n"); + } + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/GetFont.cc b/src/libs/3rdparty/winpty/misc/GetFont.cc new file mode 100644 index 00000000000..38625317ab2 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/GetFont.cc @@ -0,0 +1,261 @@ +#include +#include +#include +#include + +#include "../src/shared/OsModule.h" +#include "../src/shared/StringUtil.h" + +#include "TestUtil.cc" +#include "../src/shared/StringUtil.cc" + +#define COUNT_OF(x) (sizeof(x) / sizeof((x)[0])) + +// Some of these types and functions are missing from the MinGW headers. +// Others are undocumented. + +struct AGENT_CONSOLE_FONT_INFO { + DWORD nFont; + COORD dwFontSize; +}; + +struct AGENT_CONSOLE_FONT_INFOEX { + ULONG cbSize; + DWORD nFont; + COORD dwFontSize; + UINT FontFamily; + UINT FontWeight; + WCHAR FaceName[LF_FACESIZE]; +}; + +// undocumented XP API +typedef BOOL WINAPI SetConsoleFont_t( + HANDLE hOutput, + DWORD dwFontIndex); + +// undocumented XP API +typedef DWORD WINAPI GetNumberOfConsoleFonts_t(); + +// XP and up +typedef BOOL WINAPI GetCurrentConsoleFont_t( + HANDLE hOutput, + BOOL bMaximumWindow, + AGENT_CONSOLE_FONT_INFO *lpConsoleCurrentFont); + +// XP and up +typedef COORD WINAPI GetConsoleFontSize_t( + HANDLE hConsoleOutput, + DWORD nFont); + +// Vista and up +typedef BOOL WINAPI GetCurrentConsoleFontEx_t( + HANDLE hConsoleOutput, + BOOL bMaximumWindow, + AGENT_CONSOLE_FONT_INFOEX *lpConsoleCurrentFontEx); + +// Vista and up +typedef BOOL WINAPI SetCurrentConsoleFontEx_t( + HANDLE hConsoleOutput, + BOOL bMaximumWindow, + AGENT_CONSOLE_FONT_INFOEX *lpConsoleCurrentFontEx); + +#define GET_MODULE_PROC(mod, funcName) \ + m_##funcName = reinterpret_cast((mod).proc(#funcName)); \ + +#define DEFINE_ACCESSOR(funcName) \ + funcName##_t &funcName() const { \ + ASSERT(valid()); \ + return *m_##funcName; \ + } + +class XPFontAPI { +public: + XPFontAPI() : m_kernel32(L"kernel32.dll") { + GET_MODULE_PROC(m_kernel32, GetCurrentConsoleFont); + GET_MODULE_PROC(m_kernel32, GetConsoleFontSize); + } + + bool valid() const { + return m_GetCurrentConsoleFont != NULL && + m_GetConsoleFontSize != NULL; + } + + DEFINE_ACCESSOR(GetCurrentConsoleFont) + DEFINE_ACCESSOR(GetConsoleFontSize) + +private: + OsModule m_kernel32; + GetCurrentConsoleFont_t *m_GetCurrentConsoleFont; + GetConsoleFontSize_t *m_GetConsoleFontSize; +}; + +class UndocumentedXPFontAPI : public XPFontAPI { +public: + UndocumentedXPFontAPI() : m_kernel32(L"kernel32.dll") { + GET_MODULE_PROC(m_kernel32, SetConsoleFont); + GET_MODULE_PROC(m_kernel32, GetNumberOfConsoleFonts); + } + + bool valid() const { + return this->XPFontAPI::valid() && + m_SetConsoleFont != NULL && + m_GetNumberOfConsoleFonts != NULL; + } + + DEFINE_ACCESSOR(SetConsoleFont) + DEFINE_ACCESSOR(GetNumberOfConsoleFonts) + +private: + OsModule m_kernel32; + SetConsoleFont_t *m_SetConsoleFont; + GetNumberOfConsoleFonts_t *m_GetNumberOfConsoleFonts; +}; + +class VistaFontAPI : public XPFontAPI { +public: + VistaFontAPI() : m_kernel32(L"kernel32.dll") { + GET_MODULE_PROC(m_kernel32, GetCurrentConsoleFontEx); + GET_MODULE_PROC(m_kernel32, SetCurrentConsoleFontEx); + } + + bool valid() const { + return this->XPFontAPI::valid() && + m_GetCurrentConsoleFontEx != NULL && + m_SetCurrentConsoleFontEx != NULL; + } + + DEFINE_ACCESSOR(GetCurrentConsoleFontEx) + DEFINE_ACCESSOR(SetCurrentConsoleFontEx) + +private: + OsModule m_kernel32; + GetCurrentConsoleFontEx_t *m_GetCurrentConsoleFontEx; + SetCurrentConsoleFontEx_t *m_SetCurrentConsoleFontEx; +}; + +static std::vector > readFontTable( + XPFontAPI &api, HANDLE conout, DWORD maxCount) { + std::vector > ret; + for (DWORD i = 0; i < maxCount; ++i) { + COORD size = api.GetConsoleFontSize()(conout, i); + if (size.X == 0 && size.Y == 0) { + break; + } + ret.push_back(std::make_pair(i, size)); + } + return ret; +} + +static void dumpFontTable(HANDLE conout) { + const int kMaxCount = 1000; + XPFontAPI api; + if (!api.valid()) { + printf("dumpFontTable: cannot dump font table -- missing APIs\n"); + return; + } + std::vector > table = + readFontTable(api, conout, kMaxCount); + std::string line; + char tmp[128]; + size_t first = 0; + while (first < table.size()) { + size_t last = std::min(table.size() - 1, first + 10 - 1); + winpty_snprintf(tmp, "%02u-%02u:", + static_cast(first), static_cast(last)); + line = tmp; + for (size_t i = first; i <= last; ++i) { + if (i % 10 == 5) { + line += " - "; + } + winpty_snprintf(tmp, " %2dx%-2d", + table[i].second.X, table[i].second.Y); + line += tmp; + } + printf("%s\n", line.c_str()); + first = last + 1; + } + if (table.size() == kMaxCount) { + printf("... stopped reading at %d fonts ...\n", kMaxCount); + } +} + +static std::string stringToCodePoints(const std::wstring &str) { + std::string ret = "("; + for (size_t i = 0; i < str.size(); ++i) { + char tmp[32]; + winpty_snprintf(tmp, "%X", str[i]); + if (ret.size() > 1) { + ret.push_back(' '); + } + ret += tmp; + } + ret.push_back(')'); + return ret; +} + +static void dumpFontInfoEx( + const AGENT_CONSOLE_FONT_INFOEX &infoex) { + std::wstring faceName(infoex.FaceName, + winpty_wcsnlen(infoex.FaceName, COUNT_OF(infoex.FaceName))); + cprintf(L"nFont=%u dwFontSize=(%d,%d) " + "FontFamily=0x%x FontWeight=%u FaceName=%ls %hs\n", + static_cast(infoex.nFont), + infoex.dwFontSize.X, infoex.dwFontSize.Y, + infoex.FontFamily, infoex.FontWeight, faceName.c_str(), + stringToCodePoints(faceName).c_str()); +} + +static void dumpVistaFont(VistaFontAPI &api, HANDLE conout, BOOL maxWindow) { + AGENT_CONSOLE_FONT_INFOEX infoex = {0}; + infoex.cbSize = sizeof(infoex); + if (!api.GetCurrentConsoleFontEx()(conout, maxWindow, &infoex)) { + printf("GetCurrentConsoleFontEx call failed\n"); + return; + } + dumpFontInfoEx(infoex); +} + +static void dumpXPFont(XPFontAPI &api, HANDLE conout, BOOL maxWindow) { + AGENT_CONSOLE_FONT_INFO info = {0}; + if (!api.GetCurrentConsoleFont()(conout, maxWindow, &info)) { + printf("GetCurrentConsoleFont call failed\n"); + return; + } + printf("nFont=%u dwFontSize=(%d,%d)\n", + static_cast(info.nFont), + info.dwFontSize.X, info.dwFontSize.Y); +} + +static void dumpFontAndTable(HANDLE conout) { + VistaFontAPI vista; + if (vista.valid()) { + printf("maxWnd=0: "); dumpVistaFont(vista, conout, FALSE); + printf("maxWnd=1: "); dumpVistaFont(vista, conout, TRUE); + dumpFontTable(conout); + return; + } + UndocumentedXPFontAPI xp; + if (xp.valid()) { + printf("maxWnd=0: "); dumpXPFont(xp, conout, FALSE); + printf("maxWnd=1: "); dumpXPFont(xp, conout, TRUE); + dumpFontTable(conout); + return; + } + printf("setSmallFont: neither Vista nor XP APIs detected -- giving up\n"); + dumpFontTable(conout); +} + +int main() { + const HANDLE conout = openConout(); + const COORD largest = GetLargestConsoleWindowSize(conout); + printf("largestConsoleWindowSize=(%d,%d)\n", largest.X, largest.Y); + dumpFontAndTable(conout); + UndocumentedXPFontAPI xp; + if (xp.valid()) { + printf("GetNumberOfConsoleFonts returned %u\n", xp.GetNumberOfConsoleFonts()()); + } else { + printf("The GetNumberOfConsoleFonts API was missing\n"); + } + printf("CP=%u OutputCP=%u\n", GetConsoleCP(), GetConsoleOutputCP()); + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/IdentifyConsoleWindow.ps1 b/src/libs/3rdparty/winpty/misc/IdentifyConsoleWindow.ps1 new file mode 100644 index 00000000000..0c488597bd2 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/IdentifyConsoleWindow.ps1 @@ -0,0 +1,51 @@ +# +# Usage: powershell \IdentifyConsoleWindow.ps1 +# +# This script determines whether the process has a console attached, whether +# that console has a non-NULL window (e.g. HWND), and whether the window is on +# the current window station. +# + +$signature = @' +[DllImport("kernel32.dll", SetLastError=true)] +public static extern IntPtr GetConsoleWindow(); + +[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)] +public static extern bool SetConsoleTitle(String title); + +[DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)] +public static extern int GetWindowText(IntPtr hWnd, + System.Text.StringBuilder lpString, + int nMaxCount); +'@ + +$WinAPI = Add-Type -MemberDefinition $signature ` + -Name WinAPI -Namespace IdentifyConsoleWindow -PassThru + +if (!$WinAPI::SetConsoleTitle("ConsoleWindowScript")) { + echo "error: could not change console title -- is a console attached?" + exit 1 +} else { + echo "note: successfully set console title to ""ConsoleWindowScript""." +} + +$hwnd = $WinAPI::GetConsoleWindow() +if ($hwnd -eq 0) { + echo "note: GetConsoleWindow returned NULL." +} else { + echo "note: GetConsoleWindow returned 0x$($hwnd.ToString("X"))." + $sb = New-Object System.Text.StringBuilder -ArgumentList 4096 + if ($WinAPI::GetWindowText($hwnd, $sb, $sb.Capacity)) { + $title = $sb.ToString() + echo "note: GetWindowText returned ""${title}""." + if ($title -eq "ConsoleWindowScript") { + echo "success!" + } else { + echo "error: expected to see ""ConsoleWindowScript""." + echo " (Perhaps the console window is on a different window station?)" + } + } else { + echo "error: GetWindowText could not read the window title." + echo " (Perhaps the console window is on a different window station?)" + } +} diff --git a/src/libs/3rdparty/winpty/misc/IsNewConsole.cc b/src/libs/3rdparty/winpty/misc/IsNewConsole.cc new file mode 100644 index 00000000000..2b554c72c9f --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/IsNewConsole.cc @@ -0,0 +1,87 @@ +// Determines whether this is a new console by testing whether MARK moves the +// cursor. +// +// WARNING: This test program may behave erratically if run under winpty. +// + +#include + +#include +#include + +#include "TestUtil.cc" + +const int SC_CONSOLE_MARK = 0xFFF2; +const int SC_CONSOLE_SELECT_ALL = 0xFFF5; + +static COORD getWindowPos(HANDLE conout) { + CONSOLE_SCREEN_BUFFER_INFO info = {}; + BOOL ret = GetConsoleScreenBufferInfo(conout, &info); + ASSERT(ret && "GetConsoleScreenBufferInfo failed"); + return { info.srWindow.Left, info.srWindow.Top }; +} + +static COORD getWindowSize(HANDLE conout) { + CONSOLE_SCREEN_BUFFER_INFO info = {}; + BOOL ret = GetConsoleScreenBufferInfo(conout, &info); + ASSERT(ret && "GetConsoleScreenBufferInfo failed"); + return { + static_cast(info.srWindow.Right - info.srWindow.Left + 1), + static_cast(info.srWindow.Bottom - info.srWindow.Top + 1) + }; +} + +static COORD getCursorPos(HANDLE conout) { + CONSOLE_SCREEN_BUFFER_INFO info = {}; + BOOL ret = GetConsoleScreenBufferInfo(conout, &info); + ASSERT(ret && "GetConsoleScreenBufferInfo failed"); + return info.dwCursorPosition; +} + +static void setCursorPos(HANDLE conout, COORD pos) { + BOOL ret = SetConsoleCursorPosition(conout, pos); + ASSERT(ret && "SetConsoleCursorPosition failed"); +} + +int main() { + const HANDLE conout = openConout(); + const HWND hwnd = GetConsoleWindow(); + ASSERT(hwnd != NULL && "GetConsoleWindow() returned NULL"); + + // With the legacy console, the Mark command moves the the cursor to the + // top-left cell of the visible console window. Determine whether this + // is the new console by seeing if the cursor moves. + + const auto windowSize = getWindowSize(conout); + if (windowSize.X <= 1) { + printf("Error: console window must be at least 2 columns wide\n"); + trace("Error: console window must be at least 2 columns wide"); + return 1; + } + + bool cursorMoved = false; + const auto initialPos = getCursorPos(conout); + + const auto windowPos = getWindowPos(conout); + setCursorPos(conout, { static_cast(windowPos.X + 1), windowPos.Y }); + + { + const auto posA = getCursorPos(conout); + SendMessage(hwnd, WM_SYSCOMMAND, SC_CONSOLE_MARK, 0); + const auto posB = getCursorPos(conout); + cursorMoved = memcmp(&posA, &posB, sizeof(posA)) != 0; + SendMessage(hwnd, WM_CHAR, 27, 0x00010001); // Send ESCAPE + } + + setCursorPos(conout, initialPos); + + if (cursorMoved) { + printf("Legacy console (i.e. MARK moved cursor)\n"); + trace("Legacy console (i.e. MARK moved cursor)"); + } else { + printf("Windows 10 new console (i.e MARK did not move cursor)\n"); + trace("Windows 10 new console (i.e MARK did not move cursor)"); + } + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/MouseInputNotes.txt b/src/libs/3rdparty/winpty/misc/MouseInputNotes.txt new file mode 100644 index 00000000000..18460c6861e --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/MouseInputNotes.txt @@ -0,0 +1,90 @@ +Introduction +============ + +The only specification I could find describing mouse input escape sequences +was the /usr/share/doc/xterm/ctlseqs.txt.gz file installed on my Ubuntu +machine. + +Here are the relevant escape sequences: + + * [ON] CSI '?' M 'h' Enable mouse input mode M + * [OFF] CSI '?' M 'l' Disable mouse input mode M + * [EVT] CSI 'M' F X Y Mouse event (default or mode 1005) + * [EVT6] CSI '<' F ';' X ';' Y 'M' Mouse event with mode 1006 + * [EVT6] CSI '<' F ';' X ';' Y 'm' Mouse event with mode 1006 (up) + * [EVT15] CSI F ';' X ';' Y 'M' Mouse event with mode 1015 + +The first batch of modes affect what events are reported: + + * 9: Presses only (not as well-supported as the other modes) + * 1000: Presses and releases + * 1002: Presses, releases, and moves-while-pressed + * 1003: Presses, releases, and all moves + +The next batch of modes affect the encoding of the mouse events: + + * 1005: The X and Y coordinates are UTF-8 codepoints rather than bytes. + * 1006: Use the EVT6 sequences instead of EVT + * 1015: Use the EVT15 sequence instead of EVT (aka URVXT-mode) + +Support for modes in existing terminals +======================================= + + | 9 1000 1002 1003 | 1004 | overflow | defhi | 1005 1006 1015 +---------------------------------+---------------------+------+--------------+-------+---------------- +Eclipse TM Terminal (Neon) | _ _ _ _ | _ | n/a | n/a | _ _ _ +gnome-terminal 3.6.2 | X X X X | _ | suppressed*b | 0x07 | _ X X +iTerm2 2.1.4 | _ X X X | OI | wrap*z | n/a | X X X +jediterm/IntelliJ | _ X X X | _ | ch='?' | 0xff | X X X +Konsole 2.13.2 | _ X X *a | _ | suppressed | 0xff | X X X +mintty 2.2.2 | X X X X | OI | ch='\0' | 0xff | X X X +putty 0.66 | _ X X _ | _ | suppressed | 0xff | _ X X +rxvt 2.7.10 | X X _ _ | _ | wrap*z | n/a | _ _ _ +screen(under xterm) | X X X X | _ | suppressed | 0xff | _ _ _ +urxvt 9.21 | X X X X | _ | wrap*z | n/a | X _ X +xfce4-terminal 0.6.3 (GTK2 VTE) | X X X X | _ | wrap | n/a | _ _ _ +xterm | X X X X | OI | ch='\0' | 0xff | X X X + +*a: Mode 1003 is handled the same way as 1002. +*b: The coordinate wraps from 0xff to 0x00, then maxs out at 0x07. I'm + guessing this behavior is a bug? I'm using the Xubuntu 14.04 + gnome-terminal. +*z: These terminals have a bug where column 224 (and row 224, presumably) + yields a truncated escape sequence. 224 + 32 is 0, so it would normally + yield `CSI 'M' F '\0' Y`, but the '\0' is interpreted as a NUL-terminator. + +Problem 1: How do these flags work? +=================================== + +Terminals accept the OFF sequence with any of the input modes. This makes +little sense--there are two multi-value settings, not seven independent flags! + +All the terminals handle Granularity the same way. ON-Granularity sets +Granularity to the specified value, and OFF-Granularity sets Granularity to +OFF. + +Terminals vary in how they handle the Encoding modes. For example: + + * xterm. ON-Encoding sets Encoding. OFF-Encoding with a non-active Encoding + has no effect. OFF-Encoding otherwise resets Encoding to Default. + + * mintty (tested 2.2.2), iTerm2 2.1.4, and jediterm. ON-Encoding sets + Encoding. OFF-Encoding resets Encoding to Default. + + * Konsole (tested 2.13.2) seems to configure each encoding method + independently. The effective Encoding is the first enabled encoding in this + list: + - Mode 1006 + - Mode 1015 + - Mode 1005 + - Default + + * gnome-terminal (tested 3.6.2) also configures each encoding method + independently. The effective Encoding is the first enabled encoding in + this list: + - Mode 1006 + - Mode 1015 + - Default + Mode 1005 is not supported. + + * xfce4 terminal 0.6.3 (GTK2 VTE) always outputs the default encoding method. diff --git a/src/libs/3rdparty/winpty/misc/MoveConsoleWindow.cc b/src/libs/3rdparty/winpty/misc/MoveConsoleWindow.cc new file mode 100644 index 00000000000..7d9684fe94e --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/MoveConsoleWindow.cc @@ -0,0 +1,34 @@ +#include + +#include "TestUtil.cc" + +int main(int argc, char *argv[]) { + if (argc != 3 && argc != 5) { + printf("Usage: %s x y\n", argv[0]); + printf("Usage: %s x y width height\n", argv[0]); + return 1; + } + + HWND hwnd = GetConsoleWindow(); + + const int x = atoi(argv[1]); + const int y = atoi(argv[2]); + + int w = 0, h = 0; + if (argc == 3) { + RECT r = {}; + BOOL ret = GetWindowRect(hwnd, &r); + ASSERT(ret && "GetWindowRect failed on console window"); + w = r.right - r.left; + h = r.bottom - r.top; + } else { + w = atoi(argv[3]); + h = atoi(argv[4]); + } + + BOOL ret = MoveWindow(hwnd, x, y, w, h, TRUE); + trace("MoveWindow: ret=%d", ret); + printf("MoveWindow: ret=%d\n", ret); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/Notes.txt b/src/libs/3rdparty/winpty/misc/Notes.txt new file mode 100644 index 00000000000..410e1841986 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Notes.txt @@ -0,0 +1,219 @@ +Test programs +------------- + +Cygwin + emacs + vim + mc (Midnight Commander) + lynx + links + less + more + wget + +Capturing the console output +---------------------------- + +Initial idea: + +In the agent, keep track of the remote terminal state for N lines of +(window+history). Also keep track of the terminal size. Regularly poll for +changes to the console screen buffer, then use some number of edits to bring +the remote terminal into sync with the console. + +This idea seems to have trouble when a Unix terminal is resized. When the +server receives a resize notification, it can have a hard time figuring out +what the terminal did. Race conditions might also be a problem. + +The behavior of the terminal can be tricky: + + - When the window is expanded by one line, does the terminal add a blank line + to the bottom or move a line from the history into the top? + + - When the window is shrunk by one line, does the terminal delete the topmost + or the bottommost line? Can it delete the line with the cursor? + +Some popular behaviors for expanding: + - [all] If there are no history lines, then add a line at the bottom. + - [konsole] Always add a line at the bottom. + - [putty,xterm,rxvt] Pull in a history line from the top. + - [g-t] I can't tell. It seems to add a blank line, until the program writes + to stdout or until I click the scroll bar, then the output "snaps" back down, + pulling lines out of the history. I thought I saw different behavior + between Ubuntu 10.10 and 11.10, so maybe GNOME 3 changed something. Avoid + using "bash" to test this behavior because "bash" apparently always writes + the prompt after terminal resize. + +Some popular behaviors for shrinking: + - [konsole,putty,xterm,rxvt] If the line at the bottom is blank, then delete + it. Otherwise, move the topmost line into history. + - [g-t] If the line at the bottom has not been touched, then delete it. + Otherwise, move the topmost line into history. + +(TODO: I need to test my theories about the terminal behavior better still. +It's interesting to see how g-t handles clear differently than every other +terminal.) + +There is an ANSI escape sequence (DSR) that sends the current cursor location +to the terminal's input. One idea I had was to use this code to figure out how +the terminal had handled a resize. I currently think this idea won't work due +to race conditions. + +Newer idea: + +Keep track of the last N lines that have been sent to the remote terminal. +Poll for changes to console output. When the output changes, send just the +changed content to the terminal. In particular: + - Don't send a cursor position (CUP) code. Instead, if the line that's 3 + steps up from the latest line changes, send a relative cursor up (CUU) + code. It's OK to send an absolute column number code (CHA). + - At least in general, don't try to send complete screenshots of the current + console window. + +The idea is that sending just the changes should have good behavior for streams +of output, even when those streams modify the output (e.g. an archiver, or +maybe a downloader/packager/wget). I need to think about whether this works +for full-screen programs (e.g. emacs, less, lynx, the above list of programs). + +I noticed that console programs don't typically modify the window or buffer +coordinates. edit.com is an exception. + +I tested the pager in native Python (more?), and I verified that ENTER and SPACE +both paid no attention to the location of the console window within the screen +buffer. This makes sense -- why would they care? The Cygwin less, on the other +hand, does care. If I scroll the window up, then Cygwin less will write to a +position within the window. I didn't really expect this behavior, but it +doesn't seem to be a problem. + +Setting up a TestNetServer service +---------------------------------- + +First run the deploy.sh script to copy files into deploy. Make sure +TestNetServer.exe will run in a bare environment (no MinGW or Qt in the path). + +Install the Windows Server 2003 Resource Kit. It will have two programs in it, +instsrv and srvany. + +Run: + + InstSrv TestNetServer \srvany.exe + +This creates a service named "TestNetServer" that uses the Microsoft service +wrapper. To configure the new service to run TestNetServer, set a registry +value: + + [HKLM\SYSTEM\CurrentControlSet\Services\TestNetServer\Parameters] + Application=\TestNetServer.exe + +Also see http://www.iopus.com/guides/srvany.htm. + +To remove the service, run: + + InstSrv TestNetServer REMOVE + +TODO +---- + +Agent: When resizing the console, consider whether to add lines to the top +or bottom. I remember thinking the current behavior was wrong for some +application, but I forgot which one. + +Make the font as small as possible. The console window dimensions are limited by +the screen size, so making the font small reduces an unnecessary limitation on the +PseudoConsole size. There's a documented Vista/Win7 API for this +(SetCurrentConsoleFontEx), and apparently WinXP has an undocumented API +(SetConsoleFont): + http://blogs.microsoft.co.il/blogs/pavely/archive/2009/07/23/changing-console-fonts.aspx + +Make the agent work with DOS programs like edit and qbasic. + - Detect that the terminal program has resized the window/buffer and enter a + simple just-scrape-and-dont-resize mode. Track the client window size and + send the intersection of the console and the agent's client. + - I also need to generate keyboard scan codes. + - Solve the NTVDM.EXE console shutdown problem, probably by ignoring NTVDM.EXE + when it appears on the GetConsoleProcessList list. + +Rename the agent? Is the term "proxy" more accurate? + +Optimize the polling. e.g. Use a longer poll interval when the console is idle. +Do a minimal poll that checks whether the sync marker or window has moved. + +Increase the console buffer size to ~9000 lines. Beware making it so big that +reading the sync column exhausts the 32KB conhost<->agent heap. + +Reduce the memory overhead of the agent. The agent's m_bufferData array can +be small (a few hundred lines?) relative to the console buffer size. + +Try to handle console background color better. + Unix terminal emulators have a user-configurable foreground and background +color, and for best results, the agent really needs to avoid changing the colors, +especially the background color. It's undesirable/ugly to SSH into a machine +and see the command prompt change the colors. It's especially ugly that the +terminal retains its original colors and only drawn cells get the new colors. +(e.g. Resizing the window to the right uses the local terminal colors rather +than the remote colors.) It's especially ugly in gnome-terminal, which draws +user-configurable black as black, but VT100 black as dark-gray. + If there were a way to query the terminal emulator's colors, then I could +match the console's colors to the terminal and everything would just work. As +far as I know, that's not possible. + I thought of a kludge that might work. Instead of translating console white +and black to VT/100 white and black, I would translate them to "reset" and +"invert". I'd translate other colors normally. This approach should produce +ideal results for command-line work and tolerable results for full-screen +programs without configuration. Configuring the agent for black-on-white or +white-on-black would produce ideal results in all situations. + This kludge only really applies to the SSH application. For a Win32 Konsole +application, it should be easy to get the colors right all the time. + +Try using the screen reader API: + - To eliminate polling. + - To detect when a line wraps. When a line wraps, it'd be nice not to send a + CRLF to the terminal emulator so copy-and-paste works better. + - To detect hard tabs with Cygwin. + +Implement VT100/ANSI escape sequence recognition for input. Decide where this +functionality belongs. PseudoConsole.dll? Disambiguating ESC from an escape +sequence might be tricky. For the SSH server, I was thinking that when a small +SSH payload ended with an ESC character, I could assume the character was really +an ESC keypress, on the assumption that if it were an escape sequence, the +payload would probably contain the whole sequence. I'm not sure this works, +especially if there's a lot of other traffic multiplexed on the SSH socket. + +Support Unicode. + - Some DOS programs draw using line/box characters. Can these characters be + translated to the Unicode equivalents? + +Create automated tests. + +Experiment with the Terminator emulator, an emulator that doesn't wrap lines. +How many columns does it report having? What column does it report the cursor +in as it's writing past the right end of the window? Will Terminator be a +problem if I implement line wrapping detection in the agent? + +BUG: After the unix-adapter/pconsole.exe program exits, the blinking cursor is +replaced with a hidden cursor. + +Fix assert() in the agent. If it fails, the failure message needs to be +reported somewhere. Pop up a dialog box? Maybe switch the active desktop, +then show a dialog box? + +TODO: There's already a pconsole project on GitHub. Maybe rename this project +to something else? winpty? + +TODO: Can the DebugServer system be replaced with OutputDebugString? How +do we decide whose processes' output to collect? + +TODO: Three executables: + build/winpty-agent.exe + build/winpty.dll + build/console.exe + +BUG: Run the pconsole.exe inside another console. As I type dir, I see this: + D:\rprichard\pconsole> + D:\rprichard\pconsole>d + D:\rprichard\pconsole>di + D:\rprichard\pconsole>dir + In the output of "dir", every other line is blank. + There was a bug in Terminal::sendLine that was causing this to happen + frequently. Now that I fixed it, this bug should only manifest on lines + whose last column is not a space (i.e. a full line). diff --git a/src/libs/3rdparty/winpty/misc/OSVersion.cc b/src/libs/3rdparty/winpty/misc/OSVersion.cc new file mode 100644 index 00000000000..456708f05b1 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/OSVersion.cc @@ -0,0 +1,27 @@ +#include + +#include +#include +#include + +#include + +int main() { + setlocale(LC_ALL, ""); + + OSVERSIONINFOEXW info = {0}; + info.dwOSVersionInfoSize = sizeof(info); + assert(GetVersionExW((OSVERSIONINFOW*)&info)); + + printf("dwMajorVersion = %d\n", (int)info.dwMajorVersion); + printf("dwMinorVersion = %d\n", (int)info.dwMinorVersion); + printf("dwBuildNumber = %d\n", (int)info.dwBuildNumber); + printf("dwPlatformId = %d\n", (int)info.dwPlatformId); + printf("szCSDVersion = %ls\n", info.szCSDVersion); + printf("wServicePackMajor = %d\n", info.wServicePackMajor); + printf("wServicePackMinor = %d\n", info.wServicePackMinor); + printf("wSuiteMask = 0x%x\n", (unsigned int)info.wSuiteMask); + printf("wProductType = 0x%x\n", (unsigned int)info.wProductType); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/ScreenBufferFreezeInactive.cc b/src/libs/3rdparty/winpty/misc/ScreenBufferFreezeInactive.cc new file mode 100644 index 00000000000..656d4f126df --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/ScreenBufferFreezeInactive.cc @@ -0,0 +1,101 @@ +// +// Verify that console selection blocks writes to an inactive console screen +// buffer. Writes TEST PASSED or TEST FAILED to the popup console window. +// + +#include +#include + +#include + +#include "TestUtil.cc" + +const int SC_CONSOLE_MARK = 0xFFF2; +const int SC_CONSOLE_SELECT_ALL = 0xFFF5; + +bool g_useMark = false; + +CALLBACK DWORD pausingThread(LPVOID dummy) +{ + HWND hwnd = GetConsoleWindow(); + trace("Sending selection to freeze"); + SendMessage(hwnd, WM_SYSCOMMAND, + g_useMark ? SC_CONSOLE_MARK : + SC_CONSOLE_SELECT_ALL, + 0); + Sleep(1000); + trace("Sending escape WM_CHAR to unfreeze"); + SendMessage(hwnd, WM_CHAR, 27, 0x00010001); + Sleep(1000); +} + +static HANDLE createBuffer() { + HANDLE buf = CreateConsoleScreenBuffer( + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + CONSOLE_TEXTMODE_BUFFER, + NULL); + ASSERT(buf != INVALID_HANDLE_VALUE); + return buf; +} + +static void runTest(bool useMark, bool createEarly) { + trace("======================================="); + trace("useMark=%d createEarly=%d", useMark, createEarly); + g_useMark = useMark; + HANDLE buf = INVALID_HANDLE_VALUE; + + if (createEarly) { + buf = createBuffer(); + } + + CreateThread(NULL, 0, + pausingThread, NULL, + 0, NULL); + Sleep(500); + + if (!createEarly) { + trace("Creating buffer"); + TimeMeasurement tm1; + buf = createBuffer(); + const double elapsed1 = tm1.elapsed(); + if (elapsed1 >= 0.250) { + printf("!!! TEST FAILED !!!\n"); + Sleep(2000); + return; + } + } + + trace("Writing to aux buffer"); + TimeMeasurement tm2; + DWORD actual = 0; + BOOL ret = WriteConsoleW(buf, L"HI", 2, &actual, NULL); + const double elapsed2 = tm2.elapsed(); + trace("Writing to aux buffer: finished: ret=%d actual=%d (elapsed=%1.3f)", ret, actual, elapsed2); + if (elapsed2 < 0.250) { + printf("!!! TEST FAILED !!!\n"); + } else { + printf("TEST PASSED\n"); + } + Sleep(2000); +} + +int main(int argc, char **argv) { + if (argc == 1) { + startChildProcess(L"child"); + return 0; + } + + std::string arg = argv[1]; + if (arg == "child") { + for (int useMark = 0; useMark <= 1; useMark++) { + for (int createEarly = 0; createEarly <= 1; createEarly++) { + runTest(useMark, createEarly); + } + } + printf("done...\n"); + Sleep(1000); + } + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/ScreenBufferTest.cc b/src/libs/3rdparty/winpty/misc/ScreenBufferTest.cc new file mode 100644 index 00000000000..fa584b9fae9 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/ScreenBufferTest.cc @@ -0,0 +1,671 @@ +// +// Windows versions tested +// +// Vista Enterprise SP2 32-bit +// - ver reports [Version 6.0.6002] +// - kernel32.dll product/file versions are 6.0.6002.19381 +// +// Windows 7 Ultimate SP1 32-bit +// - ver reports [Version 6.1.7601] +// - conhost.exe product/file versions are 6.1.7601.18847 +// - kernel32.dll product/file versions are 6.1.7601.18847 +// +// Windows Server 2008 R2 Datacenter SP1 64-bit +// - ver reports [Version 6.1.7601] +// - conhost.exe product/file versions are 6.1.7601.23153 +// - kernel32.dll product/file versions are 6.1.7601.23153 +// +// Windows 8 Enterprise 32-bit +// - ver reports [Version 6.2.9200] +// - conhost.exe product/file versions are 6.2.9200.16578 +// - kernel32.dll product/file versions are 6.2.9200.16859 +// + +// +// Specific version details on working Server 2008 R2: +// +// dwMajorVersion = 6 +// dwMinorVersion = 1 +// dwBuildNumber = 7601 +// dwPlatformId = 2 +// szCSDVersion = Service Pack 1 +// wServicePackMajor = 1 +// wServicePackMinor = 0 +// wSuiteMask = 0x190 +// wProductType = 0x3 +// +// Specific version details on broken Win7: +// +// dwMajorVersion = 6 +// dwMinorVersion = 1 +// dwBuildNumber = 7601 +// dwPlatformId = 2 +// szCSDVersion = Service Pack 1 +// wServicePackMajor = 1 +// wServicePackMinor = 0 +// wSuiteMask = 0x100 +// wProductType = 0x1 +// + +#include +#include +#include + +#include "TestUtil.cc" + +const char *g_prefix = ""; + +static void dumpHandles() { + trace("%sSTDIN=0x%I64x STDOUT=0x%I64x STDERR=0x%I64x", + g_prefix, + (long long)GetStdHandle(STD_INPUT_HANDLE), + (long long)GetStdHandle(STD_OUTPUT_HANDLE), + (long long)GetStdHandle(STD_ERROR_HANDLE)); +} + +static const char *successOrFail(BOOL ret) { + return ret ? "ok" : "FAILED"; +} + +static void startChildInSameConsole(const wchar_t *args, BOOL + bInheritHandles=FALSE) { + wchar_t program[1024]; + wchar_t cmdline[1024]; + GetModuleFileNameW(NULL, program, 1024); + swprintf(cmdline, L"\"%ls\" %ls", program, args); + + STARTUPINFOW sui; + PROCESS_INFORMATION pi; + memset(&sui, 0, sizeof(sui)); + memset(&pi, 0, sizeof(pi)); + sui.cb = sizeof(sui); + + CreateProcessW(program, cmdline, + NULL, NULL, + /*bInheritHandles=*/bInheritHandles, + /*dwCreationFlags=*/0, + NULL, NULL, + &sui, &pi); +} + +static void closeHandle(HANDLE h) { + trace("%sClosing handle 0x%I64x...", g_prefix, (long long)h); + trace("%sClosing handle 0x%I64x... %s", g_prefix, (long long)h, successOrFail(CloseHandle(h))); +} + +static HANDLE createBuffer() { + + // If sa isn't provided, the handle defaults to not-inheritable. + SECURITY_ATTRIBUTES sa = {0}; + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + + trace("%sCreating a new buffer...", g_prefix); + HANDLE conout = CreateConsoleScreenBuffer( + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &sa, + CONSOLE_TEXTMODE_BUFFER, NULL); + + trace("%sCreating a new buffer... 0x%I64x", g_prefix, (long long)conout); + return conout; +} + +static HANDLE openConout() { + + // If sa isn't provided, the handle defaults to not-inheritable. + SECURITY_ATTRIBUTES sa = {0}; + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + + trace("%sOpening CONOUT...", g_prefix); + HANDLE conout = CreateFileW(L"CONOUT$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &sa, + OPEN_EXISTING, 0, NULL); + trace("%sOpening CONOUT... 0x%I64x", g_prefix, (long long)conout); + return conout; +} + +static void setConsoleActiveScreenBuffer(HANDLE conout) { + trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called...", + g_prefix, (long long)conout); + trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called... %s", + g_prefix, (long long)conout, + successOrFail(SetConsoleActiveScreenBuffer(conout))); +} + +static void writeTest(HANDLE conout, const char *msg) { + char writeData[256]; + sprintf(writeData, "%s%s\n", g_prefix, msg); + + trace("%sWriting to 0x%I64x: '%s'...", + g_prefix, (long long)conout, msg); + DWORD actual = 0; + BOOL ret = WriteConsoleA(conout, writeData, strlen(writeData), &actual, NULL); + trace("%sWriting to 0x%I64x: '%s'... %s", + g_prefix, (long long)conout, msg, + successOrFail(ret && actual == strlen(writeData))); +} + +static void writeTest(const char *msg) { + writeTest(GetStdHandle(STD_OUTPUT_HANDLE), msg); +} + + + +/////////////////////////////////////////////////////////////////////////////// +// TEST 1 -- create new buffer, activate it, and close the handle. The console +// automatically switches the screen buffer back to the original. +// +// This test passes everywhere. +// + +static void test1(int argc, char *argv[]) { + if (!strcmp(argv[1], "1")) { + startChildProcess(L"1:child"); + return; + } + + HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE); + writeTest(origBuffer, "<-- origBuffer -->"); + + HANDLE newBuffer = createBuffer(); + writeTest(newBuffer, "<-- newBuffer -->"); + setConsoleActiveScreenBuffer(newBuffer); + Sleep(2000); + + writeTest(origBuffer, "TEST PASSED!"); + + // Closing the handle w/o switching the active screen buffer automatically + // switches the console back to the original buffer. + closeHandle(newBuffer); + + while (true) { + Sleep(1000); + } +} + + + +/////////////////////////////////////////////////////////////////////////////// +// TEST 2 -- Test program that creates and activates newBuffer, starts a child +// process, then closes its newBuffer handle. newBuffer remains activated, +// because the child keeps it active. (Also see TEST D.) +// + +static void test2(int argc, char *argv[]) { + if (!strcmp(argv[1], "2")) { + startChildProcess(L"2:parent"); + return; + } + + if (!strcmp(argv[1], "2:parent")) { + g_prefix = "parent: "; + dumpHandles(); + HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE); + writeTest(origBuffer, "<-- origBuffer -->"); + + HANDLE newBuffer = createBuffer(); + writeTest(newBuffer, "<-- newBuffer -->"); + setConsoleActiveScreenBuffer(newBuffer); + + Sleep(1000); + writeTest(newBuffer, "bInheritHandles=FALSE:"); + startChildInSameConsole(L"2:child", FALSE); + Sleep(1000); + writeTest(newBuffer, "bInheritHandles=TRUE:"); + startChildInSameConsole(L"2:child", TRUE); + + Sleep(1000); + trace("parent:----"); + + // Close the new buffer. The active screen buffer doesn't automatically + // switch back to origBuffer, because the child process has a handle open + // to the original buffer. + closeHandle(newBuffer); + + Sleep(600 * 1000); + return; + } + + if (!strcmp(argv[1], "2:child")) { + g_prefix = "child: "; + dumpHandles(); + // The child's output isn't visible, because it's still writing to + // origBuffer. + trace("child:----"); + writeTest("writing to STDOUT"); + + // Handle inheritability is curious. The console handles this program + // creates are inheritable, but CreateProcess is called with both + // bInheritHandles=TRUE and bInheritHandles=FALSE. + // + // Vista and Windows 7: bInheritHandles has no effect. The child and + // parent processes have the same STDIN/STDOUT/STDERR handles: + // 0x3, 0x7, and 0xB. The parent has a 0xF handle for newBuffer. + // The child can only write to 0x7, 0xB, and 0xF. Only the writes to + // 0xF are visible (i.e. they touch newBuffer). + // + // Windows 8 or Windows 10 (legacy or non-legacy): the lowest 2 bits of + // the HANDLE to WriteConsole seem to be ignored. The new process' + // console handles always refer to the buffer that was active when they + // started, but the values of the handles depend upon bInheritHandles. + // With bInheritHandles=TRUE, the child has the same + // STDIN/STDOUT/STDERR/newBuffer handles as the parent, and the three + // output handles all work, though their output is all visible. With + // bInheritHandles=FALSE, the child has different STDIN/STDOUT/STDERR + // handles, and only the new STDOUT/STDERR handles work. + // + for (unsigned int i = 0x1; i <= 0xB0; ++i) { + char msg[256]; + sprintf(msg, "Write to handle 0x%x", i); + HANDLE h = reinterpret_cast(i); + writeTest(h, msg); + } + + Sleep(600 * 1000); + return; + } +} + + + +/////////////////////////////////////////////////////////////////////////////// +// TEST A -- demonstrate an apparent Windows bug with screen buffers +// +// Steps: +// - The parent starts a child process. +// - The child process creates and activates newBuffer +// - The parent opens CONOUT$ and writes to it. +// - The parent closes CONOUT$. +// - At this point, broken Windows reactivates origBuffer. +// - The child writes to newBuffer again. +// - The child activates origBuffer again, then closes newBuffer. +// +// Test passes if the message "TEST PASSED!" is visible. +// Test commonly fails if conhost.exe crashes. +// +// Results: +// - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes +// - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS +// - Windows 8 Enterprise 32-bit: PASS +// - Windows 10 64-bit (legacy and non-legacy): PASS +// + +static void testA_parentWork() { + // Open an extra CONOUT$ handle so that the HANDLE values in parent and + // child don't collide. I think it's OK if they collide, but since we're + // trying to track down a Windows bug, it's best to avoid unnecessary + // complication. + HANDLE dummy = openConout(); + + Sleep(3000); + + // Step 2: Open CONOUT$ in the parent. This opens the active buffer, which + // was just created in the child. It's handle 0x13. Write to it. + + HANDLE newBuffer = openConout(); + writeTest(newBuffer, "step2: writing to newBuffer"); + + Sleep(3000); + + // Step 3: Close handle 0x13. With Windows 7, the console switches back to + // origBuffer, and (unless I'm missing something) it shouldn't. + + closeHandle(newBuffer); +} + +static void testA_childWork() { + HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE); + + // + // Step 1: Create the new screen buffer in the child process and make it + // active. (Typically, it's handle 0x0F.) + // + + HANDLE newBuffer = createBuffer(); + + setConsoleActiveScreenBuffer(newBuffer); + writeTest(newBuffer, "<-- newBuffer -->"); + + Sleep(9000); + trace("child:----"); + + // Step 4: write to the newBuffer again. + writeTest(newBuffer, "TEST PASSED!"); + + // + // Step 5: Switch back to the original screen buffer and close the new + // buffer. The switch call succeeds, but the CloseHandle call freezes for + // several seconds, because conhost.exe crashes. + // + Sleep(3000); + + setConsoleActiveScreenBuffer(origBuffer); + writeTest(origBuffer, "writing to origBuffer"); + + closeHandle(newBuffer); + + // The console HWND is NULL. + trace("child: console HWND=0x%I64x", (long long)GetConsoleWindow()); + + // At this point, the console window has closed, but the parent/child + // processes are still running. Calling AllocConsole would fail, but + // calling FreeConsole followed by AllocConsole would both succeed, and a + // new console would appear. +} + +static void testA(int argc, char *argv[]) { + + if (!strcmp(argv[1], "A")) { + startChildProcess(L"A:parent"); + return; + } + + if (!strcmp(argv[1], "A:parent")) { + g_prefix = "parent: "; + trace("parent:----"); + dumpHandles(); + writeTest("<-- origBuffer -->"); + startChildInSameConsole(L"A:child"); + testA_parentWork(); + Sleep(120000); + return; + } + + if (!strcmp(argv[1], "A:child")) { + g_prefix = "child: "; + dumpHandles(); + testA_childWork(); + Sleep(120000); + return; + } +} + + + +/////////////////////////////////////////////////////////////////////////////// +// TEST B -- invert TEST A -- also crashes conhost on Windows 7 +// +// Test passes if the message "TEST PASSED!" is visible. +// Test commonly fails if conhost.exe crashes. +// +// Results: +// - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes +// - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS +// - Windows 8 Enterprise 32-bit: PASS +// - Windows 10 64-bit (legacy and non-legacy): PASS +// + +static void testB(int argc, char *argv[]) { + if (!strcmp(argv[1], "B")) { + startChildProcess(L"B:parent"); + return; + } + + if (!strcmp(argv[1], "B:parent")) { + g_prefix = "parent: "; + startChildInSameConsole(L"B:child"); + writeTest("<-- origBuffer -->"); + HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE); + + // + // Step 1: Create the new buffer and make it active. + // + trace("%s----", g_prefix); + HANDLE newBuffer = createBuffer(); + setConsoleActiveScreenBuffer(newBuffer); + writeTest(newBuffer, "<-- newBuffer -->"); + + // + // Step 4: Attempt to write again to the new buffer. + // + Sleep(9000); + trace("%s----", g_prefix); + writeTest(newBuffer, "TEST PASSED!"); + + // + // Step 5: Switch back to the original buffer. + // + Sleep(3000); + trace("%s----", g_prefix); + setConsoleActiveScreenBuffer(origBuffer); + closeHandle(newBuffer); + writeTest(origBuffer, "writing to the initial buffer"); + + Sleep(60000); + return; + } + + if (!strcmp(argv[1], "B:child")) { + g_prefix = "child: "; + Sleep(3000); + trace("%s----", g_prefix); + + // + // Step 2: Open the newly active buffer and write to it. + // + HANDLE newBuffer = openConout(); + writeTest(newBuffer, "writing to newBuffer"); + + // + // Step 3: Close the newly active buffer. + // + Sleep(3000); + closeHandle(newBuffer); + + Sleep(60000); + return; + } +} + + + +/////////////////////////////////////////////////////////////////////////////// +// TEST C -- Interleaving open/close of console handles also seems to break on +// Windows 7. +// +// Test: +// - child creates and activates newBuf1 +// - parent opens newBuf1 +// - child creates and activates newBuf2 +// - parent opens newBuf2, then closes newBuf1 +// - child switches back to newBuf1 +// * At this point, the console starts malfunctioning. +// - parent and child close newBuf2 +// - child closes newBuf1 +// +// Test passes if the message "TEST PASSED!" is visible. +// Test commonly fails if conhost.exe crashes. +// +// Results: +// - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes +// - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS +// - Windows 8 Enterprise 32-bit: PASS +// - Windows 10 64-bit (legacy and non-legacy): PASS +// + +static void testC(int argc, char *argv[]) { + if (!strcmp(argv[1], "C")) { + startChildProcess(L"C:parent"); + return; + } + + if (!strcmp(argv[1], "C:parent")) { + startChildInSameConsole(L"C:child"); + writeTest("<-- origBuffer -->"); + g_prefix = "parent: "; + + // At time=4, open newBuffer1. + Sleep(4000); + trace("%s---- t=4", g_prefix); + const HANDLE newBuffer1 = openConout(); + + // At time=8, open newBuffer2, and close newBuffer1. + Sleep(4000); + trace("%s---- t=8", g_prefix); + const HANDLE newBuffer2 = openConout(); + closeHandle(newBuffer1); + + // At time=25, cleanup of newBuffer2. + Sleep(17000); + trace("%s---- t=25", g_prefix); + closeHandle(newBuffer2); + + Sleep(240000); + return; + } + + if (!strcmp(argv[1], "C:child")) { + g_prefix = "child: "; + + // At time=2, create newBuffer1 and activate it. + Sleep(2000); + trace("%s---- t=2", g_prefix); + const HANDLE newBuffer1 = createBuffer(); + setConsoleActiveScreenBuffer(newBuffer1); + writeTest(newBuffer1, "<-- newBuffer1 -->"); + + // At time=6, create newBuffer2 and activate it. + Sleep(4000); + trace("%s---- t=6", g_prefix); + const HANDLE newBuffer2 = createBuffer(); + setConsoleActiveScreenBuffer(newBuffer2); + writeTest(newBuffer2, "<-- newBuffer2 -->"); + + // At time=10, attempt to switch back to newBuffer1. The parent process + // has opened and closed its handle to newBuffer1, so does it still exist? + Sleep(4000); + trace("%s---- t=10", g_prefix); + setConsoleActiveScreenBuffer(newBuffer1); + writeTest(newBuffer1, "write to newBuffer1: TEST PASSED!"); + + // At time=25, cleanup of newBuffer2. + Sleep(15000); + trace("%s---- t=25", g_prefix); + closeHandle(newBuffer2); + + // At time=35, cleanup of newBuffer1. The console should switch to the + // initial buffer again. + Sleep(10000); + trace("%s---- t=35", g_prefix); + closeHandle(newBuffer1); + + Sleep(240000); + return; + } +} + + + +/////////////////////////////////////////////////////////////////////////////// +// TEST D -- parent creates a new buffer, child launches, writes, +// closes it output handle, then parent writes again. (Also see TEST 2.) +// +// On success, this will appear: +// +// parent: <-- newBuffer --> +// child: writing to newBuffer +// parent: TEST PASSED! +// +// If this appears, it indicates that the child's closing its output handle did +// not destroy newBuffer. +// +// Results: +// - Windows 7 Ultimate SP1 32-bit: PASS +// - Windows 8 Enterprise 32-bit: PASS +// - Windows 10 64-bit (legacy and non-legacy): PASS +// + +static void testD(int argc, char *argv[]) { + if (!strcmp(argv[1], "D")) { + startChildProcess(L"D:parent"); + return; + } + + if (!strcmp(argv[1], "D:parent")) { + g_prefix = "parent: "; + HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE); + writeTest(origBuffer, "<-- origBuffer -->"); + + HANDLE newBuffer = createBuffer(); + writeTest(newBuffer, "<-- newBuffer -->"); + setConsoleActiveScreenBuffer(newBuffer); + + // At t=2, start a child process, explicitly forcing it to use + // newBuffer for its standard handles. These calls are apparently + // redundant on Windows 8 and up. + Sleep(2000); + trace("parent:----"); + trace("parent: starting child process"); + SetStdHandle(STD_OUTPUT_HANDLE, newBuffer); + SetStdHandle(STD_ERROR_HANDLE, newBuffer); + startChildInSameConsole(L"D:child"); + SetStdHandle(STD_OUTPUT_HANDLE, origBuffer); + SetStdHandle(STD_ERROR_HANDLE, origBuffer); + + // At t=6, write again to newBuffer. + Sleep(4000); + trace("parent:----"); + writeTest(newBuffer, "TEST PASSED!"); + + // At t=8, close the newBuffer. In earlier versions of windows + // (including Server 2008 R2), the console then switches back to + // origBuffer. As of Windows 8, it doesn't, because somehow the child + // process is keeping the console on newBuffer, even though the child + // process closed its STDIN/STDOUT/STDERR handles. Killing the child + // process by hand after the test finishes *does* force the console + // back to origBuffer. + Sleep(2000); + closeHandle(newBuffer); + + Sleep(120000); + return; + } + + if (!strcmp(argv[1], "D:child")) { + g_prefix = "child: "; + // At t=2, the child starts. + trace("child:----"); + dumpHandles(); + writeTest("writing to newBuffer"); + + // At t=4, the child explicitly closes its handle. + Sleep(2000); + trace("child:----"); + if (GetStdHandle(STD_ERROR_HANDLE) != GetStdHandle(STD_OUTPUT_HANDLE)) { + closeHandle(GetStdHandle(STD_ERROR_HANDLE)); + } + closeHandle(GetStdHandle(STD_OUTPUT_HANDLE)); + closeHandle(GetStdHandle(STD_INPUT_HANDLE)); + + Sleep(120000); + return; + } +} + + + +int main(int argc, char *argv[]) { + if (argc == 1) { + printf("USAGE: %s testnum\n", argv[0]); + return 0; + } + + if (argv[1][0] == '1') { + test1(argc, argv); + } else if (argv[1][0] == '2') { + test2(argc, argv); + } else if (argv[1][0] == 'A') { + testA(argc, argv); + } else if (argv[1][0] == 'B') { + testB(argc, argv); + } else if (argv[1][0] == 'C') { + testC(argc, argv); + } else if (argv[1][0] == 'D') { + testD(argc, argv); + } + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/ScreenBufferTest2.cc b/src/libs/3rdparty/winpty/misc/ScreenBufferTest2.cc new file mode 100644 index 00000000000..2b648c94093 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/ScreenBufferTest2.cc @@ -0,0 +1,151 @@ +#include + +#include "TestUtil.cc" + +const char *g_prefix = ""; + +static void dumpHandles() { + trace("%sSTDIN=0x%I64x STDOUT=0x%I64x STDERR=0x%I64x", + g_prefix, + (long long)GetStdHandle(STD_INPUT_HANDLE), + (long long)GetStdHandle(STD_OUTPUT_HANDLE), + (long long)GetStdHandle(STD_ERROR_HANDLE)); +} + +static HANDLE createBuffer() { + + // If sa isn't provided, the handle defaults to not-inheritable. + SECURITY_ATTRIBUTES sa = {0}; + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + + trace("%sCreating a new buffer...", g_prefix); + HANDLE conout = CreateConsoleScreenBuffer( + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &sa, + CONSOLE_TEXTMODE_BUFFER, NULL); + + trace("%sCreating a new buffer... 0x%I64x", g_prefix, (long long)conout); + return conout; +} + +static const char *successOrFail(BOOL ret) { + return ret ? "ok" : "FAILED"; +} + +static void setConsoleActiveScreenBuffer(HANDLE conout) { + trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called...", + g_prefix, (long long)conout); + trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called... %s", + g_prefix, (long long)conout, + successOrFail(SetConsoleActiveScreenBuffer(conout))); +} + +static void writeTest(HANDLE conout, const char *msg) { + char writeData[256]; + sprintf(writeData, "%s%s\n", g_prefix, msg); + + trace("%sWriting to 0x%I64x: '%s'...", + g_prefix, (long long)conout, msg); + DWORD actual = 0; + BOOL ret = WriteConsoleA(conout, writeData, strlen(writeData), &actual, NULL); + trace("%sWriting to 0x%I64x: '%s'... %s", + g_prefix, (long long)conout, msg, + successOrFail(ret && actual == strlen(writeData))); +} + +static HANDLE startChildInSameConsole(const wchar_t *args, BOOL + bInheritHandles=FALSE) { + wchar_t program[1024]; + wchar_t cmdline[1024]; + GetModuleFileNameW(NULL, program, 1024); + swprintf(cmdline, L"\"%ls\" %ls", program, args); + + STARTUPINFOW sui; + PROCESS_INFORMATION pi; + memset(&sui, 0, sizeof(sui)); + memset(&pi, 0, sizeof(pi)); + sui.cb = sizeof(sui); + + CreateProcessW(program, cmdline, + NULL, NULL, + /*bInheritHandles=*/bInheritHandles, + /*dwCreationFlags=*/0, + NULL, NULL, + &sui, &pi); + + return pi.hProcess; +} + +static HANDLE dup(HANDLE h, HANDLE targetProcess) { + HANDLE h2 = INVALID_HANDLE_VALUE; + BOOL ret = DuplicateHandle( + GetCurrentProcess(), h, + targetProcess, &h2, + 0, TRUE, DUPLICATE_SAME_ACCESS); + trace("dup(0x%I64x) to process 0x%I64x... %s, 0x%I64x", + (long long)h, + (long long)targetProcess, + successOrFail(ret), + (long long)h2); + return h2; +} + +int main(int argc, char *argv[]) { + if (argc == 1) { + startChildProcess(L"parent"); + return 0; + } + + if (!strcmp(argv[1], "parent")) { + g_prefix = "parent: "; + dumpHandles(); + HANDLE hChild = startChildInSameConsole(L"child"); + + // Windows 10. + HANDLE orig1 = GetStdHandle(STD_OUTPUT_HANDLE); + HANDLE new1 = createBuffer(); + + Sleep(2000); + setConsoleActiveScreenBuffer(new1); + + // Handle duplication results to child process in same console: + // - Windows XP: fails + // - Windows 7 Ultimate SP1 32-bit: fails + // - Windows Server 2008 R2 Datacenter SP1 64-bit: fails + // - Windows 8 Enterprise 32-bit: succeeds + // - Windows 10: succeeds + HANDLE orig2 = dup(orig1, GetCurrentProcess()); + HANDLE new2 = dup(new1, GetCurrentProcess()); + + dup(orig1, hChild); + dup(new1, hChild); + + // The writes to orig1/orig2 are invisible. The writes to new1/new2 + // are visible. + writeTest(orig1, "write to orig1"); + writeTest(orig2, "write to orig2"); + writeTest(new1, "write to new1"); + writeTest(new2, "write to new2"); + + Sleep(120000); + return 0; + } + + if (!strcmp(argv[1], "child")) { + g_prefix = "child: "; + dumpHandles(); + Sleep(4000); + for (unsigned int i = 0x1; i <= 0xB0; ++i) { + char msg[256]; + sprintf(msg, "Write to handle 0x%x", i); + HANDLE h = reinterpret_cast(i); + writeTest(h, msg); + } + Sleep(120000); + return 0; + } + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/SelectAllTest.cc b/src/libs/3rdparty/winpty/misc/SelectAllTest.cc new file mode 100644 index 00000000000..a6c27739d80 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/SelectAllTest.cc @@ -0,0 +1,45 @@ +#define _WIN32_WINNT 0x0501 +#include +#include + +#include "../src/shared/DebugClient.cc" + +const int SC_CONSOLE_MARK = 0xFFF2; +const int SC_CONSOLE_SELECT_ALL = 0xFFF5; + +CALLBACK DWORD pausingThread(LPVOID dummy) +{ + HWND hwnd = GetConsoleWindow(); + while (true) { + SendMessage(hwnd, WM_SYSCOMMAND, SC_CONSOLE_SELECT_ALL, 0); + Sleep(1000); + SendMessage(hwnd, WM_CHAR, 27, 0x00010001); + Sleep(1000); + } +} + +int main() +{ + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO info; + + GetConsoleScreenBufferInfo(out, &info); + COORD initial = info.dwCursorPosition; + + CreateThread(NULL, 0, + pausingThread, NULL, + 0, NULL); + + for (int i = 0; i < 30; ++i) { + Sleep(100); + GetConsoleScreenBufferInfo(out, &info); + if (memcmp(&info.dwCursorPosition, &initial, sizeof(COORD)) != 0) { + trace("cursor moved to [%d,%d]", + info.dwCursorPosition.X, + info.dwCursorPosition.Y); + } else { + trace("cursor in expected position"); + } + } + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/SetBufInfo.cc b/src/libs/3rdparty/winpty/misc/SetBufInfo.cc new file mode 100644 index 00000000000..f37c31bdf72 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/SetBufInfo.cc @@ -0,0 +1,90 @@ +#include + +#include +#include +#include + +#include "TestUtil.cc" + +static void usage() { + printf("usage: SetBufInfo [-set] [-buf W H] [-win W H] [-pos X Y]\n"); +} + +int main(int argc, char *argv[]) { + const HANDLE conout = CreateFileW(L"CONOUT$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + ASSERT(conout != INVALID_HANDLE_VALUE); + + bool change = false; + BOOL success; + CONSOLE_SCREEN_BUFFER_INFOEX info = {}; + info.cbSize = sizeof(info); + + success = GetConsoleScreenBufferInfoEx(conout, &info); + ASSERT(success && "GetConsoleScreenBufferInfoEx failed"); + + for (int i = 1; i < argc; ) { + std::string arg = argv[i]; + if (arg == "-buf" && (i + 2) < argc) { + info.dwSize.X = atoi(argv[i + 1]); + info.dwSize.Y = atoi(argv[i + 2]); + i += 3; + change = true; + } else if (arg == "-pos" && (i + 2) < argc) { + int dx = info.srWindow.Right - info.srWindow.Left; + int dy = info.srWindow.Bottom - info.srWindow.Top; + info.srWindow.Left = atoi(argv[i + 1]); + info.srWindow.Top = atoi(argv[i + 2]); + i += 3; + info.srWindow.Right = info.srWindow.Left + dx; + info.srWindow.Bottom = info.srWindow.Top + dy; + change = true; + } else if (arg == "-win" && (i + 2) < argc) { + info.srWindow.Right = info.srWindow.Left + atoi(argv[i + 1]) - 1; + info.srWindow.Bottom = info.srWindow.Top + atoi(argv[i + 2]) - 1; + i += 3; + change = true; + } else if (arg == "-set") { + change = true; + ++i; + } else if (arg == "--help" || arg == "-help") { + usage(); + exit(0); + } else { + fprintf(stderr, "error: unrecognized argument: %s\n", arg.c_str()); + usage(); + exit(1); + } + } + + if (change) { + success = SetConsoleScreenBufferInfoEx(conout, &info); + if (success) { + printf("success\n"); + } else { + printf("SetConsoleScreenBufferInfoEx call failed\n"); + } + success = GetConsoleScreenBufferInfoEx(conout, &info); + ASSERT(success && "GetConsoleScreenBufferInfoEx failed"); + } + + auto dump = [](const char *fmt, ...) { + char msg[256]; + va_list ap; + va_start(ap, fmt); + vsprintf(msg, fmt, ap); + va_end(ap); + trace("%s", msg); + printf("%s\n", msg); + }; + + dump("buffer-size: %d x %d", info.dwSize.X, info.dwSize.Y); + dump("window-size: %d x %d", + info.srWindow.Right - info.srWindow.Left + 1, + info.srWindow.Bottom - info.srWindow.Top + 1); + dump("window-pos: %d, %d", info.srWindow.Left, info.srWindow.Top); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/SetBufferSize.cc b/src/libs/3rdparty/winpty/misc/SetBufferSize.cc new file mode 100644 index 00000000000..b50a1f8dc36 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/SetBufferSize.cc @@ -0,0 +1,32 @@ +#include + +#include +#include + +#include "TestUtil.cc" + +int main(int argc, char *argv[]) { + if (argc != 3) { + printf("Usage: %s x y width height\n", argv[0]); + return 1; + } + + const HANDLE conout = CreateFileW(L"CONOUT$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + ASSERT(conout != INVALID_HANDLE_VALUE); + + COORD size = { + (short)atoi(argv[1]), + (short)atoi(argv[2]), + }; + + BOOL ret = SetConsoleScreenBufferSize(conout, size); + const unsigned lastError = GetLastError(); + const char *const retStr = ret ? "OK" : "failed"; + trace("SetConsoleScreenBufferSize ret: %s (LastError=0x%x)", retStr, lastError); + printf("SetConsoleScreenBufferSize ret: %s (LastError=0x%x)\n", retStr, lastError); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/SetCursorPos.cc b/src/libs/3rdparty/winpty/misc/SetCursorPos.cc new file mode 100644 index 00000000000..d20fdbdfc0d --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/SetCursorPos.cc @@ -0,0 +1,10 @@ +#include + +#include "TestUtil.cc" + +int main(int argc, char *argv[]) { + int col = atoi(argv[1]); + int row = atoi(argv[2]); + setCursorPos(col, row); + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/SetFont.cc b/src/libs/3rdparty/winpty/misc/SetFont.cc new file mode 100644 index 00000000000..9bcd4b4cc97 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/SetFont.cc @@ -0,0 +1,145 @@ +#include +#include +#include +#include +#include + +#include "TestUtil.cc" + +#define COUNT_OF(array) (sizeof(array) / sizeof((array)[0])) + +// See https://en.wikipedia.org/wiki/List_of_CJK_fonts +const wchar_t kMSGothic[] = { 0xff2d, 0xff33, 0x0020, 0x30b4, 0x30b7, 0x30c3, 0x30af, 0 }; // Japanese +const wchar_t kNSimSun[] = { 0x65b0, 0x5b8b, 0x4f53, 0 }; // Simplified Chinese +const wchar_t kMingLight[] = { 0x7d30, 0x660e, 0x9ad4, 0 }; // Traditional Chinese +const wchar_t kGulimChe[] = { 0xad74, 0xb9bc, 0xccb4, 0 }; // Korean + +int main() { + setlocale(LC_ALL, ""); + wchar_t *cmdline = GetCommandLineW(); + int argc = 0; + wchar_t **argv = CommandLineToArgvW(cmdline, &argc); + const HANDLE conout = openConout(); + + if (argc == 1) { + cprintf(L"Usage:\n"); + cprintf(L" SetFont \n"); + cprintf(L" SetFont options\n"); + cprintf(L"\n"); + cprintf(L"Options for SetCurrentConsoleFontEx:\n"); + cprintf(L" -idx INDEX\n"); + cprintf(L" -w WIDTH\n"); + cprintf(L" -h HEIGHT\n"); + cprintf(L" -family (0xNN|NN)\n"); + cprintf(L" -weight (normal|bold|NNN)\n"); + cprintf(L" -face FACENAME\n"); + cprintf(L" -face-{gothic|simsun|minglight|gulimche) [JP,CN-sim,CN-tra,KR]\n"); + cprintf(L" -tt\n"); + cprintf(L" -vec\n"); + cprintf(L" -vp\n"); + cprintf(L" -dev\n"); + cprintf(L" -roman\n"); + cprintf(L" -swiss\n"); + cprintf(L" -modern\n"); + cprintf(L" -script\n"); + cprintf(L" -decorative\n"); + return 0; + } + + if (isdigit(argv[1][0])) { + int index = _wtoi(argv[1]); + HMODULE kernel32 = LoadLibraryW(L"kernel32.dll"); + FARPROC proc = GetProcAddress(kernel32, "SetConsoleFont"); + if (proc == NULL) { + cprintf(L"Couldn't get address of SetConsoleFont\n"); + } else { + BOOL ret = reinterpret_cast(proc)( + conout, index); + cprintf(L"SetFont returned %d\n", ret); + } + return 0; + } + + CONSOLE_FONT_INFOEX fontex = {0}; + fontex.cbSize = sizeof(fontex); + + for (int i = 1; i < argc; ++i) { + std::wstring arg = argv[i]; + if (i + 1 < argc) { + std::wstring next = argv[i + 1]; + if (arg == L"-idx") { + fontex.nFont = _wtoi(next.c_str()); + ++i; continue; + } else if (arg == L"-w") { + fontex.dwFontSize.X = _wtoi(next.c_str()); + ++i; continue; + } else if (arg == L"-h") { + fontex.dwFontSize.Y = _wtoi(next.c_str()); + ++i; continue; + } else if (arg == L"-weight") { + if (next == L"normal") { + fontex.FontWeight = 400; + } else if (next == L"bold") { + fontex.FontWeight = 700; + } else { + fontex.FontWeight = _wtoi(next.c_str()); + } + ++i; continue; + } else if (arg == L"-face") { + wcsncpy(fontex.FaceName, next.c_str(), COUNT_OF(fontex.FaceName)); + ++i; continue; + } else if (arg == L"-family") { + fontex.FontFamily = strtol(narrowString(next).c_str(), nullptr, 0); + ++i; continue; + } + } + if (arg == L"-tt") { + fontex.FontFamily |= TMPF_TRUETYPE; + } else if (arg == L"-vec") { + fontex.FontFamily |= TMPF_VECTOR; + } else if (arg == L"-vp") { + // Setting the TMPF_FIXED_PITCH bit actually indicates variable + // pitch. + fontex.FontFamily |= TMPF_FIXED_PITCH; + } else if (arg == L"-dev") { + fontex.FontFamily |= TMPF_DEVICE; + } else if (arg == L"-roman") { + fontex.FontFamily = (fontex.FontFamily & ~0xF0) | FF_ROMAN; + } else if (arg == L"-swiss") { + fontex.FontFamily = (fontex.FontFamily & ~0xF0) | FF_SWISS; + } else if (arg == L"-modern") { + fontex.FontFamily = (fontex.FontFamily & ~0xF0) | FF_MODERN; + } else if (arg == L"-script") { + fontex.FontFamily = (fontex.FontFamily & ~0xF0) | FF_SCRIPT; + } else if (arg == L"-decorative") { + fontex.FontFamily = (fontex.FontFamily & ~0xF0) | FF_DECORATIVE; + } else if (arg == L"-face-gothic") { + wcsncpy(fontex.FaceName, kMSGothic, COUNT_OF(fontex.FaceName)); + } else if (arg == L"-face-simsun") { + wcsncpy(fontex.FaceName, kNSimSun, COUNT_OF(fontex.FaceName)); + } else if (arg == L"-face-minglight") { + wcsncpy(fontex.FaceName, kMingLight, COUNT_OF(fontex.FaceName)); + } else if (arg == L"-face-gulimche") { + wcsncpy(fontex.FaceName, kGulimChe, COUNT_OF(fontex.FaceName)); + } else { + cprintf(L"Unrecognized argument: %ls\n", arg.c_str()); + exit(1); + } + } + + cprintf(L"Setting to: nFont=%u dwFontSize=(%d,%d) " + L"FontFamily=0x%x FontWeight=%u " + L"FaceName=\"%ls\"\n", + static_cast(fontex.nFont), + fontex.dwFontSize.X, fontex.dwFontSize.Y, + fontex.FontFamily, fontex.FontWeight, + fontex.FaceName); + + BOOL ret = SetCurrentConsoleFontEx( + conout, + FALSE, + &fontex); + cprintf(L"SetCurrentConsoleFontEx returned %d\n", ret); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/SetWindowRect.cc b/src/libs/3rdparty/winpty/misc/SetWindowRect.cc new file mode 100644 index 00000000000..6291dd67459 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/SetWindowRect.cc @@ -0,0 +1,36 @@ +#include + +#include +#include + +#include "TestUtil.cc" + +int main(int argc, char *argv[]) { + if (argc != 5) { + printf("Usage: %s x y width height\n", argv[0]); + return 1; + } + + const HANDLE conout = CreateFileW(L"CONOUT$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + ASSERT(conout != INVALID_HANDLE_VALUE); + + SMALL_RECT sr = { + (short)atoi(argv[1]), + (short)atoi(argv[2]), + (short)(atoi(argv[1]) + atoi(argv[3]) - 1), + (short)(atoi(argv[2]) + atoi(argv[4]) - 1), + }; + + trace("Calling SetConsoleWindowInfo with {L=%d,T=%d,R=%d,B=%d}", + sr.Left, sr.Top, sr.Right, sr.Bottom); + BOOL ret = SetConsoleWindowInfo(conout, TRUE, &sr); + const unsigned lastError = GetLastError(); + const char *const retStr = ret ? "OK" : "failed"; + trace("SetConsoleWindowInfo ret: %s (LastError=0x%x)", retStr, lastError); + printf("SetConsoleWindowInfo ret: %s (LastError=0x%x)\n", retStr, lastError); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/ShowArgv.cc b/src/libs/3rdparty/winpty/misc/ShowArgv.cc new file mode 100644 index 00000000000..29a0f091316 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/ShowArgv.cc @@ -0,0 +1,12 @@ +// This test program is useful for studying commandline<->argv conversion. + +#include +#include + +int main(int argc, char **argv) +{ + printf("cmdline = [%s]\n", GetCommandLine()); + for (int i = 0; i < argc; ++i) + printf("[%s]\n", argv[i]); + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/ShowConsoleInput.cc b/src/libs/3rdparty/winpty/misc/ShowConsoleInput.cc new file mode 100644 index 00000000000..75fbfb81f10 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/ShowConsoleInput.cc @@ -0,0 +1,40 @@ +#include +#include +#include + +int main(int argc, char *argv[]) +{ + static int escCount = 0; + + HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); + while (true) { + DWORD count; + INPUT_RECORD ir; + if (!ReadConsoleInput(hStdin, &ir, 1, &count)) { + printf("ReadConsoleInput failed\n"); + return 1; + } + + if (true) { + DWORD mode; + GetConsoleMode(hStdin, &mode); + SetConsoleMode(hStdin, mode & ~ENABLE_PROCESSED_INPUT); + } + + if (ir.EventType == KEY_EVENT) { + const KEY_EVENT_RECORD &ker = ir.Event.KeyEvent; + printf("%s", ker.bKeyDown ? "dn" : "up"); + printf(" ch="); + if (isprint(ker.uChar.AsciiChar)) + printf("'%c'", ker.uChar.AsciiChar); + printf("%d", ker.uChar.AsciiChar); + printf(" vk=%#x", ker.wVirtualKeyCode); + printf(" scan=%#x", ker.wVirtualScanCode); + printf(" state=%#x", (int)ker.dwControlKeyState); + printf(" repeat=%d", ker.wRepeatCount); + printf("\n"); + if (ker.uChar.AsciiChar == 27 && ++escCount == 6) + break; + } + } +} diff --git a/src/libs/3rdparty/winpty/misc/Spew.py b/src/libs/3rdparty/winpty/misc/Spew.py new file mode 100644 index 00000000000..9d1796af375 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Spew.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +i = 0; +while True: + i += 1 + print(i) diff --git a/src/libs/3rdparty/winpty/misc/TestUtil.cc b/src/libs/3rdparty/winpty/misc/TestUtil.cc new file mode 100644 index 00000000000..c832a12b858 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/TestUtil.cc @@ -0,0 +1,172 @@ +// This file is included into test programs using #include + +#include +#include +#include +#include +#include +#include +#include + +#include "../src/shared/DebugClient.h" +#include "../src/shared/TimeMeasurement.h" + +#include "../src/shared/DebugClient.cc" +#include "../src/shared/WinptyAssert.cc" +#include "../src/shared/WinptyException.cc" + +// Launch this test program again, in a new console that we will destroy. +static void startChildProcess(const wchar_t *args) { + wchar_t program[1024]; + wchar_t cmdline[1024]; + GetModuleFileNameW(NULL, program, 1024); + swprintf(cmdline, L"\"%ls\" %ls", program, args); + + STARTUPINFOW sui; + PROCESS_INFORMATION pi; + memset(&sui, 0, sizeof(sui)); + memset(&pi, 0, sizeof(pi)); + sui.cb = sizeof(sui); + + CreateProcessW(program, cmdline, + NULL, NULL, + /*bInheritHandles=*/FALSE, + /*dwCreationFlags=*/CREATE_NEW_CONSOLE, + NULL, NULL, + &sui, &pi); +} + +static void setBufferSize(HANDLE conout, int x, int y) { + COORD size = { static_cast(x), static_cast(y) }; + BOOL success = SetConsoleScreenBufferSize(conout, size); + trace("setBufferSize: (%d,%d), result=%d", x, y, success); +} + +static void setWindowPos(HANDLE conout, int x, int y, int w, int h) { + SMALL_RECT r = { + static_cast(x), static_cast(y), + static_cast(x + w - 1), + static_cast(y + h - 1) + }; + BOOL success = SetConsoleWindowInfo(conout, /*bAbsolute=*/TRUE, &r); + trace("setWindowPos: (%d,%d,%d,%d), result=%d", x, y, w, h, success); +} + +static void setCursorPos(HANDLE conout, int x, int y) { + COORD coord = { static_cast(x), static_cast(y) }; + SetConsoleCursorPosition(conout, coord); +} + +static void setBufferSize(int x, int y) { + setBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), x, y); +} + +static void setWindowPos(int x, int y, int w, int h) { + setWindowPos(GetStdHandle(STD_OUTPUT_HANDLE), x, y, w, h); +} + +static void setCursorPos(int x, int y) { + setCursorPos(GetStdHandle(STD_OUTPUT_HANDLE), x, y); +} + +static void countDown(int sec) { + for (int i = sec; i > 0; --i) { + printf("%d.. ", i); + fflush(stdout); + Sleep(1000); + } + printf("\n"); +} + +static void writeBox(int x, int y, int w, int h, char ch, int attributes=7) { + CHAR_INFO info = { 0 }; + info.Char.AsciiChar = ch; + info.Attributes = attributes; + std::vector buf(w * h, info); + HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE); + COORD bufSize = { static_cast(w), static_cast(h) }; + COORD bufCoord = { 0, 0 }; + SMALL_RECT writeRegion = { + static_cast(x), + static_cast(y), + static_cast(x + w - 1), + static_cast(y + h - 1) + }; + WriteConsoleOutputA(conout, buf.data(), bufSize, bufCoord, &writeRegion); +} + +static void setChar(int x, int y, char ch, int attributes=7) { + writeBox(x, y, 1, 1, ch, attributes); +} + +static void fillChar(int x, int y, int repeat, char ch) { + COORD coord = { static_cast(x), static_cast(y) }; + DWORD actual = 0; + FillConsoleOutputCharacterA( + GetStdHandle(STD_OUTPUT_HANDLE), + ch, repeat, coord, &actual); +} + +static void repeatChar(int count, char ch) { + for (int i = 0; i < count; ++i) { + putchar(ch); + } + fflush(stdout); +} + +// I don't know why, but wprintf fails to print this face name, +// "MS ゴシック" (aka MS Gothic). It helps to use wprintf instead of printf, and +// it helps to call `setlocale(LC_ALL, "")`, but the Japanese symbols are +// ultimately converted to `?` symbols, even though MS Gothic is able to +// display its own name, and the current code page is 932 (Shift-JIS). +static void cvfprintf(HANDLE conout, const wchar_t *fmt, va_list ap) { + wchar_t buffer[256]; + vswprintf(buffer, 256 - 1, fmt, ap); + buffer[255] = L'\0'; + DWORD actual = 0; + if (!WriteConsoleW(conout, buffer, wcslen(buffer), &actual, NULL)) { + wprintf(L"WriteConsoleW call failed!\n"); + } +} + +static void cfprintf(HANDLE conout, const wchar_t *fmt, ...) { + va_list ap; + va_start(ap, fmt); + cvfprintf(conout, fmt, ap); + va_end(ap); +} + +static void cprintf(const wchar_t *fmt, ...) { + va_list ap; + va_start(ap, fmt); + cvfprintf(GetStdHandle(STD_OUTPUT_HANDLE), fmt, ap); + va_end(ap); +} + +static std::string narrowString(const std::wstring &input) +{ + int mblen = WideCharToMultiByte( + CP_UTF8, 0, + input.data(), input.size(), + NULL, 0, NULL, NULL); + if (mblen <= 0) { + return std::string(); + } + std::vector tmp(mblen); + int mblen2 = WideCharToMultiByte( + CP_UTF8, 0, + input.data(), input.size(), + tmp.data(), tmp.size(), + NULL, NULL); + assert(mblen2 == mblen); + return std::string(tmp.data(), tmp.size()); +} + +HANDLE openConout() { + const HANDLE conout = CreateFileW(L"CONOUT$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + ASSERT(conout != INVALID_HANDLE_VALUE); + return conout; +} diff --git a/src/libs/3rdparty/winpty/misc/UnicodeDoubleWidthTest.cc b/src/libs/3rdparty/winpty/misc/UnicodeDoubleWidthTest.cc new file mode 100644 index 00000000000..7210d410323 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/UnicodeDoubleWidthTest.cc @@ -0,0 +1,102 @@ +// Demonstrates how U+30FC is sometimes handled as a single-width character +// when it should be handled as a double-width character. +// +// It only runs on computers where 932 is a valid code page. Set the system +// local to "Japanese (Japan)" to ensure this. +// +// The problem seems to happen when U+30FC is printed in a console using the +// Lucida Console font, and only when that font is at certain sizes. +// + +#include +#include +#include +#include +#include +#include + +#include "TestUtil.cc" + +#define COUNT_OF(x) (sizeof(x) / sizeof((x)[0])) + +static void setFont(const wchar_t *faceName, int pxSize) { + CONSOLE_FONT_INFOEX infoex = {0}; + infoex.cbSize = sizeof(infoex); + infoex.dwFontSize.Y = pxSize; + wcsncpy(infoex.FaceName, faceName, COUNT_OF(infoex.FaceName)); + BOOL ret = SetCurrentConsoleFontEx( + GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &infoex); + assert(ret); +} + +static bool performTest(const wchar_t testChar) { + const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE); + + SetConsoleTextAttribute(conout, 7); + + system("cls"); + DWORD actual = 0; + BOOL ret = WriteConsoleW(conout, &testChar, 1, &actual, NULL); + assert(ret && actual == 1); + + CHAR_INFO verify[2]; + COORD bufSize = {2, 1}; + COORD bufCoord = {0, 0}; + const SMALL_RECT readRegion = {0, 0, 1, 0}; + SMALL_RECT actualRegion = readRegion; + ret = ReadConsoleOutputW(conout, verify, bufSize, bufCoord, &actualRegion); + assert(ret && !memcmp(&readRegion, &actualRegion, sizeof(readRegion))); + assert(verify[0].Char.UnicodeChar == testChar); + + if (verify[1].Char.UnicodeChar == testChar) { + // Typical double-width behavior with a TrueType font. Pass. + assert(verify[0].Attributes == 0x107); + assert(verify[1].Attributes == 0x207); + return true; + } else if (verify[1].Char.UnicodeChar == 0) { + // Typical double-width behavior with a Raster Font. Pass. + assert(verify[0].Attributes == 7); + assert(verify[1].Attributes == 0); + return true; + } else if (verify[1].Char.UnicodeChar == L' ') { + // Single-width behavior. Fail. + assert(verify[0].Attributes == 7); + assert(verify[1].Attributes == 7); + return false; + } else { + // Unexpected output. + assert(false); + } +} + +int main(int argc, char *argv[]) { + setlocale(LC_ALL, ""); + if (argc == 1) { + startChildProcess(L"CHILD"); + return 0; + } + + assert(SetConsoleCP(932)); + assert(SetConsoleOutputCP(932)); + + const wchar_t testChar = 0x30FC; + const wchar_t *const faceNames[] = { + L"Lucida Console", + L"Consolas", + L"MS ゴシック", + }; + + trace("Test started"); + + for (auto faceName : faceNames) { + for (int px = 1; px <= 50; ++px) { + setFont(faceName, px); + if (!performTest(testChar)) { + trace("FAILURE: %s %dpx", narrowString(faceName).c_str(), px); + } + } + } + + trace("Test complete"); + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/UnicodeWideTest1.cc b/src/libs/3rdparty/winpty/misc/UnicodeWideTest1.cc new file mode 100644 index 00000000000..a8d798e70dd --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/UnicodeWideTest1.cc @@ -0,0 +1,246 @@ +#include + +#include +#include + +#include "TestUtil.cc" + +#define COUNT_OF(x) (sizeof(x) / sizeof((x)[0])) + + +CHAR_INFO ci(wchar_t ch, WORD attributes) { + CHAR_INFO ret; + ret.Char.UnicodeChar = ch; + ret.Attributes = attributes; + return ret; +} + +CHAR_INFO ci(wchar_t ch) { + return ci(ch, 7); +} + +CHAR_INFO ci() { + return ci(L' '); +} + +bool operator==(SMALL_RECT x, SMALL_RECT y) { + return !memcmp(&x, &y, sizeof(x)); +} + +SMALL_RECT sr(COORD pt, COORD size) { + return { + pt.X, pt.Y, + static_cast(pt.X + size.X - 1), + static_cast(pt.Y + size.Y - 1) + }; +} + +static void set( + const COORD pt, + const COORD size, + const std::vector &data) { + assert(data.size() == size.X * size.Y); + SMALL_RECT writeRegion = sr(pt, size); + BOOL ret = WriteConsoleOutputW( + GetStdHandle(STD_OUTPUT_HANDLE), + data.data(), size, {0, 0}, &writeRegion); + assert(ret && writeRegion == sr(pt, size)); +} + +static void set( + const COORD pt, + const std::vector &data) { + set(pt, {static_cast(data.size()), 1}, data); +} + +static void writeAttrsAt( + const COORD pt, + const std::vector &data) { + DWORD actual = 0; + BOOL ret = WriteConsoleOutputAttribute( + GetStdHandle(STD_OUTPUT_HANDLE), + data.data(), data.size(), pt, &actual); + assert(ret && actual == data.size()); +} + +static void writeCharsAt( + const COORD pt, + const std::vector &data) { + DWORD actual = 0; + BOOL ret = WriteConsoleOutputCharacterW( + GetStdHandle(STD_OUTPUT_HANDLE), + data.data(), data.size(), pt, &actual); + assert(ret && actual == data.size()); +} + +static void writeChars( + const std::vector &data) { + DWORD actual = 0; + BOOL ret = WriteConsoleW( + GetStdHandle(STD_OUTPUT_HANDLE), + data.data(), data.size(), &actual, NULL); + assert(ret && actual == data.size()); +} + +std::vector get( + const COORD pt, + const COORD size) { + std::vector data(size.X * size.Y); + SMALL_RECT readRegion = sr(pt, size); + BOOL ret = ReadConsoleOutputW( + GetStdHandle(STD_OUTPUT_HANDLE), + data.data(), size, {0, 0}, &readRegion); + assert(ret && readRegion == sr(pt, size)); + return data; +} + +std::vector readCharsAt( + const COORD pt, + int size) { + std::vector data(size); + DWORD actual = 0; + BOOL ret = ReadConsoleOutputCharacterW( + GetStdHandle(STD_OUTPUT_HANDLE), + data.data(), data.size(), pt, &actual); + assert(ret); + data.resize(actual); // With double-width chars, we can read fewer than `size`. + return data; +} + +static void dump(const COORD pt, const COORD size) { + for (CHAR_INFO ci : get(pt, size)) { + printf("%04X %04X\n", ci.Char.UnicodeChar, ci.Attributes); + } +} + +static void dumpCharsAt(const COORD pt, int size) { + for (wchar_t ch : readCharsAt(pt, size)) { + printf("%04X\n", ch); + } +} + +static COORD getCursorPos() { + CONSOLE_SCREEN_BUFFER_INFO info = { sizeof(info) }; + assert(GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info)); + return info.dwCursorPosition; +} + +static void test1() { + // We write "䀀䀀", then write "䀁" in the middle of the two. The second + // write turns the first and last cells into spaces. The LEADING/TRAILING + // flags retain consistency. + printf("test1 - overlap full-width char with full-width char\n"); + writeCharsAt({1,0}, {0x4000, 0x4000}); + dump({0,0}, {6,1}); + printf("\n"); + writeCharsAt({2,0}, {0x4001}); + dump({0,0}, {6,1}); + printf("\n"); +} + +static void test2() { + // Like `test1`, but use a lower-level API to do the write. Consistency is + // preserved here too -- the first and last cells are replaced with spaces. + printf("test2 - overlap full-width char with full-width char (lowlevel)\n"); + writeCharsAt({1,0}, {0x4000, 0x4000}); + dump({0,0}, {6,1}); + printf("\n"); + set({2,0}, {ci(0x4001,0x107), ci(0x4001,0x207)}); + dump({0,0}, {6,1}); + printf("\n"); +} + +static void test3() { + // However, the lower-level API can break the LEADING/TRAILING invariant + // explicitly: + printf("test3 - explicitly violate LEADING/TRAILING using lowlevel API\n"); + set({1,0}, { + ci(0x4000, 0x207), + ci(0x4001, 0x107), + ci(0x3044, 7), + ci(L'X', 0x107), + ci(L'X', 0x207), + }); + dump({0,0}, {7,1}); +} + +static void test4() { + // It is possible for the two cells of a double-width character to have two + // colors. + printf("test4 - use lowlevel to assign two colors to one full-width char\n"); + set({0,0}, { + ci(0x4000, 0x142), + ci(0x4000, 0x224), + }); + dump({0,0}, {2,1}); +} + +static void test5() { + // WriteConsoleOutputAttribute doesn't seem to affect the LEADING/TRAILING + // flags. + printf("test5 - WriteConsoleOutputAttribute cannot affect LEADING/TRAILING\n"); + + // Trying to clear the flags doesn't work... + writeCharsAt({0,0}, {0x4000}); + dump({0,0}, {2,1}); + writeAttrsAt({0,0}, {0x42, 0x24}); + printf("\n"); + dump({0,0}, {2,1}); + + // ... and trying to add them also doesn't work. + writeCharsAt({0,1}, {'A', ' '}); + writeAttrsAt({0,1}, {0x107, 0x207}); + printf("\n"); + dump({0,1}, {2,1}); +} + +static void test6() { + // The cursor position may be on either cell of a double-width character. + // Visually, the cursor appears under both cells, regardless of which + // specific one has the cursor. + printf("test6 - cursor can be either left or right cell of full-width char\n"); + + writeCharsAt({2,1}, {0x4000}); + + setCursorPos(2, 1); + auto pos1 = getCursorPos(); + Sleep(1000); + + setCursorPos(3, 1); + auto pos2 = getCursorPos(); + Sleep(1000); + + setCursorPos(0, 15); + printf("%d,%d\n", pos1.X, pos1.Y); + printf("%d,%d\n", pos2.X, pos2.Y); +} + +static void runTest(void (&test)()) { + system("cls"); + setCursorPos(0, 14); + test(); + system("pause"); +} + +int main(int argc, char *argv[]) { + if (argc == 1) { + startChildProcess(L"CHILD"); + return 0; + } + + setWindowPos(0, 0, 1, 1); + setBufferSize(80, 40); + setWindowPos(0, 0, 80, 40); + + auto cp = GetConsoleOutputCP(); + assert(cp == 932 || cp == 936 || cp == 949 || cp == 950); + + runTest(test1); + runTest(test2); + runTest(test3); + runTest(test4); + runTest(test5); + runTest(test6); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/UnicodeWideTest2.cc b/src/libs/3rdparty/winpty/misc/UnicodeWideTest2.cc new file mode 100644 index 00000000000..05f80f70bd1 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/UnicodeWideTest2.cc @@ -0,0 +1,130 @@ +// +// Test half-width vs full-width characters. +// + +#include +#include +#include +#include + +#include "TestUtil.cc" + +static void writeChars(const wchar_t *text) { + wcslen(text); + const int len = wcslen(text); + DWORD actual = 0; + BOOL ret = WriteConsoleW( + GetStdHandle(STD_OUTPUT_HANDLE), + text, len, &actual, NULL); + trace("writeChars: ret=%d, actual=%lld", ret, (long long)actual); +} + +static void dumpChars(int x, int y, int w, int h) { + BOOL ret; + const COORD bufSize = {w, h}; + const COORD bufCoord = {0, 0}; + const SMALL_RECT topLeft = {x, y, x + w - 1, y + h - 1}; + CHAR_INFO mbcsData[w * h]; + CHAR_INFO unicodeData[w * h]; + SMALL_RECT readRegion; + readRegion = topLeft; + ret = ReadConsoleOutputW(GetStdHandle(STD_OUTPUT_HANDLE), unicodeData, + bufSize, bufCoord, &readRegion); + assert(ret); + readRegion = topLeft; + ret = ReadConsoleOutputA(GetStdHandle(STD_OUTPUT_HANDLE), mbcsData, + bufSize, bufCoord, &readRegion); + assert(ret); + + printf("\n"); + for (int i = 0; i < w * h; ++i) { + printf("(%02d,%02d) CHAR: %04x %4x -- %02x %4x\n", + x + i % w, y + i / w, + (unsigned short)unicodeData[i].Char.UnicodeChar, + (unsigned short)unicodeData[i].Attributes, + (unsigned char)mbcsData[i].Char.AsciiChar, + (unsigned short)mbcsData[i].Attributes); + } +} + +int main(int argc, char *argv[]) { + system("cls"); + setWindowPos(0, 0, 1, 1); + setBufferSize(80, 38); + setWindowPos(0, 0, 80, 38); + + // Write text. + const wchar_t text1[] = { + 0x3044, // U+3044 (HIRAGANA LETTER I) + 0x2014, // U+2014 (EM DASH) + 0x3044, // U+3044 (HIRAGANA LETTER I) + 0xFF2D, // U+FF2D (FULLWIDTH LATIN CAPITAL LETTER M) + 0x30FC, // U+30FC (KATAKANA-HIRAGANA PROLONGED SOUND MARK) + 0x0031, // U+3031 (DIGIT ONE) + 0x2014, // U+2014 (EM DASH) + 0x0032, // U+0032 (DIGIT TWO) + 0x005C, // U+005C (REVERSE SOLIDUS) + 0x3044, // U+3044 (HIRAGANA LETTER I) + 0 + }; + setCursorPos(0, 0); + writeChars(text1); + + setCursorPos(78, 1); + writeChars(L"<>"); + + const wchar_t text2[] = { + 0x0032, // U+3032 (DIGIT TWO) + 0x3044, // U+3044 (HIRAGANA LETTER I) + 0, + }; + setCursorPos(78, 1); + writeChars(text2); + + system("pause"); + + dumpChars(0, 0, 17, 1); + dumpChars(2, 0, 2, 1); + dumpChars(2, 0, 1, 1); + dumpChars(3, 0, 1, 1); + dumpChars(78, 1, 2, 1); + dumpChars(0, 2, 2, 1); + + system("pause"); + system("cls"); + + const wchar_t text3[] = { + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 1 + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 2 + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 3 + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 4 + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 5 + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 6 + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 7 + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 8 + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 9 + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 10 + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 11 + 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 12 + L'\r', '\n', + L'\r', '\n', + 0 + }; + writeChars(text3); + system("pause"); + { + const COORD bufSize = {80, 2}; + const COORD bufCoord = {0, 0}; + SMALL_RECT readRegion = {0, 0, 79, 1}; + CHAR_INFO unicodeData[160]; + BOOL ret = ReadConsoleOutputW(GetStdHandle(STD_OUTPUT_HANDLE), unicodeData, + bufSize, bufCoord, &readRegion); + assert(ret); + for (int i = 0; i < 96; ++i) { + printf("%04x ", unicodeData[i].Char.UnicodeChar); + } + printf("\n"); + } + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/UnixEcho.cc b/src/libs/3rdparty/winpty/misc/UnixEcho.cc new file mode 100644 index 00000000000..372e0451574 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/UnixEcho.cc @@ -0,0 +1,89 @@ +/* + * Unix test code that puts the terminal into raw mode, then echos typed + * characters to stdout. Derived from sample code in the Stevens book, posted + * online at http://www.lafn.org/~dave/linux/terminalIO.html. + */ + +#include +#include +#include +#include +#include "FormatChar.h" + +static struct termios save_termios; +static int term_saved; + +/* RAW! mode */ +int tty_raw(int fd) +{ + struct termios buf; + + if (tcgetattr(fd, &save_termios) < 0) /* get the original state */ + return -1; + + buf = save_termios; + + /* echo off, canonical mode off, extended input + processing off, signal chars off */ + buf.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + + /* no SIGINT on BREAK, CR-to-NL off, input parity + check off, don't strip the 8th bit on input, + ouput flow control off */ + buf.c_iflag &= ~(BRKINT | ICRNL | ISTRIP | IXON); + + /* clear size bits, parity checking off */ + buf.c_cflag &= ~(CSIZE | PARENB); + + /* set 8 bits/char */ + buf.c_cflag |= CS8; + + /* output processing off */ + buf.c_oflag &= ~(OPOST); + + buf.c_cc[VMIN] = 1; /* 1 byte at a time */ + buf.c_cc[VTIME] = 0; /* no timer on input */ + + if (tcsetattr(fd, TCSAFLUSH, &buf) < 0) + return -1; + + term_saved = 1; + + return 0; +} + + +/* set it to normal! */ +int tty_reset(int fd) +{ + if (term_saved) + if (tcsetattr(fd, TCSAFLUSH, &save_termios) < 0) + return -1; + + return 0; +} + + +int main() +{ + tty_raw(0); + + int count = 0; + while (true) { + char ch; + char buf[16]; + int actual = read(0, &ch, 1); + if (actual != 1) { + perror("read error"); + break; + } + formatChar(buf, ch); + fputs(buf, stdout); + fflush(stdout); + if (ch == 3) // Ctrl-C + break; + } + + tty_reset(0); + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/Utf16Echo.cc b/src/libs/3rdparty/winpty/misc/Utf16Echo.cc new file mode 100644 index 00000000000..ef5f302de47 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Utf16Echo.cc @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +#include +#include + +int main(int argc, char *argv[]) { + system("cls"); + + if (argc == 1) { + printf("Usage: %s hhhh\n", argv[0]); + return 0; + } + + std::wstring dataToWrite; + for (int i = 1; i < argc; ++i) { + wchar_t ch = strtol(argv[i], NULL, 16); + dataToWrite.push_back(ch); + } + + DWORD actual = 0; + BOOL ret = WriteConsoleW( + GetStdHandle(STD_OUTPUT_HANDLE), + dataToWrite.data(), dataToWrite.size(), &actual, NULL); + assert(ret && actual == dataToWrite.size()); + + // Read it back. + std::vector readBuffer(dataToWrite.size() * 2); + COORD bufSize = {static_cast(readBuffer.size()), 1}; + COORD bufCoord = {0, 0}; + SMALL_RECT topLeft = {0, 0, static_cast(readBuffer.size() - 1), 0}; + ret = ReadConsoleOutputW( + GetStdHandle(STD_OUTPUT_HANDLE), readBuffer.data(), + bufSize, bufCoord, &topLeft); + assert(ret); + + printf("\n"); + for (int i = 0; i < readBuffer.size(); ++i) { + printf("CHAR: %04x %04x\n", + readBuffer[i].Char.UnicodeChar, + readBuffer[i].Attributes); + } + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/VeryLargeRead.cc b/src/libs/3rdparty/winpty/misc/VeryLargeRead.cc new file mode 100644 index 00000000000..58f0897022e --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/VeryLargeRead.cc @@ -0,0 +1,122 @@ +// +// 2015-09-25 +// I measured these limits on the size of a single ReadConsoleOutputW call. +// The limit seems to more-or-less disppear with Windows 8, which is the first +// OS to stop using ALPCs for console I/O. My guess is that the new I/O +// method does not use the 64KiB shared memory buffer that the ALPC method +// uses. +// +// I'm guessing the remaining difference between Windows 8/8.1 and Windows 10 +// might be related to the 32-vs-64-bitness. +// +// Client OSs +// +// Windows XP 32-bit VM ==> up to 13304 characters +// - 13304x1 works, but 13305x1 fails instantly +// Windows 7 32-bit VM ==> between 16-17 thousand characters +// - 16000x1 works, 17000x1 fails instantly +// - 163x100 *crashes* conhost.exe but leaves VeryLargeRead.exe running +// Windows 8 32-bit VM ==> between 240-250 million characters +// - 10000x24000 works, but 10000x25000 does not +// Windows 8.1 32-bit VM ==> between 240-250 million characters +// - 10000x24000 works, but 10000x25000 does not +// Windows 10 64-bit VM ==> no limit (tested to 576 million characters) +// - 24000x24000 works +// - `ver` reports [Version 10.0.10240], conhost.exe and ConhostV1.dll are +// 10.0.10240.16384 for file and product version. ConhostV2.dll is +// 10.0.10240.16391 for file and product version. +// +// Server OSs +// +// Windows Server 2008 64-bit VM ==> 14300-14400 characters +// - 14300x1 works, 14400x1 fails instantly +// - This OS does not have conhost.exe. +// - `ver` reports [Version 6.0.6002] +// Windows Server 2008 R2 64-bit VM ==> 15600-15700 characters +// - 15600x1 works, 15700x1 fails instantly +// - This OS has conhost.exe, and procexp.exe reveals console ALPC ports in +// use in conhost.exe. +// - `ver` reports [Version 6.1.7601], conhost.exe is 6.1.7601.23153 for file +// and product version. +// Windows Server 2012 64-bit VM ==> at least 100 million characters +// - 10000x10000 works (VM had only 1GiB of RAM, so I skipped larger tests) +// - This OS has Windows 8's task manager and procexp.exe reveals the same +// lack of ALPC ports and the same \Device\ConDrv\* files as Windows 8. +// - `ver` reports [Version 6.2.9200], conhost.exe is 6.2.9200.16579 for file +// and product version. +// +// To summarize: +// +// client-OS server-OS notes +// --------------------------------------------------------------------------- +// XP Server 2008 CSRSS, small reads +// 7 Server 2008 R2 ALPC-to-conhost, small reads +// 8, 8.1 Server 2012 new I/O interface, large reads allowed +// 10 enhanced console w/rewrapping +// +// (Presumably, Win2K, Vista, and Win2K3 behave the same as XP. conhost.exe +// was announced as a Win7 feature.) +// + +#include +#include +#include + +#include "TestUtil.cc" + +int main(int argc, char *argv[]) { + long long width = 9000; + long long height = 9000; + + assert(argc >= 1); + if (argc == 4) { + width = atoi(argv[2]); + height = atoi(argv[3]); + } else { + if (argc == 3) { + width = atoi(argv[1]); + height = atoi(argv[2]); + } + wchar_t args[1024]; + swprintf(args, 1024, L"CHILD %lld %lld", width, height); + startChildProcess(args); + return 0; + } + + const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE); + + setWindowPos(0, 0, 1, 1); + setBufferSize(width, height); + setWindowPos(0, 0, std::min(80LL, width), std::min(50LL, height)); + + setCursorPos(0, 0); + printf("A"); + fflush(stdout); + setCursorPos(width - 2, height - 1); + printf("B"); + fflush(stdout); + + trace("sizeof(CHAR_INFO) = %d", (int)sizeof(CHAR_INFO)); + + trace("Allocating buffer..."); + CHAR_INFO *buffer = new CHAR_INFO[width * height]; + assert(buffer != NULL); + memset(&buffer[0], 0, sizeof(CHAR_INFO)); + memset(&buffer[width * height - 2], 0, sizeof(CHAR_INFO)); + + COORD bufSize = { width, height }; + COORD bufCoord = { 0, 0 }; + SMALL_RECT readRegion = { 0, 0, width - 1, height - 1 }; + trace("ReadConsoleOutputW: calling..."); + BOOL success = ReadConsoleOutputW(conout, buffer, bufSize, bufCoord, &readRegion); + trace("ReadConsoleOutputW: success=%d", success); + + assert(buffer[0].Char.UnicodeChar == L'A'); + assert(buffer[width * height - 2].Char.UnicodeChar == L'B'); + trace("Top-left and bottom-right characters read successfully!"); + + Sleep(30000); + + delete [] buffer; + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/VkEscapeTest.cc b/src/libs/3rdparty/winpty/misc/VkEscapeTest.cc new file mode 100644 index 00000000000..97bf59f998b --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/VkEscapeTest.cc @@ -0,0 +1,56 @@ +/* + * Sending VK_PAUSE to the console window almost works as a mechanism for + * pausing it, but it doesn't because the console could turn off the + * ENABLE_LINE_INPUT console mode flag. + */ + +#define _WIN32_WINNT 0x0501 +#include +#include +#include + +CALLBACK DWORD pausingThread(LPVOID dummy) +{ + if (1) { + Sleep(1000); + HWND hwnd = GetConsoleWindow(); + SendMessage(hwnd, WM_KEYDOWN, VK_PAUSE, 1); + Sleep(1000); + SendMessage(hwnd, WM_KEYDOWN, VK_ESCAPE, 1); + } + + if (0) { + INPUT_RECORD ir; + memset(&ir, 0, sizeof(ir)); + ir.EventType = KEY_EVENT; + ir.Event.KeyEvent.bKeyDown = TRUE; + ir.Event.KeyEvent.wVirtualKeyCode = VK_PAUSE; + ir.Event.KeyEvent.wRepeatCount = 1; + } + + return 0; +} + +int main() +{ + HANDLE hin = GetStdHandle(STD_INPUT_HANDLE); + HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE); + COORD c = { 0, 0 }; + + DWORD mode; + GetConsoleMode(hin, &mode); + SetConsoleMode(hin, mode & + ~(ENABLE_LINE_INPUT)); + + CreateThread(NULL, 0, + pausingThread, NULL, + 0, NULL); + + int i = 0; + while (true) { + Sleep(100); + printf("%d\n", ++i); + } + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/Win10ResizeWhileFrozen.cc b/src/libs/3rdparty/winpty/misc/Win10ResizeWhileFrozen.cc new file mode 100644 index 00000000000..82feaf3c501 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Win10ResizeWhileFrozen.cc @@ -0,0 +1,52 @@ +/* + * Demonstrates a conhost hang that occurs when widening the console buffer + * while selection is in progress. The problem affects the new Windows 10 + * console, not the "legacy" console mode that Windows 10 also includes. + * + * First tested with: + * - Windows 10.0.10240 + * - conhost.exe version 10.0.10240.16384 + * - ConhostV1.dll version 10.0.10240.16384 + * - ConhostV2.dll version 10.0.10240.16391 + */ + +#include +#include +#include +#include + +#include "TestUtil.cc" + +const int SC_CONSOLE_MARK = 0xFFF2; +const int SC_CONSOLE_SELECT_ALL = 0xFFF5; + +int main(int argc, char *argv[]) { + if (argc == 1) { + startChildProcess(L"CHILD"); + return 0; + } + + setWindowPos(0, 0, 1, 1); + setBufferSize(80, 25); + setWindowPos(0, 0, 80, 25); + + countDown(5); + + SendMessage(GetConsoleWindow(), WM_SYSCOMMAND, SC_CONSOLE_SELECT_ALL, 0); + Sleep(2000); + + // This API call does not return. In the console window, the "Select All" + // operation appears to end. The console window becomes non-responsive, + // and the conhost.exe process must be killed from the Task Manager. + // (Killing this test program or closing the console window is not + // sufficient.) + // + // The same hang occurs whether line resizing is off or on. It happens + // with both "Mark" and "Select All". Calling setBufferSize with the + // existing buffer size does not hang, but calling it with only a changed + // buffer height *does* hang. Calling setWindowPos does not hang. + setBufferSize(120, 25); + + printf("Done...\n"); + Sleep(2000); +} diff --git a/src/libs/3rdparty/winpty/misc/Win10WrapTest1.cc b/src/libs/3rdparty/winpty/misc/Win10WrapTest1.cc new file mode 100644 index 00000000000..645fa95d542 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Win10WrapTest1.cc @@ -0,0 +1,57 @@ +/* + * Demonstrates some wrapping behaviors of the new Windows 10 console. + */ + +#include +#include +#include + +#include "TestUtil.cc" + +int main(int argc, char *argv[]) { + if (argc == 1) { + startChildProcess(L"CHILD"); + return 0; + } + + setWindowPos(0, 0, 1, 1); + setBufferSize(40, 20); + setWindowPos(0, 0, 40, 20); + + system("cls"); + + repeatChar(39, 'A'); repeatChar(1, ' '); + repeatChar(39, 'B'); repeatChar(1, ' '); + printf("\n"); + + repeatChar(39, 'C'); repeatChar(1, ' '); + repeatChar(39, 'D'); repeatChar(1, ' '); + printf("\n"); + + repeatChar(40, 'E'); + repeatChar(40, 'F'); + printf("\n"); + + repeatChar(39, 'G'); repeatChar(1, ' '); + repeatChar(39, 'H'); repeatChar(1, ' '); + printf("\n"); + + Sleep(2000); + + setChar(39, 0, '*', 0x24); + setChar(39, 1, '*', 0x24); + + setChar(39, 3, ' ', 0x24); + setChar(39, 4, ' ', 0x24); + + setChar(38, 6, ' ', 0x24); + setChar(38, 7, ' ', 0x24); + + Sleep(2000); + setWindowPos(0, 0, 35, 20); + setBufferSize(35, 20); + trace("DONE"); + + printf("Sleeping forever...\n"); + while(true) { Sleep(1000); } +} diff --git a/src/libs/3rdparty/winpty/misc/Win10WrapTest2.cc b/src/libs/3rdparty/winpty/misc/Win10WrapTest2.cc new file mode 100644 index 00000000000..50615fc8c79 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Win10WrapTest2.cc @@ -0,0 +1,30 @@ +#include + +#include "TestUtil.cc" + +int main(int argc, char *argv[]) { + if (argc == 1) { + startChildProcess(L"CHILD"); + return 0; + } + + const int WIDTH = 25; + + setWindowPos(0, 0, 1, 1); + setBufferSize(WIDTH, 40); + setWindowPos(0, 0, WIDTH, 20); + + system("cls"); + + for (int i = 0; i < 100; ++i) { + printf("FOO(%d)\n", i); + } + + repeatChar(5, '\n'); + repeatChar(WIDTH * 5, '.'); + repeatChar(10, '\n'); + setWindowPos(0, 20, WIDTH, 20); + writeBox(0, 5, 1, 10, '|'); + + Sleep(120000); +} diff --git a/src/libs/3rdparty/winpty/misc/Win32Echo1.cc b/src/libs/3rdparty/winpty/misc/Win32Echo1.cc new file mode 100644 index 00000000000..06fc79f7945 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Win32Echo1.cc @@ -0,0 +1,26 @@ +/* + * A Win32 program that reads raw console input with ReadFile and echos + * it to stdout. + */ + +#include +#include +#include + +int main() +{ + int count = 0; + HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE); + HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleMode(hStdIn, 0); + + while (true) { + DWORD actual; + char ch; + ReadFile(hStdIn, &ch, 1, &actual, NULL); + printf("%02x ", ch); + if (++count == 50) + break; + } + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/Win32Echo2.cc b/src/libs/3rdparty/winpty/misc/Win32Echo2.cc new file mode 100644 index 00000000000..b2ea2ad1c5d --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Win32Echo2.cc @@ -0,0 +1,19 @@ +/* + * A Win32 program that reads raw console input with getch and echos + * it to stdout. + */ + +#include +#include + +int main() +{ + int count = 0; + while (true) { + int ch = getch(); + printf("%02x ", ch); + if (++count == 50) + break; + } + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/Win32Test1.cc b/src/libs/3rdparty/winpty/misc/Win32Test1.cc new file mode 100644 index 00000000000..a40d318a98c --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Win32Test1.cc @@ -0,0 +1,46 @@ +#define _WIN32_WINNT 0x0501 +#include "../src/shared/DebugClient.cc" +#include +#include + +const int SC_CONSOLE_MARK = 0xFFF2; + +CALLBACK DWORD writerThread(void*) +{ + while (true) { + Sleep(1000); + trace("writing"); + printf("X\n"); + trace("written"); + } +} + +int main() +{ + CreateThread(NULL, 0, writerThread, NULL, 0, NULL); + trace("marking console"); + HWND hwnd = GetConsoleWindow(); + PostMessage(hwnd, WM_SYSCOMMAND, SC_CONSOLE_MARK, 0); + + Sleep(2000); + + trace("reading output"); + CHAR_INFO buf[1]; + COORD bufSize = { 1, 1 }; + COORD zeroCoord = { 0, 0 }; + SMALL_RECT readRect = { 0, 0, 0, 0 }; + ReadConsoleOutput(GetStdHandle(STD_OUTPUT_HANDLE), + buf, + bufSize, + zeroCoord, + &readRect); + trace("done reading output"); + + Sleep(2000); + + PostMessage(hwnd, WM_CHAR, 27, 0x00010001); + + Sleep(1100); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/Win32Test2.cc b/src/libs/3rdparty/winpty/misc/Win32Test2.cc new file mode 100644 index 00000000000..2777bad4562 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Win32Test2.cc @@ -0,0 +1,70 @@ +/* + * This test demonstrates that putting a console into selection mode does not + * block the low-level console APIs, even though it blocks WriteFile. + */ + +#define _WIN32_WINNT 0x0501 +#include "../src/shared/DebugClient.cc" +#include +#include + +const int SC_CONSOLE_MARK = 0xFFF2; + +CALLBACK DWORD writerThread(void*) +{ + CHAR_INFO xChar, fillChar; + memset(&xChar, 0, sizeof(xChar)); + xChar.Char.AsciiChar = 'X'; + xChar.Attributes = 7; + memset(&fillChar, 0, sizeof(fillChar)); + fillChar.Char.AsciiChar = ' '; + fillChar.Attributes = 7; + COORD oneCoord = { 1, 1 }; + COORD zeroCoord = { 0, 0 }; + + while (true) { + SMALL_RECT writeRegion = { 5, 5, 5, 5 }; + WriteConsoleOutput(GetStdHandle(STD_OUTPUT_HANDLE), + &xChar, oneCoord, + zeroCoord, + &writeRegion); + Sleep(500); + SMALL_RECT scrollRect = { 1, 1, 20, 20 }; + COORD destCoord = { 0, 0 }; + ScrollConsoleScreenBuffer(GetStdHandle(STD_OUTPUT_HANDLE), + &scrollRect, + NULL, + destCoord, + &fillChar); + } +} + +int main() +{ + CreateThread(NULL, 0, writerThread, NULL, 0, NULL); + trace("marking console"); + HWND hwnd = GetConsoleWindow(); + PostMessage(hwnd, WM_SYSCOMMAND, SC_CONSOLE_MARK, 0); + + Sleep(2000); + + trace("reading output"); + CHAR_INFO buf[1]; + COORD bufSize = { 1, 1 }; + COORD zeroCoord = { 0, 0 }; + SMALL_RECT readRect = { 0, 0, 0, 0 }; + ReadConsoleOutput(GetStdHandle(STD_OUTPUT_HANDLE), + buf, + bufSize, + zeroCoord, + &readRect); + trace("done reading output"); + + Sleep(2000); + + PostMessage(hwnd, WM_CHAR, 27, 0x00010001); + + Sleep(1100); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/Win32Test3.cc b/src/libs/3rdparty/winpty/misc/Win32Test3.cc new file mode 100644 index 00000000000..1fb92aff3df --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Win32Test3.cc @@ -0,0 +1,78 @@ +/* + * Creates a window station and starts a process under it. The new process + * also gets a new console. + */ + +#include +#include +#include + +int main() +{ + BOOL success; + + SECURITY_ATTRIBUTES sa; + memset(&sa, 0, sizeof(sa)); + sa.bInheritHandle = TRUE; + + HWINSTA originalStation = GetProcessWindowStation(); + printf("originalStation == 0x%x\n", originalStation); + HWINSTA station = CreateWindowStation(NULL, + 0, + WINSTA_ALL_ACCESS, + &sa); + printf("station == 0x%x\n", station); + if (!SetProcessWindowStation(station)) + printf("SetWindowStation failed!\n"); + HDESK desktop = CreateDesktop("Default", NULL, NULL, + /*dwFlags=*/0, GENERIC_ALL, + &sa); + printf("desktop = 0x%x\n", desktop); + + char stationName[256]; + stationName[0] = '\0'; + success = GetUserObjectInformation(station, UOI_NAME, + stationName, sizeof(stationName), + NULL); + printf("stationName = [%s]\n", stationName); + + char startupDesktop[256]; + sprintf(startupDesktop, "%s\\Default", stationName); + + STARTUPINFO sui; + PROCESS_INFORMATION pi; + memset(&sui, 0, sizeof(sui)); + memset(&pi, 0, sizeof(pi)); + sui.cb = sizeof(STARTUPINFO); + sui.lpDesktop = startupDesktop; + + // Start a cmd subprocess, and have it start its own cmd subprocess. + // Both subprocesses will connect to the same non-interactive window + // station. + + const char program[] = "c:\\windows\\system32\\cmd.exe"; + char cmdline[256]; + sprintf(cmdline, "%s /c cmd", program); + success = CreateProcess(program, + cmdline, + NULL, + NULL, + /*bInheritHandles=*/FALSE, + /*dwCreationFlags=*/CREATE_NEW_CONSOLE, + NULL, NULL, + &sui, + &pi); + + printf("pid == %d\n", pi.dwProcessId); + + // This sleep is necessary. We must give the child enough time to + // connect to the specified window station. + Sleep(5000); + + SetProcessWindowStation(originalStation); + CloseWindowStation(station); + CloseDesktop(desktop); + Sleep(5000); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/Win32Write1.cc b/src/libs/3rdparty/winpty/misc/Win32Write1.cc new file mode 100644 index 00000000000..6e5bf966822 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/Win32Write1.cc @@ -0,0 +1,44 @@ +/* + * A Win32 program that scrolls and writes to the console using the ioctl-like + * interface. + */ + +#include +#include + +int main() +{ + HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE); + + for (int i = 0; i < 80; ++i) { + + CONSOLE_SCREEN_BUFFER_INFO info; + GetConsoleScreenBufferInfo(conout, &info); + + SMALL_RECT src = { 0, 1, info.dwSize.X - 1, info.dwSize.Y - 1 }; + COORD destOrigin = { 0, 0 }; + CHAR_INFO fillCharInfo = { 0 }; + fillCharInfo.Char.AsciiChar = ' '; + fillCharInfo.Attributes = 7; + ScrollConsoleScreenBuffer(conout, + &src, + NULL, + destOrigin, + &fillCharInfo); + + CHAR_INFO buffer = { 0 }; + buffer.Char.AsciiChar = 'X'; + buffer.Attributes = 7; + COORD bufferSize = { 1, 1 }; + COORD bufferCoord = { 0, 0 }; + SMALL_RECT writeRegion = { 0, 0, 0, 0 }; + writeRegion.Left = writeRegion.Right = i; + writeRegion.Top = writeRegion.Bottom = 5; + WriteConsoleOutput(conout, + &buffer, bufferSize, bufferCoord, + &writeRegion); + + Sleep(250); + } + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/WindowsBugCrashReader.cc b/src/libs/3rdparty/winpty/misc/WindowsBugCrashReader.cc new file mode 100644 index 00000000000..e6d9558df63 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/WindowsBugCrashReader.cc @@ -0,0 +1,27 @@ +// I noticed this on the ConEmu web site: +// +// https://social.msdn.microsoft.com/Forums/en-US/40c8e395-cca9-45c8-b9b8-2fbe6782ac2b/readconsoleoutput-cause-access-violation-writing-location-exception +// https://conemu.github.io/en/MicrosoftBugs.html +// +// In Windows 7, 8, and 8.1, a ReadConsoleOutputW with an out-of-bounds read +// region crashes the application. I have reproduced the problem on Windows 8 +// and 8.1, but not on Windows 7. +// + +#include + +#include "TestUtil.cc" + +int main() { + setWindowPos(0, 0, 1, 1); + setBufferSize(80, 25); + setWindowPos(0, 0, 80, 25); + + const HANDLE conout = openConout(); + static CHAR_INFO lineBuf[80]; + SMALL_RECT readRegion = { 0, 999, 79, 999 }; + const BOOL ret = ReadConsoleOutputW(conout, lineBuf, {80, 1}, {0, 0}, &readRegion); + ASSERT(!ret && "ReadConsoleOutputW should have failed"); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/WriteConsole.cc b/src/libs/3rdparty/winpty/misc/WriteConsole.cc new file mode 100644 index 00000000000..a03670ca929 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/WriteConsole.cc @@ -0,0 +1,106 @@ +#include + +#include +#include +#include + +#include +#include + +static std::wstring mbsToWcs(const std::string &s) { + const size_t len = mbstowcs(nullptr, s.c_str(), 0); + if (len == static_cast(-1)) { + assert(false && "mbsToWcs: invalid string"); + } + std::wstring ret; + ret.resize(len); + const size_t len2 = mbstowcs(&ret[0], s.c_str(), len); + assert(len == len2); + return ret; +} + +uint32_t parseHex(wchar_t ch, bool &invalid) { + if (ch >= L'0' && ch <= L'9') { + return ch - L'0'; + } else if (ch >= L'a' && ch <= L'f') { + return ch - L'a' + 10; + } else if (ch >= L'A' && ch <= L'F') { + return ch - L'A' + 10; + } else { + invalid = true; + return 0; + } +} + +int main(int argc, char *argv[]) { + std::vector args; + for (int i = 1; i < argc; ++i) { + args.push_back(mbsToWcs(argv[i])); + } + + std::wstring out; + for (const auto &arg : args) { + if (!out.empty()) { + out.push_back(L' '); + } + for (size_t i = 0; i < arg.size(); ++i) { + wchar_t ch = arg[i]; + wchar_t nch = i + 1 < arg.size() ? arg[i + 1] : L'\0'; + if (ch == L'\\') { + switch (nch) { + case L'a': ch = L'\a'; ++i; break; + case L'b': ch = L'\b'; ++i; break; + case L'e': ch = L'\x1b'; ++i; break; + case L'f': ch = L'\f'; ++i; break; + case L'n': ch = L'\n'; ++i; break; + case L'r': ch = L'\r'; ++i; break; + case L't': ch = L'\t'; ++i; break; + case L'v': ch = L'\v'; ++i; break; + case L'\\': ch = L'\\'; ++i; break; + case L'\'': ch = L'\''; ++i; break; + case L'\"': ch = L'\"'; ++i; break; + case L'\?': ch = L'\?'; ++i; break; + case L'x': + if (i + 3 < arg.size()) { + bool invalid = false; + uint32_t d1 = parseHex(arg[i + 2], invalid); + uint32_t d2 = parseHex(arg[i + 3], invalid); + if (!invalid) { + i += 3; + ch = (d1 << 4) | d2; + } + } + break; + case L'u': + if (i + 5 < arg.size()) { + bool invalid = false; + uint32_t d1 = parseHex(arg[i + 2], invalid); + uint32_t d2 = parseHex(arg[i + 3], invalid); + uint32_t d3 = parseHex(arg[i + 4], invalid); + uint32_t d4 = parseHex(arg[i + 5], invalid); + if (!invalid) { + i += 5; + ch = (d1 << 24) | (d2 << 16) | (d3 << 8) | d4; + } + } + break; + default: break; + } + } + out.push_back(ch); + } + } + + DWORD actual = 0; + if (!WriteConsoleW( + GetStdHandle(STD_OUTPUT_HANDLE), + out.c_str(), + out.size(), + &actual, + nullptr)) { + fprintf(stderr, "WriteConsole failed (is stdout a console?)\n"); + exit(1); + } + + return 0; +} diff --git a/src/libs/3rdparty/winpty/misc/build32.sh b/src/libs/3rdparty/winpty/misc/build32.sh new file mode 100644 index 00000000000..162993ce337 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/build32.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e +name=$1 +name=${name%.} +name=${name%.cc} +name=${name%.exe} +echo Compiling $name.cc to $name.exe +i686-w64-mingw32-g++.exe -static -std=c++11 $name.cc -o $name.exe +i686-w64-mingw32-strip $name.exe diff --git a/src/libs/3rdparty/winpty/misc/build64.sh b/src/libs/3rdparty/winpty/misc/build64.sh new file mode 100644 index 00000000000..67579676847 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/build64.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e +name=$1 +name=${name%.} +name=${name%.cc} +name=${name%.exe} +echo Compiling $name.cc to $name.exe +x86_64-w64-mingw32-g++.exe -static -std=c++11 $name.cc -o $name.exe +x86_64-w64-mingw32-strip $name.exe diff --git a/src/libs/3rdparty/winpty/misc/color-test.sh b/src/libs/3rdparty/winpty/misc/color-test.sh new file mode 100644 index 00000000000..065c8094e29 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/color-test.sh @@ -0,0 +1,212 @@ +#!/bin/bash + +FORE=$1 +BACK=$2 +FILL=$3 + +if [ "$FORE" = "" ]; then + FORE=DefaultFore +fi +if [ "$BACK" = "" ]; then + BACK=DefaultBack +fi + +# To detect color changes, we want a character that fills the whole cell +# if possible. U+2588 is perfect, except that it becomes invisible in the +# original xterm, when bolded. For that terminal, use something else, like +# "#" or "@". +if [ "$FILL" = "" ]; then + FILL="█" +fi + +# SGR (Select Graphic Rendition) +s() { + printf '\033[0m' + while [ "$1" != "" ]; do + printf '\033['"$1"'m' + shift + done +} + +# Print +p() { + echo -n "$@" +} + +# Print with newline +pn() { + echo "$@" +} + +# For practical reasons, sandwich black and white in-between the other colors. +FORE_COLORS="31 30 37 32 33 34 35 36" +BACK_COLORS="41 40 47 42 43 44 45 46" + + + +### Test order of Invert(7) -- it does not matter what order it appears in. + +# The Red color setting here (31) is shadowed by the green setting (32). The +# Reverse flag does not cause (32) to alter the background color immediately; +# instead, the Reverse flag is applied once to determine the final effective +# Fore/Back colors. +s 7 31 32; p " -- Should be: $BACK-on-green -- "; s; pn +s 31 7 32; p " -- Should be: $BACK-on-green -- "; s; pn +s 31 32 7; p " -- Should be: $BACK-on-green -- "; s; pn + +# As above, but for the background color. +s 7 41 42; p " -- Should be: green-on-$FORE -- "; s; pn +s 41 7 42; p " -- Should be: green-on-$FORE -- "; s; pn +s 41 42 7; p " -- Should be: green-on-$FORE -- "; s; pn + +# One last, related test +s 7; p "Invert text"; s 7 1; p " with some words bold"; s; pn; +s 0; p "Normal text"; s 0 1; p " with some words bold"; s; pn; + +pn + + + +### Test effect of Bold(1) on color, with and without Invert(7). + +# The Bold flag does not affect the background color when Reverse is missing. +# There should always be 8 colored boxes. +p " " +for x in $BACK_COLORS; do + s $x; p "-"; s $x 1; p "-" +done +s; pn " Bold should not affect background" + +# On some terminals, Bold affects color, and on some it doesn't. If there +# are only 8 colored boxes, then the next two tests will also show 8 colored +# boxes. If there are 16 boxes, then exactly one of the next two tests will +# also have 16 boxes. +p " " +for x in $FORE_COLORS; do + s $x; p "$FILL"; s $x 1; p "$FILL" +done +s; pn " Does bold affect foreground color?" + +# On some terminals, Bold+Invert highlights the final Background color. +p " " +for x in $FORE_COLORS; do + s $x 7; p "-"; s $x 7 1; p "-" +done +s; pn " Test if Bold+Invert affects background color" + +# On some terminals, Bold+Invert highlights the final Foreground color. +p " " +for x in $BACK_COLORS; do + s $x 7; p "$FILL"; s $x 7 1; p "$FILL" +done +s; pn " Test if Bold+Invert affects foreground color" + +pn + + + +### Test for support of ForeHi and BackHi properties. + +# ForeHi +p " " +for x in $FORE_COLORS; do + hi=$(( $x + 60 )) + s $x; p "$FILL"; s $hi; p "$FILL" +done +s; pn " Test for support of ForeHi colors" +p " " +for x in $FORE_COLORS; do + hi=$(( $x + 60 )) + s $x; p "$FILL"; s $x $hi; p "$FILL" +done +s; pn " Test for support of ForeHi colors (w/compat)" + +# BackHi +p " " +for x in $BACK_COLORS; do + hi=$(( $x + 60 )) + s $x; p "-"; s $hi; p "-" +done +s; pn " Test for support of BackHi colors" +p " " +for x in $BACK_COLORS; do + hi=$(( $x + 60 )) + s $x; p "-"; s $x $hi; p "-" +done +s; pn " Test for support of BackHi colors (w/compat)" + +pn + + + +### Identify the default fore and back colors. + +pn "Match default fore and back colors against 16-color palette" +pn " ==fore== ==back==" +for fore in $FORE_COLORS; do + forehi=$(( $fore + 60 )) + back=$(( $fore + 10 )) + backhi=$(( $back + 60 )) + p " " + s $fore; p "$FILL"; s; p "$FILL"; s $fore; p "$FILL"; s; p " " + s $forehi; p "$FILL"; s; p "$FILL"; s $forehi; p "$FILL"; s; p " " + s $back; p "-"; s; p "-"; s $back; p "-"; s; p " " + s $backhi; p "-"; s; p "-"; s $backhi; p "-"; s; p " " + pn " $fore $forehi $back $backhi" +done + +pn + + + +### Test coloring of rest-of-line. + +# +# When a new line is scrolled in, every cell in the line receives the +# current background color, which can be the default/transparent color. +# + +p "Newline with red background: usually no red -->"; s 41; pn +s; pn "This text is plain, but rest is red if scrolled -->" +s; p " "; s 41; printf '\033[1K'; s; printf '\033[1C'; pn "<-- red Erase-in-Line to beginning" +s; p "red Erase-in-Line to end -->"; s 41; printf '\033[0K'; s; pn +pn + + + +### Moving the cursor around does not change colors of anything. + +pn "Test modifying uncolored lines with a colored SGR:" +pn "aaaa" +pn +pn "____e" +s 31 42; printf '\033[4C\033[3A'; pn "bb" +pn "cccc" +pn "dddd" +s; pn + +pn "Test modifying colored+inverted+bold line with plain text:" +s 42 31 7 1; printf 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\r'; +s; pn "This text is plain and followed by green-on-red -->" +pn + + + +### Full-width character overwriting + +pn 'Overwrite part of a full-width char with a half-width char' +p 'initial U+4000 ideographs -->'; s 31 42; p '䀀䀀'; s; pn +p 'write X to index #1 -->'; s 31 42; p '䀀䀀'; s 35 44; printf '\033[24G'; p X; s; pn +p 'write X to index #2 -->'; s 31 42; p '䀀䀀'; s 35 44; printf '\033[25G'; p X; s; pn +p 'write X to index #3 -->'; s 31 42; p '䀀䀀'; s 35 44; printf '\033[26G'; p X; s; pn +p 'write X to index #4 -->'; s 31 42; p '䀀䀀'; s 35 44; printf '\033[27G'; p X; s; pn +pn + +pn 'Verify that Erase-in-Line can "fix" last char in line' +p 'original -->'; s 31 42; p '䀀䀀'; s; pn +p 'overwrite -->'; s 31 42; p '䀀䀀'; s 35 44; printf '\033[30G'; p 'XXX'; s; pn +p 'overwrite + Erase-in-Line -->'; s 31 42; p '䀀䀀'; s 35 44; printf '\033[30G'; p 'XXX'; s; printf '\033[0K'; pn +p 'original -->'; s 31 42; p 'X䀀䀀'; s; pn +p 'overwrite -->'; s 31 42; p 'X䀀䀀'; s 35 44; printf '\033[30G'; p 'ーー'; s; pn +p 'overwrite + Erase-in-Line -->'; s 31 42; p 'X䀀䀀'; s 35 44; printf '\033[30G'; p 'ーー'; s; printf '\033[0K'; pn +pn diff --git a/src/libs/3rdparty/winpty/misc/font-notes.txt b/src/libs/3rdparty/winpty/misc/font-notes.txt new file mode 100644 index 00000000000..d4e36d8e25d --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/font-notes.txt @@ -0,0 +1,300 @@ +================================================================== +Notes regarding fonts, code pages, and East Asian character widths +================================================================== + + +Registry settings +================= + + * There are console registry settings in `HKCU\Console`. That key has many + default settings (e.g. the default font settings) and also per-app subkeys + for app-specific overrides. + + * It is possible to override the code page with an app-specific setting. + + * There are registry settings in + `HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Console`. In particular, + the `TrueTypeFont` subkey has a list of suitable font names associated with + various CJK code pages, as well as default font names. + + * There are two values in `HKLM\SYSTEM\CurrentControlSet\Control\Nls\CodePage` + that specify the current code pages -- `OEMCP` and `ACP`. Setting the + system locale via the Control Panel's "Region" or "Language" dialogs seems + to change these code page values. + + +Console fonts +============= + + * The `FontFamily` field of `CONSOLE_FONT_INFOEX` has two parts: + - The high four bits can be exactly one of the `FF_xxxx` font families: + FF_DONTCARE(0x00) + FF_ROMAN(0x10) + FF_SWISS(0x20) + FF_MODERN(0x30) + FF_SCRIPT(0x40) + FF_DECORATIVE(0x50) + - The low four bits are a bitmask: + TMPF_FIXED_PITCH(1) -- actually means variable pitch + TMPF_VECTOR(2) + TMPF_TRUETYPE(4) + TMPF_DEVICE(8) + + * Each console has its own independent console font table. The current font + is identified with an index into this table. The size of the table is + returned by the undocumented `GetNumberOfConsoleFonts` API. It is apparently + possible to get the table size without this API, by instead calling + `GetConsoleFontSize` on each nonnegative index starting with 0 until the API + fails by returning (0, 0). + + * The font table grows dynamically. Each time the console is configured with + a previously-unused (FaceName, Size) combination, two entries are added to + the font table -- one with normal weight and one with bold weight. Fonts + added this way are always TrueType fonts. + + * Initially, the font table appears to contain only raster fonts. For + example, on an English Windows 8 installation, here is the initial font + table: + font 0: 4x6 + font 1: 6x8 + font 2: 8x8 + font 3: 16x8 + font 4: 5x12 + font 5: 7x12 + font 6: 8x12 -- the current font + font 7: 16x12 + font 8: 12x16 + font 9: 10x18 + `GetNumberOfConsoleFonts` returns 10, and this table matches the raster font + sizes according to the console properties dialog. + + * With a Japanese or Chinese locale, the initial font table appears to contain + the sizes applicable to both the East Asian raster font, as well as the + sizes for the CP437/CP1252 raster font. + + * The index passed to `SetCurrentConsoleFontEx` apparently has no effect. + The undocumented `SetConsoleFont` API, however, accepts *only* a font index, + and on Windows 8 English, it switches between all 10 fonts, even font index + #0. + + * If the index passed to `SetConsoleFont` identifies a Raster Font + incompatible with the current code page, then another Raster Font is + activated. + + * Passing "Terminal" to `SetCurrentConsoleFontEx` seems to have no effect. + Perhaps relatedly, `SetCurrentConsoleFontEx` does not fail if it is given a + bogus `FaceName`. Some font is still chosen and activated. Passing a face + name and height seems to work reliably, modulo the CP936 issue described + below. + + +Console fonts and code pages +============================ + + * On an English Windows installation, the default code page is 437, and it + cannot be set to 932 (Shift-JIS). (The API call fails.) Changing the + system locale to "Japanese (Japan)" using the Region/Language dialog + changes the default CP to 932 and permits changing the console CP between + 437 and 932. + + * A console has both an input code page and an output code page + (`{Get,Set}ConsoleCP` and `{Get,Set}ConsoleOutputCP`). I'm not going to + distinguish between the two for this document; presumably only the output + CP matters. The code page can change while the console is open, e.g. + by running `mode con: cp select={932,437,1252}` or by calling + `SetConsoleOutputCP`. + + * The current code page restricts which TrueType fonts and which Raster Font + sizes are available in the console properties dialog. This can change + while the console is open. + + * Changing the code page almost(?) always changes the current console font. + So far, I don't know how the new font is chosen. + + * With a CP of 932, the only TrueType font available in the console properties + dialog is "MS Gothic", displayed as "MS ゴシック". It is still possible to + use the English-default TrueType console fonts, Lucida Console and Consolas, + via `SetCurrentConsoleFontEx`. + + * When using a Raster Font and CP437 or CP1252, writing a UTF-16 codepoint not + representable in the code page instead writes a question mark ('?') to the + console. This conversion does not apply with a TrueType font, nor with the + Raster Font for CP932 or CP936. + + +ReadConsoleOutput and double-width characters +============================================== + + * With a Raster Font active, when `ReadConsoleOutputW` reads two cells of a + double-width character, it fills only a single `CHAR_INFO` structure. The + unused trailing `CHAR_INFO` structures are zero-filled. With a TrueType + font active, `ReadConsoleOutputW` instead fills two `CHAR_INFO` structures, + the first marked with `COMMON_LVB_LEADING_BYTE` and the second marked with + `COMMON_LVB_TRAILING_BYTE`. The flag is a misnomer--there aren't two + *bytes*, but two cells, and they have equal `CHAR_INFO.Char.UnicodeChar` + values. + + * `ReadConsoleOutputA`, on the other hand, reads two `CHAR_INFO` cells, and + if the UTF-16 value can be represented as two bytes in the ANSI/OEM CP, then + the two bytes are placed in the two `CHAR_INFO.Char.AsciiChar` values, and + the `COMMON_LVB_{LEADING,TRAILING}_BYTE` values are also used. If the + codepoint isn't representable, I don't remember what happens -- I think the + `AsciiChar` values take on an invalid marker. + + * Reading only one cell of a double-width character reads a space (U+0020) + instead. Raster-vs-TrueType and wide-vs-ANSI do not matter. + - XXX: what about attributes? Can a double-width character have mismatched + color attributes? + - XXX: what happens when writing to just one cell of a double-width + character? + + +Default Windows fonts for East Asian languages +============================================== +CP932 / Japanese: "MS ゴシック" (MS Gothic) +CP936 / Chinese Simplified: "新宋体" (SimSun) + + +Unreliable character width (half-width vs full-width) +===================================================== + +The half-width vs full-width status of a codepoint depends on at least these variables: + * OS version (Win10 legacy and new modes are different versions) + * system locale (English vs Japanese vs Chinese Simplified vs Chinese Traditional, etc) + * code page (437 vs 932 vs 936, etc) + * raster vs TrueType (Terminal vs MS Gothic vs SimSun, etc) + * font size + * rendered-vs-model (rendered width can be larger or smaller than model width) + +Example 1: U+2014 (EM DASH): East_Asian_Width: Ambiguous +-------------------------------------------------------- + rendered modeled +CP932: Win7/8 Raster Fonts half half +CP932: Win7/8 Gothic 14/15px half full +CP932: Win7/8 Consolas 14/15px half full +CP932: Win7/8 Lucida Console 14px half full +CP932: Win7/8 Lucida Console 15px half half +CP932: Win10New Raster Fonts half half +CP932: Win10New Gothic 14/15px half half +CP932: Win10New Consolas 14/15px half half +CP932: Win10New Lucida Console 14/15px half half + +CP936: Win7/8 Raster Fonts full full +CP936: Win7/8 SimSun 14px full full +CP936: Win7/8 SimSun 15px full half +CP936: Win7/8 Consolas 14/15px half full +CP936: Win10New Raster Fonts full full +CP936: Win10New SimSum 14/15px full full +CP936: Win10New Consolas 14/15px half half + +Example 2: U+3044 (HIRAGANA LETTER I): East_Asian_Width: Wide +------------------------------------------------------------- + rendered modeled +CP932: Win7/8/10N Raster Fonts full full +CP932: Win7/8/10N Gothic 14/15px full full +CP932: Win7/8/10N Consolas 14/15px half(*2) full +CP932: Win7/8/10N Lucida Console 14/15px half(*3) full + +CP936: Win7/8/10N Raster Fonts full full +CP936: Win7/8/10N SimSun 14/15px full full +CP936: Win7/8/10N Consolas 14/15px full full + +Example 3: U+30FC (KATAKANA-HIRAGANA PROLONGED SOUND MARK): East_Asian_Width: Wide +---------------------------------------------------------------------------------- + rendered modeled +CP932: Win7 Raster Fonts full full +CP932: Win7 Gothic 14/15px full full +CP932: Win7 Consolas 14/15px half(*2) full +CP932: Win7 Lucida Console 14px half(*3) full +CP932: Win7 Lucida Console 15px half(*3) half +CP932: Win8 Raster Fonts full full +CP932: Win8 Gothic 14px full half +CP932: Win8 Gothic 15px full full +CP932: Win8 Consolas 14/15px half(*2) full +CP932: Win8 Lucida Console 14px half(*3) full +CP932: Win8 Lucida Console 15px half(*3) half +CP932: Win10New Raster Fonts full full +CP932: Win10New Gothic 14/15px full full +CP932: Win10New Consolas 14/15px half(*2) half +CP932: Win10New Lucida Console 14/15px half(*2) half + +CP936: Win7/8 Raster Fonts full full +CP936: Win7/8 SimSun 14px full full +CP936: Win7/8 SimSun 15px full half +CP936: Win7/8 Consolas 14px full full +CP936: Win7/8 Consolas 15px full half +CP936: Win10New Raster Fonts full full +CP936: Win10New SimSum 14/15px full full +CP936: Win10New Consolas 14/15px full full + +Example 4: U+4000 (CJK UNIFIED IDEOGRAPH-4000): East_Asian_Width: Wide +---------------------------------------------------------------------- + rendered modeled +CP932: Win7 Raster Fonts half(*1) half +CP932: Win7 Gothic 14/15px full full +CP932: Win7 Consolas 14/15px half(*2) full +CP932: Win7 Lucida Console 14px half(*3) full +CP932: Win7 Lucida Console 15px half(*3) half +CP932: Win8 Raster Fonts half(*1) half +CP932: Win8 Gothic 14px full half +CP932: Win8 Gothic 15px full full +CP932: Win8 Consolas 14/15px half(*2) full +CP932: Win8 Lucida Console 14px half(*3) full +CP932: Win8 Lucida Console 15px half(*3) half +CP932: Win10New Raster Fonts half(*1) half +CP932: Win10New Gothic 14/15px full full +CP932: Win10New Consolas 14/15px half(*2) half +CP932: Win10New Lucida Console 14/15px half(*2) half + +CP936: Win7/8 Raster Fonts full full +CP936: Win7/8 SimSun 14px full full +CP936: Win7/8 SimSun 15px full half +CP936: Win7/8 Consolas 14px full full +CP936: Win7/8 Consolas 15px full half +CP936: Win10New Raster Fonts full full +CP936: Win10New SimSum 14/15px full full +CP936: Win10New Consolas 14/15px full full + +(*1) Rendered as a half-width filled white box +(*2) Rendered as a half-width box with a question mark inside +(*3) Rendered as a half-width empty box +(!!) One of the only places in Win10New where rendered and modeled width disagree + + +Windows quirk: unreliable font heights with CP936 / Chinese Simplified +====================================================================== + +When I set the font to 新宋体 17px, using either the properties dialog or +`SetCurrentConsoleFontEx`, the height reported by `GetCurrentConsoleFontEx` is +not 17, but is instead 19. The same problem does not affect Raster Fonts, +nor have I seen the problem in the English or Japanese locales. I observed +this with Windows 7 and Windows 10 new mode. + +If I set the font using the facename, width, *and* height, then the +`SetCurrentConsoleFontEx` and `GetCurrentConsoleFontEx` values agree. If I +set the font using *only* the facename and height, then the two values +disagree. + + +Windows bug: GetCurrentConsoleFontEx is initially invalid +========================================================= + + - Assume there is no configured console font name in the registry. In this + case, the console defaults to a raster font. + - Open a new console and call the `GetCurrentConsoleFontEx` API. + - The `FaceName` field of the returned `CONSOLE_FONT_INFOEX` data + structure is incorrect. On Windows 7, 8, and 10, I observed that the + field was blank. On Windows 8, occasionally, it instead contained: + U+AE72 U+75BE U+0001 + The other fields of the structure all appeared correct: + nFont=6 dwFontSize=(8,12) FontFamily=0x30 FontWeight=400 + - The `FaceName` field becomes initialized easily: + - Open the console properties dialog and click OK. (Cancel is not + sufficient.) + - Call the undocumented `SetConsoleFont` with the current font table + index, which is 6 in the example above. + - It seems that the console uncritically accepts whatever string is + stored in the registry, including a blank string, and passes it on the + the `GetCurrentConsoleFontEx` caller. It is possible to get the console + to *write* a blank setting into the registry -- simply open the console + (default or app-specific) properties and click OK. diff --git a/src/libs/3rdparty/winpty/misc/winbug-15048.cc b/src/libs/3rdparty/winpty/misc/winbug-15048.cc new file mode 100644 index 00000000000..0e98d648c54 --- /dev/null +++ b/src/libs/3rdparty/winpty/misc/winbug-15048.cc @@ -0,0 +1,201 @@ +/* + +Test program demonstrating a problem in Windows 15048's ReadConsoleOutput API. + +To compile: + + cl /nologo /EHsc winbug-15048.cc shell32.lib + +Example of regressed input: + +Case 1: + + > chcp 932 + > winbug-15048 -face-gothic 3044 + + Correct output: + + 1**34 (nb: U+3044 replaced with '**' to avoid MSVC encoding warning) + 5678 + + ReadConsoleOutputW (both rows, 3 cols) + row 0: U+0031(0007) U+3044(0107) U+3044(0207) U+0033(0007) + row 1: U+0035(0007) U+0036(0007) U+0037(0007) U+0038(0007) + + ReadConsoleOutputW (both rows, 4 cols) + row 0: U+0031(0007) U+3044(0107) U+3044(0207) U+0033(0007) U+0034(0007) + row 1: U+0035(0007) U+0036(0007) U+0037(0007) U+0038(0007) U+0020(0007) + + ReadConsoleOutputW (second row) + row 1: U+0035(0007) U+0036(0007) U+0037(0007) U+0038(0007) U+0020(0007) + + ... + + Win10 15048 bad output: + + 1**34 + 5678 + + ReadConsoleOutputW (both rows, 3 cols) + row 0: U+0031(0007) U+3044(0007) U+0033(0007) U+0035(0007) + row 1: U+0036(0007) U+0037(0007) U+0038(0007) U+0000(0000) + + ReadConsoleOutputW (both rows, 4 cols) + row 0: U+0031(0007) U+3044(0007) U+0033(0007) U+0034(0007) U+0035(0007) + row 1: U+0036(0007) U+0037(0007) U+0038(0007) U+0020(0007) U+0000(0000) + + ReadConsoleOutputW (second row) + row 1: U+0035(0007) U+0036(0007) U+0037(0007) U+0038(0007) U+0020(0007) + + ... + + The U+3044 character (HIRAGANA LETTER I) occupies two columns, but it only + fills one record in the ReadConsoleOutput output buffer, which has the + effect of shifting the first cell of the second row into the last cell of + the first row. Ordinarily, the first and second cells would also have the + COMMON_LVB_LEADING_BYTE and COMMON_LVB_TRAILING_BYTE attributes set, which + allows winpty to detect the double-column character. + +Case 2: + + > chcp 437 + > winbug-15048 -face "Lucida Console" -h 4 221A + + The same issue happens with U+221A (SQUARE ROOT), but only in certain + fonts. The console seems to think this character occupies two columns + if the font is sufficiently small. The Windows console properties dialog + doesn't allow fonts below 5 pt, but winpty tries to use 2pt and 4pt Lucida + Console to allow very large console windows. + +Case 3: + + > chcp 437 + > winbug-15048 -face "Lucida Console" -h 12 FF12 + + The console selection system thinks U+FF12 (FULLWIDTH DIGIT TWO) occupies + two columns, which happens to be correct, but it's displayed as a single + column unrecognized character. It otherwise behaves the same as the other + cases. + +*/ + +#include +#include +#include +#include +#include +#include + +#include + +#define COUNT_OF(array) (sizeof(array) / sizeof((array)[0])) + +// See https://en.wikipedia.org/wiki/List_of_CJK_fonts +const wchar_t kMSGothic[] = { 0xff2d, 0xff33, 0x0020, 0x30b4, 0x30b7, 0x30c3, 0x30af, 0 }; // Japanese +const wchar_t kNSimSun[] = { 0x65b0, 0x5b8b, 0x4f53, 0 }; // Simplified Chinese +const wchar_t kMingLight[] = { 0x7d30, 0x660e, 0x9ad4, 0 }; // Traditional Chinese +const wchar_t kGulimChe[] = { 0xad74, 0xb9bc, 0xccb4, 0 }; // Korean + +static void set_font(const wchar_t *name, int size) { + const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_FONT_INFOEX fontex {}; + fontex.cbSize = sizeof(fontex); + fontex.dwFontSize.Y = size; + fontex.FontWeight = 400; + fontex.FontFamily = 0x36; + wcsncpy(fontex.FaceName, name, COUNT_OF(fontex.FaceName)); + assert(SetCurrentConsoleFontEx(conout, FALSE, &fontex)); +} + +static void usage(const wchar_t *prog) { + printf("Usage: %ls [options]\n", prog); + printf(" -h HEIGHT\n"); + printf(" -face FACENAME\n"); + printf(" -face-{gothic|simsun|minglight|gulimche) [JP,CN-sim,CN-tra,KR]\n"); + printf(" hhhh -- print U+hhhh\n"); + exit(1); +} + +static void dump_region(SMALL_RECT region, const char *name) { + const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE); + + CHAR_INFO buf[1000]; + memset(buf, 0xcc, sizeof(buf)); + + const int w = region.Right - region.Left + 1; + const int h = region.Bottom - region.Top + 1; + + assert(ReadConsoleOutputW( + conout, buf, { (short)w, (short)h }, { 0, 0 }, + ®ion)); + + printf("\n"); + printf("ReadConsoleOutputW (%s)\n", name); + for (int y = 0; y < h; ++y) { + printf("row %d: ", region.Top + y); + for (int i = 0; i < region.Left * 13; ++i) { + printf(" "); + } + for (int x = 0; x < w; ++x) { + const int i = y * w + x; + printf("U+%04x(%04x) ", buf[i].Char.UnicodeChar, buf[i].Attributes); + } + printf("\n"); + } +} + +int main() { + wchar_t *cmdline = GetCommandLineW(); + int argc = 0; + wchar_t **argv = CommandLineToArgvW(cmdline, &argc); + const wchar_t *font_name = L"Lucida Console"; + int font_height = 8; + int test_ch = 0xff12; // U+FF12 FULLWIDTH DIGIT TWO + + for (int i = 1; i < argc; ++i) { + const std::wstring arg = argv[i]; + const std::wstring next = i + 1 < argc ? argv[i + 1] : L""; + if (arg == L"-face" && i + 1 < argc) { + font_name = argv[i + 1]; + i++; + } else if (arg == L"-face-gothic") { + font_name = kMSGothic; + } else if (arg == L"-face-simsun") { + font_name = kNSimSun; + } else if (arg == L"-face-minglight") { + font_name = kMingLight; + } else if (arg == L"-face-gulimche") { + font_name = kGulimChe; + } else if (arg == L"-h" && i + 1 < argc) { + font_height = _wtoi(next.c_str()); + i++; + } else if (arg.c_str()[0] != '-') { + test_ch = wcstol(arg.c_str(), NULL, 16); + } else { + printf("Unrecognized argument: %ls\n", arg.c_str()); + usage(argv[0]); + } + } + + const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE); + + set_font(font_name, font_height); + + system("cls"); + DWORD actual = 0; + wchar_t output[] = L"1234\n5678\n"; + output[1] = test_ch; + WriteConsoleW(conout, output, 10, &actual, nullptr); + + dump_region({ 0, 0, 3, 1 }, "both rows, 3 cols"); + dump_region({ 0, 0, 4, 1 }, "both rows, 4 cols"); + dump_region({ 0, 1, 4, 1 }, "second row"); + dump_region({ 0, 0, 4, 0 }, "first row"); + dump_region({ 1, 0, 4, 0 }, "first row, skip 1"); + dump_region({ 2, 0, 4, 0 }, "first row, skip 2"); + dump_region({ 3, 0, 4, 0 }, "first row, skip 3"); + + set_font(font_name, 14); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/ship/build-pty4j-libpty.bat b/src/libs/3rdparty/winpty/ship/build-pty4j-libpty.bat new file mode 100644 index 00000000000..b6bca7b0793 --- /dev/null +++ b/src/libs/3rdparty/winpty/ship/build-pty4j-libpty.bat @@ -0,0 +1,36 @@ +@echo off + +setlocal +cd %~dp0.. +set Path=C:\Python27;C:\Program Files\Git\cmd;%Path% + +call "%VS140COMNTOOLS%\VsDevCmd.bat" || goto :fail + +rmdir /s/q build-libpty 2>NUL +mkdir build-libpty\win +mkdir build-libpty\win\x86 +mkdir build-libpty\win\x86_64 +mkdir build-libpty\win\xp + +rmdir /s/q src\Release 2>NUL +rmdir /s/q src\.vs 2>NUL +del src\*.vcxproj src\*.vcxproj.filters src\*.sln src\*.sdf 2>NUL + +call vcbuild.bat --msvc-platform Win32 --gyp-msvs-version 2015 --toolset v140_xp || goto :fail +copy src\Release\Win32\winpty.dll build-libpty\win\xp || goto :fail +copy src\Release\Win32\winpty-agent.exe build-libpty\win\xp || goto :fail + +call vcbuild.bat --msvc-platform Win32 --gyp-msvs-version 2015 || goto :fail +copy src\Release\Win32\winpty.dll build-libpty\win\x86 || goto :fail +copy src\Release\Win32\winpty-agent.exe build-libpty\win\x86 || goto :fail + +call vcbuild.bat --msvc-platform x64 --gyp-msvs-version 2015 || goto :fail +copy src\Release\x64\winpty.dll build-libpty\win\x86_64 || goto :fail +copy src\Release\x64\winpty-agent.exe build-libpty\win\x86_64 || goto :fail + +echo success +goto :EOF + +:fail +echo error: build failed +exit /b 1 diff --git a/src/libs/3rdparty/winpty/ship/common_ship.py b/src/libs/3rdparty/winpty/ship/common_ship.py new file mode 100644 index 00000000000..b587ac7ce11 --- /dev/null +++ b/src/libs/3rdparty/winpty/ship/common_ship.py @@ -0,0 +1,89 @@ +import os +import sys + +# These scripts need to continue using Python 2 rather than 3, because +# make_msvc_package.py puts the current Python interpreter on the PATH for the +# sake of gyp, and gyp doesn't work with Python 3 yet. +# https://bugs.chromium.org/p/gyp/issues/detail?id=36 +if os.name != "nt": + sys.exit("Error: ship scripts require native Python 2.7. (wrong os.name)") +if sys.version_info[0:2] != (2,7): + sys.exit("Error: ship scripts require native Python 2.7. (wrong version)") + +import glob +import hashlib +import shutil +import subprocess +from distutils.spawn import find_executable + +topDir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) + +with open(topDir + "/VERSION.txt", "rt") as f: + winptyVersion = f.read().strip() + +def rmrf(patterns): + for pattern in patterns: + for path in glob.glob(pattern): + if os.path.isdir(path) and not os.path.islink(path): + print("+ rm -r " + path) + sys.stdout.flush() + shutil.rmtree(path) + elif os.path.isfile(path): + print("+ rm " + path) + sys.stdout.flush() + os.remove(path) + +def mkdir(path): + if not os.path.isdir(path): + os.makedirs(path) + +def requireExe(name, guesses): + if find_executable(name) is None: + for guess in guesses: + if os.path.exists(guess): + newDir = os.path.dirname(guess) + print("Adding " + newDir + " to Path to provide " + name) + os.environ["Path"] = newDir + ";" + os.environ["Path"] + ret = find_executable(name) + if ret is None: + sys.exit("Error: required EXE is missing from Path: " + name) + return ret + +class ModifyEnv: + def __init__(self, **kwargs): + self._changes = dict(kwargs) + self._original = dict() + + def __enter__(self): + for var, val in self._changes.items(): + self._original[var] = os.environ[var] + os.environ[var] = val + + def __exit__(self, type, value, traceback): + for var, val in self._original.items(): + os.environ[var] = val + +def sha256(path): + with open(path, "rb") as fp: + return hashlib.sha256(fp.read()).hexdigest() + +def checkSha256(path, expected): + actual = sha256(path) + if actual != expected: + sys.exit("error: sha256 hash mismatch on {}: expected {}, found {}".format( + path, expected, actual)) + +requireExe("git.exe", [ + "C:\\Program Files\\Git\\cmd\\git.exe", + "C:\\Program Files (x86)\\Git\\cmd\\git.exe" +]) + +commitHash = subprocess.check_output(["git.exe", "rev-parse", "HEAD"]).strip() +defaultPathEnviron = "C:\\Windows\\System32;C:\\Windows" + +ZIP_TOOL = requireExe("7z.exe", [ + "C:\\Program Files\\7-Zip\\7z.exe", + "C:\\Program Files (x86)\\7-Zip\\7z.exe", +]) + +requireExe("curl.exe", []) diff --git a/src/libs/3rdparty/winpty/ship/make_msvc_package.py b/src/libs/3rdparty/winpty/ship/make_msvc_package.py new file mode 100644 index 00000000000..2d10aac7878 --- /dev/null +++ b/src/libs/3rdparty/winpty/ship/make_msvc_package.py @@ -0,0 +1,163 @@ +#!python + +# Copyright (c) 2016 Ryan Prichard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +# +# Run with native CPython 2.7. +# +# This script looks for MSVC using a version-specific environment variable, +# such as VS140COMNTOOLS for MSVC 2015. +# + +import common_ship + +import argparse +import os +import shutil +import subprocess +import sys + +os.chdir(common_ship.topDir) + +MSVC_VERSION_TABLE = { + "2015" : { + "package_name" : "msvc2015", + "gyp_version" : "2015", + "common_tools_env" : "VS140COMNTOOLS", + "xp_toolset" : "v140_xp", + }, + "2013" : { + "package_name" : "msvc2013", + "gyp_version" : "2013", + "common_tools_env" : "VS120COMNTOOLS", + "xp_toolset" : "v120_xp", + }, +} + +ARCH_TABLE = { + "x64" : { + "msvc_platform" : "x64", + }, + "ia32" : { + "msvc_platform" : "Win32", + }, +} + +def readArguments(): + parser = argparse.ArgumentParser() + parser.add_argument("--msvc-version", default="2015") + ret = parser.parse_args() + if ret.msvc_version not in MSVC_VERSION_TABLE: + sys.exit("Error: unrecognized version: " + ret.msvc_version + ". " + + "Versions: " + " ".join(sorted(MSVC_VERSION_TABLE.keys()))) + return ret + +ARGS = readArguments() + +def checkoutGyp(): + if os.path.isdir("build-gyp"): + return + subprocess.check_call([ + "git.exe", + "clone", + "https://chromium.googlesource.com/external/gyp", + "build-gyp" + ]) + +def cleanMsvc(): + common_ship.rmrf(""" + src/Release src/.vs src/gen + src/*.vcxproj src/*.vcxproj.filters src/*.sln src/*.sdf + """.split()) + +def build(arch, packageDir, xp=False): + archInfo = ARCH_TABLE[arch] + versionInfo = MSVC_VERSION_TABLE[ARGS.msvc_version] + + devCmdPath = os.path.join(os.environ[versionInfo["common_tools_env"]], "VsDevCmd.bat") + if not os.path.isfile(devCmdPath): + sys.exit("Error: MSVC environment script missing: " + devCmdPath) + + toolsetArgument = " --toolset {}".format(versionInfo["xp_toolset"]) if xp else "" + newEnv = os.environ.copy() + newEnv["PATH"] = os.path.dirname(sys.executable) + ";" + common_ship.defaultPathEnviron + commandLine = ( + '"' + devCmdPath + '" && ' + + " vcbuild.bat" + + " --gyp-msvs-version " + versionInfo["gyp_version"] + + " --msvc-platform " + archInfo["msvc_platform"] + + " --commit-hash " + common_ship.commitHash + + toolsetArgument + ) + + subprocess.check_call(commandLine, shell=True, env=newEnv) + + archPackageDir = os.path.join(packageDir, arch) + if xp: + archPackageDir += "_xp" + + common_ship.mkdir(archPackageDir + "/bin") + common_ship.mkdir(archPackageDir + "/lib") + + binSrc = os.path.join(common_ship.topDir, "src/Release", archInfo["msvc_platform"]) + + shutil.copy(binSrc + "/winpty.dll", archPackageDir + "/bin") + shutil.copy(binSrc + "/winpty-agent.exe", archPackageDir + "/bin") + shutil.copy(binSrc + "/winpty-debugserver.exe", archPackageDir + "/bin") + shutil.copy(binSrc + "/winpty.lib", archPackageDir + "/lib") + +def buildPackage(): + versionInfo = MSVC_VERSION_TABLE[ARGS.msvc_version] + + packageName = "winpty-%s-%s" % ( + common_ship.winptyVersion, + versionInfo["package_name"], + ) + + packageRoot = os.path.join(common_ship.topDir, "ship/packages") + packageDir = os.path.join(packageRoot, packageName) + packageFile = packageDir + ".zip" + + common_ship.rmrf([packageDir]) + common_ship.rmrf([packageFile]) + common_ship.mkdir(packageDir) + + checkoutGyp() + cleanMsvc() + build("ia32", packageDir, True) + build("x64", packageDir, True) + cleanMsvc() + build("ia32", packageDir) + build("x64", packageDir) + + topDir = common_ship.topDir + + common_ship.mkdir(packageDir + "/include") + shutil.copy(topDir + "/src/include/winpty.h", packageDir + "/include") + shutil.copy(topDir + "/src/include/winpty_constants.h", packageDir + "/include") + shutil.copy(topDir + "/LICENSE", packageDir) + shutil.copy(topDir + "/README.md", packageDir) + shutil.copy(topDir + "/RELEASES.md", packageDir) + + subprocess.check_call([common_ship.ZIP_TOOL, "a", packageFile, "."], cwd=packageDir) + +if __name__ == "__main__": + buildPackage() diff --git a/src/libs/3rdparty/winpty/ship/ship.py b/src/libs/3rdparty/winpty/ship/ship.py new file mode 100644 index 00000000000..44f5862e3eb --- /dev/null +++ b/src/libs/3rdparty/winpty/ship/ship.py @@ -0,0 +1,104 @@ +#!python + +# Copyright (c) 2015 Ryan Prichard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +# +# Run with native CPython 2.7 on a 64-bit computer. +# + +import common_ship + +from optparse import OptionParser +import multiprocessing +import os +import shutil +import subprocess +import sys + + +def dllVersion(path): + version = subprocess.check_output( + ["powershell.exe", + "[System.Diagnostics.FileVersionInfo]::GetVersionInfo(\"" + path + "\").FileVersion"]) + return version.strip() + + +os.chdir(common_ship.topDir) + + +# Determine other build parameters. +BUILD_KINDS = { + "cygwin": { + "path": ["bin"], + "dll": "bin\\cygwin1.dll", + }, + "msys2": { + "path": ["opt\\bin", "usr\\bin"], + "dll": "usr\\bin\\msys-2.0.dll", + }, +} + + +def buildTarget(kind, syspath, arch): + + binPaths = [os.path.join(syspath, p) for p in BUILD_KINDS[kind]["path"]] + binPaths += common_ship.defaultPathEnviron.split(";") + newPath = ";".join(binPaths) + + dllver = dllVersion(os.path.join(syspath, BUILD_KINDS[kind]["dll"])) + packageName = "winpty-{}-{}-{}-{}".format(common_ship.winptyVersion, kind, dllver, arch) + if os.path.exists("ship\\packages\\" + packageName): + shutil.rmtree("ship\\packages\\" + packageName) + + print("+ Setting PATH to: {}".format(newPath)) + with common_ship.ModifyEnv(PATH=newPath): + subprocess.check_call(["sh.exe", "configure"]) + subprocess.check_call(["make.exe", "clean"]) + makeBaseCmd = [ + "make.exe", + "COMMIT_HASH=" + common_ship.commitHash, + "PREFIX=ship/packages/" + packageName + ] + subprocess.check_call(makeBaseCmd + ["all", "tests", "-j%d" % multiprocessing.cpu_count()]) + subprocess.check_call(["build\\trivial_test.exe"]) + subprocess.check_call(makeBaseCmd + ["install"]) + subprocess.check_call(["tar.exe", "cvfz", + packageName + ".tar.gz", + packageName], cwd=os.path.join(os.getcwd(), "ship", "packages")) + + +def main(): + parser = OptionParser() + parser.add_option("--kind", type="choice", choices=["cygwin", "msys2"]) + parser.add_option("--syspath") + parser.add_option("--arch", type="choice", choices=["ia32", "x64"]) + (args, extra) = parser.parse_args() + + args.kind or parser.error("--kind must be specified") + args.arch or parser.error("--arch must be specified") + args.syspath or parser.error("--syspath must be specified") + extra and parser.error("unexpected positional argument(s)") + + buildTarget(args.kind, args.syspath, args.arch) + + +if __name__ == "__main__": + main() diff --git a/src/libs/3rdparty/winpty/src/CMakeLists.txt b/src/libs/3rdparty/winpty/src/CMakeLists.txt new file mode 100644 index 00000000000..22b15111d4f --- /dev/null +++ b/src/libs/3rdparty/winpty/src/CMakeLists.txt @@ -0,0 +1,111 @@ +if (MSVC) + add_compile_definitions(NOMINMAX UNICODE _UNICODE) +endif() + +file(WRITE ${CMAKE_BINARY_DIR}/GenVersion.h.in [=[ +const char GenVersion_Version[] = "@VERSION@"; +const char GenVersion_Commit[] = "@COMMIT_HASH@"; +]=]) + +file(READ ../VERSION.txt VERSION) +string(REPLACE "\n" "" VERSION "${VERSION}") +configure_file(${CMAKE_BINARY_DIR}/GenVersion.h.in ${CMAKE_BINARY_DIR}/GenVersion.h @ONLY) + +set(shared_sources + shared/AgentMsg.h + shared/BackgroundDesktop.h + shared/BackgroundDesktop.cc + shared/Buffer.h + shared/Buffer.cc + shared/DebugClient.h + shared/DebugClient.cc + shared/GenRandom.h + shared/GenRandom.cc + shared/OsModule.h + shared/OwnedHandle.h + shared/OwnedHandle.cc + shared/StringBuilder.h + shared/StringUtil.cc + shared/StringUtil.h + shared/UnixCtrlChars.h + shared/WindowsSecurity.cc + shared/WindowsSecurity.h + shared/WindowsVersion.h + shared/WindowsVersion.cc + shared/WinptyAssert.h + shared/WinptyAssert.cc + shared/WinptyException.h + shared/WinptyException.cc + shared/WinptyVersion.h + shared/WinptyVersion.cc + shared/winpty_snprintf.h +) + +# +# winpty-agent +# + +add_qtc_executable(winpty-agent + INCLUDES + include ${CMAKE_BINARY_DIR} + DEFINES WINPTY_AGENT_ASSERT + PROPERTIES QT_COMPILE_OPTIONS_DISABLE_WARNINGS ON + SOURCES + agent/Agent.h + agent/Agent.cc + agent/AgentCreateDesktop.h + agent/AgentCreateDesktop.cc + agent/ConsoleFont.cc + agent/ConsoleFont.h + agent/ConsoleInput.cc + agent/ConsoleInput.h + agent/ConsoleInputReencoding.cc + agent/ConsoleInputReencoding.h + agent/ConsoleLine.cc + agent/ConsoleLine.h + agent/Coord.h + agent/DebugShowInput.h + agent/DebugShowInput.cc + agent/DefaultInputMap.h + agent/DefaultInputMap.cc + agent/DsrSender.h + agent/EventLoop.h + agent/EventLoop.cc + agent/InputMap.h + agent/InputMap.cc + agent/LargeConsoleRead.h + agent/LargeConsoleRead.cc + agent/NamedPipe.h + agent/NamedPipe.cc + agent/Scraper.h + agent/Scraper.cc + agent/SimplePool.h + agent/SmallRect.h + agent/Terminal.h + agent/Terminal.cc + agent/UnicodeEncoding.h + agent/Win32Console.cc + agent/Win32Console.h + agent/Win32ConsoleBuffer.cc + agent/Win32ConsoleBuffer.h + agent/main.cc + ${shared_sources} +) + +# +# libwinpty +# + +add_qtc_library(winpty STATIC + INCLUDES ${CMAKE_BINARY_DIR} + PUBLIC_DEFINES COMPILING_WINPTY_DLL + PROPERTIES QT_COMPILE_OPTIONS_DISABLE_WARNINGS ON + SOURCES + libwinpty/AgentLocation.cc + libwinpty/AgentLocation.h + libwinpty/winpty.cc + ${shared_sources} +) + +target_include_directories(winpty + PUBLIC $) diff --git a/src/libs/3rdparty/winpty/src/agent/Agent.cc b/src/libs/3rdparty/winpty/src/agent/Agent.cc new file mode 100644 index 00000000000..986edead133 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/Agent.cc @@ -0,0 +1,612 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "Agent.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../include/winpty_constants.h" + +#include "../shared/AgentMsg.h" +#include "../shared/Buffer.h" +#include "../shared/DebugClient.h" +#include "../shared/GenRandom.h" +#include "../shared/StringBuilder.h" +#include "../shared/StringUtil.h" +#include "../shared/WindowsVersion.h" +#include "../shared/WinptyAssert.h" + +#include "ConsoleFont.h" +#include "ConsoleInput.h" +#include "NamedPipe.h" +#include "Scraper.h" +#include "Terminal.h" +#include "Win32ConsoleBuffer.h" + +namespace { + +static BOOL WINAPI consoleCtrlHandler(DWORD dwCtrlType) +{ + if (dwCtrlType == CTRL_C_EVENT) { + // Do nothing and claim to have handled the event. + return TRUE; + } + return FALSE; +} + +// We can detect the new Windows 10 console by observing the effect of the +// Mark command. In older consoles, Mark temporarily moves the cursor to the +// top-left of the console window. In the new console, the cursor isn't +// initially moved. +// +// We might like to use Mark to freeze the console, but we can't, because when +// the Mark command ends, the console moves the cursor back to its starting +// point, even if the console application has moved it in the meantime. +static void detectNewWindows10Console( + Win32Console &console, Win32ConsoleBuffer &buffer) +{ + if (!isAtLeastWindows8()) { + return; + } + + ConsoleScreenBufferInfo info = buffer.bufferInfo(); + + // Make sure the window isn't 1x1. AFAIK, this should never happen + // accidentally. It is difficult to make it happen deliberately. + if (info.srWindow.Left == info.srWindow.Right && + info.srWindow.Top == info.srWindow.Bottom) { + trace("detectNewWindows10Console: Initial console window was 1x1 -- " + "expanding for test"); + setSmallFont(buffer.conout(), 400, false); + buffer.moveWindow(SmallRect(0, 0, 1, 1)); + buffer.resizeBuffer(Coord(400, 1)); + buffer.moveWindow(SmallRect(0, 0, 2, 1)); + // This use of GetLargestConsoleWindowSize ought to be unnecessary + // given the behavior I've seen from moveWindow(0, 0, 1, 1), but + // I'd like to be especially sure, considering that this code will + // rarely be tested. + const auto largest = GetLargestConsoleWindowSize(buffer.conout()); + buffer.moveWindow( + SmallRect(0, 0, std::min(largest.X, buffer.bufferSize().X), 1)); + info = buffer.bufferInfo(); + ASSERT(info.srWindow.Right > info.srWindow.Left && + "Could not expand console window from 1x1"); + } + + // Test whether MARK moves the cursor. + const Coord initialPosition(info.srWindow.Right, info.srWindow.Bottom); + buffer.setCursorPosition(initialPosition); + ASSERT(!console.frozen()); + console.setFreezeUsesMark(true); + console.setFrozen(true); + const bool isNewW10 = (buffer.cursorPosition() == initialPosition); + console.setFrozen(false); + buffer.setCursorPosition(Coord(0, 0)); + + trace("Attempting to detect new Windows 10 console using MARK: %s", + isNewW10 ? "detected" : "not detected"); + console.setFreezeUsesMark(false); + console.setNewW10(isNewW10); +} + +static inline WriteBuffer newPacket() { + WriteBuffer packet; + packet.putRawValue(0); // Reserve space for size. + return packet; +} + +static HANDLE duplicateHandle(HANDLE h) { + HANDLE ret = nullptr; + if (!DuplicateHandle( + GetCurrentProcess(), h, + GetCurrentProcess(), &ret, + 0, FALSE, DUPLICATE_SAME_ACCESS)) { + ASSERT(false && "DuplicateHandle failed!"); + } + return ret; +} + +// It's safe to truncate a handle from 64-bits to 32-bits, or to sign-extend it +// back to 64-bits. See the MSDN article, "Interprocess Communication Between +// 32-bit and 64-bit Applications". +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa384203.aspx +static int64_t int64FromHandle(HANDLE h) { + return static_cast(reinterpret_cast(h)); +} + +} // anonymous namespace + +Agent::Agent(LPCWSTR controlPipeName, + uint64_t agentFlags, + int mouseMode, + int initialCols, + int initialRows) : + m_useConerr((agentFlags & WINPTY_FLAG_CONERR) != 0), + m_plainMode((agentFlags & WINPTY_FLAG_PLAIN_OUTPUT) != 0), + m_mouseMode(mouseMode) +{ + trace("Agent::Agent entered"); + + ASSERT(initialCols >= 1 && initialRows >= 1); + initialCols = std::min(initialCols, MAX_CONSOLE_WIDTH); + initialRows = std::min(initialRows, MAX_CONSOLE_HEIGHT); + + const bool outputColor = + !m_plainMode || (agentFlags & WINPTY_FLAG_COLOR_ESCAPES); + const Coord initialSize(initialCols, initialRows); + + auto primaryBuffer = openPrimaryBuffer(); + if (m_useConerr) { + m_errorBuffer = Win32ConsoleBuffer::createErrorBuffer(); + } + + detectNewWindows10Console(m_console, *primaryBuffer); + + m_controlPipe = &connectToControlPipe(controlPipeName); + m_coninPipe = &createDataServerPipe(false, L"conin"); + m_conoutPipe = &createDataServerPipe(true, L"conout"); + if (m_useConerr) { + m_conerrPipe = &createDataServerPipe(true, L"conerr"); + } + + // Send an initial response packet to winpty.dll containing pipe names. + { + auto setupPacket = newPacket(); + setupPacket.putWString(m_coninPipe->name()); + setupPacket.putWString(m_conoutPipe->name()); + if (m_useConerr) { + setupPacket.putWString(m_conerrPipe->name()); + } + writePacket(setupPacket); + } + + std::unique_ptr primaryTerminal; + primaryTerminal.reset(new Terminal(*m_conoutPipe, + m_plainMode, + outputColor)); + m_primaryScraper.reset(new Scraper(m_console, + *primaryBuffer, + std::move(primaryTerminal), + initialSize)); + if (m_useConerr) { + std::unique_ptr errorTerminal; + errorTerminal.reset(new Terminal(*m_conerrPipe, + m_plainMode, + outputColor)); + m_errorScraper.reset(new Scraper(m_console, + *m_errorBuffer, + std::move(errorTerminal), + initialSize)); + } + + m_console.setTitle(m_currentTitle); + + const HANDLE conin = GetStdHandle(STD_INPUT_HANDLE); + m_consoleInput.reset( + new ConsoleInput(conin, m_mouseMode, *this, m_console)); + + // Setup Ctrl-C handling. First restore default handling of Ctrl-C. This + // attribute is inherited by child processes. Then register a custom + // Ctrl-C handler that does nothing. The handler will be called when the + // agent calls GenerateConsoleCtrlEvent. + SetConsoleCtrlHandler(NULL, FALSE); + SetConsoleCtrlHandler(consoleCtrlHandler, TRUE); + + setPollInterval(25); +} + +Agent::~Agent() +{ + trace("Agent::~Agent entered"); + agentShutdown(); + if (m_childProcess != NULL) { + CloseHandle(m_childProcess); + } +} + +// Write a "Device Status Report" command to the terminal. The terminal will +// reply with a row+col escape sequence. Presumably, the DSR reply will not +// split a keypress escape sequence, so it should be safe to assume that the +// bytes before it are complete keypresses. +void Agent::sendDsr() +{ + if (!m_plainMode && !m_conoutPipe->isClosed()) { + m_conoutPipe->write("\x1B[6n"); + } +} + +NamedPipe &Agent::connectToControlPipe(LPCWSTR pipeName) +{ + NamedPipe &pipe = createNamedPipe(); + pipe.connectToServer(pipeName, NamedPipe::OpenMode::Duplex); + pipe.setReadBufferSize(64 * 1024); + return pipe; +} + +// Returns a new server named pipe. It has not yet been connected. +NamedPipe &Agent::createDataServerPipe(bool write, const wchar_t *kind) +{ + const auto name = + (WStringBuilder(128) + << L"\\\\.\\pipe\\winpty-" + << kind << L'-' + << GenRandom().uniqueName()).str_moved(); + NamedPipe &pipe = createNamedPipe(); + pipe.openServerPipe( + name.c_str(), + write ? NamedPipe::OpenMode::Writing + : NamedPipe::OpenMode::Reading, + write ? 8192 : 0, + write ? 0 : 256); + if (!write) { + pipe.setReadBufferSize(64 * 1024); + } + return pipe; +} + +void Agent::onPipeIo(NamedPipe &namedPipe) +{ + if (&namedPipe == m_conoutPipe || &namedPipe == m_conerrPipe) { + autoClosePipesForShutdown(); + } else if (&namedPipe == m_coninPipe) { + pollConinPipe(); + } else if (&namedPipe == m_controlPipe) { + pollControlPipe(); + } +} + +void Agent::pollControlPipe() +{ + if (m_controlPipe->isClosed()) { + trace("Agent exiting (control pipe is closed)"); + shutdown(); + return; + } + + while (true) { + uint64_t packetSize = 0; + const auto amt1 = + m_controlPipe->peek(&packetSize, sizeof(packetSize)); + if (amt1 < sizeof(packetSize)) { + break; + } + ASSERT(packetSize >= sizeof(packetSize) && packetSize <= SIZE_MAX); + if (m_controlPipe->bytesAvailable() < packetSize) { + if (m_controlPipe->readBufferSize() < packetSize) { + m_controlPipe->setReadBufferSize(packetSize); + } + break; + } + std::vector packetData; + packetData.resize(packetSize); + const auto amt2 = m_controlPipe->read(packetData.data(), packetSize); + ASSERT(amt2 == packetSize); + try { + ReadBuffer buffer(std::move(packetData)); + buffer.getRawValue(); // Discard the size. + handlePacket(buffer); + } catch (const ReadBuffer::DecodeError&) { + ASSERT(false && "Decode error"); + } + } +} + +void Agent::handlePacket(ReadBuffer &packet) +{ + const int type = packet.getInt32(); + switch (type) { + case AgentMsg::StartProcess: + handleStartProcessPacket(packet); + break; + case AgentMsg::SetSize: + // TODO: I think it might make sense to collapse consecutive SetSize + // messages. i.e. The terminal process can probably generate SetSize + // messages faster than they can be processed, and some GUIs might + // generate a flood of them, so if we can read multiple SetSize packets + // at once, we can ignore the early ones. + handleSetSizePacket(packet); + break; + case AgentMsg::GetConsoleProcessList: + handleGetConsoleProcessListPacket(packet); + break; + default: + trace("Unrecognized message, id:%d", type); + } +} + +void Agent::writePacket(WriteBuffer &packet) +{ + const auto &bytes = packet.buf(); + packet.replaceRawValue(0, bytes.size()); + m_controlPipe->write(bytes.data(), bytes.size()); +} + +void Agent::handleStartProcessPacket(ReadBuffer &packet) +{ + ASSERT(m_childProcess == nullptr); + ASSERT(!m_closingOutputPipes); + + const uint64_t spawnFlags = packet.getInt64(); + const bool wantProcessHandle = packet.getInt32() != 0; + const bool wantThreadHandle = packet.getInt32() != 0; + const auto program = packet.getWString(); + const auto cmdline = packet.getWString(); + const auto cwd = packet.getWString(); + const auto env = packet.getWString(); + const auto desktop = packet.getWString(); + packet.assertEof(); + + auto cmdlineV = vectorWithNulFromString(cmdline); + auto desktopV = vectorWithNulFromString(desktop); + auto envV = vectorFromString(env); + + LPCWSTR programArg = program.empty() ? nullptr : program.c_str(); + LPWSTR cmdlineArg = cmdline.empty() ? nullptr : cmdlineV.data(); + LPCWSTR cwdArg = cwd.empty() ? nullptr : cwd.c_str(); + LPWSTR envArg = env.empty() ? nullptr : envV.data(); + + STARTUPINFOW sui = {}; + PROCESS_INFORMATION pi = {}; + sui.cb = sizeof(sui); + sui.lpDesktop = desktop.empty() ? nullptr : desktopV.data(); + BOOL inheritHandles = FALSE; + if (m_useConerr) { + inheritHandles = TRUE; + sui.dwFlags |= STARTF_USESTDHANDLES; + sui.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + sui.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + sui.hStdError = m_errorBuffer->conout(); + } + + const BOOL success = + CreateProcessW(programArg, cmdlineArg, nullptr, nullptr, + /*bInheritHandles=*/inheritHandles, + /*dwCreationFlags=*/CREATE_UNICODE_ENVIRONMENT, + envArg, cwdArg, &sui, &pi); + const int lastError = success ? 0 : GetLastError(); + + trace("CreateProcess: %s %u", + (success ? "success" : "fail"), + static_cast(pi.dwProcessId)); + + auto reply = newPacket(); + if (success) { + int64_t replyProcess = 0; + int64_t replyThread = 0; + if (wantProcessHandle) { + replyProcess = int64FromHandle(duplicateHandle(pi.hProcess)); + } + if (wantThreadHandle) { + replyThread = int64FromHandle(duplicateHandle(pi.hThread)); + } + CloseHandle(pi.hThread); + m_childProcess = pi.hProcess; + m_autoShutdown = (spawnFlags & WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN) != 0; + m_exitAfterShutdown = (spawnFlags & WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN) != 0; + reply.putInt32(static_cast(StartProcessResult::ProcessCreated)); + reply.putInt64(replyProcess); + reply.putInt64(replyThread); + } else { + reply.putInt32(static_cast(StartProcessResult::CreateProcessFailed)); + reply.putInt32(lastError); + } + writePacket(reply); +} + +void Agent::handleSetSizePacket(ReadBuffer &packet) +{ + const int cols = packet.getInt32(); + const int rows = packet.getInt32(); + packet.assertEof(); + resizeWindow(cols, rows); + auto reply = newPacket(); + writePacket(reply); +} + +void Agent::handleGetConsoleProcessListPacket(ReadBuffer &packet) +{ + packet.assertEof(); + + auto processList = std::vector(64); + auto processCount = GetConsoleProcessList(&processList[0], processList.size()); + + // The process list can change while we're trying to read it + while (processList.size() < processCount) { + // Multiplying by two caps the number of iterations + const auto newSize = std::max(processList.size() * 2, processCount); + processList.resize(newSize); + processCount = GetConsoleProcessList(&processList[0], processList.size()); + } + + if (processCount == 0) { + trace("GetConsoleProcessList failed"); + } + + auto reply = newPacket(); + reply.putInt32(processCount); + for (DWORD i = 0; i < processCount; i++) { + reply.putInt32(processList[i]); + } + writePacket(reply); +} + +void Agent::pollConinPipe() +{ + const std::string newData = m_coninPipe->readAllToString(); + if (hasDebugFlag("input_separated_bytes")) { + // This debug flag is intended to help with testing incomplete escape + // sequences and multibyte UTF-8 encodings. (I wonder if the normal + // code path ought to advance a state machine one byte at a time.) + for (size_t i = 0; i < newData.size(); ++i) { + m_consoleInput->writeInput(newData.substr(i, 1)); + } + } else { + m_consoleInput->writeInput(newData); + } +} + +void Agent::onPollTimeout() +{ + m_consoleInput->updateInputFlags(); + const bool enableMouseMode = m_consoleInput->shouldActivateTerminalMouse(); + + // Give the ConsoleInput object a chance to flush input from an incomplete + // escape sequence (e.g. pressing ESC). + m_consoleInput->flushIncompleteEscapeCode(); + + const bool shouldScrapeContent = !m_closingOutputPipes; + + // Check if the child process has exited. + if (m_autoShutdown && + m_childProcess != nullptr && + WaitForSingleObject(m_childProcess, 0) == WAIT_OBJECT_0) { + CloseHandle(m_childProcess); + m_childProcess = nullptr; + + // Close the data socket to signal to the client that the child + // process has exited. If there's any data left to send, send it + // before closing the socket. + m_closingOutputPipes = true; + } + + // Scrape for output *after* the above exit-check to ensure that we collect + // the child process's final output. + if (shouldScrapeContent) { + syncConsoleTitle(); + scrapeBuffers(); + } + + // We must ensure that we disable mouse mode before closing the CONOUT + // pipe, so update the mouse mode here. + m_primaryScraper->terminal().enableMouseMode( + enableMouseMode && !m_closingOutputPipes); + + autoClosePipesForShutdown(); +} + +void Agent::autoClosePipesForShutdown() +{ + if (m_closingOutputPipes) { + // We don't want to close a pipe before it's connected! If we do, the + // libwinpty client may try to connect to a non-existent pipe. This + // case is important for short-lived programs. + if (m_conoutPipe->isConnected() && + m_conoutPipe->bytesToSend() == 0) { + trace("Closing CONOUT pipe (auto-shutdown)"); + m_conoutPipe->closePipe(); + } + if (m_conerrPipe != nullptr && + m_conerrPipe->isConnected() && + m_conerrPipe->bytesToSend() == 0) { + trace("Closing CONERR pipe (auto-shutdown)"); + m_conerrPipe->closePipe(); + } + if (m_exitAfterShutdown && + m_conoutPipe->isClosed() && + (m_conerrPipe == nullptr || m_conerrPipe->isClosed())) { + trace("Agent exiting (exit-after-shutdown)"); + shutdown(); + } + } +} + +std::unique_ptr Agent::openPrimaryBuffer() +{ + // If we're using a separate buffer for stderr, and a program were to + // activate the stderr buffer, then we could accidentally scrape the same + // buffer twice. That probably shouldn't happen in ordinary use, but it + // can be avoided anyway by using the original console screen buffer in + // that mode. + if (!m_useConerr) { + return Win32ConsoleBuffer::openConout(); + } else { + return Win32ConsoleBuffer::openStdout(); + } +} + +void Agent::resizeWindow(int cols, int rows) +{ + ASSERT(cols >= 1 && rows >= 1); + cols = std::min(cols, MAX_CONSOLE_WIDTH); + rows = std::min(rows, MAX_CONSOLE_HEIGHT); + + Win32Console::FreezeGuard guard(m_console, m_console.frozen()); + const Coord newSize(cols, rows); + ConsoleScreenBufferInfo info; + auto primaryBuffer = openPrimaryBuffer(); + m_primaryScraper->resizeWindow(*primaryBuffer, newSize, info); + m_consoleInput->setMouseWindowRect(info.windowRect()); + if (m_errorScraper) { + m_errorScraper->resizeWindow(*m_errorBuffer, newSize, info); + } + + // Synthesize a WINDOW_BUFFER_SIZE_EVENT event. Normally, Windows + // generates this event only when the buffer size changes, not when the + // window size changes. This behavior is undesirable in two ways: + // - When winpty expands the window horizontally, it must expand the + // buffer first, then the window. At least some programs (e.g. the WSL + // bash.exe wrapper) use the window width rather than the buffer width, + // so there is a short timespan during which they can read the wrong + // value. + // - If the window's vertical size is changed, no event is generated, + // even though a typical well-behaved console program cares about the + // *window* height, not the *buffer* height. + // This synthesization works around a design flaw in the console. It's probably + // harmless. See https://github.com/rprichard/winpty/issues/110. + INPUT_RECORD sizeEvent {}; + sizeEvent.EventType = WINDOW_BUFFER_SIZE_EVENT; + sizeEvent.Event.WindowBufferSizeEvent.dwSize = primaryBuffer->bufferSize(); + DWORD actual {}; + WriteConsoleInputW(GetStdHandle(STD_INPUT_HANDLE), &sizeEvent, 1, &actual); +} + +void Agent::scrapeBuffers() +{ + Win32Console::FreezeGuard guard(m_console, m_console.frozen()); + ConsoleScreenBufferInfo info; + m_primaryScraper->scrapeBuffer(*openPrimaryBuffer(), info); + m_consoleInput->setMouseWindowRect(info.windowRect()); + if (m_errorScraper) { + m_errorScraper->scrapeBuffer(*m_errorBuffer, info); + } +} + +void Agent::syncConsoleTitle() +{ + std::wstring newTitle = m_console.title(); + if (newTitle != m_currentTitle) { + if (!m_plainMode && !m_conoutPipe->isClosed()) { + std::string command = std::string("\x1b]0;") + + utf8FromWide(newTitle) + "\x07"; + m_conoutPipe->write(command.c_str()); + } + m_currentTitle = newTitle; + } +} diff --git a/src/libs/3rdparty/winpty/src/agent/Agent.h b/src/libs/3rdparty/winpty/src/agent/Agent.h new file mode 100644 index 00000000000..1dde48fe4ab --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/Agent.h @@ -0,0 +1,103 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef AGENT_H +#define AGENT_H + +#include +#include + +#include +#include + +#include "DsrSender.h" +#include "EventLoop.h" +#include "Win32Console.h" + +class ConsoleInput; +class NamedPipe; +class ReadBuffer; +class Scraper; +class WriteBuffer; +class Win32ConsoleBuffer; + +class Agent : public EventLoop, public DsrSender +{ +public: + Agent(LPCWSTR controlPipeName, + uint64_t agentFlags, + int mouseMode, + int initialCols, + int initialRows); + virtual ~Agent(); + void sendDsr() override; + +private: + NamedPipe &connectToControlPipe(LPCWSTR pipeName); + NamedPipe &createDataServerPipe(bool write, const wchar_t *kind); + +private: + void pollControlPipe(); + void handlePacket(ReadBuffer &packet); + void writePacket(WriteBuffer &packet); + void handleStartProcessPacket(ReadBuffer &packet); + void handleSetSizePacket(ReadBuffer &packet); + void handleGetConsoleProcessListPacket(ReadBuffer &packet); + void pollConinPipe(); + +protected: + virtual void onPollTimeout() override; + virtual void onPipeIo(NamedPipe &namedPipe) override; + +private: + void autoClosePipesForShutdown(); + std::unique_ptr openPrimaryBuffer(); + void resizeWindow(int cols, int rows); + void scrapeBuffers(); + void syncConsoleTitle(); + +private: + const bool m_useConerr; + const bool m_plainMode; + const int m_mouseMode; + Win32Console m_console; + std::unique_ptr m_primaryScraper; + std::unique_ptr m_errorScraper; + std::unique_ptr m_errorBuffer; + NamedPipe *m_controlPipe = nullptr; + NamedPipe *m_coninPipe = nullptr; + NamedPipe *m_conoutPipe = nullptr; + NamedPipe *m_conerrPipe = nullptr; + bool m_autoShutdown = false; + bool m_exitAfterShutdown = false; + bool m_closingOutputPipes = false; + std::unique_ptr m_consoleInput; + HANDLE m_childProcess = nullptr; + + // If the title is initialized to the empty string, then cmd.exe will + // sometimes print this error: + // Not enough storage is available to process this command. + // It happens on Windows 7 when logged into a Cygwin SSH session, for + // example. Using a title of a single space character avoids the problem. + // See https://github.com/rprichard/winpty/issues/74. + std::wstring m_currentTitle = L" "; +}; + +#endif // AGENT_H diff --git a/src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.cc b/src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.cc new file mode 100644 index 00000000000..9ad6503b1c5 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.cc @@ -0,0 +1,84 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "AgentCreateDesktop.h" + +#include "../shared/BackgroundDesktop.h" +#include "../shared/Buffer.h" +#include "../shared/DebugClient.h" +#include "../shared/StringUtil.h" + +#include "EventLoop.h" +#include "NamedPipe.h" + +namespace { + +static inline WriteBuffer newPacket() { + WriteBuffer packet; + packet.putRawValue(0); // Reserve space for size. + return packet; +} + +class CreateDesktopLoop : public EventLoop { +public: + CreateDesktopLoop(LPCWSTR controlPipeName); + +protected: + virtual void onPipeIo(NamedPipe &namedPipe) override; + +private: + void writePacket(WriteBuffer &packet); + + BackgroundDesktop m_desktop; + NamedPipe &m_pipe; +}; + +CreateDesktopLoop::CreateDesktopLoop(LPCWSTR controlPipeName) : + m_pipe(createNamedPipe()) { + m_pipe.connectToServer(controlPipeName, NamedPipe::OpenMode::Duplex); + auto packet = newPacket(); + packet.putWString(m_desktop.desktopName()); + writePacket(packet); +} + +void CreateDesktopLoop::writePacket(WriteBuffer &packet) { + const auto &bytes = packet.buf(); + packet.replaceRawValue(0, bytes.size()); + m_pipe.write(bytes.data(), bytes.size()); +} + +void CreateDesktopLoop::onPipeIo(NamedPipe &namedPipe) { + if (m_pipe.isClosed()) { + shutdown(); + } +} + +} // anonymous namespace + +void handleCreateDesktop(LPCWSTR controlPipeName) { + try { + CreateDesktopLoop loop(controlPipeName); + loop.run(); + trace("Agent exiting..."); + } catch (const WinptyException &e) { + trace("handleCreateDesktop: internal error: %s", + utf8FromWide(e.what()).c_str()); + } +} diff --git a/src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.h b/src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.h new file mode 100644 index 00000000000..2ae539c7faa --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.h @@ -0,0 +1,28 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef AGENT_CREATE_DESKTOP_H +#define AGENT_CREATE_DESKTOP_H + +#include + +void handleCreateDesktop(LPCWSTR controlPipeName); + +#endif // AGENT_CREATE_DESKTOP_H diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleFont.cc b/src/libs/3rdparty/winpty/src/agent/ConsoleFont.cc new file mode 100644 index 00000000000..aa1f7876d38 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/ConsoleFont.cc @@ -0,0 +1,698 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "ConsoleFont.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../shared/DebugClient.h" +#include "../shared/OsModule.h" +#include "../shared/StringUtil.h" +#include "../shared/WindowsVersion.h" +#include "../shared/WinptyAssert.h" +#include "../shared/winpty_snprintf.h" + +namespace { + +#define COUNT_OF(x) (sizeof(x) / sizeof((x)[0])) + +// See https://en.wikipedia.org/wiki/List_of_CJK_fonts +const wchar_t kLucidaConsole[] = L"Lucida Console"; +const wchar_t kMSGothic[] = { 0xff2d, 0xff33, 0x0020, 0x30b4, 0x30b7, 0x30c3, 0x30af, 0 }; // 932, Japanese +const wchar_t kNSimSun[] = { 0x65b0, 0x5b8b, 0x4f53, 0 }; // 936, Chinese Simplified +const wchar_t kGulimChe[] = { 0xad74, 0xb9bc, 0xccb4, 0 }; // 949, Korean +const wchar_t kMingLight[] = { 0x7d30, 0x660e, 0x9ad4, 0 }; // 950, Chinese Traditional + +struct FontSize { + short size; + int width; +}; + +struct Font { + const wchar_t *faceName; + unsigned int family; + short size; +}; + +// Ideographs in East Asian languages take two columns rather than one. +// In the console screen buffer, a "full-width" character will occupy two +// cells of the buffer, the first with attribute 0x100 and the second with +// attribute 0x200. +// +// Windows does not correctly identify code points as double-width in all +// configurations. It depends heavily on the code page, the font facename, +// and (somehow) even the font size. In the 437 code page (MS-DOS), for +// example, no codepoints are interpreted as double-width. When the console +// is in an East Asian code page (932, 936, 949, or 950), then sometimes +// selecting a "Western" facename like "Lucida Console" or "Consolas" doesn't +// register, or if the font *can* be chosen, then the console doesn't handle +// double-width correctly. I tested the double-width handling by writing +// several code points with WriteConsole and checking whether one or two cells +// were filled. +// +// In the Japanese code page (932), Microsoft's default font is MS Gothic. +// MS Gothic double-width handling seems to be broken with console versions +// prior to Windows 10 (including Windows 10's legacy mode), and it's +// especially broken in Windows 8 and 8.1. +// +// Test by running: misc/Utf16Echo A2 A3 2014 3044 30FC 4000 +// +// The first three codepoints are always rendered as half-width with the +// Windows Japanese fonts. (Of these, the first two must be half-width, +// but U+2014 could be either.) The last three are rendered as full-width, +// and they are East_Asian_Width=Wide. +// +// Windows 7 fails by modeling all codepoints as full-width with font +// sizes 22 and above. +// +// Windows 8 gets U+00A2, U+00A3, U+2014, U+30FC, and U+4000 wrong, but +// using a point size not listed in the console properties dialog +// (e.g. "9") is less wrong: +// +// | code point | +// font | 00A2 00A3 2014 3044 30FC 4000 | cell size +// ------------+---------------------------------+---------- +// 8 | F F F F H H | 4x8 +// 9 | F F F F F F | 5x9 +// 16 | F F F F H H | 8x16 +// raster 6x13 | H H H F F H(*) | 6x13 +// +// (*) The Raster Font renders U+4000 as a white box (i.e. an unsupported +// character). +// + +// See: +// - misc/Font-Report-June2016 directory for per-size details +// - misc/font-notes.txt +// - misc/Utf16Echo.cc, misc/FontSurvey.cc, misc/SetFont.cc, misc/GetFont.cc + +const FontSize kLucidaFontSizes[] = { + { 5, 3 }, + { 6, 4 }, + { 8, 5 }, + { 10, 6 }, + { 12, 7 }, + { 14, 8 }, + { 16, 10 }, + { 18, 11 }, + { 20, 12 }, + { 36, 22 }, + { 48, 29 }, + { 60, 36 }, + { 72, 43 }, +}; + +// Japanese. Used on Vista and Windows 7. +const FontSize k932GothicVista[] = { + { 6, 3 }, + { 8, 4 }, + { 10, 5 }, + { 12, 6 }, + { 13, 7 }, + { 15, 8 }, + { 17, 9 }, + { 19, 10 }, + { 21, 11 }, + // All larger fonts are more broken w.r.t. full-size East Asian characters. +}; + +// Japanese. Used on Windows 8, 8.1, and the legacy 10 console. +const FontSize k932GothicWin8[] = { + // All of these characters are broken w.r.t. full-size East Asian + // characters, but they're equally broken. + { 5, 3 }, + { 7, 4 }, + { 9, 5 }, + { 11, 6 }, + { 13, 7 }, + { 15, 8 }, + { 17, 9 }, + { 20, 10 }, + { 22, 11 }, + { 24, 12 }, + // include extra-large fonts for small terminals + { 36, 18 }, + { 48, 24 }, + { 60, 30 }, + { 72, 36 }, +}; + +// Japanese. Used on the new Windows 10 console. +const FontSize k932GothicWin10[] = { + { 6, 3 }, + { 8, 4 }, + { 10, 5 }, + { 12, 6 }, + { 14, 7 }, + { 16, 8 }, + { 18, 9 }, + { 20, 10 }, + { 22, 11 }, + { 24, 12 }, + // include extra-large fonts for small terminals + { 36, 18 }, + { 48, 24 }, + { 60, 30 }, + { 72, 36 }, +}; + +// Chinese Simplified. +const FontSize k936SimSun[] = { + { 6, 3 }, + { 8, 4 }, + { 10, 5 }, + { 12, 6 }, + { 14, 7 }, + { 16, 8 }, + { 18, 9 }, + { 20, 10 }, + { 22, 11 }, + { 24, 12 }, + // include extra-large fonts for small terminals + { 36, 18 }, + { 48, 24 }, + { 60, 30 }, + { 72, 36 }, +}; + +// Korean. +const FontSize k949GulimChe[] = { + { 6, 3 }, + { 8, 4 }, + { 10, 5 }, + { 12, 6 }, + { 14, 7 }, + { 16, 8 }, + { 18, 9 }, + { 20, 10 }, + { 22, 11 }, + { 24, 12 }, + // include extra-large fonts for small terminals + { 36, 18 }, + { 48, 24 }, + { 60, 30 }, + { 72, 36 }, +}; + +// Chinese Traditional. +const FontSize k950MingLight[] = { + { 6, 3 }, + { 8, 4 }, + { 10, 5 }, + { 12, 6 }, + { 14, 7 }, + { 16, 8 }, + { 18, 9 }, + { 20, 10 }, + { 22, 11 }, + { 24, 12 }, + // include extra-large fonts for small terminals + { 36, 18 }, + { 48, 24 }, + { 60, 30 }, + { 72, 36 }, +}; + +// Some of these types and functions are missing from the MinGW headers. +// Others are undocumented. + +struct AGENT_CONSOLE_FONT_INFO { + DWORD nFont; + COORD dwFontSize; +}; + +struct AGENT_CONSOLE_FONT_INFOEX { + ULONG cbSize; + DWORD nFont; + COORD dwFontSize; + UINT FontFamily; + UINT FontWeight; + WCHAR FaceName[LF_FACESIZE]; +}; + +// undocumented XP API +typedef BOOL WINAPI SetConsoleFont_t( + HANDLE hOutput, + DWORD dwFontIndex); + +// undocumented XP API +typedef DWORD WINAPI GetNumberOfConsoleFonts_t(); + +// XP and up +typedef BOOL WINAPI GetCurrentConsoleFont_t( + HANDLE hOutput, + BOOL bMaximumWindow, + AGENT_CONSOLE_FONT_INFO *lpConsoleCurrentFont); + +// XP and up +typedef COORD WINAPI GetConsoleFontSize_t( + HANDLE hConsoleOutput, + DWORD nFont); + +// Vista and up +typedef BOOL WINAPI GetCurrentConsoleFontEx_t( + HANDLE hConsoleOutput, + BOOL bMaximumWindow, + AGENT_CONSOLE_FONT_INFOEX *lpConsoleCurrentFontEx); + +// Vista and up +typedef BOOL WINAPI SetCurrentConsoleFontEx_t( + HANDLE hConsoleOutput, + BOOL bMaximumWindow, + AGENT_CONSOLE_FONT_INFOEX *lpConsoleCurrentFontEx); + +#define GET_MODULE_PROC(mod, funcName) \ + m_##funcName = reinterpret_cast((mod).proc(#funcName)); \ + +#define DEFINE_ACCESSOR(funcName) \ + funcName##_t &funcName() const { \ + ASSERT(valid()); \ + return *m_##funcName; \ + } + +class XPFontAPI { +public: + XPFontAPI() : m_kernel32(L"kernel32.dll") { + GET_MODULE_PROC(m_kernel32, GetCurrentConsoleFont); + GET_MODULE_PROC(m_kernel32, GetConsoleFontSize); + } + + bool valid() const { + return m_GetCurrentConsoleFont != NULL && + m_GetConsoleFontSize != NULL; + } + + DEFINE_ACCESSOR(GetCurrentConsoleFont) + DEFINE_ACCESSOR(GetConsoleFontSize) + +private: + OsModule m_kernel32; + GetCurrentConsoleFont_t *m_GetCurrentConsoleFont; + GetConsoleFontSize_t *m_GetConsoleFontSize; +}; + +class UndocumentedXPFontAPI : public XPFontAPI { +public: + UndocumentedXPFontAPI() : m_kernel32(L"kernel32.dll") { + GET_MODULE_PROC(m_kernel32, SetConsoleFont); + GET_MODULE_PROC(m_kernel32, GetNumberOfConsoleFonts); + } + + bool valid() const { + return this->XPFontAPI::valid() && + m_SetConsoleFont != NULL && + m_GetNumberOfConsoleFonts != NULL; + } + + DEFINE_ACCESSOR(SetConsoleFont) + DEFINE_ACCESSOR(GetNumberOfConsoleFonts) + +private: + OsModule m_kernel32; + SetConsoleFont_t *m_SetConsoleFont; + GetNumberOfConsoleFonts_t *m_GetNumberOfConsoleFonts; +}; + +class VistaFontAPI : public XPFontAPI { +public: + VistaFontAPI() : m_kernel32(L"kernel32.dll") { + GET_MODULE_PROC(m_kernel32, GetCurrentConsoleFontEx); + GET_MODULE_PROC(m_kernel32, SetCurrentConsoleFontEx); + } + + bool valid() const { + return this->XPFontAPI::valid() && + m_GetCurrentConsoleFontEx != NULL && + m_SetCurrentConsoleFontEx != NULL; + } + + DEFINE_ACCESSOR(GetCurrentConsoleFontEx) + DEFINE_ACCESSOR(SetCurrentConsoleFontEx) + +private: + OsModule m_kernel32; + GetCurrentConsoleFontEx_t *m_GetCurrentConsoleFontEx; + SetCurrentConsoleFontEx_t *m_SetCurrentConsoleFontEx; +}; + +static std::vector > readFontTable( + XPFontAPI &api, HANDLE conout, DWORD maxCount) { + std::vector > ret; + for (DWORD i = 0; i < maxCount; ++i) { + COORD size = api.GetConsoleFontSize()(conout, i); + if (size.X == 0 && size.Y == 0) { + break; + } + ret.push_back(std::make_pair(i, size)); + } + return ret; +} + +static void dumpFontTable(HANDLE conout, const char *prefix) { + const int kMaxCount = 1000; + if (!isTracingEnabled()) { + return; + } + XPFontAPI api; + if (!api.valid()) { + trace("dumpFontTable: cannot dump font table -- missing APIs"); + return; + } + std::vector > table = + readFontTable(api, conout, kMaxCount); + std::string line; + char tmp[128]; + size_t first = 0; + while (first < table.size()) { + size_t last = std::min(table.size() - 1, first + 10 - 1); + winpty_snprintf(tmp, "%sfonts %02u-%02u:", + prefix, static_cast(first), static_cast(last)); + line = tmp; + for (size_t i = first; i <= last; ++i) { + if (i % 10 == 5) { + line += " - "; + } + winpty_snprintf(tmp, " %2dx%-2d", + table[i].second.X, table[i].second.Y); + line += tmp; + } + trace("%s", line.c_str()); + first = last + 1; + } + if (table.size() == kMaxCount) { + trace("%sfonts: ... stopped reading at %d fonts ...", + prefix, kMaxCount); + } +} + +static std::string stringToCodePoints(const std::wstring &str) { + std::string ret = "("; + for (size_t i = 0; i < str.size(); ++i) { + char tmp[32]; + winpty_snprintf(tmp, "%X", str[i]); + if (ret.size() > 1) { + ret.push_back(' '); + } + ret += tmp; + } + ret.push_back(')'); + return ret; +} + +static void dumpFontInfoEx( + const AGENT_CONSOLE_FONT_INFOEX &infoex, + const char *prefix) { + if (!isTracingEnabled()) { + return; + } + std::wstring faceName(infoex.FaceName, + winpty_wcsnlen(infoex.FaceName, COUNT_OF(infoex.FaceName))); + trace("%snFont=%u dwFontSize=(%d,%d) " + "FontFamily=0x%x FontWeight=%u FaceName=%s %s", + prefix, + static_cast(infoex.nFont), + infoex.dwFontSize.X, infoex.dwFontSize.Y, + infoex.FontFamily, infoex.FontWeight, utf8FromWide(faceName).c_str(), + stringToCodePoints(faceName).c_str()); +} + +static void dumpVistaFont(VistaFontAPI &api, HANDLE conout, const char *prefix) { + if (!isTracingEnabled()) { + return; + } + AGENT_CONSOLE_FONT_INFOEX infoex = {0}; + infoex.cbSize = sizeof(infoex); + if (!api.GetCurrentConsoleFontEx()(conout, FALSE, &infoex)) { + trace("GetCurrentConsoleFontEx call failed"); + return; + } + dumpFontInfoEx(infoex, prefix); +} + +static void dumpXPFont(XPFontAPI &api, HANDLE conout, const char *prefix) { + if (!isTracingEnabled()) { + return; + } + AGENT_CONSOLE_FONT_INFO info = {0}; + if (!api.GetCurrentConsoleFont()(conout, FALSE, &info)) { + trace("GetCurrentConsoleFont call failed"); + return; + } + trace("%snFont=%u dwFontSize=(%d,%d)", + prefix, + static_cast(info.nFont), + info.dwFontSize.X, info.dwFontSize.Y); +} + +static bool setFontVista( + VistaFontAPI &api, + HANDLE conout, + const Font &font) { + AGENT_CONSOLE_FONT_INFOEX infoex = {}; + infoex.cbSize = sizeof(AGENT_CONSOLE_FONT_INFOEX); + infoex.dwFontSize.Y = font.size; + infoex.FontFamily = font.family; + infoex.FontWeight = 400; + winpty_wcsncpy_nul(infoex.FaceName, font.faceName); + dumpFontInfoEx(infoex, "setFontVista: setting font to: "); + if (!api.SetCurrentConsoleFontEx()(conout, FALSE, &infoex)) { + trace("setFontVista: SetCurrentConsoleFontEx call failed"); + return false; + } + memset(&infoex, 0, sizeof(infoex)); + infoex.cbSize = sizeof(infoex); + if (!api.GetCurrentConsoleFontEx()(conout, FALSE, &infoex)) { + trace("setFontVista: GetCurrentConsoleFontEx call failed"); + return false; + } + if (wcsncmp(infoex.FaceName, font.faceName, + COUNT_OF(infoex.FaceName)) != 0) { + trace("setFontVista: face name was not set"); + dumpFontInfoEx(infoex, "setFontVista: post-call font: "); + return false; + } + // We'd like to verify that the new font size is correct, but we can't + // predict what it will be, even though we just set it to `pxSize` through + // an apprently symmetric interface. For the Chinese and Korean fonts, the + // new `infoex.dwFontSize.Y` value can be slightly larger than the height + // we specified. + return true; +} + +static Font selectSmallFont(int codePage, int columns, bool isNewW10) { + // Iterate over a set of font sizes according to the code page, and select + // one. + + const wchar_t *faceName = nullptr; + unsigned int fontFamily = 0; + const FontSize *table = nullptr; + size_t tableSize = 0; + + switch (codePage) { + case 932: // Japanese + faceName = kMSGothic; + fontFamily = 0x36; + if (isNewW10) { + table = k932GothicWin10; + tableSize = COUNT_OF(k932GothicWin10); + } else if (isAtLeastWindows8()) { + table = k932GothicWin8; + tableSize = COUNT_OF(k932GothicWin8); + } else { + table = k932GothicVista; + tableSize = COUNT_OF(k932GothicVista); + } + break; + case 936: // Chinese Simplified + faceName = kNSimSun; + fontFamily = 0x36; + table = k936SimSun; + tableSize = COUNT_OF(k936SimSun); + break; + case 949: // Korean + faceName = kGulimChe; + fontFamily = 0x36; + table = k949GulimChe; + tableSize = COUNT_OF(k949GulimChe); + break; + case 950: // Chinese Traditional + faceName = kMingLight; + fontFamily = 0x36; + table = k950MingLight; + tableSize = COUNT_OF(k950MingLight); + break; + default: + faceName = kLucidaConsole; + fontFamily = 0x36; + table = kLucidaFontSizes; + tableSize = COUNT_OF(kLucidaFontSizes); + break; + } + + size_t bestIndex = static_cast(-1); + std::tuple bestScore = std::make_tuple(-1, -1); + + // We might want to pick the smallest possible font, because we don't know + // how large the monitor is (and the monitor size can change). We might + // want to pick a larger font to accommodate console programs that resize + // the console on their own, like DOS edit.com, which tends to resize the + // console to 80 columns. + + for (size_t i = 0; i < tableSize; ++i) { + const int width = table[i].width * columns; + + // In general, we'd like to pick a font size where cutting the number + // of columns in half doesn't immediately violate the minimum width + // constraint. (e.g. To run DOS edit.com, a user might resize their + // terminal to ~100 columns so it's big enough to show the 80 columns + // post-resize.) To achieve this, give priority to fonts that allow + // this halving. We don't want to encourage *very* large fonts, + // though, so disable the effect as the number of columns scales from + // 80 to 40. + const int halfColumns = std::min(columns, std::max(40, columns / 2)); + const int halfWidth = table[i].width * halfColumns; + + std::tuple thisScore = std::make_tuple(-1, -1); + if (width >= 160 && halfWidth >= 160) { + // Both sizes are good. Prefer the smaller fonts. + thisScore = std::make_tuple(2, -width); + } else if (width >= 160) { + // Prefer the smaller fonts. + thisScore = std::make_tuple(1, -width); + } else { + // Otherwise, prefer the largest font in our table. + thisScore = std::make_tuple(0, width); + } + if (thisScore > bestScore) { + bestIndex = i; + bestScore = thisScore; + } + } + + ASSERT(bestIndex != static_cast(-1)); + return Font { faceName, fontFamily, table[bestIndex].size }; +} + +static void setSmallFontVista(VistaFontAPI &api, HANDLE conout, + int columns, bool isNewW10) { + int codePage = GetConsoleOutputCP(); + const auto font = selectSmallFont(codePage, columns, isNewW10); + if (setFontVista(api, conout, font)) { + trace("setSmallFontVista: success"); + return; + } + if (codePage == 932 || codePage == 936 || + codePage == 949 || codePage == 950) { + trace("setSmallFontVista: falling back to default codepage font instead"); + const auto fontFB = selectSmallFont(0, columns, isNewW10); + if (setFontVista(api, conout, fontFB)) { + trace("setSmallFontVista: fallback was successful"); + return; + } + } + trace("setSmallFontVista: failure"); +} + +struct FontSizeComparator { + bool operator()(const std::pair &obj1, + const std::pair &obj2) const { + int score1 = obj1.second.X + obj1.second.Y; + int score2 = obj2.second.X + obj2.second.Y; + return score1 < score2; + } +}; + +static void setSmallFontXP(UndocumentedXPFontAPI &api, HANDLE conout) { + // Read the console font table and sort it from smallest to largest. + const DWORD fontCount = api.GetNumberOfConsoleFonts()(); + trace("setSmallFontXP: number of console fonts: %u", + static_cast(fontCount)); + std::vector > table = + readFontTable(api, conout, fontCount); + std::sort(table.begin(), table.end(), FontSizeComparator()); + for (size_t i = 0; i < table.size(); ++i) { + // Skip especially narrow fonts to permit narrower terminals. + if (table[i].second.X < 4) { + continue; + } + trace("setSmallFontXP: setting font to %u", + static_cast(table[i].first)); + if (!api.SetConsoleFont()(conout, table[i].first)) { + trace("setSmallFontXP: SetConsoleFont call failed"); + continue; + } + AGENT_CONSOLE_FONT_INFO info; + if (!api.GetCurrentConsoleFont()(conout, FALSE, &info)) { + trace("setSmallFontXP: GetCurrentConsoleFont call failed"); + return; + } + if (info.nFont != table[i].first) { + trace("setSmallFontXP: font was not set"); + dumpXPFont(api, conout, "setSmallFontXP: post-call font: "); + continue; + } + trace("setSmallFontXP: success"); + return; + } + trace("setSmallFontXP: failure"); +} + +} // anonymous namespace + +// A Windows console window can never be larger than the desktop window. To +// maximize the possible size of the console in rows*cols, try to configure +// the console with a small font. Unfortunately, we cannot make the font *too* +// small, because there is also a minimum window size in pixels. +void setSmallFont(HANDLE conout, int columns, bool isNewW10) { + trace("setSmallFont: attempting to set a small font for %d columns " + "(CP=%u OutputCP=%u)", + columns, + static_cast(GetConsoleCP()), + static_cast(GetConsoleOutputCP())); + VistaFontAPI vista; + if (vista.valid()) { + dumpVistaFont(vista, conout, "previous font: "); + dumpFontTable(conout, "previous font table: "); + setSmallFontVista(vista, conout, columns, isNewW10); + dumpVistaFont(vista, conout, "new font: "); + dumpFontTable(conout, "new font table: "); + return; + } + UndocumentedXPFontAPI xp; + if (xp.valid()) { + dumpXPFont(xp, conout, "previous font: "); + dumpFontTable(conout, "previous font table: "); + setSmallFontXP(xp, conout); + dumpXPFont(xp, conout, "new font: "); + dumpFontTable(conout, "new font table: "); + return; + } + trace("setSmallFont: neither Vista nor XP APIs detected -- giving up"); + dumpFontTable(conout, "font table: "); +} diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleFont.h b/src/libs/3rdparty/winpty/src/agent/ConsoleFont.h new file mode 100644 index 00000000000..99cb10698d9 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/ConsoleFont.h @@ -0,0 +1,28 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef CONSOLEFONT_H +#define CONSOLEFONT_H + +#include + +void setSmallFont(HANDLE conout, int columns, bool isNewW10); + +#endif // CONSOLEFONT_H diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleInput.cc b/src/libs/3rdparty/winpty/src/agent/ConsoleInput.cc new file mode 100644 index 00000000000..192cac2a298 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/ConsoleInput.cc @@ -0,0 +1,852 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "ConsoleInput.h" + +#include +#include + +#include +#include + +#include "../include/winpty_constants.h" + +#include "../shared/DebugClient.h" +#include "../shared/StringBuilder.h" +#include "../shared/UnixCtrlChars.h" + +#include "ConsoleInputReencoding.h" +#include "DebugShowInput.h" +#include "DefaultInputMap.h" +#include "DsrSender.h" +#include "UnicodeEncoding.h" +#include "Win32Console.h" + +// MAPVK_VK_TO_VSC isn't defined by the old MinGW. +#ifndef MAPVK_VK_TO_VSC +#define MAPVK_VK_TO_VSC 0 +#endif + +namespace { + +struct MouseRecord { + bool release; + int flags; + COORD coord; + + std::string toString() const; +}; + +std::string MouseRecord::toString() const { + StringBuilder sb(40); + sb << "pos=" << coord.X << ',' << coord.Y + << " flags=0x" << hexOfInt(flags); + if (release) { + sb << " release"; + } + return sb.str_moved(); +} + +const unsigned int kIncompleteEscapeTimeoutMs = 1000u; + +#define CHECK(cond) \ + do { \ + if (!(cond)) { return 0; } \ + } while(0) + +#define ADVANCE() \ + do { \ + pch++; \ + if (pch == stop) { return -1; } \ + } while(0) + +#define SCAN_INT(out, maxLen) \ + do { \ + (out) = 0; \ + CHECK(isdigit(*pch)); \ + const char *begin = pch; \ + do { \ + CHECK(pch - begin + 1 < maxLen); \ + (out) = (out) * 10 + *pch - '0'; \ + ADVANCE(); \ + } while (isdigit(*pch)); \ + } while(0) + +#define SCAN_SIGNED_INT(out, maxLen) \ + do { \ + bool negative = false; \ + if (*pch == '-') { \ + negative = true; \ + ADVANCE(); \ + } \ + SCAN_INT(out, maxLen); \ + if (negative) { \ + (out) = -(out); \ + } \ + } while(0) + +// Match the Device Status Report console input: ESC [ nn ; mm R +// Returns: +// 0 no match +// >0 match, returns length of match +// -1 incomplete match +static int matchDsr(const char *input, int inputSize) +{ + int32_t dummy = 0; + const char *pch = input; + const char *stop = input + inputSize; + CHECK(*pch == '\x1B'); ADVANCE(); + CHECK(*pch == '['); ADVANCE(); + SCAN_INT(dummy, 8); + CHECK(*pch == ';'); ADVANCE(); + SCAN_INT(dummy, 8); + CHECK(*pch == 'R'); + return pch - input + 1; +} + +static int matchMouseDefault(const char *input, int inputSize, + MouseRecord &out) +{ + const char *pch = input; + const char *stop = input + inputSize; + CHECK(*pch == '\x1B'); ADVANCE(); + CHECK(*pch == '['); ADVANCE(); + CHECK(*pch == 'M'); ADVANCE(); + out.flags = (*pch - 32) & 0xFF; ADVANCE(); + out.coord.X = (*pch - '!') & 0xFF; + ADVANCE(); + out.coord.Y = (*pch - '!') & 0xFF; + out.release = false; + return pch - input + 1; +} + +static int matchMouse1006(const char *input, int inputSize, MouseRecord &out) +{ + const char *pch = input; + const char *stop = input + inputSize; + int32_t temp; + CHECK(*pch == '\x1B'); ADVANCE(); + CHECK(*pch == '['); ADVANCE(); + CHECK(*pch == '<'); ADVANCE(); + SCAN_INT(out.flags, 8); + CHECK(*pch == ';'); ADVANCE(); + SCAN_SIGNED_INT(temp, 8); out.coord.X = temp - 1; + CHECK(*pch == ';'); ADVANCE(); + SCAN_SIGNED_INT(temp, 8); out.coord.Y = temp - 1; + CHECK(*pch == 'M' || *pch == 'm'); + out.release = (*pch == 'm'); + return pch - input + 1; +} + +static int matchMouse1015(const char *input, int inputSize, MouseRecord &out) +{ + const char *pch = input; + const char *stop = input + inputSize; + int32_t temp; + CHECK(*pch == '\x1B'); ADVANCE(); + CHECK(*pch == '['); ADVANCE(); + SCAN_INT(out.flags, 8); out.flags -= 32; + CHECK(*pch == ';'); ADVANCE(); + SCAN_SIGNED_INT(temp, 8); out.coord.X = temp - 1; + CHECK(*pch == ';'); ADVANCE(); + SCAN_SIGNED_INT(temp, 8); out.coord.Y = temp - 1; + CHECK(*pch == 'M'); + out.release = false; + return pch - input + 1; +} + +// Match a mouse input escape sequence of any kind. +// 0 no match +// >0 match, returns length of match +// -1 incomplete match +static int matchMouseRecord(const char *input, int inputSize, MouseRecord &out) +{ + memset(&out, 0, sizeof(out)); + int ret; + if ((ret = matchMouse1006(input, inputSize, out)) != 0) { return ret; } + if ((ret = matchMouse1015(input, inputSize, out)) != 0) { return ret; } + if ((ret = matchMouseDefault(input, inputSize, out)) != 0) { return ret; } + return 0; +} + +#undef CHECK +#undef ADVANCE +#undef SCAN_INT + +} // anonymous namespace + +ConsoleInput::ConsoleInput(HANDLE conin, int mouseMode, DsrSender &dsrSender, + Win32Console &console) : + m_console(console), + m_conin(conin), + m_mouseMode(mouseMode), + m_dsrSender(dsrSender) +{ + addDefaultEntriesToInputMap(m_inputMap); + if (hasDebugFlag("dump_input_map")) { + m_inputMap.dumpInputMap(); + } + + // Configure Quick Edit mode according to the mouse mode. Enable + // InsertMode for two reasons: + // - If it's OFF, it's difficult for the user to turn it ON. The + // properties dialog is inaccesible. winpty still faithfully handles + // the Insert key, which toggles between the insertion and overwrite + // modes. + // - When we modify the QuickEdit setting, if ExtendedFlags is OFF, + // then we must choose the InsertMode setting. I don't *think* this + // case happens, though, because a new console always has ExtendedFlags + // ON. + // See misc/EnableExtendedFlags.txt. + DWORD mode = 0; + if (!GetConsoleMode(conin, &mode)) { + trace("Agent startup: GetConsoleMode failed"); + } else { + mode |= ENABLE_EXTENDED_FLAGS; + mode |= ENABLE_INSERT_MODE; + if (m_mouseMode == WINPTY_MOUSE_MODE_AUTO) { + mode |= ENABLE_QUICK_EDIT_MODE; + } else { + mode &= ~ENABLE_QUICK_EDIT_MODE; + } + if (!SetConsoleMode(conin, mode)) { + trace("Agent startup: SetConsoleMode failed"); + } + } + + updateInputFlags(true); +} + +void ConsoleInput::writeInput(const std::string &input) +{ + if (input.size() == 0) { + return; + } + + if (isTracingEnabled()) { + static bool debugInput = hasDebugFlag("input"); + if (debugInput) { + std::string dumpString; + for (size_t i = 0; i < input.size(); ++i) { + const char ch = input[i]; + const char ctrl = decodeUnixCtrlChar(ch); + if (ctrl != '\0') { + dumpString += '^'; + dumpString += ctrl; + } else { + dumpString += ch; + } + } + dumpString += " ("; + for (size_t i = 0; i < input.size(); ++i) { + if (i > 0) { + dumpString += ' '; + } + const unsigned char uch = input[i]; + char buf[32]; + winpty_snprintf(buf, "%02X", uch); + dumpString += buf; + } + dumpString += ')'; + trace("input chars: %s", dumpString.c_str()); + } + } + + m_byteQueue.append(input); + doWrite(false); + if (!m_byteQueue.empty() && !m_dsrSent) { + trace("send DSR"); + m_dsrSender.sendDsr(); + m_dsrSent = true; + } + m_lastWriteTick = GetTickCount(); +} + +void ConsoleInput::flushIncompleteEscapeCode() +{ + if (!m_byteQueue.empty() && + (GetTickCount() - m_lastWriteTick) > kIncompleteEscapeTimeoutMs) { + doWrite(true); + m_byteQueue.clear(); + } +} + +void ConsoleInput::updateInputFlags(bool forceTrace) +{ + const DWORD mode = inputConsoleMode(); + const bool newFlagEE = (mode & ENABLE_EXTENDED_FLAGS) != 0; + const bool newFlagMI = (mode & ENABLE_MOUSE_INPUT) != 0; + const bool newFlagQE = (mode & ENABLE_QUICK_EDIT_MODE) != 0; + const bool newFlagEI = (mode & 0x200) != 0; + if (forceTrace || + newFlagEE != m_enableExtendedEnabled || + newFlagMI != m_mouseInputEnabled || + newFlagQE != m_quickEditEnabled || + newFlagEI != m_escapeInputEnabled) { + trace("CONIN modes: Extended=%s, MouseInput=%s QuickEdit=%s EscapeInput=%s", + newFlagEE ? "on" : "off", + newFlagMI ? "on" : "off", + newFlagQE ? "on" : "off", + newFlagEI ? "on" : "off"); + } + m_enableExtendedEnabled = newFlagEE; + m_mouseInputEnabled = newFlagMI; + m_quickEditEnabled = newFlagQE; + m_escapeInputEnabled = newFlagEI; +} + +bool ConsoleInput::shouldActivateTerminalMouse() +{ + // Return whether the agent should activate the terminal's mouse mode. + if (m_mouseMode == WINPTY_MOUSE_MODE_AUTO) { + // Some programs (e.g. Cygwin command-line programs like bash.exe and + // python2.7.exe) turn off ENABLE_EXTENDED_FLAGS and turn on + // ENABLE_MOUSE_INPUT, but do not turn off QuickEdit mode and do not + // actually care about mouse input. Only enable the terminal mouse + // mode if ENABLE_EXTENDED_FLAGS is on. See + // misc/EnableExtendedFlags.txt. + return m_mouseInputEnabled && !m_quickEditEnabled && + m_enableExtendedEnabled; + } else if (m_mouseMode == WINPTY_MOUSE_MODE_FORCE) { + return true; + } else { + return false; + } +} + +void ConsoleInput::doWrite(bool isEof) +{ + const char *data = m_byteQueue.c_str(); + std::vector records; + size_t idx = 0; + while (idx < m_byteQueue.size()) { + int charSize = scanInput(records, &data[idx], m_byteQueue.size() - idx, isEof); + if (charSize == -1) + break; + idx += charSize; + } + m_byteQueue.erase(0, idx); + flushInputRecords(records); +} + +void ConsoleInput::flushInputRecords(std::vector &records) +{ + if (records.size() == 0) { + return; + } + DWORD actual = 0; + if (!WriteConsoleInputW(m_conin, records.data(), records.size(), &actual)) { + trace("WriteConsoleInputW failed"); + } + records.clear(); +} + +// This behavior isn't strictly correct, because the keypresses (probably?) +// adopt the keyboard state (e.g. Ctrl/Alt/Shift modifiers) of the current +// window station's keyboard, which has no necessary relationship to the winpty +// instance. It's unlikely to be an issue in practice, but it's conceivable. +// (Imagine a foreground SSH server, where the local user holds down Ctrl, +// while the remote user tries to use WSL navigation keys.) I suspect using +// the BackgroundDesktop mechanism in winpty would fix the problem. +// +// https://github.com/rprichard/winpty/issues/116 +static void sendKeyMessage(HWND hwnd, bool isKeyDown, uint16_t virtualKey) +{ + uint32_t scanCode = MapVirtualKey(virtualKey, MAPVK_VK_TO_VSC); + if (scanCode > 255) { + scanCode = 0; + } + SendMessage(hwnd, isKeyDown ? WM_KEYDOWN : WM_KEYUP, virtualKey, + (scanCode << 16) | 1u | (isKeyDown ? 0u : 0xc0000000u)); +} + +int ConsoleInput::scanInput(std::vector &records, + const char *input, + int inputSize, + bool isEof) +{ + ASSERT(inputSize >= 1); + + // Ctrl-C. + // + // In processed mode, use GenerateConsoleCtrlEvent so that Ctrl-C handlers + // are called. GenerateConsoleCtrlEvent unfortunately doesn't interrupt + // ReadConsole calls[1]. Using WM_KEYDOWN/UP fixes the ReadConsole + // problem, but breaks in background window stations/desktops. + // + // In unprocessed mode, there's an entry for Ctrl-C in the SimpleEncoding + // table in DefaultInputMap. + // + // [1] https://github.com/rprichard/winpty/issues/116 + if (input[0] == '\x03' && (inputConsoleMode() & ENABLE_PROCESSED_INPUT)) { + flushInputRecords(records); + trace("Ctrl-C"); + const BOOL ret = GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); + trace("GenerateConsoleCtrlEvent: %d", ret); + return 1; + } + + if (input[0] == '\x1B') { + // Attempt to match the Device Status Report (DSR) reply. + int dsrLen = matchDsr(input, inputSize); + if (dsrLen > 0) { + trace("Received a DSR reply"); + m_dsrSent = false; + return dsrLen; + } else if (!isEof && dsrLen == -1) { + // Incomplete DSR match. + trace("Incomplete DSR match"); + return -1; + } + + int mouseLen = scanMouseInput(records, input, inputSize); + if (mouseLen > 0 || (!isEof && mouseLen == -1)) { + return mouseLen; + } + } + + // Search the input map. + InputMap::Key match; + bool incomplete; + int matchLen = m_inputMap.lookupKey(input, inputSize, match, incomplete); + if (!isEof && incomplete) { + // Incomplete match -- need more characters (or wait for a + // timeout to signify flushed input). + trace("Incomplete escape sequence"); + return -1; + } else if (matchLen > 0) { + uint32_t winCodePointDn = match.unicodeChar; + if ((match.keyState & LEFT_CTRL_PRESSED) && (match.keyState & LEFT_ALT_PRESSED)) { + winCodePointDn = '\0'; + } + uint32_t winCodePointUp = winCodePointDn; + if (match.keyState & LEFT_ALT_PRESSED) { + winCodePointUp = '\0'; + } + appendKeyPress(records, match.virtualKey, + winCodePointDn, winCodePointUp, match.keyState, + match.unicodeChar, match.keyState); + return matchLen; + } + + // Recognize Alt-. + // + // This code doesn't match Alt-ESC, which is encoded as `ESC ESC`, but + // maybe it should. I was concerned that pressing ESC rapidly enough could + // accidentally trigger Alt-ESC. (e.g. The user would have to be faster + // than the DSR flushing mechanism or use a decrepit terminal. The user + // might be on a slow network connection.) + if (input[0] == '\x1B' && inputSize >= 2 && input[1] != '\x1B') { + const int len = utf8CharLength(input[1]); + if (len > 0) { + if (1 + len > inputSize) { + // Incomplete character. + trace("Incomplete UTF-8 character in Alt-"); + return -1; + } + appendUtf8Char(records, &input[1], len, true); + return 1 + len; + } + } + + // A UTF-8 character. + const int len = utf8CharLength(input[0]); + if (len == 0) { + static bool debugInput = isTracingEnabled() && hasDebugFlag("input"); + if (debugInput) { + trace("Discarding invalid input byte: %02X", + static_cast(input[0])); + } + return 1; + } + if (len > inputSize) { + // Incomplete character. + trace("Incomplete UTF-8 character"); + return -1; + } + appendUtf8Char(records, &input[0], len, false); + return len; +} + +int ConsoleInput::scanMouseInput(std::vector &records, + const char *input, + int inputSize) +{ + MouseRecord record; + const int len = matchMouseRecord(input, inputSize, record); + if (len <= 0) { + return len; + } + + if (isTracingEnabled()) { + static bool debugInput = hasDebugFlag("input"); + if (debugInput) { + trace("mouse input: %s", record.toString().c_str()); + } + } + + const int button = record.flags & 0x03; + INPUT_RECORD newRecord = {0}; + newRecord.EventType = MOUSE_EVENT; + MOUSE_EVENT_RECORD &mer = newRecord.Event.MouseEvent; + + mer.dwMousePosition.X = + m_mouseWindowRect.Left + + std::max(0, std::min(record.coord.X, + m_mouseWindowRect.width() - 1)); + + mer.dwMousePosition.Y = + m_mouseWindowRect.Top + + std::max(0, std::min(record.coord.Y, + m_mouseWindowRect.height() - 1)); + + // The modifier state is neatly independent of everything else. + if (record.flags & 0x04) { mer.dwControlKeyState |= SHIFT_PRESSED; } + if (record.flags & 0x08) { mer.dwControlKeyState |= LEFT_ALT_PRESSED; } + if (record.flags & 0x10) { mer.dwControlKeyState |= LEFT_CTRL_PRESSED; } + + if (record.flags & 0x40) { + // Mouse wheel + mer.dwEventFlags |= MOUSE_WHEELED; + if (button == 0) { + // up + mer.dwButtonState |= 0x00780000; + } else if (button == 1) { + // down + mer.dwButtonState |= 0xff880000; + } else { + // Invalid -- do nothing + return len; + } + } else { + // Ordinary mouse event + if (record.flags & 0x20) { mer.dwEventFlags |= MOUSE_MOVED; } + if (button == 3) { + m_mouseButtonState = 0; + // Potentially advance double-click detection. + m_doubleClick.released = true; + } else { + const DWORD relevantFlag = + (button == 0) ? FROM_LEFT_1ST_BUTTON_PRESSED : + (button == 1) ? FROM_LEFT_2ND_BUTTON_PRESSED : + (button == 2) ? RIGHTMOST_BUTTON_PRESSED : + 0; + ASSERT(relevantFlag != 0); + if (record.release) { + m_mouseButtonState &= ~relevantFlag; + if (relevantFlag == m_doubleClick.button) { + // Potentially advance double-click detection. + m_doubleClick.released = true; + } else { + // End double-click detection. + m_doubleClick = DoubleClickDetection(); + } + } else if ((m_mouseButtonState & relevantFlag) == 0) { + // The button has been newly pressed. + m_mouseButtonState |= relevantFlag; + // Detect a double-click. This code looks for an exact + // coordinate match, which is stricter than what Windows does, + // but Windows has pixel coordinates, and we only have terminal + // coordinates. + if (m_doubleClick.button == relevantFlag && + m_doubleClick.pos == record.coord && + (GetTickCount() - m_doubleClick.tick < + GetDoubleClickTime())) { + // Record a double-click and end double-click detection. + mer.dwEventFlags |= DOUBLE_CLICK; + m_doubleClick = DoubleClickDetection(); + } else { + // Begin double-click detection. + m_doubleClick.button = relevantFlag; + m_doubleClick.pos = record.coord; + m_doubleClick.tick = GetTickCount(); + } + } + } + } + + mer.dwButtonState |= m_mouseButtonState; + + if (m_mouseInputEnabled && !m_quickEditEnabled) { + if (isTracingEnabled()) { + static bool debugInput = hasDebugFlag("input"); + if (debugInput) { + trace("mouse event: %s", mouseEventToString(mer).c_str()); + } + } + + records.push_back(newRecord); + } + + return len; +} + +void ConsoleInput::appendUtf8Char(std::vector &records, + const char *charBuffer, + const int charLen, + const bool terminalAltEscape) +{ + const uint32_t codePoint = decodeUtf8(charBuffer); + if (codePoint == static_cast(-1)) { + static bool debugInput = isTracingEnabled() && hasDebugFlag("input"); + if (debugInput) { + StringBuilder error(64); + error << "Discarding invalid UTF-8 sequence:"; + for (int i = 0; i < charLen; ++i) { + error << ' '; + error << hexOfInt(charBuffer[i]); + } + trace("%s", error.c_str()); + } + return; + } + + const short charScan = codePoint > 0xFFFF ? -1 : VkKeyScan(codePoint); + uint16_t virtualKey = 0; + uint16_t winKeyState = 0; + uint32_t winCodePointDn = codePoint; + uint32_t winCodePointUp = codePoint; + uint16_t vtKeyState = 0; + + if (charScan != -1) { + virtualKey = charScan & 0xFF; + if (charScan & 0x100) { + winKeyState |= SHIFT_PRESSED; + } + if (charScan & 0x200) { + winKeyState |= LEFT_CTRL_PRESSED; + } + if (charScan & 0x400) { + winKeyState |= RIGHT_ALT_PRESSED; + } + if (terminalAltEscape && (winKeyState & LEFT_CTRL_PRESSED)) { + // If the terminal escapes a Ctrl- with Alt, then set the + // codepoint to 0. On the other hand, if a character requires + // AltGr (like U+00B2 on a German layout), then VkKeyScan will + // report both Ctrl and Alt pressed, and we should keep the + // codepoint. See https://github.com/rprichard/winpty/issues/109. + winCodePointDn = 0; + winCodePointUp = 0; + } + } + if (terminalAltEscape) { + winCodePointUp = 0; + winKeyState |= LEFT_ALT_PRESSED; + vtKeyState |= LEFT_ALT_PRESSED; + } + + appendKeyPress(records, virtualKey, + winCodePointDn, winCodePointUp, winKeyState, + codePoint, vtKeyState); +} + +void ConsoleInput::appendKeyPress(std::vector &records, + const uint16_t virtualKey, + const uint32_t winCodePointDn, + const uint32_t winCodePointUp, + const uint16_t winKeyState, + const uint32_t vtCodePoint, + const uint16_t vtKeyState) +{ + const bool ctrl = (winKeyState & LEFT_CTRL_PRESSED) != 0; + const bool leftAlt = (winKeyState & LEFT_ALT_PRESSED) != 0; + const bool rightAlt = (winKeyState & RIGHT_ALT_PRESSED) != 0; + const bool shift = (winKeyState & SHIFT_PRESSED) != 0; + const bool enhanced = (winKeyState & ENHANCED_KEY) != 0; + bool hasDebugInput = false; + + if (isTracingEnabled()) { + static bool debugInput = hasDebugFlag("input"); + if (debugInput) { + hasDebugInput = true; + InputMap::Key key = { virtualKey, winCodePointDn, winKeyState }; + trace("keypress: %s", key.toString().c_str()); + } + } + + if (m_escapeInputEnabled && + (virtualKey == VK_UP || + virtualKey == VK_DOWN || + virtualKey == VK_LEFT || + virtualKey == VK_RIGHT || + virtualKey == VK_HOME || + virtualKey == VK_END) && + !ctrl && !leftAlt && !rightAlt && !shift) { + flushInputRecords(records); + if (hasDebugInput) { + trace("sending keypress to console HWND"); + } + sendKeyMessage(m_console.hwnd(), true, virtualKey); + sendKeyMessage(m_console.hwnd(), false, virtualKey); + return; + } + + uint16_t stepKeyState = 0; + if (ctrl) { + stepKeyState |= LEFT_CTRL_PRESSED; + appendInputRecord(records, TRUE, VK_CONTROL, 0, stepKeyState); + } + if (leftAlt) { + stepKeyState |= LEFT_ALT_PRESSED; + appendInputRecord(records, TRUE, VK_MENU, 0, stepKeyState); + } + if (rightAlt) { + stepKeyState |= RIGHT_ALT_PRESSED; + appendInputRecord(records, TRUE, VK_MENU, 0, stepKeyState | ENHANCED_KEY); + } + if (shift) { + stepKeyState |= SHIFT_PRESSED; + appendInputRecord(records, TRUE, VK_SHIFT, 0, stepKeyState); + } + if (enhanced) { + stepKeyState |= ENHANCED_KEY; + } + if (m_escapeInputEnabled) { + reencodeEscapedKeyPress(records, virtualKey, vtCodePoint, vtKeyState); + } else { + appendCPInputRecords(records, TRUE, virtualKey, winCodePointDn, stepKeyState); + } + appendCPInputRecords(records, FALSE, virtualKey, winCodePointUp, stepKeyState); + if (enhanced) { + stepKeyState &= ~ENHANCED_KEY; + } + if (shift) { + stepKeyState &= ~SHIFT_PRESSED; + appendInputRecord(records, FALSE, VK_SHIFT, 0, stepKeyState); + } + if (rightAlt) { + stepKeyState &= ~RIGHT_ALT_PRESSED; + appendInputRecord(records, FALSE, VK_MENU, 0, stepKeyState | ENHANCED_KEY); + } + if (leftAlt) { + stepKeyState &= ~LEFT_ALT_PRESSED; + appendInputRecord(records, FALSE, VK_MENU, 0, stepKeyState); + } + if (ctrl) { + stepKeyState &= ~LEFT_CTRL_PRESSED; + appendInputRecord(records, FALSE, VK_CONTROL, 0, stepKeyState); + } +} + +void ConsoleInput::appendCPInputRecords(std::vector &records, + BOOL keyDown, + uint16_t virtualKey, + uint32_t codePoint, + uint16_t keyState) +{ + // This behavior really doesn't match that of the Windows console (in + // normal, non-escape-mode). Judging by the copy-and-paste behavior, + // Windows apparently handles everything outside of the keyboard layout by + // first sending a sequence of Alt+KeyPad events, then finally a key-up + // event whose UnicodeChar has the appropriate value. For U+00A2 (CENT + // SIGN): + // + // key: dn rpt=1 scn=56 LAlt-MENU ch=0 + // key: dn rpt=1 scn=79 LAlt-NUMPAD1 ch=0 + // key: up rpt=1 scn=79 LAlt-NUMPAD1 ch=0 + // key: dn rpt=1 scn=76 LAlt-NUMPAD5 ch=0 + // key: up rpt=1 scn=76 LAlt-NUMPAD5 ch=0 + // key: dn rpt=1 scn=76 LAlt-NUMPAD5 ch=0 + // key: up rpt=1 scn=76 LAlt-NUMPAD5 ch=0 + // key: up rpt=1 scn=56 MENU ch=0xa2 + // + // The Alt+155 value matches the encoding of U+00A2 in CP-437. Curiously, + // if I use "chcp 1252" to change the encoding, then copy-and-pasting + // produces Alt+162 instead. (U+00A2 is 162 in CP-1252.) However, typing + // Alt+155 or Alt+162 produce the same characters regardless of console + // code page. (That is, they use CP-437 and yield U+00A2 and U+00F3.) + // + // For characters outside the BMP, Windows repeats the process for both + // UTF-16 code units, e.g, for U+1F300 (CYCLONE): + // + // key: dn rpt=1 scn=56 LAlt-MENU ch=0 + // key: dn rpt=1 scn=77 LAlt-NUMPAD6 ch=0 + // key: up rpt=1 scn=77 LAlt-NUMPAD6 ch=0 + // key: dn rpt=1 scn=81 LAlt-NUMPAD3 ch=0 + // key: up rpt=1 scn=81 LAlt-NUMPAD3 ch=0 + // key: up rpt=1 scn=56 MENU ch=0xd83c + // key: dn rpt=1 scn=56 LAlt-MENU ch=0 + // key: dn rpt=1 scn=77 LAlt-NUMPAD6 ch=0 + // key: up rpt=1 scn=77 LAlt-NUMPAD6 ch=0 + // key: dn rpt=1 scn=81 LAlt-NUMPAD3 ch=0 + // key: up rpt=1 scn=81 LAlt-NUMPAD3 ch=0 + // key: up rpt=1 scn=56 MENU ch=0xdf00 + // + // In this case, it sends Alt+63 twice, which signifies '?'. Apparently + // CMD and Cygwin bash are both able to decode this. + // + // Also note that typing Alt+NNN still works if NumLock is off, e.g.: + // + // key: dn rpt=1 scn=56 LAlt-MENU ch=0 + // key: dn rpt=1 scn=79 LAlt-END ch=0 + // key: up rpt=1 scn=79 LAlt-END ch=0 + // key: dn rpt=1 scn=76 LAlt-CLEAR ch=0 + // key: up rpt=1 scn=76 LAlt-CLEAR ch=0 + // key: dn rpt=1 scn=76 LAlt-CLEAR ch=0 + // key: up rpt=1 scn=76 LAlt-CLEAR ch=0 + // key: up rpt=1 scn=56 MENU ch=0xa2 + // + // Evidently, the Alt+NNN key events are not intended to be decoded to a + // character. Maybe programs are looking for a key-up ALT/MENU event with + // a non-zero character? + + wchar_t ws[2]; + const int wslen = encodeUtf16(ws, codePoint); + + if (wslen == 1) { + appendInputRecord(records, keyDown, virtualKey, ws[0], keyState); + } else if (wslen == 2) { + appendInputRecord(records, keyDown, virtualKey, ws[0], keyState); + appendInputRecord(records, keyDown, virtualKey, ws[1], keyState); + } else { + // This situation isn't that bad, but it should never happen, + // because invalid codepoints shouldn't reach this point. + trace("INTERNAL ERROR: appendInputRecordCP: invalid codePoint: " + "U+%04X", codePoint); + } +} + +void ConsoleInput::appendInputRecord(std::vector &records, + BOOL keyDown, + uint16_t virtualKey, + wchar_t utf16Char, + uint16_t keyState) +{ + INPUT_RECORD ir = {}; + ir.EventType = KEY_EVENT; + ir.Event.KeyEvent.bKeyDown = keyDown; + ir.Event.KeyEvent.wRepeatCount = 1; + ir.Event.KeyEvent.wVirtualKeyCode = virtualKey; + ir.Event.KeyEvent.wVirtualScanCode = + MapVirtualKey(virtualKey, MAPVK_VK_TO_VSC); + ir.Event.KeyEvent.uChar.UnicodeChar = utf16Char; + ir.Event.KeyEvent.dwControlKeyState = keyState; + records.push_back(ir); +} + +DWORD ConsoleInput::inputConsoleMode() +{ + DWORD mode = 0; + if (!GetConsoleMode(m_conin, &mode)) { + trace("GetConsoleMode failed"); + return 0; + } + return mode; +} diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleInput.h b/src/libs/3rdparty/winpty/src/agent/ConsoleInput.h new file mode 100644 index 00000000000..e807d973ba5 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/ConsoleInput.h @@ -0,0 +1,109 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef CONSOLEINPUT_H +#define CONSOLEINPUT_H + +#include +#include + +#include +#include +#include + +#include "Coord.h" +#include "InputMap.h" +#include "SmallRect.h" + +class Win32Console; +class DsrSender; + +class ConsoleInput +{ +public: + ConsoleInput(HANDLE conin, int mouseMode, DsrSender &dsrSender, + Win32Console &console); + void writeInput(const std::string &input); + void flushIncompleteEscapeCode(); + void setMouseWindowRect(SmallRect val) { m_mouseWindowRect = val; } + void updateInputFlags(bool forceTrace=false); + bool shouldActivateTerminalMouse(); + +private: + void doWrite(bool isEof); + void flushInputRecords(std::vector &records); + int scanInput(std::vector &records, + const char *input, + int inputSize, + bool isEof); + int scanMouseInput(std::vector &records, + const char *input, + int inputSize); + void appendUtf8Char(std::vector &records, + const char *charBuffer, + int charLen, + bool terminalAltEscape); + void appendKeyPress(std::vector &records, + uint16_t virtualKey, + uint32_t winCodePointDn, + uint32_t winCodePointUp, + uint16_t winKeyState, + uint32_t vtCodePoint, + uint16_t vtKeyState); + +public: + static void appendCPInputRecords(std::vector &records, + BOOL keyDown, + uint16_t virtualKey, + uint32_t codePoint, + uint16_t keyState); + static void appendInputRecord(std::vector &records, + BOOL keyDown, + uint16_t virtualKey, + wchar_t utf16Char, + uint16_t keyState); + +private: + DWORD inputConsoleMode(); + +private: + Win32Console &m_console; + HANDLE m_conin = nullptr; + int m_mouseMode = 0; + DsrSender &m_dsrSender; + bool m_dsrSent = false; + std::string m_byteQueue; + InputMap m_inputMap; + DWORD m_lastWriteTick = 0; + DWORD m_mouseButtonState = 0; + struct DoubleClickDetection { + DWORD button = 0; + Coord pos; + DWORD tick = 0; + bool released = false; + } m_doubleClick; + bool m_enableExtendedEnabled = false; + bool m_mouseInputEnabled = false; + bool m_quickEditEnabled = false; + bool m_escapeInputEnabled = false; + SmallRect m_mouseWindowRect; +}; + +#endif // CONSOLEINPUT_H diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.cc b/src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.cc new file mode 100644 index 00000000000..b79545eea9c --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.cc @@ -0,0 +1,121 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "ConsoleInputReencoding.h" + +#include "ConsoleInput.h" + +namespace { + +static void outch(std::vector &out, wchar_t ch) { + ConsoleInput::appendInputRecord(out, TRUE, 0, ch, 0); +} + +} // anonymous namespace + +void reencodeEscapedKeyPress( + std::vector &out, + uint16_t virtualKey, + uint32_t codePoint, + uint16_t keyState) { + + struct EscapedKey { + enum { None, Numeric, Letter } kind; + wchar_t content[2]; + }; + + EscapedKey escapeCode = {}; + switch (virtualKey) { + case VK_UP: escapeCode = { EscapedKey::Letter, {'A'} }; break; + case VK_DOWN: escapeCode = { EscapedKey::Letter, {'B'} }; break; + case VK_RIGHT: escapeCode = { EscapedKey::Letter, {'C'} }; break; + case VK_LEFT: escapeCode = { EscapedKey::Letter, {'D'} }; break; + case VK_CLEAR: escapeCode = { EscapedKey::Letter, {'E'} }; break; + case VK_F1: escapeCode = { EscapedKey::Numeric, {'1', '1'} }; break; + case VK_F2: escapeCode = { EscapedKey::Numeric, {'1', '2'} }; break; + case VK_F3: escapeCode = { EscapedKey::Numeric, {'1', '3'} }; break; + case VK_F4: escapeCode = { EscapedKey::Numeric, {'1', '4'} }; break; + case VK_F5: escapeCode = { EscapedKey::Numeric, {'1', '5'} }; break; + case VK_F6: escapeCode = { EscapedKey::Numeric, {'1', '7'} }; break; + case VK_F7: escapeCode = { EscapedKey::Numeric, {'1', '8'} }; break; + case VK_F8: escapeCode = { EscapedKey::Numeric, {'1', '9'} }; break; + case VK_F9: escapeCode = { EscapedKey::Numeric, {'2', '0'} }; break; + case VK_F10: escapeCode = { EscapedKey::Numeric, {'2', '1'} }; break; + case VK_F11: escapeCode = { EscapedKey::Numeric, {'2', '3'} }; break; + case VK_F12: escapeCode = { EscapedKey::Numeric, {'2', '4'} }; break; + case VK_HOME: escapeCode = { EscapedKey::Letter, {'H'} }; break; + case VK_INSERT: escapeCode = { EscapedKey::Numeric, {'2'} }; break; + case VK_DELETE: escapeCode = { EscapedKey::Numeric, {'3'} }; break; + case VK_END: escapeCode = { EscapedKey::Letter, {'F'} }; break; + case VK_PRIOR: escapeCode = { EscapedKey::Numeric, {'5'} }; break; + case VK_NEXT: escapeCode = { EscapedKey::Numeric, {'6'} }; break; + } + if (escapeCode.kind != EscapedKey::None) { + int flags = 0; + if (keyState & SHIFT_PRESSED) { flags |= 0x1; } + if (keyState & LEFT_ALT_PRESSED) { flags |= 0x2; } + if (keyState & LEFT_CTRL_PRESSED) { flags |= 0x4; } + outch(out, L'\x1b'); + outch(out, L'['); + if (escapeCode.kind == EscapedKey::Numeric) { + for (wchar_t ch : escapeCode.content) { + if (ch != L'\0') { + outch(out, ch); + } + } + } else if (flags != 0) { + outch(out, L'1'); + } + if (flags != 0) { + outch(out, L';'); + outch(out, L'1' + flags); + } + if (escapeCode.kind == EscapedKey::Numeric) { + outch(out, L'~'); + } else { + outch(out, escapeCode.content[0]); + } + return; + } + + switch (virtualKey) { + case VK_BACK: + if (keyState & LEFT_ALT_PRESSED) { + outch(out, L'\x1b'); + } + outch(out, L'\x7f'); + return; + case VK_TAB: + if (keyState & SHIFT_PRESSED) { + outch(out, L'\x1b'); + outch(out, L'['); + outch(out, L'Z'); + return; + } + break; + } + + if (codePoint != 0) { + if (keyState & LEFT_ALT_PRESSED) { + outch(out, L'\x1b'); + } + ConsoleInput::appendCPInputRecords(out, TRUE, 0, codePoint, 0); + } +} diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.h b/src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.h new file mode 100644 index 00000000000..63bc006b5a7 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.h @@ -0,0 +1,36 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef AGENT_CONSOLE_INPUT_REENCODING_H +#define AGENT_CONSOLE_INPUT_REENCODING_H + +#include + +#include + +#include + +void reencodeEscapedKeyPress( + std::vector &records, + uint16_t virtualKey, + uint32_t codePoint, + uint16_t keyState); + +#endif // AGENT_CONSOLE_INPUT_REENCODING_H diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleLine.cc b/src/libs/3rdparty/winpty/src/agent/ConsoleLine.cc new file mode 100644 index 00000000000..1d2bcb76859 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/ConsoleLine.cc @@ -0,0 +1,152 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +// +// ConsoleLine +// +// This data structure keep tracks of the previous CHAR_INFO content of an +// output line and determines when a line has changed. Detecting line changes +// is made complicated by terminal resizing. +// + +#include "ConsoleLine.h" + +#include + +#include "../shared/WinptyAssert.h" + +static CHAR_INFO blankChar(WORD attributes) +{ + // N.B.: As long as we write to UnicodeChar rather than AsciiChar, there + // are no padding bytes that could contain uninitialized bytes. This fact + // is important for efficient comparison. + CHAR_INFO ret; + ret.Attributes = attributes; + ret.Char.UnicodeChar = L' '; + return ret; +} + +static bool isLineBlank(const CHAR_INFO *line, int length, WORD attributes) +{ + for (int col = 0; col < length; ++col) { + if (line[col].Attributes != attributes || + line[col].Char.UnicodeChar != L' ') { + return false; + } + } + return true; +} + +static inline bool areLinesEqual( + const CHAR_INFO *line1, + const CHAR_INFO *line2, + int length) +{ + return memcmp(line1, line2, sizeof(CHAR_INFO) * length) == 0; +} + +ConsoleLine::ConsoleLine() : m_prevLength(0) +{ +} + +void ConsoleLine::reset() +{ + m_prevLength = 0; + m_prevData.clear(); +} + +// Determines whether the given line is sufficiently different from the +// previously seen line as to justify reoutputting the line. The function +// also sets the `ConsoleLine` to the given line, exactly as if `setLine` had +// been called. +bool ConsoleLine::detectChangeAndSetLine(const CHAR_INFO *const line, const int newLength) +{ + ASSERT(newLength >= 1); + ASSERT(m_prevLength <= static_cast(m_prevData.size())); + + if (newLength == m_prevLength) { + bool equalLines = areLinesEqual(m_prevData.data(), line, newLength); + if (!equalLines) { + setLine(line, newLength); + } + return !equalLines; + } else { + if (m_prevLength == 0) { + setLine(line, newLength); + return true; + } + + ASSERT(m_prevLength >= 1); + const WORD prevBlank = m_prevData[m_prevLength - 1].Attributes; + const WORD newBlank = line[newLength - 1].Attributes; + + bool equalLines = false; + if (newLength < m_prevLength) { + // The line has become shorter. The lines are equal if the common + // part is equal, and if the newly truncated characters were blank. + equalLines = + areLinesEqual(m_prevData.data(), line, newLength) && + isLineBlank(m_prevData.data() + newLength, + m_prevLength - newLength, + newBlank); + } else { + // + // The line has become longer. The lines are equal if the common + // part is equal, and if both the extra characters and any + // potentially reexposed characters are blank. + // + // Two of the most relevant terminals for winpty--mintty and + // jediterm--don't (currently) erase the obscured content when a + // line is cleared, so we should anticipate its existence when + // making a terminal wider and reoutput the line. See: + // + // * https://github.com/mintty/mintty/issues/480 + // * https://github.com/JetBrains/jediterm/issues/118 + // + ASSERT(newLength > m_prevLength); + equalLines = + areLinesEqual(m_prevData.data(), line, m_prevLength) && + isLineBlank(m_prevData.data() + m_prevLength, + std::min(m_prevData.size(), newLength) - m_prevLength, + prevBlank) && + isLineBlank(line + m_prevLength, + newLength - m_prevLength, + prevBlank); + } + setLine(line, newLength); + return !equalLines; + } +} + +void ConsoleLine::setLine(const CHAR_INFO *const line, const int newLength) +{ + if (static_cast(m_prevData.size()) < newLength) { + m_prevData.resize(newLength); + } + memcpy(m_prevData.data(), line, sizeof(CHAR_INFO) * newLength); + m_prevLength = newLength; +} + +void ConsoleLine::blank(WORD attributes) +{ + m_prevData.resize(1); + m_prevData[0] = blankChar(attributes); + m_prevLength = 1; +} diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleLine.h b/src/libs/3rdparty/winpty/src/agent/ConsoleLine.h new file mode 100644 index 00000000000..802c189c756 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/ConsoleLine.h @@ -0,0 +1,41 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef CONSOLE_LINE_H +#define CONSOLE_LINE_H + +#include + +#include + +class ConsoleLine +{ +public: + ConsoleLine(); + void reset(); + bool detectChangeAndSetLine(const CHAR_INFO *line, int newLength); + void setLine(const CHAR_INFO *line, int newLength); + void blank(WORD attributes); +private: + int m_prevLength; + std::vector m_prevData; +}; + +#endif // CONSOLE_LINE_H diff --git a/src/libs/3rdparty/winpty/src/agent/Coord.h b/src/libs/3rdparty/winpty/src/agent/Coord.h new file mode 100644 index 00000000000..74c98addac6 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/Coord.h @@ -0,0 +1,87 @@ +// Copyright (c) 2011-2012 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef COORD_H +#define COORD_H + +#include + +#include + +#include "../shared/winpty_snprintf.h" + +struct Coord : COORD { + Coord() + { + X = 0; + Y = 0; + } + + Coord(SHORT x, SHORT y) + { + X = x; + Y = y; + } + + Coord(COORD other) + { + *(COORD*)this = other; + } + + Coord(const Coord &other) + { + *(COORD*)this = *(const COORD*)&other; + } + + Coord &operator=(const Coord &other) + { + *(COORD*)this = *(const COORD*)&other; + return *this; + } + + bool operator==(const Coord &other) const + { + return X == other.X && Y == other.Y; + } + + bool operator!=(const Coord &other) const + { + return !(*this == other); + } + + Coord operator+(const Coord &other) const + { + return Coord(X + other.X, Y + other.Y); + } + + bool isEmpty() const + { + return X <= 0 || Y <= 0; + } + + std::string toString() const + { + char ret[32]; + winpty_snprintf(ret, "(%d,%d)", X, Y); + return std::string(ret); + } +}; + +#endif // COORD_H diff --git a/src/libs/3rdparty/winpty/src/agent/DebugShowInput.cc b/src/libs/3rdparty/winpty/src/agent/DebugShowInput.cc new file mode 100644 index 00000000000..191b2e1466a --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/DebugShowInput.cc @@ -0,0 +1,239 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "DebugShowInput.h" + +#include +#include +#include +#include + +#include + +#include "../shared/StringBuilder.h" +#include "InputMap.h" + +namespace { + +struct Flag { + DWORD value; + const char *text; +}; + +static const Flag kButtonStates[] = { + { FROM_LEFT_1ST_BUTTON_PRESSED, "1" }, + { FROM_LEFT_2ND_BUTTON_PRESSED, "2" }, + { FROM_LEFT_3RD_BUTTON_PRESSED, "3" }, + { FROM_LEFT_4TH_BUTTON_PRESSED, "4" }, + { RIGHTMOST_BUTTON_PRESSED, "R" }, +}; + +static const Flag kControlKeyStates[] = { + { CAPSLOCK_ON, "CapsLock" }, + { ENHANCED_KEY, "Enhanced" }, + { LEFT_ALT_PRESSED, "LAlt" }, + { LEFT_CTRL_PRESSED, "LCtrl" }, + { NUMLOCK_ON, "NumLock" }, + { RIGHT_ALT_PRESSED, "RAlt" }, + { RIGHT_CTRL_PRESSED, "RCtrl" }, + { SCROLLLOCK_ON, "ScrollLock" }, + { SHIFT_PRESSED, "Shift" }, +}; + +static const Flag kMouseEventFlags[] = { + { DOUBLE_CLICK, "Double" }, + { 8/*MOUSE_HWHEELED*/, "HWheel" }, + { MOUSE_MOVED, "Move" }, + { MOUSE_WHEELED, "Wheel" }, +}; + +static void writeFlags(StringBuilder &out, DWORD flags, + const char *remainderName, + const Flag *table, size_t tableSize, + char pre, char sep, char post) { + DWORD remaining = flags; + bool wroteSomething = false; + for (size_t i = 0; i < tableSize; ++i) { + const Flag &f = table[i]; + if ((f.value & flags) == f.value) { + if (!wroteSomething && pre != '\0') { + out << pre; + } else if (wroteSomething && sep != '\0') { + out << sep; + } + out << f.text; + wroteSomething = true; + remaining &= ~f.value; + } + } + if (remaining != 0) { + if (!wroteSomething && pre != '\0') { + out << pre; + } else if (wroteSomething && sep != '\0') { + out << sep; + } + out << remainderName << "(0x" << hexOfInt(remaining) << ')'; + wroteSomething = true; + } + if (wroteSomething && post != '\0') { + out << post; + } +} + +template +static void writeFlags(StringBuilder &out, DWORD flags, + const char *remainderName, + const Flag (&table)[n], + char pre, char sep, char post) { + writeFlags(out, flags, remainderName, table, n, pre, sep, post); +} + +} // anonymous namespace + +std::string controlKeyStatePrefix(DWORD controlKeyState) { + StringBuilder sb; + writeFlags(sb, controlKeyState, + "keyState", kControlKeyStates, '\0', '-', '-'); + return sb.str_moved(); +} + +std::string mouseEventToString(const MOUSE_EVENT_RECORD &mer) { + const uint16_t buttons = mer.dwButtonState & 0xFFFF; + const int16_t wheel = mer.dwButtonState >> 16; + StringBuilder sb; + sb << "pos=" << mer.dwMousePosition.X << ',' + << mer.dwMousePosition.Y; + writeFlags(sb, mer.dwControlKeyState, "keyState", kControlKeyStates, ' ', ' ', '\0'); + writeFlags(sb, mer.dwEventFlags, "flags", kMouseEventFlags, ' ', ' ', '\0'); + writeFlags(sb, buttons, "buttons", kButtonStates, ' ', ' ', '\0'); + if (wheel != 0) { + sb << " wheel=" << wheel; + } + return sb.str_moved(); +} + +void debugShowInput(bool enableMouse, bool escapeInput) { + HANDLE conin = GetStdHandle(STD_INPUT_HANDLE); + DWORD origConsoleMode = 0; + if (!GetConsoleMode(conin, &origConsoleMode)) { + fprintf(stderr, "Error: could not read console mode -- " + "is STDIN a console handle?\n"); + exit(1); + } + DWORD restoreConsoleMode = origConsoleMode; + if (enableMouse && !(restoreConsoleMode & ENABLE_EXTENDED_FLAGS)) { + // We need to disable QuickEdit mode, because it blocks mouse events. + // If ENABLE_EXTENDED_FLAGS wasn't originally in the console mode, then + // we have no way of knowning whether QuickEdit or InsertMode are + // currently enabled. Enable them both (eventually), because they're + // sensible defaults. This case shouldn't happen typically. See + // misc/EnableExtendedFlags.txt. + restoreConsoleMode |= ENABLE_EXTENDED_FLAGS; + restoreConsoleMode |= ENABLE_QUICK_EDIT_MODE; + restoreConsoleMode |= ENABLE_INSERT_MODE; + } + DWORD newConsoleMode = restoreConsoleMode; + newConsoleMode &= ~ENABLE_PROCESSED_INPUT; + newConsoleMode &= ~ENABLE_LINE_INPUT; + newConsoleMode &= ~ENABLE_ECHO_INPUT; + newConsoleMode |= ENABLE_WINDOW_INPUT; + if (enableMouse) { + newConsoleMode |= ENABLE_MOUSE_INPUT; + newConsoleMode &= ~ENABLE_QUICK_EDIT_MODE; + } else { + newConsoleMode &= ~ENABLE_MOUSE_INPUT; + } + if (escapeInput) { + // As of this writing (2016-06-05), Microsoft has shipped two preview + // builds of Windows 10 (14316 and 14342) that include a new "Windows + // Subsystem for Linux" that runs Ubuntu in a new subsystem. Running + // bash in this subsystem requires the non-legacy console mode, and the + // console input buffer is put into a special mode where escape + // sequences are written into the console input buffer. This mode is + // enabled with the 0x200 flag, which is as-yet undocumented. + // See https://github.com/rprichard/winpty/issues/82. + newConsoleMode |= 0x200; + } + if (!SetConsoleMode(conin, newConsoleMode)) { + fprintf(stderr, "Error: could not set console mode " + "(0x%x -> 0x%x -> 0x%x)\n", + static_cast(origConsoleMode), + static_cast(newConsoleMode), + static_cast(restoreConsoleMode)); + exit(1); + } + printf("\nPress any keys -- Ctrl-D exits\n\n"); + INPUT_RECORD records[32]; + DWORD actual = 0; + bool finished = false; + while (!finished && + ReadConsoleInputW(conin, records, 32, &actual) && actual >= 1) { + StringBuilder sb; + for (DWORD i = 0; i < actual; ++i) { + const INPUT_RECORD &record = records[i]; + if (record.EventType == KEY_EVENT) { + const KEY_EVENT_RECORD &ker = record.Event.KeyEvent; + InputMap::Key key = { + ker.wVirtualKeyCode, + ker.uChar.UnicodeChar, + static_cast(ker.dwControlKeyState), + }; + sb << "key: " << (ker.bKeyDown ? "dn" : "up") + << " rpt=" << ker.wRepeatCount + << " scn=" << (ker.wVirtualScanCode ? "0x" : "") << hexOfInt(ker.wVirtualScanCode) + << ' ' << key.toString() << '\n'; + if ((ker.dwControlKeyState & + (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) && + ker.wVirtualKeyCode == 'D') { + finished = true; + break; + } else if (ker.wVirtualKeyCode == 0 && + ker.wVirtualScanCode == 0 && + ker.uChar.UnicodeChar == 4) { + // Also look for a zeroed-out Ctrl-D record generated for + // ENABLE_VIRTUAL_TERMINAL_INPUT. + finished = true; + break; + } + } else if (record.EventType == MOUSE_EVENT) { + const MOUSE_EVENT_RECORD &mer = record.Event.MouseEvent; + sb << "mouse: " << mouseEventToString(mer) << '\n'; + } else if (record.EventType == WINDOW_BUFFER_SIZE_EVENT) { + const WINDOW_BUFFER_SIZE_RECORD &wbsr = + record.Event.WindowBufferSizeEvent; + sb << "buffer-resized: dwSize=(" + << wbsr.dwSize.X << ',' + << wbsr.dwSize.Y << ")\n"; + } else if (record.EventType == MENU_EVENT) { + const MENU_EVENT_RECORD &mer = record.Event.MenuEvent; + sb << "menu-event: commandId=0x" + << hexOfInt(mer.dwCommandId) << '\n'; + } else if (record.EventType == FOCUS_EVENT) { + const FOCUS_EVENT_RECORD &fer = record.Event.FocusEvent; + sb << "focus: " << (fer.bSetFocus ? "gained" : "lost") << '\n'; + } + } + + const auto str = sb.str_moved(); + fwrite(str.data(), 1, str.size(), stdout); + fflush(stdout); + } + SetConsoleMode(conin, restoreConsoleMode); +} diff --git a/src/libs/3rdparty/winpty/src/agent/DebugShowInput.h b/src/libs/3rdparty/winpty/src/agent/DebugShowInput.h new file mode 100644 index 00000000000..4fa13604bd4 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/DebugShowInput.h @@ -0,0 +1,32 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef AGENT_DEBUG_SHOW_INPUT_H +#define AGENT_DEBUG_SHOW_INPUT_H + +#include + +#include + +std::string controlKeyStatePrefix(DWORD controlKeyState); +std::string mouseEventToString(const MOUSE_EVENT_RECORD &mer); +void debugShowInput(bool enableMouse, bool escapeInput); + +#endif // AGENT_DEBUG_SHOW_INPUT_H diff --git a/src/libs/3rdparty/winpty/src/agent/DefaultInputMap.cc b/src/libs/3rdparty/winpty/src/agent/DefaultInputMap.cc new file mode 100644 index 00000000000..5e29d98e4ec --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/DefaultInputMap.cc @@ -0,0 +1,422 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "DefaultInputMap.h" + +#include +#include + +#include + +#include "../shared/StringBuilder.h" +#include "../shared/WinptyAssert.h" +#include "InputMap.h" + +#define ESC "\x1B" +#define DIM(x) (sizeof(x) / sizeof((x)[0])) + +namespace { + +struct EscapeEncoding { + bool alt_prefix_allowed; + char prefix; + char id; + int modifiers; + InputMap::Key key; +}; + +// Modifiers. A "modifier" is an integer from 2 to 8 that conveys the status +// of Shift(1), Alt(2), and Ctrl(4). The value is constructed by OR'ing the +// appropriate value for each active modifier, then adding 1. +// +// Details: +// - kBare: expands to: ESC +// - kSemiMod: expands to: ESC ; +// - kBareMod: expands to: ESC +const int kBare = 0x01; +const int kSemiMod = 0x02; +const int kBareMod = 0x04; + +// Numeric escape sequences suffixes: +// - with no flag: accept: ~ +// - kSuffixCtrl: accept: ~ ^ +// - kSuffixShift: accept: ~ $ +// - kSuffixBoth: accept: ~ ^ $ @ +const int kSuffixCtrl = 0x08; +const int kSuffixShift = 0x10; +const int kSuffixBoth = kSuffixCtrl | kSuffixShift; + +static const EscapeEncoding escapeLetterEncodings[] = { + // Conventional arrow keys + // kBareMod: Ubuntu /etc/inputrc and IntelliJ/JediTerm use escapes like: ESC [ n ABCD + { true, '[', 'A', kBare | kBareMod | kSemiMod, { VK_UP, '\0', 0 } }, + { true, '[', 'B', kBare | kBareMod | kSemiMod, { VK_DOWN, '\0', 0 } }, + { true, '[', 'C', kBare | kBareMod | kSemiMod, { VK_RIGHT, '\0', 0 } }, + { true, '[', 'D', kBare | kBareMod | kSemiMod, { VK_LEFT, '\0', 0 } }, + + // putty. putty uses this sequence for Ctrl-Arrow, Shift-Arrow, and + // Ctrl-Shift-Arrow, but I can only decode to one choice, so I'm just + // leaving the modifier off altogether. + { true, 'O', 'A', kBare, { VK_UP, '\0', 0 } }, + { true, 'O', 'B', kBare, { VK_DOWN, '\0', 0 } }, + { true, 'O', 'C', kBare, { VK_RIGHT, '\0', 0 } }, + { true, 'O', 'D', kBare, { VK_LEFT, '\0', 0 } }, + + // rxvt, rxvt-unicode + // Shift-Ctrl-Arrow can't be identified. It's the same as Shift-Arrow. + { true, '[', 'a', kBare, { VK_UP, '\0', SHIFT_PRESSED } }, + { true, '[', 'b', kBare, { VK_DOWN, '\0', SHIFT_PRESSED } }, + { true, '[', 'c', kBare, { VK_RIGHT, '\0', SHIFT_PRESSED } }, + { true, '[', 'd', kBare, { VK_LEFT, '\0', SHIFT_PRESSED } }, + { true, 'O', 'a', kBare, { VK_UP, '\0', LEFT_CTRL_PRESSED } }, + { true, 'O', 'b', kBare, { VK_DOWN, '\0', LEFT_CTRL_PRESSED } }, + { true, 'O', 'c', kBare, { VK_RIGHT, '\0', LEFT_CTRL_PRESSED } }, + { true, 'O', 'd', kBare, { VK_LEFT, '\0', LEFT_CTRL_PRESSED } }, + + // Numpad 5 with NumLock off + // * xterm, mintty, and gnome-terminal use `ESC [ E`. + // * putty, TERM=cygwin, TERM=linux all use `ESC [ G` for 5 + // * putty uses `ESC O G` for Ctrl-5 and Shift-5. Omit the modifier + // as with putty's arrow keys. + // * I never saw modifiers inserted into these escapes, but I think + // it should be completely OK with the CSI escapes. + { true, '[', 'E', kBare | kSemiMod, { VK_CLEAR, '\0', 0 } }, + { true, '[', 'G', kBare | kSemiMod, { VK_CLEAR, '\0', 0 } }, + { true, 'O', 'G', kBare, { VK_CLEAR, '\0', 0 } }, + + // Home/End, letter version + // * gnome-terminal uses `ESC O [HF]`. I never saw it modified. + // kBareMod: IntelliJ/JediTerm uses escapes like: ESC [ n HF + { true, '[', 'H', kBare | kBareMod | kSemiMod, { VK_HOME, '\0', 0 } }, + { true, '[', 'F', kBare | kBareMod | kSemiMod, { VK_END, '\0', 0 } }, + { true, 'O', 'H', kBare, { VK_HOME, '\0', 0 } }, + { true, 'O', 'F', kBare, { VK_END, '\0', 0 } }, + + // F1-F4, letter version (xterm, VTE, konsole) + { true, '[', 'P', kSemiMod, { VK_F1, '\0', 0 } }, + { true, '[', 'Q', kSemiMod, { VK_F2, '\0', 0 } }, + { true, '[', 'R', kSemiMod, { VK_F3, '\0', 0 } }, + { true, '[', 'S', kSemiMod, { VK_F4, '\0', 0 } }, + + // GNOME VTE and Konsole have special encodings for modified F1-F4: + // * [VTE] ESC O 1 ; n [PQRS] + // * [Konsole] ESC O n [PQRS] + { false, 'O', 'P', kBare | kBareMod | kSemiMod, { VK_F1, '\0', 0 } }, + { false, 'O', 'Q', kBare | kBareMod | kSemiMod, { VK_F2, '\0', 0 } }, + { false, 'O', 'R', kBare | kBareMod | kSemiMod, { VK_F3, '\0', 0 } }, + { false, 'O', 'S', kBare | kBareMod | kSemiMod, { VK_F4, '\0', 0 } }, + + // Handle the "application numpad" escape sequences. + // + // Terminals output these codes under various circumstances: + // * rxvt-unicode: numpad, hold down SHIFT + // * rxvt: numpad, by default + // * xterm: numpad, after enabling app-mode using DECPAM (`ESC =`). xterm + // generates `ESC O ` for modified numpad presses, + // necessitating kBareMod. + // * mintty: by combining Ctrl with various keys such as '1' or ','. + // Handling those keys is difficult, because mintty is generating the + // same sequence for Ctrl-1 and Ctrl-NumPadEnd -- should the virtualKey + // be '1' or VK_HOME? + + { true, 'O', 'M', kBare | kBareMod, { VK_RETURN, '\r', 0 } }, + { true, 'O', 'j', kBare | kBareMod, { VK_MULTIPLY, '*', 0 } }, + { true, 'O', 'k', kBare | kBareMod, { VK_ADD, '+', 0 } }, + { true, 'O', 'm', kBare | kBareMod, { VK_SUBTRACT, '-', 0 } }, + { true, 'O', 'n', kBare | kBareMod, { VK_DELETE, '\0', 0 } }, + { true, 'O', 'o', kBare | kBareMod, { VK_DIVIDE, '/', 0 } }, + { true, 'O', 'p', kBare | kBareMod, { VK_INSERT, '\0', 0 } }, + { true, 'O', 'q', kBare | kBareMod, { VK_END, '\0', 0 } }, + { true, 'O', 'r', kBare | kBareMod, { VK_DOWN, '\0', 0 } }, + { true, 'O', 's', kBare | kBareMod, { VK_NEXT, '\0', 0 } }, + { true, 'O', 't', kBare | kBareMod, { VK_LEFT, '\0', 0 } }, + { true, 'O', 'u', kBare | kBareMod, { VK_CLEAR, '\0', 0 } }, + { true, 'O', 'v', kBare | kBareMod, { VK_RIGHT, '\0', 0 } }, + { true, 'O', 'w', kBare | kBareMod, { VK_HOME, '\0', 0 } }, + { true, 'O', 'x', kBare | kBareMod, { VK_UP, '\0', 0 } }, + { true, 'O', 'y', kBare | kBareMod, { VK_PRIOR, '\0', 0 } }, + + { true, '[', 'M', kBare | kSemiMod, { VK_RETURN, '\r', 0 } }, + { true, '[', 'j', kBare | kSemiMod, { VK_MULTIPLY, '*', 0 } }, + { true, '[', 'k', kBare | kSemiMod, { VK_ADD, '+', 0 } }, + { true, '[', 'm', kBare | kSemiMod, { VK_SUBTRACT, '-', 0 } }, + { true, '[', 'n', kBare | kSemiMod, { VK_DELETE, '\0', 0 } }, + { true, '[', 'o', kBare | kSemiMod, { VK_DIVIDE, '/', 0 } }, + { true, '[', 'p', kBare | kSemiMod, { VK_INSERT, '\0', 0 } }, + { true, '[', 'q', kBare | kSemiMod, { VK_END, '\0', 0 } }, + { true, '[', 'r', kBare | kSemiMod, { VK_DOWN, '\0', 0 } }, + { true, '[', 's', kBare | kSemiMod, { VK_NEXT, '\0', 0 } }, + { true, '[', 't', kBare | kSemiMod, { VK_LEFT, '\0', 0 } }, + { true, '[', 'u', kBare | kSemiMod, { VK_CLEAR, '\0', 0 } }, + { true, '[', 'v', kBare | kSemiMod, { VK_RIGHT, '\0', 0 } }, + { true, '[', 'w', kBare | kSemiMod, { VK_HOME, '\0', 0 } }, + { true, '[', 'x', kBare | kSemiMod, { VK_UP, '\0', 0 } }, + { true, '[', 'y', kBare | kSemiMod, { VK_PRIOR, '\0', 0 } }, + + { false, '[', 'Z', kBare, { VK_TAB, '\t', SHIFT_PRESSED } }, +}; + +static const EscapeEncoding escapeNumericEncodings[] = { + { true, '[', 1, kBare | kSemiMod | kSuffixBoth, { VK_HOME, '\0', 0 } }, + { true, '[', 2, kBare | kSemiMod | kSuffixBoth, { VK_INSERT, '\0', 0 } }, + { true, '[', 3, kBare | kSemiMod | kSuffixBoth, { VK_DELETE, '\0', 0 } }, + { true, '[', 4, kBare | kSemiMod | kSuffixBoth, { VK_END, '\0', 0 } }, + { true, '[', 5, kBare | kSemiMod | kSuffixBoth, { VK_PRIOR, '\0', 0 } }, + { true, '[', 6, kBare | kSemiMod | kSuffixBoth, { VK_NEXT, '\0', 0 } }, + { true, '[', 7, kBare | kSemiMod | kSuffixBoth, { VK_HOME, '\0', 0 } }, + { true, '[', 8, kBare | kSemiMod | kSuffixBoth, { VK_END, '\0', 0 } }, + { true, '[', 11, kBare | kSemiMod | kSuffixBoth, { VK_F1, '\0', 0 } }, + { true, '[', 12, kBare | kSemiMod | kSuffixBoth, { VK_F2, '\0', 0 } }, + { true, '[', 13, kBare | kSemiMod | kSuffixBoth, { VK_F3, '\0', 0 } }, + { true, '[', 14, kBare | kSemiMod | kSuffixBoth, { VK_F4, '\0', 0 } }, + { true, '[', 15, kBare | kSemiMod | kSuffixBoth, { VK_F5, '\0', 0 } }, + { true, '[', 17, kBare | kSemiMod | kSuffixBoth, { VK_F6, '\0', 0 } }, + { true, '[', 18, kBare | kSemiMod | kSuffixBoth, { VK_F7, '\0', 0 } }, + { true, '[', 19, kBare | kSemiMod | kSuffixBoth, { VK_F8, '\0', 0 } }, + { true, '[', 20, kBare | kSemiMod | kSuffixBoth, { VK_F9, '\0', 0 } }, + { true, '[', 21, kBare | kSemiMod | kSuffixBoth, { VK_F10, '\0', 0 } }, + { true, '[', 23, kBare | kSemiMod | kSuffixBoth, { VK_F11, '\0', 0 } }, + { true, '[', 24, kBare | kSemiMod | kSuffixBoth, { VK_F12, '\0', 0 } }, + { true, '[', 25, kBare | kSemiMod | kSuffixBoth, { VK_F3, '\0', SHIFT_PRESSED } }, + { true, '[', 26, kBare | kSemiMod | kSuffixBoth, { VK_F4, '\0', SHIFT_PRESSED } }, + { true, '[', 28, kBare | kSemiMod | kSuffixBoth, { VK_F5, '\0', SHIFT_PRESSED } }, + { true, '[', 29, kBare | kSemiMod | kSuffixBoth, { VK_F6, '\0', SHIFT_PRESSED } }, + { true, '[', 31, kBare | kSemiMod | kSuffixBoth, { VK_F7, '\0', SHIFT_PRESSED } }, + { true, '[', 32, kBare | kSemiMod | kSuffixBoth, { VK_F8, '\0', SHIFT_PRESSED } }, + { true, '[', 33, kBare | kSemiMod | kSuffixBoth, { VK_F9, '\0', SHIFT_PRESSED } }, + { true, '[', 34, kBare | kSemiMod | kSuffixBoth, { VK_F10, '\0', SHIFT_PRESSED } }, +}; + +const int kCsiShiftModifier = 1; +const int kCsiAltModifier = 2; +const int kCsiCtrlModifier = 4; + +static inline bool useEnhancedForVirtualKey(uint16_t vk) { + switch (vk) { + case VK_UP: + case VK_DOWN: + case VK_LEFT: + case VK_RIGHT: + case VK_INSERT: + case VK_DELETE: + case VK_HOME: + case VK_END: + case VK_PRIOR: + case VK_NEXT: + return true; + default: + return false; + } +} + +static void addSimpleEntries(InputMap &inputMap) { + struct SimpleEncoding { + const char *encoding; + InputMap::Key key; + }; + + static const SimpleEncoding simpleEncodings[] = { + // Ctrl- seems to be handled OK by the default code path. + + { "\x7F", { VK_BACK, '\x08', 0, } }, + { ESC "\x7F", { VK_BACK, '\x08', LEFT_ALT_PRESSED, } }, + { "\x03", { 'C', '\x03', LEFT_CTRL_PRESSED, } }, + + // Handle special F1-F5 for TERM=linux and TERM=cygwin. + { ESC "[[A", { VK_F1, '\0', 0 } }, + { ESC "[[B", { VK_F2, '\0', 0 } }, + { ESC "[[C", { VK_F3, '\0', 0 } }, + { ESC "[[D", { VK_F4, '\0', 0 } }, + { ESC "[[E", { VK_F5, '\0', 0 } }, + + { ESC ESC "[[A", { VK_F1, '\0', LEFT_ALT_PRESSED } }, + { ESC ESC "[[B", { VK_F2, '\0', LEFT_ALT_PRESSED } }, + { ESC ESC "[[C", { VK_F3, '\0', LEFT_ALT_PRESSED } }, + { ESC ESC "[[D", { VK_F4, '\0', LEFT_ALT_PRESSED } }, + { ESC ESC "[[E", { VK_F5, '\0', LEFT_ALT_PRESSED } }, + }; + + for (size_t i = 0; i < DIM(simpleEncodings); ++i) { + auto k = simpleEncodings[i].key; + if (useEnhancedForVirtualKey(k.virtualKey)) { + k.keyState |= ENHANCED_KEY; + } + inputMap.set(simpleEncodings[i].encoding, + strlen(simpleEncodings[i].encoding), + k); + } +} + +struct ExpandContext { + InputMap &inputMap; + const EscapeEncoding &e; + char *buffer; + char *bufferEnd; +}; + +static inline void setEncoding(const ExpandContext &ctx, char *end, + uint16_t extraKeyState) { + InputMap::Key k = ctx.e.key; + k.keyState |= extraKeyState; + if (k.keyState & LEFT_CTRL_PRESSED) { + switch (k.virtualKey) { + case VK_ADD: + case VK_DIVIDE: + case VK_MULTIPLY: + case VK_SUBTRACT: + k.unicodeChar = '\0'; + break; + case VK_RETURN: + k.unicodeChar = '\n'; + break; + } + } + if (useEnhancedForVirtualKey(k.virtualKey)) { + k.keyState |= ENHANCED_KEY; + } + ctx.inputMap.set(ctx.buffer, end - ctx.buffer, k); +} + +static inline uint16_t keyStateForMod(int mod) { + int ret = 0; + if ((mod - 1) & kCsiShiftModifier) ret |= SHIFT_PRESSED; + if ((mod - 1) & kCsiAltModifier) ret |= LEFT_ALT_PRESSED; + if ((mod - 1) & kCsiCtrlModifier) ret |= LEFT_CTRL_PRESSED; + return ret; +} + +static void expandNumericEncodingSuffix(const ExpandContext &ctx, char *p, + uint16_t extraKeyState) { + ASSERT(p <= ctx.bufferEnd - 1); + { + char *q = p; + *q++ = '~'; + setEncoding(ctx, q, extraKeyState); + } + if (ctx.e.modifiers & kSuffixShift) { + char *q = p; + *q++ = '$'; + setEncoding(ctx, q, extraKeyState | SHIFT_PRESSED); + } + if (ctx.e.modifiers & kSuffixCtrl) { + char *q = p; + *q++ = '^'; + setEncoding(ctx, q, extraKeyState | LEFT_CTRL_PRESSED); + } + if (ctx.e.modifiers & (kSuffixCtrl | kSuffixShift)) { + char *q = p; + *q++ = '@'; + setEncoding(ctx, q, extraKeyState | SHIFT_PRESSED | LEFT_CTRL_PRESSED); + } +} + +template +static inline void expandEncodingAfterAltPrefix( + const ExpandContext &ctx, char *p, uint16_t extraKeyState) { + auto appendId = [&](char *&ptr) { + const auto idstr = decOfInt(ctx.e.id); + ASSERT(ptr <= ctx.bufferEnd - idstr.size()); + std::copy(idstr.data(), idstr.data() + idstr.size(), ptr); + ptr += idstr.size(); + }; + ASSERT(p <= ctx.bufferEnd - 2); + *p++ = '\x1b'; + *p++ = ctx.e.prefix; + if (ctx.e.modifiers & kBare) { + char *q = p; + if (is_numeric) { + appendId(q); + expandNumericEncodingSuffix(ctx, q, extraKeyState); + } else { + ASSERT(q <= ctx.bufferEnd - 1); + *q++ = ctx.e.id; + setEncoding(ctx, q, extraKeyState); + } + } + if (ctx.e.modifiers & kBareMod) { + ASSERT(!is_numeric && "kBareMod is invalid with numeric sequences"); + for (int mod = 2; mod <= 8; ++mod) { + char *q = p; + ASSERT(q <= ctx.bufferEnd - 2); + *q++ = '0' + mod; + *q++ = ctx.e.id; + setEncoding(ctx, q, extraKeyState | keyStateForMod(mod)); + } + } + if (ctx.e.modifiers & kSemiMod) { + for (int mod = 2; mod <= 8; ++mod) { + char *q = p; + if (is_numeric) { + appendId(q); + ASSERT(q <= ctx.bufferEnd - 2); + *q++ = ';'; + *q++ = '0' + mod; + expandNumericEncodingSuffix( + ctx, q, extraKeyState | keyStateForMod(mod)); + } else { + ASSERT(q <= ctx.bufferEnd - 4); + *q++ = '1'; + *q++ = ';'; + *q++ = '0' + mod; + *q++ = ctx.e.id; + setEncoding(ctx, q, extraKeyState | keyStateForMod(mod)); + } + } + } +} + +template +static inline void expandEncoding(const ExpandContext &ctx) { + if (ctx.e.alt_prefix_allowed) { + // For better or for worse, this code expands all of: + // * ESC [ -- + // * ESC ESC [ -- Alt- + // * ESC [ 1 ; 3 -- Alt- + // * ESC ESC [ 1 ; 3 -- Alt- specified twice + // I suspect no terminal actually emits the last one (i.e. specifying + // the Alt modifier using both methods), but I have seen a terminal + // that emitted a prefix ESC for Alt and a non-Alt modifier. + char *p = ctx.buffer; + ASSERT(p <= ctx.bufferEnd - 1); + *p++ = '\x1b'; + expandEncodingAfterAltPrefix(ctx, p, LEFT_ALT_PRESSED); + } + expandEncodingAfterAltPrefix(ctx, ctx.buffer, 0); +} + +template +static void addEscapes(InputMap &inputMap, const EscapeEncoding (&encodings)[N]) { + char buffer[32]; + for (size_t i = 0; i < DIM(encodings); ++i) { + ExpandContext ctx = { + inputMap, encodings[i], + buffer, buffer + sizeof(buffer) + }; + expandEncoding(ctx); + } +} + +} // anonymous namespace + +void addDefaultEntriesToInputMap(InputMap &inputMap) { + addEscapes(inputMap, escapeLetterEncodings); + addEscapes(inputMap, escapeNumericEncodings); + addSimpleEntries(inputMap); +} diff --git a/src/libs/3rdparty/winpty/src/agent/DefaultInputMap.h b/src/libs/3rdparty/winpty/src/agent/DefaultInputMap.h new file mode 100644 index 00000000000..c4b90836788 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/DefaultInputMap.h @@ -0,0 +1,28 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef DEFAULT_INPUT_MAP_H +#define DEFAULT_INPUT_MAP_H + +class InputMap; + +void addDefaultEntriesToInputMap(InputMap &inputMap); + +#endif // DEFAULT_INPUT_MAP_H diff --git a/src/libs/3rdparty/winpty/src/agent/DsrSender.h b/src/libs/3rdparty/winpty/src/agent/DsrSender.h new file mode 100644 index 00000000000..1ec0a97d2e2 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/DsrSender.h @@ -0,0 +1,30 @@ +// Copyright (c) 2011-2012 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef DSRSENDER_H +#define DSRSENDER_H + +class DsrSender +{ +public: + virtual void sendDsr() = 0; +}; + +#endif // DSRSENDER_H diff --git a/src/libs/3rdparty/winpty/src/agent/EventLoop.cc b/src/libs/3rdparty/winpty/src/agent/EventLoop.cc new file mode 100644 index 00000000000..ba5cf18cc8a --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/EventLoop.cc @@ -0,0 +1,99 @@ +// Copyright (c) 2011-2012 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "EventLoop.h" + +#include + +#include "NamedPipe.h" +#include "../shared/DebugClient.h" +#include "../shared/WinptyAssert.h" + +EventLoop::~EventLoop() { + for (NamedPipe *pipe : m_pipes) { + delete pipe; + } + m_pipes.clear(); +} + +// Enter the event loop. Runs until the I/O or timeout handler calls exit(). +void EventLoop::run() +{ + std::vector waitHandles; + DWORD lastTime = GetTickCount(); + while (!m_exiting) { + bool didSomething = false; + + // Attempt to make progress with the pipes. + waitHandles.clear(); + for (size_t i = 0; i < m_pipes.size(); ++i) { + if (m_pipes[i]->serviceIo(&waitHandles)) { + onPipeIo(*m_pipes[i]); + didSomething = true; + } + } + + // Call the timeout if enough time has elapsed. + if (m_pollInterval > 0) { + int elapsed = GetTickCount() - lastTime; + if (elapsed >= m_pollInterval) { + onPollTimeout(); + lastTime = GetTickCount(); + didSomething = true; + } + } + + if (didSomething) + continue; + + // If there's nothing to do, wait. + DWORD timeout = INFINITE; + if (m_pollInterval > 0) + timeout = std::max(0, (int)(lastTime + m_pollInterval - GetTickCount())); + if (waitHandles.size() == 0) { + ASSERT(timeout != INFINITE); + if (timeout > 0) + Sleep(timeout); + } else { + DWORD result = WaitForMultipleObjects(waitHandles.size(), + waitHandles.data(), + FALSE, + timeout); + ASSERT(result != WAIT_FAILED); + } + } +} + +NamedPipe &EventLoop::createNamedPipe() +{ + NamedPipe *ret = new NamedPipe(); + m_pipes.push_back(ret); + return *ret; +} + +void EventLoop::setPollInterval(int ms) +{ + m_pollInterval = ms; +} + +void EventLoop::shutdown() +{ + m_exiting = true; +} diff --git a/src/libs/3rdparty/winpty/src/agent/EventLoop.h b/src/libs/3rdparty/winpty/src/agent/EventLoop.h new file mode 100644 index 00000000000..eddb0f62679 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/EventLoop.h @@ -0,0 +1,47 @@ +// Copyright (c) 2011-2012 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef EVENTLOOP_H +#define EVENTLOOP_H + +#include + +class NamedPipe; + +class EventLoop +{ +public: + virtual ~EventLoop(); + void run(); + +protected: + NamedPipe &createNamedPipe(); + void setPollInterval(int ms); + void shutdown(); + virtual void onPollTimeout() {} + virtual void onPipeIo(NamedPipe &namedPipe) {} + +private: + bool m_exiting = false; + std::vector m_pipes; + int m_pollInterval = 0; +}; + +#endif // EVENTLOOP_H diff --git a/src/libs/3rdparty/winpty/src/agent/InputMap.cc b/src/libs/3rdparty/winpty/src/agent/InputMap.cc new file mode 100644 index 00000000000..b1fbfc2e306 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/InputMap.cc @@ -0,0 +1,246 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "InputMap.h" + +#include +#include +#include +#include + +#include "DebugShowInput.h" +#include "SimplePool.h" +#include "../shared/DebugClient.h" +#include "../shared/UnixCtrlChars.h" +#include "../shared/WinptyAssert.h" +#include "../shared/winpty_snprintf.h" + +namespace { + +static const char *getVirtualKeyString(int virtualKey) +{ + switch (virtualKey) { +#define WINPTY_GVKS_KEY(x) case VK_##x: return #x; + WINPTY_GVKS_KEY(RBUTTON) WINPTY_GVKS_KEY(F9) + WINPTY_GVKS_KEY(CANCEL) WINPTY_GVKS_KEY(F10) + WINPTY_GVKS_KEY(MBUTTON) WINPTY_GVKS_KEY(F11) + WINPTY_GVKS_KEY(XBUTTON1) WINPTY_GVKS_KEY(F12) + WINPTY_GVKS_KEY(XBUTTON2) WINPTY_GVKS_KEY(F13) + WINPTY_GVKS_KEY(BACK) WINPTY_GVKS_KEY(F14) + WINPTY_GVKS_KEY(TAB) WINPTY_GVKS_KEY(F15) + WINPTY_GVKS_KEY(CLEAR) WINPTY_GVKS_KEY(F16) + WINPTY_GVKS_KEY(RETURN) WINPTY_GVKS_KEY(F17) + WINPTY_GVKS_KEY(SHIFT) WINPTY_GVKS_KEY(F18) + WINPTY_GVKS_KEY(CONTROL) WINPTY_GVKS_KEY(F19) + WINPTY_GVKS_KEY(MENU) WINPTY_GVKS_KEY(F20) + WINPTY_GVKS_KEY(PAUSE) WINPTY_GVKS_KEY(F21) + WINPTY_GVKS_KEY(CAPITAL) WINPTY_GVKS_KEY(F22) + WINPTY_GVKS_KEY(HANGUL) WINPTY_GVKS_KEY(F23) + WINPTY_GVKS_KEY(JUNJA) WINPTY_GVKS_KEY(F24) + WINPTY_GVKS_KEY(FINAL) WINPTY_GVKS_KEY(NUMLOCK) + WINPTY_GVKS_KEY(KANJI) WINPTY_GVKS_KEY(SCROLL) + WINPTY_GVKS_KEY(ESCAPE) WINPTY_GVKS_KEY(LSHIFT) + WINPTY_GVKS_KEY(CONVERT) WINPTY_GVKS_KEY(RSHIFT) + WINPTY_GVKS_KEY(NONCONVERT) WINPTY_GVKS_KEY(LCONTROL) + WINPTY_GVKS_KEY(ACCEPT) WINPTY_GVKS_KEY(RCONTROL) + WINPTY_GVKS_KEY(MODECHANGE) WINPTY_GVKS_KEY(LMENU) + WINPTY_GVKS_KEY(SPACE) WINPTY_GVKS_KEY(RMENU) + WINPTY_GVKS_KEY(PRIOR) WINPTY_GVKS_KEY(BROWSER_BACK) + WINPTY_GVKS_KEY(NEXT) WINPTY_GVKS_KEY(BROWSER_FORWARD) + WINPTY_GVKS_KEY(END) WINPTY_GVKS_KEY(BROWSER_REFRESH) + WINPTY_GVKS_KEY(HOME) WINPTY_GVKS_KEY(BROWSER_STOP) + WINPTY_GVKS_KEY(LEFT) WINPTY_GVKS_KEY(BROWSER_SEARCH) + WINPTY_GVKS_KEY(UP) WINPTY_GVKS_KEY(BROWSER_FAVORITES) + WINPTY_GVKS_KEY(RIGHT) WINPTY_GVKS_KEY(BROWSER_HOME) + WINPTY_GVKS_KEY(DOWN) WINPTY_GVKS_KEY(VOLUME_MUTE) + WINPTY_GVKS_KEY(SELECT) WINPTY_GVKS_KEY(VOLUME_DOWN) + WINPTY_GVKS_KEY(PRINT) WINPTY_GVKS_KEY(VOLUME_UP) + WINPTY_GVKS_KEY(EXECUTE) WINPTY_GVKS_KEY(MEDIA_NEXT_TRACK) + WINPTY_GVKS_KEY(SNAPSHOT) WINPTY_GVKS_KEY(MEDIA_PREV_TRACK) + WINPTY_GVKS_KEY(INSERT) WINPTY_GVKS_KEY(MEDIA_STOP) + WINPTY_GVKS_KEY(DELETE) WINPTY_GVKS_KEY(MEDIA_PLAY_PAUSE) + WINPTY_GVKS_KEY(HELP) WINPTY_GVKS_KEY(LAUNCH_MAIL) + WINPTY_GVKS_KEY(LWIN) WINPTY_GVKS_KEY(LAUNCH_MEDIA_SELECT) + WINPTY_GVKS_KEY(RWIN) WINPTY_GVKS_KEY(LAUNCH_APP1) + WINPTY_GVKS_KEY(APPS) WINPTY_GVKS_KEY(LAUNCH_APP2) + WINPTY_GVKS_KEY(SLEEP) WINPTY_GVKS_KEY(OEM_1) + WINPTY_GVKS_KEY(NUMPAD0) WINPTY_GVKS_KEY(OEM_PLUS) + WINPTY_GVKS_KEY(NUMPAD1) WINPTY_GVKS_KEY(OEM_COMMA) + WINPTY_GVKS_KEY(NUMPAD2) WINPTY_GVKS_KEY(OEM_MINUS) + WINPTY_GVKS_KEY(NUMPAD3) WINPTY_GVKS_KEY(OEM_PERIOD) + WINPTY_GVKS_KEY(NUMPAD4) WINPTY_GVKS_KEY(OEM_2) + WINPTY_GVKS_KEY(NUMPAD5) WINPTY_GVKS_KEY(OEM_3) + WINPTY_GVKS_KEY(NUMPAD6) WINPTY_GVKS_KEY(OEM_4) + WINPTY_GVKS_KEY(NUMPAD7) WINPTY_GVKS_KEY(OEM_5) + WINPTY_GVKS_KEY(NUMPAD8) WINPTY_GVKS_KEY(OEM_6) + WINPTY_GVKS_KEY(NUMPAD9) WINPTY_GVKS_KEY(OEM_7) + WINPTY_GVKS_KEY(MULTIPLY) WINPTY_GVKS_KEY(OEM_8) + WINPTY_GVKS_KEY(ADD) WINPTY_GVKS_KEY(OEM_102) + WINPTY_GVKS_KEY(SEPARATOR) WINPTY_GVKS_KEY(PROCESSKEY) + WINPTY_GVKS_KEY(SUBTRACT) WINPTY_GVKS_KEY(PACKET) + WINPTY_GVKS_KEY(DECIMAL) WINPTY_GVKS_KEY(ATTN) + WINPTY_GVKS_KEY(DIVIDE) WINPTY_GVKS_KEY(CRSEL) + WINPTY_GVKS_KEY(F1) WINPTY_GVKS_KEY(EXSEL) + WINPTY_GVKS_KEY(F2) WINPTY_GVKS_KEY(EREOF) + WINPTY_GVKS_KEY(F3) WINPTY_GVKS_KEY(PLAY) + WINPTY_GVKS_KEY(F4) WINPTY_GVKS_KEY(ZOOM) + WINPTY_GVKS_KEY(F5) WINPTY_GVKS_KEY(NONAME) + WINPTY_GVKS_KEY(F6) WINPTY_GVKS_KEY(PA1) + WINPTY_GVKS_KEY(F7) WINPTY_GVKS_KEY(OEM_CLEAR) + WINPTY_GVKS_KEY(F8) +#undef WINPTY_GVKS_KEY + default: return NULL; + } +} + +} // anonymous namespace + +std::string InputMap::Key::toString() const { + std::string ret; + ret += controlKeyStatePrefix(keyState); + char buf[256]; + const char *vkString = getVirtualKeyString(virtualKey); + if (vkString != NULL) { + ret += vkString; + } else if ((virtualKey >= 'A' && virtualKey <= 'Z') || + (virtualKey >= '0' && virtualKey <= '9')) { + ret += static_cast(virtualKey); + } else { + winpty_snprintf(buf, "%#x", virtualKey); + ret += buf; + } + if (unicodeChar >= 32 && unicodeChar <= 126) { + winpty_snprintf(buf, " ch='%c'", + static_cast(unicodeChar)); + } else { + winpty_snprintf(buf, " ch=%#x", + static_cast(unicodeChar)); + } + ret += buf; + return ret; +} + +void InputMap::set(const char *encoding, int encodingLen, const Key &key) { + ASSERT(encodingLen > 0); + setHelper(m_root, encoding, encodingLen, key); +} + +void InputMap::setHelper(Node &node, const char *encoding, int encodingLen, const Key &key) { + if (encodingLen == 0) { + node.key = key; + } else { + setHelper(getOrCreateChild(node, encoding[0]), encoding + 1, encodingLen - 1, key); + } +} + +InputMap::Node &InputMap::getOrCreateChild(Node &node, unsigned char ch) { + Node *ret = getChild(node, ch); + if (ret != NULL) { + return *ret; + } + if (node.childCount < Node::kTinyCount) { + // Maintain sorted order for the sake of the InputMap dumping. + int insertIndex = node.childCount; + for (int i = 0; i < node.childCount; ++i) { + if (ch < node.u.tiny.values[i]) { + insertIndex = i; + break; + } + } + for (int j = node.childCount; j > insertIndex; --j) { + node.u.tiny.values[j] = node.u.tiny.values[j - 1]; + node.u.tiny.children[j] = node.u.tiny.children[j - 1]; + } + node.u.tiny.values[insertIndex] = ch; + node.u.tiny.children[insertIndex] = ret = m_nodePool.alloc(); + ++node.childCount; + return *ret; + } + if (node.childCount == Node::kTinyCount) { + Branch *branch = m_branchPool.alloc(); + for (int i = 0; i < node.childCount; ++i) { + branch->children[node.u.tiny.values[i]] = node.u.tiny.children[i]; + } + node.u.branch = branch; + } + node.u.branch->children[ch] = ret = m_nodePool.alloc(); + ++node.childCount; + return *ret; +} + +// Find the longest matching key and node. +int InputMap::lookupKey(const char *input, int inputSize, + Key &keyOut, bool &incompleteOut) const { + keyOut = kKeyZero; + incompleteOut = false; + + const Node *node = &m_root; + InputMap::Key longestMatch = kKeyZero; + int longestMatchLen = 0; + + for (int i = 0; i < inputSize; ++i) { + unsigned char ch = input[i]; + node = getChild(*node, ch); + if (node == NULL) { + keyOut = longestMatch; + return longestMatchLen; + } else if (node->hasKey()) { + longestMatchLen = i + 1; + longestMatch = node->key; + } + } + keyOut = longestMatch; + incompleteOut = node->childCount > 0; + return longestMatchLen; +} + +void InputMap::dumpInputMap() const { + std::string encoding; + dumpInputMapHelper(m_root, encoding); +} + +void InputMap::dumpInputMapHelper( + const Node &node, std::string &encoding) const { + if (node.hasKey()) { + trace("%s -> %s", + encoding.c_str(), + node.key.toString().c_str()); + } + for (int i = 0; i < 256; ++i) { + const Node *child = getChild(node, i); + if (child != NULL) { + size_t oldSize = encoding.size(); + if (!encoding.empty()) { + encoding.push_back(' '); + } + char ctrlChar = decodeUnixCtrlChar(i); + if (ctrlChar != '\0') { + encoding.push_back('^'); + encoding.push_back(static_cast(ctrlChar)); + } else if (i == ' ') { + encoding.append("' '"); + } else { + encoding.push_back(static_cast(i)); + } + dumpInputMapHelper(*child, encoding); + encoding.resize(oldSize); + } + } +} diff --git a/src/libs/3rdparty/winpty/src/agent/InputMap.h b/src/libs/3rdparty/winpty/src/agent/InputMap.h new file mode 100644 index 00000000000..9a666c79762 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/InputMap.h @@ -0,0 +1,114 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef INPUT_MAP_H +#define INPUT_MAP_H + +#include +#include +#include + +#include + +#include "SimplePool.h" +#include "../shared/WinptyAssert.h" + +class InputMap { +public: + struct Key { + uint16_t virtualKey; + uint32_t unicodeChar; + uint16_t keyState; + + std::string toString() const; + }; + +private: + struct Node; + + struct Branch { + Branch() { + memset(&children, 0, sizeof(children)); + } + + Node *children[256]; + }; + + struct Node { + Node() : childCount(0) { + Key zeroKey = { 0, 0, 0 }; + key = zeroKey; + } + + Key key; + int childCount; + enum { kTinyCount = 8 }; + union { + Branch *branch; + struct { + unsigned char values[kTinyCount]; + Node *children[kTinyCount]; + } tiny; + } u; + + bool hasKey() const { + return key.virtualKey != 0 || key.unicodeChar != 0; + } + }; + +private: + SimplePool m_nodePool; + SimplePool m_branchPool; + Node m_root; + +public: + void set(const char *encoding, int encodingLen, const Key &key); + int lookupKey(const char *input, int inputSize, + Key &keyOut, bool &incompleteOut) const; + void dumpInputMap() const; + +private: + Node *getChild(Node &node, unsigned char ch) { + return const_cast(getChild(static_cast(node), ch)); + } + + const Node *getChild(const Node &node, unsigned char ch) const { + if (node.childCount <= Node::kTinyCount) { + for (int i = 0; i < node.childCount; ++i) { + if (node.u.tiny.values[i] == ch) { + return node.u.tiny.children[i]; + } + } + return NULL; + } else { + return node.u.branch->children[ch]; + } + } + + void setHelper(Node &node, const char *encoding, int encodingLen, const Key &key); + Node &getOrCreateChild(Node &node, unsigned char ch); + void dumpInputMapHelper(const Node &node, std::string &encoding) const; +}; + +const InputMap::Key kKeyZero = { 0, 0, 0 }; + +void dumpInputMap(InputMap &inputMap); + +#endif // INPUT_MAP_H diff --git a/src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.cc b/src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.cc new file mode 100644 index 00000000000..80ac640e488 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.cc @@ -0,0 +1,71 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "LargeConsoleRead.h" + +#include + +#include "../shared/WindowsVersion.h" +#include "Scraper.h" +#include "Win32ConsoleBuffer.h" + +LargeConsoleReadBuffer::LargeConsoleReadBuffer() : + m_rect(0, 0, 0, 0), m_rectWidth(0) +{ +} + +void largeConsoleRead(LargeConsoleReadBuffer &out, + Win32ConsoleBuffer &buffer, + const SmallRect &readArea, + WORD attributesMask) { + ASSERT(readArea.Left >= 0 && + readArea.Top >= 0 && + readArea.Right >= readArea.Left && + readArea.Bottom >= readArea.Top && + readArea.width() <= MAX_CONSOLE_WIDTH); + const size_t count = readArea.width() * readArea.height(); + if (out.m_data.size() < count) { + out.m_data.resize(count); + } + out.m_rect = readArea; + out.m_rectWidth = readArea.width(); + + static const bool useLargeReads = isAtLeastWindows8(); + if (useLargeReads) { + buffer.read(readArea, out.m_data.data()); + } else { + const int maxReadLines = std::max(1, MAX_CONSOLE_WIDTH / readArea.width()); + int curLine = readArea.Top; + while (curLine <= readArea.Bottom) { + const SmallRect subReadArea( + readArea.Left, + curLine, + readArea.width(), + std::min(maxReadLines, readArea.Bottom + 1 - curLine)); + buffer.read(subReadArea, out.lineDataMut(curLine)); + curLine = subReadArea.Bottom + 1; + } + } + if (attributesMask != static_cast(~0)) { + for (size_t i = 0; i < count; ++i) { + out.m_data[i].Attributes &= attributesMask; + } + } +} diff --git a/src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.h b/src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.h new file mode 100644 index 00000000000..1bcf2c0232b --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.h @@ -0,0 +1,68 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef LARGE_CONSOLE_READ_H +#define LARGE_CONSOLE_READ_H + +#include +#include + +#include + +#include "SmallRect.h" +#include "../shared/DebugClient.h" +#include "../shared/WinptyAssert.h" + +class Win32ConsoleBuffer; + +class LargeConsoleReadBuffer { +public: + LargeConsoleReadBuffer(); + const SmallRect &rect() const { return m_rect; } + const CHAR_INFO *lineData(int line) const { + validateLineNumber(line); + return &m_data[(line - m_rect.Top) * m_rectWidth]; + } + +private: + CHAR_INFO *lineDataMut(int line) { + validateLineNumber(line); + return &m_data[(line - m_rect.Top) * m_rectWidth]; + } + + void validateLineNumber(int line) const { + if (line < m_rect.Top || line > m_rect.Bottom) { + trace("Fatal error: LargeConsoleReadBuffer: invalid line %d for " + "read rect %s", line, m_rect.toString().c_str()); + abort(); + } + } + + SmallRect m_rect; + int m_rectWidth; + std::vector m_data; + + friend void largeConsoleRead(LargeConsoleReadBuffer &out, + Win32ConsoleBuffer &buffer, + const SmallRect &readArea, + WORD attributesMask); +}; + +#endif // LARGE_CONSOLE_READ_H diff --git a/src/libs/3rdparty/winpty/src/agent/NamedPipe.cc b/src/libs/3rdparty/winpty/src/agent/NamedPipe.cc new file mode 100644 index 00000000000..64044e6e5d2 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/NamedPipe.cc @@ -0,0 +1,378 @@ +// Copyright (c) 2011-2012 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include + +#include + +#include "EventLoop.h" +#include "NamedPipe.h" +#include "../shared/DebugClient.h" +#include "../shared/StringUtil.h" +#include "../shared/WindowsSecurity.h" +#include "../shared/WinptyAssert.h" + +// Returns true if anything happens (data received, data sent, pipe error). +bool NamedPipe::serviceIo(std::vector *waitHandles) +{ + bool justConnected = false; + const auto kError = ServiceResult::Error; + const auto kProgress = ServiceResult::Progress; + const auto kNoProgress = ServiceResult::NoProgress; + if (m_handle == NULL) { + return false; + } + if (m_connectEvent.get() != nullptr) { + // We're still connecting this server pipe. Check whether the pipe is + // now connected. If it isn't, add the pipe to the list of handles to + // wait on. + DWORD actual = 0; + BOOL success = + GetOverlappedResult(m_handle, &m_connectOver, &actual, FALSE); + if (!success && GetLastError() == ERROR_PIPE_CONNECTED) { + // I'm not sure this can happen, but it's easy to handle if it + // does. + success = TRUE; + } + if (!success) { + ASSERT(GetLastError() == ERROR_IO_INCOMPLETE && + "Pended ConnectNamedPipe call failed"); + waitHandles->push_back(m_connectEvent.get()); + } else { + TRACE("Server pipe [%s] connected", + utf8FromWide(m_name).c_str()); + m_connectEvent.dispose(); + startPipeWorkers(); + justConnected = true; + } + } + const auto readProgress = m_inputWorker ? m_inputWorker->service() : kNoProgress; + const auto writeProgress = m_outputWorker ? m_outputWorker->service() : kNoProgress; + if (readProgress == kError || writeProgress == kError) { + closePipe(); + return true; + } + if (m_inputWorker && m_inputWorker->getWaitEvent() != nullptr) { + waitHandles->push_back(m_inputWorker->getWaitEvent()); + } + if (m_outputWorker && m_outputWorker->getWaitEvent() != nullptr) { + waitHandles->push_back(m_outputWorker->getWaitEvent()); + } + return justConnected + || readProgress == kProgress + || writeProgress == kProgress; +} + +// manual reset, initially unset +static OwnedHandle createEvent() { + HANDLE ret = CreateEventW(nullptr, TRUE, FALSE, nullptr); + ASSERT(ret != nullptr && "CreateEventW failed"); + return OwnedHandle(ret); +} + +NamedPipe::IoWorker::IoWorker(NamedPipe &namedPipe) : + m_namedPipe(namedPipe), + m_event(createEvent()) +{ +} + +NamedPipe::ServiceResult NamedPipe::IoWorker::service() +{ + ServiceResult progress = ServiceResult::NoProgress; + if (m_pending) { + DWORD actual = 0; + BOOL ret = GetOverlappedResult(m_namedPipe.m_handle, &m_over, &actual, FALSE); + if (!ret) { + if (GetLastError() == ERROR_IO_INCOMPLETE) { + // There is a pending I/O. + return progress; + } else { + // Pipe error. + return ServiceResult::Error; + } + } + ResetEvent(m_event.get()); + m_pending = false; + completeIo(actual); + m_currentIoSize = 0; + progress = ServiceResult::Progress; + } + DWORD nextSize = 0; + bool isRead = false; + while (shouldIssueIo(&nextSize, &isRead)) { + m_currentIoSize = nextSize; + DWORD actual = 0; + memset(&m_over, 0, sizeof(m_over)); + m_over.hEvent = m_event.get(); + BOOL ret = isRead + ? ReadFile(m_namedPipe.m_handle, m_buffer, nextSize, &actual, &m_over) + : WriteFile(m_namedPipe.m_handle, m_buffer, nextSize, &actual, &m_over); + if (!ret) { + if (GetLastError() == ERROR_IO_PENDING) { + // There is a pending I/O. + m_pending = true; + return progress; + } else { + // Pipe error. + return ServiceResult::Error; + } + } + ResetEvent(m_event.get()); + completeIo(actual); + m_currentIoSize = 0; + progress = ServiceResult::Progress; + } + return progress; +} + +// This function is called after CancelIo has returned. We need to block until +// the I/O operations have completed, which should happen very quickly. +// https://blogs.msdn.microsoft.com/oldnewthing/20110202-00/?p=11613 +void NamedPipe::IoWorker::waitForCanceledIo() +{ + if (m_pending) { + DWORD actual = 0; + GetOverlappedResult(m_namedPipe.m_handle, &m_over, &actual, TRUE); + m_pending = false; + } +} + +HANDLE NamedPipe::IoWorker::getWaitEvent() +{ + return m_pending ? m_event.get() : NULL; +} + +void NamedPipe::InputWorker::completeIo(DWORD size) +{ + m_namedPipe.m_inQueue.append(m_buffer, size); +} + +bool NamedPipe::InputWorker::shouldIssueIo(DWORD *size, bool *isRead) +{ + *isRead = true; + ASSERT(!m_namedPipe.isConnecting()); + if (m_namedPipe.isClosed()) { + return false; + } else if (m_namedPipe.m_inQueue.size() < m_namedPipe.readBufferSize()) { + *size = kIoSize; + return true; + } else { + return false; + } +} + +void NamedPipe::OutputWorker::completeIo(DWORD size) +{ + ASSERT(size == m_currentIoSize); +} + +bool NamedPipe::OutputWorker::shouldIssueIo(DWORD *size, bool *isRead) +{ + *isRead = false; + if (!m_namedPipe.m_outQueue.empty()) { + auto &out = m_namedPipe.m_outQueue; + const DWORD writeSize = std::min(out.size(), kIoSize); + std::copy(&out[0], &out[writeSize], m_buffer); + out.erase(0, writeSize); + *size = writeSize; + return true; + } else { + return false; + } +} + +DWORD NamedPipe::OutputWorker::getPendingIoSize() +{ + return m_pending ? m_currentIoSize : 0; +} + +void NamedPipe::openServerPipe(LPCWSTR pipeName, OpenMode::t openMode, + int outBufferSize, int inBufferSize) { + ASSERT(isClosed()); + ASSERT((openMode & OpenMode::Duplex) != 0); + const DWORD winOpenMode = + ((openMode & OpenMode::Reading) ? PIPE_ACCESS_INBOUND : 0) + | ((openMode & OpenMode::Writing) ? PIPE_ACCESS_OUTBOUND : 0) + | FILE_FLAG_FIRST_PIPE_INSTANCE + | FILE_FLAG_OVERLAPPED; + const auto sd = createPipeSecurityDescriptorOwnerFullControl(); + ASSERT(sd && "error creating data pipe SECURITY_DESCRIPTOR"); + SECURITY_ATTRIBUTES sa = {}; + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = sd.get(); + HANDLE handle = CreateNamedPipeW( + pipeName, + /*dwOpenMode=*/winOpenMode, + /*dwPipeMode=*/rejectRemoteClientsPipeFlag(), + /*nMaxInstances=*/1, + /*nOutBufferSize=*/outBufferSize, + /*nInBufferSize=*/inBufferSize, + /*nDefaultTimeOut=*/30000, + &sa); + TRACE("opened server pipe [%s], handle == %p", + utf8FromWide(pipeName).c_str(), handle); + ASSERT(handle != INVALID_HANDLE_VALUE && "Could not open server pipe"); + m_name = pipeName; + m_handle = handle; + m_openMode = openMode; + + // Start an asynchronous connection attempt. + m_connectEvent = createEvent(); + memset(&m_connectOver, 0, sizeof(m_connectOver)); + m_connectOver.hEvent = m_connectEvent.get(); + BOOL success = ConnectNamedPipe(m_handle, &m_connectOver); + const auto err = GetLastError(); + if (!success && err == ERROR_PIPE_CONNECTED) { + success = TRUE; + } + if (success) { + TRACE("Server pipe [%s] connected", utf8FromWide(pipeName).c_str()); + m_connectEvent.dispose(); + startPipeWorkers(); + } else if (err != ERROR_IO_PENDING) { + ASSERT(false && "ConnectNamedPipe call failed"); + } +} + +void NamedPipe::connectToServer(LPCWSTR pipeName, OpenMode::t openMode) +{ + ASSERT(isClosed()); + ASSERT((openMode & OpenMode::Duplex) != 0); + HANDLE handle = CreateFileW( + pipeName, + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION | FILE_FLAG_OVERLAPPED, + NULL); + TRACE("connected to [%s], handle == %p", + utf8FromWide(pipeName).c_str(), handle); + ASSERT(handle != INVALID_HANDLE_VALUE && "Could not connect to pipe"); + m_name = pipeName; + m_handle = handle; + m_openMode = openMode; + startPipeWorkers(); +} + +void NamedPipe::startPipeWorkers() +{ + if (m_openMode & OpenMode::Reading) { + m_inputWorker.reset(new InputWorker(*this)); + } + if (m_openMode & OpenMode::Writing) { + m_outputWorker.reset(new OutputWorker(*this)); + } +} + +size_t NamedPipe::bytesToSend() +{ + ASSERT(m_openMode & OpenMode::Writing); + auto ret = m_outQueue.size(); + if (m_outputWorker != NULL) { + ret += m_outputWorker->getPendingIoSize(); + } + return ret; +} + +void NamedPipe::write(const void *data, size_t size) +{ + ASSERT(m_openMode & OpenMode::Writing); + m_outQueue.append(reinterpret_cast(data), size); +} + +void NamedPipe::write(const char *text) +{ + write(text, strlen(text)); +} + +size_t NamedPipe::readBufferSize() +{ + ASSERT(m_openMode & OpenMode::Reading); + return m_readBufferSize; +} + +void NamedPipe::setReadBufferSize(size_t size) +{ + ASSERT(m_openMode & OpenMode::Reading); + m_readBufferSize = size; +} + +size_t NamedPipe::bytesAvailable() +{ + ASSERT(m_openMode & OpenMode::Reading); + return m_inQueue.size(); +} + +size_t NamedPipe::peek(void *data, size_t size) +{ + ASSERT(m_openMode & OpenMode::Reading); + const auto out = reinterpret_cast(data); + const size_t ret = std::min(size, m_inQueue.size()); + std::copy(&m_inQueue[0], &m_inQueue[ret], out); + return ret; +} + +size_t NamedPipe::read(void *data, size_t size) +{ + size_t ret = peek(data, size); + m_inQueue.erase(0, ret); + return ret; +} + +std::string NamedPipe::readToString(size_t size) +{ + ASSERT(m_openMode & OpenMode::Reading); + size_t retSize = std::min(size, m_inQueue.size()); + std::string ret = m_inQueue.substr(0, retSize); + m_inQueue.erase(0, retSize); + return ret; +} + +std::string NamedPipe::readAllToString() +{ + ASSERT(m_openMode & OpenMode::Reading); + std::string ret = m_inQueue; + m_inQueue.clear(); + return ret; +} + +void NamedPipe::closePipe() +{ + if (m_handle == NULL) { + return; + } + CancelIo(m_handle); + if (m_connectEvent.get() != nullptr) { + DWORD actual = 0; + GetOverlappedResult(m_handle, &m_connectOver, &actual, TRUE); + m_connectEvent.dispose(); + } + if (m_inputWorker) { + m_inputWorker->waitForCanceledIo(); + m_inputWorker.reset(); + } + if (m_outputWorker) { + m_outputWorker->waitForCanceledIo(); + m_outputWorker.reset(); + } + CloseHandle(m_handle); + m_handle = NULL; +} diff --git a/src/libs/3rdparty/winpty/src/agent/NamedPipe.h b/src/libs/3rdparty/winpty/src/agent/NamedPipe.h new file mode 100644 index 00000000000..0a4d8b0c75a --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/NamedPipe.h @@ -0,0 +1,125 @@ +// Copyright (c) 2011-2012 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef NAMEDPIPE_H +#define NAMEDPIPE_H + +#include + +#include +#include +#include + +#include "../shared/OwnedHandle.h" + +class EventLoop; + +class NamedPipe +{ +private: + // The EventLoop uses these private members. + friend class EventLoop; + NamedPipe() {} + ~NamedPipe() { closePipe(); } + bool serviceIo(std::vector *waitHandles); + void startPipeWorkers(); + + enum class ServiceResult { NoProgress, Error, Progress }; + +private: + class IoWorker + { + public: + IoWorker(NamedPipe &namedPipe); + virtual ~IoWorker() {} + ServiceResult service(); + void waitForCanceledIo(); + HANDLE getWaitEvent(); + protected: + NamedPipe &m_namedPipe; + bool m_pending = false; + DWORD m_currentIoSize = 0; + OwnedHandle m_event; + OVERLAPPED m_over = {}; + enum { kIoSize = 64 * 1024 }; + char m_buffer[kIoSize]; + virtual void completeIo(DWORD size) = 0; + virtual bool shouldIssueIo(DWORD *size, bool *isRead) = 0; + }; + + class InputWorker : public IoWorker + { + public: + InputWorker(NamedPipe &namedPipe) : IoWorker(namedPipe) {} + protected: + virtual void completeIo(DWORD size) override; + virtual bool shouldIssueIo(DWORD *size, bool *isRead) override; + }; + + class OutputWorker : public IoWorker + { + public: + OutputWorker(NamedPipe &namedPipe) : IoWorker(namedPipe) {} + DWORD getPendingIoSize(); + protected: + virtual void completeIo(DWORD size) override; + virtual bool shouldIssueIo(DWORD *size, bool *isRead) override; + }; + +public: + struct OpenMode { + typedef int t; + enum { None = 0, Reading = 1, Writing = 2, Duplex = 3 }; + }; + + std::wstring name() const { return m_name; } + void openServerPipe(LPCWSTR pipeName, OpenMode::t openMode, + int outBufferSize, int inBufferSize); + void connectToServer(LPCWSTR pipeName, OpenMode::t openMode); + size_t bytesToSend(); + void write(const void *data, size_t size); + void write(const char *text); + size_t readBufferSize(); + void setReadBufferSize(size_t size); + size_t bytesAvailable(); + size_t peek(void *data, size_t size); + size_t read(void *data, size_t size); + std::string readToString(size_t size); + std::string readAllToString(); + void closePipe(); + bool isClosed() { return m_handle == nullptr; } + bool isConnected() { return !isClosed() && !isConnecting(); } + bool isConnecting() { return m_connectEvent.get() != nullptr; } + +private: + // Input/output buffers + std::wstring m_name; + OVERLAPPED m_connectOver = {}; + OwnedHandle m_connectEvent; + OpenMode::t m_openMode = OpenMode::None; + size_t m_readBufferSize = 64 * 1024; + std::string m_inQueue; + std::string m_outQueue; + HANDLE m_handle = nullptr; + std::unique_ptr m_inputWorker; + std::unique_ptr m_outputWorker; +}; + +#endif // NAMEDPIPE_H diff --git a/src/libs/3rdparty/winpty/src/agent/Scraper.cc b/src/libs/3rdparty/winpty/src/agent/Scraper.cc new file mode 100644 index 00000000000..21f9c67104e --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/Scraper.cc @@ -0,0 +1,699 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "Scraper.h" + +#include + +#include + +#include +#include + +#include "../shared/WinptyAssert.h" +#include "../shared/winpty_snprintf.h" + +#include "ConsoleFont.h" +#include "Win32Console.h" +#include "Win32ConsoleBuffer.h" + +namespace { + +template +T constrained(T min, T val, T max) { + ASSERT(min <= max); + return std::min(std::max(min, val), max); +} + +} // anonymous namespace + +Scraper::Scraper( + Win32Console &console, + Win32ConsoleBuffer &buffer, + std::unique_ptr terminal, + Coord initialSize) : + m_console(console), + m_terminal(std::move(terminal)), + m_ptySize(initialSize) +{ + m_consoleBuffer = &buffer; + + resetConsoleTracking(Terminal::OmitClear, buffer.windowRect().top()); + + m_bufferData.resize(BUFFER_LINE_COUNT); + + // Setup the initial screen buffer and window size. + // + // Use SetConsoleWindowInfo to shrink the console window as much as + // possible -- to a 1x1 cell at the top-left. This call always succeeds. + // Prior to the new Windows 10 console, it also actually resizes the GUI + // window to 1x1 cell. Nevertheless, even though the GUI window can + // therefore be narrower than its minimum, calling + // SetConsoleScreenBufferSize with a 1x1 size still fails. + // + // While the small font intends to support large buffers, a user could + // still hit a limit imposed by their monitor width, so cap the new window + // size to GetLargestConsoleWindowSize(). + setSmallFont(buffer.conout(), initialSize.X, m_console.isNewW10()); + buffer.moveWindow(SmallRect(0, 0, 1, 1)); + buffer.resizeBufferRange(Coord(initialSize.X, BUFFER_LINE_COUNT)); + const auto largest = GetLargestConsoleWindowSize(buffer.conout()); + buffer.moveWindow(SmallRect( + 0, 0, + std::min(initialSize.X, largest.X), + std::min(initialSize.Y, largest.Y))); + buffer.setCursorPosition(Coord(0, 0)); + + // For the sake of the color translation heuristic, set the console color + // to LtGray-on-Black. + buffer.setTextAttribute(Win32ConsoleBuffer::kDefaultAttributes); + buffer.clearAllLines(m_consoleBuffer->bufferInfo()); + + m_consoleBuffer = nullptr; +} + +Scraper::~Scraper() +{ +} + +// Whether or not the agent is frozen on entry, it will be frozen on exit. +void Scraper::resizeWindow(Win32ConsoleBuffer &buffer, + Coord newSize, + ConsoleScreenBufferInfo &finalInfoOut) +{ + m_consoleBuffer = &buffer; + m_ptySize = newSize; + syncConsoleContentAndSize(true, finalInfoOut); + m_consoleBuffer = nullptr; +} + +// This function may freeze the agent, but it will not unfreeze it. +void Scraper::scrapeBuffer(Win32ConsoleBuffer &buffer, + ConsoleScreenBufferInfo &finalInfoOut) +{ + m_consoleBuffer = &buffer; + syncConsoleContentAndSize(false, finalInfoOut); + m_consoleBuffer = nullptr; +} + +void Scraper::resetConsoleTracking( + Terminal::SendClearFlag sendClear, int64_t scrapedLineCount) +{ + for (ConsoleLine &line : m_bufferData) { + line.reset(); + } + m_syncRow = -1; + m_scrapedLineCount = scrapedLineCount; + m_scrolledCount = 0; + m_maxBufferedLine = -1; + m_dirtyWindowTop = -1; + m_dirtyLineCount = 0; + m_terminal->reset(sendClear, m_scrapedLineCount); +} + +// Detect window movement. If the window moves down (presumably as a +// result of scrolling), then assume that all screen buffer lines down to +// the bottom of the window are dirty. +void Scraper::markEntireWindowDirty(const SmallRect &windowRect) +{ + m_dirtyLineCount = std::max(m_dirtyLineCount, + windowRect.top() + windowRect.height()); +} + +// Scan the screen buffer and advance the dirty line count when we find +// non-empty lines. +void Scraper::scanForDirtyLines(const SmallRect &windowRect) +{ + const int w = m_readBuffer.rect().width(); + ASSERT(m_dirtyLineCount >= 1); + const CHAR_INFO *const prevLine = + m_readBuffer.lineData(m_dirtyLineCount - 1); + WORD prevLineAttr = prevLine[w - 1].Attributes; + const int stopLine = windowRect.top() + windowRect.height(); + + for (int line = m_dirtyLineCount; line < stopLine; ++line) { + const CHAR_INFO *lineData = m_readBuffer.lineData(line); + for (int col = 0; col < w; ++col) { + const WORD colAttr = lineData[col].Attributes; + if (lineData[col].Char.UnicodeChar != L' ' || + colAttr != prevLineAttr) { + m_dirtyLineCount = line + 1; + break; + } + } + prevLineAttr = lineData[w - 1].Attributes; + } +} + +// Clear lines in the line buffer. The `firstRow` parameter is in +// screen-buffer coordinates. +void Scraper::clearBufferLines( + const int firstRow, + const int count) +{ + ASSERT(!m_directMode); + for (int row = firstRow; row < firstRow + count; ++row) { + const int64_t bufLine = row + m_scrolledCount; + m_maxBufferedLine = std::max(m_maxBufferedLine, bufLine); + m_bufferData[bufLine % BUFFER_LINE_COUNT].blank( + Win32ConsoleBuffer::kDefaultAttributes); + } +} + +static bool cursorInWindow(const ConsoleScreenBufferInfo &info) +{ + return info.dwCursorPosition.Y >= info.srWindow.Top && + info.dwCursorPosition.Y <= info.srWindow.Bottom; +} + +void Scraper::resizeImpl(const ConsoleScreenBufferInfo &origInfo) +{ + ASSERT(m_console.frozen()); + const int cols = m_ptySize.X; + const int rows = m_ptySize.Y; + Coord finalBufferSize; + + { + // + // To accommodate Windows 10, erase all lines up to the top of the + // visible window. It's hard to tell whether this is strictly + // necessary. It ensures that the sync marker won't move downward, + // and it ensures that we won't repeat lines that have already scrolled + // up into the scrollback. + // + // It *is* possible for these blank lines to reappear in the visible + // window (e.g. if the window is made taller), but because we blanked + // the lines in the line buffer, we still don't output them again. + // + const Coord origBufferSize = origInfo.bufferSize(); + const SmallRect origWindowRect = origInfo.windowRect(); + + if (m_directMode) { + for (ConsoleLine &line : m_bufferData) { + line.reset(); + } + } else { + m_consoleBuffer->clearLines(0, origWindowRect.Top, origInfo); + clearBufferLines(0, origWindowRect.Top); + if (m_syncRow != -1) { + createSyncMarker(std::min( + m_syncRow, + BUFFER_LINE_COUNT - rows + - SYNC_MARKER_LEN + - SYNC_MARKER_MARGIN)); + } + } + + finalBufferSize = Coord( + cols, + // If there was previously no scrollback (e.g. a full-screen app + // in direct mode) and we're reducing the window height, then + // reduce the console buffer's height too. + (origWindowRect.height() == origBufferSize.Y) + ? rows + : std::max(rows, origBufferSize.Y)); + + // Reset the console font size. We need to do this before shrinking + // the window, because we might need to make the font bigger to permit + // a smaller window width. Making the font smaller could expand the + // screen buffer, which would hang the conhost process in the + // Windows 10 (10240 build) if the console selection is in progress, so + // unfreeze it first. + m_console.setFrozen(false); + setSmallFont(m_consoleBuffer->conout(), cols, m_console.isNewW10()); + } + + // We try to make the font small enough so that the entire screen buffer + // fits on the monitor, but it can't be guaranteed. + const auto largest = + GetLargestConsoleWindowSize(m_consoleBuffer->conout()); + const short visibleCols = std::min(cols, largest.X); + const short visibleRows = std::min(rows, largest.Y); + + { + // Make the window small enough. We want the console frozen during + // this step so we don't accidentally move the window above the cursor. + m_console.setFrozen(true); + const auto info = m_consoleBuffer->bufferInfo(); + const auto &bufferSize = info.dwSize; + const int tmpWindowWidth = std::min(bufferSize.X, visibleCols); + const int tmpWindowHeight = std::min(bufferSize.Y, visibleRows); + SmallRect tmpWindowRect( + 0, + std::min(bufferSize.Y - tmpWindowHeight, + info.windowRect().Top), + tmpWindowWidth, + tmpWindowHeight); + if (cursorInWindow(info)) { + tmpWindowRect = tmpWindowRect.ensureLineIncluded( + info.cursorPosition().Y); + } + m_consoleBuffer->moveWindow(tmpWindowRect); + } + + { + // Resize the buffer to the final desired size. + m_console.setFrozen(false); + m_consoleBuffer->resizeBufferRange(finalBufferSize); + } + + { + // Expand the window to its full size. + m_console.setFrozen(true); + const ConsoleScreenBufferInfo info = m_consoleBuffer->bufferInfo(); + + SmallRect finalWindowRect( + 0, + std::min(info.bufferSize().Y - visibleRows, + info.windowRect().Top), + visibleCols, + visibleRows); + + // + // Once a line in the screen buffer is "dirty", it should stay visible + // in the console window, so that we continue to update its content in + // the terminal. This code is particularly (only?) necessary on + // Windows 10, where making the buffer wider can rewrap lines and move + // the console window upward. + // + if (!m_directMode && m_dirtyLineCount > finalWindowRect.Bottom + 1) { + // In theory, we avoid ensureLineIncluded, because, a massive + // amount of output could have occurred while the console was + // unfrozen, so that the *top* of the window is now below the + // dirtiest tracked line. + finalWindowRect = SmallRect( + 0, m_dirtyLineCount - visibleRows, + visibleCols, visibleRows); + } + + // Highest priority constraint: ensure that the cursor remains visible. + if (cursorInWindow(info)) { + finalWindowRect = finalWindowRect.ensureLineIncluded( + info.cursorPosition().Y); + } + + m_consoleBuffer->moveWindow(finalWindowRect); + m_dirtyWindowTop = finalWindowRect.Top; + } + + ASSERT(m_console.frozen()); +} + +void Scraper::syncConsoleContentAndSize( + bool forceResize, + ConsoleScreenBufferInfo &finalInfoOut) +{ + // We'll try to avoid freezing the console by reading large chunks (or + // all!) of the screen buffer without otherwise attempting to synchronize + // with the console application. We can only do this on Windows 10 and up + // because: + // - Prior to Windows 8, the size of a ReadConsoleOutputW call was limited + // by the ~32KB RPC buffer. + // - Prior to Windows 10, an out-of-range read region crashes the caller. + // (See misc/WindowsBugCrashReader.cc.) + // + if (!m_console.isNewW10() || forceResize) { + m_console.setFrozen(true); + } + + const ConsoleScreenBufferInfo info = m_consoleBuffer->bufferInfo(); + bool cursorVisible = true; + CONSOLE_CURSOR_INFO cursorInfo = {}; + if (!GetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursorInfo)) { + trace("GetConsoleCursorInfo failed"); + } else { + cursorVisible = cursorInfo.bVisible != 0; + } + + // If an app resizes the buffer height, then we enter "direct mode", where + // we stop trying to track incremental console changes. + const bool newDirectMode = (info.bufferSize().Y != BUFFER_LINE_COUNT); + if (newDirectMode != m_directMode) { + trace("Entering %s mode", newDirectMode ? "direct" : "scrolling"); + resetConsoleTracking(Terminal::SendClear, + newDirectMode ? 0 : info.windowRect().top()); + m_directMode = newDirectMode; + + // When we switch from direct->scrolling mode, make sure the console is + // the right size. + if (!m_directMode) { + m_console.setFrozen(true); + forceResize = true; + } + } + + if (m_directMode) { + // In direct-mode, resizing the console redraws the terminal, so do it + // before scraping. + if (forceResize) { + resizeImpl(info); + } + directScrapeOutput(info, cursorVisible); + } else { + if (!m_console.frozen()) { + if (!scrollingScrapeOutput(info, cursorVisible, true)) { + m_console.setFrozen(true); + } + } + if (m_console.frozen()) { + scrollingScrapeOutput(info, cursorVisible, false); + } + // In scrolling mode, we want to scrape before resizing, because we'll + // erase everything in the console buffer up to the top of the console + // window. + if (forceResize) { + resizeImpl(info); + } + } + + finalInfoOut = forceResize ? m_consoleBuffer->bufferInfo() : info; +} + +// Try to match Windows' behavior w.r.t. to the LVB attribute flags. In some +// situations, Windows ignores the LVB flags on a character cell because of +// backwards compatibility -- apparently some programs set the flags without +// intending to enable reverse-video or underscores. +// +// [rprichard 2017-01-15] I haven't actually noticed any old programs that need +// this treatment -- the motivation for this function comes from the MSDN +// documentation for SetConsoleMode and ENABLE_LVB_GRID_WORLDWIDE. +WORD Scraper::attributesMask() +{ + const auto WINPTY_ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4u; + const auto WINPTY_ENABLE_LVB_GRID_WORLDWIDE = 0x10u; + const auto WINPTY_COMMON_LVB_REVERSE_VIDEO = 0x4000u; + const auto WINPTY_COMMON_LVB_UNDERSCORE = 0x8000u; + + const auto cp = GetConsoleOutputCP(); + const auto isCjk = (cp == 932 || cp == 936 || cp == 949 || cp == 950); + + const DWORD outputMode = [this]{ + ASSERT(this->m_consoleBuffer != nullptr); + DWORD mode = 0; + if (!GetConsoleMode(this->m_consoleBuffer->conout(), &mode)) { + mode = 0; + } + return mode; + }(); + const bool hasEnableLvbGridWorldwide = + (outputMode & WINPTY_ENABLE_LVB_GRID_WORLDWIDE) != 0; + const bool hasEnableVtProcessing = + (outputMode & WINPTY_ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0; + + // The new Windows 10 console (as of 14393) seems to respect + // COMMON_LVB_REVERSE_VIDEO even in CP437 w/o the other enabling modes, so + // try to match that behavior. + const auto isReverseSupported = + isCjk || hasEnableLvbGridWorldwide || hasEnableVtProcessing || m_console.isNewW10(); + const auto isUnderscoreSupported = + isCjk || hasEnableLvbGridWorldwide || hasEnableVtProcessing; + + WORD mask = ~0; + if (!isReverseSupported) { mask &= ~WINPTY_COMMON_LVB_REVERSE_VIDEO; } + if (!isUnderscoreSupported) { mask &= ~WINPTY_COMMON_LVB_UNDERSCORE; } + return mask; +} + +void Scraper::directScrapeOutput(const ConsoleScreenBufferInfo &info, + bool consoleCursorVisible) +{ + const SmallRect windowRect = info.windowRect(); + + const SmallRect scrapeRect( + windowRect.left(), windowRect.top(), + std::min(std::min(windowRect.width(), m_ptySize.X), + MAX_CONSOLE_WIDTH), + std::min(std::min(windowRect.height(), m_ptySize.Y), + BUFFER_LINE_COUNT)); + const int w = scrapeRect.width(); + const int h = scrapeRect.height(); + + const Coord cursor = info.cursorPosition(); + const bool showTerminalCursor = + consoleCursorVisible && scrapeRect.contains(cursor); + const int cursorColumn = !showTerminalCursor ? -1 : cursor.X - scrapeRect.Left; + const int cursorLine = !showTerminalCursor ? -1 : cursor.Y - scrapeRect.Top; + + if (!showTerminalCursor) { + m_terminal->hideTerminalCursor(); + } + + largeConsoleRead(m_readBuffer, *m_consoleBuffer, scrapeRect, attributesMask()); + + for (int line = 0; line < h; ++line) { + const CHAR_INFO *const curLine = + m_readBuffer.lineData(scrapeRect.top() + line); + ConsoleLine &bufLine = m_bufferData[line]; + if (bufLine.detectChangeAndSetLine(curLine, w)) { + const int lineCursorColumn = + line == cursorLine ? cursorColumn : -1; + m_terminal->sendLine(line, curLine, w, lineCursorColumn); + } + } + + if (showTerminalCursor) { + m_terminal->showTerminalCursor(cursorColumn, cursorLine); + } +} + +bool Scraper::scrollingScrapeOutput(const ConsoleScreenBufferInfo &info, + bool consoleCursorVisible, + bool tentative) +{ + const Coord cursor = info.cursorPosition(); + const SmallRect windowRect = info.windowRect(); + + if (m_syncRow != -1) { + // If a synchronizing marker was placed into the history, look for it + // and adjust the scroll count. + const int markerRow = findSyncMarker(); + if (markerRow == -1) { + if (tentative) { + // I *think* it's possible to keep going, but it's simple to + // bail out. + return false; + } + // Something has happened. Reset the terminal. + trace("Sync marker has disappeared -- resetting the terminal" + " (m_syncCounter=%u)", + m_syncCounter); + resetConsoleTracking(Terminal::SendClear, windowRect.top()); + } else if (markerRow != m_syncRow) { + ASSERT(markerRow < m_syncRow); + m_scrolledCount += (m_syncRow - markerRow); + m_syncRow = markerRow; + // If the buffer has scrolled, then the entire window is dirty. + markEntireWindowDirty(windowRect); + } + } + + // Creating a new sync row requires clearing part of the console buffer, so + // avoid doing it if there's already a sync row that's good enough. + const int newSyncRow = + static_cast(windowRect.top()) - SYNC_MARKER_LEN - SYNC_MARKER_MARGIN; + const bool shouldCreateSyncRow = + newSyncRow >= m_syncRow + SYNC_MARKER_LEN + SYNC_MARKER_MARGIN; + if (tentative && shouldCreateSyncRow) { + // It's difficult even in principle to put down a new marker if the + // console can scroll an arbitrarily amount while we're writing. + return false; + } + + // Update the dirty line count: + // - If the window has moved, the entire window is dirty. + // - Everything up to the cursor is dirty. + // - All lines above the window are dirty. + // - Any non-blank lines are dirty. + if (m_dirtyWindowTop != -1) { + if (windowRect.top() > m_dirtyWindowTop) { + // The window has moved down, presumably as a result of scrolling. + markEntireWindowDirty(windowRect); + } else if (windowRect.top() < m_dirtyWindowTop) { + if (tentative) { + // I *think* it's possible to keep going, but it's simple to + // bail out. + return false; + } + // The window has moved upward. This is generally not expected to + // happen, but the CMD/PowerShell CLS command will move the window + // to the top as part of clearing everything else in the console. + trace("Window moved upward -- resetting the terminal" + " (m_syncCounter=%u)", + m_syncCounter); + resetConsoleTracking(Terminal::SendClear, windowRect.top()); + } + } + m_dirtyWindowTop = windowRect.top(); + m_dirtyLineCount = std::max(m_dirtyLineCount, cursor.Y + 1); + m_dirtyLineCount = std::max(m_dirtyLineCount, (int)windowRect.top()); + + // There will be at least one dirty line, because there is a cursor. + ASSERT(m_dirtyLineCount >= 1); + + // The first line to scrape, in virtual line coordinates. + const int64_t firstVirtLine = std::min(m_scrapedLineCount, + windowRect.top() + m_scrolledCount); + + // Read all the data we will need from the console. Start reading with the + // first line to scrape, but adjust the the read area upward to account for + // scanForDirtyLines' need to read the previous attribute. Read to the + // bottom of the window. (It's not clear to me whether the + // m_dirtyLineCount adjustment here is strictly necessary. It isn't + // necessary so long as the cursor is inside the current window.) + const int firstReadLine = std::min(firstVirtLine - m_scrolledCount, + m_dirtyLineCount - 1); + const int stopReadLine = std::max(windowRect.top() + windowRect.height(), + m_dirtyLineCount); + ASSERT(firstReadLine >= 0 && stopReadLine > firstReadLine); + largeConsoleRead(m_readBuffer, + *m_consoleBuffer, + SmallRect(0, firstReadLine, + std::min(info.bufferSize().X, + MAX_CONSOLE_WIDTH), + stopReadLine - firstReadLine), + attributesMask()); + + // If we're scraping the buffer without freezing it, we have to query the + // buffer position data separately from the buffer content, so the two + // could easily be out-of-sync. If they *are* out-of-sync, abort the + // scrape operation and restart it frozen. (We may have updated the + // dirty-line high-water-mark, but that should be OK.) + if (tentative) { + const auto infoCheck = m_consoleBuffer->bufferInfo(); + if (info.bufferSize() != infoCheck.bufferSize() || + info.windowRect() != infoCheck.windowRect() || + info.cursorPosition() != infoCheck.cursorPosition()) { + return false; + } + if (m_syncRow != -1 && m_syncRow != findSyncMarker()) { + return false; + } + } + + if (shouldCreateSyncRow) { + ASSERT(!tentative); + createSyncMarker(newSyncRow); + } + + // At this point, we're finished interacting (reading or writing) the + // console, and we just need to convert our collected data into terminal + // output. + + scanForDirtyLines(windowRect); + + // Note that it's possible for all the lines on the current window to + // be non-dirty. + + // The line to stop scraping at, in virtual line coordinates. + const int64_t stopVirtLine = + std::min(m_dirtyLineCount, windowRect.top() + windowRect.height()) + + m_scrolledCount; + + const bool showTerminalCursor = + consoleCursorVisible && windowRect.contains(cursor); + const int64_t cursorLine = !showTerminalCursor ? -1 : cursor.Y + m_scrolledCount; + const int cursorColumn = !showTerminalCursor ? -1 : cursor.X; + + if (!showTerminalCursor) { + m_terminal->hideTerminalCursor(); + } + + bool sawModifiedLine = false; + + const int w = m_readBuffer.rect().width(); + for (int64_t line = firstVirtLine; line < stopVirtLine; ++line) { + const CHAR_INFO *curLine = + m_readBuffer.lineData(line - m_scrolledCount); + ConsoleLine &bufLine = m_bufferData[line % BUFFER_LINE_COUNT]; + if (line > m_maxBufferedLine) { + m_maxBufferedLine = line; + sawModifiedLine = true; + } + if (sawModifiedLine) { + bufLine.setLine(curLine, w); + } else { + sawModifiedLine = bufLine.detectChangeAndSetLine(curLine, w); + } + if (sawModifiedLine) { + const int lineCursorColumn = + line == cursorLine ? cursorColumn : -1; + m_terminal->sendLine(line, curLine, w, lineCursorColumn); + } + } + + m_scrapedLineCount = windowRect.top() + m_scrolledCount; + + if (showTerminalCursor) { + m_terminal->showTerminalCursor(cursorColumn, cursorLine); + } + + return true; +} + +void Scraper::syncMarkerText(CHAR_INFO (&output)[SYNC_MARKER_LEN]) +{ + // XXX: The marker text generated here could easily collide with ordinary + // console output. Does it make sense to try to avoid the collision? + char str[SYNC_MARKER_LEN + 1]; + winpty_snprintf(str, "S*Y*N*C*%08x", m_syncCounter); + for (int i = 0; i < SYNC_MARKER_LEN; ++i) { + output[i].Char.UnicodeChar = str[i]; + output[i].Attributes = 7; + } +} + +int Scraper::findSyncMarker() +{ + ASSERT(m_syncRow >= 0); + CHAR_INFO marker[SYNC_MARKER_LEN]; + CHAR_INFO column[BUFFER_LINE_COUNT]; + syncMarkerText(marker); + SmallRect rect(0, 0, 1, m_syncRow + SYNC_MARKER_LEN); + m_consoleBuffer->read(rect, column); + int i; + for (i = m_syncRow; i >= 0; --i) { + int j; + for (j = 0; j < SYNC_MARKER_LEN; ++j) { + if (column[i + j].Char.UnicodeChar != marker[j].Char.UnicodeChar) + break; + } + if (j == SYNC_MARKER_LEN) + return i; + } + return -1; +} + +void Scraper::createSyncMarker(int row) +{ + ASSERT(row >= 1); + + // Clear the lines around the marker to ensure that Windows 10's rewrapping + // does not affect the marker. + m_consoleBuffer->clearLines(row - 1, SYNC_MARKER_LEN + 1, + m_consoleBuffer->bufferInfo()); + + // Write a new marker. + m_syncCounter++; + CHAR_INFO marker[SYNC_MARKER_LEN]; + syncMarkerText(marker); + m_syncRow = row; + SmallRect markerRect(0, m_syncRow, 1, SYNC_MARKER_LEN); + m_consoleBuffer->write(markerRect, marker); +} diff --git a/src/libs/3rdparty/winpty/src/agent/Scraper.h b/src/libs/3rdparty/winpty/src/agent/Scraper.h new file mode 100644 index 00000000000..9c10d80aedc --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/Scraper.h @@ -0,0 +1,103 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef AGENT_SCRAPER_H +#define AGENT_SCRAPER_H + +#include + +#include + +#include +#include + +#include "ConsoleLine.h" +#include "Coord.h" +#include "LargeConsoleRead.h" +#include "SmallRect.h" +#include "Terminal.h" + +class ConsoleScreenBufferInfo; +class Win32Console; +class Win32ConsoleBuffer; + +// We must be able to issue a single ReadConsoleOutputW call of +// MAX_CONSOLE_WIDTH characters, and a single read of approximately several +// hundred fewer characters than BUFFER_LINE_COUNT. +const int BUFFER_LINE_COUNT = 3000; +const int MAX_CONSOLE_WIDTH = 2500; +const int MAX_CONSOLE_HEIGHT = 2000; +const int SYNC_MARKER_LEN = 16; +const int SYNC_MARKER_MARGIN = 200; + +class Scraper { +public: + Scraper( + Win32Console &console, + Win32ConsoleBuffer &buffer, + std::unique_ptr terminal, + Coord initialSize); + ~Scraper(); + void resizeWindow(Win32ConsoleBuffer &buffer, + Coord newSize, + ConsoleScreenBufferInfo &finalInfoOut); + void scrapeBuffer(Win32ConsoleBuffer &buffer, + ConsoleScreenBufferInfo &finalInfoOut); + Terminal &terminal() { return *m_terminal; } + +private: + void resetConsoleTracking( + Terminal::SendClearFlag sendClear, int64_t scrapedLineCount); + void markEntireWindowDirty(const SmallRect &windowRect); + void scanForDirtyLines(const SmallRect &windowRect); + void clearBufferLines(int firstRow, int count); + void resizeImpl(const ConsoleScreenBufferInfo &origInfo); + void syncConsoleContentAndSize(bool forceResize, + ConsoleScreenBufferInfo &finalInfoOut); + WORD attributesMask(); + void directScrapeOutput(const ConsoleScreenBufferInfo &info, + bool consoleCursorVisible); + bool scrollingScrapeOutput(const ConsoleScreenBufferInfo &info, + bool consoleCursorVisible, + bool tentative); + void syncMarkerText(CHAR_INFO (&output)[SYNC_MARKER_LEN]); + int findSyncMarker(); + void createSyncMarker(int row); + +private: + Win32Console &m_console; + Win32ConsoleBuffer *m_consoleBuffer = nullptr; + std::unique_ptr m_terminal; + + int m_syncRow = -1; + unsigned int m_syncCounter = 0; + + bool m_directMode = false; + Coord m_ptySize; + int64_t m_scrapedLineCount = 0; + int64_t m_scrolledCount = 0; + int64_t m_maxBufferedLine = -1; + LargeConsoleReadBuffer m_readBuffer; + std::vector m_bufferData; + int m_dirtyWindowTop = -1; + int m_dirtyLineCount = 0; +}; + +#endif // AGENT_SCRAPER_H diff --git a/src/libs/3rdparty/winpty/src/agent/SimplePool.h b/src/libs/3rdparty/winpty/src/agent/SimplePool.h new file mode 100644 index 00000000000..41ff94a90de --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/SimplePool.h @@ -0,0 +1,75 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef SIMPLE_POOL_H +#define SIMPLE_POOL_H + +#include + +#include + +#include "../shared/WinptyAssert.h" + +template +class SimplePool { +public: + ~SimplePool(); + T *alloc(); + void clear(); +private: + struct Chunk { + size_t count; + T *data; + }; + std::vector m_chunks; +}; + +template +SimplePool::~SimplePool() { + clear(); +} + +template +void SimplePool::clear() { + for (size_t ci = 0; ci < m_chunks.size(); ++ci) { + Chunk &chunk = m_chunks[ci]; + for (size_t ti = 0; ti < chunk.count; ++ti) { + chunk.data[ti].~T(); + } + free(chunk.data); + } + m_chunks.clear(); +} + +template +T *SimplePool::alloc() { + if (m_chunks.empty() || m_chunks.back().count == chunkSize) { + T *newData = reinterpret_cast(malloc(sizeof(T) * chunkSize)); + ASSERT(newData != NULL); + Chunk newChunk = { 0, newData }; + m_chunks.push_back(newChunk); + } + Chunk &chunk = m_chunks.back(); + T *ret = &chunk.data[chunk.count++]; + new (ret) T(); + return ret; +} + +#endif // SIMPLE_POOL_H diff --git a/src/libs/3rdparty/winpty/src/agent/SmallRect.h b/src/libs/3rdparty/winpty/src/agent/SmallRect.h new file mode 100644 index 00000000000..bad0b88683a --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/SmallRect.h @@ -0,0 +1,143 @@ +// Copyright (c) 2011-2012 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef SMALLRECT_H +#define SMALLRECT_H + +#include + +#include +#include + +#include "../shared/winpty_snprintf.h" +#include "Coord.h" + +struct SmallRect : SMALL_RECT +{ + SmallRect() + { + Left = Right = Top = Bottom = 0; + } + + SmallRect(SHORT x, SHORT y, SHORT width, SHORT height) + { + Left = x; + Top = y; + Right = x + width - 1; + Bottom = y + height - 1; + } + + SmallRect(const COORD &topLeft, const COORD &size) + { + Left = topLeft.X; + Top = topLeft.Y; + Right = Left + size.X - 1; + Bottom = Top + size.Y - 1; + } + + SmallRect(const SMALL_RECT &other) + { + *(SMALL_RECT*)this = other; + } + + SmallRect(const SmallRect &other) + { + *(SMALL_RECT*)this = *(const SMALL_RECT*)&other; + } + + SmallRect &operator=(const SmallRect &other) + { + *(SMALL_RECT*)this = *(const SMALL_RECT*)&other; + return *this; + } + + bool contains(const SmallRect &other) const + { + return other.Left >= Left && + other.Right <= Right && + other.Top >= Top && + other.Bottom <= Bottom; + } + + bool contains(const Coord &other) const + { + return other.X >= Left && + other.X <= Right && + other.Y >= Top && + other.Y <= Bottom; + } + + SmallRect intersected(const SmallRect &other) const + { + int x1 = std::max(Left, other.Left); + int x2 = std::min(Right, other.Right); + int y1 = std::max(Top, other.Top); + int y2 = std::min(Bottom, other.Bottom); + return SmallRect(x1, + y1, + std::max(0, x2 - x1 + 1), + std::max(0, y2 - y1 + 1)); + } + + SmallRect ensureLineIncluded(SHORT line) const + { + const SHORT h = height(); + if (line < Top) { + return SmallRect(Left, line, width(), h); + } else if (line > Bottom) { + return SmallRect(Left, line - h + 1, width(), h); + } else { + return *this; + } + } + + SHORT top() const { return Top; } + SHORT left() const { return Left; } + SHORT width() const { return Right - Left + 1; } + SHORT height() const { return Bottom - Top + 1; } + void setTop(SHORT top) { Top = top; } + void setLeft(SHORT left) { Left = left; } + void setWidth(SHORT width) { Right = Left + width - 1; } + void setHeight(SHORT height) { Bottom = Top + height - 1; } + Coord size() const { return Coord(width(), height()); } + + bool operator==(const SmallRect &other) const + { + return Left == other.Left && + Right == other.Right && + Top == other.Top && + Bottom == other.Bottom; + } + + bool operator!=(const SmallRect &other) const + { + return !(*this == other); + } + + std::string toString() const + { + char ret[64]; + winpty_snprintf(ret, "(x=%d,y=%d,w=%d,h=%d)", + Left, Top, width(), height()); + return std::string(ret); + } +}; + +#endif // SMALLRECT_H diff --git a/src/libs/3rdparty/winpty/src/agent/Terminal.cc b/src/libs/3rdparty/winpty/src/agent/Terminal.cc new file mode 100644 index 00000000000..afa0a362600 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/Terminal.cc @@ -0,0 +1,535 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "Terminal.h" + +#include +#include +#include + +#include + +#include "NamedPipe.h" +#include "UnicodeEncoding.h" +#include "../shared/DebugClient.h" +#include "../shared/WinptyAssert.h" +#include "../shared/winpty_snprintf.h" + +#define CSI "\x1b[" + +// Work around the old MinGW, which lacks COMMON_LVB_LEADING_BYTE and +// COMMON_LVB_TRAILING_BYTE. +const int WINPTY_COMMON_LVB_LEADING_BYTE = 0x100; +const int WINPTY_COMMON_LVB_TRAILING_BYTE = 0x200; +const int WINPTY_COMMON_LVB_REVERSE_VIDEO = 0x4000; +const int WINPTY_COMMON_LVB_UNDERSCORE = 0x8000; + +const int COLOR_ATTRIBUTE_MASK = + FOREGROUND_BLUE | + FOREGROUND_GREEN | + FOREGROUND_RED | + FOREGROUND_INTENSITY | + BACKGROUND_BLUE | + BACKGROUND_GREEN | + BACKGROUND_RED | + BACKGROUND_INTENSITY | + WINPTY_COMMON_LVB_REVERSE_VIDEO | + WINPTY_COMMON_LVB_UNDERSCORE; + +const int FLAG_RED = 1; +const int FLAG_GREEN = 2; +const int FLAG_BLUE = 4; +const int FLAG_BRIGHT = 8; + +const int BLACK = 0; +const int DKGRAY = BLACK | FLAG_BRIGHT; +const int LTGRAY = FLAG_RED | FLAG_GREEN | FLAG_BLUE; +const int WHITE = LTGRAY | FLAG_BRIGHT; + +// SGR parameters (Select Graphic Rendition) +const int SGR_FORE = 30; +const int SGR_FORE_HI = 90; +const int SGR_BACK = 40; +const int SGR_BACK_HI = 100; + +namespace { + +static void outUInt(std::string &out, unsigned int n) +{ + char buf[32]; + char *pbuf = &buf[32]; + *(--pbuf) = '\0'; + do { + *(--pbuf) = '0' + n % 10; + n /= 10; + } while (n != 0); + out.append(pbuf); +} + +static void outputSetColorSgrParams(std::string &out, bool isFore, int color) +{ + out.push_back(';'); + const int sgrBase = isFore ? SGR_FORE : SGR_BACK; + if (color & FLAG_BRIGHT) { + // Some terminals don't support the 9X/10X "intensive" color parameters + // (e.g. the Eclipse TM terminal as of this writing). Those terminals + // will quietly ignore a 9X/10X code, and the other terminals will + // ignore a 3X/4X code if it's followed by a 9X/10X code. Therefore, + // output a 3X/4X code as a fallback, then override it. + const int colorBase = color & ~FLAG_BRIGHT; + outUInt(out, sgrBase + colorBase); + out.push_back(';'); + outUInt(out, sgrBase + (SGR_FORE_HI - SGR_FORE) + colorBase); + } else { + outUInt(out, sgrBase + color); + } +} + +static void outputSetColor(std::string &out, int color) +{ + int fore = 0; + int back = 0; + if (color & FOREGROUND_RED) fore |= FLAG_RED; + if (color & FOREGROUND_GREEN) fore |= FLAG_GREEN; + if (color & FOREGROUND_BLUE) fore |= FLAG_BLUE; + if (color & FOREGROUND_INTENSITY) fore |= FLAG_BRIGHT; + if (color & BACKGROUND_RED) back |= FLAG_RED; + if (color & BACKGROUND_GREEN) back |= FLAG_GREEN; + if (color & BACKGROUND_BLUE) back |= FLAG_BLUE; + if (color & BACKGROUND_INTENSITY) back |= FLAG_BRIGHT; + + if (color & WINPTY_COMMON_LVB_REVERSE_VIDEO) { + // n.b.: The COMMON_LVB_REVERSE_VIDEO flag also swaps + // FOREGROUND_INTENSITY and BACKGROUND_INTENSITY. Tested on + // Windows 10 v14393. + std::swap(fore, back); + } + + // Translate the fore/back colors into terminal escape codes using + // a heuristic that works OK with common white-on-black or + // black-on-white color schemes. We don't know which color scheme + // the terminal is using. It is ugly to force white-on-black text + // on a black-on-white terminal, and it's even ugly to force the + // matching scheme. It's probably relevant that the default + // fore/back terminal colors frequently do not match any of the 16 + // palette colors. + + // Typical default terminal color schemes (according to palette, + // when possible): + // - mintty: LtGray-on-Black(A) + // - putty: LtGray-on-Black(A) + // - xterm: LtGray-on-Black(A) + // - Konsole: LtGray-on-Black(A) + // - JediTerm/JetBrains: Black-on-White(B) + // - rxvt: Black-on-White(B) + + // If the background is the default color (black), then it will + // map to Black(A) or White(B). If we translate White to White, + // then a Black background and a White background in the console + // are both White with (B). Therefore, we should translate White + // using SGR 7 (Invert). The typical finished mapping table for + // background grayscale colors is: + // + // (A) White => LtGray(fore) + // (A) Black => Black(back) + // (A) LtGray => LtGray + // (A) DkGray => DkGray + // + // (B) White => Black(fore) + // (B) Black => White(back) + // (B) LtGray => LtGray + // (B) DkGray => DkGray + // + + out.append(CSI "0"); + if (back == BLACK) { + if (fore == LTGRAY) { + // The "default" foreground color. Use the terminal's + // default colors. + } else if (fore == WHITE) { + // Sending the literal color white would behave poorly if + // the terminal were black-on-white. Sending Bold is not + // guaranteed to alter the color, but it will make the text + // visually distinct, so do that instead. + out.append(";1"); + } else if (fore == DKGRAY) { + // Set the foreground color to DkGray(90) with a fallback + // of LtGray(37) for terminals that don't handle the 9X SGR + // parameters (e.g. Eclipse's TM Terminal as of this + // writing). + out.append(";37;90"); + } else { + outputSetColorSgrParams(out, true, fore); + } + } else if (back == WHITE) { + // Set the background color using Invert on the default + // foreground color, and set the foreground color by setting a + // background color. + + // Use the terminal's inverted colors. + out.append(";7"); + if (fore == LTGRAY || fore == BLACK) { + // We're likely mapping Console White to terminal LtGray or + // Black. If they are the Console foreground color, then + // don't set a terminal foreground color to avoid creating + // invisible text. + } else { + outputSetColorSgrParams(out, false, fore); + } + } else { + // Set the foreground and background to match exactly that in + // the Windows console. + outputSetColorSgrParams(out, true, fore); + outputSetColorSgrParams(out, false, back); + } + if (fore == back) { + // The foreground and background colors are exactly equal, so + // attempt to hide the text using the Conceal SGR parameter, + // which some terminals support. + out.append(";8"); + } + if (color & WINPTY_COMMON_LVB_UNDERSCORE) { + out.append(";4"); + } + out.push_back('m'); +} + +static inline unsigned int fixSpecialCharacters(unsigned int ch) +{ + if (ch <= 0x1b) { + switch (ch) { + // The Windows Console has a popup window (e.g. that appears with + // F7) that is sometimes bordered with box-drawing characters. + // With the Japanese and Korean system locales (CP932 and CP949), + // the UnicodeChar values for the box-drawing characters are 1 + // through 6. Detect this and map the values to the correct + // Unicode values. + // + // N.B. In the English locale, the UnicodeChar values are correct, + // and they identify single-line characters rather than + // double-line. In the Chinese Simplified and Traditional locales, + // the popups use ASCII characters instead. + case 1: return 0x2554; // BOX DRAWINGS DOUBLE DOWN AND RIGHT + case 2: return 0x2557; // BOX DRAWINGS DOUBLE DOWN AND LEFT + case 3: return 0x255A; // BOX DRAWINGS DOUBLE UP AND RIGHT + case 4: return 0x255D; // BOX DRAWINGS DOUBLE UP AND LEFT + case 5: return 0x2551; // BOX DRAWINGS DOUBLE VERTICAL + case 6: return 0x2550; // BOX DRAWINGS DOUBLE HORIZONTAL + + // Convert an escape character to some other character. This + // conversion only applies to console cells containing an escape + // character. In newer versions of Windows 10 (e.g. 10.0.10586), + // the non-legacy console recognizes escape sequences in + // WriteConsole and interprets them without writing them to the + // cells of the screen buffer. In that case, the conversion here + // does not apply. + case 0x1b: return '?'; + } + } + return ch; +} + +static inline bool isFullWidthCharacter(const CHAR_INFO *data, int width) +{ + if (width < 2) { + return false; + } + return + (data[0].Attributes & WINPTY_COMMON_LVB_LEADING_BYTE) && + (data[1].Attributes & WINPTY_COMMON_LVB_TRAILING_BYTE) && + data[0].Char.UnicodeChar == data[1].Char.UnicodeChar; +} + +// Scan to find a single Unicode Scalar Value. Full-width characters occupy +// two console cells, and this code also tries to handle UTF-16 surrogate +// pairs. +// +// Windows expands at least some wide characters outside the Basic +// Multilingual Plane into four cells, such as U+20000: +// 1. 0xD840, attr=0x107 +// 2. 0xD840, attr=0x207 +// 3. 0xDC00, attr=0x107 +// 4. 0xDC00, attr=0x207 +// Even in the Traditional Chinese locale on Windows 10, this text is rendered +// as two boxes, but if those boxes are copied-and-pasted, the character is +// copied correctly. +static inline void scanUnicodeScalarValue( + const CHAR_INFO *data, int width, + int &outCellCount, unsigned int &outCharValue) +{ + ASSERT(width >= 1); + + const int w1 = isFullWidthCharacter(data, width) ? 2 : 1; + const wchar_t c1 = data[0].Char.UnicodeChar; + + if ((c1 & 0xF800) == 0xD800) { + // The first cell is either a leading or trailing surrogate pair. + if ((c1 & 0xFC00) != 0xD800 || + width <= w1 || + ((data[w1].Char.UnicodeChar & 0xFC00) != 0xDC00)) { + // Invalid surrogate pair + outCellCount = w1; + outCharValue = '?'; + } else { + // Valid surrogate pair + outCellCount = w1 + (isFullWidthCharacter(&data[w1], width - w1) ? 2 : 1); + outCharValue = decodeSurrogatePair(c1, data[w1].Char.UnicodeChar); + } + } else { + outCellCount = w1; + outCharValue = c1; + } +} + +} // anonymous namespace + +void Terminal::reset(SendClearFlag sendClearFirst, int64_t newLine) +{ + if (sendClearFirst == SendClear && !m_plainMode) { + // 0m ==> reset SGR parameters + // 1;1H ==> move cursor to top-left position + // 2J ==> clear the entire screen + m_output.write(CSI "0m" CSI "1;1H" CSI "2J"); + } + m_remoteLine = newLine; + m_remoteColumn = 0; + m_lineData.clear(); + m_cursorHidden = false; + m_remoteColor = -1; +} + +void Terminal::sendLine(int64_t line, const CHAR_INFO *lineData, int width, + int cursorColumn) +{ + ASSERT(width >= 1); + + moveTerminalToLine(line); + + // If possible, see if we can append to what we've already output for this + // line. + if (m_lineDataValid) { + ASSERT(m_lineData.size() == static_cast(m_remoteColumn)); + if (m_remoteColumn > 0) { + // In normal mode, if m_lineData.size() equals `width`, then we + // will have trouble outputing the "erase rest of line" command, + // which must be output before reaching the end of the line. In + // plain mode, we don't output that command, so we're OK with a + // full line. + bool okWidth = false; + if (m_plainMode) { + okWidth = static_cast(width) >= m_lineData.size(); + } else { + okWidth = static_cast(width) > m_lineData.size(); + } + if (!okWidth || + memcmp(m_lineData.data(), lineData, + sizeof(CHAR_INFO) * m_lineData.size()) != 0) { + m_lineDataValid = false; + } + } + } + if (!m_lineDataValid) { + // We can't reuse, so we must reset this line. + hideTerminalCursor(); + if (m_plainMode) { + // We can't backtrack, so repeat this line. + m_output.write("\r\n"); + } else { + m_output.write("\r"); + } + m_lineDataValid = true; + m_lineData.clear(); + m_remoteColumn = 0; + } + + std::string &termLine = m_termLineWorkingBuffer; + termLine.clear(); + size_t trimmedLineLength = 0; + int trimmedCellCount = m_lineData.size(); + bool alreadyErasedLine = false; + + int cellCount = 1; + for (int i = m_lineData.size(); i < width; i += cellCount) { + if (m_outputColor) { + int color = lineData[i].Attributes & COLOR_ATTRIBUTE_MASK; + if (color != m_remoteColor) { + outputSetColor(termLine, color); + trimmedLineLength = termLine.size(); + m_remoteColor = color; + + // All the cells just up to this color change will be output. + trimmedCellCount = i; + } + } + unsigned int ch; + scanUnicodeScalarValue(&lineData[i], width - i, cellCount, ch); + if (ch == ' ') { + // Tentatively add this space character. We'll only output it if + // we see something interesting after it. + termLine.push_back(' '); + } else { + if (i + cellCount == width) { + // We'd like to erase the line after outputting all non-blank + // characters, but this doesn't work if the last cell in the + // line is non-blank. At the point, the cursor is positioned + // just past the end of the line, but in many terminals, + // issuing a CSI 0K at that point also erases the last cell in + // the line. Work around this behavior by issuing the erase + // one character early in that case. + if (!m_plainMode) { + termLine.append(CSI "0K"); // Erase from cursor to EOL + } + alreadyErasedLine = true; + } + ch = fixSpecialCharacters(ch); + char enc[4]; + int enclen = encodeUtf8(enc, ch); + if (enclen == 0) { + enc[0] = '?'; + enclen = 1; + } + termLine.append(enc, enclen); + trimmedLineLength = termLine.size(); + + // All the cells up to and including this cell will be output. + trimmedCellCount = i + cellCount; + } + } + + if (cursorColumn != -1 && trimmedCellCount > cursorColumn) { + // The line content would run past the cursor, so hide it before we + // output. + hideTerminalCursor(); + } + + m_output.write(termLine.data(), trimmedLineLength); + if (!alreadyErasedLine && !m_plainMode) { + m_output.write(CSI "0K"); // Erase from cursor to EOL + } + + ASSERT(trimmedCellCount <= width); + m_lineData.insert(m_lineData.end(), + &lineData[m_lineData.size()], + &lineData[trimmedCellCount]); + m_remoteColumn = trimmedCellCount; +} + +void Terminal::showTerminalCursor(int column, int64_t line) +{ + moveTerminalToLine(line); + if (!m_plainMode) { + if (m_remoteColumn != column) { + char buffer[32]; + winpty_snprintf(buffer, CSI "%dG", column + 1); + m_output.write(buffer); + m_lineDataValid = (column == 0); + m_lineData.clear(); + m_remoteColumn = column; + } + if (m_cursorHidden) { + m_output.write(CSI "?25h"); + m_cursorHidden = false; + } + } +} + +void Terminal::hideTerminalCursor() +{ + if (!m_plainMode) { + if (m_cursorHidden) { + return; + } + m_output.write(CSI "?25l"); + m_cursorHidden = true; + } +} + +void Terminal::moveTerminalToLine(int64_t line) +{ + if (line == m_remoteLine) { + return; + } + + // Do not use CPL or CNL. Konsole 2.5.4 does not support Cursor Previous + // Line (CPL) -- there are "Undecodable sequence" errors. gnome-terminal + // 2.32.0 does handle it. Cursor Next Line (CNL) does nothing if the + // cursor is on the last line already. + + hideTerminalCursor(); + + if (line < m_remoteLine) { + if (m_plainMode) { + // We can't backtrack, so instead repeat the lines again. + m_output.write("\r\n"); + m_remoteLine = line; + } else { + // Backtrack and overwrite previous lines. + // CUrsor Up (CUU) + char buffer[32]; + winpty_snprintf(buffer, "\r" CSI "%uA", + static_cast(m_remoteLine - line)); + m_output.write(buffer); + m_remoteLine = line; + } + } else if (line > m_remoteLine) { + while (line > m_remoteLine) { + m_output.write("\r\n"); + m_remoteLine++; + } + } + + m_lineDataValid = true; + m_lineData.clear(); + m_remoteColumn = 0; +} + +void Terminal::enableMouseMode(bool enabled) +{ + if (m_mouseModeEnabled == enabled || m_plainMode) { + return; + } + m_mouseModeEnabled = enabled; + if (enabled) { + // Start by disabling UTF-8 coordinate mode (1005), just in case we + // have a terminal that does not support 1006/1015 modes, and 1005 + // happens to be enabled. The UTF-8 coordinates can't be unambiguously + // decoded. + // + // Enable basic mouse support first (1000), then try to switch to + // button-move mode (1002), then try full mouse-move mode (1003). + // Terminals that don't support a mode will be stuck at the highest + // mode they do support. + // + // Enable encoding mode 1015 first, then try to switch to 1006. On + // some terminals, both modes will be enabled, but 1006 will have + // priority. On other terminals, 1006 wins because it's listed last. + // + // See misc/MouseInputNotes.txt for details. + m_output.write( + CSI "?1005l" + CSI "?1000h" CSI "?1002h" CSI "?1003h" CSI "?1015h" CSI "?1006h"); + } else { + // Resetting both encoding modes (1006 and 1015) is necessary, but + // apparently we only need to use reset on one of the 100[023] modes. + // Doing both doesn't hurt. + m_output.write( + CSI "?1006l" CSI "?1015l" CSI "?1003l" CSI "?1002l" CSI "?1000l"); + } +} diff --git a/src/libs/3rdparty/winpty/src/agent/Terminal.h b/src/libs/3rdparty/winpty/src/agent/Terminal.h new file mode 100644 index 00000000000..058eb2650e7 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/Terminal.h @@ -0,0 +1,69 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef TERMINAL_H +#define TERMINAL_H + +#include +#include + +#include +#include + +#include "Coord.h" + +class NamedPipe; + +class Terminal +{ +public: + explicit Terminal(NamedPipe &output, bool plainMode, bool outputColor) + : m_output(output), m_plainMode(plainMode), m_outputColor(outputColor) + { + } + + enum SendClearFlag { OmitClear, SendClear }; + void reset(SendClearFlag sendClearFirst, int64_t newLine); + void sendLine(int64_t line, const CHAR_INFO *lineData, int width, + int cursorColumn); + void showTerminalCursor(int column, int64_t line); + void hideTerminalCursor(); + +private: + void moveTerminalToLine(int64_t line); + +public: + void enableMouseMode(bool enabled); + +private: + NamedPipe &m_output; + int64_t m_remoteLine = 0; + int m_remoteColumn = 0; + bool m_lineDataValid = true; + std::vector m_lineData; + bool m_cursorHidden = false; + int m_remoteColor = -1; + std::string m_termLineWorkingBuffer; + bool m_plainMode = false; + bool m_outputColor = true; + bool m_mouseModeEnabled = false; +}; + +#endif // TERMINAL_H diff --git a/src/libs/3rdparty/winpty/src/agent/UnicodeEncoding.h b/src/libs/3rdparty/winpty/src/agent/UnicodeEncoding.h new file mode 100644 index 00000000000..6b0de3eff9f --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/UnicodeEncoding.h @@ -0,0 +1,157 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef UNICODE_ENCODING_H +#define UNICODE_ENCODING_H + +#include + +// Encode the Unicode codepoint with UTF-8. The buffer must be at least 4 +// bytes in size. +static inline int encodeUtf8(char *out, uint32_t code) { + if (code < 0x80) { + out[0] = code; + return 1; + } else if (code < 0x800) { + out[0] = ((code >> 6) & 0x1F) | 0xC0; + out[1] = ((code >> 0) & 0x3F) | 0x80; + return 2; + } else if (code < 0x10000) { + if (code >= 0xD800 && code <= 0xDFFF) { + // The code points 0xD800 to 0xDFFF are reserved for UTF-16 + // surrogate pairs and do not have an encoding in UTF-8. + return 0; + } + out[0] = ((code >> 12) & 0x0F) | 0xE0; + out[1] = ((code >> 6) & 0x3F) | 0x80; + out[2] = ((code >> 0) & 0x3F) | 0x80; + return 3; + } else if (code < 0x110000) { + out[0] = ((code >> 18) & 0x07) | 0xF0; + out[1] = ((code >> 12) & 0x3F) | 0x80; + out[2] = ((code >> 6) & 0x3F) | 0x80; + out[3] = ((code >> 0) & 0x3F) | 0x80; + return 4; + } else { + // Encoding error + return 0; + } +} + +// Encode the Unicode codepoint with UTF-16. The buffer must be large enough +// to hold the output -- either 1 or 2 elements. +static inline int encodeUtf16(wchar_t *out, uint32_t code) { + if (code < 0x10000) { + if (code >= 0xD800 && code <= 0xDFFF) { + // The code points 0xD800 to 0xDFFF are reserved for UTF-16 + // surrogate pairs and do not have an encoding in UTF-16. + return 0; + } + out[0] = code; + return 1; + } else if (code < 0x110000) { + code -= 0x10000; + out[0] = 0xD800 | (code >> 10); + out[1] = 0xDC00 | (code & 0x3FF); + return 2; + } else { + // Encoding error + return 0; + } +} + +// Return the byte size of a UTF-8 character using the value of the first +// byte. +static inline int utf8CharLength(char firstByte) { + // This code would probably be faster if it used __builtin_clz. + if ((firstByte & 0x80) == 0) { + return 1; + } else if ((firstByte & 0xE0) == 0xC0) { + return 2; + } else if ((firstByte & 0xF0) == 0xE0) { + return 3; + } else if ((firstByte & 0xF8) == 0xF0) { + return 4; + } else { + // Malformed UTF-8. + return 0; + } +} + +// The pointer must point to 1-4 bytes, as indicated by the first byte. +// Returns -1 on decoding error. +static inline uint32_t decodeUtf8(const char *in) { + const uint32_t kInvalid = static_cast(-1); + switch (utf8CharLength(in[0])) { + case 1: { + return in[0]; + } + case 2: { + if ((in[1] & 0xC0) != 0x80) { + return kInvalid; + } + uint32_t tmp = 0; + tmp = (in[0] & 0x1F) << 6; + tmp |= (in[1] & 0x3F); + return tmp <= 0x7F ? kInvalid : tmp; + } + case 3: { + if ((in[1] & 0xC0) != 0x80 || + (in[2] & 0xC0) != 0x80) { + return kInvalid; + } + uint32_t tmp = 0; + tmp = (in[0] & 0x0F) << 12; + tmp |= (in[1] & 0x3F) << 6; + tmp |= (in[2] & 0x3F); + if (tmp <= 0x07FF || (tmp >= 0xD800 && tmp <= 0xDFFF)) { + return kInvalid; + } else { + return tmp; + } + } + case 4: { + if ((in[1] & 0xC0) != 0x80 || + (in[2] & 0xC0) != 0x80 || + (in[3] & 0xC0) != 0x80) { + return kInvalid; + } + uint32_t tmp = 0; + tmp = (in[0] & 0x07) << 18; + tmp |= (in[1] & 0x3F) << 12; + tmp |= (in[2] & 0x3F) << 6; + tmp |= (in[3] & 0x3F); + if (tmp <= 0xFFFF || tmp > 0x10FFFF) { + return kInvalid; + } else { + return tmp; + } + } + default: { + return kInvalid; + } + } +} + +static inline uint32_t decodeSurrogatePair(wchar_t ch1, wchar_t ch2) { + return ((ch1 - 0xD800) << 10) + (ch2 - 0xDC00) + 0x10000; +} + +#endif // UNICODE_ENCODING_H diff --git a/src/libs/3rdparty/winpty/src/agent/UnicodeEncodingTest.cc b/src/libs/3rdparty/winpty/src/agent/UnicodeEncodingTest.cc new file mode 100644 index 00000000000..cd4abeb1916 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/UnicodeEncodingTest.cc @@ -0,0 +1,189 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +// Encode every code-point using this module and verify that it matches the +// encoding generated using Windows WideCharToMultiByte. + +#include "UnicodeEncoding.h" + +#include +#include +#include +#include +#include + +static void correctnessByCode() +{ + char mbstr1[4]; + char mbstr2[4]; + wchar_t wch[2]; + for (unsigned int code = 0; code < 0x110000; ++code) { + + // Surrogate pair reserved region. + const bool isReserved = (code >= 0xD800 && code <= 0xDFFF); + + int mblen1 = encodeUtf8(mbstr1, code); + if (isReserved ? mblen1 != 0 : mblen1 <= 0) { + printf("Error: 0x%04X: mblen1=%d\n", code, mblen1); + continue; + } + + int wlen = encodeUtf16(wch, code); + if (isReserved ? wlen != 0 : wlen <= 0) { + printf("Error: 0x%04X: wlen=%d\n", code, wlen); + continue; + } + + if (isReserved) { + continue; + } + + if (mblen1 != utf8CharLength(mbstr1[0])) { + printf("Error: 0x%04X: mblen1=%d, utf8CharLength(mbstr1[0])=%d\n", + code, mblen1, utf8CharLength(mbstr1[0])); + continue; + } + + if (code != decodeUtf8(mbstr1)) { + printf("Error: 0x%04X: decodeUtf8(mbstr1)=%u\n", + code, decodeUtf8(mbstr1)); + continue; + } + + int mblen2 = WideCharToMultiByte(CP_UTF8, 0, wch, wlen, mbstr2, 4, NULL, NULL); + if (mblen1 != mblen2) { + printf("Error: 0x%04X: mblen1=%d, mblen2=%d\n", code, mblen1, mblen2); + continue; + } + + if (memcmp(mbstr1, mbstr2, mblen1) != 0) { + printf("Error: 0x%04x: encodings are different\n", code); + continue; + } + } +} + +static const char *encodingStr(char (&output)[128], char (&buf)[4]) +{ + sprintf(output, "Encoding %02X %02X %02X %02X", + static_cast(buf[0]), + static_cast(buf[1]), + static_cast(buf[2]), + static_cast(buf[3])); + return output; +} + +// This test can take a couple of minutes to run. +static void correctnessByUtf8Encoding() +{ + for (uint64_t encoding = 0; encoding <= 0xFFFFFFFF; ++encoding) { + + char mb[4]; + mb[0] = encoding; + mb[1] = encoding >> 8; + mb[2] = encoding >> 16; + mb[3] = encoding >> 24; + + const int mblen = utf8CharLength(mb[0]); + if (mblen == 0) { + continue; + } + + // Test this module. + const uint32_t code1 = decodeUtf8(mb); + wchar_t ws1[2] = {}; + const int wslen1 = encodeUtf16(ws1, code1); + + // Test using Windows. We can't decode a codepoint directly; we have + // to do UTF8->UTF16, then decode the surrogate pair. + wchar_t ws2[2] = {}; + const int wslen2 = MultiByteToWideChar( + CP_UTF8, MB_ERR_INVALID_CHARS, mb, mblen, ws2, 2); + const uint32_t code2 = + (wslen2 == 1 ? ws2[0] : + wslen2 == 2 ? decodeSurrogatePair(ws2[0], ws2[1]) : + static_cast(-1)); + + // Verify that the two implementations match. + char prefix[128]; + if (code1 != code2) { + printf("%s: code1=0x%04x code2=0x%04x\n", + encodingStr(prefix, mb), + code1, code2); + continue; + } + if (wslen1 != wslen2) { + printf("%s: wslen1=%d wslen2=%d\n", + encodingStr(prefix, mb), + wslen1, wslen2); + continue; + } + if (memcmp(ws1, ws2, wslen1 * sizeof(wchar_t)) != 0) { + printf("%s: ws1 != ws2\n", encodingStr(prefix, mb)); + continue; + } + } +} + +wchar_t g_wch_TEST[] = { 0xD840, 0xDC00 }; +char g_ch_TEST[4]; +wchar_t *volatile g_pwch = g_wch_TEST; +char *volatile g_pch = g_ch_TEST; +unsigned int volatile g_code = 0xA2000; + +static void performance() +{ + { + clock_t start = clock(); + for (long long i = 0; i < 250000000LL; ++i) { + int mblen = WideCharToMultiByte(CP_UTF8, 0, g_pwch, 2, g_pch, 4, NULL, NULL); + assert(mblen == 4); + } + clock_t stop = clock(); + printf("%.3fns per char\n", (double)(stop - start) / CLOCKS_PER_SEC * 4.0); + } + + { + clock_t start = clock(); + for (long long i = 0; i < 3000000000LL; ++i) { + int mblen = encodeUtf8(g_pch, g_code); + assert(mblen == 4); + } + clock_t stop = clock(); + printf("%.3fns per char\n", (double)(stop - start) / CLOCKS_PER_SEC / 3.0); + } +} + +int main() +{ + printf("Testing correctnessByCode...\n"); + fflush(stdout); + correctnessByCode(); + + printf("Testing correctnessByUtf8Encoding... (may take a couple minutes)\n"); + fflush(stdout); + correctnessByUtf8Encoding(); + + printf("Testing performance...\n"); + fflush(stdout); + performance(); + + return 0; +} diff --git a/src/libs/3rdparty/winpty/src/agent/Win32Console.cc b/src/libs/3rdparty/winpty/src/agent/Win32Console.cc new file mode 100644 index 00000000000..d53de021f5a --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/Win32Console.cc @@ -0,0 +1,107 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "Win32Console.h" + +#include +#include + +#include + +#include "../shared/DebugClient.h" +#include "../shared/WinptyAssert.h" + +Win32Console::Win32Console() : m_titleWorkBuf(16) +{ + // The console window must be non-NULL. It is used for two purposes: + // (1) "Freezing" the console to detect the exact number of lines that + // have scrolled. + // (2) Killing processes attached to the console, by posting a WM_CLOSE + // message to the console window. + m_hwnd = GetConsoleWindow(); + ASSERT(m_hwnd != nullptr); +} + +std::wstring Win32Console::title() +{ + while (true) { + // Calling GetConsoleTitleW is tricky, because its behavior changed + // from XP->Vista, then again from Win7->Win8. The Vista+Win7 behavior + // is especially broken. + // + // The MSDN documentation documents nSize as the "size of the buffer + // pointed to by the lpConsoleTitle parameter, in characters" and the + // successful return value as "the length of the console window's + // title, in characters." + // + // On XP, the function returns the title length, AFTER truncation + // (excluding the NUL terminator). If the title is blank, the API + // returns 0 and does not NUL-terminate the buffer. To accommodate + // XP, the function must: + // * Terminate the buffer itself. + // * Double the size of the title buffer in a loop. + // + // On Vista and up, the function returns the non-truncated title + // length (excluding the NUL terminator). + // + // On Vista and Windows 7, there is a bug where the buffer size is + // interpreted as a byte count rather than a wchar_t count. To + // work around this, we must pass GetConsoleTitleW a buffer that is + // twice as large as what is actually needed. + // + // See misc/*/Test_GetConsoleTitleW.cc for tests demonstrating Windows' + // behavior. + + DWORD count = GetConsoleTitleW(m_titleWorkBuf.data(), + m_titleWorkBuf.size()); + const size_t needed = (count + 1) * sizeof(wchar_t); + if (m_titleWorkBuf.size() < needed) { + m_titleWorkBuf.resize(needed); + continue; + } + m_titleWorkBuf[count] = L'\0'; + return m_titleWorkBuf.data(); + } +} + +void Win32Console::setTitle(const std::wstring &title) +{ + if (!SetConsoleTitleW(title.c_str())) { + trace("SetConsoleTitleW failed"); + } +} + +void Win32Console::setFrozen(bool frozen) { + const int SC_CONSOLE_MARK = 0xFFF2; + const int SC_CONSOLE_SELECT_ALL = 0xFFF5; + if (frozen == m_frozen) { + // Do nothing. + } else if (frozen) { + // Enter selection mode by activating either Mark or SelectAll. + const int command = m_freezeUsesMark ? SC_CONSOLE_MARK + : SC_CONSOLE_SELECT_ALL; + SendMessage(m_hwnd, WM_SYSCOMMAND, command, 0); + m_frozen = true; + } else { + // Send Escape to cancel the selection. + SendMessage(m_hwnd, WM_CHAR, 27, 0x00010001); + m_frozen = false; + } +} diff --git a/src/libs/3rdparty/winpty/src/agent/Win32Console.h b/src/libs/3rdparty/winpty/src/agent/Win32Console.h new file mode 100644 index 00000000000..ed83877e993 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/Win32Console.h @@ -0,0 +1,67 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef AGENT_WIN32_CONSOLE_H +#define AGENT_WIN32_CONSOLE_H + +#include + +#include +#include + +class Win32Console +{ +public: + class FreezeGuard { + public: + FreezeGuard(Win32Console &console, bool frozen) : + m_console(console), m_previous(console.frozen()) { + m_console.setFrozen(frozen); + } + ~FreezeGuard() { + m_console.setFrozen(m_previous); + } + FreezeGuard(const FreezeGuard &other) = delete; + FreezeGuard &operator=(const FreezeGuard &other) = delete; + private: + Win32Console &m_console; + bool m_previous; + }; + + Win32Console(); + + HWND hwnd() { return m_hwnd; } + std::wstring title(); + void setTitle(const std::wstring &title); + void setFreezeUsesMark(bool useMark) { m_freezeUsesMark = useMark; } + void setNewW10(bool isNewW10) { m_isNewW10 = isNewW10; } + bool isNewW10() { return m_isNewW10; } + void setFrozen(bool frozen=true); + bool frozen() { return m_frozen; } + +private: + HWND m_hwnd = nullptr; + bool m_frozen = false; + bool m_freezeUsesMark = false; + bool m_isNewW10 = false; + std::vector m_titleWorkBuf; +}; + +#endif // AGENT_WIN32_CONSOLE_H diff --git a/src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.cc b/src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.cc new file mode 100644 index 00000000000..ed93f4081f8 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.cc @@ -0,0 +1,193 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "Win32ConsoleBuffer.h" + +#include + +#include "../shared/DebugClient.h" +#include "../shared/StringBuilder.h" +#include "../shared/WinptyAssert.h" + +std::unique_ptr Win32ConsoleBuffer::openStdout() { + return std::unique_ptr( + new Win32ConsoleBuffer(GetStdHandle(STD_OUTPUT_HANDLE), false)); +} + +std::unique_ptr Win32ConsoleBuffer::openConout() { + const HANDLE conout = CreateFileW(L"CONOUT$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + ASSERT(conout != INVALID_HANDLE_VALUE); + return std::unique_ptr( + new Win32ConsoleBuffer(conout, true)); +} + +std::unique_ptr Win32ConsoleBuffer::createErrorBuffer() { + SECURITY_ATTRIBUTES sa = {}; + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + const HANDLE conout = + CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &sa, + CONSOLE_TEXTMODE_BUFFER, + nullptr); + ASSERT(conout != INVALID_HANDLE_VALUE); + return std::unique_ptr( + new Win32ConsoleBuffer(conout, true)); +} + +HANDLE Win32ConsoleBuffer::conout() { + return m_conout; +} + +void Win32ConsoleBuffer::clearLines( + int row, + int count, + const ConsoleScreenBufferInfo &info) { + // TODO: error handling + const int width = info.bufferSize().X; + DWORD actual = 0; + if (!FillConsoleOutputCharacterW( + m_conout, L' ', width * count, Coord(0, row), + &actual) || static_cast(actual) != width * count) { + trace("FillConsoleOutputCharacterW failed"); + } + if (!FillConsoleOutputAttribute( + m_conout, kDefaultAttributes, width * count, Coord(0, row), + &actual) || static_cast(actual) != width * count) { + trace("FillConsoleOutputAttribute failed"); + } +} + +void Win32ConsoleBuffer::clearAllLines(const ConsoleScreenBufferInfo &info) { + clearLines(0, info.bufferSize().Y, info); +} + +ConsoleScreenBufferInfo Win32ConsoleBuffer::bufferInfo() { + // TODO: error handling + ConsoleScreenBufferInfo info; + if (!GetConsoleScreenBufferInfo(m_conout, &info)) { + trace("GetConsoleScreenBufferInfo failed"); + } + return info; +} + +Coord Win32ConsoleBuffer::bufferSize() { + return bufferInfo().bufferSize(); +} + +SmallRect Win32ConsoleBuffer::windowRect() { + return bufferInfo().windowRect(); +} + +bool Win32ConsoleBuffer::resizeBufferRange(const Coord &initialSize, + Coord &finalSize) { + if (SetConsoleScreenBufferSize(m_conout, initialSize)) { + finalSize = initialSize; + return true; + } + // The font might be too small to accommodate a very narrow console window. + // In that case, rather than simply give up, it's better to try wider + // buffer sizes until the call succeeds. + Coord size = initialSize; + while (size.X < 20) { + size.X++; + if (SetConsoleScreenBufferSize(m_conout, size)) { + finalSize = size; + trace("SetConsoleScreenBufferSize: initial size (%d,%d) failed, " + "but wider size (%d,%d) succeeded", + initialSize.X, initialSize.Y, + finalSize.X, finalSize.Y); + return true; + } + } + trace("SetConsoleScreenBufferSize failed: " + "tried (%d,%d) through (%d,%d)", + initialSize.X, initialSize.Y, + size.X, size.Y); + return false; +} + +void Win32ConsoleBuffer::resizeBuffer(const Coord &size) { + // TODO: error handling + if (!SetConsoleScreenBufferSize(m_conout, size)) { + trace("SetConsoleScreenBufferSize failed: size=(%d,%d)", + size.X, size.Y); + } +} + +void Win32ConsoleBuffer::moveWindow(const SmallRect &rect) { + // TODO: error handling + if (!SetConsoleWindowInfo(m_conout, TRUE, &rect)) { + trace("SetConsoleWindowInfo failed"); + } +} + +Coord Win32ConsoleBuffer::cursorPosition() { + return bufferInfo().dwCursorPosition; +} + +void Win32ConsoleBuffer::setCursorPosition(const Coord &coord) { + // TODO: error handling + if (!SetConsoleCursorPosition(m_conout, coord)) { + trace("SetConsoleCursorPosition failed"); + } +} + +void Win32ConsoleBuffer::read(const SmallRect &rect, CHAR_INFO *data) { + // TODO: error handling + SmallRect tmp(rect); + if (!ReadConsoleOutputW(m_conout, data, rect.size(), Coord(), &tmp) && + isTracingEnabled()) { + StringBuilder sb(256); + auto outStruct = [&](const SMALL_RECT &sr) { + sb << "{L=" << sr.Left << ",T=" << sr.Top + << ",R=" << sr.Right << ",B=" << sr.Bottom << '}'; + }; + sb << "Win32ConsoleBuffer::read: ReadConsoleOutput failed: readRegion="; + outStruct(rect); + CONSOLE_SCREEN_BUFFER_INFO info = {}; + if (GetConsoleScreenBufferInfo(m_conout, &info)) { + sb << ", dwSize=(" << info.dwSize.X << ',' << info.dwSize.Y + << "), srWindow="; + outStruct(info.srWindow); + } else { + sb << ", GetConsoleScreenBufferInfo also failed"; + } + trace("%s", sb.c_str()); + } +} + +void Win32ConsoleBuffer::write(const SmallRect &rect, const CHAR_INFO *data) { + // TODO: error handling + SmallRect tmp(rect); + if (!WriteConsoleOutputW(m_conout, data, rect.size(), Coord(), &tmp)) { + trace("WriteConsoleOutput failed"); + } +} + +void Win32ConsoleBuffer::setTextAttribute(WORD attributes) { + if (!SetConsoleTextAttribute(m_conout, attributes)) { + trace("SetConsoleTextAttribute failed"); + } +} diff --git a/src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.h b/src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.h new file mode 100644 index 00000000000..a68d8d304fd --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.h @@ -0,0 +1,99 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef AGENT_WIN32_CONSOLE_BUFFER_H +#define AGENT_WIN32_CONSOLE_BUFFER_H + +#include + +#include + +#include + +#include "Coord.h" +#include "SmallRect.h" + +class ConsoleScreenBufferInfo : public CONSOLE_SCREEN_BUFFER_INFO { +public: + ConsoleScreenBufferInfo() + { + memset(this, 0, sizeof(*this)); + } + + Coord bufferSize() const { return dwSize; } + SmallRect windowRect() const { return srWindow; } + Coord cursorPosition() const { return dwCursorPosition; } +}; + +class Win32ConsoleBuffer { +private: + Win32ConsoleBuffer(HANDLE conout, bool owned) : + m_conout(conout), m_owned(owned) + { + } + +public: + static const int kDefaultAttributes = 7; + + ~Win32ConsoleBuffer() { + if (m_owned) { + CloseHandle(m_conout); + } + } + + static std::unique_ptr openStdout(); + static std::unique_ptr openConout(); + static std::unique_ptr createErrorBuffer(); + + Win32ConsoleBuffer(const Win32ConsoleBuffer &other) = delete; + Win32ConsoleBuffer &operator=(const Win32ConsoleBuffer &other) = delete; + + HANDLE conout(); + void clearLines(int row, int count, const ConsoleScreenBufferInfo &info); + void clearAllLines(const ConsoleScreenBufferInfo &info); + + // Buffer and window sizes. + ConsoleScreenBufferInfo bufferInfo(); + Coord bufferSize(); + SmallRect windowRect(); + void resizeBuffer(const Coord &size); + bool resizeBufferRange(const Coord &initialSize, Coord &finalSize); + bool resizeBufferRange(const Coord &initialSize) { + Coord dummy; + return resizeBufferRange(initialSize, dummy); + } + void moveWindow(const SmallRect &rect); + + // Cursor. + Coord cursorPosition(); + void setCursorPosition(const Coord &point); + + // Screen content. + void read(const SmallRect &rect, CHAR_INFO *data); + void write(const SmallRect &rect, const CHAR_INFO *data); + + void setTextAttribute(WORD attributes); + +private: + HANDLE m_conout = nullptr; + bool m_owned = false; +}; + +#endif // AGENT_WIN32_CONSOLE_BUFFER_H diff --git a/src/libs/3rdparty/winpty/src/agent/main.cc b/src/libs/3rdparty/winpty/src/agent/main.cc new file mode 100644 index 00000000000..427cb3a3aa1 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/main.cc @@ -0,0 +1,120 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include +#include +#include + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#include + +#include "../shared/StringUtil.h" +#include "../shared/WindowsVersion.h" +#include "../shared/WinptyAssert.h" +#include "../shared/WinptyVersion.h" + +#include "Agent.h" +#include "AgentCreateDesktop.h" +#include "DebugShowInput.h" + +const char USAGE[] = +"Usage: %ls controlPipeName flags mouseMode cols rows\n" +"Usage: %ls controlPipeName --create-desktop\n" +"\n" +"Ordinarily, this program is launched by winpty.dll and is not directly\n" +"useful to winpty users. However, it also has options intended for\n" +"debugging winpty.\n" +"\n" +"Usage: %ls [options]\n" +"\n" +"Options:\n" +" --show-input [--with-mouse] [--escape-input]\n" +" Dump INPUT_RECORDs from the console input buffer\n" +" --with-mouse: Include MOUSE_INPUT_RECORDs in the dump\n" +" output\n" +" --escape-input: Direct the new Windows 10 console to use\n" +" escape sequences for input\n" +" --version Print the winpty version\n"; + +static uint64_t winpty_atoi64(const char *str) { + return strtoll(str, NULL, 10); +} + +int main() { + dumpWindowsVersion(); + dumpVersionToTrace(); + + // Technically, we should free the CommandLineToArgvW return value using + // a single call to LocalFree, but the call will never actually happen in + // the normal case. + int argc = 0; + wchar_t *cmdline = GetCommandLineW(); + ASSERT(cmdline != nullptr && "GetCommandLineW returned NULL"); + wchar_t **argv = CommandLineToArgvW(cmdline, &argc); + ASSERT(argv != nullptr && "CommandLineToArgvW returned NULL"); + + if (argc == 2 && !wcscmp(argv[1], L"--version")) { + dumpVersionToStdout(); + return 0; + } + + if (argc >= 2 && !wcscmp(argv[1], L"--show-input")) { + bool withMouse = false; + bool escapeInput = false; + for (int i = 2; i < argc; ++i) { + if (!wcscmp(argv[i], L"--with-mouse")) { + withMouse = true; + } else if (!wcscmp(argv[i], L"--escape-input")) { + escapeInput = true; + } else { + fprintf(stderr, "Unrecognized --show-input option: %ls\n", + argv[i]); + return 1; + } + } + debugShowInput(withMouse, escapeInput); + return 0; + } + + if (argc == 3 && !wcscmp(argv[2], L"--create-desktop")) { + handleCreateDesktop(argv[1]); + return 0; + } + + if (argc != 6) { + fprintf(stderr, USAGE, argv[0], argv[0], argv[0]); + return 1; + } + + Agent agent(argv[1], + winpty_atoi64(utf8FromWide(argv[2]).c_str()), + atoi(utf8FromWide(argv[3]).c_str()), + atoi(utf8FromWide(argv[4]).c_str()), + atoi(utf8FromWide(argv[5]).c_str())); + agent.run(); + + // The Agent destructor shouldn't return, but if it does, exit + // unsuccessfully. + return 1; +} diff --git a/src/libs/3rdparty/winpty/src/agent/subdir.mk b/src/libs/3rdparty/winpty/src/agent/subdir.mk new file mode 100644 index 00000000000..1c7d37e3e53 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/agent/subdir.mk @@ -0,0 +1,61 @@ +# Copyright (c) 2011-2015 Ryan Prichard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +ALL_TARGETS += build/winpty-agent.exe + +$(eval $(call def_mingw_target,agent,-DWINPTY_AGENT_ASSERT)) + +AGENT_OBJECTS = \ + build/agent/agent/Agent.o \ + build/agent/agent/AgentCreateDesktop.o \ + build/agent/agent/ConsoleFont.o \ + build/agent/agent/ConsoleInput.o \ + build/agent/agent/ConsoleInputReencoding.o \ + build/agent/agent/ConsoleLine.o \ + build/agent/agent/DebugShowInput.o \ + build/agent/agent/DefaultInputMap.o \ + build/agent/agent/EventLoop.o \ + build/agent/agent/InputMap.o \ + build/agent/agent/LargeConsoleRead.o \ + build/agent/agent/NamedPipe.o \ + build/agent/agent/Scraper.o \ + build/agent/agent/Terminal.o \ + build/agent/agent/Win32Console.o \ + build/agent/agent/Win32ConsoleBuffer.o \ + build/agent/agent/main.o \ + build/agent/shared/BackgroundDesktop.o \ + build/agent/shared/Buffer.o \ + build/agent/shared/DebugClient.o \ + build/agent/shared/GenRandom.o \ + build/agent/shared/OwnedHandle.o \ + build/agent/shared/StringUtil.o \ + build/agent/shared/WindowsSecurity.o \ + build/agent/shared/WindowsVersion.o \ + build/agent/shared/WinptyAssert.o \ + build/agent/shared/WinptyException.o \ + build/agent/shared/WinptyVersion.o + +build/agent/shared/WinptyVersion.o : build/gen/GenVersion.h + +build/winpty-agent.exe : $(AGENT_OBJECTS) + $(info Linking $@) + @$(MINGW_CXX) $(MINGW_LDFLAGS) -o $@ $^ + +-include $(AGENT_OBJECTS:.o=.d) diff --git a/src/libs/3rdparty/winpty/src/configurations.gypi b/src/libs/3rdparty/winpty/src/configurations.gypi new file mode 100644 index 00000000000..e990a60338e --- /dev/null +++ b/src/libs/3rdparty/winpty/src/configurations.gypi @@ -0,0 +1,60 @@ +# By default gyp/msbuild build for 32-bit Windows. This gyp include file +# defines configurations for both 32-bit and 64-bit Windows. To use it, run: +# +# C:\...\winpty\src>gyp -I configurations.gypi +# +# This command generates Visual Studio project files with a Release +# configuration and two Platforms--Win32 and x64. Both can be built: +# +# C:\...\winpty\src>msbuild winpty.sln /p:Platform=Win32 +# C:\...\winpty\src>msbuild winpty.sln /p:Platform=x64 +# +# The output is placed in: +# +# C:\...\winpty\src\Release\Win32 +# C:\...\winpty\src\Release\x64 +# +# Windows XP note: By default, the project files will use the default "toolset" +# for the given MSVC version. For MSVC 2013 and MSVC 2015, the default toolset +# generates binaries that do not run on Windows XP. To target Windows XP, +# select the XP-specific toolset by passing +# -D WINPTY_MSBUILD_TOOLSET={v120_xp,v140_xp} to gyp (v120_xp == MSVC 2013, +# v140_xp == MSVC 2015). Unfortunately, it isn't possible to have a single +# project file with configurations for both XP and post-XP. This seems to be a +# limitation of the MSVC project file format. +# +# This file is not included by default, because I suspect it would interfere +# with node-gyp, which has a different system for building 32-vs-64-bit +# binaries. It uses a common.gypi, and the project files it generates can only +# build a single architecture, the output paths are not differentiated by +# architecture. + +{ + 'variables': { + 'WINPTY_MSBUILD_TOOLSET%': '', + }, + 'target_defaults': { + 'default_configuration': 'Release_Win32', + 'configurations': { + 'Release_Win32': { + 'msvs_configuration_platform': 'Win32', + }, + 'Release_x64': { + 'msvs_configuration_platform': 'x64', + }, + }, + 'msvs_configuration_attributes': { + 'OutputDirectory': '$(SolutionDir)$(ConfigurationName)\\$(Platform)', + 'IntermediateDirectory': '$(ConfigurationName)\\$(Platform)\\obj\\$(ProjectName)', + }, + 'msvs_settings': { + 'VCLinkerTool': { + 'SubSystem': '1', # /SUBSYSTEM:CONSOLE + }, + 'VCCLCompilerTool': { + 'RuntimeLibrary': '0', # MultiThreaded (/MT) + }, + }, + 'msbuild_toolset' : '<(WINPTY_MSBUILD_TOOLSET)', + } +} diff --git a/src/libs/3rdparty/winpty/src/debugserver/DebugServer.cc b/src/libs/3rdparty/winpty/src/debugserver/DebugServer.cc new file mode 100644 index 00000000000..353d31c1c6e --- /dev/null +++ b/src/libs/3rdparty/winpty/src/debugserver/DebugServer.cc @@ -0,0 +1,117 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include +#include + +#include + +#include "../shared/WindowsSecurity.h" +#include "../shared/WinptyException.h" + +const wchar_t *kPipeName = L"\\\\.\\pipe\\DebugServer"; + +// A message may not be larger than this size. +const int MSG_SIZE = 4096; + +static void usage(const char *program, int code) { + printf("Usage: %s [--everyone]\n" + "\n" + "Creates the named pipe %ls and reads messages. Prints each\n" + "message to stdout. By default, only the current user can send messages.\n" + "Pass --everyone to let anyone send a message.\n" + "\n" + "Use the WINPTY_DEBUG environment variable to enable winpty trace output.\n" + "(e.g. WINPTY_DEBUG=trace for the default trace output.) Set WINPTYDBG=1\n" + "to enable trace with older winpty versions.\n", + program, kPipeName); + exit(code); +} + +int main(int argc, char *argv[]) { + bool everyone = false; + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + if (arg == "--everyone") { + everyone = true; + } else if (arg == "-h" || arg == "--help") { + usage(argv[0], 0); + } else { + usage(argv[0], 1); + } + } + + SecurityDescriptor sd; + PSECURITY_ATTRIBUTES psa = nullptr; + SECURITY_ATTRIBUTES sa = {}; + if (everyone) { + try { + sd = createPipeSecurityDescriptorOwnerFullControlEveryoneWrite(); + } catch (const WinptyException &e) { + fprintf(stderr, + "error creating security descriptor: %ls\n", e.what()); + exit(1); + } + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = sd.get(); + psa = &sa; + } + + HANDLE serverPipe = CreateNamedPipeW( + kPipeName, + /*dwOpenMode=*/PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE, + /*dwPipeMode=*/PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | + rejectRemoteClientsPipeFlag(), + /*nMaxInstances=*/1, + /*nOutBufferSize=*/MSG_SIZE, + /*nInBufferSize=*/MSG_SIZE, + /*nDefaultTimeOut=*/10 * 1000, + psa); + + if (serverPipe == INVALID_HANDLE_VALUE) { + fprintf(stderr, "error: could not create %ls pipe: error %u\n", + kPipeName, static_cast(GetLastError())); + exit(1); + } + + char msgBuffer[MSG_SIZE + 1]; + + while (true) { + if (!ConnectNamedPipe(serverPipe, nullptr)) { + fprintf(stderr, "error: ConnectNamedPipe failed\n"); + fflush(stderr); + exit(1); + } + DWORD bytesRead = 0; + if (!ReadFile(serverPipe, msgBuffer, MSG_SIZE, &bytesRead, nullptr)) { + fprintf(stderr, "error: ReadFile on pipe failed\n"); + fflush(stderr); + DisconnectNamedPipe(serverPipe); + continue; + } + msgBuffer[bytesRead] = '\n'; + fwrite(msgBuffer, 1, bytesRead + 1, stdout); + fflush(stdout); + + DWORD bytesWritten = 0; + WriteFile(serverPipe, "OK", 2, &bytesWritten, nullptr); + DisconnectNamedPipe(serverPipe); + } +} diff --git a/src/libs/3rdparty/winpty/src/debugserver/subdir.mk b/src/libs/3rdparty/winpty/src/debugserver/subdir.mk new file mode 100644 index 00000000000..beed1bd597d --- /dev/null +++ b/src/libs/3rdparty/winpty/src/debugserver/subdir.mk @@ -0,0 +1,41 @@ +# Copyright (c) 2015 Ryan Prichard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +ALL_TARGETS += build/winpty-debugserver.exe + +$(eval $(call def_mingw_target,debugserver,)) + +DEBUGSERVER_OBJECTS = \ + build/debugserver/debugserver/DebugServer.o \ + build/debugserver/shared/DebugClient.o \ + build/debugserver/shared/OwnedHandle.o \ + build/debugserver/shared/StringUtil.o \ + build/debugserver/shared/WindowsSecurity.o \ + build/debugserver/shared/WindowsVersion.o \ + build/debugserver/shared/WinptyAssert.o \ + build/debugserver/shared/WinptyException.o + +build/debugserver/shared/WindowsVersion.o : build/gen/GenVersion.h + +build/winpty-debugserver.exe : $(DEBUGSERVER_OBJECTS) + $(info Linking $@) + @$(MINGW_CXX) $(MINGW_LDFLAGS) -o $@ $^ + +-include $(DEBUGSERVER_OBJECTS:.o=.d) diff --git a/src/libs/3rdparty/winpty/src/include/winpty.h b/src/libs/3rdparty/winpty/src/include/winpty.h new file mode 100644 index 00000000000..fdfe4bca21d --- /dev/null +++ b/src/libs/3rdparty/winpty/src/include/winpty.h @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2011-2016 Ryan Prichard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef WINPTY_H +#define WINPTY_H + +#include + +#include "winpty_constants.h" + +/* On 32-bit Windows, winpty functions have the default __cdecl (not __stdcall) + * calling convention. (64-bit Windows has only a single calling convention.) + * When compiled with __declspec(dllexport), with either MinGW or MSVC, the + * winpty functions are unadorned--no underscore prefix or '@nn' suffix--so + * GetProcAddress can be used easily. */ +#ifdef COMPILING_WINPTY_DLL +#define WINPTY_API __declspec(dllexport) +#else +#define WINPTY_API __declspec(dllimport) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* The winpty API uses wide characters, instead of UTF-8, to avoid conversion + * complications related to surrogates. Windows generally tolerates unpaired + * surrogates in text, which makes conversion to and from UTF-8 ambiguous and + * complicated. (There are different UTF-8 variants that deal with UTF-16 + * surrogates differently.) */ + + + +/***************************************************************************** + * Error handling. */ + +/* All the APIs have an optional winpty_error_t output parameter. If a + * non-NULL argument is specified, then either the API writes NULL to the + * value (on success) or writes a newly allocated winpty_error_t object. The + * object must be freed using winpty_error_free. */ + +/* An error object. */ +typedef struct winpty_error_s winpty_error_t; +typedef winpty_error_t *winpty_error_ptr_t; + +/* An error code -- one of WINPTY_ERROR_xxx. */ +typedef DWORD winpty_result_t; + +/* Gets the error code from the error object. */ +WINPTY_API winpty_result_t winpty_error_code(winpty_error_ptr_t err); + +/* Returns a textual representation of the error. The string is freed when + * the error is freed. */ +WINPTY_API LPCWSTR winpty_error_msg(winpty_error_ptr_t err); + +/* Free the error object. Every error returned from the winpty API must be + * freed. */ +WINPTY_API void winpty_error_free(winpty_error_ptr_t err); + + + +/***************************************************************************** + * Configuration of a new agent. */ + +/* The winpty_config_t object is not thread-safe. */ +typedef struct winpty_config_s winpty_config_t; + +/* Allocate a winpty_config_t value. Returns NULL on error. There are no + * required settings -- the object may immediately be used. agentFlags is a + * set of zero or more WINPTY_FLAG_xxx values. An unrecognized flag results + * in an assertion failure. */ +WINPTY_API winpty_config_t * +winpty_config_new(UINT64 agentFlags, winpty_error_ptr_t *err /*OPTIONAL*/); + +/* Free the cfg object after passing it to winpty_open. */ +WINPTY_API void winpty_config_free(winpty_config_t *cfg); + +WINPTY_API void +winpty_config_set_initial_size(winpty_config_t *cfg, int cols, int rows); + +/* Set the mouse mode to one of the WINPTY_MOUSE_MODE_xxx constants. */ +WINPTY_API void +winpty_config_set_mouse_mode(winpty_config_t *cfg, int mouseMode); + +/* Amount of time to wait for the agent to startup and to wait for any given + * agent RPC request. Must be greater than 0. Can be INFINITE. */ +WINPTY_API void +winpty_config_set_agent_timeout(winpty_config_t *cfg, DWORD timeoutMs); + + + +/***************************************************************************** + * Start the agent. */ + +/* The winpty_t object is thread-safe. */ +typedef struct winpty_s winpty_t; + +/* Starts the agent. Returns NULL on error. This process will connect to the + * agent over a control pipe, and the agent will open data pipes (e.g. CONIN + * and CONOUT). */ +WINPTY_API winpty_t * +winpty_open(const winpty_config_t *cfg, + winpty_error_ptr_t *err /*OPTIONAL*/); + +/* A handle to the agent process. This value is valid for the lifetime of the + * winpty_t object. Do not close it. */ +WINPTY_API HANDLE winpty_agent_process(winpty_t *wp); + + + +/***************************************************************************** + * I/O pipes. */ + +/* Returns the names of named pipes used for terminal I/O. Each input or + * output direction uses a different half-duplex pipe. The agent creates + * these pipes, and the client can connect to them using ordinary I/O methods. + * The strings are freed when the winpty_t object is freed. + * + * winpty_conerr_name returns NULL unless WINPTY_FLAG_CONERR is specified. + * + * N.B.: CreateFile does not block when connecting to a local server pipe. If + * the server pipe does not exist or is already connected, then it fails + * instantly. */ +WINPTY_API LPCWSTR winpty_conin_name(winpty_t *wp); +WINPTY_API LPCWSTR winpty_conout_name(winpty_t *wp); +WINPTY_API LPCWSTR winpty_conerr_name(winpty_t *wp); + + + +/***************************************************************************** + * winpty agent RPC call: process creation. */ + +/* The winpty_spawn_config_t object is not thread-safe. */ +typedef struct winpty_spawn_config_s winpty_spawn_config_t; + +/* winpty_spawn_config strings do not need to live as long as the config + * object. They are copied. Returns NULL on error. spawnFlags is a set of + * zero or more WINPTY_SPAWN_FLAG_xxx values. An unrecognized flag results in + * an assertion failure. + * + * env is a a pointer to an environment block like that passed to + * CreateProcess--a contiguous array of NUL-terminated "VAR=VAL" strings + * followed by a final NUL terminator. + * + * N.B.: If you want to gather all of the child's output, you may want the + * WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN flag. + */ +WINPTY_API winpty_spawn_config_t * +winpty_spawn_config_new(UINT64 spawnFlags, + LPCWSTR appname /*OPTIONAL*/, + LPCWSTR cmdline /*OPTIONAL*/, + LPCWSTR cwd /*OPTIONAL*/, + LPCWSTR env /*OPTIONAL*/, + winpty_error_ptr_t *err /*OPTIONAL*/); + +/* Free the cfg object after passing it to winpty_spawn. */ +WINPTY_API void winpty_spawn_config_free(winpty_spawn_config_t *cfg); + +/* + * Spawns the new process. + * + * The function initializes all output parameters to zero or NULL. + * + * On success, the function returns TRUE. For each of process_handle and + * thread_handle that is non-NULL, the HANDLE returned from CreateProcess is + * duplicated from the agent and returned to the winpty client. The client is + * responsible for closing these HANDLES. + * + * On failure, the function returns FALSE, and if err is non-NULL, then *err + * is set to an error object. + * + * If the agent's CreateProcess call failed, then *create_process_error is set + * to GetLastError(), and the WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED error + * is returned. + * + * winpty_spawn can only be called once per winpty_t object. If it is called + * before the output data pipe(s) is/are connected, then collected output is + * buffered until the pipes are connected, rather than being discarded. + * + * N.B.: GetProcessId works even if the process has exited. The PID is not + * recycled until the NT process object is freed. + * (https://blogs.msdn.microsoft.com/oldnewthing/20110107-00/?p=11803) + */ +WINPTY_API BOOL +winpty_spawn(winpty_t *wp, + const winpty_spawn_config_t *cfg, + HANDLE *process_handle /*OPTIONAL*/, + HANDLE *thread_handle /*OPTIONAL*/, + DWORD *create_process_error /*OPTIONAL*/, + winpty_error_ptr_t *err /*OPTIONAL*/); + + + +/***************************************************************************** + * winpty agent RPC calls: everything else */ + +/* Change the size of the Windows console window. */ +WINPTY_API BOOL +winpty_set_size(winpty_t *wp, int cols, int rows, + winpty_error_ptr_t *err /*OPTIONAL*/); + +/* Gets a list of processes attached to the console. */ +WINPTY_API int +winpty_get_console_process_list(winpty_t *wp, int *processList, const int processCount, + winpty_error_ptr_t *err /*OPTIONAL*/); + +/* Frees the winpty_t object and the OS resources contained in it. This + * call breaks the connection with the agent, which should then close its + * console, terminating the processes attached to it. + * + * This function must not be called if any other threads are using the + * winpty_t object. Undefined behavior results. */ +WINPTY_API void winpty_free(winpty_t *wp); + + + +/****************************************************************************/ + +#ifdef __cplusplus +} +#endif + +#endif /* WINPTY_H */ diff --git a/src/libs/3rdparty/winpty/src/include/winpty_constants.h b/src/libs/3rdparty/winpty/src/include/winpty_constants.h new file mode 100644 index 00000000000..11e34cf171c --- /dev/null +++ b/src/libs/3rdparty/winpty/src/include/winpty_constants.h @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2016 Ryan Prichard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef WINPTY_CONSTANTS_H +#define WINPTY_CONSTANTS_H + +/* + * You may want to include winpty.h instead, which includes this header. + * + * This file is split out from winpty.h so that the agent can access the + * winpty flags without also declaring the libwinpty APIs. + */ + +/***************************************************************************** + * Error codes. */ + +#define WINPTY_ERROR_SUCCESS 0 +#define WINPTY_ERROR_OUT_OF_MEMORY 1 +#define WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED 2 +#define WINPTY_ERROR_LOST_CONNECTION 3 +#define WINPTY_ERROR_AGENT_EXE_MISSING 4 +#define WINPTY_ERROR_UNSPECIFIED 5 +#define WINPTY_ERROR_AGENT_DIED 6 +#define WINPTY_ERROR_AGENT_TIMEOUT 7 +#define WINPTY_ERROR_AGENT_CREATION_FAILED 8 + + + +/***************************************************************************** + * Configuration of a new agent. */ + +/* Create a new screen buffer (connected to the "conerr" terminal pipe) and + * pass it to child processes as the STDERR handle. This flag also prevents + * the agent from reopening CONOUT$ when it polls -- regardless of whether the + * active screen buffer changes, winpty continues to monitor the original + * primary screen buffer. */ +#define WINPTY_FLAG_CONERR 0x1ull + +/* Don't output escape sequences. */ +#define WINPTY_FLAG_PLAIN_OUTPUT 0x2ull + +/* Do output color escape sequences. These escapes are output by default, but + * are suppressed with WINPTY_FLAG_PLAIN_OUTPUT. Use this flag to reenable + * them. */ +#define WINPTY_FLAG_COLOR_ESCAPES 0x4ull + +/* On XP and Vista, winpty needs to put the hidden console on a desktop in a + * service window station so that its polling does not interfere with other + * (visible) console windows. To create this desktop, it must change the + * process' window station (i.e. SetProcessWindowStation) for the duration of + * the winpty_open call. In theory, this change could interfere with the + * winpty client (e.g. other threads, spawning children), so winpty by default + * spawns a special agent process to create the hidden desktop. Spawning + * processes on Windows is slow, though, so if + * WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION is set, winpty changes this + * process' window station instead. + * See https://github.com/rprichard/winpty/issues/58. */ +#define WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION 0x8ull + +#define WINPTY_FLAG_MASK (0ull \ + | WINPTY_FLAG_CONERR \ + | WINPTY_FLAG_PLAIN_OUTPUT \ + | WINPTY_FLAG_COLOR_ESCAPES \ + | WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION \ +) + +/* QuickEdit mode is initially disabled, and the agent does not send mouse + * mode sequences to the terminal. If it receives mouse input, though, it + * still writes MOUSE_EVENT_RECORD values into CONIN. */ +#define WINPTY_MOUSE_MODE_NONE 0 + +/* QuickEdit mode is initially enabled. As CONIN enters or leaves mouse + * input mode (i.e. where ENABLE_MOUSE_INPUT is on and ENABLE_QUICK_EDIT_MODE + * is off), the agent enables or disables mouse input on the terminal. + * + * This is the default mode. */ +#define WINPTY_MOUSE_MODE_AUTO 1 + +/* QuickEdit mode is initially disabled, and the agent enables the terminal's + * mouse input mode. It does not disable terminal mouse mode (until exit). */ +#define WINPTY_MOUSE_MODE_FORCE 2 + + + +/***************************************************************************** + * winpty agent RPC call: process creation. */ + +/* If the spawn is marked "auto-shutdown", then the agent shuts down console + * output once the process exits. The agent stops polling for new console + * output, and once all pending data has been written to the output pipe, the + * agent closes the pipe. (At that point, the pipe may still have data in it, + * which the client may read. Once all the data has been read, further reads + * return EOF.) */ +#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ull + +/* After the agent shuts down output, and after all output has been written + * into the pipe(s), exit the agent by closing the console. If there any + * surviving processes still attached to the console, they are killed. + * + * Note: With this flag, an RPC call (e.g. winpty_set_size) issued after the + * agent exits will fail with an I/O or dead-agent error. */ +#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull + +/* All the spawn flags. */ +#define WINPTY_SPAWN_FLAG_MASK (0ull \ + | WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN \ + | WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN \ +) + + + +#endif /* WINPTY_CONSTANTS_H */ diff --git a/src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.cc b/src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.cc new file mode 100644 index 00000000000..82d00b2da2d --- /dev/null +++ b/src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.cc @@ -0,0 +1,75 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "AgentLocation.h" + +#include + +#include + +#include "../shared/WinptyAssert.h" + +#include "LibWinptyException.h" + +#define AGENT_EXE L"winpty-agent.exe" + +static HMODULE getCurrentModule() { + HMODULE module; + if (!GetModuleHandleExW( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(getCurrentModule), + &module)) { + ASSERT(false && "GetModuleHandleEx failed"); + } + return module; +} + +static std::wstring getModuleFileName(HMODULE module) { + const int bufsize = 4096; + wchar_t path[bufsize]; + int size = GetModuleFileNameW(module, path, bufsize); + ASSERT(size != 0 && size != bufsize); + return std::wstring(path); +} + +static std::wstring dirname(const std::wstring &path) { + std::wstring::size_type pos = path.find_last_of(L"\\/"); + if (pos == std::wstring::npos) { + return L""; + } else { + return path.substr(0, pos); + } +} + +static bool pathExists(const std::wstring &path) { + return GetFileAttributesW(path.c_str()) != 0xFFFFFFFF; +} + +std::wstring findAgentProgram() { + std::wstring progDir = dirname(getModuleFileName(getCurrentModule())); + std::wstring ret = progDir + (L"\\" AGENT_EXE); + if (!pathExists(ret)) { + throw LibWinptyException( + WINPTY_ERROR_AGENT_EXE_MISSING, + (L"agent executable does not exist: '" + ret + L"'").c_str()); + } + return ret; +} diff --git a/src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.h b/src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.h new file mode 100644 index 00000000000..a96b854cd2a --- /dev/null +++ b/src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.h @@ -0,0 +1,28 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef LIBWINPTY_AGENT_LOCATION_H +#define LIBWINPTY_AGENT_LOCATION_H + +#include + +std::wstring findAgentProgram(); + +#endif // LIBWINPTY_AGENT_LOCATION_H diff --git a/src/libs/3rdparty/winpty/src/libwinpty/LibWinptyException.h b/src/libs/3rdparty/winpty/src/libwinpty/LibWinptyException.h new file mode 100644 index 00000000000..2274798d238 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/libwinpty/LibWinptyException.h @@ -0,0 +1,54 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef LIB_WINPTY_EXCEPTION_H +#define LIB_WINPTY_EXCEPTION_H + +#include "../include/winpty.h" + +#include "../shared/WinptyException.h" + +#include +#include + +class LibWinptyException : public WinptyException { +public: + LibWinptyException(winpty_result_t code, const wchar_t *what) : + m_code(code), m_what(std::make_shared(what)) {} + + winpty_result_t code() const WINPTY_NOEXCEPT { + return m_code; + } + + const wchar_t *what() const WINPTY_NOEXCEPT override { + return m_what->c_str(); + } + + std::shared_ptr whatSharedStr() const WINPTY_NOEXCEPT { + return m_what; + } + +private: + winpty_result_t m_code; + // Using a shared_ptr ensures that copying the object raises no exception. + std::shared_ptr m_what; +}; + +#endif // LIB_WINPTY_EXCEPTION_H diff --git a/src/libs/3rdparty/winpty/src/libwinpty/WinptyInternal.h b/src/libs/3rdparty/winpty/src/libwinpty/WinptyInternal.h new file mode 100644 index 00000000000..93e992d5c55 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/libwinpty/WinptyInternal.h @@ -0,0 +1,72 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef LIBWINPTY_WINPTY_INTERNAL_H +#define LIBWINPTY_WINPTY_INTERNAL_H + +#include +#include + +#include "../include/winpty.h" + +#include "../shared/Mutex.h" +#include "../shared/OwnedHandle.h" + +// The structures in this header are not intended to be accessed directly by +// client programs. + +struct winpty_error_s { + winpty_result_t code; + const wchar_t *msgStatic; + // Use a pointer to a std::shared_ptr so that the struct remains simple + // enough to statically initialize, for the benefit of static error + // objects like kOutOfMemory. + std::shared_ptr *msgDynamic; +}; + +struct winpty_config_s { + uint64_t flags = 0; + int cols = 80; + int rows = 25; + int mouseMode = WINPTY_MOUSE_MODE_AUTO; + DWORD timeoutMs = 30000; +}; + +struct winpty_s { + Mutex mutex; + OwnedHandle agentProcess; + OwnedHandle controlPipe; + DWORD agentTimeoutMs = 0; + OwnedHandle ioEvent; + std::wstring spawnDesktopName; + std::wstring coninPipeName; + std::wstring conoutPipeName; + std::wstring conerrPipeName; +}; + +struct winpty_spawn_config_s { + uint64_t winptyFlags = 0; + std::wstring appname; + std::wstring cmdline; + std::wstring cwd; + std::wstring env; +}; + +#endif // LIBWINPTY_WINPTY_INTERNAL_H diff --git a/src/libs/3rdparty/winpty/src/libwinpty/subdir.mk b/src/libs/3rdparty/winpty/src/libwinpty/subdir.mk new file mode 100644 index 00000000000..ba32bad6e64 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/libwinpty/subdir.mk @@ -0,0 +1,46 @@ +# Copyright (c) 2011-2015 Ryan Prichard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +ALL_TARGETS += build/winpty.dll + +$(eval $(call def_mingw_target,libwinpty,-DCOMPILING_WINPTY_DLL)) + +LIBWINPTY_OBJECTS = \ + build/libwinpty/libwinpty/AgentLocation.o \ + build/libwinpty/libwinpty/winpty.o \ + build/libwinpty/shared/BackgroundDesktop.o \ + build/libwinpty/shared/Buffer.o \ + build/libwinpty/shared/DebugClient.o \ + build/libwinpty/shared/GenRandom.o \ + build/libwinpty/shared/OwnedHandle.o \ + build/libwinpty/shared/StringUtil.o \ + build/libwinpty/shared/WindowsSecurity.o \ + build/libwinpty/shared/WindowsVersion.o \ + build/libwinpty/shared/WinptyAssert.o \ + build/libwinpty/shared/WinptyException.o \ + build/libwinpty/shared/WinptyVersion.o + +build/libwinpty/shared/WinptyVersion.o : build/gen/GenVersion.h + +build/winpty.dll : $(LIBWINPTY_OBJECTS) + $(info Linking $@) + @$(MINGW_CXX) $(MINGW_LDFLAGS) -shared -o $@ $^ -Wl,--out-implib,build/winpty.lib + +-include $(LIBWINPTY_OBJECTS:.o=.d) diff --git a/src/libs/3rdparty/winpty/src/libwinpty/winpty.cc b/src/libs/3rdparty/winpty/src/libwinpty/winpty.cc new file mode 100644 index 00000000000..3d977498ef9 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/libwinpty/winpty.cc @@ -0,0 +1,970 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include + +#include +#include +#include + +#include +#include +#include + +#include "../include/winpty.h" + +#include "../shared/AgentMsg.h" +#include "../shared/BackgroundDesktop.h" +#include "../shared/Buffer.h" +#include "../shared/DebugClient.h" +#include "../shared/GenRandom.h" +#include "../shared/OwnedHandle.h" +#include "../shared/StringBuilder.h" +#include "../shared/StringUtil.h" +#include "../shared/WindowsSecurity.h" +#include "../shared/WindowsVersion.h" +#include "../shared/WinptyAssert.h" +#include "../shared/WinptyException.h" +#include "../shared/WinptyVersion.h" + +#include "AgentLocation.h" +#include "LibWinptyException.h" +#include "WinptyInternal.h" + + + +/***************************************************************************** + * Error handling -- translate C++ exceptions to an optional error object + * output and log the result. */ + +static const winpty_error_s kOutOfMemory = { + WINPTY_ERROR_OUT_OF_MEMORY, + L"Out of memory", + nullptr +}; + +static const winpty_error_s kBadRpcPacket = { + WINPTY_ERROR_UNSPECIFIED, + L"Bad RPC packet", + nullptr +}; + +static const winpty_error_s kUncaughtException = { + WINPTY_ERROR_UNSPECIFIED, + L"Uncaught C++ exception", + nullptr +}; + +/* Gets the error code from the error object. */ +WINPTY_API winpty_result_t winpty_error_code(winpty_error_ptr_t err) { + return err != nullptr ? err->code : WINPTY_ERROR_SUCCESS; +} + +/* Returns a textual representation of the error. The string is freed when + * the error is freed. */ +WINPTY_API LPCWSTR winpty_error_msg(winpty_error_ptr_t err) { + if (err != nullptr) { + if (err->msgStatic != nullptr) { + return err->msgStatic; + } else { + ASSERT(err->msgDynamic != nullptr); + std::wstring *msgPtr = err->msgDynamic->get(); + ASSERT(msgPtr != nullptr); + return msgPtr->c_str(); + } + } else { + return L"Success"; + } +} + +/* Free the error object. Every error returned from the winpty API must be + * freed. */ +WINPTY_API void winpty_error_free(winpty_error_ptr_t err) { + if (err != nullptr && err->msgDynamic != nullptr) { + delete err->msgDynamic; + delete err; + } +} + +static void translateException(winpty_error_ptr_t *&err) { + winpty_error_ptr_t ret = nullptr; + try { + try { + throw; + } catch (const ReadBuffer::DecodeError&) { + ret = const_cast(&kBadRpcPacket); + } catch (const LibWinptyException &e) { + std::unique_ptr obj(new winpty_error_t); + obj->code = e.code(); + obj->msgStatic = nullptr; + obj->msgDynamic = + new std::shared_ptr(e.whatSharedStr()); + ret = obj.release(); + } catch (const WinptyException &e) { + std::unique_ptr obj(new winpty_error_t); + std::shared_ptr msg(new std::wstring(e.what())); + obj->code = WINPTY_ERROR_UNSPECIFIED; + obj->msgStatic = nullptr; + obj->msgDynamic = new std::shared_ptr(msg); + ret = obj.release(); + } + } catch (const std::bad_alloc&) { + ret = const_cast(&kOutOfMemory); + } catch (...) { + ret = const_cast(&kUncaughtException); + } + trace("libwinpty error: code=%u msg='%s'", + static_cast(ret->code), + utf8FromWide(winpty_error_msg(ret)).c_str()); + if (err != nullptr) { + *err = ret; + } else { + winpty_error_free(ret); + } +} + +#define API_TRY \ + if (err != nullptr) { *err = nullptr; } \ + try + +#define API_CATCH(ret) \ + catch (...) { translateException(err); return (ret); } + + + +/***************************************************************************** + * Configuration of a new agent. */ + +WINPTY_API winpty_config_t * +winpty_config_new(UINT64 flags, winpty_error_ptr_t *err /*OPTIONAL*/) { + API_TRY { + ASSERT((flags & WINPTY_FLAG_MASK) == flags); + std::unique_ptr ret(new winpty_config_t); + ret->flags = flags; + return ret.release(); + } API_CATCH(nullptr) +} + +WINPTY_API void winpty_config_free(winpty_config_t *cfg) { + delete cfg; +} + +WINPTY_API void +winpty_config_set_initial_size(winpty_config_t *cfg, int cols, int rows) { + ASSERT(cfg != nullptr && cols > 0 && rows > 0); + cfg->cols = cols; + cfg->rows = rows; +} + +WINPTY_API void +winpty_config_set_mouse_mode(winpty_config_t *cfg, int mouseMode) { + ASSERT(cfg != nullptr && + mouseMode >= WINPTY_MOUSE_MODE_NONE && + mouseMode <= WINPTY_MOUSE_MODE_FORCE); + cfg->mouseMode = mouseMode; +} + +WINPTY_API void +winpty_config_set_agent_timeout(winpty_config_t *cfg, DWORD timeoutMs) { + ASSERT(cfg != nullptr && timeoutMs > 0); + cfg->timeoutMs = timeoutMs; +} + + + +/***************************************************************************** + * Agent I/O. */ + +namespace { + +// Once an I/O operation fails with ERROR_IO_PENDING, the caller *must* wait +// for it to complete, even after calling CancelIo on it! See +// https://blogs.msdn.microsoft.com/oldnewthing/20110202-00/?p=11613. This +// class enforces that requirement. +class PendingIo { + HANDLE m_file; + OVERLAPPED &m_over; + bool m_finished; +public: + // The file handle and OVERLAPPED object must live as long as the PendingIo + // object. + PendingIo(HANDLE file, OVERLAPPED &over) : + m_file(file), m_over(over), m_finished(false) {} + ~PendingIo() { + if (!m_finished) { + // We're not usually that interested in CancelIo's return value. + // In any case, we must not throw an exception in this dtor. + CancelIo(m_file); + waitForCompletion(); + } + } + std::tuple waitForCompletion(DWORD &actual) WINPTY_NOEXCEPT { + m_finished = true; + const BOOL success = + GetOverlappedResult(m_file, &m_over, &actual, TRUE); + return std::make_tuple(success, GetLastError()); + } + std::tuple waitForCompletion() WINPTY_NOEXCEPT { + DWORD actual = 0; + return waitForCompletion(actual); + } +}; + +} // anonymous namespace + +static void handlePendingIo(winpty_t &wp, OVERLAPPED &over, BOOL &success, + DWORD &lastError, DWORD &actual) { + if (!success && lastError == ERROR_IO_PENDING) { + PendingIo io(wp.controlPipe.get(), over); + const HANDLE waitHandles[2] = { wp.ioEvent.get(), + wp.agentProcess.get() }; + DWORD waitRet = WaitForMultipleObjects( + 2, waitHandles, FALSE, wp.agentTimeoutMs); + if (waitRet != WAIT_OBJECT_0) { + // The I/O is still pending. Cancel it, close the I/O event, and + // throw an exception. + if (waitRet == WAIT_OBJECT_0 + 1) { + throw LibWinptyException(WINPTY_ERROR_AGENT_DIED, L"agent died"); + } else if (waitRet == WAIT_TIMEOUT) { + throw LibWinptyException(WINPTY_ERROR_AGENT_TIMEOUT, + L"agent timed out"); + } else if (waitRet == WAIT_FAILED) { + throwWindowsError(L"WaitForMultipleObjects failed"); + } else { + ASSERT(false && + "unexpected WaitForMultipleObjects return value"); + } + } + std::tie(success, lastError) = io.waitForCompletion(actual); + } +} + +static void handlePendingIo(winpty_t &wp, OVERLAPPED &over, BOOL &success, + DWORD &lastError) { + DWORD actual = 0; + handlePendingIo(wp, over, success, lastError, actual); +} + +static void handleReadWriteErrors(winpty_t &wp, BOOL success, DWORD lastError, + const wchar_t *genericErrMsg) { + if (!success) { + // If the pipe connection is broken after it's been connected, then + // later I/O operations fail with ERROR_BROKEN_PIPE (reads) or + // ERROR_NO_DATA (writes). With Wine, they may also fail with + // ERROR_PIPE_NOT_CONNECTED. See this gist[1]. + // + // [1] https://gist.github.com/rprichard/8dd8ca134b39534b7da2733994aa07ba + if (lastError == ERROR_BROKEN_PIPE || lastError == ERROR_NO_DATA || + lastError == ERROR_PIPE_NOT_CONNECTED) { + throw LibWinptyException(WINPTY_ERROR_LOST_CONNECTION, + L"lost connection to agent"); + } else { + throwWindowsError(genericErrMsg, lastError); + } + } +} + +// Calls ConnectNamedPipe to wait until the agent connects to the control pipe. +static void +connectControlPipe(winpty_t &wp) { + OVERLAPPED over = {}; + over.hEvent = wp.ioEvent.get(); + BOOL success = ConnectNamedPipe(wp.controlPipe.get(), &over); + DWORD lastError = GetLastError(); + handlePendingIo(wp, over, success, lastError); + if (!success && lastError == ERROR_PIPE_CONNECTED) { + success = TRUE; + } + if (!success) { + throwWindowsError(L"ConnectNamedPipe failed", lastError); + } +} + +static void writeData(winpty_t &wp, const void *data, size_t amount) { + // Perform a single pipe write. + DWORD actual = 0; + OVERLAPPED over = {}; + over.hEvent = wp.ioEvent.get(); + BOOL success = WriteFile(wp.controlPipe.get(), data, amount, + &actual, &over); + DWORD lastError = GetLastError(); + if (!success) { + handlePendingIo(wp, over, success, lastError, actual); + handleReadWriteErrors(wp, success, lastError, L"WriteFile failed"); + ASSERT(success); + } + // TODO: Can a partial write actually happen somehow? + ASSERT(actual == amount && "WriteFile wrote fewer bytes than requested"); +} + +static inline WriteBuffer newPacket() { + WriteBuffer packet; + packet.putRawValue(0); // Reserve space for size. + return packet; +} + +static void writePacket(winpty_t &wp, WriteBuffer &packet) { + const auto &buf = packet.buf(); + packet.replaceRawValue(0, buf.size()); + writeData(wp, buf.data(), buf.size()); +} + +static size_t readData(winpty_t &wp, void *data, size_t amount) { + DWORD actual = 0; + OVERLAPPED over = {}; + over.hEvent = wp.ioEvent.get(); + BOOL success = ReadFile(wp.controlPipe.get(), data, amount, + &actual, &over); + DWORD lastError = GetLastError(); + if (!success) { + handlePendingIo(wp, over, success, lastError, actual); + handleReadWriteErrors(wp, success, lastError, L"ReadFile failed"); + } + return actual; +} + +static void readAll(winpty_t &wp, void *data, size_t amount) { + while (amount > 0) { + const size_t chunk = readData(wp, data, amount); + ASSERT(chunk <= amount && "readData result is larger than amount"); + data = reinterpret_cast(data) + chunk; + amount -= chunk; + } +} + +static uint64_t readUInt64(winpty_t &wp) { + uint64_t ret = 0; + readAll(wp, &ret, sizeof(ret)); + return ret; +} + +// Returns a reply packet's payload. +static ReadBuffer readPacket(winpty_t &wp) { + const uint64_t packetSize = readUInt64(wp); + if (packetSize < sizeof(packetSize) || packetSize > SIZE_MAX) { + throwWinptyException(L"Agent RPC error: invalid packet size"); + } + const size_t payloadSize = packetSize - sizeof(packetSize); + std::vector bytes(payloadSize); + readAll(wp, bytes.data(), bytes.size()); + return ReadBuffer(std::move(bytes)); +} + +static OwnedHandle createControlPipe(const std::wstring &name) { + const auto sd = createPipeSecurityDescriptorOwnerFullControl(); + if (!sd) { + throwWinptyException( + L"could not create the control pipe's SECURITY_DESCRIPTOR"); + } + SECURITY_ATTRIBUTES sa = {}; + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = sd.get(); + HANDLE ret = CreateNamedPipeW(name.c_str(), + /*dwOpenMode=*/ + PIPE_ACCESS_DUPLEX | + FILE_FLAG_FIRST_PIPE_INSTANCE | + FILE_FLAG_OVERLAPPED, + /*dwPipeMode=*/rejectRemoteClientsPipeFlag(), + /*nMaxInstances=*/1, + /*nOutBufferSize=*/8192, + /*nInBufferSize=*/256, + /*nDefaultTimeOut=*/30000, + &sa); + if (ret == INVALID_HANDLE_VALUE) { + throwWindowsError(L"CreateNamedPipeW failed"); + } + return OwnedHandle(ret); +} + + + +/***************************************************************************** + * Start the agent. */ + +static OwnedHandle createEvent() { + // manual reset, initially unset + HANDLE h = CreateEventW(nullptr, TRUE, FALSE, nullptr); + if (h == nullptr) { + throwWindowsError(L"CreateEventW failed"); + } + return OwnedHandle(h); +} + +// For debugging purposes, provide a way to keep the console on the main window +// station, visible. +static bool shouldShowConsoleWindow() { + char buf[32]; + return GetEnvironmentVariableA("WINPTY_SHOW_CONSOLE", buf, sizeof(buf)) > 0; +} + +static bool shouldCreateBackgroundDesktop(bool &createUsingAgent) { + // Prior to Windows 7, winpty's repeated selection-deselection loop + // prevented the user from interacting with their *visible* console + // windows, unless we placed the console onto a background desktop. + // The SetProcessWindowStation call interferes with the clipboard and + // isn't thread-safe, though[1]. The call should perhaps occur in a + // special agent subprocess. Spawning a process in a background desktop + // also breaks ConEmu, but marking the process SW_HIDE seems to correct + // that[2]. + // + // Windows 7 moved a lot of console handling out of csrss.exe and into + // a per-console conhost.exe process, which may explain why it isn't + // affected. + // + // This is a somewhat risky change, so there are low-level flags to + // assist in debugging if there are issues. + // + // [1] https://github.com/rprichard/winpty/issues/58 + // [2] https://github.com/rprichard/winpty/issues/70 + bool ret = !shouldShowConsoleWindow() && !isAtLeastWindows7(); + const bool force = hasDebugFlag("force_desktop"); + const bool force_spawn = hasDebugFlag("force_desktop_spawn"); + const bool force_curproc = hasDebugFlag("force_desktop_curproc"); + const bool suppress = hasDebugFlag("no_desktop"); + if (force + force_spawn + force_curproc + suppress > 1) { + trace("error: Only one of force_desktop, force_desktop_spawn, " + "force_desktop_curproc, and no_desktop may be set"); + } else if (force) { + ret = true; + } else if (force_spawn) { + ret = true; + createUsingAgent = true; + } else if (force_curproc) { + ret = true; + createUsingAgent = false; + } else if (suppress) { + ret = false; + } + return ret; +} + +static bool shouldSpecifyHideFlag() { + const bool force = hasDebugFlag("force_sw_hide"); + const bool suppress = hasDebugFlag("no_sw_hide"); + bool ret = !shouldShowConsoleWindow(); + if (force && suppress) { + trace("error: Both the force_sw_hide and no_sw_hide flags are set"); + } else if (force) { + ret = true; + } else if (suppress) { + ret = false; + } + return ret; +} + +static OwnedHandle startAgentProcess( + const std::wstring &desktop, + const std::wstring &controlPipeName, + const std::wstring ¶ms, + DWORD creationFlags, + DWORD &agentPid) { + const std::wstring exePath = findAgentProgram(); + const std::wstring cmdline = + (WStringBuilder(256) + << L"\"" << exePath << L"\" " + << controlPipeName << L' ' + << params).str_moved(); + + auto cmdlineV = vectorWithNulFromString(cmdline); + auto desktopV = vectorWithNulFromString(desktop); + + // Start the agent. + STARTUPINFOW sui = {}; + sui.cb = sizeof(sui); + sui.lpDesktop = desktop.empty() ? nullptr : desktopV.data(); + + if (shouldSpecifyHideFlag()) { + sui.dwFlags |= STARTF_USESHOWWINDOW; + sui.wShowWindow = SW_HIDE; + } + PROCESS_INFORMATION pi = {}; + const BOOL success = + CreateProcessW(exePath.c_str(), + cmdlineV.data(), + nullptr, nullptr, + /*bInheritHandles=*/FALSE, + /*dwCreationFlags=*/creationFlags, + nullptr, nullptr, + &sui, &pi); + if (!success) { + const DWORD lastError = GetLastError(); + const auto errStr = + (WStringBuilder(256) + << L"winpty-agent CreateProcess failed: cmdline='" << cmdline + << L"' err=0x" << whexOfInt(lastError)).str_moved(); + throw LibWinptyException( + WINPTY_ERROR_AGENT_CREATION_FAILED, errStr.c_str()); + } + CloseHandle(pi.hThread); + TRACE("Created agent successfully, pid=%u, cmdline=%s", + static_cast(pi.dwProcessId), + utf8FromWide(cmdline).c_str()); + agentPid = pi.dwProcessId; + return OwnedHandle(pi.hProcess); +} + +static void verifyPipeClientPid(HANDLE serverPipe, DWORD agentPid) { + const auto client = getNamedPipeClientProcessId(serverPipe); + const auto success = std::get<0>(client); + const auto lastError = std::get<2>(client); + if (success == GetNamedPipeClientProcessId_Result::Success) { + const auto clientPid = std::get<1>(client); + if (clientPid != agentPid) { + WStringBuilder errMsg; + errMsg << L"Security check failed: pipe client pid (" << clientPid + << L") does not match agent pid (" << agentPid << L")"; + throwWinptyException(errMsg.c_str()); + } + } else if (success == GetNamedPipeClientProcessId_Result::UnsupportedOs) { + trace("Pipe client PID security check skipped: " + "GetNamedPipeClientProcessId unsupported on this OS version"); + } else { + throwWindowsError(L"GetNamedPipeClientProcessId failed", lastError); + } +} + +static std::unique_ptr +createAgentSession(const winpty_config_t *cfg, + const std::wstring &desktop, + const std::wstring ¶ms, + DWORD creationFlags) { + std::unique_ptr wp(new winpty_t); + wp->agentTimeoutMs = cfg->timeoutMs; + wp->ioEvent = createEvent(); + + // Create control server pipe. + const auto pipeName = + L"\\\\.\\pipe\\winpty-control-" + GenRandom().uniqueName(); + wp->controlPipe = createControlPipe(pipeName); + + DWORD agentPid = 0; + wp->agentProcess = startAgentProcess( + desktop, pipeName, params, creationFlags, agentPid); + connectControlPipe(*wp.get()); + verifyPipeClientPid(wp->controlPipe.get(), agentPid); + + return std::move(wp); +} + +namespace { + +class AgentDesktop { +public: + virtual std::wstring name() = 0; + virtual ~AgentDesktop() {} +}; + +class AgentDesktopDirect : public AgentDesktop { +public: + AgentDesktopDirect(BackgroundDesktop &&desktop) : + m_desktop(std::move(desktop)) + { + } + std::wstring name() override { return m_desktop.desktopName(); } +private: + BackgroundDesktop m_desktop; +}; + +class AgentDesktopIndirect : public AgentDesktop { +public: + AgentDesktopIndirect(std::unique_ptr &&wp, + std::wstring &&desktopName) : + m_wp(std::move(wp)), + m_desktopName(std::move(desktopName)) + { + } + std::wstring name() override { return m_desktopName; } +private: + std::unique_ptr m_wp; + std::wstring m_desktopName; +}; + +} // anonymous namespace + +std::unique_ptr +setupBackgroundDesktop(const winpty_config_t *cfg) { + bool useDesktopAgent = + !(cfg->flags & WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION); + const bool useDesktop = shouldCreateBackgroundDesktop(useDesktopAgent); + + if (!useDesktop) { + return std::unique_ptr(); + } + + if (useDesktopAgent) { + auto wp = createAgentSession( + cfg, std::wstring(), L"--create-desktop", DETACHED_PROCESS); + + // Read the desktop name. + auto packet = readPacket(*wp.get()); + auto desktopName = packet.getWString(); + packet.assertEof(); + + if (desktopName.empty()) { + return std::unique_ptr(); + } else { + return std::unique_ptr( + new AgentDesktopIndirect(std::move(wp), + std::move(desktopName))); + } + } else { + try { + BackgroundDesktop desktop; + return std::unique_ptr(new AgentDesktopDirect( + std::move(desktop))); + } catch (const WinptyException &e) { + trace("Error: failed to create background desktop, " + "using original desktop instead: %s", + utf8FromWide(e.what()).c_str()); + return std::unique_ptr(); + } + } +} + +WINPTY_API winpty_t * +winpty_open(const winpty_config_t *cfg, + winpty_error_ptr_t *err /*OPTIONAL*/) { + API_TRY { + ASSERT(cfg != nullptr); + dumpWindowsVersion(); + dumpVersionToTrace(); + + // Setup a background desktop for the agent. + auto desktop = setupBackgroundDesktop(cfg); + const auto desktopName = desktop ? desktop->name() : std::wstring(); + + // Start the primary agent session. + const auto params = + (WStringBuilder(128) + << cfg->flags << L' ' + << cfg->mouseMode << L' ' + << cfg->cols << L' ' + << cfg->rows).str_moved(); + auto wp = createAgentSession(cfg, desktopName, params, + CREATE_NEW_CONSOLE); + + // Close handles to the background desktop and restore the original + // window station. This must wait until we know the agent is running + // -- if we close these handles too soon, then the desktop and + // windowstation will be destroyed before the agent can connect with + // them. + // + // If we used a separate agent process to create the desktop, we + // disconnect from that process here, allowing it to exit. + desktop.reset(); + + // If we ran the agent process on a background desktop, then when we + // spawn a child process from the agent, it will need to be explicitly + // placed back onto the original desktop. + if (!desktopName.empty()) { + wp->spawnDesktopName = getCurrentDesktopName(); + } + + // Get the CONIN/CONOUT pipe names. + auto packet = readPacket(*wp.get()); + wp->coninPipeName = packet.getWString(); + wp->conoutPipeName = packet.getWString(); + if (cfg->flags & WINPTY_FLAG_CONERR) { + wp->conerrPipeName = packet.getWString(); + } + packet.assertEof(); + + return wp.release(); + } API_CATCH(nullptr) +} + +WINPTY_API HANDLE winpty_agent_process(winpty_t *wp) { + ASSERT(wp != nullptr); + return wp->agentProcess.get(); +} + + + +/***************************************************************************** + * I/O pipes. */ + +static const wchar_t *cstrFromWStringOrNull(const std::wstring &str) { + try { + return str.c_str(); + } catch (const std::bad_alloc&) { + return nullptr; + } +} + +WINPTY_API LPCWSTR winpty_conin_name(winpty_t *wp) { + ASSERT(wp != nullptr); + return cstrFromWStringOrNull(wp->coninPipeName); +} + +WINPTY_API LPCWSTR winpty_conout_name(winpty_t *wp) { + ASSERT(wp != nullptr); + return cstrFromWStringOrNull(wp->conoutPipeName); +} + +WINPTY_API LPCWSTR winpty_conerr_name(winpty_t *wp) { + ASSERT(wp != nullptr); + if (wp->conerrPipeName.empty()) { + return nullptr; + } else { + return cstrFromWStringOrNull(wp->conerrPipeName); + } +} + + + +/***************************************************************************** + * winpty agent RPC calls. */ + +namespace { + +// Close the control pipe if something goes wrong with the pipe communication, +// which could leave the control pipe in an inconsistent state. +class RpcOperation { +public: + RpcOperation(winpty_t &wp) : m_wp(wp) { + if (m_wp.controlPipe.get() == nullptr) { + throwWinptyException(L"Agent shutdown due to RPC failure"); + } + } + ~RpcOperation() { + if (!m_success) { + trace("~RpcOperation: Closing control pipe"); + m_wp.controlPipe.dispose(true); + } + } + void success() { m_success = true; } +private: + winpty_t &m_wp; + bool m_success = false; +}; + +} // anonymous namespace + + + +/***************************************************************************** + * winpty agent RPC call: process creation. */ + +// Return a std::wstring containing every character of the environment block. +// Typically, the block is non-empty, so the std::wstring returned ends with +// two NUL terminators. (These two terminators are counted in size(), so +// calling c_str() produces a triply-terminated string.) +static std::wstring wstringFromEnvBlock(const wchar_t *env) { + std::wstring envStr; + if (env != NULL) { + const wchar_t *p = env; + while (*p != L'\0') { + p += wcslen(p) + 1; + } + p++; + envStr.assign(env, p); + + // Assuming the environment was non-empty, envStr now ends with two NUL + // terminators. + // + // If the environment were empty, though, then envStr would only be + // singly terminated, but the MSDN documentation thinks an env block is + // always doubly-terminated, so add an extra NUL just in case it + // matters. + const auto envStrSz = envStr.size(); + if (envStrSz == 1) { + ASSERT(envStr[0] == L'\0'); + envStr.push_back(L'\0'); + } else { + ASSERT(envStrSz >= 3); + ASSERT(envStr[envStrSz - 3] != L'\0'); + ASSERT(envStr[envStrSz - 2] == L'\0'); + ASSERT(envStr[envStrSz - 1] == L'\0'); + } + } + return envStr; +} + +WINPTY_API winpty_spawn_config_t * +winpty_spawn_config_new(UINT64 winptyFlags, + LPCWSTR appname /*OPTIONAL*/, + LPCWSTR cmdline /*OPTIONAL*/, + LPCWSTR cwd /*OPTIONAL*/, + LPCWSTR env /*OPTIONAL*/, + winpty_error_ptr_t *err /*OPTIONAL*/) { + API_TRY { + ASSERT((winptyFlags & WINPTY_SPAWN_FLAG_MASK) == winptyFlags); + std::unique_ptr cfg(new winpty_spawn_config_t); + cfg->winptyFlags = winptyFlags; + if (appname != nullptr) { cfg->appname = appname; } + if (cmdline != nullptr) { cfg->cmdline = cmdline; } + if (cwd != nullptr) { cfg->cwd = cwd; } + if (env != nullptr) { cfg->env = wstringFromEnvBlock(env); } + return cfg.release(); + } API_CATCH(nullptr) +} + +WINPTY_API void winpty_spawn_config_free(winpty_spawn_config_t *cfg) { + delete cfg; +} + +// It's safe to truncate a handle from 64-bits to 32-bits, or to sign-extend it +// back to 64-bits. See the MSDN article, "Interprocess Communication Between +// 32-bit and 64-bit Applications". +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa384203.aspx +static inline HANDLE handleFromInt64(int64_t i) { + return reinterpret_cast(static_cast(i)); +} + +// Given a process and a handle in that process, duplicate the handle into the +// current process and close it in the originating process. +static inline OwnedHandle stealHandle(HANDLE process, HANDLE handle) { + HANDLE result = nullptr; + if (!DuplicateHandle(process, handle, + GetCurrentProcess(), + &result, 0, FALSE, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + throwWindowsError(L"DuplicateHandle of process handle"); + } + return OwnedHandle(result); +} + +WINPTY_API BOOL +winpty_spawn(winpty_t *wp, + const winpty_spawn_config_t *cfg, + HANDLE *process_handle /*OPTIONAL*/, + HANDLE *thread_handle /*OPTIONAL*/, + DWORD *create_process_error /*OPTIONAL*/, + winpty_error_ptr_t *err /*OPTIONAL*/) { + API_TRY { + ASSERT(wp != nullptr && cfg != nullptr); + + if (process_handle != nullptr) { *process_handle = nullptr; } + if (thread_handle != nullptr) { *thread_handle = nullptr; } + if (create_process_error != nullptr) { *create_process_error = 0; } + + LockGuard lock(wp->mutex); + RpcOperation rpc(*wp); + + // Send spawn request. + auto packet = newPacket(); + packet.putInt32(AgentMsg::StartProcess); + packet.putInt64(cfg->winptyFlags); + packet.putInt32(process_handle != nullptr); + packet.putInt32(thread_handle != nullptr); + packet.putWString(cfg->appname); + packet.putWString(cfg->cmdline); + packet.putWString(cfg->cwd); + packet.putWString(cfg->env); + packet.putWString(wp->spawnDesktopName); + writePacket(*wp, packet); + + // Receive reply. + auto reply = readPacket(*wp); + const auto result = static_cast(reply.getInt32()); + if (result == StartProcessResult::CreateProcessFailed) { + const DWORD lastError = reply.getInt32(); + reply.assertEof(); + if (create_process_error != nullptr) { + *create_process_error = lastError; + } + rpc.success(); + throw LibWinptyException(WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED, + L"CreateProcess failed"); + } else if (result == StartProcessResult::ProcessCreated) { + const HANDLE remoteProcess = handleFromInt64(reply.getInt64()); + const HANDLE remoteThread = handleFromInt64(reply.getInt64()); + reply.assertEof(); + OwnedHandle localProcess; + OwnedHandle localThread; + if (remoteProcess != nullptr) { + localProcess = + stealHandle(wp->agentProcess.get(), remoteProcess); + } + if (remoteThread != nullptr) { + localThread = + stealHandle(wp->agentProcess.get(), remoteThread); + } + if (process_handle != nullptr) { + *process_handle = localProcess.release(); + } + if (thread_handle != nullptr) { + *thread_handle = localThread.release(); + } + rpc.success(); + } else { + throwWinptyException( + L"Agent RPC error: invalid StartProcessResult"); + } + return TRUE; + } API_CATCH(FALSE) +} + + + +/***************************************************************************** + * winpty agent RPC calls: everything else */ + +WINPTY_API BOOL +winpty_set_size(winpty_t *wp, int cols, int rows, + winpty_error_ptr_t *err /*OPTIONAL*/) { + API_TRY { + ASSERT(wp != nullptr && cols > 0 && rows > 0); + LockGuard lock(wp->mutex); + RpcOperation rpc(*wp); + auto packet = newPacket(); + packet.putInt32(AgentMsg::SetSize); + packet.putInt32(cols); + packet.putInt32(rows); + writePacket(*wp, packet); + readPacket(*wp).assertEof(); + rpc.success(); + return TRUE; + } API_CATCH(FALSE) +} + +WINPTY_API int +winpty_get_console_process_list(winpty_t *wp, int *processList, const int processCount, + winpty_error_ptr_t *err /*OPTIONAL*/) { + API_TRY { + ASSERT(wp != nullptr); + ASSERT(processList != nullptr); + LockGuard lock(wp->mutex); + RpcOperation rpc(*wp); + auto packet = newPacket(); + packet.putInt32(AgentMsg::GetConsoleProcessList); + writePacket(*wp, packet); + auto reply = readPacket(*wp); + + auto actualProcessCount = reply.getInt32(); + + if (actualProcessCount <= processCount) { + for (auto i = 0; i < actualProcessCount; i++) { + processList[i] = reply.getInt32(); + } + } + + reply.assertEof(); + rpc.success(); + return actualProcessCount; + } API_CATCH(0) +} + +WINPTY_API void winpty_free(winpty_t *wp) { + // At least in principle, CloseHandle can fail, so this deletion can + // fail. It won't throw an exception, but maybe there's an error that + // should be propagated? + delete wp; +} diff --git a/src/libs/3rdparty/winpty/src/shared/AgentMsg.h b/src/libs/3rdparty/winpty/src/shared/AgentMsg.h new file mode 100644 index 00000000000..ab60c6b9619 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/AgentMsg.h @@ -0,0 +1,38 @@ +// Copyright (c) 2011-2012 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_SHARED_AGENT_MSG_H +#define WINPTY_SHARED_AGENT_MSG_H + +struct AgentMsg +{ + enum Type { + StartProcess, + SetSize, + GetConsoleProcessList, + }; +}; + +enum class StartProcessResult { + CreateProcessFailed, + ProcessCreated, +}; + +#endif // WINPTY_SHARED_AGENT_MSG_H diff --git a/src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.cc b/src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.cc new file mode 100644 index 00000000000..1bea7e53dd4 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.cc @@ -0,0 +1,122 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "BackgroundDesktop.h" + +#include + +#include "DebugClient.h" +#include "StringUtil.h" +#include "WinptyException.h" + +namespace { + +static std::wstring getObjectName(HANDLE object) { + BOOL success; + DWORD lengthNeeded = 0; + GetUserObjectInformationW(object, UOI_NAME, + nullptr, 0, + &lengthNeeded); + ASSERT(lengthNeeded % sizeof(wchar_t) == 0); + std::unique_ptr tmp( + new wchar_t[lengthNeeded / sizeof(wchar_t)]); + success = GetUserObjectInformationW(object, UOI_NAME, + tmp.get(), lengthNeeded, + nullptr); + if (!success) { + throwWindowsError(L"GetUserObjectInformationW failed"); + } + return std::wstring(tmp.get()); +} + +static std::wstring getDesktopName(HWINSTA winsta, HDESK desk) { + return getObjectName(winsta) + L"\\" + getObjectName(desk); +} + +} // anonymous namespace + +// Get a non-interactive window station for the agent. +// TODO: review security w.r.t. windowstation and desktop. +BackgroundDesktop::BackgroundDesktop() { + try { + m_originalStation = GetProcessWindowStation(); + if (m_originalStation == nullptr) { + throwWindowsError( + L"BackgroundDesktop ctor: " + L"GetProcessWindowStation returned NULL"); + } + m_newStation = + CreateWindowStationW(nullptr, 0, WINSTA_ALL_ACCESS, nullptr); + if (m_newStation == nullptr) { + throwWindowsError( + L"BackgroundDesktop ctor: CreateWindowStationW returned NULL"); + } + if (!SetProcessWindowStation(m_newStation)) { + throwWindowsError( + L"BackgroundDesktop ctor: SetProcessWindowStation failed"); + } + m_newDesktop = CreateDesktopW( + L"Default", nullptr, nullptr, 0, GENERIC_ALL, nullptr); + if (m_newDesktop == nullptr) { + throwWindowsError( + L"BackgroundDesktop ctor: CreateDesktopW failed"); + } + m_newDesktopName = getDesktopName(m_newStation, m_newDesktop); + TRACE("Created background desktop: %s", + utf8FromWide(m_newDesktopName).c_str()); + } catch (...) { + dispose(); + throw; + } +} + +void BackgroundDesktop::dispose() WINPTY_NOEXCEPT { + if (m_originalStation != nullptr) { + SetProcessWindowStation(m_originalStation); + m_originalStation = nullptr; + } + if (m_newDesktop != nullptr) { + CloseDesktop(m_newDesktop); + m_newDesktop = nullptr; + } + if (m_newStation != nullptr) { + CloseWindowStation(m_newStation); + m_newStation = nullptr; + } +} + +std::wstring getCurrentDesktopName() { + // MSDN says that the handles returned by GetProcessWindowStation and + // GetThreadDesktop do not need to be passed to CloseWindowStation and + // CloseDesktop, respectively. + const HWINSTA winsta = GetProcessWindowStation(); + if (winsta == nullptr) { + throwWindowsError( + L"getCurrentDesktopName: " + L"GetProcessWindowStation returned NULL"); + } + const HDESK desk = GetThreadDesktop(GetCurrentThreadId()); + if (desk == nullptr) { + throwWindowsError( + L"getCurrentDesktopName: " + L"GetThreadDesktop returned NULL"); + } + return getDesktopName(winsta, desk); +} diff --git a/src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.h b/src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.h new file mode 100644 index 00000000000..c692e57dc49 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.h @@ -0,0 +1,73 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_SHARED_BACKGROUND_DESKTOP_H +#define WINPTY_SHARED_BACKGROUND_DESKTOP_H + +#include + +#include + +#include "WinptyException.h" + +class BackgroundDesktop { +public: + BackgroundDesktop(); + ~BackgroundDesktop() { dispose(); } + void dispose() WINPTY_NOEXCEPT; + const std::wstring &desktopName() const { return m_newDesktopName; } + + BackgroundDesktop(const BackgroundDesktop &other) = delete; + BackgroundDesktop &operator=(const BackgroundDesktop &other) = delete; + + // We can't default the move constructor and assignment operator with + // MSVC 2013. We *could* if we required at least MSVC 2015 to build. + + BackgroundDesktop(BackgroundDesktop &&other) : + m_originalStation(other.m_originalStation), + m_newStation(other.m_newStation), + m_newDesktop(other.m_newDesktop), + m_newDesktopName(std::move(other.m_newDesktopName)) { + other.m_originalStation = nullptr; + other.m_newStation = nullptr; + other.m_newDesktop = nullptr; + } + BackgroundDesktop &operator=(BackgroundDesktop &&other) { + dispose(); + m_originalStation = other.m_originalStation; + m_newStation = other.m_newStation; + m_newDesktop = other.m_newDesktop; + m_newDesktopName = std::move(other.m_newDesktopName); + other.m_originalStation = nullptr; + other.m_newStation = nullptr; + other.m_newDesktop = nullptr; + return *this; + } + +private: + HWINSTA m_originalStation = nullptr; + HWINSTA m_newStation = nullptr; + HDESK m_newDesktop = nullptr; + std::wstring m_newDesktopName; +}; + +std::wstring getCurrentDesktopName(); + +#endif // WINPTY_SHARED_BACKGROUND_DESKTOP_H diff --git a/src/libs/3rdparty/winpty/src/shared/Buffer.cc b/src/libs/3rdparty/winpty/src/shared/Buffer.cc new file mode 100644 index 00000000000..158a629d564 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/Buffer.cc @@ -0,0 +1,103 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "Buffer.h" + +#include + +#include "DebugClient.h" +#include "WinptyAssert.h" + +// Define the READ_BUFFER_CHECK() macro. It *must* evaluate its condition, +// exactly once. +#define READ_BUFFER_CHECK(cond) \ + do { \ + if (!(cond)) { \ + trace("decode error: %s", #cond); \ + throw DecodeError(); \ + } \ + } while (false) + +enum class Piece : uint8_t { Int32, Int64, WString }; + +void WriteBuffer::putRawData(const void *data, size_t len) { + const auto p = reinterpret_cast(data); + m_buf.insert(m_buf.end(), p, p + len); +} + +void WriteBuffer::replaceRawData(size_t pos, const void *data, size_t len) { + ASSERT(pos <= m_buf.size() && len <= m_buf.size() - pos); + const auto p = reinterpret_cast(data); + std::copy(p, p + len, &m_buf[pos]); +} + +void WriteBuffer::putInt32(int32_t i) { + putRawValue(Piece::Int32); + putRawValue(i); +} + +void WriteBuffer::putInt64(int64_t i) { + putRawValue(Piece::Int64); + putRawValue(i); +} + +// len is in characters, excluding NUL, i.e. the number of wchar_t elements +void WriteBuffer::putWString(const wchar_t *str, size_t len) { + putRawValue(Piece::WString); + putRawValue(static_cast(len)); + putRawData(str, sizeof(wchar_t) * len); +} + +void ReadBuffer::getRawData(void *data, size_t len) { + ASSERT(m_off <= m_buf.size()); + READ_BUFFER_CHECK(len <= m_buf.size() - m_off); + const char *const inp = &m_buf[m_off]; + std::copy(inp, inp + len, reinterpret_cast(data)); + m_off += len; +} + +int32_t ReadBuffer::getInt32() { + READ_BUFFER_CHECK(getRawValue() == Piece::Int32); + return getRawValue(); +} + +int64_t ReadBuffer::getInt64() { + READ_BUFFER_CHECK(getRawValue() == Piece::Int64); + return getRawValue(); +} + +std::wstring ReadBuffer::getWString() { + READ_BUFFER_CHECK(getRawValue() == Piece::WString); + const uint64_t charLen = getRawValue(); + READ_BUFFER_CHECK(charLen <= SIZE_MAX / sizeof(wchar_t)); + // To be strictly conforming, we can't use the convenient wstring + // constructor, because the string in m_buf mightn't be aligned. + std::wstring ret; + if (charLen > 0) { + const size_t byteLen = charLen * sizeof(wchar_t); + ret.resize(charLen); + getRawData(&ret[0], byteLen); + } + return ret; +} + +void ReadBuffer::assertEof() { + READ_BUFFER_CHECK(m_off == m_buf.size()); +} diff --git a/src/libs/3rdparty/winpty/src/shared/Buffer.h b/src/libs/3rdparty/winpty/src/shared/Buffer.h new file mode 100644 index 00000000000..c2dd382e5b2 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/Buffer.h @@ -0,0 +1,102 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_SHARED_BUFFER_H +#define WINPTY_SHARED_BUFFER_H + +#include +#include + +#include +#include +#include +#include + +#include "WinptyException.h" + +class WriteBuffer { +private: + std::vector m_buf; + +public: + WriteBuffer() {} + + template void putRawValue(const T &t) { + putRawData(&t, sizeof(t)); + } + template void replaceRawValue(size_t pos, const T &t) { + replaceRawData(pos, &t, sizeof(t)); + } + + void putRawData(const void *data, size_t len); + void replaceRawData(size_t pos, const void *data, size_t len); + void putInt32(int32_t i); + void putInt64(int64_t i); + void putWString(const wchar_t *str, size_t len); + void putWString(const wchar_t *str) { putWString(str, wcslen(str)); } + void putWString(const std::wstring &str) { putWString(str.data(), str.size()); } + std::vector &buf() { return m_buf; } + + // MSVC 2013 does not generate these automatically, so help it out. + WriteBuffer(WriteBuffer &&other) : m_buf(std::move(other.m_buf)) {} + WriteBuffer &operator=(WriteBuffer &&other) { + m_buf = std::move(other.m_buf); + return *this; + } +}; + +class ReadBuffer { +public: + class DecodeError : public WinptyException { + virtual const wchar_t *what() const WINPTY_NOEXCEPT override { + return L"DecodeError: RPC message decoding error"; + } + }; + +private: + std::vector m_buf; + size_t m_off = 0; + +public: + explicit ReadBuffer(std::vector &&buf) : m_buf(std::move(buf)) {} + + template T getRawValue() { + T ret = {}; + getRawData(&ret, sizeof(ret)); + return ret; + } + + void getRawData(void *data, size_t len); + int32_t getInt32(); + int64_t getInt64(); + std::wstring getWString(); + void assertEof(); + + // MSVC 2013 does not generate these automatically, so help it out. + ReadBuffer(ReadBuffer &&other) : + m_buf(std::move(other.m_buf)), m_off(other.m_off) {} + ReadBuffer &operator=(ReadBuffer &&other) { + m_buf = std::move(other.m_buf); + m_off = other.m_off; + return *this; + } +}; + +#endif // WINPTY_SHARED_BUFFER_H diff --git a/src/libs/3rdparty/winpty/src/shared/DebugClient.cc b/src/libs/3rdparty/winpty/src/shared/DebugClient.cc new file mode 100644 index 00000000000..bafe0c89541 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/DebugClient.cc @@ -0,0 +1,187 @@ +// Copyright (c) 2011-2012 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "DebugClient.h" + +#include +#include +#include +#include + +#include +#include + +#include "winpty_snprintf.h" + +const wchar_t *const kPipeName = L"\\\\.\\pipe\\DebugServer"; + +void *volatile g_debugConfig; + +namespace { + +// It would be easy to accidentally trample on the Windows LastError value +// by adding logging/debugging code. Ensure that can't happen by saving and +// restoring the value. This saving and restoring doesn't happen along the +// fast path. +class PreserveLastError { +public: + PreserveLastError() : m_lastError(GetLastError()) {} + ~PreserveLastError() { SetLastError(m_lastError); } +private: + DWORD m_lastError; +}; + +} // anonymous namespace + +static void sendToDebugServer(const char *message) +{ + HANDLE tracePipe = INVALID_HANDLE_VALUE; + + do { + // The default impersonation level is SECURITY_IMPERSONATION, which allows + // a sufficiently authorized named pipe server to impersonate the client. + // There's no need for impersonation in this debugging system, so reduce + // the impersonation level to SECURITY_IDENTIFICATION, which allows a + // server to merely identify us. + tracePipe = CreateFileW( + kPipeName, + GENERIC_READ | GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, + SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION, + NULL); + } while (tracePipe == INVALID_HANDLE_VALUE && + GetLastError() == ERROR_PIPE_BUSY && + WaitNamedPipeW(kPipeName, NMPWAIT_WAIT_FOREVER)); + + if (tracePipe != INVALID_HANDLE_VALUE) { + DWORD newMode = PIPE_READMODE_MESSAGE; + SetNamedPipeHandleState(tracePipe, &newMode, NULL, NULL); + char response[16]; + DWORD actual = 0; + TransactNamedPipe(tracePipe, + const_cast(message), strlen(message), + response, sizeof(response), &actual, NULL); + CloseHandle(tracePipe); + } +} + +// Get the current UTC time as milliseconds from the epoch (ignoring leap +// seconds). Use the Unix epoch for consistency with DebugClient.py. There +// are 134774 days between 1601-01-01 (the Win32 epoch) and 1970-01-01 (the +// Unix epoch). +static long long unixTimeMillis() +{ + FILETIME fileTime; + GetSystemTimeAsFileTime(&fileTime); + long long msTime = (((long long)fileTime.dwHighDateTime << 32) + + fileTime.dwLowDateTime) / 10000; + return msTime - 134774LL * 24 * 3600 * 1000; +} + +static const char *getDebugConfig() +{ + if (g_debugConfig == NULL) { + PreserveLastError preserve; + const int bufSize = 256; + char buf[bufSize]; + DWORD actualSize = + GetEnvironmentVariableA("WINPTY_DEBUG", buf, bufSize); + if (actualSize == 0 || actualSize >= static_cast(bufSize)) { + buf[0] = '\0'; + } + const size_t len = strlen(buf) + 1; + char *newConfig = new char[len]; + std::copy(buf, buf + len, newConfig); + void *oldValue = InterlockedCompareExchangePointer( + &g_debugConfig, newConfig, NULL); + if (oldValue != NULL) { + delete [] newConfig; + } + } + return static_cast(g_debugConfig); +} + +bool isTracingEnabled() +{ + static bool disabled, enabled; + if (disabled) { + return false; + } else if (enabled) { + return true; + } else { + // Recognize WINPTY_DEBUG=1 for backwards compatibility. + PreserveLastError preserve; + bool value = hasDebugFlag("trace") || hasDebugFlag("1"); + disabled = !value; + enabled = value; + return value; + } +} + +bool hasDebugFlag(const char *flag) +{ + if (strchr(flag, ',') != NULL) { + trace("INTERNAL ERROR: hasDebugFlag flag has comma: '%s'", flag); + abort(); + } + const char *const configCStr = getDebugConfig(); + if (configCStr[0] == '\0') { + return false; + } + PreserveLastError preserve; + std::string config(configCStr); + std::string flagStr(flag); + config = "," + config + ","; + flagStr = "," + flagStr + ","; + return config.find(flagStr) != std::string::npos; +} + +void trace(const char *format, ...) +{ + if (!isTracingEnabled()) + return; + + PreserveLastError preserve; + char message[1024]; + + va_list ap; + va_start(ap, format); + winpty_vsnprintf(message, format, ap); + message[sizeof(message) - 1] = '\0'; + va_end(ap); + + const int currentTime = (int)(unixTimeMillis() % (100000 * 1000)); + + char moduleName[1024]; + moduleName[0] = '\0'; + GetModuleFileNameA(NULL, moduleName, sizeof(moduleName)); + const char *baseName = strrchr(moduleName, '\\'); + baseName = (baseName != NULL) ? baseName + 1 : moduleName; + + char fullMessage[1024]; + winpty_snprintf(fullMessage, + "[%05d.%03d %s,p%04d,t%04d]: %s", + currentTime / 1000, currentTime % 1000, + baseName, (int)GetCurrentProcessId(), (int)GetCurrentThreadId(), + message); + fullMessage[sizeof(fullMessage) - 1] = '\0'; + + sendToDebugServer(fullMessage); +} diff --git a/src/libs/3rdparty/winpty/src/shared/DebugClient.h b/src/libs/3rdparty/winpty/src/shared/DebugClient.h new file mode 100644 index 00000000000..b1260711301 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/DebugClient.h @@ -0,0 +1,38 @@ +// Copyright (c) 2011-2012 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef DEBUGCLIENT_H +#define DEBUGCLIENT_H + +#include "winpty_snprintf.h" + +bool isTracingEnabled(); +bool hasDebugFlag(const char *flag); +void trace(const char *format, ...) WINPTY_SNPRINTF_FORMAT(1, 2); + +// This macro calls trace without evaluating the arguments. +#define TRACE(format, ...) \ + do { \ + if (isTracingEnabled()) { \ + trace((format), ## __VA_ARGS__); \ + } \ + } while (false) + +#endif // DEBUGCLIENT_H diff --git a/src/libs/3rdparty/winpty/src/shared/GenRandom.cc b/src/libs/3rdparty/winpty/src/shared/GenRandom.cc new file mode 100644 index 00000000000..6d7920643af --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/GenRandom.cc @@ -0,0 +1,138 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "GenRandom.h" + +#include +#include + +#include "DebugClient.h" +#include "StringBuilder.h" + +static volatile LONG g_pipeCounter; + +GenRandom::GenRandom() : m_advapi32(L"advapi32.dll") { + // First try to use the pseudo-documented RtlGenRandom function from + // advapi32.dll. Creating a CryptoAPI context is slow, and RtlGenRandom + // avoids the overhead. It's documented in this blog post[1] and on + // MSDN[2] with a disclaimer about future breakage. This technique is + // apparently built-in into the MSVC CRT, though, for the rand_s function, + // so perhaps it is stable enough. + // + // [1] http://blogs.msdn.com/b/michael_howard/archive/2005/01/14/353379.aspx + // [2] https://msdn.microsoft.com/en-us/library/windows/desktop/aa387694(v=vs.85).aspx + // + // Both RtlGenRandom and the Crypto API functions exist in XP and up. + m_rtlGenRandom = reinterpret_cast( + m_advapi32.proc("SystemFunction036")); + // The OsModule class logs an error message if the proc is nullptr. + if (m_rtlGenRandom != nullptr) { + return; + } + + // Fall back to the crypto API. + m_cryptProvIsValid = + CryptAcquireContext(&m_cryptProv, nullptr, nullptr, + PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) != 0; + if (!m_cryptProvIsValid) { + trace("GenRandom: CryptAcquireContext failed: %u", + static_cast(GetLastError())); + } +} + +GenRandom::~GenRandom() { + if (m_cryptProvIsValid) { + CryptReleaseContext(m_cryptProv, 0); + } +} + +// Returns false if the context is invalid or the generation fails. +bool GenRandom::fillBuffer(void *buffer, size_t size) { + memset(buffer, 0, size); + bool success = false; + if (m_rtlGenRandom != nullptr) { + success = m_rtlGenRandom(buffer, size) != 0; + if (!success) { + trace("GenRandom: RtlGenRandom/SystemFunction036 failed: %u", + static_cast(GetLastError())); + } + } else if (m_cryptProvIsValid) { + success = + CryptGenRandom(m_cryptProv, size, + reinterpret_cast(buffer)) != 0; + if (!success) { + trace("GenRandom: CryptGenRandom failed, size=%d, lasterror=%u", + static_cast(size), + static_cast(GetLastError())); + } + } + return success; +} + +// Returns an empty string if either of CryptAcquireContext or CryptGenRandom +// fail. +std::string GenRandom::randomBytes(size_t numBytes) { + std::string ret(numBytes, '\0'); + if (!fillBuffer(&ret[0], numBytes)) { + return std::string(); + } + return ret; +} + +std::wstring GenRandom::randomHexString(size_t numBytes) { + const std::string bytes = randomBytes(numBytes); + std::wstring ret(bytes.size() * 2, L'\0'); + for (size_t i = 0; i < bytes.size(); ++i) { + static const wchar_t hex[] = L"0123456789abcdef"; + ret[i * 2] = hex[static_cast(bytes[i]) >> 4]; + ret[i * 2 + 1] = hex[static_cast(bytes[i]) & 0xF]; + } + return ret; +} + +// Returns a 64-bit value representing the number of 100-nanosecond intervals +// since January 1, 1601. +static uint64_t systemTimeAsUInt64() { + FILETIME monotonicTime = {}; + GetSystemTimeAsFileTime(&monotonicTime); + return (static_cast(monotonicTime.dwHighDateTime) << 32) | + static_cast(monotonicTime.dwLowDateTime); +} + +// Generates a unique and hard-to-guess case-insensitive string suitable for +// use in a pipe filename or a Windows object name. +std::wstring GenRandom::uniqueName() { + // First include enough information to avoid collisions assuming + // cooperative software. This code assumes that a process won't die and + // be replaced with a recycled PID within a single GetSystemTimeAsFileTime + // interval. + WStringBuilder sb(64); + sb << GetCurrentProcessId() + << L'-' << InterlockedIncrement(&g_pipeCounter) + << L'-' << whexOfInt(systemTimeAsUInt64()); + // It isn't clear to me how the crypto APIs would fail. It *probably* + // doesn't matter that much anyway? In principle, a predictable pipe name + // is subject to a local denial-of-service attack. + auto random = randomHexString(16); + if (!random.empty()) { + sb << L'-' << random; + } + return sb.str_moved(); +} diff --git a/src/libs/3rdparty/winpty/src/shared/GenRandom.h b/src/libs/3rdparty/winpty/src/shared/GenRandom.h new file mode 100644 index 00000000000..746cb1ecf70 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/GenRandom.h @@ -0,0 +1,55 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_GEN_RANDOM_H +#define WINPTY_GEN_RANDOM_H + +// The original MinGW requires that we include wincrypt.h. With MinGW-w64 and +// MSVC, including windows.h is sufficient. +#include +#include + +#include + +#include "OsModule.h" + +class GenRandom { + typedef BOOLEAN WINAPI RtlGenRandom_t(PVOID, ULONG); + + OsModule m_advapi32; + RtlGenRandom_t *m_rtlGenRandom = nullptr; + bool m_cryptProvIsValid = false; + HCRYPTPROV m_cryptProv = 0; + +public: + GenRandom(); + ~GenRandom(); + bool fillBuffer(void *buffer, size_t size); + std::string randomBytes(size_t numBytes); + std::wstring randomHexString(size_t numBytes); + std::wstring uniqueName(); + + // Return true if the crypto context was successfully initialized. + bool valid() const { + return m_rtlGenRandom != nullptr || m_cryptProvIsValid; + } +}; + +#endif // WINPTY_GEN_RANDOM_H diff --git a/src/libs/3rdparty/winpty/src/shared/GetCommitHash.bat b/src/libs/3rdparty/winpty/src/shared/GetCommitHash.bat new file mode 100644 index 00000000000..a9f8e9cef0f --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/GetCommitHash.bat @@ -0,0 +1,13 @@ +@echo off + +REM -- Echo the git commit hash. If git isn't available for some reason, +REM -- output nothing instead. + +git rev-parse HEAD >NUL 2>NUL && ( + git rev-parse HEAD +) || ( + echo none +) + +REM -- Set ERRORLEVEL to 0 using this cryptic syntax. +(call ) diff --git a/src/libs/3rdparty/winpty/src/shared/Mutex.h b/src/libs/3rdparty/winpty/src/shared/Mutex.h new file mode 100644 index 00000000000..98215365ad2 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/Mutex.h @@ -0,0 +1,54 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +// Recent 4.x MinGW and MinGW-w64 gcc compilers lack std::mutex and +// std::lock_guard. I have a 5.2.0 MinGW-w64 compiler packaged through MSYS2 +// that *is* new enough, but that's one compiler against several deficient +// ones. Wrap CRITICAL_SECTION instead. + +#ifndef WINPTY_SHARED_MUTEX_H +#define WINPTY_SHARED_MUTEX_H + +#include + +class Mutex { + CRITICAL_SECTION m_mutex; +public: + Mutex() { InitializeCriticalSection(&m_mutex); } + ~Mutex() { DeleteCriticalSection(&m_mutex); } + void lock() { EnterCriticalSection(&m_mutex); } + void unlock() { LeaveCriticalSection(&m_mutex); } + + Mutex(const Mutex &other) = delete; + Mutex &operator=(const Mutex &other) = delete; +}; + +template +class LockGuard { + T &m_lock; +public: + LockGuard(T &lock) : m_lock(lock) { m_lock.lock(); } + ~LockGuard() { m_lock.unlock(); } + + LockGuard(const LockGuard &other) = delete; + LockGuard &operator=(const LockGuard &other) = delete; +}; + +#endif // WINPTY_SHARED_MUTEX_H diff --git a/src/libs/3rdparty/winpty/src/shared/OsModule.h b/src/libs/3rdparty/winpty/src/shared/OsModule.h new file mode 100644 index 00000000000..9713fa2b2dd --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/OsModule.h @@ -0,0 +1,63 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_SHARED_OS_MODULE_H +#define WINPTY_SHARED_OS_MODULE_H + +#include + +#include + +#include "DebugClient.h" +#include "WinptyAssert.h" +#include "WinptyException.h" + +class OsModule { + HMODULE m_module; +public: + enum class LoadErrorBehavior { Abort, Throw }; + OsModule(const wchar_t *fileName, + LoadErrorBehavior behavior=LoadErrorBehavior::Abort) { + m_module = LoadLibraryW(fileName); + if (behavior == LoadErrorBehavior::Abort) { + ASSERT(m_module != NULL); + } else { + if (m_module == nullptr) { + const auto err = GetLastError(); + throwWindowsError( + (L"LoadLibraryW error: " + std::wstring(fileName)).c_str(), + err); + } + } + } + ~OsModule() { + FreeLibrary(m_module); + } + HMODULE handle() const { return m_module; } + FARPROC proc(const char *funcName) { + FARPROC ret = GetProcAddress(m_module, funcName); + if (ret == NULL) { + trace("GetProcAddress: %s is missing", funcName); + } + return ret; + } +}; + +#endif // WINPTY_SHARED_OS_MODULE_H diff --git a/src/libs/3rdparty/winpty/src/shared/OwnedHandle.cc b/src/libs/3rdparty/winpty/src/shared/OwnedHandle.cc new file mode 100644 index 00000000000..7b173536e6b --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/OwnedHandle.cc @@ -0,0 +1,36 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "OwnedHandle.h" + +#include "DebugClient.h" +#include "WinptyException.h" + +void OwnedHandle::dispose(bool nothrow) { + if (m_h != nullptr && m_h != INVALID_HANDLE_VALUE) { + if (!CloseHandle(m_h)) { + trace("CloseHandle(%p) failed", m_h); + if (!nothrow) { + throwWindowsError(L"CloseHandle failed"); + } + } + } + m_h = nullptr; +} diff --git a/src/libs/3rdparty/winpty/src/shared/OwnedHandle.h b/src/libs/3rdparty/winpty/src/shared/OwnedHandle.h new file mode 100644 index 00000000000..70a8d6163a9 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/OwnedHandle.h @@ -0,0 +1,45 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_SHARED_OWNED_HANDLE_H +#define WINPTY_SHARED_OWNED_HANDLE_H + +#include + +class OwnedHandle { + HANDLE m_h; +public: + OwnedHandle() : m_h(nullptr) {} + explicit OwnedHandle(HANDLE h) : m_h(h) {} + ~OwnedHandle() { dispose(true); } + void dispose(bool nothrow=false); + HANDLE get() const { return m_h; } + HANDLE release() { HANDLE ret = m_h; m_h = nullptr; return ret; } + OwnedHandle(const OwnedHandle &other) = delete; + OwnedHandle(OwnedHandle &&other) : m_h(other.release()) {} + OwnedHandle &operator=(const OwnedHandle &other) = delete; + OwnedHandle &operator=(OwnedHandle &&other) { + dispose(); + m_h = other.release(); + return *this; + } +}; + +#endif // WINPTY_SHARED_OWNED_HANDLE_H diff --git a/src/libs/3rdparty/winpty/src/shared/PrecompiledHeader.h b/src/libs/3rdparty/winpty/src/shared/PrecompiledHeader.h new file mode 100644 index 00000000000..7d9b8f8b4af --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/PrecompiledHeader.h @@ -0,0 +1,43 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_PRECOMPILED_HEADER_H +#define WINPTY_PRECOMPILED_HEADER_H + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif // WINPTY_PRECOMPILED_HEADER_H diff --git a/src/libs/3rdparty/winpty/src/shared/StringBuilder.h b/src/libs/3rdparty/winpty/src/shared/StringBuilder.h new file mode 100644 index 00000000000..f3155bdd29e --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/StringBuilder.h @@ -0,0 +1,227 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +// Efficient integer->string conversion and string concatenation. The +// hexadecimal conversion may optionally have leading zeros. Other ways to +// convert integers to strings in C++ suffer these drawbacks: +// +// * std::stringstream: Inefficient, even more so than stdio. +// +// * std::to_string: No hexadecimal output, tends to use heap allocation, not +// supported on Cygwin. +// +// * stdio routines: Requires parsing a format string (inefficient). The +// caller *must* know how large the content is for correctness. The +// string-printf functions are extremely inconsistent on Windows. In +// particular, 64-bit integers, wide strings, and return values are +// problem areas. +// +// StringBuilderTest.cc is a standalone program that tests this header. + +#ifndef WINPTY_STRING_BUILDER_H +#define WINPTY_STRING_BUILDER_H + +#include +#include +#include + +#ifdef STRING_BUILDER_TESTING +#include +#define STRING_BUILDER_CHECK(cond) assert(cond) +#else +#define STRING_BUILDER_CHECK(cond) +#endif // STRING_BUILDER_TESTING + +#include "WinptyAssert.h" + +template +struct ValueString { + std::array m_array; + size_t m_offset; + size_t m_size; + + const C *c_str() const { return m_array.data() + m_offset; } + const C *data() const { return m_array.data() + m_offset; } + size_t size() const { return m_size; } + std::basic_string str() const { + return std::basic_string(data(), m_size); + } +}; + +#ifdef _MSC_VER +// Disable an MSVC /SDL error that forbids unsigned negation. Signed negation +// invokes undefined behavior for INTxx_MIN, so unsigned negation is simpler to +// reason about. (We assume twos-complement in any case.) +#define STRING_BUILDER_ALLOW_UNSIGNED_NEGATE(x) \ + ( \ + __pragma(warning(push)) \ + __pragma(warning(disable:4146)) \ + (x) \ + __pragma(warning(pop)) \ + ) +#else +#define STRING_BUILDER_ALLOW_UNSIGNED_NEGATE(x) (x) +#endif + +// Formats an integer as decimal without leading zeros. +template +ValueString gdecOfInt(const I value) { + typedef typename std::make_unsigned::type U; + auto unsValue = static_cast(value); + const bool isNegative = (value < 0); + if (isNegative) { + unsValue = STRING_BUILDER_ALLOW_UNSIGNED_NEGATE(-unsValue); + } + decltype(gdecOfInt(value)) out; + auto &arr = out.m_array; + C *const endp = arr.data() + arr.size(); + C *outp = endp; + *(--outp) = '\0'; + STRING_BUILDER_CHECK(outp >= arr.data()); + do { + const int digit = unsValue % 10; + unsValue /= 10; + *(--outp) = '0' + digit; + STRING_BUILDER_CHECK(outp >= arr.data()); + } while (unsValue != 0); + if (isNegative) { + *(--outp) = '-'; + STRING_BUILDER_CHECK(outp >= arr.data()); + } + out.m_offset = outp - arr.data(); + out.m_size = endp - outp - 1; + return out; +} + +template decltype(gdecOfInt(0)) decOfInt(I i) { + return gdecOfInt(i); +} + +template decltype(gdecOfInt(0)) wdecOfInt(I i) { + return gdecOfInt(i); +} + +// Formats an integer as hexadecimal, with or without leading zeros. +template +ValueString ghexOfInt(const I value) { + typedef typename std::make_unsigned::type U; + const auto unsValue = static_cast(value); + static const C hex[16] = {'0','1','2','3','4','5','6','7', + '8','9','a','b','c','d','e','f'}; + decltype(ghexOfInt(value)) out; + auto &arr = out.m_array; + C *outp = arr.data(); + int inIndex = 0; + int shift = sizeof(I) * 8 - 4; + const int len = sizeof(I) * 2; + if (!leadingZeros) { + for (; inIndex < len - 1; ++inIndex, shift -= 4) { + STRING_BUILDER_CHECK(shift >= 0 && shift < sizeof(unsValue) * 8); + const int digit = (unsValue >> shift) & 0xF; + if (digit != 0) { + break; + } + } + } + for (; inIndex < len; ++inIndex, shift -= 4) { + const int digit = (unsValue >> shift) & 0xF; + *(outp++) = hex[digit]; + STRING_BUILDER_CHECK(outp <= arr.data() + arr.size()); + } + *(outp++) = '\0'; + STRING_BUILDER_CHECK(outp <= arr.data() + arr.size()); + out.m_offset = 0; + out.m_size = outp - arr.data() - 1; + return out; +} + +template +decltype(ghexOfInt(0)) hexOfInt(I i) { + return ghexOfInt(i); +} + +template +decltype(ghexOfInt(0)) whexOfInt(I i) { + return ghexOfInt(i); +} + +template +class GStringBuilder { +public: + typedef std::basic_string StringType; + + GStringBuilder() {} + GStringBuilder(size_t capacity) { + m_out.reserve(capacity); + } + + GStringBuilder &operator<<(C ch) { m_out.push_back(ch); return *this; } + GStringBuilder &operator<<(const C *str) { m_out.append(str); return *this; } + GStringBuilder &operator<<(const StringType &str) { m_out.append(str); return *this; } + + template + GStringBuilder &operator<<(const ValueString &str) { + m_out.append(str.data(), str.size()); + return *this; + } + +private: + // Forbid output of char/wchar_t for GStringBuilder if the type doesn't + // exactly match the builder element type. The code still allows + // signed char and unsigned char, but I'm a little worried about what + // happens if a user tries to output int8_t or uint8_t. + template + typename std::enable_if< + (std::is_same::value || std::is_same::value) && + !std::is_same::value, GStringBuilder&>::type + operator<<(P ch) { + ASSERT(false && "Method was not supposed to be reachable."); + return *this; + } + +public: + GStringBuilder &operator<<(short i) { return *this << gdecOfInt(i); } + GStringBuilder &operator<<(unsigned short i) { return *this << gdecOfInt(i); } + GStringBuilder &operator<<(int i) { return *this << gdecOfInt(i); } + GStringBuilder &operator<<(unsigned int i) { return *this << gdecOfInt(i); } + GStringBuilder &operator<<(long i) { return *this << gdecOfInt(i); } + GStringBuilder &operator<<(unsigned long i) { return *this << gdecOfInt(i); } + GStringBuilder &operator<<(long long i) { return *this << gdecOfInt(i); } + GStringBuilder &operator<<(unsigned long long i) { return *this << gdecOfInt(i); } + + GStringBuilder &operator<<(const void *p) { + m_out.push_back(static_cast('0')); + m_out.push_back(static_cast('x')); + *this << ghexOfInt(reinterpret_cast(p)); + return *this; + } + + StringType str() { return m_out; } + StringType str_moved() { return std::move(m_out); } + const C *c_str() const { return m_out.c_str(); } + +private: + StringType m_out; +}; + +typedef GStringBuilder StringBuilder; +typedef GStringBuilder WStringBuilder; + +#endif // WINPTY_STRING_BUILDER_H diff --git a/src/libs/3rdparty/winpty/src/shared/StringBuilderTest.cc b/src/libs/3rdparty/winpty/src/shared/StringBuilderTest.cc new file mode 100644 index 00000000000..e6c2d3138c8 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/StringBuilderTest.cc @@ -0,0 +1,114 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#define STRING_BUILDER_TESTING + +#include "StringBuilder.h" + +#include +#include + +#include +#include + +void display(const std::string &str) { fprintf(stderr, "%s", str.c_str()); } +void display(const std::wstring &str) { fprintf(stderr, "%ls", str.c_str()); } + +#define CHECK_EQ(x, y) \ + do { \ + const auto xval = (x); \ + const auto yval = (y); \ + if (xval != yval) { \ + fprintf(stderr, "error: %s:%d: %s != %s: ", \ + __FILE__, __LINE__, #x, #y); \ + display(xval); \ + fprintf(stderr, " != "); \ + display(yval); \ + fprintf(stderr, "\n"); \ + } \ + } while(0) + +template +std::basic_string decOfIntSS(const I value) { + // std::to_string and std::to_wstring are missing in Cygwin as of this + // writing (early 2016). + std::basic_stringstream ss; + ss << +value; // We must promote char to print it as an integer. + return ss.str(); +} + + +template +std::basic_string hexOfIntSS(const I value) { + typedef typename std::make_unsigned::type U; + const unsigned long long u64Value = value & static_cast(~0); + std::basic_stringstream ss; + if (leadingZeros) { + ss << std::setfill(static_cast('0')) << std::setw(sizeof(I) * 2); + } + ss << std::hex << u64Value; + return ss.str(); +} + +template +void testValue(I value) { + CHECK_EQ(decOfInt(value).str(), (decOfIntSS(value))); + CHECK_EQ(wdecOfInt(value).str(), (decOfIntSS(value))); + CHECK_EQ((hexOfInt(value).str()), (hexOfIntSS(value))); + CHECK_EQ((hexOfInt(value).str()), (hexOfIntSS(value))); + CHECK_EQ((whexOfInt(value).str()), (hexOfIntSS(value))); + CHECK_EQ((whexOfInt(value).str()), (hexOfIntSS(value))); +} + +template +void testType() { + typedef typename std::make_unsigned::type U; + const U quarter = static_cast(1) << (sizeof(U) * 8 - 2); + for (unsigned quarterIndex = 0; quarterIndex < 4; ++quarterIndex) { + for (int offset = -18; offset <= 18; ++offset) { + const I value = quarter * quarterIndex + static_cast(offset); + testValue(value); + } + } + testValue(static_cast(42)); + testValue(static_cast(123456)); + testValue(static_cast(0xdeadfacecafebeefull)); +} + +int main() { + testType(); + + testType(); + testType(); + testType(); + testType(); + testType(); + + testType(); + testType(); + testType(); + testType(); + testType(); + + StringBuilder() << static_cast("TEST"); + WStringBuilder() << static_cast("TEST"); + + fprintf(stderr, "All tests completed!\n"); +} diff --git a/src/libs/3rdparty/winpty/src/shared/StringUtil.cc b/src/libs/3rdparty/winpty/src/shared/StringUtil.cc new file mode 100644 index 00000000000..3a85a3ec941 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/StringUtil.cc @@ -0,0 +1,55 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "StringUtil.h" + +#include + +#include "WinptyAssert.h" + +// Workaround. MinGW (from mingw.org) does not have wcsnlen. MinGW-w64 *does* +// have wcsnlen, but use this function for consistency. +size_t winpty_wcsnlen(const wchar_t *s, size_t maxlen) { + ASSERT(s != NULL); + for (size_t i = 0; i < maxlen; ++i) { + if (s[i] == L'\0') { + return i; + } + } + return maxlen; +} + +std::string utf8FromWide(const std::wstring &input) { + int mblen = WideCharToMultiByte( + CP_UTF8, 0, + input.data(), input.size(), + NULL, 0, NULL, NULL); + if (mblen <= 0) { + return std::string(); + } + std::vector tmp(mblen); + int mblen2 = WideCharToMultiByte( + CP_UTF8, 0, + input.data(), input.size(), + tmp.data(), tmp.size(), + NULL, NULL); + ASSERT(mblen2 == mblen); + return std::string(tmp.data(), tmp.size()); +} diff --git a/src/libs/3rdparty/winpty/src/shared/StringUtil.h b/src/libs/3rdparty/winpty/src/shared/StringUtil.h new file mode 100644 index 00000000000..e4bf3c91212 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/StringUtil.h @@ -0,0 +1,80 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_SHARED_STRING_UTIL_H +#define WINPTY_SHARED_STRING_UTIL_H + +#include +#include +#include + +#include +#include +#include + +#include "WinptyAssert.h" + +size_t winpty_wcsnlen(const wchar_t *s, size_t maxlen); +std::string utf8FromWide(const std::wstring &input); + +// Return a vector containing each character in the string. +template +std::vector vectorFromString(const std::basic_string &str) { + return std::vector(str.begin(), str.end()); +} + +// Return a vector containing each character in the string, followed by a +// NUL terminator. +template +std::vector vectorWithNulFromString(const std::basic_string &str) { + std::vector ret; + ret.reserve(str.size() + 1); + ret.insert(ret.begin(), str.begin(), str.end()); + ret.push_back('\0'); + return ret; +} + +// A safer(?) version of wcsncpy that is accepted by MSVC's /SDL mode. +template +wchar_t *winpty_wcsncpy(wchar_t (&d)[N], const wchar_t *s) { + ASSERT(s != nullptr); + size_t i = 0; + for (; i < N; ++i) { + if (s[i] == L'\0') { + break; + } + d[i] = s[i]; + } + for (; i < N; ++i) { + d[i] = L'\0'; + } + return d; +} + +// Like wcsncpy, but ensure that the destination buffer is NUL-terminated. +template +wchar_t *winpty_wcsncpy_nul(wchar_t (&d)[N], const wchar_t *s) { + static_assert(N > 0, "array cannot be 0-size"); + winpty_wcsncpy(d, s); + d[N - 1] = L'\0'; + return d; +} + +#endif // WINPTY_SHARED_STRING_UTIL_H diff --git a/src/libs/3rdparty/winpty/src/shared/TimeMeasurement.h b/src/libs/3rdparty/winpty/src/shared/TimeMeasurement.h new file mode 100644 index 00000000000..716a027fcbd --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/TimeMeasurement.h @@ -0,0 +1,63 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +// Convenience header library for using the high-resolution performance counter +// to measure how long some process takes. + +#ifndef TIME_MEASUREMENT_H +#define TIME_MEASUREMENT_H + +#include +#include +#include + +class TimeMeasurement { +public: + TimeMeasurement() { + static double freq = static_cast(getFrequency()); + m_freq = freq; + m_start = value(); + } + + double elapsed() { + uint64_t elapsedTicks = value() - m_start; + return static_cast(elapsedTicks) / m_freq; + } + +private: + uint64_t getFrequency() { + LARGE_INTEGER freq; + BOOL success = QueryPerformanceFrequency(&freq); + assert(success && "QueryPerformanceFrequency failed"); + return freq.QuadPart; + } + + uint64_t value() { + LARGE_INTEGER ret; + BOOL success = QueryPerformanceCounter(&ret); + assert(success && "QueryPerformanceCounter failed"); + return ret.QuadPart; + } + + uint64_t m_start; + double m_freq; +}; + +#endif // TIME_MEASUREMENT_H diff --git a/src/libs/3rdparty/winpty/src/shared/UnixCtrlChars.h b/src/libs/3rdparty/winpty/src/shared/UnixCtrlChars.h new file mode 100644 index 00000000000..39dfa62ec9f --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/UnixCtrlChars.h @@ -0,0 +1,45 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef UNIX_CTRL_CHARS_H +#define UNIX_CTRL_CHARS_H + +inline char decodeUnixCtrlChar(char ch) { + const char ctrlKeys[] = { + /* 0x00 */ '@', /* 0x01 */ 'A', /* 0x02 */ 'B', /* 0x03 */ 'C', + /* 0x04 */ 'D', /* 0x05 */ 'E', /* 0x06 */ 'F', /* 0x07 */ 'G', + /* 0x08 */ 'H', /* 0x09 */ 'I', /* 0x0A */ 'J', /* 0x0B */ 'K', + /* 0x0C */ 'L', /* 0x0D */ 'M', /* 0x0E */ 'N', /* 0x0F */ 'O', + /* 0x10 */ 'P', /* 0x11 */ 'Q', /* 0x12 */ 'R', /* 0x13 */ 'S', + /* 0x14 */ 'T', /* 0x15 */ 'U', /* 0x16 */ 'V', /* 0x17 */ 'W', + /* 0x18 */ 'X', /* 0x19 */ 'Y', /* 0x1A */ 'Z', /* 0x1B */ '[', + /* 0x1C */ '\\', /* 0x1D */ ']', /* 0x1E */ '^', /* 0x1F */ '_', + }; + unsigned char uch = ch; + if (uch < 32) { + return ctrlKeys[uch]; + } else if (uch == 127) { + return '?'; + } else { + return '\0'; + } +} + +#endif // UNIX_CTRL_CHARS_H diff --git a/src/libs/3rdparty/winpty/src/shared/UpdateGenVersion.bat b/src/libs/3rdparty/winpty/src/shared/UpdateGenVersion.bat new file mode 100644 index 00000000000..ea2a7d64ed5 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/UpdateGenVersion.bat @@ -0,0 +1,20 @@ +@echo off + +rem -- Echo the git commit hash. If git isn't available for some reason, +rem -- output nothing instead. + +mkdir ..\gen 2>nul + +set /p VERSION=<..\..\VERSION.txt +set COMMIT=%1 + +echo // AUTO-GENERATED BY %0 %*>..\gen\GenVersion.h +echo const char GenVersion_Version[] = "%VERSION%";>>..\gen\GenVersion.h +echo const char GenVersion_Commit[] = "%COMMIT%";>>..\gen\GenVersion.h + +rem -- The winpty.gyp file expects the script to output the include directory, +rem -- relative to src. +echo gen + +rem -- Set ERRORLEVEL to 0 using this cryptic syntax. +(call ) diff --git a/src/libs/3rdparty/winpty/src/shared/WindowsSecurity.cc b/src/libs/3rdparty/winpty/src/shared/WindowsSecurity.cc new file mode 100644 index 00000000000..711a8637c8c --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/WindowsSecurity.cc @@ -0,0 +1,460 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "WindowsSecurity.h" + +#include + +#include "DebugClient.h" +#include "OsModule.h" +#include "OwnedHandle.h" +#include "StringBuilder.h" +#include "WindowsVersion.h" +#include "WinptyAssert.h" +#include "WinptyException.h" + +namespace { + +struct LocalFreer { + void operator()(void *ptr) { + if (ptr != nullptr) { + LocalFree(reinterpret_cast(ptr)); + } + } +}; + +typedef std::unique_ptr PointerLocal; + +template +SecurityItem localItem(typename T::type v) { + typedef typename T::type P; + struct Impl : SecurityItem::Impl { + P m_v; + Impl(P v) : m_v(v) {} + virtual ~Impl() { + LocalFree(reinterpret_cast(m_v)); + } + }; + return SecurityItem(v, std::unique_ptr(new Impl { v })); +} + +Sid allocatedSid(PSID v) { + struct Impl : Sid::Impl { + PSID m_v; + Impl(PSID v) : m_v(v) {} + virtual ~Impl() { + if (m_v != nullptr) { + FreeSid(m_v); + } + } + }; + return Sid(v, std::unique_ptr(new Impl { v })); +} + +} // anonymous namespace + +// Returns a handle to the thread's effective security token. If the thread +// is impersonating another user, its token is returned, and otherwise, the +// process' security token is opened. The handle is opened with TOKEN_QUERY. +static OwnedHandle openSecurityTokenForQuery() { + HANDLE token = nullptr; + // It is unclear to me whether OpenAsSelf matters for winpty, or what the + // most appropriate value is. + if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, + /*OpenAsSelf=*/FALSE, &token)) { + if (GetLastError() != ERROR_NO_TOKEN) { + throwWindowsError(L"OpenThreadToken failed"); + } + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) { + throwWindowsError(L"OpenProcessToken failed"); + } + } + ASSERT(token != nullptr && + "OpenThreadToken/OpenProcessToken token is NULL"); + return OwnedHandle(token); +} + +// Returns the TokenOwner of the thread's effective security token. +Sid getOwnerSid() { + struct Impl : Sid::Impl { + std::unique_ptr buffer; + }; + + OwnedHandle token = openSecurityTokenForQuery(); + DWORD actual = 0; + BOOL success; + success = GetTokenInformation(token.get(), TokenOwner, + nullptr, 0, &actual); + if (success) { + throwWinptyException(L"getOwnerSid: GetTokenInformation: " + L"expected ERROR_INSUFFICIENT_BUFFER"); + } else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + throwWindowsError(L"getOwnerSid: GetTokenInformation: " + L"expected ERROR_INSUFFICIENT_BUFFER"); + } + std::unique_ptr impl(new Impl); + impl->buffer = std::unique_ptr(new char[actual]); + success = GetTokenInformation(token.get(), TokenOwner, + impl->buffer.get(), actual, &actual); + if (!success) { + throwWindowsError(L"getOwnerSid: GetTokenInformation"); + } + TOKEN_OWNER tmp; + ASSERT(actual >= sizeof(tmp)); + std::copy( + impl->buffer.get(), + impl->buffer.get() + sizeof(tmp), + reinterpret_cast(&tmp)); + return Sid(tmp.Owner, std::move(impl)); +} + +Sid wellKnownSid( + const wchar_t *debuggingName, + SID_IDENTIFIER_AUTHORITY authority, + BYTE authorityCount, + DWORD subAuthority0/*=0*/, + DWORD subAuthority1/*=0*/) { + PSID psid = nullptr; + if (!AllocateAndInitializeSid(&authority, authorityCount, + subAuthority0, + subAuthority1, + 0, 0, 0, 0, 0, 0, + &psid)) { + const auto err = GetLastError(); + const auto msg = + std::wstring(L"wellKnownSid: error getting ") + + debuggingName + L" SID"; + throwWindowsError(msg.c_str(), err); + } + return allocatedSid(psid); +} + +Sid builtinAdminsSid() { + // S-1-5-32-544 + SID_IDENTIFIER_AUTHORITY authority = { SECURITY_NT_AUTHORITY }; + return wellKnownSid(L"BUILTIN\\Administrators group", + authority, 2, + SECURITY_BUILTIN_DOMAIN_RID, // 32 + DOMAIN_ALIAS_RID_ADMINS); // 544 +} + +Sid localSystemSid() { + // S-1-5-18 + SID_IDENTIFIER_AUTHORITY authority = { SECURITY_NT_AUTHORITY }; + return wellKnownSid(L"LocalSystem account", + authority, 1, + SECURITY_LOCAL_SYSTEM_RID); // 18 +} + +Sid everyoneSid() { + // S-1-1-0 + SID_IDENTIFIER_AUTHORITY authority = { SECURITY_WORLD_SID_AUTHORITY }; + return wellKnownSid(L"Everyone account", + authority, 1, + SECURITY_WORLD_RID); // 0 +} + +static SecurityDescriptor finishSecurityDescriptor( + size_t daclEntryCount, + EXPLICIT_ACCESSW *daclEntries, + Acl &outAcl) { + { + PACL aclRaw = nullptr; + DWORD aclError = + SetEntriesInAclW(daclEntryCount, + daclEntries, + nullptr, &aclRaw); + if (aclError != ERROR_SUCCESS) { + WStringBuilder sb(64); + sb << L"finishSecurityDescriptor: " + << L"SetEntriesInAcl failed: " << aclError; + throwWinptyException(sb.c_str()); + } + outAcl = localItem(aclRaw); + } + + const PSECURITY_DESCRIPTOR sdRaw = + reinterpret_cast( + LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH)); + if (sdRaw == nullptr) { + throwWinptyException(L"finishSecurityDescriptor: LocalAlloc failed"); + } + SecurityDescriptor sd = localItem(sdRaw); + if (!InitializeSecurityDescriptor(sdRaw, SECURITY_DESCRIPTOR_REVISION)) { + throwWindowsError( + L"finishSecurityDescriptor: InitializeSecurityDescriptor"); + } + if (!SetSecurityDescriptorDacl(sdRaw, TRUE, outAcl.get(), FALSE)) { + throwWindowsError( + L"finishSecurityDescriptor: SetSecurityDescriptorDacl"); + } + + return std::move(sd); +} + +// Create a security descriptor that grants full control to the local system +// account, built-in administrators, and the owner. +SecurityDescriptor +createPipeSecurityDescriptorOwnerFullControl() { + + struct Impl : SecurityDescriptor::Impl { + Sid localSystem; + Sid builtinAdmins; + Sid owner; + std::array daclEntries = {}; + Acl dacl; + SecurityDescriptor value; + }; + + std::unique_ptr impl(new Impl); + impl->localSystem = localSystemSid(); + impl->builtinAdmins = builtinAdminsSid(); + impl->owner = getOwnerSid(); + + for (auto &ea : impl->daclEntries) { + ea.grfAccessPermissions = GENERIC_ALL; + ea.grfAccessMode = SET_ACCESS; + ea.grfInheritance = NO_INHERITANCE; + ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; + } + impl->daclEntries[0].Trustee.ptstrName = + reinterpret_cast(impl->localSystem.get()); + impl->daclEntries[1].Trustee.ptstrName = + reinterpret_cast(impl->builtinAdmins.get()); + impl->daclEntries[2].Trustee.ptstrName = + reinterpret_cast(impl->owner.get()); + + impl->value = finishSecurityDescriptor( + impl->daclEntries.size(), + impl->daclEntries.data(), + impl->dacl); + + const auto retValue = impl->value.get(); + return SecurityDescriptor(retValue, std::move(impl)); +} + +SecurityDescriptor +createPipeSecurityDescriptorOwnerFullControlEveryoneWrite() { + + struct Impl : SecurityDescriptor::Impl { + Sid localSystem; + Sid builtinAdmins; + Sid owner; + Sid everyone; + std::array daclEntries = {}; + Acl dacl; + SecurityDescriptor value; + }; + + std::unique_ptr impl(new Impl); + impl->localSystem = localSystemSid(); + impl->builtinAdmins = builtinAdminsSid(); + impl->owner = getOwnerSid(); + impl->everyone = everyoneSid(); + + for (auto &ea : impl->daclEntries) { + ea.grfAccessPermissions = GENERIC_ALL; + ea.grfAccessMode = SET_ACCESS; + ea.grfInheritance = NO_INHERITANCE; + ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; + } + impl->daclEntries[0].Trustee.ptstrName = + reinterpret_cast(impl->localSystem.get()); + impl->daclEntries[1].Trustee.ptstrName = + reinterpret_cast(impl->builtinAdmins.get()); + impl->daclEntries[2].Trustee.ptstrName = + reinterpret_cast(impl->owner.get()); + impl->daclEntries[3].Trustee.ptstrName = + reinterpret_cast(impl->everyone.get()); + // Avoid using FILE_GENERIC_WRITE because it includes FILE_APPEND_DATA, + // which is equal to FILE_CREATE_PIPE_INSTANCE. Instead, include all the + // flags that comprise FILE_GENERIC_WRITE, except for the one. + impl->daclEntries[3].grfAccessPermissions = + FILE_GENERIC_READ | + FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA | FILE_WRITE_EA | + STANDARD_RIGHTS_WRITE | SYNCHRONIZE; + + impl->value = finishSecurityDescriptor( + impl->daclEntries.size(), + impl->daclEntries.data(), + impl->dacl); + + const auto retValue = impl->value.get(); + return SecurityDescriptor(retValue, std::move(impl)); +} + +SecurityDescriptor getObjectSecurityDescriptor(HANDLE handle) { + PACL dacl = nullptr; + PSECURITY_DESCRIPTOR sd = nullptr; + const DWORD errCode = GetSecurityInfo(handle, SE_KERNEL_OBJECT, + OWNER_SECURITY_INFORMATION | + GROUP_SECURITY_INFORMATION | + DACL_SECURITY_INFORMATION, + nullptr, nullptr, &dacl, nullptr, &sd); + if (errCode != ERROR_SUCCESS) { + throwWindowsError(L"GetSecurityInfo failed"); + } + return localItem(sd); +} + +// The (SID/SD)<->string conversion APIs are useful for testing/debugging, so +// create convenient accessor functions for them. They're too slow for +// ordinary use. The APIs exist in XP and up, but the MinGW headers only +// declare the SID<->string APIs, not the SD APIs. MinGW also gets the +// prototype wrong for ConvertStringSidToSidW (LPWSTR instead of LPCWSTR) and +// requires WINVER to be defined. MSVC and MinGW-w64 get everything right, but +// for consistency, use LoadLibrary/GetProcAddress for all four APIs. + +typedef BOOL WINAPI ConvertStringSidToSidW_t( + LPCWSTR StringSid, + PSID *Sid); + +typedef BOOL WINAPI ConvertSidToStringSidW_t( + PSID Sid, + LPWSTR *StringSid); + +typedef BOOL WINAPI ConvertStringSecurityDescriptorToSecurityDescriptorW_t( + LPCWSTR StringSecurityDescriptor, + DWORD StringSDRevision, + PSECURITY_DESCRIPTOR *SecurityDescriptor, + PULONG SecurityDescriptorSize); + +typedef BOOL WINAPI ConvertSecurityDescriptorToStringSecurityDescriptorW_t( + PSECURITY_DESCRIPTOR SecurityDescriptor, + DWORD RequestedStringSDRevision, + SECURITY_INFORMATION SecurityInformation, + LPWSTR *StringSecurityDescriptor, + PULONG StringSecurityDescriptorLen); + +#define GET_MODULE_PROC(mod, funcName) \ + const auto p##funcName = \ + reinterpret_cast( \ + mod.proc(#funcName)); \ + if (p##funcName == nullptr) { \ + throwWinptyException( \ + L"" L ## #funcName L" API is missing from ADVAPI32.DLL"); \ + } + +const DWORD kSDDL_REVISION_1 = 1; + +std::wstring sidToString(PSID sid) { + OsModule advapi32(L"advapi32.dll"); + GET_MODULE_PROC(advapi32, ConvertSidToStringSidW); + wchar_t *sidString = NULL; + BOOL success = pConvertSidToStringSidW(sid, &sidString); + if (!success) { + throwWindowsError(L"ConvertSidToStringSidW failed"); + } + PointerLocal freer(sidString); + return std::wstring(sidString); +} + +Sid stringToSid(const std::wstring &str) { + // Cast the string from const wchar_t* to LPWSTR because the function is + // incorrectly prototyped in the MinGW sddl.h header. The API does not + // modify the string -- it is correctly prototyped as taking LPCWSTR in + // MinGW-w64, MSVC, and MSDN. + OsModule advapi32(L"advapi32.dll"); + GET_MODULE_PROC(advapi32, ConvertStringSidToSidW); + PSID psid = nullptr; + BOOL success = pConvertStringSidToSidW(const_cast(str.c_str()), + &psid); + if (!success) { + const auto err = GetLastError(); + throwWindowsError( + (std::wstring(L"ConvertStringSidToSidW failed on \"") + + str + L'"').c_str(), + err); + } + return localItem(psid); +} + +SecurityDescriptor stringToSd(const std::wstring &str) { + OsModule advapi32(L"advapi32.dll"); + GET_MODULE_PROC(advapi32, ConvertStringSecurityDescriptorToSecurityDescriptorW); + PSECURITY_DESCRIPTOR desc = nullptr; + if (!pConvertStringSecurityDescriptorToSecurityDescriptorW( + str.c_str(), kSDDL_REVISION_1, &desc, nullptr)) { + const auto err = GetLastError(); + throwWindowsError( + (std::wstring(L"ConvertStringSecurityDescriptorToSecurityDescriptorW failed on \"") + + str + L'"').c_str(), + err); + } + return localItem(desc); +} + +std::wstring sdToString(PSECURITY_DESCRIPTOR sd) { + OsModule advapi32(L"advapi32.dll"); + GET_MODULE_PROC(advapi32, ConvertSecurityDescriptorToStringSecurityDescriptorW); + wchar_t *sdString = nullptr; + if (!pConvertSecurityDescriptorToStringSecurityDescriptorW( + sd, + kSDDL_REVISION_1, + OWNER_SECURITY_INFORMATION | + GROUP_SECURITY_INFORMATION | + DACL_SECURITY_INFORMATION, + &sdString, + nullptr)) { + throwWindowsError( + L"ConvertSecurityDescriptorToStringSecurityDescriptor failed"); + } + PointerLocal freer(sdString); + return std::wstring(sdString); +} + +// Vista added a useful flag to CreateNamedPipe, PIPE_REJECT_REMOTE_CLIENTS, +// that rejects remote connections. Return this flag on Vista, or return 0 +// otherwise. +DWORD rejectRemoteClientsPipeFlag() { + if (isAtLeastWindowsVista()) { + // MinGW lacks this flag; MinGW-w64 has it. + const DWORD kPIPE_REJECT_REMOTE_CLIENTS = 8; + return kPIPE_REJECT_REMOTE_CLIENTS; + } else { + trace("Omitting PIPE_REJECT_REMOTE_CLIENTS on pre-Vista OS"); + return 0; + } +} + +typedef BOOL WINAPI GetNamedPipeClientProcessId_t( + HANDLE Pipe, + PULONG ClientProcessId); + +std::tuple +getNamedPipeClientProcessId(HANDLE serverPipe) { + OsModule kernel32(L"kernel32.dll"); + const auto pGetNamedPipeClientProcessId = + reinterpret_cast( + kernel32.proc("GetNamedPipeClientProcessId")); + if (pGetNamedPipeClientProcessId == nullptr) { + return std::make_tuple( + GetNamedPipeClientProcessId_Result::UnsupportedOs, 0, 0); + } + ULONG pid = 0; + if (!pGetNamedPipeClientProcessId(serverPipe, &pid)) { + return std::make_tuple( + GetNamedPipeClientProcessId_Result::Failure, 0, GetLastError()); + } + return std::make_tuple( + GetNamedPipeClientProcessId_Result::Success, + static_cast(pid), + 0); +} diff --git a/src/libs/3rdparty/winpty/src/shared/WindowsSecurity.h b/src/libs/3rdparty/winpty/src/shared/WindowsSecurity.h new file mode 100644 index 00000000000..5f9d53aff6d --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/WindowsSecurity.h @@ -0,0 +1,104 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_WINDOWS_SECURITY_H +#define WINPTY_WINDOWS_SECURITY_H + +#include +#include + +#include +#include +#include +#include + +// PSID and PSECURITY_DESCRIPTOR are both pointers to void, but we want +// Sid and SecurityDescriptor to be different types. +struct SidTag { typedef PSID type; }; +struct AclTag { typedef PACL type; }; +struct SecurityDescriptorTag { typedef PSECURITY_DESCRIPTOR type; }; + +template +class SecurityItem { +public: + struct Impl { + virtual ~Impl() {} + }; + +private: + typedef typename T::type P; + P m_v; + std::unique_ptr m_pimpl; + +public: + P get() const { return m_v; } + operator bool() const { return m_v != nullptr; } + + SecurityItem() : m_v(nullptr) {} + SecurityItem(P v, std::unique_ptr &&pimpl) : + m_v(v), m_pimpl(std::move(pimpl)) {} + SecurityItem(SecurityItem &&other) : + m_v(other.m_v), m_pimpl(std::move(other.m_pimpl)) { + other.m_v = nullptr; + } + SecurityItem &operator=(SecurityItem &&other) { + m_v = other.m_v; + other.m_v = nullptr; + m_pimpl = std::move(other.m_pimpl); + return *this; + } +}; + +typedef SecurityItem Sid; +typedef SecurityItem Acl; +typedef SecurityItem SecurityDescriptor; + +Sid getOwnerSid(); +Sid wellKnownSid( + const wchar_t *debuggingName, + SID_IDENTIFIER_AUTHORITY authority, + BYTE authorityCount, + DWORD subAuthority0=0, + DWORD subAuthority1=0); +Sid builtinAdminsSid(); +Sid localSystemSid(); +Sid everyoneSid(); + +SecurityDescriptor createPipeSecurityDescriptorOwnerFullControl(); +SecurityDescriptor createPipeSecurityDescriptorOwnerFullControlEveryoneWrite(); +SecurityDescriptor getObjectSecurityDescriptor(HANDLE handle); + +std::wstring sidToString(PSID sid); +Sid stringToSid(const std::wstring &str); +SecurityDescriptor stringToSd(const std::wstring &str); +std::wstring sdToString(PSECURITY_DESCRIPTOR sd); + +DWORD rejectRemoteClientsPipeFlag(); + +enum class GetNamedPipeClientProcessId_Result { + Success, + Failure, + UnsupportedOs, +}; + +std::tuple +getNamedPipeClientProcessId(HANDLE serverPipe); + +#endif // WINPTY_WINDOWS_SECURITY_H diff --git a/src/libs/3rdparty/winpty/src/shared/WindowsVersion.cc b/src/libs/3rdparty/winpty/src/shared/WindowsVersion.cc new file mode 100644 index 00000000000..d89b00d8382 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/WindowsVersion.cc @@ -0,0 +1,252 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "WindowsVersion.h" + +#include +#include + +#include +#include +#include + +#include "DebugClient.h" +#include "OsModule.h" +#include "StringBuilder.h" +#include "StringUtil.h" +#include "WinptyAssert.h" +#include "WinptyException.h" + +namespace { + +typedef std::tuple Version; + +// This function can only return a version up to 6.2 unless the executable is +// manifested for a newer version of Windows. See the MSDN documentation for +// GetVersionEx. +OSVERSIONINFOEX getWindowsVersionInfo() { + // Allow use of deprecated functions (i.e. GetVersionEx). We need to use + // GetVersionEx for the old MinGW toolchain and with MSVC when it targets XP. + // Having two code paths makes code harder to test, and it's not obvious how + // to detect the presence of a new enough SDK. (Including ntverp.h and + // examining VER_PRODUCTBUILD apparently works, but even then, MinGW-w64 and + // MSVC seem to use different version numbers.) +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4996) +#endif + OSVERSIONINFOEX info = {}; + info.dwOSVersionInfoSize = sizeof(info); + const auto success = GetVersionEx(reinterpret_cast(&info)); + ASSERT(success && "GetVersionEx failed"); + return info; +#ifdef _MSC_VER +#pragma warning(pop) +#endif +} + +Version getWindowsVersion() { + const auto info = getWindowsVersionInfo(); + return Version(info.dwMajorVersion, info.dwMinorVersion); +} + +struct ModuleNotFound : WinptyException { + virtual const wchar_t *what() const WINPTY_NOEXCEPT override { + return L"ModuleNotFound"; + } +}; + +// Throws WinptyException on error. +std::wstring getSystemDirectory() { + wchar_t systemDirectory[MAX_PATH]; + const UINT size = GetSystemDirectoryW(systemDirectory, MAX_PATH); + if (size == 0) { + throwWindowsError(L"GetSystemDirectory failed"); + } else if (size >= MAX_PATH) { + throwWinptyException( + L"GetSystemDirectory: path is longer than MAX_PATH"); + } + return systemDirectory; +} + +#define GET_VERSION_DLL_API(name) \ + const auto p ## name = \ + reinterpret_cast( \ + versionDll.proc(#name)); \ + if (p ## name == nullptr) { \ + throwWinptyException(L ## #name L" is missing"); \ + } + +// Throws WinptyException on error. +VS_FIXEDFILEINFO getFixedFileInfo(const std::wstring &path) { + // version.dll is not a conventional KnownDll, so if we link to it, there's + // a danger of accidentally loading a malicious DLL. In a more typical + // application, perhaps we'd guard against this security issue by + // controlling which directories this code runs in (e.g. *not* the + // "Downloads" directory), but that's harder for the winpty library. + OsModule versionDll( + (getSystemDirectory() + L"\\version.dll").c_str(), + OsModule::LoadErrorBehavior::Throw); + GET_VERSION_DLL_API(GetFileVersionInfoSizeW); + GET_VERSION_DLL_API(GetFileVersionInfoW); + GET_VERSION_DLL_API(VerQueryValueW); + DWORD size = pGetFileVersionInfoSizeW(path.c_str(), nullptr); + if (!size) { + // I see ERROR_FILE_NOT_FOUND on Win7 and + // ERROR_RESOURCE_DATA_NOT_FOUND on WinXP. + if (GetLastError() == ERROR_FILE_NOT_FOUND || + GetLastError() == ERROR_RESOURCE_DATA_NOT_FOUND) { + throw ModuleNotFound(); + } else { + throwWindowsError( + (L"GetFileVersionInfoSizeW failed on " + path).c_str()); + } + } + std::unique_ptr versionBuffer(new char[size]); + if (!pGetFileVersionInfoW(path.c_str(), 0, size, versionBuffer.get())) { + throwWindowsError((L"GetFileVersionInfoW failed on " + path).c_str()); + } + VS_FIXEDFILEINFO *versionInfo = nullptr; + UINT versionInfoSize = 0; + if (!pVerQueryValueW( + versionBuffer.get(), L"\\", + reinterpret_cast(&versionInfo), &versionInfoSize) || + versionInfo == nullptr || + versionInfoSize != sizeof(VS_FIXEDFILEINFO) || + versionInfo->dwSignature != 0xFEEF04BD) { + throwWinptyException((L"VerQueryValueW failed on " + path).c_str()); + } + return *versionInfo; +} + +uint64_t productVersionFromInfo(const VS_FIXEDFILEINFO &info) { + return (static_cast(info.dwProductVersionMS) << 32) | + (static_cast(info.dwProductVersionLS)); +} + +uint64_t fileVersionFromInfo(const VS_FIXEDFILEINFO &info) { + return (static_cast(info.dwFileVersionMS) << 32) | + (static_cast(info.dwFileVersionLS)); +} + +std::string versionToString(uint64_t version) { + StringBuilder b(32); + b << ((uint16_t)(version >> 48)); + b << '.'; + b << ((uint16_t)(version >> 32)); + b << '.'; + b << ((uint16_t)(version >> 16)); + b << '.'; + b << ((uint16_t)(version >> 0)); + return b.str_moved(); +} + +} // anonymous namespace + +// Returns true for Windows Vista (or Windows Server 2008) or newer. +bool isAtLeastWindowsVista() { + return getWindowsVersion() >= Version(6, 0); +} + +// Returns true for Windows 7 (or Windows Server 2008 R2) or newer. +bool isAtLeastWindows7() { + return getWindowsVersion() >= Version(6, 1); +} + +// Returns true for Windows 8 (or Windows Server 2012) or newer. +bool isAtLeastWindows8() { + return getWindowsVersion() >= Version(6, 2); +} + +#define WINPTY_IA32 1 +#define WINPTY_X64 2 + +#if defined(_M_IX86) || defined(__i386__) +#define WINPTY_ARCH WINPTY_IA32 +#elif defined(_M_X64) || defined(__x86_64__) +#define WINPTY_ARCH WINPTY_X64 +#endif + +typedef BOOL WINAPI IsWow64Process_t(HANDLE hProcess, PBOOL Wow64Process); + +void dumpWindowsVersion() { + if (!isTracingEnabled()) { + return; + } + const auto info = getWindowsVersionInfo(); + StringBuilder b; + b << info.dwMajorVersion << '.' << info.dwMinorVersion + << '.' << info.dwBuildNumber << ' ' + << "SP" << info.wServicePackMajor << '.' << info.wServicePackMinor + << ' '; + switch (info.wProductType) { + case VER_NT_WORKSTATION: b << "Client"; break; + case VER_NT_DOMAIN_CONTROLLER: b << "DomainController"; break; + case VER_NT_SERVER: b << "Server"; break; + default: + b << "product=" << info.wProductType; break; + } + b << ' '; +#if WINPTY_ARCH == WINPTY_IA32 + b << "IA32"; + OsModule kernel32(L"kernel32.dll"); + IsWow64Process_t *pIsWow64Process = + reinterpret_cast( + kernel32.proc("IsWow64Process")); + if (pIsWow64Process != nullptr) { + BOOL result = false; + const BOOL success = pIsWow64Process(GetCurrentProcess(), &result); + if (!success) { + b << " WOW64:error"; + } else if (success && result) { + b << " WOW64"; + } + } else { + b << " WOW64:missingapi"; + } +#elif WINPTY_ARCH == WINPTY_X64 + b << "X64"; +#endif + const auto dllVersion = [](const wchar_t *dllPath) -> std::string { + try { + const auto info = getFixedFileInfo(dllPath); + StringBuilder fb(64); + fb << utf8FromWide(dllPath) << ':'; + fb << "F:" << versionToString(fileVersionFromInfo(info)) << '/' + << "P:" << versionToString(productVersionFromInfo(info)); + return fb.str_moved(); + } catch (const ModuleNotFound&) { + return utf8FromWide(dllPath) + ":none"; + } catch (const WinptyException &e) { + trace("Error getting %s version: %s", + utf8FromWide(dllPath).c_str(), utf8FromWide(e.what()).c_str()); + return utf8FromWide(dllPath) + ":error"; + } + }; + b << ' ' << dllVersion(L"kernel32.dll"); + // ConEmu provides a DLL that hooks many Windows APIs, especially console + // APIs. Its existence and version number could be useful in debugging. +#if WINPTY_ARCH == WINPTY_IA32 + b << ' ' << dllVersion(L"ConEmuHk.dll"); +#elif WINPTY_ARCH == WINPTY_X64 + b << ' ' << dllVersion(L"ConEmuHk64.dll"); +#endif + trace("Windows version: %s", b.c_str()); +} diff --git a/src/libs/3rdparty/winpty/src/shared/WindowsVersion.h b/src/libs/3rdparty/winpty/src/shared/WindowsVersion.h new file mode 100644 index 00000000000..a80798417eb --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/WindowsVersion.h @@ -0,0 +1,29 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_SHARED_WINDOWS_VERSION_H +#define WINPTY_SHARED_WINDOWS_VERSION_H + +bool isAtLeastWindowsVista(); +bool isAtLeastWindows7(); +bool isAtLeastWindows8(); +void dumpWindowsVersion(); + +#endif // WINPTY_SHARED_WINDOWS_VERSION_H diff --git a/src/libs/3rdparty/winpty/src/shared/WinptyAssert.cc b/src/libs/3rdparty/winpty/src/shared/WinptyAssert.cc new file mode 100644 index 00000000000..1ff0de475ab --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/WinptyAssert.cc @@ -0,0 +1,55 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "WinptyAssert.h" + +#include +#include + +#include "DebugClient.h" + +void assertTrace(const char *file, int line, const char *cond) { + trace("Assertion failed: %s, file %s, line %d", + cond, file, line); +} + +#ifdef WINPTY_AGENT_ASSERT + +void agentShutdown() { + HWND hwnd = GetConsoleWindow(); + if (hwnd != NULL) { + PostMessage(hwnd, WM_CLOSE, 0, 0); + Sleep(30000); + trace("Agent shutdown: WM_CLOSE did not end agent process"); + } else { + trace("Agent shutdown: GetConsoleWindow() is NULL"); + } + // abort() prints a message to the console, and if it is frozen, then the + // process would hang, so instead use exit(). (We shouldn't ever get here, + // though, because the WM_CLOSE message should have ended this process.) + exit(1); +} + +void agentAssertFail(const char *file, int line, const char *cond) { + assertTrace(file, line, cond); + agentShutdown(); +} + +#endif diff --git a/src/libs/3rdparty/winpty/src/shared/WinptyAssert.h b/src/libs/3rdparty/winpty/src/shared/WinptyAssert.h new file mode 100644 index 00000000000..b2b8b5e64c6 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/WinptyAssert.h @@ -0,0 +1,64 @@ +// Copyright (c) 2011-2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_ASSERT_H +#define WINPTY_ASSERT_H + +#ifdef WINPTY_AGENT_ASSERT + +void agentShutdown(); +void agentAssertFail(const char *file, int line, const char *cond); + +// Calling the standard assert() function does not work in the agent because +// the error message would be printed to the console, and the only way the +// user can see the console is via a working agent! Moreover, the console may +// be frozen, so attempting to write to it would block forever. This custom +// assert function instead sends the message to the DebugServer, then attempts +// to close the console, then quietly exits. +#define ASSERT(cond) \ + do { \ + if (!(cond)) { \ + agentAssertFail(__FILE__, __LINE__, #cond); \ + } \ + } while(0) + +#else + +void assertTrace(const char *file, int line, const char *cond); + +// In the other targets, log the assert failure to the debugserver, then fail +// using the ordinary assert mechanism. In case assert is compiled out, fail +// using abort. The amount of code inlined is unfortunate, but asserts aren't +// used much outside the agent. +#include +#include +#define ASSERT_CONDITION(cond) (false && (cond)) +#define ASSERT(cond) \ + do { \ + if (!(cond)) { \ + assertTrace(__FILE__, __LINE__, #cond); \ + assert(ASSERT_CONDITION(#cond)); \ + abort(); \ + } \ + } while(0) + +#endif + +#endif // WINPTY_ASSERT_H diff --git a/src/libs/3rdparty/winpty/src/shared/WinptyException.cc b/src/libs/3rdparty/winpty/src/shared/WinptyException.cc new file mode 100644 index 00000000000..d0d48823d22 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/WinptyException.cc @@ -0,0 +1,57 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "WinptyException.h" + +#include +#include + +#include "StringBuilder.h" + +namespace { + +class ExceptionImpl : public WinptyException { +public: + ExceptionImpl(const wchar_t *what) : + m_what(std::make_shared(what)) {} + virtual const wchar_t *what() const WINPTY_NOEXCEPT override { + return m_what->c_str(); + } +private: + // Using a shared_ptr ensures that copying the object raises no exception. + std::shared_ptr m_what; +}; + +} // anonymous namespace + +void throwWinptyException(const wchar_t *what) { + throw ExceptionImpl(what); +} + +void throwWindowsError(const wchar_t *prefix, DWORD errorCode) { + WStringBuilder sb(64); + if (prefix != nullptr) { + sb << prefix << L": "; + } + // It might make sense to use FormatMessage here, but IIRC, its API is hard + // to figure out. + sb << L"Windows error " << errorCode; + throwWinptyException(sb.c_str()); +} diff --git a/src/libs/3rdparty/winpty/src/shared/WinptyException.h b/src/libs/3rdparty/winpty/src/shared/WinptyException.h new file mode 100644 index 00000000000..ec353369e5b --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/WinptyException.h @@ -0,0 +1,43 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_EXCEPTION_H +#define WINPTY_EXCEPTION_H + +#include + +#if defined(__GNUC__) +#define WINPTY_NOEXCEPT noexcept +#elif defined(_MSC_VER) && _MSC_VER >= 1900 +#define WINPTY_NOEXCEPT noexcept +#else +#define WINPTY_NOEXCEPT +#endif + +class WinptyException { +public: + virtual const wchar_t *what() const WINPTY_NOEXCEPT = 0; + virtual ~WinptyException() {} +}; + +void throwWinptyException(const wchar_t *what); +void throwWindowsError(const wchar_t *prefix, DWORD error=GetLastError()); + +#endif // WINPTY_EXCEPTION_H diff --git a/src/libs/3rdparty/winpty/src/shared/WinptyVersion.cc b/src/libs/3rdparty/winpty/src/shared/WinptyVersion.cc new file mode 100644 index 00000000000..76bb8a584d2 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/WinptyVersion.cc @@ -0,0 +1,42 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "WinptyVersion.h" + +#include +#include + +#include "DebugClient.h" + +// This header is auto-generated by either the Makefile (Unix) or +// UpdateGenVersion.bat (gyp). It is placed in a 'gen' directory, which is +// added to the search path. +#include "GenVersion.h" + +void dumpVersionToStdout() { + printf("winpty version %s\n", GenVersion_Version); + printf("commit %s\n", GenVersion_Commit); +} + +void dumpVersionToTrace() { + trace("winpty version %s (commit %s)", + GenVersion_Version, + GenVersion_Commit); +} diff --git a/src/libs/3rdparty/winpty/src/shared/WinptyVersion.h b/src/libs/3rdparty/winpty/src/shared/WinptyVersion.h new file mode 100644 index 00000000000..e6224d7b847 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/WinptyVersion.h @@ -0,0 +1,27 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_VERSION_H +#define WINPTY_VERSION_H + +void dumpVersionToStdout(); +void dumpVersionToTrace(); + +#endif // WINPTY_VERSION_H diff --git a/src/libs/3rdparty/winpty/src/shared/winpty_snprintf.h b/src/libs/3rdparty/winpty/src/shared/winpty_snprintf.h new file mode 100644 index 00000000000..e716f245e8c --- /dev/null +++ b/src/libs/3rdparty/winpty/src/shared/winpty_snprintf.h @@ -0,0 +1,99 @@ +// Copyright (c) 2016 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef WINPTY_SNPRINTF_H +#define WINPTY_SNPRINTF_H + +#include +#include +#include + +#include "WinptyAssert.h" + +#if defined(__CYGWIN__) || defined(__MSYS__) +#define WINPTY_SNPRINTF_FORMAT(fmtarg, vararg) \ + __attribute__((format(printf, (fmtarg), ((vararg))))) +#elif defined(__GNUC__) +#define WINPTY_SNPRINTF_FORMAT(fmtarg, vararg) \ + __attribute__((format(ms_printf, (fmtarg), ((vararg))))) +#else +#define WINPTY_SNPRINTF_FORMAT(fmtarg, vararg) +#endif + +// Returns a value between 0 and size - 1 (inclusive) on success. Returns -1 +// on failure (including truncation). The output buffer is always +// NUL-terminated. +inline int +winpty_vsnprintf(char *out, size_t size, const char *fmt, va_list ap) { + ASSERT(size > 0); + out[0] = '\0'; +#if defined(_MSC_VER) && _MSC_VER < 1900 + // MSVC 2015 added a C99-conforming vsnprintf. + int count = _vsnprintf_s(out, size, _TRUNCATE, fmt, ap); +#else + // MinGW configurations frequently provide a vsnprintf function that simply + // calls one of the MS _vsnprintf* functions, which are not C99 conformant. + int count = vsnprintf(out, size, fmt, ap); +#endif + if (count < 0 || static_cast(count) >= size) { + // On truncation, some *printf* implementations return the + // non-truncated size, but other implementations returns -1. Return + // -1 for consistency. + count = -1; + // Guarantee NUL termination. + out[size - 1] = '\0'; + } else { + // Guarantee NUL termination. + out[count] = '\0'; + } + return count; +} + +// Wraps winpty_vsnprintf. +inline int winpty_snprintf(char *out, size_t size, const char *fmt, ...) + WINPTY_SNPRINTF_FORMAT(3, 4); +inline int winpty_snprintf(char *out, size_t size, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + const int count = winpty_vsnprintf(out, size, fmt, ap); + va_end(ap); + return count; +} + +// Wraps winpty_vsnprintf with automatic size determination. +template +int winpty_vsnprintf(char (&out)[size], const char *fmt, va_list ap) { + return winpty_vsnprintf(out, size, fmt, ap); +} + +// Wraps winpty_vsnprintf with automatic size determination. +template +int winpty_snprintf(char (&out)[size], const char *fmt, ...) + WINPTY_SNPRINTF_FORMAT(2, 3); +template +int winpty_snprintf(char (&out)[size], const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + const int count = winpty_vsnprintf(out, size, fmt, ap); + va_end(ap); + return count; +} + +#endif // WINPTY_SNPRINTF_H diff --git a/src/libs/3rdparty/winpty/src/subdir.mk b/src/libs/3rdparty/winpty/src/subdir.mk new file mode 100644 index 00000000000..9ae8031b084 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/subdir.mk @@ -0,0 +1,5 @@ +include src/agent/subdir.mk +include src/debugserver/subdir.mk +include src/libwinpty/subdir.mk +include src/tests/subdir.mk +include src/unix-adapter/subdir.mk diff --git a/src/libs/3rdparty/winpty/src/tests/subdir.mk b/src/libs/3rdparty/winpty/src/tests/subdir.mk new file mode 100644 index 00000000000..18799c4a5a0 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/tests/subdir.mk @@ -0,0 +1,28 @@ +# Copyright (c) 2015 Ryan Prichard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +build/%.exe : src/tests/%.cc build/winpty.dll + $(info Building $@) + @$(MINGW_CXX) $(MINGW_CXXFLAGS) $(MINGW_LDFLAGS) -o $@ $^ + +TEST_PROGRAMS = \ + build/trivial_test.exe + +-include $(TEST_PROGRAMS:.exe=.d) diff --git a/src/libs/3rdparty/winpty/src/tests/trivial_test.cc b/src/libs/3rdparty/winpty/src/tests/trivial_test.cc new file mode 100644 index 00000000000..2188a4befb1 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/tests/trivial_test.cc @@ -0,0 +1,158 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include + +#include +#include +#include +#include +#include +#include + +#include "../include/winpty.h" +#include "../shared/DebugClient.h" + +static std::vector filterContent( + const std::vector &content) { + std::vector result; + auto it = content.begin(); + const auto itEnd = content.end(); + while (it < itEnd) { + if (*it == '\r') { + // Filter out carriage returns. Sometimes the output starts with + // a single CR; other times, it has multiple CRs. + it++; + } else if (*it == '\x1b' && (it + 1) < itEnd && *(it + 1) == '[') { + // Filter out escape sequences. They have no interior letters and + // end with a single letter. + it += 2; + while (it < itEnd && !isalpha(*it)) { + it++; + } + it++; + } else { + // Let everything else through. + result.push_back(*it); + it++; + } + } + return result; +} + +// Read bytes from the non-overlapped file handle until the file is closed or +// until an I/O error occurs. +static std::vector readAll(HANDLE handle) { + unsigned char buf[1024]; + std::vector result; + while (true) { + DWORD amount = 0; + BOOL ret = ReadFile(handle, buf, sizeof(buf), &amount, nullptr); + if (!ret || amount == 0) { + break; + } + result.insert(result.end(), buf, buf + amount); + } + return result; +} + +static void parentTest() { + wchar_t program[1024]; + wchar_t cmdline[1024]; + GetModuleFileNameW(nullptr, program, 1024); + + { + // XXX: We'd like to use swprintf, which is part of C99 and takes a + // size_t maxlen argument. MinGW-w64 has this function, as does MSVC. + // The old MinGW doesn't, though -- instead, it apparently provides an + // swprintf taking no maxlen argument. This *might* be a regression? + // (There is also no swnprintf, but that function is obsolescent with a + // correct swprintf, and it isn't in POSIX or ISO C.) + // + // Visual C++ 6 also provided this non-conformant swprintf, and I'm + // guessing MSVCRT.DLL does too. (My impression is that the old MinGW + // prefers to rely on MSVCRT.DLL for convenience?) + // + // I could compile differently for old MinGW, but what if it fixes its + // function later? Instead, use a workaround. It's starting to make + // sense to drop MinGW support in favor of MinGW-w64. This is too + // annoying. + // + // grepbait: OLD-MINGW / WINPTY_TARGET_MSYS1 + cmdline[0] = L'\0'; + wcscat(cmdline, L"\""); + wcscat(cmdline, program); + wcscat(cmdline, L"\" CHILD"); + } + // swnprintf(cmdline, sizeof(cmdline) / sizeof(cmdline[0]), + // L"\"%ls\" CHILD", program); + + auto agentCfg = winpty_config_new(0, nullptr); + assert(agentCfg != nullptr); + auto pty = winpty_open(agentCfg, nullptr); + assert(pty != nullptr); + winpty_config_free(agentCfg); + + HANDLE conin = CreateFileW( + winpty_conin_name(pty), + GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); + HANDLE conout = CreateFileW( + winpty_conout_name(pty), + GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr); + assert(conin != INVALID_HANDLE_VALUE); + assert(conout != INVALID_HANDLE_VALUE); + + auto spawnCfg = winpty_spawn_config_new( + WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, program, cmdline, + nullptr, nullptr, nullptr); + assert(spawnCfg != nullptr); + HANDLE process = nullptr; + BOOL spawnSuccess = winpty_spawn( + pty, spawnCfg, &process, nullptr, nullptr, nullptr); + assert(spawnSuccess && process != nullptr); + + auto content = readAll(conout); + content = filterContent(content); + + std::vector expectedContent = { + 'H', 'I', '\n', 'X', 'Y', '\n' + }; + DWORD exitCode = 0; + assert(GetExitCodeProcess(process, &exitCode) && exitCode == 42); + CloseHandle(process); + CloseHandle(conin); + CloseHandle(conout); + assert(content == expectedContent); + winpty_free(pty); +} + +static void childTest() { + printf("HI\nXY\n"); + exit(42); +} + +int main(int argc, char *argv[]) { + if (argc == 1) { + parentTest(); + } else { + childTest(); + } + return 0; +} diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.cc b/src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.cc new file mode 100644 index 00000000000..39f1e096850 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.cc @@ -0,0 +1,114 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "InputHandler.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "../shared/DebugClient.h" +#include "Util.h" +#include "WakeupFd.h" + +InputHandler::InputHandler( + HANDLE conin, int inputfd, WakeupFd &completionWakeup) : + m_conin(conin), + m_inputfd(inputfd), + m_completionWakeup(completionWakeup), + m_threadHasBeenJoined(false), + m_shouldShutdown(0), + m_threadCompleted(0) +{ + pthread_create(&m_thread, NULL, InputHandler::threadProcS, this); +} + +void InputHandler::shutdown() { + startShutdown(); + if (!m_threadHasBeenJoined) { + int ret = pthread_join(m_thread, NULL); + assert(ret == 0 && "pthread_join failed"); + m_threadHasBeenJoined = true; + } +} + +void InputHandler::threadProc() { + std::vector buffer(4096); + fd_set readfds; + FD_ZERO(&readfds); + while (true) { + // Handle shutdown. + m_wakeup.reset(); + if (m_shouldShutdown) { + trace("InputHandler: shutting down"); + break; + } + + // Block until data arrives. + { + const int max_fd = std::max(m_inputfd, m_wakeup.fd()); + FD_SET(m_inputfd, &readfds); + FD_SET(m_wakeup.fd(), &readfds); + selectWrapper("InputHandler", max_fd + 1, &readfds); + if (!FD_ISSET(m_inputfd, &readfds)) { + continue; + } + } + + const int numRead = read(m_inputfd, &buffer[0], buffer.size()); + if (numRead == -1 && errno == EINTR) { + // Apparently, this read is interrupted on Cygwin 1.7 by a SIGWINCH + // signal even though I set the SA_RESTART flag on the handler. + continue; + } + + // tty is closed, or the read failed for some unexpected reason. + if (numRead <= 0) { + trace("InputHandler: tty read failed: numRead=%d", numRead); + break; + } + + DWORD written = 0; + BOOL ret = WriteFile(m_conin, + &buffer[0], numRead, + &written, NULL); + if (!ret || written != static_cast(numRead)) { + if (!ret && GetLastError() == ERROR_BROKEN_PIPE) { + trace("InputHandler: pipe closed: written=%u", + static_cast(written)); + } else { + trace("InputHandler: write failed: " + "ret=%d lastError=0x%x numRead=%d written=%u", + ret, + static_cast(GetLastError()), + numRead, + static_cast(written)); + } + break; + } + } + m_threadCompleted = 1; + m_completionWakeup.set(); +} diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.h b/src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.h new file mode 100644 index 00000000000..9c3f540d634 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.h @@ -0,0 +1,56 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef UNIX_ADAPTER_INPUT_HANDLER_H +#define UNIX_ADAPTER_INPUT_HANDLER_H + +#include +#include +#include + +#include "WakeupFd.h" + +// Connect a Cygwin blocking fd to winpty CONIN. +class InputHandler { +public: + InputHandler(HANDLE conin, int inputfd, WakeupFd &completionWakeup); + ~InputHandler() { shutdown(); } + bool isComplete() { return m_threadCompleted; } + void startShutdown() { m_shouldShutdown = 1; m_wakeup.set(); } + void shutdown(); + +private: + static void *threadProcS(void *pvthis) { + reinterpret_cast(pvthis)->threadProc(); + return NULL; + } + void threadProc(); + + HANDLE m_conin; + int m_inputfd; + pthread_t m_thread; + WakeupFd &m_completionWakeup; + WakeupFd m_wakeup; + bool m_threadHasBeenJoined; + volatile sig_atomic_t m_shouldShutdown; + volatile sig_atomic_t m_threadCompleted; +}; + +#endif // UNIX_ADAPTER_INPUT_HANDLER_H diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.cc b/src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.cc new file mode 100644 index 00000000000..573b8adced3 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "OutputHandler.h" + +#include +#include +#include +#include + +#include +#include + +#include "../shared/DebugClient.h" +#include "Util.h" +#include "WakeupFd.h" + +OutputHandler::OutputHandler( + HANDLE conout, int outputfd, WakeupFd &completionWakeup) : + m_conout(conout), + m_outputfd(outputfd), + m_completionWakeup(completionWakeup), + m_threadHasBeenJoined(false), + m_threadCompleted(0) +{ + pthread_create(&m_thread, NULL, OutputHandler::threadProcS, this); +} + +void OutputHandler::shutdown() { + if (!m_threadHasBeenJoined) { + int ret = pthread_join(m_thread, NULL); + assert(ret == 0 && "pthread_join failed"); + m_threadHasBeenJoined = true; + } +} + +void OutputHandler::threadProc() { + std::vector buffer(4096); + while (true) { + DWORD numRead = 0; + BOOL ret = ReadFile(m_conout, + &buffer[0], buffer.size(), + &numRead, NULL); + if (!ret || numRead == 0) { + if (!ret && GetLastError() == ERROR_BROKEN_PIPE) { + trace("OutputHandler: pipe closed: numRead=%u", + static_cast(numRead)); + } else { + trace("OutputHandler: read failed: " + "ret=%d lastError=0x%x numRead=%u", + ret, + static_cast(GetLastError()), + static_cast(numRead)); + } + break; + } + if (!writeAll(m_outputfd, &buffer[0], numRead)) { + break; + } + } + m_threadCompleted = 1; + m_completionWakeup.set(); +} diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.h b/src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.h new file mode 100644 index 00000000000..48241c55387 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.h @@ -0,0 +1,53 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef UNIX_ADAPTER_OUTPUT_HANDLER_H +#define UNIX_ADAPTER_OUTPUT_HANDLER_H + +#include +#include +#include + +#include "WakeupFd.h" + +// Connect winpty CONOUT/CONERR to a Cygwin blocking fd. +class OutputHandler { +public: + OutputHandler(HANDLE conout, int outputfd, WakeupFd &completionWakeup); + ~OutputHandler() { shutdown(); } + bool isComplete() { return m_threadCompleted; } + void shutdown(); + +private: + static void *threadProcS(void *pvthis) { + reinterpret_cast(pvthis)->threadProc(); + return NULL; + } + void threadProc(); + + HANDLE m_conout; + int m_outputfd; + pthread_t m_thread; + WakeupFd &m_completionWakeup; + bool m_threadHasBeenJoined; + volatile sig_atomic_t m_threadCompleted; +}; + +#endif // UNIX_ADAPTER_OUTPUT_HANDLER_H diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/Util.cc b/src/libs/3rdparty/winpty/src/unix-adapter/Util.cc new file mode 100644 index 00000000000..e13f84a5299 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/unix-adapter/Util.cc @@ -0,0 +1,86 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "Util.h" + +#include +#include +#include +#include +#include + +#include "../shared/DebugClient.h" + +// Write the entire buffer, restarting it as necessary. +bool writeAll(int fd, const void *buffer, size_t size) { + size_t written = 0; + while (written < size) { + int ret = write(fd, + reinterpret_cast(buffer) + written, + size - written); + if (ret == -1 && errno == EINTR) { + continue; + } + if (ret <= 0) { + trace("write failed: " + "fd=%d errno=%d size=%u written=%d ret=%d", + fd, + errno, + static_cast(size), + static_cast(written), + ret); + return false; + } + assert(static_cast(ret) <= size - written); + written += ret; + } + assert(written == size); + return true; +} + +bool writeStr(int fd, const char *str) { + return writeAll(fd, str, strlen(str)); +} + +void selectWrapper(const char *diagName, int nfds, fd_set *readfds) { + int ret = select(nfds, readfds, NULL, NULL, NULL); + if (ret < 0) { + if (errno == EINTR) { + FD_ZERO(readfds); + return; + } +#ifdef WINPTY_TARGET_MSYS1 + // The select system call sometimes fails with EAGAIN instead of EINTR. + // This apparantly only happens with the old Cygwin fork "MSYS" used in + // the mingw.org project. select is not supposed to fail with EAGAIN, + // and EAGAIN does not make much sense as an error code. (The whole + // point of select is to block.) + if (errno == EAGAIN) { + trace("%s select returned EAGAIN: interpreting like EINTR", + diagName); + FD_ZERO(readfds); + return; + } +#endif + fprintf(stderr, "Internal error: %s select failed: " + "error %d", diagName, errno); + abort(); + } +} diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/Util.h b/src/libs/3rdparty/winpty/src/unix-adapter/Util.h new file mode 100644 index 00000000000..cadb4c82a96 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/unix-adapter/Util.h @@ -0,0 +1,31 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef UNIX_ADAPTER_UTIL_H +#define UNIX_ADAPTER_UTIL_H + +#include +#include + +bool writeAll(int fd, const void *buffer, size_t size); +bool writeStr(int fd, const char *str); +void selectWrapper(const char *diagName, int nfds, fd_set *readfds); + +#endif // UNIX_ADAPTER_UTIL_H diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.cc b/src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.cc new file mode 100644 index 00000000000..6b473790153 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.cc @@ -0,0 +1,70 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "WakeupFd.h" + +#include +#include +#include +#include +#include + +static void setFdNonBlock(int fd) { + int status = fcntl(fd, F_GETFL); + fcntl(fd, F_SETFL, status | O_NONBLOCK); +} + +WakeupFd::WakeupFd() { + int pipeFd[2]; + if (pipe(pipeFd) != 0) { + perror("Could not create internal wakeup pipe"); + abort(); + } + m_pipeReadFd = pipeFd[0]; + m_pipeWriteFd = pipeFd[1]; + setFdNonBlock(m_pipeReadFd); + setFdNonBlock(m_pipeWriteFd); +} + +WakeupFd::~WakeupFd() { + close(m_pipeReadFd); + close(m_pipeWriteFd); +} + +void WakeupFd::set() { + char dummy = 0; + int ret; + do { + ret = write(m_pipeWriteFd, &dummy, 1); + } while (ret < 0 && errno == EINTR); +} + +void WakeupFd::reset() { + char tmpBuf[256]; + while (true) { + int amount = read(m_pipeReadFd, tmpBuf, sizeof(tmpBuf)); + if (amount < 0 && errno == EAGAIN) { + break; + } else if (amount <= 0) { + perror("error reading from internal wakeup pipe"); + abort(); + } + } +} diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.h b/src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.h new file mode 100644 index 00000000000..dd8d362aa10 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.h @@ -0,0 +1,42 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#ifndef UNIX_ADAPTER_WAKEUP_FD_H +#define UNIX_ADAPTER_WAKEUP_FD_H + +class WakeupFd { +public: + WakeupFd(); + ~WakeupFd(); + int fd() { return m_pipeReadFd; } + void set(); + void reset(); + +private: + // Do not allow copying the WakeupFd object. + WakeupFd(const WakeupFd &other); + WakeupFd &operator=(const WakeupFd &other); + +private: + int m_pipeReadFd; + int m_pipeWriteFd; +}; + +#endif // UNIX_ADAPTER_WAKEUP_FD_H diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/main.cc b/src/libs/3rdparty/winpty/src/unix-adapter/main.cc new file mode 100644 index 00000000000..992cb70e449 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/unix-adapter/main.cc @@ -0,0 +1,729 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +// MSYS's sys/cygwin.h header only declares cygwin_internal if WINVER is +// defined, which is defined in windows.h. Therefore, include windows.h early. +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include "../shared/DebugClient.h" +#include "../shared/UnixCtrlChars.h" +#include "../shared/WinptyVersion.h" +#include "InputHandler.h" +#include "OutputHandler.h" +#include "Util.h" +#include "WakeupFd.h" + +#define CSI "\x1b[" + +static WakeupFd *g_mainWakeup = NULL; + +static WakeupFd &mainWakeup() +{ + if (g_mainWakeup == NULL) { + static const char msg[] = "Internal error: g_mainWakeup is NULL\r\n"; + write(STDERR_FILENO, msg, sizeof(msg) - 1); + abort(); + } + return *g_mainWakeup; +} + +struct SavedTermiosMode { + int count; + bool valid[3]; + termios mode[3]; +}; + +// Put the input terminal into non-canonical mode. +static SavedTermiosMode setRawTerminalMode( + bool allowNonTtys, bool setStdout, bool setStderr) +{ + SavedTermiosMode ret; + const char *const kNames[3] = { "stdin", "stdout", "stderr" }; + + ret.valid[0] = true; + ret.valid[1] = setStdout; + ret.valid[2] = setStderr; + + for (int i = 0; i < 3; ++i) { + if (!ret.valid[i]) { + continue; + } + if (!isatty(i)) { + ret.valid[i] = false; + if (!allowNonTtys) { + fprintf(stderr, "%s is not a tty\n", kNames[i]); + exit(1); + } + } else { + ret.valid[i] = true; + if (tcgetattr(i, &ret.mode[i]) < 0) { + perror("tcgetattr failed"); + exit(1); + } + } + } + + if (ret.valid[STDIN_FILENO]) { + termios buf; + if (tcgetattr(STDIN_FILENO, &buf) < 0) { + perror("tcgetattr failed"); + exit(1); + } + buf.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + buf.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + buf.c_cflag &= ~(CSIZE | PARENB); + buf.c_cflag |= CS8; + buf.c_cc[VMIN] = 1; // blocking read + buf.c_cc[VTIME] = 0; + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &buf) < 0) { + fprintf(stderr, "tcsetattr failed\n"); + exit(1); + } + } + + for (int i = STDOUT_FILENO; i <= STDERR_FILENO; ++i) { + if (!ret.valid[i]) { + continue; + } + termios buf; + if (tcgetattr(i, &buf) < 0) { + perror("tcgetattr failed"); + exit(1); + } + buf.c_cflag &= ~(CSIZE | PARENB); + buf.c_cflag |= CS8; + buf.c_oflag &= ~OPOST; + if (tcsetattr(i, TCSAFLUSH, &buf) < 0) { + fprintf(stderr, "tcsetattr failed\n"); + exit(1); + } + } + + return ret; +} + +static void restoreTerminalMode(const SavedTermiosMode &original) +{ + for (int i = 0; i < 3; ++i) { + if (!original.valid[i]) { + continue; + } + if (tcsetattr(i, TCSAFLUSH, &original.mode[i]) < 0) { + perror("error restoring terminal mode"); + exit(1); + } + } +} + +static void debugShowKey(bool allowNonTtys) +{ + printf("\nPress any keys -- Ctrl-D exits\n\n"); + const SavedTermiosMode saved = + setRawTerminalMode(allowNonTtys, false, false); + char buf[128]; + while (true) { + const ssize_t len = read(STDIN_FILENO, buf, sizeof(buf)); + if (len <= 0) { + break; + } + for (int i = 0; i < len; ++i) { + char ctrl = decodeUnixCtrlChar(buf[i]); + if (ctrl == '\0') { + putchar(buf[i]); + } else { + putchar('^'); + putchar(ctrl); + } + } + for (int i = 0; i < len; ++i) { + unsigned char uch = buf[i]; + printf("\t%3d %04o 0x%02x\n", uch, uch, uch); + fflush(stdout); + } + if (buf[0] == 4) { + // Ctrl-D + break; + } + } + restoreTerminalMode(saved); +} + +static void terminalResized(int signo) +{ + mainWakeup().set(); +} + +static void registerResizeSignalHandler() +{ + struct sigaction resizeSigAct; + memset(&resizeSigAct, 0, sizeof(resizeSigAct)); + resizeSigAct.sa_handler = terminalResized; + resizeSigAct.sa_flags = SA_RESTART; + sigaction(SIGWINCH, &resizeSigAct, NULL); +} + +// Convert the path to a Win32 path if it is a POSIX path, and convert slashes +// to backslashes. +static std::string convertPosixPathToWin(const std::string &path) +{ + char *tmp; +#if defined(CYGWIN_VERSION_CYGWIN_CONV) && \ + CYGWIN_VERSION_API_MINOR >= CYGWIN_VERSION_CYGWIN_CONV + // MSYS2 and versions of Cygwin released after 2009 or so use this API. + // The original MSYS still lacks this API. + ssize_t newSize = cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE, + path.c_str(), NULL, 0); + assert(newSize >= 0); + tmp = new char[newSize + 1]; + ssize_t success = cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE, + path.c_str(), tmp, newSize + 1); + assert(success == 0); +#else + // In the current Cygwin header file, this API is documented as deprecated + // because it's restricted to paths of MAX_PATH length. In the CVS version + // of MSYS, the newer API doesn't exist, and this older API is implemented + // using msys_p2w, which seems like it would handle paths larger than + // MAX_PATH, but there's no way to query how large the new path is. + // Hopefully, this is large enough. + tmp = new char[MAX_PATH + path.size()]; + cygwin_conv_to_win32_path(path.c_str(), tmp); +#endif + for (int i = 0; tmp[i] != '\0'; ++i) { + if (tmp[i] == '/') + tmp[i] = '\\'; + } + std::string ret(tmp); + delete [] tmp; + return ret; +} + +static std::string resolvePath(const std::string &path) +{ + char ret[PATH_MAX]; + ret[0] = '\0'; + if (realpath(path.c_str(), ret) != ret) { + return std::string(); + } + return ret; +} + +template +static bool endsWith(const std::string &path, const char (&suf)[N]) +{ + const size_t suffixLen = N - 1; + char actualSuf[N]; + if (path.size() < suffixLen) { + return false; + } + strcpy(actualSuf, &path.c_str()[path.size() - suffixLen]); + for (size_t i = 0; i < suffixLen; ++i) { + actualSuf[i] = tolower(actualSuf[i]); + } + return !strcmp(actualSuf, suf); +} + +static std::string findProgram( + const char *winptyProgName, + const std::string &prog) +{ + std::string candidate; + if (prog.find('/') == std::string::npos && + prog.find('\\') == std::string::npos) { + // XXX: It would be nice to use a lambda here (once/if old MSYS support + // is dropped). + // Search the PATH. + const char *const pathVar = getenv("PATH"); + const std::string pathList(pathVar ? pathVar : ""); + size_t elpos = 0; + while (true) { + const size_t elend = pathList.find(':', elpos); + candidate = pathList.substr(elpos, elend - elpos); + if (!candidate.empty() && *(candidate.end() - 1) != '/') { + candidate += '/'; + } + candidate += prog; + candidate = resolvePath(candidate); + if (!candidate.empty()) { + int perm = X_OK; + if (endsWith(candidate, ".bat") || endsWith(candidate, ".cmd")) { +#ifdef __MSYS__ + // In MSYS/MSYS2, batch files don't have the execute bit + // set, so just check that they're readable. + perm = R_OK; +#endif + } else if (endsWith(candidate, ".com") || endsWith(candidate, ".exe")) { + // Do nothing. + } else { + // Make the exe extension explicit so that we don't try to + // run shell scripts with CreateProcess/winpty_spawn. + candidate += ".exe"; + } + if (!access(candidate.c_str(), perm)) { + break; + } + } + if (elend == std::string::npos) { + fprintf(stderr, "%s: error: cannot start '%s': Not found in PATH\n", + winptyProgName, prog.c_str()); + exit(1); + } else { + elpos = elend + 1; + } + } + } else { + candidate = resolvePath(prog); + if (candidate.empty()) { + std::string errstr(strerror(errno)); + fprintf(stderr, "%s: error: cannot start '%s': %s\n", + winptyProgName, prog.c_str(), errstr.c_str()); + exit(1); + } + } + return convertPosixPathToWin(candidate); +} + +// Convert argc/argv into a Win32 command-line following the escaping convention +// documented on MSDN. (e.g. see CommandLineToArgvW documentation) +static std::string argvToCommandLine(const std::vector &argv) +{ + std::string result; + for (size_t argIndex = 0; argIndex < argv.size(); ++argIndex) { + if (argIndex > 0) + result.push_back(' '); + const char *arg = argv[argIndex].c_str(); + const bool quote = + strchr(arg, ' ') != NULL || + strchr(arg, '\t') != NULL || + *arg == '\0'; + if (quote) + result.push_back('\"'); + int bsCount = 0; + for (const char *p = arg; *p != '\0'; ++p) { + if (*p == '\\') { + bsCount++; + } else if (*p == '\"') { + result.append(bsCount * 2 + 1, '\\'); + result.push_back('\"'); + bsCount = 0; + } else { + result.append(bsCount, '\\'); + bsCount = 0; + result.push_back(*p); + } + } + if (quote) { + result.append(bsCount * 2, '\\'); + result.push_back('\"'); + } else { + result.append(bsCount, '\\'); + } + } + return result; +} + +static wchar_t *heapMbsToWcs(const char *text) +{ + // Calling mbstowcs with a NULL first argument seems to be broken on MSYS. + // Instead of returning the size of the converted string, it returns 0. + // Using strlen(text) * 2 is probably big enough. + size_t maxLen = strlen(text) * 2 + 1; + wchar_t *ret = new wchar_t[maxLen]; + size_t len = mbstowcs(ret, text, maxLen); + assert(len != (size_t)-1 && len < maxLen); + return ret; +} + +static char *heapWcsToMbs(const wchar_t *text) +{ + // Calling wcstombs with a NULL first argument seems to be broken on MSYS. + // Instead of returning the size of the converted string, it returns 0. + // Using wcslen(text) * 3 is big enough for UTF-8 and probably other + // encodings. For UTF-8, codepoints that fit in a single wchar + // (U+0000 to U+FFFF) are encoded using 1-3 bytes. The remaining code + // points needs two wchar's and are encoded using 4 bytes. + size_t maxLen = wcslen(text) * 3 + 1; + char *ret = new char[maxLen]; + size_t len = wcstombs(ret, text, maxLen); + if (len == (size_t)-1 || len >= maxLen) { + delete [] ret; + return NULL; + } else { + return ret; + } +} + +static std::string wcsToMbs(const wchar_t *text) +{ + std::string ret; + const char *ptr = heapWcsToMbs(text); + if (ptr != NULL) { + ret = ptr; + delete [] ptr; + } + return ret; +} + +void setupWin32Environment() +{ + std::map varsToCopy; + const char *vars[] = { + "WINPTY_DEBUG", + "WINPTY_SHOW_CONSOLE", + NULL + }; + for (int i = 0; vars[i] != NULL; ++i) { + const char *cstr = getenv(vars[i]); + if (cstr != NULL && cstr[0] != '\0') { + varsToCopy[vars[i]] = cstr; + } + } + +#if defined(__MSYS__) && CYGWIN_VERSION_API_MINOR >= 48 || \ + !defined(__MSYS__) && CYGWIN_VERSION_API_MINOR >= 153 + // Use CW_SYNC_WINENV to copy the Unix environment to the Win32 + // environment. The command performs special translation on some variables + // (such as PATH and TMP). It also copies the debugging environment + // variables. + // + // Note that the API minor versions have diverged in Cygwin and MSYS. + // CW_SYNC_WINENV was added to Cygwin in version 153. (Cygwin's + // include/cygwin/version.h says that CW_SETUP_WINENV was added in 153. + // The flag was renamed 8 days after it was added, but the API docs weren't + // updated.) The flag was added to MSYS in version 48. + // + // Also, in my limited testing, this call seems to be necessary with Cygwin + // but unnecessary with MSYS. Perhaps MSYS is automatically syncing the + // Unix environment with the Win32 environment before starting console.exe? + // It shouldn't hurt to call it for MSYS. + cygwin_internal(CW_SYNC_WINENV); +#endif + + // Copy debugging environment variables from the Cygwin environment + // to the Win32 environment so the agent will inherit it. + for (std::map::iterator it = varsToCopy.begin(); + it != varsToCopy.end(); + ++it) { + wchar_t *nameW = heapMbsToWcs(it->first.c_str()); + wchar_t *valueW = heapMbsToWcs(it->second.c_str()); + SetEnvironmentVariableW(nameW, valueW); + delete [] nameW; + delete [] valueW; + } + + // Clear the TERM variable. The child process's immediate console/terminal + // environment is a Windows console, not the terminal that winpty is + // communicating with. Leaving the TERM variable set can break programs in + // various ways. (e.g. arrows keys broken in Cygwin less, IronPython's + // help(...) function doesn't start, misc programs decide they should + // output color escape codes on pre-Win10). See + // https://github.com/rprichard/winpty/issues/43. + SetEnvironmentVariableW(L"TERM", NULL); +} + +static void usage(const char *program, int exitCode) +{ + printf("Usage: %s [options] [--] program [args]\n", program); + printf("\n"); + printf("Options:\n"); + printf(" -h, --help Show this help message\n"); + printf(" --mouse Enable terminal mouse input\n"); + printf(" --showkey Dump STDIN escape sequences\n"); + printf(" --version Show the winpty version number\n"); + exit(exitCode); +} + +struct Arguments { + std::vector childArgv; + bool mouseInput; + bool testAllowNonTtys; + bool testConerr; + bool testPlainOutput; + bool testColorEscapes; +}; + +static void parseArguments(int argc, char *argv[], Arguments &out) +{ + out.mouseInput = false; + out.testAllowNonTtys = false; + out.testConerr = false; + out.testPlainOutput = false; + out.testColorEscapes = false; + bool doShowKeys = false; + const char *const program = argc >= 1 ? argv[0] : ""; + int argi = 1; + while (argi < argc) { + std::string arg(argv[argi++]); + if (arg.size() >= 1 && arg[0] == '-') { + if (arg == "-h" || arg == "--help") { + usage(program, 0); + } else if (arg == "--mouse") { + out.mouseInput = true; + } else if (arg == "--showkey") { + doShowKeys = true; + } else if (arg == "--version") { + dumpVersionToStdout(); + exit(0); + } else if (arg == "-Xallow-non-tty") { + out.testAllowNonTtys = true; + } else if (arg == "-Xconerr") { + out.testConerr = true; + } else if (arg == "-Xplain") { + out.testPlainOutput = true; + } else if (arg == "-Xcolor") { + out.testColorEscapes = true; + } else if (arg == "--") { + break; + } else { + fprintf(stderr, "Error: unrecognized option: '%s'\n", + arg.c_str()); + exit(1); + } + } else { + out.childArgv.push_back(arg); + break; + } + } + for (; argi < argc; ++argi) { + out.childArgv.push_back(argv[argi]); + } + if (doShowKeys) { + debugShowKey(out.testAllowNonTtys); + exit(0); + } + if (out.childArgv.size() == 0) { + usage(program, 1); + } +} + +static std::string errorMessageToString(DWORD err) +{ + // Use FormatMessageW rather than FormatMessageA, because we want to use + // wcstombs to convert to the Cygwin locale, which might not match the + // codepage FormatMessageA would use. We need to convert using wcstombs, + // rather than print using %ls, because %ls doesn't work in the original + // MSYS. + wchar_t *wideMsgPtr = NULL; + const DWORD formatRet = FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&wideMsgPtr), + 0, + NULL); + if (formatRet == 0 || wideMsgPtr == NULL) { + return std::string(); + } + std::string msg = wcsToMbs(wideMsgPtr); + LocalFree(wideMsgPtr); + const size_t pos = msg.find_last_not_of(" \r\n\t"); + if (pos == std::string::npos) { + msg.clear(); + } else { + msg.erase(pos + 1); + } + return msg; +} + +static std::string formatErrorMessage(DWORD err) +{ + char buf[64]; + sprintf(buf, "error %#x", static_cast(err)); + std::string ret = errorMessageToString(err); + if (ret.empty()) { + ret += buf; + } else { + ret += " ("; + ret += buf; + ret += ")"; + } + return ret; +} + +int main(int argc, char *argv[]) +{ + setlocale(LC_ALL, ""); + + g_mainWakeup = new WakeupFd(); + + Arguments args; + parseArguments(argc, argv, args); + + setupWin32Environment(); + + winsize sz = { 0 }; + sz.ws_col = 80; + sz.ws_row = 25; + ioctl(STDIN_FILENO, TIOCGWINSZ, &sz); + + DWORD agentFlags = WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION; + if (args.testConerr) { agentFlags |= WINPTY_FLAG_CONERR; } + if (args.testPlainOutput) { agentFlags |= WINPTY_FLAG_PLAIN_OUTPUT; } + if (args.testColorEscapes) { agentFlags |= WINPTY_FLAG_COLOR_ESCAPES; } + winpty_config_t *agentCfg = winpty_config_new(agentFlags, NULL); + assert(agentCfg != NULL); + winpty_config_set_initial_size(agentCfg, sz.ws_col, sz.ws_row); + if (args.mouseInput) { + winpty_config_set_mouse_mode(agentCfg, WINPTY_MOUSE_MODE_FORCE); + } + + winpty_error_ptr_t openErr = NULL; + winpty_t *wp = winpty_open(agentCfg, &openErr); + if (wp == NULL) { + fprintf(stderr, "Error creating winpty: %s\n", + wcsToMbs(winpty_error_msg(openErr)).c_str()); + exit(1); + } + winpty_config_free(agentCfg); + winpty_error_free(openErr); + + HANDLE conin = CreateFileW(winpty_conin_name(wp), GENERIC_WRITE, 0, NULL, + OPEN_EXISTING, 0, NULL); + HANDLE conout = CreateFileW(winpty_conout_name(wp), GENERIC_READ, 0, NULL, + OPEN_EXISTING, 0, NULL); + assert(conin != INVALID_HANDLE_VALUE); + assert(conout != INVALID_HANDLE_VALUE); + HANDLE conerr = NULL; + if (args.testConerr) { + conerr = CreateFileW(winpty_conerr_name(wp), GENERIC_READ, 0, NULL, + OPEN_EXISTING, 0, NULL); + assert(conerr != INVALID_HANDLE_VALUE); + } + + HANDLE childHandle = NULL; + + { + // Start the child process under the console. + args.childArgv[0] = findProgram(argv[0], args.childArgv[0]); + std::string cmdLine = argvToCommandLine(args.childArgv); + wchar_t *cmdLineW = heapMbsToWcs(cmdLine.c_str()); + + winpty_spawn_config_t *spawnCfg = winpty_spawn_config_new( + WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, + NULL, cmdLineW, NULL, NULL, NULL); + assert(spawnCfg != NULL); + + winpty_error_ptr_t spawnErr = NULL; + DWORD lastError = 0; + BOOL spawnRet = winpty_spawn(wp, spawnCfg, &childHandle, NULL, + &lastError, &spawnErr); + winpty_spawn_config_free(spawnCfg); + + if (!spawnRet) { + winpty_result_t spawnCode = winpty_error_code(spawnErr); + if (spawnCode == WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED) { + fprintf(stderr, "%s: error: cannot start '%s': %s\n", + argv[0], + cmdLine.c_str(), + formatErrorMessage(lastError).c_str()); + } else { + fprintf(stderr, "%s: error: cannot start '%s': internal error: %s\n", + argv[0], + cmdLine.c_str(), + wcsToMbs(winpty_error_msg(spawnErr)).c_str()); + } + exit(1); + } + winpty_error_free(spawnErr); + delete [] cmdLineW; + } + + registerResizeSignalHandler(); + SavedTermiosMode mode = + setRawTerminalMode(args.testAllowNonTtys, true, args.testConerr); + + InputHandler inputHandler(conin, STDIN_FILENO, mainWakeup()); + OutputHandler outputHandler(conout, STDOUT_FILENO, mainWakeup()); + OutputHandler *errorHandler = NULL; + if (args.testConerr) { + errorHandler = new OutputHandler(conerr, STDERR_FILENO, mainWakeup()); + } + + while (true) { + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(mainWakeup().fd(), &readfds); + selectWrapper("main thread", mainWakeup().fd() + 1, &readfds); + mainWakeup().reset(); + + // Check for terminal resize. + { + winsize sz2; + ioctl(STDIN_FILENO, TIOCGWINSZ, &sz2); + if (memcmp(&sz, &sz2, sizeof(sz)) != 0) { + sz = sz2; + winpty_set_size(wp, sz.ws_col, sz.ws_row, NULL); + } + } + + // Check for an I/O handler shutting down (possibly indicating that the + // child process has exited). + if (inputHandler.isComplete() || outputHandler.isComplete() || + (errorHandler != NULL && errorHandler->isComplete())) { + break; + } + } + + // Kill the agent connection. This will kill the agent, closing the CONIN + // and CONOUT pipes on the agent pipe, prompting our I/O handler to shut + // down. + winpty_free(wp); + + inputHandler.shutdown(); + outputHandler.shutdown(); + CloseHandle(conin); + CloseHandle(conout); + + if (errorHandler != NULL) { + errorHandler->shutdown(); + delete errorHandler; + CloseHandle(conerr); + } + + restoreTerminalMode(mode); + + DWORD exitCode = 0; + if (!GetExitCodeProcess(childHandle, &exitCode)) { + exitCode = 1; + } + CloseHandle(childHandle); + return exitCode; +} diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/subdir.mk b/src/libs/3rdparty/winpty/src/unix-adapter/subdir.mk new file mode 100644 index 00000000000..200193a1b15 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/unix-adapter/subdir.mk @@ -0,0 +1,41 @@ +# Copyright (c) 2011-2015 Ryan Prichard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +ALL_TARGETS += build/$(UNIX_ADAPTER_EXE) + +$(eval $(call def_unix_target,unix-adapter,)) + +UNIX_ADAPTER_OBJECTS = \ + build/unix-adapter/unix-adapter/InputHandler.o \ + build/unix-adapter/unix-adapter/OutputHandler.o \ + build/unix-adapter/unix-adapter/Util.o \ + build/unix-adapter/unix-adapter/WakeupFd.o \ + build/unix-adapter/unix-adapter/main.o \ + build/unix-adapter/shared/DebugClient.o \ + build/unix-adapter/shared/WinptyAssert.o \ + build/unix-adapter/shared/WinptyVersion.o + +build/unix-adapter/shared/WinptyVersion.o : build/gen/GenVersion.h + +build/$(UNIX_ADAPTER_EXE) : $(UNIX_ADAPTER_OBJECTS) build/winpty.dll + $(info Linking $@) + @$(UNIX_CXX) $(UNIX_LDFLAGS) -o $@ $^ + +-include $(UNIX_ADAPTER_OBJECTS:.o=.d) diff --git a/src/libs/3rdparty/winpty/src/winpty.gyp b/src/libs/3rdparty/winpty/src/winpty.gyp new file mode 100644 index 00000000000..7ee68d55e60 --- /dev/null +++ b/src/libs/3rdparty/winpty/src/winpty.gyp @@ -0,0 +1,206 @@ +{ + # The MSVC generator is the default. Select the compiler version by + # passing -G msvs_version= to gyp. is a string like 2013e. + # See gyp\pylib\gyp\MSVSVersion.py for sample version strings. You + # can also pass configurations.gypi to gyp for 32-bit and 64-bit builds. + # See that file for details. + # + # Pass --format=make to gyp to generate a Makefile instead. The Makefile + # can be configured by passing variables to make, e.g.: + # make -j4 CXX=i686-w64-mingw32-g++ LDFLAGS="-static -static-libgcc -static-libstdc++" + + 'variables': { + 'WINPTY_COMMIT_HASH%': ' + +Q_DECLARE_LOGGING_CATEGORY(adsLog) diff --git a/src/libs/advanceddockingsystem/advanceddockingsystem.qbs b/src/libs/advanceddockingsystem/advanceddockingsystem.qbs index 9778f629492..8d758b3f7eb 100644 --- a/src/libs/advanceddockingsystem/advanceddockingsystem.qbs +++ b/src/libs/advanceddockingsystem/advanceddockingsystem.qbs @@ -7,7 +7,7 @@ QtcLibrary { cpp.defines: base.concat("ADVANCEDDOCKINGSYSTEM_LIBRARY") cpp.includePaths: base.concat([".", linux.prefix]) - Depends { name: "Qt"; submodules: ["widgets", "core", "gui"] } + Depends { name: "Qt"; submodules: ["widgets", "xml"] } Depends { name: "Utils" } Group { @@ -31,7 +31,9 @@ QtcLibrary { "floatingdockcontainer.cpp", "floatingdockcontainer.h", "floatingdragpreview.cpp", "floatingdragpreview.h", "iconprovider.cpp", "iconprovider.h", + "workspace.cpp", "workspace.h", "workspacedialog.cpp", "workspacedialog.h", + "workspaceinputdialog.cpp", "workspaceinputdialog.h", "workspacemodel.cpp", "workspacemodel.h", "workspaceview.cpp", "workspaceview.h", ] diff --git a/src/libs/advanceddockingsystem/dockareatabbar.cpp b/src/libs/advanceddockingsystem/dockareatabbar.cpp index ec1bf782ff0..62f0b80b499 100644 --- a/src/libs/advanceddockingsystem/dockareatabbar.cpp +++ b/src/libs/advanceddockingsystem/dockareatabbar.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later #include "dockareatabbar.h" +#include "ads_globals_p.h" #include "dockareawidget.h" #include "dockwidget.h" @@ -16,8 +17,6 @@ #include -static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg) - namespace ADS { /** diff --git a/src/libs/advanceddockingsystem/dockareatitlebar.cpp b/src/libs/advanceddockingsystem/dockareatitlebar.cpp index 5e45c2cf909..02c83489fa7 100644 --- a/src/libs/advanceddockingsystem/dockareatitlebar.cpp +++ b/src/libs/advanceddockingsystem/dockareatitlebar.cpp @@ -4,6 +4,7 @@ #include "dockareatitlebar.h" #include "ads_globals.h" +#include "ads_globals_p.h" #include "advanceddockingsystemtr.h" #include "dockareatabbar.h" #include "dockareawidget.h" @@ -28,8 +29,6 @@ #include -static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg) - namespace ADS { /** diff --git a/src/libs/advanceddockingsystem/dockareawidget.cpp b/src/libs/advanceddockingsystem/dockareawidget.cpp index b4c21f71198..522b6b985ee 100644 --- a/src/libs/advanceddockingsystem/dockareawidget.cpp +++ b/src/libs/advanceddockingsystem/dockareawidget.cpp @@ -3,6 +3,7 @@ #include "dockareawidget.h" +#include "ads_globals_p.h" #include "dockareatabbar.h" #include "dockareatitlebar.h" #include "dockcomponentsfactory.h" @@ -29,8 +30,6 @@ #include -static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg) - namespace ADS { static const char *const INDEX_PROPERTY = "index"; diff --git a/src/libs/advanceddockingsystem/dockcontainerwidget.cpp b/src/libs/advanceddockingsystem/dockcontainerwidget.cpp index 2cf073a94fe..51ced580963 100644 --- a/src/libs/advanceddockingsystem/dockcontainerwidget.cpp +++ b/src/libs/advanceddockingsystem/dockcontainerwidget.cpp @@ -4,6 +4,7 @@ #include "dockcontainerwidget.h" #include "ads_globals.h" +#include "ads_globals_p.h" #include "dockareawidget.h" #include "dockingstatereader.h" #include "dockmanager.h" @@ -25,8 +26,6 @@ #include #include -static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg) - namespace ADS { static unsigned int zOrderCounter = 0; diff --git a/src/libs/advanceddockingsystem/dockmanager.cpp b/src/libs/advanceddockingsystem/dockmanager.cpp index 44d3ce89f9f..1b2a1c322a1 100644 --- a/src/libs/advanceddockingsystem/dockmanager.cpp +++ b/src/libs/advanceddockingsystem/dockmanager.cpp @@ -5,6 +5,7 @@ #include "ads_globals.h" #include "advanceddockingsystemtr.h" +#include "ads_globals_p.h" #include "dockareawidget.h" #include "dockfocuscontroller.h" #include "dockingstatereader.h" @@ -41,7 +42,7 @@ #include #include -static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg); +Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg); using namespace Utils; diff --git a/src/libs/advanceddockingsystem/docksplitter.cpp b/src/libs/advanceddockingsystem/docksplitter.cpp index e8537dbd775..a10161da596 100644 --- a/src/libs/advanceddockingsystem/docksplitter.cpp +++ b/src/libs/advanceddockingsystem/docksplitter.cpp @@ -3,14 +3,15 @@ #include "docksplitter.h" +#include "ads_globals_p.h" #include "dockareawidget.h" +#include + #include #include #include -static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg) - namespace ADS { /** @@ -31,7 +32,7 @@ namespace ADS , d(new DockSplitterPrivate(this)) { //setProperty("ads-splitter", true); // TODO - setProperty("minisplitter", true); + setProperty(Utils::StyleHelper::C_MINI_SPLITTER, true); setChildrenCollapsible(false); } diff --git a/src/libs/advanceddockingsystem/dockwidget.cpp b/src/libs/advanceddockingsystem/dockwidget.cpp index 34887e91638..ea680149661 100644 --- a/src/libs/advanceddockingsystem/dockwidget.cpp +++ b/src/libs/advanceddockingsystem/dockwidget.cpp @@ -4,6 +4,7 @@ #include "dockwidget.h" #include "ads_globals.h" +#include "ads_globals_p.h" #include "dockareawidget.h" #include "dockcomponentsfactory.h" #include "dockcontainerwidget.h" @@ -25,8 +26,6 @@ #include #include -static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg) - namespace ADS { /** diff --git a/src/libs/advanceddockingsystem/dockwidgettab.cpp b/src/libs/advanceddockingsystem/dockwidgettab.cpp index 67754784975..4531e0da2da 100644 --- a/src/libs/advanceddockingsystem/dockwidgettab.cpp +++ b/src/libs/advanceddockingsystem/dockwidgettab.cpp @@ -4,6 +4,7 @@ #include "dockwidgettab.h" #include "ads_globals.h" +#include "ads_globals_p.h" #include "advanceddockingsystemtr.h" #include "dockareawidget.h" #include "dockmanager.h" @@ -32,8 +33,6 @@ #include -static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg) - namespace ADS { using TabLabelType = ElidingLabel; diff --git a/src/libs/advanceddockingsystem/floatingdockcontainer.cpp b/src/libs/advanceddockingsystem/floatingdockcontainer.cpp index 4b8ae0913dd..c99044a3d5a 100644 --- a/src/libs/advanceddockingsystem/floatingdockcontainer.cpp +++ b/src/libs/advanceddockingsystem/floatingdockcontainer.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later #include "floatingdockcontainer.h" +#include "ads_globals_p.h" #include "dockareawidget.h" #include "dockcontainerwidget.h" @@ -29,8 +30,6 @@ #include #include -static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg) - namespace ADS { #ifdef Q_OS_WIN diff --git a/src/libs/advanceddockingsystem/floatingdragpreview.cpp b/src/libs/advanceddockingsystem/floatingdragpreview.cpp index f427ab85edf..3111e46c6bd 100644 --- a/src/libs/advanceddockingsystem/floatingdragpreview.cpp +++ b/src/libs/advanceddockingsystem/floatingdragpreview.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later #include "floatingdragpreview.h" +#include "ads_globals_p.h" #include "dockareawidget.h" #include "dockcontainerwidget.h" @@ -19,8 +20,6 @@ #include -static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg) - namespace ADS { /** diff --git a/src/libs/advanceddockingsystem/workspacedialog.cpp b/src/libs/advanceddockingsystem/workspacedialog.cpp index fb636887dbe..06e74f310a1 100644 --- a/src/libs/advanceddockingsystem/workspacedialog.cpp +++ b/src/libs/advanceddockingsystem/workspacedialog.cpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace ADS { @@ -47,7 +48,7 @@ WorkspaceDialog::WorkspaceDialog(DockManager *manager, QWidget *parent) connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - using namespace Utils::Layouting; + using namespace Layouting; Column{Row{Column{m_workspaceView, m_autoLoadCheckBox}, Column{m_btCreateNew, diff --git a/src/libs/advanceddockingsystem/workspaceinputdialog.cpp b/src/libs/advanceddockingsystem/workspaceinputdialog.cpp index 56e75433231..15f36398c2a 100644 --- a/src/libs/advanceddockingsystem/workspaceinputdialog.cpp +++ b/src/libs/advanceddockingsystem/workspaceinputdialog.cpp @@ -69,7 +69,7 @@ WorkspaceNameInputDialog::WorkspaceNameInputDialog(DockManager *manager, QWidget m_switchToButton->setEnabled(m_newWorkspaceLineEdit->hasAcceptableInput()); }); - using namespace Utils::Layouting; + using namespace Layouting; Column { label, diff --git a/src/libs/advanceddockingsystem/workspaceview.cpp b/src/libs/advanceddockingsystem/workspaceview.cpp index bab54e5e927..1b4cfc7e270 100644 --- a/src/libs/advanceddockingsystem/workspaceview.cpp +++ b/src/libs/advanceddockingsystem/workspaceview.cpp @@ -325,10 +325,10 @@ bool WorkspaceView::confirmWorkspaceDelete(const QStringList &fileNames) { const QString title = fileNames.size() == 1 ? Tr::tr("Delete Workspace") : Tr::tr("Delete Workspaces"); - const QString question - = fileNames.size() == 1 - ? Tr::tr("Delete workspace %1?").arg(fileNames.first()) - : Tr::tr("Delete these workspaces?\n %1").arg(fileNames.join("\n ")); + const QString question = fileNames.size() == 1 + ? Tr::tr("Delete workspace \"%1\"?").arg(fileNames.first()) + : Tr::tr("Delete these workspaces?") + + QString("\n %1").arg(fileNames.join("\n ")); return QMessageBox::question(parentWidget(), title, question, QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes; } diff --git a/src/libs/cplusplus/CppDocument.cpp b/src/libs/cplusplus/CppDocument.cpp index 6e241fb29b0..07714c0fe2f 100644 --- a/src/libs/cplusplus/CppDocument.cpp +++ b/src/libs/cplusplus/CppDocument.cpp @@ -26,7 +26,6 @@ #include #include -#include #include /*! @@ -297,12 +296,13 @@ void Document::setLastModified(const QDateTime &lastModified) _lastModified = lastModified; } -FilePaths Document::includedFiles() const +FilePaths Document::includedFiles(Duplicates duplicates) const { FilePaths files; for (const Include &i : std::as_const(_resolvedIncludes)) files.append(i.resolvedFileName()); - FilePath::removeDuplicates(files); + if (duplicates == Duplicates::Remove) + FilePath::removeDuplicates(files); return files; } @@ -772,7 +772,7 @@ QSet Snapshot::allIncludesForDocument(const FilePath &filePath) const while (!files.isEmpty()) { FilePath file = files.pop(); if (Document::Ptr doc = document(file)) { - const FilePaths includedFiles = doc->includedFiles(); + const FilePaths includedFiles = doc->includedFiles(Document::Duplicates::Keep); for (const FilePath &inc : includedFiles) { if (!result.contains(inc)) { result.insert(inc); @@ -821,16 +821,10 @@ FilePaths Snapshot::filesDependingOn(const FilePath &filePath) const return m_deps.filesDependingOn(filePath); } -void Snapshot::updateDependencyTable() const -{ - QFutureInterfaceBase futureInterface; - updateDependencyTable(futureInterface); -} - -void Snapshot::updateDependencyTable(QFutureInterfaceBase &futureInterface) const +void Snapshot::updateDependencyTable(const std::optional> &future) const { if (m_deps.files.isEmpty()) - m_deps.build(futureInterface, *this); + m_deps.build(future, *this); } bool Snapshot::operator==(const Snapshot &other) const diff --git a/src/libs/cplusplus/CppDocument.h b/src/libs/cplusplus/CppDocument.h index cfbad3be1ea..00bebf277d9 100644 --- a/src/libs/cplusplus/CppDocument.h +++ b/src/libs/cplusplus/CppDocument.h @@ -15,12 +15,9 @@ #include #include #include +#include #include -QT_BEGIN_NAMESPACE -class QFutureInterfaceBase; -QT_END_NAMESPACE - namespace CPlusPlus { class Macro; @@ -300,7 +297,12 @@ public: } }; - Utils::FilePaths includedFiles() const; + enum class Duplicates { + Remove, + Keep, + }; + + Utils::FilePaths includedFiles(Duplicates duplicates = Duplicates::Remove) const; void addIncludeFile(const Include &include); const QList &resolvedIncludes() const @@ -406,8 +408,7 @@ public: Utils::FilePaths filesDependingOn(const Utils::FilePath &filePath) const; - void updateDependencyTable() const; - void updateDependencyTable(QFutureInterfaceBase &futureInterface) const; + void updateDependencyTable(const std::optional> &future = {}) const; bool operator==(const Snapshot &other) const; diff --git a/src/libs/cplusplus/DependencyTable.cpp b/src/libs/cplusplus/DependencyTable.cpp index 00aca7dc65a..5960957330b 100644 --- a/src/libs/cplusplus/DependencyTable.cpp +++ b/src/libs/cplusplus/DependencyTable.cpp @@ -4,7 +4,7 @@ #include "CppDocument.h" #include -#include +#include using namespace Utils; @@ -28,14 +28,14 @@ Utils::FilePaths DependencyTable::filesDependingOn(const Utils::FilePath &fileNa return deps; } -void DependencyTable::build(QFutureInterfaceBase &futureInterface, const Snapshot &snapshot) +void DependencyTable::build(const std::optional> &future, const Snapshot &snapshot) { files.clear(); fileIndex.clear(); includes.clear(); includeMap.clear(); - if (futureInterface.isCanceled()) + if (future && future->isCanceled()) return; const int documentCount = snapshot.size(); @@ -49,7 +49,7 @@ void DependencyTable::build(QFutureInterfaceBase &futureInterface, const Snapsho fileIndex[it.key()] = i; } - if (futureInterface.isCanceled()) + if (future && future->isCanceled()) return; for (int i = 0; i < files.size(); ++i) { @@ -68,13 +68,13 @@ void DependencyTable::build(QFutureInterfaceBase &futureInterface, const Snapsho directIncludes.append(index); bitmap.setBit(index, true); - if (futureInterface.isCanceled()) + if (future && future->isCanceled()) return; } includeMap[i] = bitmap; includes[i] = directIncludes; - if (futureInterface.isCanceled()) + if (future && future->isCanceled()) return; } } @@ -91,7 +91,7 @@ void DependencyTable::build(QFutureInterfaceBase &futureInterface, const Snapsho const QList includedFileIndexes = includes.value(i); for (const int includedFileIndex : includedFileIndexes) { bitmap |= includeMap.value(includedFileIndex); - if (futureInterface.isCanceled()) + if (future && future->isCanceled()) return; } @@ -99,10 +99,10 @@ void DependencyTable::build(QFutureInterfaceBase &futureInterface, const Snapsho includeMap[i] = bitmap; changed = true; } - if (futureInterface.isCanceled()) + if (future && future->isCanceled()) return; } - if (futureInterface.isCanceled()) + if (future && future->isCanceled()) return; } while (changed); } diff --git a/src/libs/cplusplus/DependencyTable.h b/src/libs/cplusplus/DependencyTable.h index d9057844330..d460ebeb7f3 100644 --- a/src/libs/cplusplus/DependencyTable.h +++ b/src/libs/cplusplus/DependencyTable.h @@ -14,7 +14,8 @@ #include QT_BEGIN_NAMESPACE -class QFutureInterfaceBase; +template +class QFuture; QT_END_NAMESPACE namespace CPlusPlus { @@ -25,7 +26,7 @@ class CPLUSPLUS_EXPORT DependencyTable { private: friend class Snapshot; - void build(QFutureInterfaceBase &futureInterface, const Snapshot &snapshot); + void build(const std::optional> &future, const Snapshot &snapshot); Utils::FilePaths filesDependingOn(const Utils::FilePath &fileName) const; QVector files; diff --git a/src/libs/cplusplus/cplusplus.qbs b/src/libs/cplusplus/cplusplus.qbs index 80c6174ba22..0aed0ab438f 100644 --- a/src/libs/cplusplus/cplusplus.qbs +++ b/src/libs/cplusplus/cplusplus.qbs @@ -41,6 +41,7 @@ Project { "FullySpecifiedType.cpp", "FullySpecifiedType.h", "Keywords.cpp", + "Keywords.kwgen", "Lexer.cpp", "Lexer.h", "LiteralTable.h", diff --git a/src/libs/cplusplus/pp-engine.cpp b/src/libs/cplusplus/pp-engine.cpp index d8221b151f6..ef27d9eace8 100644 --- a/src/libs/cplusplus/pp-engine.cpp +++ b/src/libs/cplusplus/pp-engine.cpp @@ -37,20 +37,17 @@ #include #include #include +#include #include #include #include +#include #include #include -#define NO_DEBUG - -#ifndef NO_DEBUG -# include -#endif // NO_DEBUG - -#include +// FIXME: This is used for errors that should appear in the editor. +static Q_LOGGING_CATEGORY(lexerLog, "qtc.cpp.lexer", QtWarningMsg) using namespace Utils; @@ -119,13 +116,6 @@ static bool isQtReservedWord(const char *name, int size) return false; } -static void nestingTooDeep() -{ -#ifndef NO_DEBUG - std::cerr << "*** WARNING #if / #ifdef nesting exceeded the max level " << MAX_LEVEL << std::endl; -#endif -} - } // anonymous namespace namespace CPlusPlus { @@ -1680,10 +1670,7 @@ void Preprocessor::handleIncludeDirective(PPToken *tk, bool includeNext) GuardLocker depthLocker(m_includeDepthGuard); if (m_includeDepthGuard.lockCount() > MAX_INCLUDE_DEPTH) { - // FIXME: Categorized logging! -#ifndef NO_DEBUG - std::cerr << "Maximum include depth exceeded" << m_state.m_currentFileName << std::endl; -#endif + qCWarning(lexerLog) << "Maximum include depth exceeded" << m_state.m_currentFileName; return; } @@ -1929,10 +1916,8 @@ void Preprocessor::handleIfDirective(PPToken *tk) Value result; const PPToken lastExpressionToken = evalExpression(tk, result); - if (m_state.m_ifLevel >= MAX_LEVEL - 1) { - nestingTooDeep(); + if (!checkConditionalNesting()) return; - } const bool value = !result.is_zero(); @@ -1953,7 +1938,7 @@ void Preprocessor::handleIfDirective(PPToken *tk) void Preprocessor::handleElifDirective(PPToken *tk, const PPToken £Token) { if (m_state.m_ifLevel == 0) { -// std::cerr << "*** WARNING #elif without #if" << std::endl; + qCWarning(lexerLog) << "#elif without #if"; handleIfDirective(tk); } else { lex(tk); // consume "elif" token @@ -2000,22 +1985,18 @@ void Preprocessor::handleElseDirective(PPToken *tk, const PPToken £Token) else if (m_client && !wasSkipping && startSkipping) startSkippingBlocks(poundToken); } -#ifndef NO_DEBUG } else { - std::cerr << "*** WARNING #else without #if" << std::endl; -#endif // NO_DEBUG + qCWarning(lexerLog) << "#else without #if"; } } void Preprocessor::handleEndIfDirective(PPToken *tk, const PPToken £Token) { if (m_state.m_ifLevel == 0) { -#ifndef NO_DEBUG - std::cerr << "*** WARNING #endif without #if"; + qCWarning(lexerLog) << "#endif without #if"; if (!tk->generated()) - std::cerr << " on line " << tk->lineno << " of file " << m_state.m_currentFileName.toUtf8().constData(); - std::cerr << std::endl; -#endif // NO_DEBUG + qCWarning(lexerLog) << "on line" << tk->lineno << "of file" + << m_state.m_currentFileName.toUtf8().constData(); } else { bool wasSkipping = m_state.m_skipping[m_state.m_ifLevel]; m_state.m_skipping[m_state.m_ifLevel] = false; @@ -2061,22 +2042,18 @@ void Preprocessor::handleIfDefDirective(bool checkUndefined, PPToken *tk) const bool wasSkipping = m_state.m_skipping[m_state.m_ifLevel]; - if (m_state.m_ifLevel < MAX_LEVEL - 1) { + if (checkConditionalNesting()) { ++m_state.m_ifLevel; m_state.m_trueTest[m_state.m_ifLevel] = value; m_state.m_skipping[m_state.m_ifLevel] = wasSkipping ? wasSkipping : !value; if (m_client && !wasSkipping && !value) startSkippingBlocks(*tk); - } else { - nestingTooDeep(); } lex(tk); // consume the identifier -#ifndef NO_DEBUG } else { - std::cerr << "*** WARNING #ifdef without identifier" << std::endl; -#endif // NO_DEBUG + qCWarning(lexerLog) << "#ifdef without identifier"; } } @@ -2103,10 +2080,8 @@ void Preprocessor::handleUndefDirective(PPToken *tk) m_client->macroAdded(*macro); } lex(tk); // consume macro name -#ifndef NO_DEBUG } else { - std::cerr << "*** WARNING #undef without identifier" << std::endl; -#endif // NO_DEBUG + qCWarning(lexerLog) << "#undef without identifier"; } } @@ -2203,4 +2178,14 @@ void Preprocessor::maybeStartOutputLine() buffer.append('\n'); } +bool Preprocessor::checkConditionalNesting() const +{ + if (m_state.m_ifLevel >= MAX_LEVEL - 1) { + qCWarning(lexerLog) << "#if/#ifdef nesting exceeding maximum level" << MAX_LEVEL; + return false; + } + return true; +} + + } // namespace CPlusPlus diff --git a/src/libs/cplusplus/pp-engine.h b/src/libs/cplusplus/pp-engine.h index c888e8775d6..2163380dea2 100644 --- a/src/libs/cplusplus/pp-engine.h +++ b/src/libs/cplusplus/pp-engine.h @@ -237,6 +237,7 @@ private: PPToken generateConcatenated(const PPToken &leftTk, const PPToken &rightTk); void startSkippingBlocks(const PPToken &tk) const; + bool checkConditionalNesting() const; private: Client *m_client; diff --git a/src/libs/extensionsystem/CMakeLists.txt b/src/libs/extensionsystem/CMakeLists.txt index ea7cb60beb0..0e4e6607a55 100644 --- a/src/libs/extensionsystem/CMakeLists.txt +++ b/src/libs/extensionsystem/CMakeLists.txt @@ -19,7 +19,7 @@ add_qtc_library(ExtensionSystem SKIP_AUTOMOC pluginmanager.cpp ) -find_package(Qt5 COMPONENTS Test QUIET) +find_package(Qt6 COMPONENTS Test QUIET) extend_qtc_library(ExtensionSystem CONDITION TARGET Qt::Test diff --git a/src/libs/extensionsystem/iplugin.cpp b/src/libs/extensionsystem/iplugin.cpp index 9f7ca221e51..2af49255735 100644 --- a/src/libs/extensionsystem/iplugin.cpp +++ b/src/libs/extensionsystem/iplugin.cpp @@ -161,12 +161,47 @@ namespace ExtensionSystem { namespace Internal { +class ObjectInitializer +{ +public: + ObjectCreator creator; + ObjectDestructor destructor; + ObjectCreationPolicy policy; +}; + class IPluginPrivate { public: + void tryCreateObjects(); + QList testCreators; + + QList objectInitializers; + QList> objectDestructors; + + // For debugging purposes: + QList createdObjects; // Not owned. }; +void IPluginPrivate::tryCreateObjects() +{ + QList unhandledObjectInitializers; + + for (const ObjectInitializer &initializer : std::as_const(objectInitializers)) { + if (!initializer.policy.dependsOn.isEmpty()) { + qWarning("Initialization dependencies are not supported yet"); + unhandledObjectInitializers.append(initializer); + continue; + } + + void *object = initializer.creator(); + createdObjects.append(object); + objectDestructors.append([initializer, object] { initializer.destructor(object); }); + } + + objectInitializers = unhandledObjectInitializers; +} + } // Internal /*! @@ -182,10 +217,20 @@ IPlugin::IPlugin() */ IPlugin::~IPlugin() { + for (const std::function &dtor : std::as_const(d->objectDestructors)) + dtor(); + delete d; d = nullptr; } +void IPlugin::addManagedHelper(const ObjectCreator &creator, + const ObjectDestructor &destructor, + const ObjectCreationPolicy &policy) +{ + d->objectInitializers.append({creator, destructor, policy}); +} + bool IPlugin::initialize(const QStringList &arguments, QString *errorString) { Q_UNUSED(arguments) @@ -195,7 +240,16 @@ bool IPlugin::initialize(const QStringList &arguments, QString *errorString) } /*! - Registers a function object that creates a test object. + \internal +*/ +void IPlugin::tryCreateObjects() +{ + d->tryCreateObjects(); +} + +/*! + Registers a function object that creates a test object with the owner + \a creator. The created objects are meant to be passed on to \l QTest::qExec(). @@ -211,9 +265,7 @@ void IPlugin::addTestCreator(const TestCreator &creator) } /*! - \deprecated [10.0] Use addTest() instead - - \sa addTest() + \deprecated [10.0] Use \c addTest() instead. */ QVector IPlugin::createTestObjects() const { diff --git a/src/libs/extensionsystem/iplugin.h b/src/libs/extensionsystem/iplugin.h index 5a8fceb3d6e..3e477e42e81 100644 --- a/src/libs/extensionsystem/iplugin.h +++ b/src/libs/extensionsystem/iplugin.h @@ -5,6 +5,8 @@ #include "extensionsystem_global.h" +#include + #include #include @@ -15,6 +17,17 @@ namespace Internal { class IPluginPrivate; } using TestCreator = std::function; +using ObjectCreator = std::function; +using ObjectDestructor = std::function; + +struct EXTENSIONSYSTEM_EXPORT ObjectCreationPolicy +{ + // Can be empty if nothing depends on it. + Utils::Id id; + // Objects with empty dependencies are created as soon as possible. + QList dependsOn; +}; + class EXTENSIONSYSTEM_EXPORT IPlugin : public QObject { Q_OBJECT @@ -39,6 +52,7 @@ public: // Deprecated in 10.0, use addTest() virtual QVector createTestObjects() const; + virtual void tryCreateObjects(); protected: virtual void initialize() {} @@ -47,6 +61,17 @@ protected: void addTest(Args && ...args) { addTestCreator([args...] { return new Test(args...); }); } void addTestCreator(const TestCreator &creator); + template + void addManaged(const ObjectCreationPolicy &policy = {}) { + addManagedHelper([]() -> void * { return new Type(); }, + [](void *p) { delete static_cast(p); }, + policy); + } + + void addManagedHelper(const ObjectCreator &creator, + const ObjectDestructor &destructor, + const ObjectCreationPolicy &policy); + signals: void asynchronousShutdownFinished(); diff --git a/src/libs/extensionsystem/plugindetailsview.cpp b/src/libs/extensionsystem/plugindetailsview.cpp index 8242d8dac60..49ba0815a35 100644 --- a/src/libs/extensionsystem/plugindetailsview.cpp +++ b/src/libs/extensionsystem/plugindetailsview.cpp @@ -53,7 +53,7 @@ public: , license(createTextEdit()) , dependencies(new QListWidget(q)) { - using namespace Utils::Layouting; + using namespace Layouting; // clang-format off Form { @@ -68,8 +68,9 @@ public: Tr::tr("Description:"), description, br, Tr::tr("Copyright:"), copyright, br, Tr::tr("License:"), license, br, - Tr::tr("Dependencies:"), dependencies - }.attachTo(q, WithoutMargins); + Tr::tr("Dependencies:"), dependencies, + noMargin + }.attachTo(q); // clang-format on } diff --git a/src/libs/extensionsystem/pluginerroroverview.cpp b/src/libs/extensionsystem/pluginerroroverview.cpp index 20e6f5ac900..69562e504df 100644 --- a/src/libs/extensionsystem/pluginerroroverview.cpp +++ b/src/libs/extensionsystem/pluginerroroverview.cpp @@ -42,7 +42,7 @@ PluginErrorOverview::PluginErrorOverview(QWidget *parent) QObject::connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); QObject::connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - using namespace Utils::Layouting; + using namespace Layouting; auto createLabel = [this](const QString &text) { QLabel *label = new QLabel(text, this); diff --git a/src/libs/extensionsystem/pluginerrorview.cpp b/src/libs/extensionsystem/pluginerrorview.cpp index fac7fd9a427..31c4334b6af 100644 --- a/src/libs/extensionsystem/pluginerrorview.cpp +++ b/src/libs/extensionsystem/pluginerrorview.cpp @@ -41,12 +41,13 @@ public: errorString->setTabChangesFocus(true); errorString->setReadOnly(true); - using namespace Utils::Layouting; + using namespace Layouting; Form { Tr::tr("State:"), state, br, - Tr::tr("Error message:"), errorString - }.attachTo(q, WithoutMargins); + Tr::tr("Error message:"), errorString, + noMargin, + }.attachTo(q); } PluginErrorView *q = nullptr; diff --git a/src/libs/extensionsystem/pluginmanager.cpp b/src/libs/extensionsystem/pluginmanager.cpp index aec053b6a8f..ae2dbf8903b 100644 --- a/src/libs/extensionsystem/pluginmanager.cpp +++ b/src/libs/extensionsystem/pluginmanager.cpp @@ -32,10 +32,11 @@ #include #include #include +#include #include #include +#include #include -#include #include #include @@ -400,7 +401,7 @@ QString PluginManager::systemInformation() QString result; CommandLine qtDiag(FilePath::fromString(QLibraryInfo::location(QLibraryInfo::BinariesPath)) .pathAppended("qtdiag").withExecutableSuffix()); - QtcProcess qtDiagProc; + Process qtDiagProc; qtDiagProc.setCommand(qtDiag); qtDiagProc.runBlocking(); if (qtDiagProc.result() == ProcessResult::FinishedWithSuccess) @@ -422,6 +423,11 @@ QString PluginManager::systemInformation() return result; } +FutureSynchronizer *PluginManager::futureSynchronizer() +{ + return d->m_futureSynchronizer.get(); +} + /*! The list of paths were the plugin manager searches for plugins. @@ -976,6 +982,7 @@ void PluginManagerPrivate::nextDelayedInitialize() PluginManagerPrivate::PluginManagerPrivate(PluginManager *pluginManager) : q(pluginManager) { + m_futureSynchronizer.reset(new FutureSynchronizer); } @@ -1027,6 +1034,7 @@ void PluginManagerPrivate::readSettings() */ void PluginManagerPrivate::stopAll() { + m_isShuttingDown = true; if (delayedInitializeTimer && delayedInitializeTimer->isActive()) { delayedInitializeTimer->stop(); delete delayedInitializeTimer; @@ -1043,6 +1051,7 @@ void PluginManagerPrivate::stopAll() */ void PluginManagerPrivate::deleteAll() { + m_futureSynchronizer.reset(); // Synchronize all futures from all plugins Utils::reverseForeach(loadQueue(), [this](PluginSpec *spec) { loadPlugin(spec, PluginSpec::Deleted); }); @@ -1831,6 +1840,11 @@ bool PluginManager::isInitializationDone() return d->m_isInitializationDone; } +bool PluginManager::isShuttingDown() +{ + return d->m_isShuttingDown; +} + /*! Retrieves one object with \a name from the object pool. \sa addObject() diff --git a/src/libs/extensionsystem/pluginmanager.h b/src/libs/extensionsystem/pluginmanager.h index 8dac9544cca..ecd0ee70b73 100644 --- a/src/libs/extensionsystem/pluginmanager.h +++ b/src/libs/extensionsystem/pluginmanager.h @@ -15,6 +15,8 @@ QT_BEGIN_NAMESPACE class QTextStream; QT_END_NAMESPACE +namespace Utils { class FutureSynchronizer; } + namespace ExtensionSystem { class IPlugin; class PluginSpec; @@ -127,12 +129,15 @@ public: static QString platformName(); static bool isInitializationDone(); + static bool isShuttingDown(); static void remoteArguments(const QString &serializedArguments, QObject *socket); static void shutdown(); static QString systemInformation(); + static Utils::FutureSynchronizer *futureSynchronizer(); + signals: void objectAdded(QObject *obj); void aboutToRemoveObject(QObject *obj); diff --git a/src/libs/extensionsystem/pluginmanager_p.h b/src/libs/extensionsystem/pluginmanager_p.h index decd6271778..c7a4291a6b7 100644 --- a/src/libs/extensionsystem/pluginmanager_p.h +++ b/src/libs/extensionsystem/pluginmanager_p.h @@ -26,6 +26,7 @@ class QEventLoop; QT_END_NAMESPACE namespace Utils { +class FutureSynchronizer; class QtcSettings; } @@ -123,6 +124,7 @@ public: bool m_isInitializationDone = false; bool enableCrashCheck = true; + bool m_isShuttingDown = false; QHash> m_scenarios; QString m_requestedScenario; @@ -133,6 +135,7 @@ public: QWaitCondition m_scenarioWaitCondition; PluginManager::ProcessData m_creatorProcessData; + std::unique_ptr m_futureSynchronizer; private: PluginManager *q; diff --git a/src/libs/extensionsystem/pluginspec.cpp b/src/libs/extensionsystem/pluginspec.cpp index d18f410e818..62b3c0096f6 100644 --- a/src/libs/extensionsystem/pluginspec.cpp +++ b/src/libs/extensionsystem/pluginspec.cpp @@ -1119,6 +1119,7 @@ bool PluginSpecPrivate::initializePlugin() hasError = true; return false; } + plugin->tryCreateObjects(); state = PluginSpec::Initialized; return true; } @@ -1145,6 +1146,7 @@ bool PluginSpecPrivate::initializeExtensions() return false; } plugin->extensionsInitialized(); + plugin->tryCreateObjects(); state = PluginSpec::Running; return true; } @@ -1164,7 +1166,9 @@ bool PluginSpecPrivate::delayedInitialize() hasError = true; return false; } - return plugin->delayedInitialize(); + const bool res = plugin->delayedInitialize(); + plugin->tryCreateObjects(); + return res; } /*! diff --git a/src/libs/languageserverprotocol/jsonrpcmessages.cpp b/src/libs/languageserverprotocol/jsonrpcmessages.cpp index 31183cc0cc1..cef4c47cb09 100644 --- a/src/libs/languageserverprotocol/jsonrpcmessages.cpp +++ b/src/libs/languageserverprotocol/jsonrpcmessages.cpp @@ -77,8 +77,7 @@ JsonRpcMessage::JsonRpcMessage(const BaseMessage &message) if (doc.isObject()) m_jsonObject = doc.object(); else if (doc.isNull()) - m_parseError = - Tr::tr("Could not parse JSON message \"%1\".").arg(error.errorString()); + m_parseError = Tr::tr("Could not parse JSON message: \"%1\".").arg(error.errorString()); else m_parseError = Tr::tr("Expected a JSON object, but got a JSON \"%1\" value.").arg(docType(doc)); diff --git a/src/libs/languageserverprotocol/jsonrpcmessages.h b/src/libs/languageserverprotocol/jsonrpcmessages.h index 0b018d7f54a..680bff95782 100644 --- a/src/libs/languageserverprotocol/jsonrpcmessages.h +++ b/src/libs/languageserverprotocol/jsonrpcmessages.h @@ -141,6 +141,7 @@ public: void setMethod(const QString &method) { m_jsonObject.insert(methodKey, method); } + using Parameters = Params; std::optional params() const { const QJsonValue ¶ms = m_jsonObject.value(paramsKey); diff --git a/src/libs/languageserverprotocol/lsptypes.cpp b/src/libs/languageserverprotocol/lsptypes.cpp index 4bf3f3746e2..6f0b57c1e35 100644 --- a/src/libs/languageserverprotocol/lsptypes.cpp +++ b/src/libs/languageserverprotocol/lsptypes.cpp @@ -275,7 +275,7 @@ Position Position::withOffset(int offset, const QTextDocument *doc) const int line; int character; Utils::Text::convertPosition(doc, toPositionInDocument(doc) + offset, &line, &character); - return Position(line - 1, character - 1); + return Position(line - 1, character); } Range::Range(const Position &start, const Position &end) @@ -288,13 +288,13 @@ Range::Range(const QTextCursor &cursor) { int line, character = 0; Utils::Text::convertPosition(cursor.document(), cursor.selectionStart(), &line, &character); - if (line <= 0 || character <= 0) + if (line <= 0 || character < 0) return; - setStart(Position(line - 1, character - 1)); + setStart(Position(line - 1, character)); Utils::Text::convertPosition(cursor.document(), cursor.selectionEnd(), &line, &character); - if (line <= 0 || character <= 0) + if (line <= 0 || character < 0) return; - setEnd(Position(line - 1, character - 1)); + setEnd(Position(line - 1, character)); } bool Range::contains(const Range &other) const @@ -311,6 +311,16 @@ bool Range::overlaps(const Range &range) const return !isLeftOf(range) && !range.isLeftOf(*this); } +QTextCursor Range::toSelection(QTextDocument *doc) const +{ + QTC_ASSERT(doc, return {}); + if (!isValid()) + return {}; + QTextCursor cursor = start().toTextCursor(doc); + cursor.setPosition(end().toPositionInDocument(doc), QTextCursor::KeepAnchor); + return cursor; +} + QString expressionForGlob(QString globPattern) { const QString anySubDir("qtc_anysubdir_id"); diff --git a/src/libs/languageserverprotocol/lsptypes.h b/src/libs/languageserverprotocol/lsptypes.h index 239d40f6622..3a9adb1b0d8 100644 --- a/src/libs/languageserverprotocol/lsptypes.h +++ b/src/libs/languageserverprotocol/lsptypes.h @@ -117,6 +117,8 @@ public: bool isLeftOf(const Range &other) const { return isEmpty() || other.isEmpty() ? end() < other.start() : end() <= other.start(); } + QTextCursor toSelection(QTextDocument *doc) const; + bool isValid() const override { return JsonObject::contains(startKey) && JsonObject::contains(endKey); } }; @@ -556,7 +558,6 @@ enum class SymbolKind { TypeParameter = 26, LastSymbolKind = TypeParameter, }; -using SymbolStringifier = std::function; namespace CompletionItemKind { enum Kind { diff --git a/src/libs/languageserverprotocol/lsputils.h b/src/libs/languageserverprotocol/lsputils.h index 5515b515767..0c815bafd8a 100644 --- a/src/libs/languageserverprotocol/lsputils.h +++ b/src/libs/languageserverprotocol/lsputils.h @@ -87,6 +87,13 @@ public: return QJsonValue(); } + QList toListOrEmpty() const + { + if (std::holds_alternative>(*this)) + return std::get>(*this); + return {}; + } + QList toList() const { QTC_ASSERT(std::holds_alternative>(*this), return {}); diff --git a/src/libs/languageserverprotocol/workspace.h b/src/libs/languageserverprotocol/workspace.h index efe77a68ec2..42cfcf451d2 100644 --- a/src/libs/languageserverprotocol/workspace.h +++ b/src/libs/languageserverprotocol/workspace.h @@ -175,7 +175,7 @@ class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceSymbolRequest : public Request< LanguageClientArray, std::nullptr_t, WorkspaceSymbolParams> { public: - WorkspaceSymbolRequest(const WorkspaceSymbolParams ¶ms); + explicit WorkspaceSymbolRequest(const WorkspaceSymbolParams ¶ms); using Request::Request; constexpr static const char methodName[] = "workspace/symbol"; }; diff --git a/src/libs/libs.qbs b/src/libs/libs.qbs index 92f84fe4d19..ffc3017cba0 100644 --- a/src/libs/libs.qbs +++ b/src/libs/libs.qbs @@ -19,12 +19,15 @@ Project { "qmljs/qmljs.qbs", "qmldebug/qmldebug.qbs", "qtcreatorcdbext/qtcreatorcdbext.qbs", + "solutions/solutions.qbs", "sqlite/sqlite.qbs", "tracing/tracing.qbs", - "utils/process_stub.qbs", "utils/process_ctrlc_stub.qbs", "utils/utils.qbs", + "3rdparty/libptyqt/ptyqt.qbs", + "3rdparty/libvterm/vterm.qbs", "3rdparty/syntax-highlighting/syntax-highlighting.qbs", + "3rdparty/winpty/winpty.qbs", "3rdparty/yaml-cpp/yaml-cpp.qbs", ].concat(qlitehtml).concat(project.additionalLibs) } diff --git a/src/libs/qlitehtml b/src/libs/qlitehtml index f05f78ef332..8e541a22b51 160000 --- a/src/libs/qlitehtml +++ b/src/libs/qlitehtml @@ -1 +1 @@ -Subproject commit f05f78ef33225823d348ee18f2fa464e95024dd2 +Subproject commit 8e541a22b513432ed566fca824af207395ee0c90 diff --git a/src/libs/qmleditorwidgets/contextpanetextwidget.cpp b/src/libs/qmleditorwidgets/contextpanetextwidget.cpp index 1a7d5272948..a7f9e31f02e 100644 --- a/src/libs/qmleditorwidgets/contextpanetextwidget.cpp +++ b/src/libs/qmleditorwidgets/contextpanetextwidget.cpp @@ -80,7 +80,7 @@ ContextPaneTextWidget::ContextPaneTextWidget(QWidget *parent) : vAlignButtons->addButton(m_centerVAlignmentButton); vAlignButtons->addButton(m_bottomAlignmentButton); - using namespace Utils::Layouting; + using namespace Layouting; Column { Row { m_fontComboBox, m_colorButton, m_fontSizeSpinBox, }, Row { diff --git a/src/libs/qmleditorwidgets/contextpanewidget.cpp b/src/libs/qmleditorwidgets/contextpanewidget.cpp index 688c65ad7b3..8d675bf95b1 100644 --- a/src/libs/qmleditorwidgets/contextpanewidget.cpp +++ b/src/libs/qmleditorwidgets/contextpanewidget.cpp @@ -6,6 +6,7 @@ #include "qmleditorwidgetstr.h" #include +#include #include #include @@ -27,26 +28,6 @@ using namespace Utils; namespace QmlEditorWidgets { -/* XPM */ -static const char * pin_xpm[] = { -"12 9 7 1", -" c None", -". c #000000", -"+ c #515151", -"@ c #A8A8A8", -"# c #A9A9A9", -"$ c #999999", -"% c #696969", -" . ", -" ......+", -" .@@@@@.", -" .#####.", -"+.....$$$$$.", -" .%%%%%.", -" .......", -" ......+", -" . "}; - DragWidget::DragWidget(QWidget *parent) : QFrame(parent) { setFrameStyle(QFrame::NoFrame); @@ -143,7 +124,7 @@ ContextPaneWidget::ContextPaneWidget(QWidget *parent) : DragWidget(parent), m_cu m_toolButton->setIcon(style()->standardIcon(QStyle::SP_DockWidgetCloseButton)); m_toolButton->setToolButtonStyle(Qt::ToolButtonIconOnly); - m_toolButton->setFixedSize(16, 16); + m_toolButton->setFixedSize(20, 20); m_toolButton->setToolTip(Tr::tr("Hides this toolbar.")); connect(m_toolButton, &QToolButton::clicked, this, &ContextPaneWidget::onTogglePane); @@ -464,9 +445,7 @@ void ContextPaneWidget::setPinButton() m_toolButton->setAutoRaise(true); m_pinned = true; - m_toolButton->setIcon(QPixmap::fromImage(QImage(pin_xpm))); - m_toolButton->setToolButtonStyle(Qt::ToolButtonIconOnly); - m_toolButton->setFixedSize(20, 20); + m_toolButton->setIcon(Utils::Icons::PINNED_SMALL.icon()); m_toolButton->setToolTip(Tr::tr("Unpins the toolbar and moves it to the default position.")); emit pinnedChanged(true); @@ -481,8 +460,6 @@ void ContextPaneWidget::setLineButton() m_pinned = false; m_toolButton->setAutoRaise(true); m_toolButton->setIcon(style()->standardIcon(QStyle::SP_DockWidgetCloseButton)); - m_toolButton->setToolButtonStyle(Qt::ToolButtonIconOnly); - m_toolButton->setFixedSize(20, 20); m_toolButton->setToolTip(Tr::tr("Hides this toolbar. This toolbar can be" " permanently disabled in the options page or in the context menu.")); diff --git a/src/libs/qmleditorwidgets/contextpanewidgetimage.cpp b/src/libs/qmleditorwidgets/contextpanewidgetimage.cpp index f4ba3a6a5c0..12ec4da3d51 100644 --- a/src/libs/qmleditorwidgets/contextpanewidgetimage.cpp +++ b/src/libs/qmleditorwidgets/contextpanewidgetimage.cpp @@ -97,7 +97,7 @@ ContextPaneWidgetImage::ContextPaneWidgetImage(QWidget *parent, bool borderImage vRadioButtons->addButton(m_borderImage.verticalStretchRadioButton); vRadioButtons->addButton(m_borderImage.verticalTileRadioButtonNoCrop); - using namespace Utils::Layouting; + using namespace Layouting; Row { Column { m_previewLabel, m_sizeLabel, }, Column { @@ -146,7 +146,7 @@ ContextPaneWidgetImage::ContextPaneWidgetImage(QWidget *parent, bool borderImage m_image.cropAspectFitRadioButton = radioButton("aspect-crop-icon", Tr::tr("The image is scaled uniformly to fill, cropping if necessary.")); - using namespace Utils::Layouting; + using namespace Layouting; Row { Column { m_previewLabel, m_sizeLabel, }, Column { diff --git a/src/libs/qmleditorwidgets/contextpanewidgetrectangle.cpp b/src/libs/qmleditorwidgets/contextpanewidgetrectangle.cpp index 52afeff187c..c757f06802e 100644 --- a/src/libs/qmleditorwidgets/contextpanewidgetrectangle.cpp +++ b/src/libs/qmleditorwidgets/contextpanewidgetrectangle.cpp @@ -33,13 +33,19 @@ ContextPaneWidgetRectangle::ContextPaneWidgetRectangle(QWidget *parent) return result; }; + const auto colorButton = [] { + auto result = new ColorButton; + result->setCheckable(true); + result->setShowArrow(false); + return result; + }; + m_gradientLabel = new QLabel(Tr::tr("Gradient")); m_gradientLabel->setAlignment(Qt::AlignBottom); m_gradientLine = new GradientLine; m_gradientLine->setMinimumWidth(240); - m_colorColorButton = new ColorButton; - m_colorColorButton->setShowArrow(false); + m_colorColorButton = colorButton(); m_colorSolid = toolButton("icon_color_solid"); m_colorGradient = toolButton("icon_color_gradient"); m_colorNone = toolButton("icon_color_none"); @@ -48,15 +54,14 @@ ContextPaneWidgetRectangle::ContextPaneWidgetRectangle(QWidget *parent) colorButtons->addButton(m_colorGradient); colorButtons->addButton(m_colorNone); - m_borderColorButton = new ColorButton; - m_borderColorButton->setShowArrow(false); + m_borderColorButton = colorButton(); m_borderSolid = toolButton("icon_color_solid"); m_borderNone = toolButton("icon_color_none"); auto borderButtons = new QButtonGroup(this); borderButtons->addButton(m_borderSolid); borderButtons->addButton(m_borderNone); - using namespace Utils::Layouting; + using namespace Layouting; Grid { m_gradientLabel, m_gradientLine, br, Tr::tr("Color"), Row { m_colorColorButton, m_colorSolid, m_colorGradient, m_colorNone, st, }, br, diff --git a/src/libs/qmleditorwidgets/easingpane/easingcontextpane.cpp b/src/libs/qmleditorwidgets/easingpane/easingcontextpane.cpp index e61f3c2592c..c35f6d792c0 100644 --- a/src/libs/qmleditorwidgets/easingpane/easingcontextpane.cpp +++ b/src/libs/qmleditorwidgets/easingpane/easingcontextpane.cpp @@ -145,7 +145,7 @@ EasingContextPane::EasingContextPane(QWidget *parent) spinBox->setMaximum(999999.9); } - using namespace Utils::Layouting; + using namespace Layouting; Column { Row { m_graphicsView, m_playButton, }, Row { diff --git a/src/libs/qmljs/qmljsbind.cpp b/src/libs/qmljs/qmljsbind.cpp index e2143ed68ec..6ccddcbe630 100644 --- a/src/libs/qmljs/qmljsbind.cpp +++ b/src/libs/qmljs/qmljsbind.cpp @@ -326,7 +326,7 @@ bool Bind::visit(UiInlineComponent *ast) if (!_currentComponentName.isEmpty()) { _currentComponentName += "."; _diagnosticMessages->append( - errorMessage(ast, Tr::tr("Nested inline components are not supported"))); + errorMessage(ast, Tr::tr("Nested inline components are not supported."))); } _currentComponentName += ast->name.toString(); _rootObjectValue = nullptr; diff --git a/src/libs/qmljs/qmljsbundle.cpp b/src/libs/qmljs/qmljsbundle.cpp index c55edcdf5e7..e4d536a3da2 100644 --- a/src/libs/qmljs/qmljsbundle.cpp +++ b/src/libs/qmljs/qmljsbundle.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -186,8 +187,10 @@ QString QmlBundle::toString(const QString &indent) } QStringList QmlBundle::maybeReadTrie(Trie &trie, Utils::JsonObjectValue *config, - const QString &path, const QString &propertyName, bool required) + const QString &path, const QString &propertyName, + bool required, bool stripVersions) { + static const QRegularExpression versionNumberAtEnd("^(.+)( \\d+\\.\\d+)$"); QStringList res; if (!config->hasMember(propertyName)) { if (required) @@ -202,7 +205,13 @@ QStringList QmlBundle::maybeReadTrie(Trie &trie, Utils::JsonObjectValue *config, for (Utils::JsonValue *v : elements) { Utils::JsonStringValue *impStr = ((v != nullptr) ? v->toString() : nullptr); if (impStr != nullptr) { - trie.insert(impStr->value()); + QString value = impStr->value(); + if (stripVersions) { + const QRegularExpressionMatch match = versionNumberAtEnd.match(value); + if (match.hasMatch()) + value = match.captured(1); + } + trie.insert(value); } else { res.append(QString::fromLatin1("Expected all elements of array in property \"%1\" " "to be strings in QmlBundle at %2.") @@ -217,7 +226,7 @@ QStringList QmlBundle::maybeReadTrie(Trie &trie, Utils::JsonObjectValue *config, return res; } -bool QmlBundle::readFrom(QString path, QStringList *errors) +bool QmlBundle::readFrom(QString path, bool stripVersions, QStringList *errors) { Utils::JsonMemoryPool pool; @@ -249,8 +258,8 @@ bool QmlBundle::readFrom(QString path, QStringList *errors) } errs << maybeReadTrie(m_searchPaths, config, path, QLatin1String("searchPaths")); errs << maybeReadTrie(m_installPaths, config, path, QLatin1String("installPaths")); - errs << maybeReadTrie(m_supportedImports, config, path, QLatin1String("supportedImports") - , true); + errs << maybeReadTrie(m_supportedImports, config, path, QLatin1String("supportedImports"), + true, stripVersions); errs << maybeReadTrie(m_implicitImports, config, path, QLatin1String("implicitImports")); if (errors) (*errors) << errs; diff --git a/src/libs/qmljs/qmljsbundle.h b/src/libs/qmljs/qmljsbundle.h index 8cf31456717..5d2058eef48 100644 --- a/src/libs/qmljs/qmljsbundle.h +++ b/src/libs/qmljs/qmljsbundle.h @@ -53,14 +53,15 @@ public: bool writeTo(const QString &path) const; bool writeTo(QTextStream &stream, const QString &indent = QString()) const; QString toString(const QString &indent = QString()); - bool readFrom(QString path, QStringList *errors); + bool readFrom(QString path, bool stripVersions, QStringList *errors); bool operator==(const QmlBundle &o) const; bool operator!=(const QmlBundle &o) const; private: static void printEscaped(QTextStream &s, const QString &str); static void writeTrie(QTextStream &stream, const Trie &t, const QString &indent); QStringList maybeReadTrie(Trie &trie, Utils::JsonObjectValue *config, const QString &path, - const QString &propertyName, bool required = false); + const QString &propertyName, bool required = false, + bool stripVersions = false); QString m_name; Trie m_searchPaths; diff --git a/src/libs/qmljs/qmljscheck.cpp b/src/libs/qmljs/qmljscheck.cpp index 8d5bcadad57..51be467387d 100644 --- a/src/libs/qmljs/qmljscheck.cpp +++ b/src/libs/qmljs/qmljscheck.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -639,7 +640,46 @@ Q_GLOBAL_STATIC(UnsupportedRootObjectTypesByVisualDesigner, unsupportedRootObjec Q_GLOBAL_STATIC(UnsupportedRootObjectTypesByQmlUi, unsupportedRootObjectTypesByQmlUi) Q_GLOBAL_STATIC(UnsupportedTypesByQmlUi, unsupportedTypesByQmlUi) -Check::Check(Document::Ptr doc, const ContextPtr &context) +QList Check::defaultDisabledMessages() +{ + static const QList disabled = Utils::sorted(QList{ + HintAnonymousFunctionSpacing, + HintDeclareVarsInOneLine, + HintDeclarationsShouldBeAtStartOfFunction, + HintBinaryOperatorSpacing, + HintOneStatementPerLine, + HintExtraParentheses, + + // QmlDesigner related + WarnImperativeCodeNotEditableInVisualDesigner, + WarnUnsupportedTypeInVisualDesigner, + WarnReferenceToParentItemNotSupportedByVisualDesigner, + WarnUndefinedValueForVisualDesigner, + WarnStatesOnlyInRootItemForVisualDesigner, + ErrUnsupportedRootTypeInVisualDesigner, + ErrInvalidIdeInVisualDesigner, + + }); + return disabled; +} + +QList Check::defaultDisabledMessagesForNonQuickUi() +{ + static const QList disabled = Utils::sorted(QList{ + // QmlDesigner related + ErrUnsupportedRootTypeInQmlUi, + ErrUnsupportedTypeInQmlUi, + ErrFunctionsNotSupportedInQmlUi, + ErrBlocksNotSupportedInQmlUi, + ErrBehavioursNotSupportedInQmlUi, + ErrStatesOnlyInRootItemInQmlUi, + ErrReferenceToParentItemNotSupportedInQmlUi, + ErrDoNotMixTranslationFunctionsInQmlUi, + }); + return disabled; +} + +Check::Check(Document::Ptr doc, const ContextPtr &context, Utils::QtcSettings *qtcSettings) : _doc(doc) , _context(context) , _scopeChain(doc, _context) @@ -655,16 +695,25 @@ Check::Check(Document::Ptr doc, const ContextPtr &context) } _enabledMessages = Utils::toSet(Message::allMessageTypes()); - disableMessage(HintAnonymousFunctionSpacing); - disableMessage(HintDeclareVarsInOneLine); - disableMessage(HintDeclarationsShouldBeAtStartOfFunction); - disableMessage(HintBinaryOperatorSpacing); - disableMessage(HintOneStatementPerLine); - disableMessage(HintExtraParentheses); + if (qtcSettings && qtcSettings->value("J.QtQuick/QmlJSEditor.useCustomAnalyzer").toBool()) { + auto disabled = qtcSettings->value("J.QtQuick/QmlJSEditor.disabledMessages").toList(); + for (const QVariant &disabledNumber : disabled) + disableMessage(StaticAnalysis::Type(disabledNumber.toInt())); - disableQmlDesignerChecks(); - if (!isQtQuick2Ui()) - disableQmlDesignerUiFileChecks(); + if (!isQtQuick2Ui()) { + auto disabled = qtcSettings->value("J.QtQuick/QmlJSEditor.disabledMessagesNonQuickUI").toList(); + for (const QVariant &disabledNumber : disabled) + disableMessage(StaticAnalysis::Type(disabledNumber.toInt())); + } + } else { + for (auto type : defaultDisabledMessages()) + disableMessage(type); + + if (!isQtQuick2Ui()) { + for (auto type : defaultDisabledMessagesForNonQuickUi()) + disableMessage(type); + } + } } Check::~Check() @@ -702,17 +751,6 @@ void Check::enableQmlDesignerChecks() //## triggers too often ## check.enableMessage(StaticAnalysis::WarnUndefinedValueForVisualDesigner); } -void Check::disableQmlDesignerChecks() -{ - disableMessage(WarnImperativeCodeNotEditableInVisualDesigner); - disableMessage(WarnUnsupportedTypeInVisualDesigner); - disableMessage(WarnReferenceToParentItemNotSupportedByVisualDesigner); - disableMessage(WarnUndefinedValueForVisualDesigner); - disableMessage(WarnStatesOnlyInRootItemForVisualDesigner); - disableMessage(ErrUnsupportedRootTypeInVisualDesigner); - disableMessage(ErrInvalidIdeInVisualDesigner); -} - void Check::enableQmlDesignerUiFileChecks() { enableMessage(ErrUnsupportedRootTypeInQmlUi); diff --git a/src/libs/qmljs/qmljscheck.h b/src/libs/qmljs/qmljscheck.h index a868bbe15e2..60484fc43f1 100644 --- a/src/libs/qmljs/qmljscheck.h +++ b/src/libs/qmljs/qmljscheck.h @@ -12,6 +12,8 @@ #include #include +namespace Utils { class QtcSettings; } + namespace QmlJS { class Imports; @@ -22,7 +24,7 @@ class QMLJS_EXPORT Check: protected AST::Visitor public: // prefer taking root scope chain? - Check(Document::Ptr doc, const ContextPtr &context); + Check(Document::Ptr doc, const ContextPtr &context, Utils::QtcSettings *qtcSettings = nullptr); ~Check(); QList operator()(); @@ -31,11 +33,13 @@ public: void disableMessage(StaticAnalysis::Type type); void enableQmlDesignerChecks(); - void disableQmlDesignerChecks(); void enableQmlDesignerUiFileChecks(); void disableQmlDesignerUiFileChecks(); + static QList defaultDisabledMessages(); + static QList defaultDisabledMessagesForNonQuickUi(); + protected: bool preVisit(AST::Node *ast) override; void postVisit(AST::Node *ast) override; diff --git a/src/libs/qmljs/qmljscodeformatter.cpp b/src/libs/qmljs/qmljscodeformatter.cpp index 55ed2e6190c..a2f944700f9 100644 --- a/src/libs/qmljs/qmljscodeformatter.cpp +++ b/src/libs/qmljs/qmljscodeformatter.cpp @@ -253,9 +253,16 @@ void CodeFormatter::recalculateStateAfter(const QTextBlock &block) case function_arglist_closed: switch (kind) { case LeftBrace: turnInto(jsblock_open); break; + case Colon: turnInto(function_type_annotated_return); break; default: leave(true); continue; // error recovery } break; + case function_type_annotated_return: + switch (kind) { + case LeftBrace: turnInto(jsblock_open); break; + default: break; + } break; + case expression_or_objectdefinition: switch (kind) { case Dot: diff --git a/src/libs/qmljs/qmljscodeformatter.h b/src/libs/qmljs/qmljscodeformatter.h index 4800fccfdf0..abef85a782d 100644 --- a/src/libs/qmljs/qmljscodeformatter.h +++ b/src/libs/qmljs/qmljscodeformatter.h @@ -99,6 +99,7 @@ public: // must be public to make Q_GADGET introspection work function_start, // after 'function' function_arglist_open, // after '(' starting function argument list function_arglist_closed, // after ')' in argument list, expecting '{' + function_type_annotated_return, // after ':' expecting a type binding_or_objectdefinition, // after an identifier diff --git a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp index 3d2fc32120b..6bcfe7ee25c 100644 --- a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp +++ b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp @@ -15,8 +15,8 @@ #include #include +#include #include -#include #include #ifdef WITH_TESTS @@ -337,11 +337,9 @@ QFuture ModelManagerInterface::refreshSourceFiles(const QList(); - QFuture result = Utils::runAsync(&m_threadPool, - &ModelManagerInterface::parse, - workingCopyInternal(), sourceFiles, - this, Dialect(Dialect::Qml), - emitDocumentOnDiskChanged); + QFuture result = Utils::asyncRun(&m_threadPool, &ModelManagerInterface::parse, + workingCopyInternal(), sourceFiles, this, + Dialect(Dialect::Qml), emitDocumentOnDiskChanged); addFuture(result); if (sourceFiles.count() > 1) @@ -365,13 +363,8 @@ QFuture ModelManagerInterface::refreshSourceFiles(const QList &files) @@ -1044,24 +1037,24 @@ void ModelManagerInterface::parseLoop(QSet &scannedPaths, class FutureReporter { public: - FutureReporter(QFutureInterface &future, int multiplier, int base) - : future(future), multiplier(multiplier), base(base) + FutureReporter(QPromise &promise, int multiplier, int base) + : m_promise(promise), m_multiplier(multiplier), m_base(base) {} bool operator()(qreal val) { - if (future.isCanceled()) + if (m_promise.isCanceled()) return false; - future.setProgressValue(int(base + multiplier * val)); + m_promise.setProgressValue(int(m_base + m_multiplier * val)); return true; } private: - QFutureInterface &future; - int multiplier; - int base; + QPromise &m_promise; + int m_multiplier; + int m_base; }; -void ModelManagerInterface::parse(QFutureInterface &future, +void ModelManagerInterface::parse(QPromise &promise, const WorkingCopy &workingCopy, QList files, ModelManagerInterface *modelManager, @@ -1069,8 +1062,8 @@ void ModelManagerInterface::parse(QFutureInterface &future, bool emitDocChangedOnDisk) { const int progressMax = 100; - FutureReporter reporter(future, progressMax, 0); - future.setProgressRange(0, progressMax); + FutureReporter reporter(promise, progressMax, 0); + promise.setProgressRange(0, progressMax); // paths we have scanned for files and added to the files list QSet scannedPaths; @@ -1078,7 +1071,7 @@ void ModelManagerInterface::parse(QFutureInterface &future, QSet newLibraries; parseLoop(scannedPaths, newLibraries, workingCopy, std::move(files), modelManager, mainLanguage, emitDocChangedOnDisk, reporter); - future.setProgressValue(progressMax); + promise.setProgressValue(progressMax); } struct ScanItem { @@ -1087,11 +1080,20 @@ struct ScanItem { Dialect language = Dialect::AnyLanguage; }; -void ModelManagerInterface::importScan(QFutureInterface &future, - const ModelManagerInterface::WorkingCopy &workingCopy, +void ModelManagerInterface::importScan(const WorkingCopy &workingCopy, const PathsAndLanguages &paths, ModelManagerInterface *modelManager, - bool emitDocChangedOnDisk, bool libOnly, bool forceRescan) + bool emitDocChanged, bool libOnly, bool forceRescan) +{ + QPromise promise; + promise.start(); + importScanAsync(promise, workingCopy, paths, modelManager, emitDocChanged, libOnly, forceRescan); +} + +void ModelManagerInterface::importScanAsync(QPromise &promise, const WorkingCopy &workingCopy, + const PathsAndLanguages &paths, + ModelManagerInterface *modelManager, + bool emitDocChanged, bool libOnly, bool forceRescan) { // paths we have scanned for files and added to the files list QSet scannedPaths; @@ -1118,9 +1120,9 @@ void ModelManagerInterface::importScan(QFutureInterface &future, int progressRange = pathsToScan.size() * (1 << (2 + maxScanDepth)); int totalWork = progressRange; int workDone = 0; - future.setProgressRange(0, progressRange); // update max length while iterating? + promise.setProgressRange(0, progressRange); // update max length while iterating? const Snapshot snapshot = modelManager->snapshot(); - bool isCanceled = future.isCanceled(); + bool isCanceled = promise.isCanceled(); while (!pathsToScan.isEmpty() && !isCanceled) { ScanItem toScan = pathsToScan.last(); pathsToScan.pop_back(); @@ -1135,16 +1137,16 @@ void ModelManagerInterface::importScan(QFutureInterface &future, toScan.language.companionLanguages()); } workDone += 1; - future.setProgressValue(progressRange * workDone / totalWork); + promise.setProgressValue(progressRange * workDone / totalWork); if (!importedFiles.isEmpty()) { - FutureReporter reporter(future, progressRange * pathBudget / (4 * totalWork), + FutureReporter reporter(promise, progressRange * pathBudget / (4 * totalWork), progressRange * workDone / totalWork); parseLoop(scannedPaths, newLibraries, workingCopy, importedFiles, modelManager, - toScan.language, emitDocChangedOnDisk, reporter); // run in parallel?? + toScan.language, emitDocChanged, reporter); // run in parallel?? importedFiles.clear(); } workDone += pathBudget / 4 - 1; - future.setProgressValue(progressRange * workDone / totalWork); + promise.setProgressValue(progressRange * workDone / totalWork); } else { workDone += pathBudget / 4; } @@ -1159,10 +1161,10 @@ void ModelManagerInterface::importScan(QFutureInterface &future, } else { workDone += pathBudget * 3 / 4; } - future.setProgressValue(progressRange * workDone / totalWork); - isCanceled = future.isCanceled(); + promise.setProgressValue(progressRange * workDone / totalWork); + isCanceled = promise.isCanceled(); } - future.setProgressValue(progressRange); + promise.setProgressValue(progressRange); if (isCanceled) { // assume no work has been done QMutexLocker l(&modelManager->m_mutex); @@ -1206,8 +1208,8 @@ void ModelManagerInterface::maybeScan(const PathsAndLanguages &importPaths) } if (pathToScan.length() >= 1) { - QFuture result = Utils::runAsync(&m_threadPool, - &ModelManagerInterface::importScan, + QFuture result = Utils::asyncRun(&m_threadPool, + &ModelManagerInterface::importScanAsync, workingCopyInternal(), pathToScan, this, true, true, false); addFuture(result); @@ -1373,8 +1375,8 @@ void ModelManagerInterface::startCppQmlTypeUpdate() if (!cppModelManager) return; - m_cppQmlTypesUpdater = Utils::runAsync(&ModelManagerInterface::updateCppQmlTypes, - this, cppModelManager->snapshot(), m_queuedCppDocuments); + m_cppQmlTypesUpdater = Utils::asyncRun(&ModelManagerInterface::updateCppQmlTypes, this, + cppModelManager->snapshot(), m_queuedCppDocuments); m_queuedCppDocuments.clear(); } @@ -1415,13 +1417,12 @@ bool rescanExports(const QString &fileName, FindExportedCppTypes &finder, return hasNewInfo; } -void ModelManagerInterface::updateCppQmlTypes( - QFutureInterface &futureInterface, ModelManagerInterface *qmlModelManager, - const CPlusPlus::Snapshot &snapshot, +void ModelManagerInterface::updateCppQmlTypes(QPromise &promise, + ModelManagerInterface *qmlModelManager, const CPlusPlus::Snapshot &snapshot, const QHash> &documents) { - futureInterface.setProgressRange(0, documents.size()); - futureInterface.setProgressValue(0); + promise.setProgressRange(0, documents.size()); + promise.setProgressValue(0); CppDataHash newData; QHash> newDeclarations; @@ -1436,9 +1437,9 @@ void ModelManagerInterface::updateCppQmlTypes( bool hasNewInfo = false; using DocScanPair = QPair; for (const DocScanPair &pair : documents) { - if (futureInterface.isCanceled()) + if (promise.isCanceled()) return; - futureInterface.setProgressValue(futureInterface.progressValue() + 1); + promise.setProgressValue(promise.future().progressValue() + 1); CPlusPlus::Document::Ptr doc = pair.first; const bool scan = pair.second; diff --git a/src/libs/qmljs/qmljsmodelmanagerinterface.h b/src/libs/qmljs/qmljsmodelmanagerinterface.h index b7b88c24ff5..e702b91afbb 100644 --- a/src/libs/qmljs/qmljsmodelmanagerinterface.h +++ b/src/libs/qmljs/qmljsmodelmanagerinterface.h @@ -179,10 +179,12 @@ public: void addFuture(const QFuture &future); QmlJS::Document::Ptr ensuredGetDocumentForPath(const Utils::FilePath &filePath); - static void importScan(QFutureInterface &future, const WorkingCopy& workingCopyInternal, - const PathsAndLanguages& paths, ModelManagerInterface *modelManager, - bool emitDocChangedOnDisk, bool libOnly = true, - bool forceRescan = false); + static void importScan(const WorkingCopy &workingCopy, const PathsAndLanguages &paths, + ModelManagerInterface *modelManager, bool emitDocChanged, + bool libOnly = true, bool forceRescan = false); + static void importScanAsync(QPromise &promise, const WorkingCopy& workingCopyInternal, + const PathsAndLanguages& paths, ModelManagerInterface *modelManager, + bool emitDocChanged, bool libOnly = true, bool forceRescan = false); virtual void resetCodeModel(); void removeProjectInfo(ProjectExplorer::Project *project); @@ -218,16 +220,15 @@ protected: QmlJS::Dialect mainLanguage, bool emitDocChangedOnDisk, const std::function &reportProgress); - static void parse(QFutureInterface &future, + static void parse(QPromise &promise, const WorkingCopy &workingCopyInternal, QList files, ModelManagerInterface *modelManager, QmlJS::Dialect mainLanguage, bool emitDocChangedOnDisk); - static void updateCppQmlTypes( - QFutureInterface &futureInterface, ModelManagerInterface *qmlModelManager, - const CPlusPlus::Snapshot &snapshot, - const QHash> &documents); + static void updateCppQmlTypes(QPromise &promise, ModelManagerInterface *qmlModelManager, + const CPlusPlus::Snapshot &snapshot, + const QHash> &documents); void maybeScan(const PathsAndLanguages &importPaths); void updateImportPaths(); diff --git a/src/libs/qmljs/qmljsplugindumper.cpp b/src/libs/qmljs/qmljsplugindumper.cpp index a7bafdde948..761df90ddcd 100644 --- a/src/libs/qmljs/qmljsplugindumper.cpp +++ b/src/libs/qmljs/qmljsplugindumper.cpp @@ -7,14 +7,13 @@ #include "qmljsmodelmanagerinterface.h" #include "qmljstr.h" #include "qmljsutils.h" -#include "qmljsviewercontext.h" #include +#include #include #include #include -#include -#include +#include #include #include @@ -204,7 +203,7 @@ static void printParseWarnings(const FilePath &libraryPath, const QString &warni "%2").arg(libraryPath.toUserOutput(), warning)); } -static QString qmlPluginDumpErrorMessage(QtcProcess *process) +static QString qmlPluginDumpErrorMessage(Process *process) { QString errorMessage; const QString binary = process->commandLine().executable().toUserOutput(); @@ -238,7 +237,7 @@ static QString qmlPluginDumpErrorMessage(QtcProcess *process) return errorMessage; } -void PluginDumper::qmlPluginTypeDumpDone(QtcProcess *process) +void PluginDumper::qmlPluginTypeDumpDone(Process *process) { process->deleteLater(); @@ -273,14 +272,13 @@ void PluginDumper::qmlPluginTypeDumpDone(QtcProcess *process) QStringList dependencies; }; - auto future = Utils::runAsync(m_modelManager->threadPool(), - [output, libraryPath](QFutureInterface& future) - { + auto future = Utils::asyncRun(m_modelManager->threadPool(), + [output, libraryPath](QPromise &promise) { CppQmlTypesInfo infos; - CppQmlTypesLoader::parseQmlTypeDescriptions(output, &infos.objectsList, &infos.moduleApis, &infos.dependencies, - &infos.error, &infos.warning, - "'); - future.reportFinished(&infos); + CppQmlTypesLoader::parseQmlTypeDescriptions(output, &infos.objectsList, + &infos.moduleApis, &infos.dependencies, &infos.error, &infos.warning, + "'); + promise.addResult(infos); }); m_modelManager->addFuture(future); @@ -327,8 +325,8 @@ void PluginDumper::pluginChanged(const QString &pluginLibrary) QFuture PluginDumper::loadQmlTypeDescription(const FilePaths &paths) const { - auto future = Utils::runAsync(m_modelManager->threadPool(), [=](QFutureInterface &future) - { + auto future = Utils::asyncRun(m_modelManager->threadPool(), + [=](QPromise &promise) { PluginDumper::QmlTypeDescription result; for (const FilePath &p: paths) { @@ -355,8 +353,7 @@ QFuture PluginDumper::loadQmlTypeDescription(c if (!warning.isEmpty()) result.warnings += warning; } - - future.reportFinished(&result); + promise.addResult(result); }); m_modelManager->addFuture(future); @@ -600,11 +597,11 @@ void PluginDumper::loadQmltypesFile(const FilePaths &qmltypesFilePaths, void PluginDumper::runQmlDump(const ModelManagerInterface::ProjectInfo &info, const QStringList &arguments, const FilePath &importPath) { - auto process = new QtcProcess(this); + auto process = new Process(this); process->setEnvironment(info.qmlDumpEnvironment); process->setWorkingDirectory(importPath); process->setCommand({info.qmlDumpPath, arguments}); - connect(process, &QtcProcess::done, this, [this, process] { qmlPluginTypeDumpDone(process); }); + connect(process, &Process::done, this, [this, process] { qmlPluginTypeDumpDone(process); }); process->start(); m_runningQmldumps.insert(process, importPath); } diff --git a/src/libs/qmljs/qmljsplugindumper.h b/src/libs/qmljs/qmljsplugindumper.h index 1b672cefdc3..e27bfeba968 100644 --- a/src/libs/qmljs/qmljsplugindumper.h +++ b/src/libs/qmljs/qmljsplugindumper.h @@ -14,7 +14,7 @@ QT_END_NAMESPACE namespace Utils { class FileSystemWatcher; -class QtcProcess; +class Process; } namespace QmlJS { @@ -41,7 +41,7 @@ private: const QString &importUri, const QString &importVersion); Q_INVOKABLE void dumpAllPlugins(); - void qmlPluginTypeDumpDone(Utils::QtcProcess *process); + void qmlPluginTypeDumpDone(Utils::Process *process); void pluginChanged(const QString &pluginLibrary); private: @@ -102,7 +102,7 @@ private: ModelManagerInterface *m_modelManager; Utils::FileSystemWatcher *m_pluginWatcher; - QHash m_runningQmldumps; + QHash m_runningQmldumps; QList m_plugins; QHash m_libraryToPluginIndex; QHash m_qtToInfo; diff --git a/src/libs/qmljs/qmljsreformatter.cpp b/src/libs/qmljs/qmljsreformatter.cpp index 461c944afaf..232c0361a1a 100644 --- a/src/libs/qmljs/qmljsreformatter.cpp +++ b/src/libs/qmljs/qmljsreformatter.cpp @@ -101,22 +101,23 @@ public: _hadEmptyLine = false; _binaryExpDepth = 0; - + const QString &source = _doc->source(); // emit directives if (_doc->bind()->isJsLibrary()) { - out(QLatin1String(".pragma library")); + const QString pragmaLine(".pragma library"); + out(pragmaLine, SourceLocation(source.indexOf(".pragma"), pragmaLine.length())); newLine(); } const QList &directives = _doc->jsDirectives(); for (const auto &d: directives) { - quint32 line = 1; - int i = 0; - while (line++ < d.startLine && i++ >= 0) - i = _doc->source().indexOf(QChar('\n'), i); + quint32 line = 0; + int i = -1; + while (++line < d.startLine) + i = source.indexOf(QChar('\n'), i + 1); quint32 offset = static_cast(i) + d.startColumn; - int endline = _doc->source().indexOf('\n', static_cast(offset) + 1); - int end = endline == -1 ? _doc->source().length() : endline; - quint32 length = static_cast(end) - offset; + int endline = source.indexOf('\n', static_cast(offset) + 1); + int end = endline == -1 ? source.length() : endline; + quint32 length = static_cast(end) - offset + 1; out(SourceLocation(offset, length, d.startLine, d.startColumn)); } if (!directives.isEmpty()) @@ -1087,7 +1088,10 @@ protected: out(" "); out(ast->lparenToken); accept(ast->lhs); - out(" in "); + if (ast->type == ForEachType::In) + out(" in "); + else + out(" of "); accept(ast->expression); out(ast->rparenToken); acceptBlockOrIndented(ast->statement); diff --git a/src/libs/qmljs/qmljsutils.cpp b/src/libs/qmljs/qmljsutils.cpp index 1609e49c68c..e33a6b3e809 100644 --- a/src/libs/qmljs/qmljsutils.cpp +++ b/src/libs/qmljs/qmljsutils.cpp @@ -115,8 +115,9 @@ SourceLocation QmlJS::fullLocationForQualifiedId(AST::UiQualifiedId *qualifiedId } /*! - \returns the value of the 'id:' binding in \a object - \param idBinding optional out parameter to get the UiScriptBinding for the id binding + Returns the value of the 'id:' binding in \a object. + + \a idBinding is optional out parameter to get the UiScriptBinding for the id binding. */ QString QmlJS::idOfObject(Node *object, UiScriptBinding **idBinding) { diff --git a/src/libs/solutions/CMakeLists.txt b/src/libs/solutions/CMakeLists.txt new file mode 100644 index 00000000000..694d940195d --- /dev/null +++ b/src/libs/solutions/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(tasking) diff --git a/src/libs/solutions/README.md b/src/libs/solutions/README.md new file mode 100644 index 00000000000..ab4cc9727d3 --- /dev/null +++ b/src/libs/solutions/README.md @@ -0,0 +1,48 @@ +# Solutions project + +The Solutions project is designed to contain a collection of +object libraries, independent on any Creator's specific code, +ready to be a part of Qt. Kind of a staging area for possible +inclusions into Qt. + +## Motivation and benefits + +- Such a separation will ensure no future back dependencies to the Creator + specific code are introduced by mistake during maintenance. +- Easy to compile outside of Creator code. +- General hub of ideas to be considered by foundation team to be integrated + into Qt. +- The more stuff of a general purpose goes into Qt, the less maintenance work + for Creator. + +## Conformity of solutions + +Each solution: +- Is a separate object lib. +- Is placed in a separate subdirectory. +- Is enclosed within a namespace (namespace name = solution name). +- Should compile independently, i.e. there are no cross-includes + between solutions. + +## Dependencies of solution libraries + +**Do not add dependencies to non-Qt libraries.** +The only allowed dependencies are to Qt libraries. +Especially, don't add dependencies to any Creator's library +nor to any 3rd party library. + +If you can't avoid a dependency to the other Creator's library +in your solution, place it somewhere else (e.g. inside Utils library). + +The Utils lib depends on the solution libraries. + +## Predictions on possible integration into Qt + +The solutions in this project may have a bigger / faster chance to be +integrated into Qt when they: +- Conform to Qt API style. +- Integrate easily with existing classes / types in Qt + (instead of providing own structures / data types). +- Have full docs. +- Have auto tests. +- Have at least one example (however, autotests often play this role, too). diff --git a/src/libs/solutions/solutions.qbs b/src/libs/solutions/solutions.qbs new file mode 100644 index 00000000000..6184dce2af7 --- /dev/null +++ b/src/libs/solutions/solutions.qbs @@ -0,0 +1,7 @@ +Project { + name: "Solutions" + + references: [ + "tasking/tasking.qbs", + ].concat(project.additionalLibs) +} diff --git a/src/libs/solutions/tasking/CMakeLists.txt b/src/libs/solutions/tasking/CMakeLists.txt new file mode 100644 index 00000000000..5beed2fe5b4 --- /dev/null +++ b/src/libs/solutions/tasking/CMakeLists.txt @@ -0,0 +1,9 @@ +add_qtc_library(Tasking OBJECT +# Never add dependencies to non-Qt libraries for this library + DEPENDS Qt::Core + PUBLIC_DEFINES TASKING_LIBRARY + SOURCES + barrier.cpp barrier.h + tasking_global.h + tasktree.cpp tasktree.h +) diff --git a/src/libs/solutions/tasking/barrier.cpp b/src/libs/solutions/tasking/barrier.cpp new file mode 100644 index 00000000000..c4daa033b41 --- /dev/null +++ b/src/libs/solutions/tasking/barrier.cpp @@ -0,0 +1,52 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "barrier.h" + +namespace Tasking { + +// That's cut down qtcassert.{c,h} to avoid the dependency. +#define QTC_STRINGIFY_HELPER(x) #x +#define QTC_STRINGIFY(x) QTC_STRINGIFY_HELPER(x) +#define QTC_STRING(cond) qDebug("SOFT ASSERT: \"%s\" in %s: %s", cond, __FILE__, QTC_STRINGIFY(__LINE__)) +#define QTC_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QTC_STRING(#cond); action; } do {} while (0) +#define QTC_CHECK(cond) if (cond) {} else { QTC_STRING(#cond); } do {} while (0) + +void Barrier::setLimit(int value) +{ + QTC_ASSERT(!isRunning(), return); + QTC_ASSERT(value > 0, return); + + m_limit = value; +} + +void Barrier::start() +{ + QTC_ASSERT(!isRunning(), return); + m_current = 0; + m_result = {}; +} + +void Barrier::advance() +{ + // Calling advance on finished is OK + QTC_ASSERT(isRunning() || m_result, return); + if (!isRunning()) // no-op + return; + ++m_current; + if (m_current == m_limit) + stopWithResult(true); +} + +void Barrier::stopWithResult(bool success) +{ + // Calling stopWithResult on finished is OK when the same success is passed + QTC_ASSERT(isRunning() || (m_result && *m_result == success), return); + if (!isRunning()) // no-op + return; + m_current = -1; + m_result = success; + emit done(success); +} + +} // namespace Tasking diff --git a/src/libs/solutions/tasking/barrier.h b/src/libs/solutions/tasking/barrier.h new file mode 100644 index 00000000000..6939da5b365 --- /dev/null +++ b/src/libs/solutions/tasking/barrier.h @@ -0,0 +1,97 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "tasking_global.h" + +#include "tasktree.h" + +namespace Tasking { + +class TASKING_EXPORT Barrier final : public QObject +{ + Q_OBJECT + +public: + void setLimit(int value); + int limit() const { return m_limit; } + + void start(); + void advance(); // If limit reached, stops with true + void stopWithResult(bool success); // Ignores limit + + bool isRunning() const { return m_current >= 0; } + int current() const { return m_current; } + std::optional result() const { return m_result; } + +signals: + void done(bool success); + +private: + std::optional m_result = {}; + int m_limit = 1; + int m_current = -1; +}; + +class TASKING_EXPORT BarrierTaskAdapter : public Tasking::TaskAdapter +{ +public: + BarrierTaskAdapter() { connect(task(), &Barrier::done, this, &TaskInterface::done); } + void start() final { task()->start(); } +}; + +} // namespace Tasking + +TASKING_DECLARE_TASK(BarrierTask, Tasking::BarrierTaskAdapter); + +namespace Tasking { + +template +class SharedBarrier +{ +public: + static_assert(Limit > 0, "SharedBarrier's limit should be 1 or more."); + SharedBarrier() : m_barrier(new Barrier) { + m_barrier->setLimit(Limit); + m_barrier->start(); + } + Barrier *barrier() const { return m_barrier.get(); } + +private: + std::shared_ptr m_barrier; +}; + +template +using MultiBarrier = TreeStorage>; + +// Can't write: "MultiBarrier barrier;". Only "MultiBarrier<> barrier;" would work. +// Can't have one alias with default type in C++17, getting the following error: +// alias template deduction only available with C++20. +using SingleBarrier = MultiBarrier<1>; + +class TASKING_EXPORT WaitForBarrierTask : public BarrierTask +{ +public: + template + WaitForBarrierTask(const MultiBarrier &sharedBarrier) + : BarrierTask([sharedBarrier](Barrier &barrier) { + SharedBarrier *activeBarrier = sharedBarrier.activeStorage(); + if (!activeBarrier) { + qWarning("The barrier referenced from WaitForBarrier element " + "is not reachable in the running tree. " + "It is possible that no barrier was added to the tree, " + "or the storage is not reachable from where it is referenced. " + "The WaitForBarrier task will finish with error. "); + return TaskAction::StopWithError; + } + Barrier *activeSharedBarrier = activeBarrier->barrier(); + const std::optional result = activeSharedBarrier->result(); + if (result.has_value()) + return result.value() ? TaskAction::StopWithDone : TaskAction::StopWithError; + QObject::connect(activeSharedBarrier, &Barrier::done, &barrier, &Barrier::stopWithResult); + return TaskAction::Continue; + }) {} +}; + +} // namespace Tasking diff --git a/src/libs/solutions/tasking/tasking.qbs b/src/libs/solutions/tasking/tasking.qbs new file mode 100644 index 00000000000..8697b9c009b --- /dev/null +++ b/src/libs/solutions/tasking/tasking.qbs @@ -0,0 +1,14 @@ +QtcLibrary { + name: "Tasking" + Depends { name: "Qt"; submodules: ["core"] } + cpp.defines: base.concat("TASKING_LIBRARY") + + files: [ + "barrier.cpp", + "barrier.h", + "tasking_global.h", + "tasktree.cpp", + "tasktree.h", + ] +} + diff --git a/src/libs/solutions/tasking/tasking_global.h b/src/libs/solutions/tasking/tasking_global.h new file mode 100644 index 00000000000..d7e76fa9e6f --- /dev/null +++ b/src/libs/solutions/tasking/tasking_global.h @@ -0,0 +1,14 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +#if defined(TASKING_LIBRARY) +# define TASKING_EXPORT Q_DECL_EXPORT +#elif defined(TASKING_STATIC_LIBRARY) +# define TASKING_EXPORT +#else +# define TASKING_EXPORT Q_DECL_IMPORT +#endif diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp new file mode 100644 index 00000000000..f9fe58375e8 --- /dev/null +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -0,0 +1,1980 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "tasktree.h" + +#include +#include +#include +#include +#include + +using namespace std::chrono; + +namespace Tasking { + +// That's cut down qtcassert.{c,h} to avoid the dependency. +#define QTC_STRINGIFY_HELPER(x) #x +#define QTC_STRINGIFY(x) QTC_STRINGIFY_HELPER(x) +#define QTC_STRING(cond) qDebug("SOFT ASSERT: \"%s\" in %s: %s", cond, __FILE__, QTC_STRINGIFY(__LINE__)) +#define QTC_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QTC_STRING(#cond); action; } do {} while (0) +#define QTC_CHECK(cond) if (cond) {} else { QTC_STRING(#cond); } do {} while (0) + +class Guard +{ + Q_DISABLE_COPY(Guard) +public: + Guard() = default; + ~Guard() { QTC_CHECK(m_lockCount == 0); } + bool isLocked() const { return m_lockCount; } +private: + int m_lockCount = 0; + friend class GuardLocker; +}; + +class GuardLocker +{ + Q_DISABLE_COPY(GuardLocker) +public: + GuardLocker(Guard &guard) : m_guard(guard) { ++m_guard.m_lockCount; } + ~GuardLocker() { --m_guard.m_lockCount; } +private: + Guard &m_guard; +}; + +/*! + \class Tasking::TaskItem + \inheaderfile solutions/tasking/tasktree.h + \inmodule QtCreator + \ingroup mainclasses + \brief The TaskItem class represents the basic element for composing nested tree structures. +*/ + +/*! + \enum Tasking::WorkflowPolicy + + This enum describes the possible behavior of the Group element when any group's child task + finishes its execution. It's also used when the running Group is stopped. + + \value StopOnError + Default. Corresponds to the stopOnError global element. + If any child task finishes with an error, the group stops and finishes with an error. + If all child tasks finished with success, the group finishes with success. + If a group is empty, it finishes with success. + \value ContinueOnError + Corresponds to the continueOnError global element. + Similar to stopOnError, but in case any child finishes with an error, + the execution continues until all tasks finish, and the group reports an error + afterwards, even when some other tasks in the group finished with success. + If all child tasks finish successfully, the group finishes with success. + If a group is empty, it finishes with success. + \value StopOnDone + Corresponds to the stopOnDone global element. + If any child task finishes with success, the group stops and finishes with success. + If all child tasks finished with an error, the group finishes with an error. + If a group is empty, it finishes with an error. + \value ContinueOnDone + Corresponds to the continueOnDone global element. + Similar to stopOnDone, but in case any child finishes successfully, + the execution continues until all tasks finish, and the group reports success + afterwards, even when some other tasks in the group finished with an error. + If all child tasks finish with an error, the group finishes with an error. + If a group is empty, it finishes with an error. + \value StopOnFinished + Corresponds to the stopOnFinished global element. + The group starts as many tasks as it can. When any task finishes, + the group stops and reports the task's result. + Useful only in parallel mode. + In sequential mode, only the first task is started, and when finished, + the group finishes too, so the other tasks are always skipped. + If a group is empty, it finishes with an error. + \value FinishAllAndDone + Corresponds to the finishAllAndDone global element. + The group executes all tasks and ignores their return results. When all + tasks finished, the group finishes with success. + If a group is empty, it finishes with success. + \value FinishAllAndError + Corresponds to the finishAllAndError global element. + The group executes all tasks and ignores their return results. When all + tasks finished, the group finishes with an error. + If a group is empty, it finishes with an error. + + Whenever a child task's result causes the Group to stop, + i.e. in case of StopOnError, StopOnDone, or StopOnFinished policies, + the Group stops the other running child tasks (if any - for example in parallel mode), + and skips executing tasks it has not started yet (for example, in the sequential mode - + those, that are placed after the failed task). Both stopping and skipping child tasks + may happen when parallelLimit is used. + + The table below summarizes the differences between various workflow policies: + + \table + \header + \li \l WorkflowPolicy + \li Executes all child tasks + \li Result + \li Result when the group is empty + \row + \li StopOnError + \li Stops when any child task finished with an error and reports an error + \li An error when at least one child task failed, success otherwise + \li Success + \row + \li ContinueOnError + \li Yes + \li An error when at least one child task failed, success otherwise + \li Success + \row + \li StopOnDone + \li Stops when any child task finished with success and reports success + \li Success when at least one child task succeeded, an error otherwise + \li An error + \row + \li ContinueOnDone + \li Yes + \li Success when at least one child task succeeded, an error otherwise + \li An error + \row + \li StopOnFinished + \li Stops when any child task finished and reports child task's result + \li Success or an error, depending on the finished child task's result + \li An error + \row + \li FinishAllAndDone + \li Yes + \li Success + \li Success + \row + \li FinishAllAndError + \li Yes + \li An error + \li An error + \endtable + + If a child of a group is also a group, the child group runs its tasks according to its own + workflow policy. When a parent group stops the running child group because + of parent group's workflow policy, i.e. when the StopOnError, StopOnDone, or StopOnFinished + policy was used for the parent, the child group's result is reported according to the + \b Result column and to the \b {child group's workflow policy} row in the table above. +*/ + +/*! + \variable sequential + A convenient global group's element describing the sequential execution mode. + + This is the default execution mode of the Group element. + + When a Group has no execution mode, it runs in the sequential mode. + All the direct child tasks of a group are started in a chain, so that when one task finishes, + the next one starts. This enables you to pass the results from the previous task + as input to the next task before it starts. This mode guarantees that the next task + is started only after the previous task finishes. + + \sa parallel, parallelLimit +*/ + +/*! + \variable parallel + A convenient global group's element describing the parallel execution mode. + + All the direct child tasks of a group are started after the group is started, + without waiting for the previous child tasks to finish. + In this mode, all child tasks run simultaneously. + + \sa sequential, parallelLimit +*/ + +/*! + \variable stopOnError + A convenient global group's element describing the StopOnError workflow policy. + + This is the default workflow policy of the Group element. +*/ + +/*! + \variable continueOnError + A convenient global group's element describing the ContinueOnError workflow policy. +*/ + +/*! + \variable stopOnDone + A convenient global group's element describing the StopOnDone workflow policy. +*/ + +/*! + \variable continueOnDone + A convenient global group's element describing the ContinueOnDone workflow policy. +*/ + +/*! + \variable stopOnFinished + A convenient global group's element describing the StopOnFinished workflow policy. +*/ + +/*! + \variable finishAllAndDone + A convenient global group's element describing the FinishAllAndDone workflow policy. +*/ + +/*! + \variable finishAllAndError + A convenient global group's element describing the FinishAllAndError workflow policy. +*/ + +/*! + \enum Tasking::TaskAction + + This enum is optionally returned from the group's or task's setup handler function. + It instructs the running task tree on how to proceed after the setup handler's execution + finished. + \value Continue + Default. The group's or task's execution continues nomally. + When a group's or task's setup handler returns void, it's assumed that + it returned Continue. + \value StopWithDone + The group's or task's execution stops immediately with success. + When returned from the group's setup handler, all child tasks are skipped, + and the group's onGroupDone handler is invoked (if provided). + When returned from the task's setup handler, the task isn't started, + its done handler isn't invoked, and the task reports success to its parent. + \value StopWithError + The group's or task's execution stops immediately with an error. + When returned from the group's setup handler, all child tasks are skipped, + and the group's onGroupError handler is invoked (if provided). + When returned from the task's setup handler, the task isn't started, + its error handler isn't invoked, and the task reports an error to its parent. +*/ + +/*! + \typealias TaskItem::GroupSetupHandler + + Type alias for \c std::function. + + The GroupSetupHandler is used when constructing the onGroupSetup element. + Any function with the above signature, when passed as a group setup handler, + will be called by the running task tree when the group executions starts. + + The return value of the handler instructs the running group on how to proceed + after the handler's invocation is finished. The default return value of TaskAction::Continue + instructs the group to continue running, i.e. to start executing its child tasks. + The return value of TaskAction::StopWithDone or TaskAction::StopWithError + instructs the group to skip the child tasks' execution and finish immediately with + success or an error, respectively. + + When the return type is either TaskAction::StopWithDone + of TaskAction::StopWithError, the group's done or error handler (if provided) + is called synchronously immediately afterwards. + + \note Even if the group setup handler returns StopWithDone or StopWithError, + one of the group's done or error handlers is invoked. This behavior differs + from that of task handlers and might change in the future. + + The onGroupSetup accepts also functions in the shortened form of \c std::function, + i.e. the return value is void. In this case it's assumed that the return value + is TaskAction::Continue by default. + + \sa onGroupSetup +*/ + +/*! + \typealias TaskItem::GroupEndHandler + + Type alias for \c std::function\. + + The GroupEndHandler is used when constructing the onGroupDone and onGroupError elements. + Any function with the above signature, when passed as a group done or error handler, + will be called by the running task tree when the group ends with success or an error, + respectively. + + \sa onGroupDone, onGroupError +*/ + +/*! + \fn template TaskItem onGroupSetup(SetupHandler &&handler) + + Constructs a group's element holding the group setup handler. + The \a handler is invoked whenever the group starts. + + The passed \a handler is either of \c std::function or \c std::function + type. For more information on possible argument type, refer to \l {TaskItem::GroupSetupHandler}. + + When the \a handler is invoked, none of the group's child tasks are running yet. + + If a group contains the Storage elements, the \a handler is invoked + after the storages are constructed, so that the \a handler may already + perform some initial modifications to the active storages. + + \sa TaskItem::GroupSetupHandler, onGroupDone, onGroupError +*/ + +/*! + Constructs a group's element holding the group done handler. + The \a handler is invoked whenever the group finishes with success. + Depending on the group's workflow policy, this handler may also be called + when the running group is stopped (e.g. when finishAllAndDone element was used). + + When the \a handler is invoked, all of the group's child tasks are already finished. + + If a group contains the Storage elements, the \a handler is invoked + before the storages are destructed, so that the \a handler may still + perform a last read of the active storages' data. + + \sa TaskItem::GroupEndHandler, onGroupSetup, onGroupError +*/ +TaskItem onGroupDone(const TaskItem::GroupEndHandler &handler) +{ + return Group::onGroupDone(handler); +} + +/*! + Constructs a group's element holding the group error handler. + The \a handler is invoked whenever the group finishes with an error. + Depending on the group's workflow policy, this handler may also be called + when the running group is stopped (e.g. when stopOnError element was used). + + When the \a handler is invoked, all of the group's child tasks are already finished. + + If a group contains the Storage elements, the \a handler is invoked + before the storages are destructed, so that the \a handler may still + perform a last read of the active storages' data. + + \sa TaskItem::GroupEndHandler, onGroupSetup, onGroupDone +*/ +TaskItem onGroupError(const TaskItem::GroupEndHandler &handler) +{ + return Group::onGroupError(handler); +} + +/*! + Constructs a group's element describing the \l{Execution Mode}{execution mode}. + + The execution mode element in a Group specifies how the direct child tasks of + the Group are started. + + For convenience, when appropriate, the \l sequential or \l parallel global elements + may be used instead. + + The \a limit defines the maximum number of direct child tasks running in parallel: + + \list + \li When \a limit equals to 0, there is no limit, and all direct child tasks are started + together, in the oder in which they appear in a group. This means the fully parallel + execution, and the \l parallel element may be used instead. + + \li When \a limit equals to 1, it means that only one child task may run at the time. + This means the sequential execution, and the \l sequential element may be used instead. + In this case child tasks run in chain, so the next child task starts after + the previous child task has finished. + + \li When other positive number is passed as \a limit, the group's child tasks run + in parallel, but with a limited number of tasks running simultanously. + The \e limit defines the maximum number of tasks running in parallel in a group. + When the group is started, the first batch of tasks is started + (the number of tasks in a batch equals to the passed \a limit, at most), + while the others are kept waiting. When any running task finishes, + the group starts the next remaining one, so that the \e limit of simultaneously + running tasks inside a group isn't exceeded. This repeats on every child task's + finish until all child tasks are started. This enables you to limit the maximum + number of tasks that run simultaneously, for example if running too many processes might + block the machine for a long time. + \endlist + + In all execution modes, a group starts tasks in the oder in which they appear. + + If a child of a group is also a group, the child group runs its tasks according + to its own execution mode. + + \sa sequential, parallel +*/ +TaskItem parallelLimit(int limit) +{ + return Group::parallelLimit(qMax(limit, 0)); +} + +/*! + Constructs a group's workflow policy element for a given \a policy. + + For convenience, global elements may be used instead. + + \sa stopOnError, continueOnError, stopOnDone, continueOnDone, stopOnFinished, finishAllAndDone, + finishAllAndError, WorkflowPolicy +*/ +TaskItem workflowPolicy(WorkflowPolicy policy) +{ + return Group::workflowPolicy(policy); +} + +const TaskItem sequential = parallelLimit(1); +const TaskItem parallel = parallelLimit(0); + +const TaskItem stopOnError = workflowPolicy(WorkflowPolicy::StopOnError); +const TaskItem continueOnError = workflowPolicy(WorkflowPolicy::ContinueOnError); +const TaskItem stopOnDone = workflowPolicy(WorkflowPolicy::StopOnDone); +const TaskItem continueOnDone = workflowPolicy(WorkflowPolicy::ContinueOnDone); +const TaskItem stopOnFinished = workflowPolicy(WorkflowPolicy::StopOnFinished); +const TaskItem finishAllAndDone = workflowPolicy(WorkflowPolicy::FinishAllAndDone); +const TaskItem finishAllAndError = workflowPolicy(WorkflowPolicy::FinishAllAndError); + +static TaskAction toTaskAction(bool success) +{ + return success ? TaskAction::StopWithDone : TaskAction::StopWithError; +} + +bool TreeStorageBase::isValid() const +{ + return m_storageData && m_storageData->m_constructor && m_storageData->m_destructor; +} + +TreeStorageBase::TreeStorageBase(StorageConstructor ctor, StorageDestructor dtor) + : m_storageData(new StorageData{ctor, dtor}) { } + +TreeStorageBase::StorageData::~StorageData() +{ + QTC_CHECK(m_storageHash.isEmpty()); + for (void *ptr : std::as_const(m_storageHash)) + m_destructor(ptr); +} + +void *TreeStorageBase::activeStorageVoid() const +{ + QTC_ASSERT(m_storageData->m_activeStorage, qWarning( + "The referenced storage is not reachable in the running tree. " + "A nullptr will be returned which might lead to a crash in the calling code. " + "It is possible that no storage was added to the tree, " + "or the storage is not reachable from where it is referenced."); + return nullptr); + const auto it = m_storageData->m_storageHash.constFind(m_storageData->m_activeStorage); + QTC_ASSERT(it != m_storageData->m_storageHash.constEnd(), return nullptr); + return it.value(); +} + +int TreeStorageBase::createStorage() const +{ + QTC_ASSERT(m_storageData->m_constructor, return 0); // TODO: add isValid()? + QTC_ASSERT(m_storageData->m_destructor, return 0); + QTC_ASSERT(m_storageData->m_activeStorage == 0, return 0); // TODO: should be allowed? + const int newId = ++m_storageData->m_storageCounter; + m_storageData->m_storageHash.insert(newId, m_storageData->m_constructor()); + return newId; +} + +void TreeStorageBase::deleteStorage(int id) const +{ + QTC_ASSERT(m_storageData->m_constructor, return); // TODO: add isValid()? + QTC_ASSERT(m_storageData->m_destructor, return); + QTC_ASSERT(m_storageData->m_activeStorage == 0, return); // TODO: should be allowed? + const auto it = m_storageData->m_storageHash.constFind(id); + QTC_ASSERT(it != m_storageData->m_storageHash.constEnd(), return); + m_storageData->m_destructor(it.value()); + m_storageData->m_storageHash.erase(it); +} + +// passing 0 deactivates currently active storage +void TreeStorageBase::activateStorage(int id) const +{ + if (id == 0) { + QTC_ASSERT(m_storageData->m_activeStorage, return); + m_storageData->m_activeStorage = 0; + return; + } + QTC_ASSERT(m_storageData->m_activeStorage == 0, return); + const auto it = m_storageData->m_storageHash.find(id); + QTC_ASSERT(it != m_storageData->m_storageHash.end(), return); + m_storageData->m_activeStorage = id; +} + +void TaskItem::addChildren(const QList &children) +{ + QTC_ASSERT(m_type == Type::Group, qWarning("Only Group may have children, skipping..."); + return); + for (const TaskItem &child : children) { + switch (child.m_type) { + case Type::Group: + m_children.append(child); + break; + case Type::GroupData: + if (child.m_groupData.m_groupHandler.m_setupHandler) { + QTC_ASSERT(!m_groupData.m_groupHandler.m_setupHandler, + qWarning("Group Setup Handler redefinition, overriding...")); + m_groupData.m_groupHandler.m_setupHandler + = child.m_groupData.m_groupHandler.m_setupHandler; + } + if (child.m_groupData.m_groupHandler.m_doneHandler) { + QTC_ASSERT(!m_groupData.m_groupHandler.m_doneHandler, + qWarning("Group Done Handler redefinition, overriding...")); + m_groupData.m_groupHandler.m_doneHandler + = child.m_groupData.m_groupHandler.m_doneHandler; + } + if (child.m_groupData.m_groupHandler.m_errorHandler) { + QTC_ASSERT(!m_groupData.m_groupHandler.m_errorHandler, + qWarning("Group Error Handler redefinition, overriding...")); + m_groupData.m_groupHandler.m_errorHandler + = child.m_groupData.m_groupHandler.m_errorHandler; + } + if (child.m_groupData.m_parallelLimit) { + QTC_ASSERT(!m_groupData.m_parallelLimit, + qWarning("Group Execution Mode redefinition, overriding...")); + m_groupData.m_parallelLimit = child.m_groupData.m_parallelLimit; + } + if (child.m_groupData.m_workflowPolicy) { + QTC_ASSERT(!m_groupData.m_workflowPolicy, + qWarning("Group Workflow Policy redefinition, overriding...")); + m_groupData.m_workflowPolicy = child.m_groupData.m_workflowPolicy; + } + break; + case Type::TaskHandler: + QTC_ASSERT(child.m_taskHandler.m_createHandler, + qWarning("Task Create Handler can't be null, skipping..."); return); + m_children.append(child); + break; + case Type::Storage: + m_storageList.append(child.m_storageList); + break; + } + } +} + +void TaskItem::setTaskSetupHandler(const TaskSetupHandler &handler) +{ + if (!handler) { + qWarning("Setting empty Setup Handler is no-op, skipping..."); + return; + } + if (m_taskHandler.m_setupHandler) + qWarning("Setup Handler redefinition, overriding..."); + m_taskHandler.m_setupHandler = handler; +} + +void TaskItem::setTaskDoneHandler(const TaskEndHandler &handler) +{ + if (!handler) { + qWarning("Setting empty Done Handler is no-op, skipping..."); + return; + } + if (m_taskHandler.m_doneHandler) + qWarning("Done Handler redefinition, overriding..."); + m_taskHandler.m_doneHandler = handler; +} + +void TaskItem::setTaskErrorHandler(const TaskEndHandler &handler) +{ + if (!handler) { + qWarning("Setting empty Error Handler is no-op, skipping..."); + return; + } + if (m_taskHandler.m_errorHandler) + qWarning("Error Handler redefinition, overriding..."); + m_taskHandler.m_errorHandler = handler; +} + +TaskItem TaskItem::withTimeout(const TaskItem &item, milliseconds timeout, + const GroupEndHandler &handler) +{ + const TimeoutTask::EndHandler taskHandler = handler + ? [handler](const milliseconds &) { handler(); } : TimeoutTask::EndHandler(); + return Group { + parallel, + stopOnFinished, + Group { + finishAllAndError, + TimeoutTask([timeout](milliseconds &timeoutData) { timeoutData = timeout; }, + taskHandler) + }, + item + }; +} + +class TaskTreePrivate; +class TaskNode; + +class TaskTreePrivate +{ + Q_DISABLE_COPY_MOVE(TaskTreePrivate) + +public: + TaskTreePrivate(TaskTree *taskTree) + : q(taskTree) {} + + void start(); + void stop(); + void advanceProgress(int byValue); + void emitStartedAndProgress(); + void emitProgress(); + void emitDone(); + void emitError(); + QList addStorages(const QList &storages); + void callSetupHandler(TreeStorageBase storage, int storageId) { + callStorageHandler(storage, storageId, &StorageHandler::m_setupHandler); + } + void callDoneHandler(TreeStorageBase storage, int storageId) { + callStorageHandler(storage, storageId, &StorageHandler::m_doneHandler); + } + struct StorageHandler { + TaskTree::StorageVoidHandler m_setupHandler = {}; + TaskTree::StorageVoidHandler m_doneHandler = {}; + }; + typedef TaskTree::StorageVoidHandler StorageHandler::*HandlerPtr; // ptr to class member + void callStorageHandler(TreeStorageBase storage, int storageId, HandlerPtr ptr) + { + const auto it = m_storageHandlers.constFind(storage); + if (it == m_storageHandlers.constEnd()) + return; + GuardLocker locker(m_guard); + const StorageHandler storageHandler = *it; + storage.activateStorage(storageId); + if (storageHandler.*ptr) + (storageHandler.*ptr)(storage.activeStorageVoid()); + storage.activateStorage(0); + } + + TaskTree *q = nullptr; + Guard m_guard; + int m_progressValue = 0; + QSet m_storages; + QHash m_storageHandlers; + std::unique_ptr m_root = nullptr; // Keep me last in order to destruct first +}; + +class TaskContainer +{ + Q_DISABLE_COPY_MOVE(TaskContainer) + +public: + TaskContainer(TaskTreePrivate *taskTreePrivate, const TaskItem &task, + TaskNode *parentNode, TaskContainer *parentContainer) + : m_constData(taskTreePrivate, task, parentNode, parentContainer, this) {} + TaskAction start(); + TaskAction continueStart(TaskAction startAction, int nextChild); + TaskAction startChildren(int nextChild); + TaskAction childDone(bool success); + void stop(); + void invokeEndHandler(); + bool isRunning() const { return m_runtimeData.has_value(); } + bool isStarting() const { return isRunning() && m_runtimeData->m_startGuard.isLocked(); } + + struct ConstData { + ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task, TaskNode *parentNode, + TaskContainer *parentContainer, TaskContainer *thisContainer); + ~ConstData() { qDeleteAll(m_children); } + TaskTreePrivate * const m_taskTreePrivate = nullptr; + TaskNode * const m_parentNode = nullptr; + TaskContainer * const m_parentContainer = nullptr; + + const int m_parallelLimit = 1; + const WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError; + const TaskItem::GroupHandler m_groupHandler; + const QList m_storageList; + const QList m_children; + const int m_taskCount = 0; + }; + + struct RuntimeData { + RuntimeData(const ConstData &constData); + ~RuntimeData(); + + static QList createStorages(const TaskContainer::ConstData &constData); + void callStorageDoneHandlers(); + bool updateSuccessBit(bool success); + int currentLimit() const; + + const ConstData &m_constData; + const QList m_storageIdList; + bool m_successBit = true; + int m_doneCount = 0; + Guard m_startGuard; + }; + + const ConstData m_constData; + std::optional m_runtimeData; +}; + +class TaskNode +{ + Q_DISABLE_COPY_MOVE(TaskNode) + +public: + TaskNode(TaskTreePrivate *taskTreePrivate, const TaskItem &task, + TaskContainer *parentContainer) + : m_taskHandler(task.taskHandler()) + , m_container(taskTreePrivate, task, this, parentContainer) + {} + + // If returned value != Continue, childDone() needs to be called in parent container (in caller) + // in order to unwind properly. + TaskAction start(); + void stop(); + void invokeEndHandler(bool success); + bool isRunning() const { return m_task || m_container.isRunning(); } + bool isTask() const { return bool(m_taskHandler.m_createHandler); } + int taskCount() const { return isTask() ? 1 : m_container.m_constData.m_taskCount; } + TaskContainer *parentContainer() const { return m_container.m_constData.m_parentContainer; } + TaskTree *taskTree() const { return m_container.m_constData.m_taskTreePrivate->q; } + +private: + const TaskItem::TaskHandler m_taskHandler; + TaskContainer m_container; + std::unique_ptr m_task; +}; + +void TaskTreePrivate::start() +{ + QTC_ASSERT(m_root, return); + m_progressValue = 0; + emitStartedAndProgress(); + // TODO: check storage handlers for not existing storages in tree + for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) { + QTC_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't " + "exist in task tree. Its handlers will never be called.")); + } + m_root->start(); +} + +void TaskTreePrivate::stop() +{ + QTC_ASSERT(m_root, return); + if (!m_root->isRunning()) + return; + // TODO: should we have canceled flag (passed to handler)? + // Just one done handler with result flag: + // FinishedWithSuccess, FinishedWithError, Canceled, TimedOut. + // Canceled either directly by user, or by workflow policy - doesn't matter, in both + // cases canceled from outside. + m_root->stop(); + emitError(); +} + +void TaskTreePrivate::advanceProgress(int byValue) +{ + if (byValue == 0) + return; + QTC_CHECK(byValue > 0); + QTC_CHECK(m_progressValue + byValue <= m_root->taskCount()); + m_progressValue += byValue; + emitProgress(); +} + +void TaskTreePrivate::emitStartedAndProgress() +{ + GuardLocker locker(m_guard); + emit q->started(); + emit q->progressValueChanged(m_progressValue); +} + +void TaskTreePrivate::emitProgress() +{ + GuardLocker locker(m_guard); + emit q->progressValueChanged(m_progressValue); +} + +void TaskTreePrivate::emitDone() +{ + QTC_CHECK(m_progressValue == m_root->taskCount()); + GuardLocker locker(m_guard); + emit q->done(); +} + +void TaskTreePrivate::emitError() +{ + QTC_CHECK(m_progressValue == m_root->taskCount()); + GuardLocker locker(m_guard); + emit q->errorOccurred(); +} + +QList TaskTreePrivate::addStorages(const QList &storages) +{ + QList addedStorages; + for (const TreeStorageBase &storage : storages) { + QTC_ASSERT(!m_storages.contains(storage), qWarning("Can't add the same storage into " + "one TaskTree twice, skipping..."); continue); + addedStorages << storage; + m_storages << storage; + } + return addedStorages; +} + +class ExecutionContextActivator +{ +public: + ExecutionContextActivator(TaskContainer *container) + : m_container(container) { activateContext(m_container); } + ~ExecutionContextActivator() { deactivateContext(m_container); } + +private: + static void activateContext(TaskContainer *container) + { + QTC_ASSERT(container && container->isRunning(), return); + const TaskContainer::ConstData &constData = container->m_constData; + if (constData.m_parentContainer) + activateContext(constData.m_parentContainer); + for (int i = 0; i < constData.m_storageList.size(); ++i) + constData.m_storageList[i].activateStorage(container->m_runtimeData->m_storageIdList.value(i)); + } + static void deactivateContext(TaskContainer *container) + { + QTC_ASSERT(container && container->isRunning(), return); + const TaskContainer::ConstData &constData = container->m_constData; + for (int i = constData.m_storageList.size() - 1; i >= 0; --i) // iterate in reverse order + constData.m_storageList[i].activateStorage(0); + if (constData.m_parentContainer) + deactivateContext(constData.m_parentContainer); + } + TaskContainer *m_container = nullptr; +}; + +template > +ReturnType invokeHandler(TaskContainer *container, Handler &&handler, Args &&...args) +{ + ExecutionContextActivator activator(container); + GuardLocker locker(container->m_constData.m_taskTreePrivate->m_guard); + return std::invoke(std::forward(handler), std::forward(args)...); +} + +static QList createChildren(TaskTreePrivate *taskTreePrivate, TaskContainer *container, + const TaskItem &task) +{ + QList result; + const QList &children = task.children(); + for (const TaskItem &child : children) + result.append(new TaskNode(taskTreePrivate, child, container)); + return result; +} + +TaskContainer::ConstData::ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task, + TaskNode *parentNode, TaskContainer *parentContainer, + TaskContainer *thisContainer) + : m_taskTreePrivate(taskTreePrivate) + , m_parentNode(parentNode) + , m_parentContainer(parentContainer) + , m_parallelLimit(task.groupData().m_parallelLimit.value_or(1)) + , m_workflowPolicy(task.groupData().m_workflowPolicy.value_or(WorkflowPolicy::StopOnError)) + , m_groupHandler(task.groupData().m_groupHandler) + , m_storageList(taskTreePrivate->addStorages(task.storageList())) + , m_children(createChildren(taskTreePrivate, thisContainer, task)) + , m_taskCount(std::accumulate(m_children.cbegin(), m_children.cend(), 0, + [](int r, TaskNode *n) { return r + n->taskCount(); })) +{} + +QList TaskContainer::RuntimeData::createStorages(const TaskContainer::ConstData &constData) +{ + QList storageIdList; + for (const TreeStorageBase &storage : constData.m_storageList) { + const int storageId = storage.createStorage(); + storageIdList.append(storageId); + constData.m_taskTreePrivate->callSetupHandler(storage, storageId); + } + return storageIdList; +} + +void TaskContainer::RuntimeData::callStorageDoneHandlers() +{ + for (int i = m_constData.m_storageList.size() - 1; i >= 0; --i) { // iterate in reverse order + const TreeStorageBase storage = m_constData.m_storageList[i]; + const int storageId = m_storageIdList.value(i); + m_constData.m_taskTreePrivate->callDoneHandler(storage, storageId); + } +} + +static bool initialSuccessBit(WorkflowPolicy workflowPolicy) +{ + switch (workflowPolicy) { + case WorkflowPolicy::StopOnError: + case WorkflowPolicy::ContinueOnError: + case WorkflowPolicy::FinishAllAndDone: + return true; + case WorkflowPolicy::StopOnDone: + case WorkflowPolicy::ContinueOnDone: + case WorkflowPolicy::StopOnFinished: + case WorkflowPolicy::FinishAllAndError: + return false; + } + QTC_CHECK(false); + return false; +} + +TaskContainer::RuntimeData::RuntimeData(const ConstData &constData) + : m_constData(constData) + , m_storageIdList(createStorages(constData)) + , m_successBit(initialSuccessBit(m_constData.m_workflowPolicy)) +{} + +TaskContainer::RuntimeData::~RuntimeData() +{ + for (int i = m_constData.m_storageList.size() - 1; i >= 0; --i) { // iterate in reverse order + const TreeStorageBase storage = m_constData.m_storageList[i]; + const int storageId = m_storageIdList.value(i); + storage.deleteStorage(storageId); + } +} + +bool TaskContainer::RuntimeData::updateSuccessBit(bool success) +{ + if (m_constData.m_workflowPolicy == WorkflowPolicy::FinishAllAndDone + || m_constData.m_workflowPolicy == WorkflowPolicy::FinishAllAndError + || m_constData.m_workflowPolicy == WorkflowPolicy::StopOnFinished) { + if (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnFinished) + m_successBit = success; + return m_successBit; + } + + const bool donePolicy = m_constData.m_workflowPolicy == WorkflowPolicy::StopOnDone + || m_constData.m_workflowPolicy == WorkflowPolicy::ContinueOnDone; + m_successBit = donePolicy ? (m_successBit || success) : (m_successBit && success); + return m_successBit; +} + +int TaskContainer::RuntimeData::currentLimit() const +{ + const int childCount = m_constData.m_children.size(); + return m_constData.m_parallelLimit + ? qMin(m_doneCount + m_constData.m_parallelLimit, childCount) : childCount; +} + +TaskAction TaskContainer::start() +{ + QTC_CHECK(!isRunning()); + m_runtimeData.emplace(m_constData); + + TaskAction startAction = TaskAction::Continue; + if (m_constData.m_groupHandler.m_setupHandler) { + startAction = invokeHandler(this, m_constData.m_groupHandler.m_setupHandler); + if (startAction != TaskAction::Continue) + m_constData.m_taskTreePrivate->advanceProgress(m_constData.m_taskCount); + } + if (startAction == TaskAction::Continue) { + if (m_constData.m_children.isEmpty()) + startAction = toTaskAction(m_runtimeData->m_successBit); + } + return continueStart(startAction, 0); +} + +TaskAction TaskContainer::continueStart(TaskAction startAction, int nextChild) +{ + const TaskAction groupAction = startAction == TaskAction::Continue ? startChildren(nextChild) + : startAction; + QTC_CHECK(isRunning()); // TODO: superfluous + if (groupAction != TaskAction::Continue) { + const bool success = m_runtimeData->updateSuccessBit(groupAction == TaskAction::StopWithDone); + invokeEndHandler(); + if (TaskContainer *parentContainer = m_constData.m_parentContainer) { + QTC_CHECK(parentContainer->isRunning()); + if (!parentContainer->isStarting()) + parentContainer->childDone(success); + } else if (success) { + m_constData.m_taskTreePrivate->emitDone(); + } else { + m_constData.m_taskTreePrivate->emitError(); + } + } + return groupAction; +} + +TaskAction TaskContainer::startChildren(int nextChild) +{ + QTC_CHECK(isRunning()); + GuardLocker locker(m_runtimeData->m_startGuard); + for (int i = nextChild; i < m_constData.m_children.size(); ++i) { + const int limit = m_runtimeData->currentLimit(); + if (i >= limit) + break; + + const TaskAction startAction = m_constData.m_children.at(i)->start(); + if (startAction == TaskAction::Continue) + continue; + + const TaskAction finalizeAction = childDone(startAction == TaskAction::StopWithDone); + if (finalizeAction == TaskAction::Continue) + continue; + + int skippedTaskCount = 0; + // Skip scheduled but not run yet. The current (i) was already notified. + for (int j = i + 1; j < limit; ++j) + skippedTaskCount += m_constData.m_children.at(j)->taskCount(); + m_constData.m_taskTreePrivate->advanceProgress(skippedTaskCount); + return finalizeAction; + } + return TaskAction::Continue; +} + +TaskAction TaskContainer::childDone(bool success) +{ + QTC_CHECK(isRunning()); + const int limit = m_runtimeData->currentLimit(); // Read before bumping m_doneCount and stop() + const bool shouldStop = m_constData.m_workflowPolicy == WorkflowPolicy::StopOnFinished + || (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnDone && success) + || (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnError && !success); + if (shouldStop) + stop(); + + ++m_runtimeData->m_doneCount; + const bool updatedSuccess = m_runtimeData->updateSuccessBit(success); + const TaskAction startAction + = (shouldStop || m_runtimeData->m_doneCount == m_constData.m_children.size()) + ? toTaskAction(updatedSuccess) : TaskAction::Continue; + + if (isStarting()) + return startAction; + return continueStart(startAction, limit); +} + +void TaskContainer::stop() +{ + if (!isRunning()) + return; + + const int limit = m_runtimeData->currentLimit(); + for (int i = 0; i < limit; ++i) + m_constData.m_children.at(i)->stop(); + + int skippedTaskCount = 0; + for (int i = limit; i < m_constData.m_children.size(); ++i) + skippedTaskCount += m_constData.m_children.at(i)->taskCount(); + + m_constData.m_taskTreePrivate->advanceProgress(skippedTaskCount); +} + +void TaskContainer::invokeEndHandler() +{ + const TaskItem::GroupHandler &groupHandler = m_constData.m_groupHandler; + if (m_runtimeData->m_successBit && groupHandler.m_doneHandler) + invokeHandler(this, groupHandler.m_doneHandler); + else if (!m_runtimeData->m_successBit && groupHandler.m_errorHandler) + invokeHandler(this, groupHandler.m_errorHandler); + m_runtimeData->callStorageDoneHandlers(); + m_runtimeData.reset(); +} + +TaskAction TaskNode::start() +{ + QTC_CHECK(!isRunning()); + if (!isTask()) + return m_container.start(); + + m_task.reset(m_taskHandler.m_createHandler()); + const TaskAction startAction = m_taskHandler.m_setupHandler + ? invokeHandler(parentContainer(), m_taskHandler.m_setupHandler, *m_task.get()) + : TaskAction::Continue; + if (startAction != TaskAction::Continue) { + m_container.m_constData.m_taskTreePrivate->advanceProgress(1); + m_task.reset(); + return startAction; + } + const std::shared_ptr unwindAction + = std::make_shared(TaskAction::Continue); + QObject::connect(m_task.get(), &TaskInterface::done, taskTree(), [=](bool success) { + invokeEndHandler(success); + QObject::disconnect(m_task.get(), &TaskInterface::done, taskTree(), nullptr); + m_task.release()->deleteLater(); + QTC_ASSERT(parentContainer() && parentContainer()->isRunning(), return); + if (parentContainer()->isStarting()) + *unwindAction = toTaskAction(success); + else + parentContainer()->childDone(success); + }); + + m_task->start(); + return *unwindAction; +} + +void TaskNode::stop() +{ + if (!isRunning()) + return; + + if (!m_task) { + m_container.stop(); + m_container.m_runtimeData->updateSuccessBit(false); + m_container.invokeEndHandler(); + return; + } + + // TODO: cancelHandler? + // TODO: call TaskInterface::stop() ? + invokeEndHandler(false); + m_task.reset(); +} + +void TaskNode::invokeEndHandler(bool success) +{ + if (success && m_taskHandler.m_doneHandler) + invokeHandler(parentContainer(), m_taskHandler.m_doneHandler, *m_task.get()); + else if (!success && m_taskHandler.m_errorHandler) + invokeHandler(parentContainer(), m_taskHandler.m_errorHandler, *m_task.get()); + m_container.m_constData.m_taskTreePrivate->advanceProgress(1); +} + +/*! + \namespace Tasking + \inmodule QtCreator + \brief The Tasking namespace contains a general purpose TaskTree solution. + + The Tasking namespace depends on Qt only, and doesn't depend on any \QC + specific code. +*/ + +/*! + \class Tasking::TaskTree + \inheaderfile solutions/tasking/tasktree.h + \inmodule QtCreator + \ingroup mainclasses + \brief The TaskTree class runs an async task tree structure defined in a + declarative way. + + Use the Tasking namespace to build extensible, declarative task tree + structures that contain possibly asynchronous tasks, such as Process, + FileTransfer, or Async. TaskTree structures enable you + to create a sophisticated mixture of a parallel or sequential flow of tasks + in the form of a tree and to run it any time later. + + \section1 Root Element and Tasks + + The TaskTree has a mandatory Group root element, which may contain + any number of tasks of various types, such as ProcessTask, FileTransferTask, + or AsyncTask: + + \code + using namespace Tasking; + + const Group root { + ProcessTask(...), + AsyncTask(...), + FileTransferTask(...) + }; + + TaskTree *taskTree = new TaskTree(root); + connect(taskTree, &TaskTree::done, ...); // a successfully finished handler + connect(taskTree, &TaskTree::errorOccurred, ...); // an erroneously finished handler + taskTree->start(); + \endcode + + The task tree above has a top level element of the Group type that contains + tasks of the type ProcessTask, FileTransferTask, and AsyncTask. + After taskTree->start() is called, the tasks are run in a chain, starting + with ProcessTask. When the ProcessTask finishes successfully, the AsyncTask task is + started. Finally, when the asynchronous task finishes successfully, the + FileTransferTask task is started. + + When the last running task finishes with success, the task tree is considered + to have run successfully and the TaskTree::done() signal is emitted. + When a task finishes with an error, the execution of the task tree is stopped + and the remaining tasks are skipped. The task tree finishes with an error and + sends the TaskTree::errorOccurred() signal. + + \section1 Groups + + The parent of the Group sees it as a single task. Like other tasks, + the group can be started and it can finish with success or an error. + The Group elements can be nested to create a tree structure: + + \code + const Group root { + Group { + parallel, + ProcessTask(...), + AsyncTask(...) + }, + FileTransferTask(...) + }; + \endcode + + The example above differs from the first example in that the root element has + a subgroup that contains the ProcessTask and AsyncTask. The subgroup is a + sibling element of the FileTransferTask in the root. The subgroup contains an + additional \e parallel element that instructs its Group to execute its tasks + in parallel. + + So, when the tree above is started, the ProcessTask and AsyncTask start + immediately and run in parallel. Since the root group doesn't contain a + \e parallel element, its direct child tasks are run in sequence. Thus, the + FileTransferTask starts when the whole subgroup finishes. The group is + considered as finished when all its tasks have finished. The order in which + the tasks finish is not relevant. + + So, depending on which task lasts longer (ProcessTask or AsyncTask), the + following scenarios can take place: + + \table + \header + \li Scenario 1 + \li Scenario 2 + \row + \li Root Group starts + \li Root Group starts + \row + \li Sub Group starts + \li Sub Group starts + \row + \li ProcessTask starts + \li ProcessTask starts + \row + \li AsyncTask starts + \li AsyncTask starts + \row + \li ... + \li ... + \row + \li \b {ProcessTask finishes} + \li \b {AsyncTask finishes} + \row + \li ... + \li ... + \row + \li \b {AsyncTask finishes} + \li \b {ProcessTask finishes} + \row + \li Sub Group finishes + \li Sub Group finishes + \row + \li FileTransferTask starts + \li FileTransferTask starts + \row + \li ... + \li ... + \row + \li FileTransferTask finishes + \li FileTransferTask finishes + \row + \li Root Group finishes + \li Root Group finishes + \endtable + + The differences between the scenarios are marked with bold. Three dots mean + that an unspecified amount of time passes between previous and next events + (a task or tasks continue to run). No dots between events + means that they occur synchronously. + + The presented scenarios assume that all tasks run successfully. If a task + fails during execution, the task tree finishes with an error. In particular, + when ProcessTask finishes with an error while AsyncTask is still being executed, + the AsyncTask is automatically stopped, the subgroup finishes with an error, + the FileTransferTask is skipped, and the tree finishes with an error. + + \section1 Task Types + + Each task type is associated with its corresponding task class that executes + the task. For example, a ProcessTask inside a task tree is associated with + the Process class that executes the process. The associated objects are + automatically created, started, and destructed exclusively by the task tree + at the appropriate time. + + If a root group consists of five sequential ProcessTask tasks, and the task tree + executes the group, it creates an instance of Process for the first + ProcessTask and starts it. If the Process instance finishes successfully, + the task tree destructs it and creates a new Process instance for the + second ProcessTask, and so on. If the first task finishes with an error, the task + tree stops creating Process instances, and the root group finishes with an + error. + + The following table shows examples of task types and their corresponding task + classes: + + \table + \header + \li Task Type (Tasking Namespace) + \li Associated Task Class + \li Brief Description + \row + \li ProcessTask + \li Utils::Process + \li Starts processes. + \row + \li AsyncTask + \li Utils::Async + \li Starts asynchronous tasks; run in separate thread. + \row + \li TaskTreeTask + \li Utils::TaskTree + \li Starts a nested task tree. + \row + \li FileTransferTask + \li ProjectExplorer::FileTransfer + \li Starts file transfer between different devices. + \endtable + + \section1 Task Handlers + + Use Task handlers to set up a task for execution and to enable reading + the output data from the task when it finishes with success or an error. + + \section2 Task's Start Handler + + When a corresponding task class object is created and before it's started, + the task tree invokes a mandatory user-provided setup handler. The setup + handler should always take a \e reference to the associated task class object: + + \code + const auto onSetup = [](Process &process) { + process.setCommand({"sleep", {"3"}}); + }; + const Group root { + ProcessTask(onSetup) + }; + \endcode + + You can modify the passed Process in the setup handler, so that the task + tree can start the process according to your configuration. + You should not call \e {process.start();} in the setup handler, + as the task tree calls it when needed. The setup handler is optional. When used, + it must be the first argument of the task's constructor. + + Optionally, the setup handler may return a TaskAction. The returned + TaskAction influences the further start behavior of a given task. The + possible values are: + + \table + \header + \li TaskAction Value + \li Brief Description + \row + \li Continue + \li The task will be started normally. This is the default behavior when the + setup handler doesn't return TaskAction (that is, its return type is + void). + \row + \li StopWithDone + \li The task won't be started and it will report success to its parent. + \row + \li StopWithError + \li The task won't be started and it will report an error to its parent. + \endtable + + This is useful for running a task only when a condition is met and the data + needed to evaluate this condition is not known until previously started tasks + finish. In this way, the setup handler dynamically decides whether to start the + corresponding task normally or skip it and report success or an error. + For more information about inter-task data exchange, see \l Storage. + + \section2 Task's Done and Error Handlers + + When a running task finishes, the task tree invokes an optionally provided + done or error handler. Both handlers should always take a \e {const reference} + to the associated task class object: + + \code + const auto onSetup = [](Process &process) { + process.setCommand({"sleep", {"3"}}); + }; + const auto onDone = [](const Process &process) { + qDebug() << "Success" << process.cleanedStdOut(); + }; + const auto onError = [](const Process &process) { + qDebug() << "Failure" << process.cleanedStdErr(); + }; + const Group root { + ProcessTask(onSetup, onDone, onError) + }; + \endcode + + The done and error handlers may collect output data from Process, and store it + for further processing or perform additional actions. The done handler is optional. + When used, it must be the second argument of the task's constructor. + The error handler is also optional. When used, it must always be the third argument. + You can omit the handlers or substitute the ones that you do not need with curly braces ({}). + + \note If the task setup handler returns StopWithDone or StopWithError, + neither the done nor error handler is invoked. + + \section1 Group Handlers + + Similarly to task handlers, group handlers enable you to set up a group to + execute and to apply more actions when the whole group finishes with + success or an error. + + \section2 Group's Start Handler + + The task tree invokes the group start handler before it starts the child + tasks. The group handler doesn't take any arguments: + + \code + const auto onSetup = [] { + qDebug() << "Entering the group"; + }; + const Group root { + onGroupSetup(onSetup), + ProcessTask(...) + }; + \endcode + + The group setup handler is optional. To define a group setup handler, add an + onGroupSetup element to a group. The argument of onGroupSetup is a user + handler. If you add more than one onGroupSetup element to a group, an assert + is triggered at runtime that includes an error message. + + Like the task's start handler, the group start handler may return TaskAction. + The returned TaskAction value affects the start behavior of the + whole group. If you do not specify a group start handler or its return type + is void, the default group's action is TaskAction::Continue, so that all + tasks are started normally. Otherwise, when the start handler returns + TaskAction::StopWithDone or TaskAction::StopWithError, the tasks are not + started (they are skipped) and the group itself reports success or failure, + depending on the returned value, respectively. + + \code + const Group root { + onGroupSetup([] { qDebug() << "Root setup"; }), + Group { + onGroupSetup([] { qDebug() << "Group 1 setup"; return TaskAction::Continue; }), + ProcessTask(...) // Process 1 + }, + Group { + onGroupSetup([] { qDebug() << "Group 2 setup"; return TaskAction::StopWithDone; }), + ProcessTask(...) // Process 2 + }, + Group { + onGroupSetup([] { qDebug() << "Group 3 setup"; return TaskAction::StopWithError; }), + ProcessTask(...) // Process 3 + }, + ProcessTask(...) // Process 4 + }; + \endcode + + In the above example, all subgroups of a root group define their setup handlers. + The following scenario assumes that all started processes finish with success: + + \table + \header + \li Scenario + \li Comment + \row + \li Root Group starts + \li Doesn't return TaskAction, so its tasks are executed. + \row + \li Group 1 starts + \li Returns Continue, so its tasks are executed. + \row + \li Process 1 starts + \li + \row + \li ... + \li ... + \row + \li Process 1 finishes (success) + \li + \row + \li Group 1 finishes (success) + \li + \row + \li Group 2 starts + \li Returns StopWithDone, so Process 2 is skipped and Group 2 reports + success. + \row + \li Group 2 finishes (success) + \li + \row + \li Group 3 starts + \li Returns StopWithError, so Process 3 is skipped and Group 3 reports + an error. + \row + \li Group 3 finishes (error) + \li + \row + \li Root Group finishes (error) + \li Group 3, which is a direct child of the root group, finished with an + error, so the root group stops executing, skips Process 4, which has + not started yet, and reports an error. + \endtable + + \section2 Groups's Done and Error Handlers + + A Group's done or error handler is executed after the successful or failed + execution of its tasks, respectively. The final value reported by the + group depends on its \l {Workflow Policy}. The handlers can apply other + necessary actions. The done and error handlers are defined inside the + onGroupDone and onGroupError elements of a group, respectively. They do not + take arguments: + + \code + const Group root { + onGroupSetup([] { qDebug() << "Root setup"; }), + ProcessTask(...), + onGroupDone([] { qDebug() << "Root finished with success"; }), + onGroupError([] { qDebug() << "Root finished with error"; }) + }; + \endcode + + The group done and error handlers are optional. If you add more than one + onGroupDone or onGroupError each to a group, an assert is triggered at + runtime that includes an error message. + + \note Even if the group setup handler returns StopWithDone or StopWithError, + one of the group's done or error handlers is invoked. This behavior differs + from that of task handlers and might change in the future. + + \section1 Other Group Elements + + A group can contain other elements that describe the processing flow, such as + the execution mode or workflow policy. It can also contain storage elements + that are responsible for collecting and sharing custom common data gathered + during group execution. + + \section2 Execution Mode + + The execution mode element in a Group specifies how the direct child tasks of + the Group are started. The most common execution modes are \l sequential and + \l parallel. It's also possible to specify the limit of tasks running + in parallel by using the parallelLimit function. + + In all execution modes, a group starts tasks in the oder in which they appear. + + If a child of a group is also a group, the child group runs its tasks + according to its own execution mode. + + \section2 Workflow Policy + + The workflow policy element in a Group specifies how the group should behave + when any of its \e direct child's tasks finish. For a detailed description of possible + policies, refer to WorkflowPolicy. + + If a child of a group is also a group, the child group runs its tasks + according to its own workflow policy. + + \section2 Storage + + Use the Storage element to exchange information between tasks. Especially, + in the sequential execution mode, when a task needs data from another, + already finished task, before it can start. For example, a task tree that copies data by reading + it from a source and writing it to a destination might look as follows: + + \code + static QByteArray load(const QString &fileName) { ... } + static void save(const QString &fileName, const QByteArray &array) { ... } + + static TaskItem copyRecipe(const QString &source, const QString &destination) + { + struct CopyStorage { // [1] custom inter-task struct + QByteArray content; // [2] custom inter-task data + }; + + // [3] instance of custom inter-task struct manageable by task tree + const TreeStorage storage; + + const auto onLoaderSetup = [source](Async &async) { + async.setConcurrentCallData(&load, source); + }; + // [4] runtime: task tree activates the instance from [7] before invoking handler + const auto onLoaderDone = [storage](const Async &async) { + storage->content = async.result(); // [5] loader stores the result in storage + }; + + // [4] runtime: task tree activates the instance from [7] before invoking handler + const auto onSaverSetup = [storage, destination](Async &async) { + const QByteArray content = storage->content; // [6] saver takes data from storage + async.setConcurrentCallData(&save, destination, content); + }; + const auto onSaverDone = [](const Async &async) { + qDebug() << "Save done successfully"; + }; + + const Group root { + // [7] runtime: task tree creates an instance of CopyStorage when root is entered + Storage(storage), + AsyncTask(onLoaderSetup, onLoaderDone), + AsyncTask(onSaverSetup, onSaverDone) + }; + return root; + } + + const QString source = ...; + const QString destination = ...; + TaskTree taskTree(copyRecipe(source, destination)); + connect(&taskTree, &TaskTree::done, + &taskTree, [] { qDebug() << "The copying finished successfully."; }); + tasktree.start(); + \endcode + + In the example above, the inter-task data consists of a QByteArray content + variable [2] enclosed in a CopyStorage custom struct [1]. If the loader + finishes successfully, it stores the data in a CopyStorage::content + variable [5]. The saver then uses the variable to configure the saving task [6]. + + To enable a task tree to manage the CopyStorage struct, an instance of + TreeStorage is created [3]. If a copy of this object is + inserted as group's child task [7], an instance of CopyStorage struct is + created dynamically when the task tree enters this group. When the task + tree leaves this group, the existing instance of CopyStorage struct is + destructed as it's no longer needed. + + If several task trees that hold a copy of the common TreeStorage + instance run simultaneously, each task tree contains its own copy of the + CopyStorage struct. + + You can access CopyStorage from any handler in the group with a storage object. + This includes all handlers of all descendant tasks of the group with + a storage object. To access the custom struct in a handler, pass the + copy of the TreeStorage object to the handler (for example, in + a lambda capture) [4]. + + When the task tree invokes a handler in a subtree containing the storage [7], + the task tree activates its own CopyStorage instance inside the + TreeStorage object. Therefore, the CopyStorage struct may be + accessed only from within the handler body. To access the currently active + CopyStorage from within TreeStorage, use the TreeStorage::operator->(), + TreeStorage::operator*() or TreeStorage::activeStorage() method. + + The following list summarizes how to employ a Storage object into the task + tree: + \list 1 + \li Define the custom structure MyStorage with custom data [1], [2] + \li Create an instance of TreeStorage storage [3] + \li Pass the TreeStorage instance to handlers [4] + \li Access the MyStorage instance in handlers [5], [6] + \li Insert the TreeStorage instance into a group [7] + \endlist + + \note The current implementation assumes that all running task trees + containing copies of the same TreeStorage run in the same thread. Otherwise, + the behavior is undefined. + + \section1 TaskTree + + TaskTree executes the tree structure of asynchronous tasks according to the + recipe described by the Group root element. + + As TaskTree is also an asynchronous task, it can be a part of another TaskTree. + To place a nested TaskTree inside another TaskTree, insert the TaskTreeTask + element into other tree's Group element. + + TaskTree reports progress of completed tasks when running. The progress value + is increased when a task finishes or is skipped or stopped. + When TaskTree is finished and the TaskTree::done() or TaskTree::errorOccurred() + signal is emitted, the current value of the progress equals the maximum + progress value. Maximum progress equals the total number of tasks in a tree. + A nested TaskTree is counted as a single task, and its child tasks are not + counted in the top level tree. Groups themselves are not counted as tasks, + but their tasks are counted. + + To set additional initial data for the running tree, modify the storage + instances in a tree when it creates them by installing a storage setup + handler: + + \code + TreeStorage storage; + Group root = ...; // storage placed inside root's group and inside handlers + TaskTree taskTree(root); + auto initStorage = [](CopyStorage *storage){ + storage->content = "initial content"; + }; + taskTree.onStorageSetup(storage, initStorage); + taskTree.start(); + \endcode + + When the running task tree creates a CopyStorage instance, and before any + handler inside a tree is called, the task tree calls the initStorage handler, + to enable setting up initial data of the storage, unique to this particular + run of taskTree. + + Similarly, to collect some additional result data from the running tree, + read it from storage instances in the tree when they are about to be + destroyed. To do this, install a storage done handler: + + \code + TreeStorage storage; + Group root = ...; // storage placed inside root's group and inside handlers + TaskTree taskTree(root); + auto collectStorage = [](CopyStorage *storage){ + qDebug() << "final content" << storage->content; + }; + taskTree.onStorageDone(storage, collectStorage); + taskTree.start(); + \endcode + + When the running task tree is about to destroy a CopyStorage instance, the + task tree calls the collectStorage handler, to enable reading the final data + from the storage, unique to this particular run of taskTree. + + \section1 Task Adapters + + To extend a TaskTree with new a task type, implement a simple adapter class + derived from the TaskAdapter class template. The following class is an + adapter for a single shot timer, which may be considered as a new + asynchronous task: + + \code + class TimeoutTaskAdapter : public Tasking::TaskAdapter + { + public: + TimeoutTaskAdapter() { + task()->setSingleShot(true); + task()->setInterval(1000); + connect(task(), &QTimer::timeout, this, [this] { emit done(true); }); + } + void start() final { task()->start(); } + }; + + QTC_DECLARE_CUSTOM_TASK(TimeoutTask, TimeoutTaskAdapter); + \endcode + + You must derive the custom adapter from the TaskAdapter class template + instantiated with a template parameter of the class implementing a running + task. The code above uses QTimer to run the task. This class appears + later as an argument to the task's handlers. The instance of this class + parameter automatically becomes a member of the TaskAdapter template, and is + accessible through the TaskAdapter::task() method. The constructor + of TimeoutTaskAdapter initially configures the QTimer object and connects + to the QTimer::timeout signal. When the signal is triggered, TimeoutTaskAdapter + emits the done(true) signal to inform the task tree that the task finished + successfully. If it emits done(false), the task finished with an error. + The TaskAdapter::start() method starts the timer. + + To make QTimer accessible inside TaskTree under the \e TimeoutTask name, + register it with QTC_DECLARE_CUSTOM_TASK(TimeoutTask, TimeoutTaskAdapter). + TimeoutTask becomes a new task type inside Tasking namespace, using TimeoutTaskAdapter. + + The new task type is now registered, and you can use it in TaskTree: + + \code + const auto onTimeoutSetup = [](QTimer &task) { + task.setInterval(2000); + }; + const auto onTimeoutDone = [](const QTimer &task) { + qDebug() << "timeout triggered"; + }; + + const Group root { + TimeoutTask(onTimeoutSetup, onTimeoutDone) + }; + \endcode + + When a task tree containing the root from the above example is started, it + prints a debug message within two seconds and then finishes successfully. + + \note The class implementing the running task should have a default constructor, + and objects of this class should be freely destructible. It should be allowed + to destroy a running object, preferably without waiting for the running task + to finish (that is, safe non-blocking destructor of a running task). +*/ + +TaskTree::TaskTree() + : d(new TaskTreePrivate(this)) +{ +} + +TaskTree::TaskTree(const Group &root) : TaskTree() +{ + setupRoot(root); +} + +TaskTree::~TaskTree() +{ + QTC_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting TaskTree instance directly from " + "one of its handlers will lead to crash!")); + // TODO: delete storages explicitly here? + delete d; +} + +void TaskTree::setupRoot(const Group &root) +{ + QTC_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return); + QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The setupRoot() is called from one of the" + "TaskTree handlers, ingoring..."); return); + d->m_storages.clear(); + d->m_root.reset(new TaskNode(d, root, nullptr)); +} + +void TaskTree::start() +{ + QTC_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return); + QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The start() is called from one of the" + "TaskTree handlers, ingoring..."); return); + d->start(); +} + +void TaskTree::stop() +{ + QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The stop() is called from one of the" + "TaskTree handlers, ingoring..."); return); + d->stop(); +} + +bool TaskTree::isRunning() const +{ + return d->m_root && d->m_root->isRunning(); +} + +bool TaskTree::runBlocking() +{ + QPromise dummy; + dummy.start(); + return runBlocking(dummy.future()); +} + +bool TaskTree::runBlocking(const QFuture &future) +{ + if (future.isCanceled()) + return false; + + bool ok = false; + QEventLoop loop; + + const auto finalize = [&loop, &ok](bool success) { + ok = success; + // Otherwise, the tasks from inside the running tree that were deleteLater() + // will be leaked. Refer to the QObject::deleteLater() docs. + QMetaObject::invokeMethod(&loop, [&loop] { loop.quit(); }, Qt::QueuedConnection); + }; + + QFutureWatcher watcher; + connect(&watcher, &QFutureWatcherBase::canceled, this, &TaskTree::stop); + watcher.setFuture(future); + + connect(this, &TaskTree::done, &loop, [finalize] { finalize(true); }); + connect(this, &TaskTree::errorOccurred, &loop, [finalize] { finalize(false); }); + QTimer::singleShot(0, this, &TaskTree::start); + + loop.exec(QEventLoop::ExcludeUserInputEvents); + if (!ok) { + auto nonConstFuture = future; + nonConstFuture.cancel(); + } + return ok; +} + +bool TaskTree::runBlocking(const Group &recipe, milliseconds timeout) +{ + QPromise dummy; + dummy.start(); + return TaskTree::runBlocking(recipe, dummy.future(), timeout); +} + +bool TaskTree::runBlocking(const Group &recipe, const QFuture &future, milliseconds timeout) +{ + const Group root = timeout == milliseconds::max() ? recipe + : Group { recipe.withTimeout(timeout) }; + TaskTree taskTree(root); + return taskTree.runBlocking(future); +} + +int TaskTree::taskCount() const +{ + return d->m_root ? d->m_root->taskCount() : 0; +} + +int TaskTree::progressValue() const +{ + return d->m_progressValue; +} + +void TaskTree::setupStorageHandler(const TreeStorageBase &storage, + StorageVoidHandler setupHandler, + StorageVoidHandler doneHandler) +{ + auto it = d->m_storageHandlers.find(storage); + if (it == d->m_storageHandlers.end()) { + d->m_storageHandlers.insert(storage, {setupHandler, doneHandler}); + return; + } + if (setupHandler) { + QTC_ASSERT(!it->m_setupHandler, + qWarning("The storage has its setup handler defined, overriding...")); + it->m_setupHandler = setupHandler; + } + if (doneHandler) { + QTC_ASSERT(!it->m_doneHandler, + qWarning("The storage has its done handler defined, overriding...")); + it->m_doneHandler = doneHandler; + } +} + +TaskTreeTaskAdapter::TaskTreeTaskAdapter() +{ + connect(task(), &TaskTree::done, this, [this] { emit done(true); }); + connect(task(), &TaskTree::errorOccurred, this, [this] { emit done(false); }); +} + +void TaskTreeTaskAdapter::start() +{ + task()->start(); +} + +using TimeoutCallback = std::function; + +struct TimerData +{ + system_clock::time_point m_deadline; + QPointer m_context; + TimeoutCallback m_callback; +}; + +QMutex s_mutex; +std::atomic_int s_timerId = 0; +QHash s_timerIdToTimerData = {}; +QMultiMap s_deadlineToTimerId = {}; + +static QList prepareForActivation(int timerId) +{ + QMutexLocker lock(&s_mutex); + const auto it = s_timerIdToTimerData.constFind(timerId); + if (it == s_timerIdToTimerData.cend()) + return {}; // the timer was already activated + + const system_clock::time_point deadline = it->m_deadline; + QList toActivate; + auto itMap = s_deadlineToTimerId.cbegin(); + while (itMap != s_deadlineToTimerId.cend()) { + if (itMap.key() > deadline) + break; + + const auto it = s_timerIdToTimerData.constFind(itMap.value()); + if (it != s_timerIdToTimerData.cend()) { + toActivate.append(it.value()); + s_timerIdToTimerData.erase(it); + } + itMap = s_deadlineToTimerId.erase(itMap); + } + return toActivate; +} + +static void removeTimerId(int timerId) +{ + QMutexLocker lock(&s_mutex); + const auto it = s_timerIdToTimerData.constFind(timerId); + QTC_ASSERT(it != s_timerIdToTimerData.cend(), + qWarning("Removing active timerId failed."); return); + + const system_clock::time_point deadline = it->m_deadline; + s_timerIdToTimerData.erase(it); + + const int removedCount = s_deadlineToTimerId.remove(deadline, timerId); + QTC_ASSERT(removedCount == 1, qWarning("Removing active timerId failed."); return); +} + +static void handleTimeout(int timerId) +{ + const QList toActivate = prepareForActivation(timerId); + for (const TimerData &timerData : toActivate) { + if (timerData.m_context) + QMetaObject::invokeMethod(timerData.m_context.get(), timerData.m_callback); + } +} + +static int scheduleTimeout(milliseconds timeout, QObject *context, const TimeoutCallback &callback) +{ + const int timerId = s_timerId.fetch_add(1) + 1; + const system_clock::time_point deadline = system_clock::now() + timeout; + QTimer::singleShot(timeout, context, [timerId] { handleTimeout(timerId); }); + QMutexLocker lock(&s_mutex); + s_timerIdToTimerData.emplace(timerId, TimerData{deadline, context, callback}); + s_deadlineToTimerId.insert(deadline, timerId); + return timerId; +} + +TimeoutTaskAdapter::TimeoutTaskAdapter() +{ + *task() = std::chrono::milliseconds::zero(); +} + +TimeoutTaskAdapter::~TimeoutTaskAdapter() +{ + if (m_timerId) + removeTimerId(*m_timerId); +} + +void TimeoutTaskAdapter::start() +{ + if (*task() == milliseconds::zero()) + QTimer::singleShot(0, this, [this] { emit done(true); }); + else + m_timerId = scheduleTimeout(*task(), this, [this] { m_timerId = {}; emit done(true); }); +} + +} // namespace Tasking diff --git a/src/libs/solutions/tasking/tasktree.h b/src/libs/solutions/tasking/tasktree.h new file mode 100644 index 00000000000..647c680b5b0 --- /dev/null +++ b/src/libs/solutions/tasking/tasktree.h @@ -0,0 +1,468 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "tasking_global.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE +template +class QFuture; +QT_END_NAMESPACE + +namespace Tasking { + +Q_NAMESPACE_EXPORT(TASKING_EXPORT) + +class ExecutionContextActivator; +class TaskContainer; +class TaskTreePrivate; + +class TASKING_EXPORT TaskInterface : public QObject +{ + Q_OBJECT + +public: + TaskInterface() = default; + virtual void start() = 0; + +signals: + void done(bool success); +}; + +class TASKING_EXPORT TreeStorageBase +{ +public: + bool isValid() const; + +protected: + using StorageConstructor = std::function; + using StorageDestructor = std::function; + + TreeStorageBase(StorageConstructor ctor, StorageDestructor dtor); + void *activeStorageVoid() const; + +private: + int createStorage() const; + void deleteStorage(int id) const; + void activateStorage(int id) const; + + friend bool operator==(const TreeStorageBase &first, const TreeStorageBase &second) + { return first.m_storageData == second.m_storageData; } + + friend bool operator!=(const TreeStorageBase &first, const TreeStorageBase &second) + { return first.m_storageData != second.m_storageData; } + + friend size_t qHash(const TreeStorageBase &storage, uint seed = 0) + { return size_t(storage.m_storageData.get()) ^ seed; } + + struct StorageData { + ~StorageData(); + StorageConstructor m_constructor = {}; + StorageDestructor m_destructor = {}; + QHash m_storageHash = {}; + int m_activeStorage = 0; // 0 means no active storage + int m_storageCounter = 0; + }; + QSharedPointer m_storageData; + friend ExecutionContextActivator; + friend TaskContainer; + friend TaskTreePrivate; +}; + +template +class TreeStorage : public TreeStorageBase +{ +public: + TreeStorage() : TreeStorageBase(TreeStorage::ctor(), TreeStorage::dtor()) {} + StorageStruct &operator*() const noexcept { return *activeStorage(); } + StorageStruct *operator->() const noexcept { return activeStorage(); } + StorageStruct *activeStorage() const { + return static_cast(activeStorageVoid()); + } + +private: + static StorageConstructor ctor() { return [] { return new StorageStruct; }; } + static StorageDestructor dtor() { + return [](void *storage) { delete static_cast(storage); }; + } +}; + +// WorkflowPolicy: +// 1. When all children finished with done -> report done, otherwise: +// a) Report error on first error and stop executing other children (including their subtree). +// b) On first error - continue executing all children and report error afterwards. +// 2. When all children finished with error -> report error, otherwise: +// a) Report done on first done and stop executing other children (including their subtree). +// b) On first done - continue executing all children and report done afterwards. +// 3. Stops on first finished child. In sequential mode it will never run other children then the first one. +// Useful only in parallel mode. +// 4. Always run all children, let them finish, ignore their results and report done afterwards. +// 5. Always run all children, let them finish, ignore their results and report error afterwards. + +enum class WorkflowPolicy { + StopOnError, // 1a - Reports error on first child error, otherwise done (if all children were done). + ContinueOnError, // 1b - The same, but children execution continues. Reports done when no children. + StopOnDone, // 2a - Reports done on first child done, otherwise error (if all children were error). + ContinueOnDone, // 2b - The same, but children execution continues. Reports error when no children. + StopOnFinished, // 3 - Stops on first finished child and report its result. + FinishAllAndDone, // 4 - Reports done after all children finished. + FinishAllAndError // 5 - Reports error after all children finished. +}; +Q_ENUM_NS(WorkflowPolicy); + +enum class TaskAction +{ + Continue, + StopWithDone, + StopWithError +}; +Q_ENUM_NS(TaskAction); + +class TASKING_EXPORT TaskItem +{ +public: + // Internal, provided by QTC_DECLARE_CUSTOM_TASK + using TaskCreateHandler = std::function; + // Called prior to task start, just after createHandler + using TaskSetupHandler = std::function; + // Called on task done / error + using TaskEndHandler = std::function; + // Called when group entered + using GroupSetupHandler = std::function; + // Called when group done / error + using GroupEndHandler = std::function; + + struct TaskHandler { + TaskCreateHandler m_createHandler; + TaskSetupHandler m_setupHandler = {}; + TaskEndHandler m_doneHandler = {}; + TaskEndHandler m_errorHandler = {}; + }; + + struct GroupHandler { + GroupSetupHandler m_setupHandler; + GroupEndHandler m_doneHandler = {}; + GroupEndHandler m_errorHandler = {}; + }; + + struct GroupData { + GroupHandler m_groupHandler = {}; + std::optional m_parallelLimit = {}; + std::optional m_workflowPolicy = {}; + }; + + QList children() const { return m_children; } + GroupData groupData() const { return m_groupData; } + QList storageList() const { return m_storageList; } + TaskHandler taskHandler() const { return m_taskHandler; } + +protected: + enum class Type { + Group, + GroupData, + Storage, + TaskHandler + }; + + TaskItem() = default; + TaskItem(const GroupData &data) + : m_type(Type::GroupData) + , m_groupData(data) {} + TaskItem(const TreeStorageBase &storage) + : m_type(Type::Storage) + , m_storageList{storage} {} + TaskItem(const TaskHandler &handler) + : m_type(Type::TaskHandler) + , m_taskHandler(handler) {} + void addChildren(const QList &children); + + void setTaskSetupHandler(const TaskSetupHandler &handler); + void setTaskDoneHandler(const TaskEndHandler &handler); + void setTaskErrorHandler(const TaskEndHandler &handler); + static TaskItem groupHandler(const GroupHandler &handler) { return TaskItem({handler}); } + static TaskItem parallelLimit(int limit) { return TaskItem({{}, limit}); } + static TaskItem workflowPolicy(WorkflowPolicy policy) { return TaskItem({{}, {}, policy}); } + static TaskItem withTimeout(const TaskItem &item, std::chrono::milliseconds timeout, + const GroupEndHandler &handler = {}); + +private: + Type m_type = Type::Group; + QList m_children; + GroupData m_groupData; + QList m_storageList; + TaskHandler m_taskHandler; +}; + +class TASKING_EXPORT Group : public TaskItem +{ +public: + Group(const QList &children) { addChildren(children); } + Group(std::initializer_list children) { addChildren(children); } + + // GroupData related: + template + static TaskItem onGroupSetup(SetupHandler &&handler) { + return groupHandler({wrapGroupSetup(std::forward(handler))}); + } + static TaskItem onGroupDone(const GroupEndHandler &handler) { + return groupHandler({{}, handler}); + } + static TaskItem onGroupError(const GroupEndHandler &handler) { + return groupHandler({{}, {}, handler}); + } + using TaskItem::parallelLimit; // Default: 1 (sequential). 0 means unlimited (parallel). + using TaskItem::workflowPolicy; // Default: WorkflowPolicy::StopOnError. + + TaskItem withTimeout(std::chrono::milliseconds timeout, + const GroupEndHandler &handler = {}) const { + return TaskItem::withTimeout(*this, timeout, handler); + } + +private: + template + static GroupSetupHandler wrapGroupSetup(SetupHandler &&handler) + { + static constexpr bool isDynamic + = std::is_same_v>>; + constexpr bool isVoid + = std::is_same_v>>; + static_assert(isDynamic || isVoid, + "Group setup handler needs to take no arguments and has to return " + "void or TaskAction. The passed handler doesn't fulfill these requirements."); + return [=] { + if constexpr (isDynamic) + return std::invoke(handler); + std::invoke(handler); + return TaskAction::Continue; + }; + }; +}; + +template +static TaskItem onGroupSetup(SetupHandler &&handler) +{ + return Group::onGroupSetup(std::forward(handler)); +} + +TASKING_EXPORT TaskItem onGroupDone(const TaskItem::GroupEndHandler &handler); +TASKING_EXPORT TaskItem onGroupError(const TaskItem::GroupEndHandler &handler); +TASKING_EXPORT TaskItem parallelLimit(int limit); +TASKING_EXPORT TaskItem workflowPolicy(WorkflowPolicy policy); + +TASKING_EXPORT extern const TaskItem sequential; +TASKING_EXPORT extern const TaskItem parallel; + +TASKING_EXPORT extern const TaskItem stopOnError; +TASKING_EXPORT extern const TaskItem continueOnError; +TASKING_EXPORT extern const TaskItem stopOnDone; +TASKING_EXPORT extern const TaskItem continueOnDone; +TASKING_EXPORT extern const TaskItem stopOnFinished; +TASKING_EXPORT extern const TaskItem finishAllAndDone; +TASKING_EXPORT extern const TaskItem finishAllAndError; + +class TASKING_EXPORT Storage : public TaskItem +{ +public: + Storage(const TreeStorageBase &storage) : TaskItem(storage) { } +}; + +// Synchronous invocation. Similarly to Group - isn't counted as a task inside taskCount() +class TASKING_EXPORT Sync : public Group +{ + +public: + template + Sync(Function &&function) : Group(init(std::forward(function))) {} + +private: + template + static QList init(Function &&function) { + constexpr bool isInvocable = std::is_invocable_v>; + static_assert(isInvocable, + "Sync element: The synchronous function can't take any arguments."); + constexpr bool isBool = std::is_same_v>>; + constexpr bool isVoid = std::is_same_v>>; + static_assert(isBool || isVoid, + "Sync element: The synchronous function has to return void or bool."); + if constexpr (isBool) { + return {onGroupSetup([function] { return function() ? TaskAction::StopWithDone + : TaskAction::StopWithError; })}; + } + return {onGroupSetup([function] { function(); return TaskAction::StopWithDone; })}; + }; +}; + +template +class TaskAdapter : public TaskInterface +{ +public: + using Type = Task; + TaskAdapter() = default; + Task *task() { return &m_task; } + const Task *task() const { return &m_task; } +private: + Task m_task; +}; + +template +class CustomTask : public TaskItem +{ +public: + using Task = typename Adapter::Type; + using EndHandler = std::function; + static Adapter *createAdapter() { return new Adapter; } + CustomTask() : TaskItem({&createAdapter}) {} + template + CustomTask(SetupFunction &&function, const EndHandler &done = {}, const EndHandler &error = {}) + : TaskItem({&createAdapter, wrapSetup(std::forward(function)), + wrapEnd(done), wrapEnd(error)}) {} + + template + CustomTask &onSetup(SetupFunction &&function) { + setTaskSetupHandler(wrapSetup(std::forward(function))); + return *this; + } + CustomTask &onDone(const EndHandler &handler) { + setTaskDoneHandler(wrapEnd(handler)); + return *this; + } + CustomTask &onError(const EndHandler &handler) { + setTaskErrorHandler(wrapEnd(handler)); + return *this; + } + + TaskItem withTimeout(std::chrono::milliseconds timeout, + const GroupEndHandler &handler = {}) const { + return TaskItem::withTimeout(*this, timeout, handler); + } + +private: + template + static TaskItem::TaskSetupHandler wrapSetup(SetupFunction &&function) { + static constexpr bool isDynamic = std::is_same_v, typename Adapter::Type &>>; + constexpr bool isVoid = std::is_same_v, typename Adapter::Type &>>; + static_assert(isDynamic || isVoid, + "Task setup handler needs to take (Task &) as an argument and has to return " + "void or TaskAction. The passed handler doesn't fulfill these requirements."); + return [=](TaskInterface &taskInterface) { + Adapter &adapter = static_cast(taskInterface); + if constexpr (isDynamic) + return std::invoke(function, *adapter.task()); + std::invoke(function, *adapter.task()); + return TaskAction::Continue; + }; + }; + + static TaskEndHandler wrapEnd(const EndHandler &handler) { + if (!handler) + return {}; + return [handler](const TaskInterface &taskInterface) { + const Adapter &adapter = static_cast(taskInterface); + handler(*adapter.task()); + }; + }; +}; + +class TaskTreePrivate; + +class TASKING_EXPORT TaskTree final : public QObject +{ + Q_OBJECT + +public: + TaskTree(); + TaskTree(const Group &root); + ~TaskTree(); + + void setupRoot(const Group &root); + + void start(); + void stop(); + bool isRunning() const; + + // Helper methods. They execute a local event loop with ExcludeUserInputEvents. + // The passed future is used for listening to the cancel event. + // Don't use it in main thread. To be used in non-main threads or in auto tests. + bool runBlocking(); + bool runBlocking(const QFuture &future); + static bool runBlocking(const Group &recipe, + std::chrono::milliseconds timeout = std::chrono::milliseconds::max()); + static bool runBlocking(const Group &recipe, const QFuture &future, + std::chrono::milliseconds timeout = std::chrono::milliseconds::max()); + + int taskCount() const; + int progressMaximum() const { return taskCount(); } + int progressValue() const; // all finished / skipped / stopped tasks, groups itself excluded + + template + void onStorageSetup(const TreeStorage &storage, StorageHandler &&handler) { + setupStorageHandler(storage, + wrapHandler(std::forward(handler)), {}); + } + template + void onStorageDone(const TreeStorage &storage, StorageHandler &&handler) { + setupStorageHandler(storage, + {}, wrapHandler(std::forward(handler))); + } + +signals: + void started(); + void done(); + void errorOccurred(); + void progressValueChanged(int value); // updated whenever task finished / skipped / stopped + +private: + using StorageVoidHandler = std::function; + void setupStorageHandler(const TreeStorageBase &storage, + StorageVoidHandler setupHandler, + StorageVoidHandler doneHandler); + template + StorageVoidHandler wrapHandler(StorageHandler &&handler) { + return [=](void *voidStruct) { + StorageStruct *storageStruct = static_cast(voidStruct); + std::invoke(handler, storageStruct); + }; + } + + friend class TaskTreePrivate; + TaskTreePrivate *d; +}; + +class TASKING_EXPORT TaskTreeTaskAdapter : public TaskAdapter +{ +public: + TaskTreeTaskAdapter(); + void start() final; +}; + +class TASKING_EXPORT TimeoutTaskAdapter : public TaskAdapter +{ +public: + TimeoutTaskAdapter(); + ~TimeoutTaskAdapter(); + void start() final; + +private: + std::optional m_timerId; +}; + +} // namespace Tasking + +#define TASKING_DECLARE_TASK(CustomTaskName, TaskAdapterClass)\ +namespace Tasking { using CustomTaskName = CustomTask; } + +#define TASKING_DECLARE_TEMPLATE_TASK(CustomTaskName, TaskAdapterClass)\ +namespace Tasking {\ +template \ +using CustomTaskName = CustomTask>;\ +} // namespace Tasking + +TASKING_DECLARE_TASK(TaskTreeTask, TaskTreeTaskAdapter); +TASKING_DECLARE_TASK(TimeoutTask, TimeoutTaskAdapter); diff --git a/src/libs/sqlite/CMakeLists.txt b/src/libs/sqlite/CMakeLists.txt index 7f33b189215..4c7cd774c60 100644 --- a/src/libs/sqlite/CMakeLists.txt +++ b/src/libs/sqlite/CMakeLists.txt @@ -1,5 +1,5 @@ add_qtc_library(SqliteC OBJECT - PROPERTIES AUTOMOC OFF AUTOUIC OFF QT_COMPILE_OPTIONS_DISABLE_WARNINGS ON POSITION_INDEPENDENT_CODE ON + PROPERTIES AUTOMOC OFF AUTOUIC OFF QT_COMPILE_OPTIONS_DISABLE_WARNINGS ON DEFINES SQLITE_CORE SQLITE_CUSTOM_INCLUDE=config.h $<$:SQLITE_DEBUG> PROPERTIES COMPILE_OPTIONS $,/FIconfig.h,-includeconfig.h> PUBLIC_INCLUDES diff --git a/src/libs/sqlite/sqliteexception.h b/src/libs/sqlite/sqliteexception.h index d030f652809..f0cadfc7483 100644 --- a/src/libs/sqlite/sqliteexception.h +++ b/src/libs/sqlite/sqliteexception.h @@ -3,6 +3,7 @@ #pragma once +#include "sqlite3_fwd.h" #include "sqliteglobal.h" #include @@ -10,10 +11,6 @@ #include #include -extern "C" { -struct sqlite3; -} - namespace Sqlite { class SQLITE_EXPORT Exception : public std::exception diff --git a/src/libs/tracing/qml/ImageToolButton.qml b/src/libs/tracing/qml/ImageToolButton.qml index 2d0231494bc..45ca12b8997 100644 --- a/src/libs/tracing/qml/ImageToolButton.qml +++ b/src/libs/tracing/qml/ImageToolButton.qml @@ -22,10 +22,12 @@ ToolButton { smooth: false } - background: Rectangle { + background: PaddedRectangle { + padding: Theme.compactToolbar() ? 0 : 3 + radius: Theme.compactToolbar() ? 0 : 5 color: (parent.checked || parent.pressed) ? Theme.color(Theme.FancyToolButtonSelectedColor) - : parent.hovered + : (parent.hovered && parent.enabled) ? Theme.color(Theme.FancyToolButtonHoverColor) : "#00000000" } diff --git a/src/libs/tracing/qml/MainView.qml b/src/libs/tracing/qml/MainView.qml index 5aa4e835a37..052ec4aa762 100644 --- a/src/libs/tracing/qml/MainView.qml +++ b/src/libs/tracing/qml/MainView.qml @@ -156,7 +156,7 @@ Rectangle { anchors.top: parent.top anchors.left: parent.left width: 150 - height: 24 + height: Theme.toolBarHeight() onZoomControlChanged: zoomSliderToolBar.visible = !zoomSliderToolBar.visible onJumpToNext: { var next = timelineModelAggregator.nextItem(root.selectedModel, root.selectedItem, diff --git a/src/libs/tracing/qml/RangeDetails.qml b/src/libs/tracing/qml/RangeDetails.qml index 8c0e64f25c2..9ce3e996d94 100644 --- a/src/libs/tracing/qml/RangeDetails.qml +++ b/src/libs/tracing/qml/RangeDetails.qml @@ -9,7 +9,7 @@ import QtCreator.Tracing Item { id: rangeDetails - property real titleBarHeight: 20 + property real titleBarHeight: Theme.toolBarHeight() / 1.2 property real borderWidth: 1 property real outerMargin: 10 property real innerMargin: 5 diff --git a/src/libs/tracing/qml/TimeDisplay.qml b/src/libs/tracing/qml/TimeDisplay.qml index ffcbea2e841..64cb0474cfd 100644 --- a/src/libs/tracing/qml/TimeDisplay.qml +++ b/src/libs/tracing/qml/TimeDisplay.qml @@ -11,7 +11,7 @@ Item { property double rangeDuration property int textMargin: 5 - property int labelsHeight: 24 + property int labelsHeight: Theme.toolBarHeight() property int fontSize: 8 property int initialBlockLength: 120 property double spacing: width / rangeDuration diff --git a/src/libs/tracing/timelinetheme.cpp b/src/libs/tracing/timelinetheme.cpp index df02cb63961..41c0c44b4ff 100644 --- a/src/libs/tracing/timelinetheme.cpp +++ b/src/libs/tracing/timelinetheme.cpp @@ -5,8 +5,9 @@ #include #include -#include +#include #include +#include #include #include @@ -92,4 +93,14 @@ void TimelineTheme::setupTheme(QQmlEngine *engine) engine->addImageProvider(QLatin1String("icons"), new TimelineImageIconProvider); } +bool TimelineTheme::compactToolbar() const +{ + return StyleHelper::toolbarStyle() == StyleHelper::ToolbarStyleCompact; +} + +int TimelineTheme::toolBarHeight() const +{ + return StyleHelper::navigationWidgetHeight(); +} + } // namespace Timeline diff --git a/src/libs/tracing/timelinetheme.h b/src/libs/tracing/timelinetheme.h index ebca62447dd..f15eccbdaf1 100644 --- a/src/libs/tracing/timelinetheme.h +++ b/src/libs/tracing/timelinetheme.h @@ -22,6 +22,8 @@ public: explicit TimelineTheme(QObject *parent = nullptr); static void setupTheme(QQmlEngine* engine); + Q_INVOKABLE bool compactToolbar() const; + Q_INVOKABLE int toolBarHeight() const; }; } // namespace Timeline diff --git a/src/libs/tracing/timelinetracemanager.cpp b/src/libs/tracing/timelinetracemanager.cpp index 96eb147519e..98f333da91b 100644 --- a/src/libs/tracing/timelinetracemanager.cpp +++ b/src/libs/tracing/timelinetracemanager.cpp @@ -6,12 +6,10 @@ #include "timelinetracemanager.h" #include "tracingtr.h" +#include #include -#include -#include #include -#include #include @@ -223,8 +221,11 @@ QFuture TimelineTraceManager::save(const QString &filename) connect(writer, &QObject::destroyed, this, &TimelineTraceManager::saveFinished); connect(writer, &TimelineTraceFile::error, this, &TimelineTraceManager::error); - return Utils::runAsync([filename, writer] (QFutureInterface &future) { - writer->setFuture(future); + QFutureInterface fi; + fi.reportStarted(); + writer->setFuture(fi); + + Utils::asyncRun([filename, writer, fi] { QFile file(filename); if (file.open(QIODevice::WriteOnly)) @@ -232,10 +233,13 @@ QFuture TimelineTraceManager::save(const QString &filename) else writer->fail(Tr::tr("Could not open %1 for writing.").arg(filename)); - if (future.isCanceled()) + if (fi.isCanceled()) file.remove(); writer->deleteLater(); + QFutureInterface fiCopy = fi; + fiCopy.reportFinished(); }); + return fi.future(); } QFuture TimelineTraceManager::load(const QString &filename) @@ -249,8 +253,10 @@ QFuture TimelineTraceManager::load(const QString &filename) connect(reader, &QObject::destroyed, this, &TimelineTraceManager::loadFinished); connect(reader, &TimelineTraceFile::error, this, &TimelineTraceManager::error); - QFuture future = Utils::runAsync([filename, reader] (QFutureInterface &future) { - reader->setFuture(future); + QFutureInterface fi; + fi.reportStarted(); + reader->setFuture(fi); + Utils::asyncRun([filename, reader, fi] { QFile file(filename); if (file.open(QIODevice::ReadOnly)) @@ -259,11 +265,13 @@ QFuture TimelineTraceManager::load(const QString &filename) reader->fail(Tr::tr("Could not open %1 for reading.").arg(filename)); reader->deleteLater(); + QFutureInterface fiCopy = fi; + fiCopy.reportFinished(); }); QFutureWatcher *watcher = new QFutureWatcher(reader); connect(watcher, &QFutureWatcherBase::canceled, this, &TimelineTraceManager::clearAll); - connect(watcher, &QFutureWatcherBase::finished, this, [this, reader]() { + connect(watcher, &QFutureWatcherBase::finished, this, [this, reader] { if (!reader->isCanceled()) { if (reader->traceStart() >= 0) decreaseTraceStart(reader->traceStart()); @@ -272,9 +280,8 @@ QFuture TimelineTraceManager::load(const QString &filename) finalize(); } }); - watcher->setFuture(future); - - return future; + watcher->setFuture(fi.future()); + return fi.future(); } qint64 TimelineTraceManager::traceStart() const @@ -366,10 +373,9 @@ void TimelineTraceManager::restrictByFilter(TraceEventFilter filter) QFutureInterface future; replayEvents(filter(std::bind(&TimelineTraceManagerPrivate::dispatch, d, - std::placeholders::_1, std::placeholders::_2)), - [this]() { + std::placeholders::_1, std::placeholders::_2)), [this] { initialize(); - }, [this]() { + }, [this] { if (d->notesModel) d->notesModel->restore(); finalize(); diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index 4362294341c..aa6a3e199e6 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -1,8 +1,8 @@ add_qtc_library(Utils - DEPENDS Qt::Qml Qt::Xml + DEPENDS Tasking Qt::Qml Qt::Xml PUBLIC_DEPENDS Qt::Concurrent Qt::Core Qt::Network Qt::Gui Qt::Widgets - Qt6Core5Compat + Qt::Core5Compat SOURCES ../3rdparty/span/span.hpp ../3rdparty/tl_expected/include/tl/expected.hpp @@ -12,7 +12,7 @@ add_qtc_library(Utils appmainwindow.cpp appmainwindow.h archive.cpp archive.h aspects.cpp aspects.h - asynctask.cpp asynctask.h + async.cpp async.h basetreeview.cpp basetreeview.h benchmarker.cpp benchmarker.h buildablehelperlibrary.cpp buildablehelperlibrary.h @@ -45,6 +45,7 @@ add_qtc_library(Utils execmenu.cpp execmenu.h executeondestruction.h expected.h + externalterminalprocessimpl.cpp externalterminalprocessimpl.h fadingindicator.cpp fadingindicator.h faketooltip.cpp faketooltip.h fancylineedit.cpp fancylineedit.h @@ -55,6 +56,8 @@ add_qtc_library(Utils filepath.cpp filepath.h filepathinfo.h filesearch.cpp filesearch.h + filestreamer.cpp filestreamer.h + filestreamermanager.cpp filestreamermanager.h filesystemmodel.cpp filesystemmodel.h filesystemwatcher.cpp filesystemwatcher.h fileutils.cpp fileutils.h @@ -86,7 +89,6 @@ add_qtc_library(Utils launcherpackets.cpp launcherpackets.h launchersocket.cpp launchersocket.h layoutbuilder.cpp layoutbuilder.h - linecolumn.cpp linecolumn.h link.cpp link.h listmodel.h listutils.h @@ -124,6 +126,7 @@ add_qtc_library(Utils port.cpp port.h portlist.cpp portlist.h predicates.h + process.cpp process.h processenums.h processhandle.cpp processhandle.h processinfo.cpp processinfo.h @@ -136,7 +139,6 @@ add_qtc_library(Utils qrcparser.cpp qrcparser.h qtcassert.cpp qtcassert.h qtcolorbutton.cpp qtcolorbutton.h - qtcprocess.cpp qtcprocess.h qtcsettings.cpp qtcsettings.h ranges.h reloadpromptutils.cpp reloadpromptutils.h @@ -144,6 +146,8 @@ add_qtc_library(Utils runextensions.cpp runextensions.h savefile.cpp savefile.h scopedswap.h + scopedtimer.cpp scopedtimer.h + searchresultitem.cpp searchresultitem.h set_algorithm.h settingsaccessor.cpp settingsaccessor.h settingsselector.cpp settingsselector.h @@ -167,12 +171,12 @@ add_qtc_library(Utils styleanimator.cpp styleanimator.h styledbar.cpp styledbar.h stylehelper.cpp stylehelper.h - tasktree.cpp tasktree.h templateengine.cpp templateengine.h temporarydirectory.cpp temporarydirectory.h temporaryfile.cpp temporaryfile.h terminalcommand.cpp terminalcommand.h - terminalprocess.cpp terminalprocess_p.h + terminalhooks.cpp terminalhooks.h + terminalinterface.cpp terminalinterface.h textfieldcheckbox.cpp textfieldcheckbox.h textfieldcombobox.cpp textfieldcombobox.h textfileformat.cpp textfileformat.h @@ -194,6 +198,7 @@ add_qtc_library(Utils utils_global.h utilstr.h utilsicons.cpp utilsicons.h + utiltypes.h variablechooser.cpp variablechooser.h winutils.cpp winutils.h wizard.cpp wizard.h @@ -260,7 +265,7 @@ extend_qtc_library(Utils CONDITION UNIX AND NOT APPLE extend_qtc_library(Utils CONDITION TARGET Qt::CorePrivate - DEPENDS Qt::CorePrivate + DEPENDS Qt::CorePrivate ptyqt DEFINES QTC_UTILS_WITH_FSENGINE SOURCES fsengine/fsengine_impl.cpp fsengine/fsengine_impl.h @@ -274,19 +279,10 @@ extend_qtc_library(Utils ) if (WIN32) - add_qtc_executable(qtcreator_process_stub - SOURCES process_stub_win.c - DEPENDS shell32 - DEFINES _UNICODE UNICODE _CRT_SECURE_NO_WARNINGS - ) - add_qtc_executable(qtcreator_ctrlc_stub DEPENDS user32 shell32 DEFINES _UNICODE UNICODE _CRT_SECURE_NO_WARNINGS SOURCES process_ctrlc_stub.cpp ) -else() - add_qtc_executable(qtcreator_process_stub SOURCES process_stub_unix.c) endif() - diff --git a/src/libs/utils/ansiescapecodehandler.cpp b/src/libs/utils/ansiescapecodehandler.cpp index 0909a3d2c6f..91f13d9a25e 100644 --- a/src/libs/utils/ansiescapecodehandler.cpp +++ b/src/libs/utils/ansiescapecodehandler.cpp @@ -9,6 +9,7 @@ namespace Utils { /*! \class Utils::AnsiEscapeCodeHandler + \inmodule QtCreator \brief The AnsiEscapeCodeHandler class parses text and extracts ANSI escape codes from it. diff --git a/src/libs/utils/archive.cpp b/src/libs/utils/archive.cpp index 408c75d7c58..5e62835a20f 100644 --- a/src/libs/utils/archive.cpp +++ b/src/libs/utils/archive.cpp @@ -5,8 +5,8 @@ #include "algorithm.h" #include "mimeutils.h" +#include "process.h" #include "qtcassert.h" -#include "qtcprocess.h" #include "utilstr.h" #include @@ -160,12 +160,12 @@ void Archive::unarchive() m_workingDirectory.ensureWritableDir(); - m_process.reset(new QtcProcess); + m_process.reset(new Process); m_process->setProcessChannelMode(QProcess::MergedChannels); - QObject::connect(m_process.get(), &QtcProcess::readyReadStandardOutput, this, [this] { + QObject::connect(m_process.get(), &Process::readyReadStandardOutput, this, [this] { emit outputReceived(m_process->readAllStandardOutput()); }); - QObject::connect(m_process.get(), &QtcProcess::done, this, [this] { + QObject::connect(m_process.get(), &Process::done, this, [this] { const bool successfulFinish = m_process->result() == ProcessResult::FinishedWithSuccess; if (!successfulFinish) emit outputReceived(Tr::tr("Command failed.")); diff --git a/src/libs/utils/archive.h b/src/libs/utils/archive.h index 30f58585e3a..ccf62d3885c 100644 --- a/src/libs/utils/archive.h +++ b/src/libs/utils/archive.h @@ -12,7 +12,7 @@ namespace Utils { class FilePath; -class QtcProcess; +class Process; class QTCREATOR_UTILS_EXPORT Archive : public QObject { @@ -33,7 +33,7 @@ signals: private: CommandLine m_commandLine; FilePath m_workingDirectory; - std::unique_ptr m_process; + std::unique_ptr m_process; }; } // namespace Utils diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 72e667d76a6..57d358a7c1b 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -4,11 +4,13 @@ #include "aspects.h" #include "algorithm.h" +#include "checkablemessagebox.h" #include "environment.h" #include "fancylineedit.h" #include "layoutbuilder.h" #include "pathchooser.h" #include "qtcassert.h" +#include "qtcolorbutton.h" #include "qtcsettings.h" #include "utilstr.h" #include "variablechooser.h" @@ -29,7 +31,7 @@ #include #include -using namespace Utils::Layouting; +using namespace Layouting; namespace Utils { namespace Internal { @@ -88,11 +90,15 @@ public: */ /*! - Constructs a BaseAspect. + Constructs a base aspect. + + If \a container is non-null, the aspect is made known to the container. */ -BaseAspect::BaseAspect() +BaseAspect::BaseAspect(AspectContainer *container) : d(new Internal::BaseAspectPrivate) { + if (container) + container->registerAspect(this); addDataExtractor(this, &BaseAspect::value, &Data::value); } @@ -120,9 +126,9 @@ QVariant BaseAspect::value() const } /*! - Sets value. + Sets \a value. - Emits changed() if the value changed. + Emits \c changed() if the value changed. */ void BaseAspect::setValue(const QVariant &value) { @@ -133,7 +139,7 @@ void BaseAspect::setValue(const QVariant &value) } /*! - Sets value without emitting changed() + Sets \a value without emitting \c changed(). Returns whether the value changed. */ @@ -151,7 +157,7 @@ QVariant BaseAspect::defaultValue() const } /*! - Sets a default value and the current value for this aspect. + Sets a default \a value and the current value for this aspect. \note The current value will be set silently to the same value. It is reasonable to only set default values in the setup phase @@ -209,17 +215,15 @@ void BaseAspect::setupLabel() registerSubWidget(d->m_label); } -void BaseAspect::addLabeledItem(Layouting::LayoutBuilder &builder, QWidget *widget) +void BaseAspect::addLabeledItem(LayoutItem &parent, QWidget *widget) { setupLabel(); if (QLabel *l = label()) { l->setBuddy(widget); - builder.addItem(l); - LayoutItem item(widget); - item.span = std::max(d->m_spanX - 1, 1); - builder.addItem(item); + parent.addItem(l); + parent.addItem(Span(std::max(d->m_spanX - 1, 1), LayoutItem(widget))); } else { - builder.addItem(LayoutItem(widget)); + parent.addItem(LayoutItem(widget)); } } @@ -337,7 +341,7 @@ bool BaseAspect::isAutoApply() const } /*! - Sets auto-apply mode. When auto-apply mode is on, user interaction to this + Sets auto-apply mode. When auto-apply mode is \a on, user interaction to this aspect's widget will not modify the \c value of the aspect until \c apply() is called programmatically. @@ -368,7 +372,7 @@ QString BaseAspect::settingsKey() const } /*! - Sets the key to be used when accessing the settings. + Sets the \a key to be used when accessing the settings. \sa settingsKey() */ @@ -378,7 +382,7 @@ void BaseAspect::setSettingsKey(const QString &key) } /*! - Sets the key and group to be used when accessing the settings. + Sets the \a key and \a group to be used when accessing the settings. \sa settingsKey() */ @@ -417,13 +421,24 @@ QAction *BaseAspect::action() } /*! - Adds the visual representation of this aspect to a layout using - a layout builder. + Adds the visual representation of this aspect to the layout with the + specified \a parent using a layout builder. */ -void BaseAspect::addToLayout(Layouting::LayoutBuilder &) +void BaseAspect::addToLayout(LayoutItem &) { } +void createItem(Layouting::LayoutItem *item, const BaseAspect &aspect) +{ + const_cast(aspect).addToLayout(*item); +} + +void createItem(Layouting::LayoutItem *item, const BaseAspect *aspect) +{ + const_cast(aspect)->addToLayout(*item); +} + + /*! Updates this aspect's value from user-initiated changes in the widget. @@ -515,7 +530,7 @@ void BaseAspect::saveToMap(QVariantMap &data, const QVariant &value, } /*! - Retrieves the internal value of this BaseAspect from a \c QVariantMap. + Retrieves the internal value of this BaseAspect from the QVariantMap \a map. */ void BaseAspect::fromMap(const QVariantMap &map) { @@ -524,7 +539,7 @@ void BaseAspect::fromMap(const QVariantMap &map) } /*! - Stores the internal value of this BaseAspect into a \c QVariantMap. + Stores the internal value of this BaseAspect into the QVariantMap \a map. */ void BaseAspect::toMap(QVariantMap &map) const { @@ -575,8 +590,15 @@ class BoolAspectPrivate { public: BoolAspect::LabelPlacement m_labelPlacement = BoolAspect::LabelPlacement::AtCheckBox; - QPointer m_checkBox; // Owned by configuration widget + QPointer m_button; // Owned by configuration widget QPointer m_groupBox; // For BoolAspects handling GroupBox check boxes + bool m_buttonIsAdopted = false; +}; + +class ColorAspectPrivate +{ +public: + QPointer m_colorButton; // Owned by configuration widget }; class SelectionAspectPrivate @@ -623,9 +645,12 @@ public: Qt::TextElideMode m_elideMode = Qt::ElideNone; QString m_placeHolderText; + QString m_prompDialogFilter; + QString m_prompDialogTitle; + QStringList m_commandVersionArguments; QString m_historyCompleterKey; PathChooser::Kind m_expectedKind = PathChooser::File; - EnvironmentChange m_environmentChange; + Environment m_environment; QPointer m_labelDisplay; QPointer m_lineEditDisplay; QPointer m_pathChooserDisplay; @@ -645,6 +670,7 @@ public: // Used to block recursive editingFinished signals for example when return is pressed, and // the validation changes focus by opening a dialog bool m_blockAutoApply = false; + bool m_allowPathFromDevice = true; template void updateWidgetFromCheckStatus(StringAspect *aspect, Widget *w) { @@ -742,11 +768,11 @@ public: */ /*! - Constructs a StringAspect. + Constructs the string aspect \a container. */ -StringAspect::StringAspect() - : d(new Internal::StringAspectPrivate) +StringAspect::StringAspect(AspectContainer *container) + : BaseAspect(container), d(new Internal::StringAspectPrivate) { setDefaultValue(QString()); setSpan(2, 1); // Default: Label + something @@ -777,7 +803,7 @@ QString StringAspect::value() const } /*! - Sets the \a value of this StringAspect from an ordinary \c QString. + Sets the value, \a val, of this StringAspect from an ordinary \c QString. */ void StringAspect::setValue(const QString &val) { @@ -931,6 +957,34 @@ void StringAspect::setPlaceHolderText(const QString &placeHolderText) d->m_textEditDisplay->setPlaceholderText(placeHolderText); } +void StringAspect::setPromptDialogFilter(const QString &filter) +{ + d->m_prompDialogFilter = filter; + if (d->m_pathChooserDisplay) + d->m_pathChooserDisplay->setPromptDialogFilter(filter); +} + +void StringAspect::setPromptDialogTitle(const QString &title) +{ + d->m_prompDialogTitle = title; + if (d->m_pathChooserDisplay) + d->m_pathChooserDisplay->setPromptDialogTitle(title); +} + +void StringAspect::setCommandVersionArguments(const QStringList &arguments) +{ + d->m_commandVersionArguments = arguments; + if (d->m_pathChooserDisplay) + d->m_pathChooserDisplay->setCommandVersionArguments(arguments); +} + +void StringAspect::setAllowPathFromDevice(bool allowPathFromDevice) +{ + d->m_allowPathFromDevice = allowPathFromDevice; + if (d->m_pathChooserDisplay) + d->m_pathChooserDisplay->setAllowPathFromDevice(allowPathFromDevice); +} + /*! Sets \a elideMode as label elide mode. */ @@ -968,16 +1022,11 @@ void StringAspect::setExpectedKind(const PathChooser::Kind expectedKind) d->m_pathChooserDisplay->setExpectedKind(expectedKind); } -void StringAspect::setEnvironmentChange(const EnvironmentChange &change) -{ - d->m_environmentChange = change; - if (d->m_pathChooserDisplay) - d->m_pathChooserDisplay->setEnvironmentChange(change); -} - void StringAspect::setEnvironment(const Environment &env) { - setEnvironmentChange(EnvironmentChange::fromDictionary(env.toDictionary())); + d->m_environment = env; + if (d->m_pathChooserDisplay) + d->m_pathChooserDisplay->setEnvironment(env); } void StringAspect::setBaseFileName(const FilePath &baseFileName) @@ -1050,11 +1099,11 @@ void StringAspect::setUncheckedSemantics(StringAspect::UncheckedSemantics semant d->m_uncheckedSemantics = semantics; } -void StringAspect::addToLayout(Layouting::LayoutBuilder &builder) +void StringAspect::addToLayout(LayoutItem &parent) { if (d->m_checker && d->m_checkBoxPlacement == CheckBoxPlacement::Top) { - d->m_checker->addToLayout(builder); - builder.finishRow(); + d->m_checker->addToLayout(parent); + parent.addItem(br); } const auto useMacroExpander = [this](QWidget *w) { @@ -1075,9 +1124,13 @@ void StringAspect::addToLayout(Layouting::LayoutBuilder &builder) d->m_pathChooserDisplay->setHistoryCompleter(d->m_historyCompleterKey); if (d->m_validator) d->m_pathChooserDisplay->setValidationFunction(d->m_validator); - d->m_pathChooserDisplay->setEnvironmentChange(d->m_environmentChange); + d->m_pathChooserDisplay->setEnvironment(d->m_environment); d->m_pathChooserDisplay->setBaseDirectory(d->m_baseFileName); d->m_pathChooserDisplay->setOpenTerminalHandler(d->m_openTerminal); + d->m_pathChooserDisplay->setPromptDialogFilter(d->m_prompDialogFilter); + d->m_pathChooserDisplay->setPromptDialogTitle(d->m_prompDialogTitle); + d->m_pathChooserDisplay->setCommandVersionArguments(d->m_commandVersionArguments); + d->m_pathChooserDisplay->setAllowPathFromDevice(d->m_allowPathFromDevice); if (defaultValue() == value()) d->m_pathChooserDisplay->setDefaultValue(defaultValue()); else @@ -1086,7 +1139,7 @@ void StringAspect::addToLayout(Layouting::LayoutBuilder &builder) if (d->m_pathChooserDisplay->lineEdit()->placeholderText().isEmpty()) d->m_pathChooserDisplay->lineEdit()->setPlaceholderText(d->m_placeHolderText); d->updateWidgetFromCheckStatus(this, d->m_pathChooserDisplay.data()); - addLabeledItem(builder, d->m_pathChooserDisplay); + addLabeledItem(parent, d->m_pathChooserDisplay); useMacroExpander(d->m_pathChooserDisplay->lineEdit()); if (isAutoApply()) { if (d->m_autoApplyOnEditingFinished) { @@ -1094,7 +1147,7 @@ void StringAspect::addToLayout(Layouting::LayoutBuilder &builder) if (d->m_blockAutoApply) return; d->m_blockAutoApply = true; - setValue(d->m_pathChooserDisplay->filePath().toString()); + setValueQuietly(d->m_pathChooserDisplay->filePath().toString()); d->m_blockAutoApply = false; }; connect(d->m_pathChooserDisplay, &PathChooser::editingFinished, this, setPathChooserValue); @@ -1102,7 +1155,7 @@ void StringAspect::addToLayout(Layouting::LayoutBuilder &builder) } else { connect(d->m_pathChooserDisplay, &PathChooser::textChanged, this, [this](const QString &path) { - setValue(path); + setValueQuietly(path); }); } } @@ -1117,7 +1170,7 @@ void StringAspect::addToLayout(Layouting::LayoutBuilder &builder) d->m_lineEditDisplay->setTextKeepingActiveCursor(displayedString); d->m_lineEditDisplay->setReadOnly(isReadOnly()); d->updateWidgetFromCheckStatus(this, d->m_lineEditDisplay.data()); - addLabeledItem(builder, d->m_lineEditDisplay); + addLabeledItem(parent, d->m_lineEditDisplay); useMacroExpander(d->m_lineEditDisplay); if (isAutoApply()) { if (d->m_autoApplyOnEditingFinished) { @@ -1147,7 +1200,7 @@ void StringAspect::addToLayout(Layouting::LayoutBuilder &builder) connect(d->m_lineEditDisplay, &QLineEdit::textChanged, this, [this, resetButton] { resetButton->setEnabled(d->m_lineEditDisplay->text() != defaultValue()); }); - builder.addItem(resetButton); + parent.addItem(resetButton); } break; case TextEditDisplay: @@ -1159,7 +1212,7 @@ void StringAspect::addToLayout(Layouting::LayoutBuilder &builder) d->m_textEditDisplay->setText(displayedString); d->m_textEditDisplay->setReadOnly(isReadOnly()); d->updateWidgetFromCheckStatus(this, d->m_textEditDisplay.data()); - addLabeledItem(builder, d->m_textEditDisplay); + addLabeledItem(parent, d->m_textEditDisplay); useMacroExpander(d->m_textEditDisplay); if (isAutoApply()) { connect(d->m_textEditDisplay, &QTextEdit::textChanged, this, [this] { @@ -1173,19 +1226,18 @@ void StringAspect::addToLayout(Layouting::LayoutBuilder &builder) d->m_labelDisplay->setTextInteractionFlags(Qt::TextSelectableByMouse); d->m_labelDisplay->setText(displayedString); d->m_labelDisplay->setToolTip(d->m_showToolTipOnLabel ? displayedString : toolTip()); - addLabeledItem(builder, d->m_labelDisplay); + addLabeledItem(parent, d->m_labelDisplay); break; } validateInput(); if (d->m_checker && d->m_checkBoxPlacement == CheckBoxPlacement::Right) - d->m_checker->addToLayout(builder); + d->m_checker->addToLayout(parent); } QVariant StringAspect::volatileValue() const { - QTC_CHECK(!isAutoApply()); switch (d->m_displayStyle) { case PathChooserDisplay: QTC_ASSERT(d->m_pathChooserDisplay, return {}); @@ -1287,6 +1339,92 @@ void StringAspect::makeCheckable(CheckBoxPlacement checkBoxPlacement, update(); } + +/*! + \class Utils::FilePathAspect + \inmodule QtCreator + + \brief A file path aspect is shallow wrapper around a Utils::StringAspect that + represents a file in the file system. + + It is displayed by default using Utils::PathChooser. + + The visual representation often contains a label in front of the display + of the actual value. + + \sa Utils::StringAspect +*/ + + +FilePathAspect::FilePathAspect(AspectContainer *container) + : StringAspect(container) +{ + setDisplayStyle(PathChooserDisplay); +} + +/*! + \class Utils::ColorAspect + \inmodule QtCreator + + \brief A color aspect is a color property of some object, together with + a description of its behavior for common operations like visualizing or + persisting. + + The color aspect is displayed using a QtColorButton. +*/ + +ColorAspect::ColorAspect(AspectContainer *container) + : BaseAspect(container), d(new Internal::ColorAspectPrivate) +{ + setDefaultValue(QColor::fromRgb(0, 0, 0)); + setSpan(1, 1); + + addDataExtractor(this, &ColorAspect::value, &Data::value); +} + +ColorAspect::~ColorAspect() = default; + +void ColorAspect::addToLayout(Layouting::LayoutItem &parent) +{ + QTC_CHECK(!d->m_colorButton); + d->m_colorButton = createSubWidget(); + parent.addItem(d->m_colorButton.data()); + d->m_colorButton->setColor(value()); + if (isAutoApply()) { + connect(d->m_colorButton.data(), + &QtColorButton::colorChanged, + this, + [this](const QColor &color) { setValue(color); }); + } +} + +QColor ColorAspect::value() const +{ + return BaseAspect::value().value(); +} + +void ColorAspect::setValue(const QColor &value) +{ + if (BaseAspect::setValueQuietly(value)) + emit changed(); +} + +QVariant ColorAspect::volatileValue() const +{ + QTC_CHECK(!isAutoApply()); + if (d->m_colorButton) + return d->m_colorButton->color(); + QTC_CHECK(false); + return {}; +} + +void ColorAspect::setVolatileValue(const QVariant &val) +{ + QTC_CHECK(!isAutoApply()); + if (d->m_colorButton) + d->m_colorButton->setColor(val.value()); +} + /*! \class Utils::BoolAspect \inmodule QtCreator @@ -1302,54 +1440,68 @@ void StringAspect::makeCheckable(CheckBoxPlacement checkBoxPlacement, */ -BoolAspect::BoolAspect(const QString &settingsKey) - : d(new Internal::BoolAspectPrivate) +BoolAspect::BoolAspect(AspectContainer *container) + : BaseAspect(container), d(new Internal::BoolAspectPrivate) { setDefaultValue(false); - setSettingsKey(settingsKey); setSpan(2, 1); addDataExtractor(this, &BoolAspect::value, &Data::value); } /*! - \reimp + \internal */ BoolAspect::~BoolAspect() = default; /*! \reimp */ -void BoolAspect::addToLayout(Layouting::LayoutBuilder &builder) +void BoolAspect::addToLayout(Layouting::LayoutItem &parent) { - QTC_CHECK(!d->m_checkBox); - d->m_checkBox = createSubWidget(); + if (!d->m_buttonIsAdopted) { + QTC_CHECK(!d->m_button); + d->m_button = createSubWidget(); + } switch (d->m_labelPlacement) { - case LabelPlacement::AtCheckBoxWithoutDummyLabel: - d->m_checkBox->setText(labelText()); - builder.addItem(d->m_checkBox.data()); + case LabelPlacement::AtCheckBox: + d->m_button->setText(labelText()); + parent.addItem(d->m_button.data()); break; - case LabelPlacement::AtCheckBox: { - d->m_checkBox->setText(labelText()); - Layouting::LayoutBuilder::LayoutType type = builder.layoutType(); - if (type == LayoutBuilder::FormLayout) - builder.addItem(createSubWidget()); - builder.addItem(d->m_checkBox.data()); - break; - } case LabelPlacement::InExtraLabel: - addLabeledItem(builder, d->m_checkBox); + addLabeledItem(parent, d->m_button); break; } - d->m_checkBox->setChecked(value()); + d->m_button->setChecked(value()); if (isAutoApply()) { - connect(d->m_checkBox.data(), &QAbstractButton::clicked, + connect(d->m_button.data(), &QAbstractButton::clicked, this, [this](bool val) { setValue(val); }); } - connect(d->m_checkBox.data(), &QAbstractButton::clicked, + connect(d->m_button.data(), &QAbstractButton::clicked, this, &BoolAspect::volatileValueChanged); } +void BoolAspect::adoptButton(QAbstractButton *button) +{ + QTC_ASSERT(button, return); + QTC_CHECK(!d->m_button); + d->m_button = button; + d->m_buttonIsAdopted = true; + registerSubWidget(button); +} + +std::function BoolAspect::groupChecker() +{ + return [this](QObject *target) { + auto groupBox = qobject_cast(target); + QTC_ASSERT(groupBox, return); + registerSubWidget(groupBox); + groupBox->setCheckable(true); + groupBox->setChecked(value()); + d->m_groupBox = groupBox; + }; +} + QAction *BoolAspect::action() { if (hasAction()) @@ -1371,9 +1523,8 @@ QAction *BoolAspect::action() QVariant BoolAspect::volatileValue() const { - QTC_CHECK(!isAutoApply()); - if (d->m_checkBox) - return d->m_checkBox->isChecked(); + if (d->m_button) + return d->m_button->isChecked(); if (d->m_groupBox) return d->m_groupBox->isChecked(); QTC_CHECK(false); @@ -1382,9 +1533,8 @@ QVariant BoolAspect::volatileValue() const void BoolAspect::setVolatileValue(const QVariant &val) { - QTC_CHECK(!isAutoApply()); - if (d->m_checkBox) - d->m_checkBox->setChecked(val.toBool()); + if (d->m_button) + d->m_button->setChecked(val.toBool()); else if (d->m_groupBox) d->m_groupBox->setChecked(val.toBool()); } @@ -1396,7 +1546,7 @@ void BoolAspect::emitChangedValue() /*! - \reimp + Returns the value of the boolean aspect as a boolean value. */ bool BoolAspect::value() const @@ -1407,8 +1557,8 @@ bool BoolAspect::value() const void BoolAspect::setValue(bool value) { if (BaseAspect::setValueQuietly(value)) { - if (d->m_checkBox) - d->m_checkBox->setChecked(value); + if (d->m_button) + d->m_button->setChecked(value); //qDebug() << "SetValue: Changing" << labelText() << " to " << value; emit changed(); //QTC_CHECK(!labelText().isEmpty()); @@ -1442,10 +1592,12 @@ void BoolAspect::setLabelPlacement(BoolAspect::LabelPlacement labelPlacement) d->m_labelPlacement = labelPlacement; } -void BoolAspect::setHandlesGroup(QGroupBox *box) +CheckableDecider BoolAspect::checkableDecider() { - registerSubWidget(box); - d->m_groupBox = box; + return CheckableDecider( + [this] { return !value(); }, + [this] { setValue(true); } + ); } /*! @@ -1459,21 +1611,21 @@ void BoolAspect::setHandlesGroup(QGroupBox *box) QRadioButtons in a QButtonGroup. */ -SelectionAspect::SelectionAspect() - : d(new Internal::SelectionAspectPrivate) +SelectionAspect::SelectionAspect(AspectContainer *container) + : BaseAspect(container), d(new Internal::SelectionAspectPrivate) { setSpan(2, 1); } /*! - \reimp + \internal */ SelectionAspect::~SelectionAspect() = default; /*! \reimp */ -void SelectionAspect::addToLayout(Layouting::LayoutBuilder &builder) +void SelectionAspect::addToLayout(Layouting::LayoutItem &parent) { QTC_CHECK(d->m_buttonGroup == nullptr); QTC_CHECK(!d->m_comboBox); @@ -1489,7 +1641,7 @@ void SelectionAspect::addToLayout(Layouting::LayoutBuilder &builder) button->setChecked(i == value()); button->setEnabled(option.enabled); button->setToolTip(option.tooltip); - builder.addItems({Layouting::empty, button}); + parent.addItem(button); d->m_buttons.append(button); d->m_buttonGroup->addButton(button, i); if (isAutoApply()) { @@ -1511,14 +1663,13 @@ void SelectionAspect::addToLayout(Layouting::LayoutBuilder &builder) connect(d->m_comboBox.data(), &QComboBox::currentIndexChanged, this, &SelectionAspect::volatileValueChanged); d->m_comboBox->setCurrentIndex(value()); - addLabeledItem(builder, d->m_comboBox); + addLabeledItem(parent, d->m_comboBox); break; } } QVariant SelectionAspect::volatileValue() const { - QTC_CHECK(!isAutoApply()); switch (d->m_displayStyle) { case DisplayStyle::RadioButtons: QTC_ASSERT(d->m_buttonGroup, return {}); @@ -1532,7 +1683,6 @@ QVariant SelectionAspect::volatileValue() const void SelectionAspect::setVolatileValue(const QVariant &val) { - QTC_CHECK(!isAutoApply()); switch (d->m_displayStyle) { case DisplayStyle::RadioButtons: { if (d->m_buttonGroup) { @@ -1603,12 +1753,14 @@ void SelectionAspect::setDefaultValue(const QString &val) QString SelectionAspect::stringValue() const { - return d->m_options.at(value()).displayName; + const int idx = value(); + return idx >= 0 && idx < d->m_options.size() ? d->m_options.at(idx).displayName : QString(); } QVariant SelectionAspect::itemValue() const { - return d->m_options.at(value()).itemData; + const int idx = value(); + return idx >= 0 && idx < d->m_options.size() ? d->m_options.at(idx).itemData : QVariant(); } void SelectionAspect::addOption(const QString &displayName, const QString &toolTip) @@ -1662,22 +1814,22 @@ QVariant SelectionAspect::itemValueForIndex(int index) const checkable items. */ -MultiSelectionAspect::MultiSelectionAspect() - : d(new Internal::MultiSelectionAspectPrivate(this)) +MultiSelectionAspect::MultiSelectionAspect(AspectContainer *container) + : BaseAspect(container), d(new Internal::MultiSelectionAspectPrivate(this)) { setDefaultValue(QStringList()); setSpan(2, 1); } /*! - \reimp + \internal */ MultiSelectionAspect::~MultiSelectionAspect() = default; /*! \reimp */ -void MultiSelectionAspect::addToLayout(Layouting::LayoutBuilder &builder) +void MultiSelectionAspect::addToLayout(LayoutItem &builder) { QTC_CHECK(d->m_listView == nullptr); if (d->m_allValues.isEmpty()) @@ -1769,8 +1921,8 @@ void MultiSelectionAspect::setValue(const QStringList &value) // IntegerAspect -IntegerAspect::IntegerAspect() - : d(new Internal::IntegerAspectPrivate) +IntegerAspect::IntegerAspect(AspectContainer *container) + : BaseAspect(container), d(new Internal::IntegerAspectPrivate) { setDefaultValue(qint64(0)); setSpan(2, 1); @@ -1779,14 +1931,14 @@ IntegerAspect::IntegerAspect() } /*! - \reimp + \internal */ IntegerAspect::~IntegerAspect() = default; /*! \reimp */ -void IntegerAspect::addToLayout(Layouting::LayoutBuilder &builder) +void IntegerAspect::addToLayout(Layouting::LayoutItem &parent) { QTC_CHECK(!d->m_spinBox); d->m_spinBox = createSubWidget(); @@ -1799,11 +1951,11 @@ void IntegerAspect::addToLayout(Layouting::LayoutBuilder &builder) d->m_spinBox->setRange(int(d->m_minimumValue.value() / d->m_displayScaleFactor), int(d->m_maximumValue.value() / d->m_displayScaleFactor)); d->m_spinBox->setValue(int(value() / d->m_displayScaleFactor)); // Must happen after setRange() - addLabeledItem(builder, d->m_spinBox); + addLabeledItem(parent, d->m_spinBox); if (isAutoApply()) { connect(d->m_spinBox.data(), &QSpinBox::valueChanged, - this, [this] { setValue(d->m_spinBox->value()); }); + this, [this] { setValue(d->m_spinBox->value() * d->m_displayScaleFactor); }); } } @@ -1830,7 +1982,7 @@ void IntegerAspect::setValue(qint64 value) { if (BaseAspect::setValueQuietly(value)) { if (d->m_spinBox) - d->m_spinBox->setValue(value); + d->m_spinBox->setValue(value / d->m_displayScaleFactor); //qDebug() << "SetValue: Changing" << labelText() << " to " << value; emit changed(); //QTC_CHECK(!labelText().isEmpty()); @@ -1905,22 +2057,22 @@ void IntegerAspect::setSingleStep(qint64 step) the display of the spin box. */ -DoubleAspect::DoubleAspect() - : d(new Internal::DoubleAspectPrivate) +DoubleAspect::DoubleAspect(AspectContainer *container) + : BaseAspect(container), d(new Internal::DoubleAspectPrivate) { setDefaultValue(double(0)); setSpan(2, 1); } /*! - \reimp + \internal */ DoubleAspect::~DoubleAspect() = default; /*! \reimp */ -void DoubleAspect::addToLayout(Layouting::LayoutBuilder &builder) +void DoubleAspect::addToLayout(LayoutItem &builder) { QTC_CHECK(!d->m_spinBox); d->m_spinBox = createSubWidget(); @@ -2001,7 +2153,7 @@ void DoubleAspect::setSingleStep(double step) /*! - \class Utils::BaseTristateAspect + \class Utils::TriStateAspect \inmodule QtCreator \brief A tristate aspect is a property of some object that can have @@ -2010,8 +2162,11 @@ void DoubleAspect::setSingleStep(double step) Its visual representation is a QComboBox with three items. */ -TriStateAspect::TriStateAspect(const QString &onString, const QString &offString, +TriStateAspect::TriStateAspect(AspectContainer *container, + const QString &onString, + const QString &offString, const QString &defaultString) + : SelectionAspect(container) { setDisplayStyle(DisplayStyle::ComboBox); setDefaultValue(TriState::Default); @@ -2060,23 +2215,23 @@ TriState TriState::fromVariant(const QVariant &variant) that is a list of strings. */ -StringListAspect::StringListAspect() - : d(new Internal::StringListAspectPrivate) +StringListAspect::StringListAspect(AspectContainer *container) + : BaseAspect(container), d(new Internal::StringListAspectPrivate) { setDefaultValue(QStringList()); } /*! - \reimp + \internal */ StringListAspect::~StringListAspect() = default; /*! \reimp */ -void StringListAspect::addToLayout(Layouting::LayoutBuilder &builder) +void StringListAspect::addToLayout(LayoutItem &parent) { - Q_UNUSED(builder) + Q_UNUSED(parent) // TODO - when needed. } @@ -2125,28 +2280,30 @@ void StringListAspect::removeValues(const QStringList &values) /*! \class Utils::IntegerListAspect + \internal \inmodule QtCreator \brief A string list aspect represents a property of some object that is a list of strings. */ -IntegersAspect::IntegersAspect() +IntegersAspect::IntegersAspect(AspectContainer *container) + : BaseAspect(container) { setDefaultValue({}); } /*! - \reimp + \internal */ IntegersAspect::~IntegersAspect() = default; /*! \reimp */ -void IntegersAspect::addToLayout(Layouting::LayoutBuilder &builder) +void IntegersAspect::addToLayout(Layouting::LayoutItem &parent) { - Q_UNUSED(builder) + Q_UNUSED(parent) // TODO - when needed. } @@ -2180,6 +2337,7 @@ void IntegersAspect::setDefaultValue(const QList &value) /*! \class Utils::TextDisplay + \inmodule QtCreator \brief A text display is a phony aspect with the sole purpose of providing some text display using an Utils::InfoLabel in places where otherwise @@ -2188,6 +2346,10 @@ void IntegersAspect::setDefaultValue(const QList &value) A text display does not have a real value. */ +TextDisplay::TextDisplay(AspectContainer *container) + : BaseAspect(container) +{} + /*! Constructs a text display showing the \a message with an icon representing type \a type. @@ -2200,14 +2362,14 @@ TextDisplay::TextDisplay(const QString &message, InfoLabel::InfoType type) } /*! - \reimp + \internal */ TextDisplay::~TextDisplay() = default; /*! \reimp */ -void TextDisplay::addToLayout(LayoutBuilder &builder) +void TextDisplay::addToLayout(LayoutItem &parent) { if (!d->m_label) { d->m_label = createSubWidget(d->m_message, d->m_type); @@ -2219,7 +2381,7 @@ void TextDisplay::addToLayout(LayoutBuilder &builder) if (!isVisible()) d->m_label->setVisible(false); } - builder.addItem(d->m_label.data()); + parent.addItem(d->m_label.data()); } /*! @@ -2253,9 +2415,9 @@ namespace Internal { class AspectContainerPrivate { public: - QList m_items; // Not owned + QList m_items; // Both owned and non-owned. + QList m_ownedItems; // Owned only. bool m_autoApply = true; - bool m_ownsSubAspects = false; QStringList m_settingsGroup; }; @@ -2266,21 +2428,22 @@ AspectContainer::AspectContainer(QObject *parent) {} /*! - \reimp + \internal */ AspectContainer::~AspectContainer() { - if (d->m_ownsSubAspects) - qDeleteAll(d->m_items); + qDeleteAll(d->m_ownedItems); } /*! \internal */ -void AspectContainer::registerAspect(BaseAspect *aspect) +void AspectContainer::registerAspect(BaseAspect *aspect, bool takeOwnership) { aspect->setAutoApply(d->m_autoApply); d->m_items.append(aspect); + if (takeOwnership) + d->m_ownedItems.append(aspect); } void AspectContainer::registerAspects(const AspectContainer &aspects) @@ -2292,7 +2455,7 @@ void AspectContainer::registerAspects(const AspectContainer &aspects) /*! Retrieves a BaseAspect with a given \a id, or nullptr if no such aspect is contained. - \sa BaseAspect. + \sa BaseAspect */ BaseAspect *AspectContainer::aspect(Id id) const { @@ -2365,10 +2528,15 @@ void AspectContainer::setSettingsGroups(const QString &groupKey, const QString & void AspectContainer::apply() { + const bool willChange = isDirty(); + for (BaseAspect *aspect : std::as_const(d->m_items)) aspect->apply(); emit applied(); + + if (willChange) + emit changed(); } void AspectContainer::cancel() @@ -2396,11 +2564,6 @@ void AspectContainer::setAutoApply(bool on) aspect->setAutoApply(on); } -void AspectContainer::setOwnsSubAspects(bool on) -{ - d->m_ownsSubAspects = on; -} - bool AspectContainer::isDirty() const { for (BaseAspect *aspect : std::as_const(d->m_items)) { diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index 413a17e6245..b99c28c0bef 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -9,28 +9,28 @@ #include "macroexpander.h" #include "pathchooser.h" -#include - #include #include #include QT_BEGIN_NAMESPACE class QAction; -class QGroupBox; class QSettings; QT_END_NAMESPACE +namespace Layouting { class LayoutItem; } + namespace Utils { class AspectContainer; class BoolAspect; -namespace Layouting { class LayoutBuilder; } +class CheckableDecider; namespace Internal { class AspectContainerPrivate; class BaseAspectPrivate; class BoolAspectPrivate; +class ColorAspectPrivate; class DoubleAspectPrivate; class IntegerAspectPrivate; class MultiSelectionAspectPrivate; @@ -45,7 +45,7 @@ class QTCREATOR_UTILS_EXPORT BaseAspect : public QObject Q_OBJECT public: - BaseAspect(); + BaseAspect(AspectContainer *container = nullptr); ~BaseAspect() override; Id id() const; @@ -98,7 +98,7 @@ public: virtual void toMap(QVariantMap &map) const; virtual void toActiveMap(QVariantMap &map) const { toMap(map); } - virtual void addToLayout(Layouting::LayoutBuilder &builder); + virtual void addToLayout(Layouting::LayoutItem &parent); virtual QVariant volatileValue() const; virtual void setVolatileValue(const QVariant &val); @@ -116,7 +116,7 @@ public: virtual void apply(); virtual void cancel(); virtual void finish(); - bool isDirty() const; + virtual bool isDirty() const; bool hasAction() const; class QTCREATOR_UTILS_EXPORT Data @@ -169,7 +169,7 @@ signals: protected: QLabel *label() const; void setupLabel(); - void addLabeledItem(Layouting::LayoutBuilder &builder, QWidget *widget); + void addLabeledItem(Layouting::LayoutItem &parent, QWidget *widget); void setDataCreatorHelper(const DataCreator &creator) const; void setDataClonerHelper(const DataCloner &cloner) const; @@ -205,12 +205,15 @@ private: std::unique_ptr d; }; +QTCREATOR_UTILS_EXPORT void createItem(Layouting::LayoutItem *item, const BaseAspect &aspect); +QTCREATOR_UTILS_EXPORT void createItem(Layouting::LayoutItem *item, const BaseAspect *aspect); + class QTCREATOR_UTILS_EXPORT BoolAspect : public BaseAspect { Q_OBJECT public: - explicit BoolAspect(const QString &settingsKey = QString()); + BoolAspect(AspectContainer *container = nullptr); ~BoolAspect() override; struct Data : BaseAspect::Data @@ -218,7 +221,9 @@ public: bool value; }; - void addToLayout(Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; + std::function groupChecker(); + Utils::CheckableDecider checkableDecider(); QAction *action() override; @@ -226,16 +231,18 @@ public: void setVolatileValue(const QVariant &val) override; void emitChangedValue() override; + bool operator()() const { return value(); } bool value() const; void setValue(bool val); bool defaultValue() const; void setDefaultValue(bool val); - enum class LabelPlacement { AtCheckBox, AtCheckBoxWithoutDummyLabel, InExtraLabel }; + enum class LabelPlacement { AtCheckBox, InExtraLabel }; void setLabel(const QString &labelText, LabelPlacement labelPlacement = LabelPlacement::InExtraLabel); void setLabelPlacement(LabelPlacement labelPlacement); - void setHandlesGroup(QGroupBox *box); + + void adoptButton(QAbstractButton *button); signals: void valueChanged(bool newValue); @@ -245,19 +252,45 @@ private: std::unique_ptr d; }; +class QTCREATOR_UTILS_EXPORT ColorAspect : public BaseAspect +{ + Q_OBJECT + +public: + ColorAspect(AspectContainer *container = nullptr); + ~ColorAspect() override; + + struct Data : BaseAspect::Data + { + QColor value; + }; + + void addToLayout(Layouting::LayoutItem &parent) override; + + QColor value() const; + void setValue(const QColor &val); + + QVariant volatileValue() const override; + void setVolatileValue(const QVariant &val) override; + +private: + std::unique_ptr d; +}; + class QTCREATOR_UTILS_EXPORT SelectionAspect : public BaseAspect { Q_OBJECT public: - SelectionAspect(); + SelectionAspect(AspectContainer *container = nullptr); ~SelectionAspect() override; - void addToLayout(Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; QVariant volatileValue() const override; void setVolatileValue(const QVariant &val) override; void finish() override; + int operator()() const { return value(); } int value() const; void setValue(int val); @@ -304,10 +337,10 @@ class QTCREATOR_UTILS_EXPORT MultiSelectionAspect : public BaseAspect Q_OBJECT public: - MultiSelectionAspect(); + MultiSelectionAspect(AspectContainer *container = nullptr); ~MultiSelectionAspect() override; - void addToLayout(Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; enum class DisplayStyle { ListView }; void setDisplayStyle(DisplayStyle style); @@ -327,7 +360,7 @@ class QTCREATOR_UTILS_EXPORT StringAspect : public BaseAspect Q_OBJECT public: - StringAspect(); + StringAspect(AspectContainer *container = nullptr); ~StringAspect() override; struct Data : BaseAspect::Data @@ -336,7 +369,7 @@ public: FilePath filePath; }; - void addToLayout(Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; QVariant volatileValue() const override; void setVolatileValue(const QVariant &val) override; @@ -345,6 +378,8 @@ public: // Hook between UI and StringAspect: using ValueAcceptor = std::function(const QString &, const QString &)>; void setValueAcceptor(ValueAcceptor &&acceptor); + + QString operator()() const { return value(); } QString value() const; void setValue(const QString &val); @@ -355,9 +390,11 @@ public: void setDisplayFilter(const std::function &displayFilter); void setPlaceHolderText(const QString &placeHolderText); + void setPromptDialogFilter(const QString &filter); + void setPromptDialogTitle(const QString &title); + void setCommandVersionArguments(const QStringList &arguments); void setHistoryCompleter(const QString &historyCompleterKey); void setExpectedKind(const PathChooser::Kind expectedKind); - void setEnvironmentChange(const EnvironmentChange &change); void setEnvironment(const Environment &env); void setBaseFileName(const FilePath &baseFileName); void setUndoRedoEnabled(bool readOnly); @@ -369,6 +406,7 @@ public: void setOpenTerminalHandler(const std::function &openTerminal); void setAutoApplyOnEditingFinished(bool applyOnEditingFinished); void setElideMode(Qt::TextElideMode elideMode); + void setAllowPathFromDevice(bool allowPathFromDevice); void validateInput(); @@ -407,19 +445,28 @@ protected: std::unique_ptr d; }; +class QTCREATOR_UTILS_EXPORT FilePathAspect : public StringAspect +{ +public: + FilePathAspect(AspectContainer *container = nullptr); + + FilePath operator()() const { return filePath(); } +}; + class QTCREATOR_UTILS_EXPORT IntegerAspect : public BaseAspect { Q_OBJECT public: - IntegerAspect(); + IntegerAspect(AspectContainer *container = nullptr); ~IntegerAspect() override; - void addToLayout(Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; QVariant volatileValue() const override; void setVolatileValue(const QVariant &val) override; + qint64 operator()() const { return value(); } qint64 value() const; void setValue(qint64 val); @@ -449,14 +496,15 @@ class QTCREATOR_UTILS_EXPORT DoubleAspect : public BaseAspect Q_OBJECT public: - DoubleAspect(); + DoubleAspect(AspectContainer *container = nullptr); ~DoubleAspect() override; - void addToLayout(Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; QVariant volatileValue() const override; void setVolatileValue(const QVariant &val) override; + double operator()() const { return value(); } double value() const; void setValue(double val); @@ -501,7 +549,8 @@ class QTCREATOR_UTILS_EXPORT TriStateAspect : public SelectionAspect Q_OBJECT public: - TriStateAspect(const QString &onString = {}, + TriStateAspect(AspectContainer *container = nullptr, + const QString &onString = {}, const QString &offString = {}, const QString &defaultString = {}); @@ -517,10 +566,10 @@ class QTCREATOR_UTILS_EXPORT StringListAspect : public BaseAspect Q_OBJECT public: - StringListAspect(); + StringListAspect(AspectContainer *container = nullptr); ~StringListAspect() override; - void addToLayout(Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; QStringList value() const; void setValue(const QStringList &val); @@ -539,12 +588,13 @@ class QTCREATOR_UTILS_EXPORT IntegersAspect : public BaseAspect Q_OBJECT public: - IntegersAspect(); + IntegersAspect(AspectContainer *container = nullptr); ~IntegersAspect() override; - void addToLayout(Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; void emitChangedValue() override; + QList operator()() const { return value(); } QList value() const; void setValue(const QList &value); @@ -560,11 +610,12 @@ class QTCREATOR_UTILS_EXPORT TextDisplay : public BaseAspect Q_OBJECT public: + explicit TextDisplay(AspectContainer *container); TextDisplay(const QString &message = {}, InfoLabel::InfoType type = InfoLabel::None); ~TextDisplay() override; - void addToLayout(Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; void setIconType(InfoLabel::InfoType t); void setText(const QString &message); @@ -603,14 +654,14 @@ public: AspectContainer(const AspectContainer &) = delete; AspectContainer &operator=(const AspectContainer &) = delete; - void registerAspect(BaseAspect *aspect); + void registerAspect(BaseAspect *aspect, bool takeOwnership = false); void registerAspects(const AspectContainer &aspects); template Aspect *addAspect(Args && ...args) { auto aspect = new Aspect(args...); - registerAspect(aspect); + registerAspect(aspect, true); return aspect; } @@ -631,7 +682,6 @@ public: bool equals(const AspectContainer &other) const; void copyFrom(const AspectContainer &other); void setAutoApply(bool on); - void setOwnsSubAspects(bool on); bool isDirty() const; template T *aspect() const @@ -661,6 +711,7 @@ public: signals: void applied(); + void changed(); void fromMapFinished(); private: diff --git a/src/libs/utils/async.cpp b/src/libs/utils/async.cpp new file mode 100644 index 00000000000..33487db73be --- /dev/null +++ b/src/libs/utils/async.cpp @@ -0,0 +1,55 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "async.h" + +namespace Utils { + +static int s_maxThreadCount = INT_MAX; + +class AsyncThreadPool : public QThreadPool +{ +public: + AsyncThreadPool(QThread::Priority priority) { + setThreadPriority(priority); + setMaxThreadCount(s_maxThreadCount); + moveToThread(qApp->thread()); + } +}; + +#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0) +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_idle, (QThread::IdlePriority)); +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_lowest, (QThread::LowestPriority)); +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_low, (QThread::LowPriority)); +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_normal, (QThread::NormalPriority)); +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_high, (QThread::HighPriority)); +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_highest, (QThread::HighestPriority)); +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_timeCritical, (QThread::TimeCriticalPriority)); +Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_inherit, (QThread::InheritPriority)); +#else +Q_GLOBAL_STATIC(AsyncThreadPool, s_idle, QThread::IdlePriority); +Q_GLOBAL_STATIC(AsyncThreadPool, s_lowest, QThread::LowestPriority); +Q_GLOBAL_STATIC(AsyncThreadPool, s_low, QThread::LowPriority); +Q_GLOBAL_STATIC(AsyncThreadPool, s_normal, QThread::NormalPriority); +Q_GLOBAL_STATIC(AsyncThreadPool, s_high, QThread::HighPriority); +Q_GLOBAL_STATIC(AsyncThreadPool, s_highest, QThread::HighestPriority); +Q_GLOBAL_STATIC(AsyncThreadPool, s_timeCritical, QThread::TimeCriticalPriority); +Q_GLOBAL_STATIC(AsyncThreadPool, s_inherit, QThread::InheritPriority); +#endif + +QThreadPool *asyncThreadPool(QThread::Priority priority) +{ + switch (priority) { + case QThread::IdlePriority : return s_idle; + case QThread::LowestPriority : return s_lowest; + case QThread::LowPriority : return s_low; + case QThread::NormalPriority : return s_normal; + case QThread::HighPriority : return s_high; + case QThread::HighestPriority : return s_highest; + case QThread::TimeCriticalPriority : return s_timeCritical; + case QThread::InheritPriority : return s_inherit; + } + return nullptr; +} + +} // namespace Utils diff --git a/src/libs/utils/async.h b/src/libs/utils/async.h new file mode 100644 index 00000000000..f8dc6813a7b --- /dev/null +++ b/src/libs/utils/async.h @@ -0,0 +1,215 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "utils_global.h" + +#include "futuresynchronizer.h" +#include "qtcassert.h" + +#include + +#include +#include + +namespace Utils { + +QTCREATOR_UTILS_EXPORT QThreadPool *asyncThreadPool(QThread::Priority priority); + +template +auto asyncRun(QThreadPool *threadPool, QThread::Priority priority, + Function &&function, Args &&...args) +{ + QThreadPool *pool = threadPool ? threadPool : asyncThreadPool(priority); + return QtConcurrent::run(pool, std::forward(function), std::forward(args)...); +} + +template +auto asyncRun(QThread::Priority priority, Function &&function, Args &&...args) +{ + return asyncRun(nullptr, priority, + std::forward(function), std::forward(args)...); +} + +template +auto asyncRun(QThreadPool *threadPool, Function &&function, Args &&...args) +{ + return asyncRun(threadPool, QThread::InheritPriority, + std::forward(function), std::forward(args)...); +} + +template +auto asyncRun(Function &&function, Args &&...args) +{ + return asyncRun(nullptr, QThread::InheritPriority, + std::forward(function), std::forward(args)...); +} + +/*! + Adds a handler for when a result is ready. + This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions + or create a QFutureWatcher already for other reasons. +*/ +template +const QFuture &onResultReady(const QFuture &future, R *receiver, void(R::*member)(const T &)) +{ + auto watcher = new QFutureWatcher(); + QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); + QObject::connect(watcher, &QFutureWatcherBase::resultReadyAt, receiver, [=](int index) { + (receiver->*member)(watcher->future().resultAt(index)); + }); + watcher->setFuture(future); + return future; +} + +/*! + Adds a handler for when a result is ready. The guard object determines the lifetime of + the connection. + This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions + or create a QFutureWatcher already for other reasons. +*/ +template +const QFuture &onResultReady(const QFuture &future, QObject *guard, Function f) +{ + auto watcher = new QFutureWatcher(); + QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); + QObject::connect(watcher, &QFutureWatcherBase::resultReadyAt, guard, [f, watcher](int index) { + f(watcher->future().resultAt(index)); + }); + watcher->setFuture(future); + return future; +} + +/*! + Adds a handler for when the future is finished. + This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions + or create a QFutureWatcher already for other reasons. +*/ +template +const QFuture &onFinished(const QFuture &future, + R *receiver, void (R::*member)(const QFuture &)) +{ + auto watcher = new QFutureWatcher(); + QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); + QObject::connect(watcher, &QFutureWatcherBase::finished, receiver, + [=] { (receiver->*member)(watcher->future()); }); + watcher->setFuture(future); + return future; +} + +/*! + Adds a handler for when the future is finished. The guard object determines the lifetime of + the connection. + This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions + or create a QFutureWatcher already for other reasons. +*/ +template +const QFuture &onFinished(const QFuture &future, QObject *guard, Function f) +{ + auto watcher = new QFutureWatcher(); + QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); + QObject::connect(watcher, &QFutureWatcherBase::finished, guard, [f, watcher] { + f(watcher->future()); + }); + watcher->setFuture(future); + return future; +} + +class QTCREATOR_UTILS_EXPORT AsyncBase : public QObject +{ + Q_OBJECT + +signals: + void started(); + void done(); + void resultReadyAt(int index); +}; + +template +class Async : public AsyncBase +{ +public: + Async() { + connect(&m_watcher, &QFutureWatcherBase::finished, this, &AsyncBase::done); + connect(&m_watcher, &QFutureWatcherBase::resultReadyAt, this, &AsyncBase::resultReadyAt); + } + ~Async() + { + if (isDone()) + return; + + m_watcher.cancel(); + if (!m_synchronizer) + m_watcher.waitForFinished(); + } + + template + void setConcurrentCallData(Function &&function, Args &&...args) + { + return wrapConcurrent(std::forward(function), std::forward(args)...); + } + + void setFutureSynchronizer(FutureSynchronizer *synchorizer) { m_synchronizer = synchorizer; } + void setThreadPool(QThreadPool *pool) { m_threadPool = pool; } + void setPriority(QThread::Priority priority) { m_priority = priority; } + + void start() + { + QTC_ASSERT(m_startHandler, qWarning("No start handler specified."); return); + m_watcher.setFuture(m_startHandler()); + emit started(); + if (m_synchronizer) + m_synchronizer->addFuture(m_watcher.future()); + } + + bool isDone() const { return m_watcher.isFinished(); } + bool isCanceled() const { return m_watcher.isCanceled(); } + + QFuture future() const { return m_watcher.future(); } + ResultType result() const { return m_watcher.result(); } + ResultType resultAt(int index) const { return m_watcher.resultAt(index); } + QList results() const { return future().results(); } + bool isResultAvailable() const { return future().resultCount(); } + +private: + template + void wrapConcurrent(Function &&function, Args &&...args) + { + m_startHandler = [=] { + return asyncRun(m_threadPool, m_priority, function, args...); + }; + } + + template + void wrapConcurrent(std::reference_wrapper &&wrapper, Args &&...args) + { + m_startHandler = [=] { + return asyncRun(m_threadPool, m_priority, std::forward(wrapper.get()), + args...); + }; + } + + using StartHandler = std::function()>; + StartHandler m_startHandler; + FutureSynchronizer *m_synchronizer = nullptr; + QThreadPool *m_threadPool = nullptr; + QThread::Priority m_priority = QThread::InheritPriority; + QFutureWatcher m_watcher; +}; + +template +class AsyncTaskAdapter : public Tasking::TaskAdapter> +{ +public: + AsyncTaskAdapter() { + this->connect(this->task(), &AsyncBase::done, this, [this] { + emit this->done(!this->task()->isCanceled()); + }); + } + void start() final { this->task()->start(); } +}; + +} // namespace Utils + +TASKING_DECLARE_TEMPLATE_TASK(AsyncTask, Utils::AsyncTaskAdapter); diff --git a/src/libs/utils/asynctask.h b/src/libs/utils/asynctask.h deleted file mode 100644 index 1a6ae7d1b6a..00000000000 --- a/src/libs/utils/asynctask.h +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "utils_global.h" - -#include "futuresynchronizer.h" -#include "qtcassert.h" -#include "runextensions.h" -#include "tasktree.h" - -#include - -namespace Utils { - -class QTCREATOR_UTILS_EXPORT AsyncTaskBase : public QObject -{ - Q_OBJECT - -signals: - void started(); - void done(); -}; - -template -class AsyncTask : public AsyncTaskBase -{ -public: - AsyncTask() { connect(&m_watcher, &QFutureWatcherBase::finished, this, &AsyncTaskBase::done); } - ~AsyncTask() - { - if (isDone()) - return; - - m_watcher.cancel(); - if (!m_synchronizer) - m_watcher.waitForFinished(); - } - - using StartHandler = std::function()>; - - template - void setAsyncCallData(const Function &function, const Args &...args) - { - m_startHandler = [=] { - return Utils::runAsync(m_threadPool, m_priority, function, args...); - }; - } - void setFutureSynchronizer(FutureSynchronizer *synchorizer) { m_synchronizer = synchorizer; } - void setThreadPool(QThreadPool *pool) { m_threadPool = pool; } - void setPriority(QThread::Priority priority) { m_priority = priority; } - - void start() - { - QTC_ASSERT(m_startHandler, qWarning("No start handler specified."); return); - m_watcher.setFuture(m_startHandler()); - emit started(); - if (m_synchronizer) - m_synchronizer->addFuture(m_watcher.future()); - } - - bool isDone() const { return m_watcher.isFinished(); } - bool isCanceled() const { return m_watcher.isCanceled(); } - - QFuture future() const { return m_watcher.future(); } - ResultType result() const { return m_watcher.result(); } - QList results() const { return future().results(); } - bool isResultAvailable() const { return future().resultCount(); } - -private: - StartHandler m_startHandler; - FutureSynchronizer *m_synchronizer = nullptr; - QThreadPool *m_threadPool = nullptr; - QThread::Priority m_priority = QThread::InheritPriority; - QFutureWatcher m_watcher; -}; - -template -class AsyncTaskAdapter : public Tasking::TaskAdapter> -{ -public: - AsyncTaskAdapter() { - this->connect(this->task(), &AsyncTaskBase::done, this, [this] { - emit this->done(!this->task()->isCanceled()); - }); - } - void start() final { this->task()->start(); } -}; - -} // namespace Utils - -QTC_DECLARE_CUSTOM_TEMPLATE_TASK(Async, AsyncTaskAdapter); diff --git a/src/libs/utils/buildablehelperlibrary.cpp b/src/libs/utils/buildablehelperlibrary.cpp index eac24cfdba2..cafd5b0452c 100644 --- a/src/libs/utils/buildablehelperlibrary.cpp +++ b/src/libs/utils/buildablehelperlibrary.cpp @@ -4,7 +4,7 @@ #include "buildablehelperlibrary.h" #include "environment.h" #include "hostosinfo.h" -#include "qtcprocess.h" +#include "process.h" #include #include @@ -21,7 +21,7 @@ bool BuildableHelperLibrary::isQtChooser(const FilePath &filePath) FilePath BuildableHelperLibrary::qtChooserToQmakePath(const FilePath &qtChooser) { const QString toolDir = QLatin1String("QTTOOLDIR=\""); - QtcProcess proc; + Process proc; proc.setTimeoutS(1); proc.setCommand({qtChooser, {"-print-env"}}); proc.runBlocking(); @@ -103,7 +103,7 @@ QString BuildableHelperLibrary::qtVersionForQMake(const FilePath &qmakePath) if (qmakePath.isEmpty()) return QString(); - QtcProcess qmake; + Process qmake; qmake.setTimeoutS(5); qmake.setCommand({qmakePath, {"--version"}}); qmake.runBlocking(); diff --git a/src/libs/utils/checkablemessagebox.cpp b/src/libs/utils/checkablemessagebox.cpp index 4420fc66cbc..d3ede7d4e7e 100644 --- a/src/libs/utils/checkablemessagebox.cpp +++ b/src/libs/utils/checkablemessagebox.cpp @@ -3,6 +3,7 @@ #include "checkablemessagebox.h" +#include "hostosinfo.h" #include "qtcassert.h" #include "utilstr.h" @@ -17,10 +18,11 @@ /*! \class Utils::CheckableMessageBox + \inmodule QtCreator \brief The CheckableMessageBox class implements a message box suitable for - questions with a - "Do not ask me again" checkbox. + questions with a \uicontrol {Do not ask again} or \uicontrol {Do not show again} + checkbox. Emulates the QMessageBox API with static conveniences. The message label can open external URLs. @@ -30,435 +32,155 @@ static const char kDoNotAskAgainKey[] = "DoNotAskAgain"; namespace Utils { -class CheckableMessageBoxPrivate +static QSettings *theSettings; + +static QMessageBox::StandardButton exec( + QWidget *parent, + QMessageBox::Icon icon, + const QString &title, + const QString &text, + const CheckableDecider &decider, + QMessageBox::StandardButtons buttons, + QMessageBox::StandardButton defaultButton, + QMessageBox::StandardButton acceptButton, + QMap buttonTextOverrides, + const QString &msg) { -public: - CheckableMessageBoxPrivate(QDialog *q) - { - QSizePolicy sizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); + QMessageBox msgBox(parent); + msgBox.setWindowTitle(title); + msgBox.setIcon(icon); + msgBox.setText(text); + msgBox.setTextFormat(Qt::RichText); + msgBox.setTextInteractionFlags(Qt::LinksAccessibleByKeyboard | Qt::LinksAccessibleByMouse); - pixmapLabel = new QLabel(q); - sizePolicy.setHorizontalStretch(0); - sizePolicy.setVerticalStretch(0); - sizePolicy.setHeightForWidth(pixmapLabel->sizePolicy().hasHeightForWidth()); - pixmapLabel->setSizePolicy(sizePolicy); - pixmapLabel->setVisible(false); - pixmapLabel->setFocusPolicy(Qt::NoFocus); - - auto pixmapSpacer = - new QSpacerItem(0, 5, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); - - messageLabel = new QLabel(q); - messageLabel->setMinimumSize(QSize(300, 0)); - messageLabel->setWordWrap(true); - messageLabel->setOpenExternalLinks(true); - messageLabel->setTextInteractionFlags(Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse); - messageLabel->setFocusPolicy(Qt::NoFocus); - messageLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); - - checkBox = new QCheckBox(q); - checkBox->setText(Tr::tr("Do not ask again")); - - const QString showText = Tr::tr("Show Details..."); - detailsButton = new QPushButton(showText, q); - detailsButton->setAutoDefault(false); - detailsButton->hide(); - detailsText = new QTextEdit(q); - detailsText->hide(); - QObject::connect(detailsButton, &QPushButton::clicked, detailsText, [this, showText] { - detailsText->setVisible(!detailsText->isVisible()); - detailsButton->setText( - detailsText->isVisible() ? Tr::tr("Hide Details...") : showText); - }); - - buttonBox = new QDialogButtonBox(q); - buttonBox->setOrientation(Qt::Horizontal); - buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); - - auto verticalLayout = new QVBoxLayout(); - verticalLayout->addWidget(pixmapLabel); - verticalLayout->addItem(pixmapSpacer); - - auto horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->addLayout(verticalLayout); - horizontalLayout_2->addWidget(messageLabel, 10); - - auto horizontalLayout = new QHBoxLayout(); - horizontalLayout->addWidget(checkBox); - horizontalLayout->addStretch(10); - - auto detailsButtonLayout = new QHBoxLayout; - detailsButtonLayout->addWidget(detailsButton); - detailsButtonLayout->addStretch(10); - - auto verticalLayout_2 = new QVBoxLayout(q); - verticalLayout_2->addLayout(horizontalLayout_2); - verticalLayout_2->addLayout(horizontalLayout); - verticalLayout_2->addLayout(detailsButtonLayout); - verticalLayout_2->addWidget(detailsText, 10); - verticalLayout_2->addStretch(1); - verticalLayout_2->addWidget(buttonBox); - } - - QLabel *pixmapLabel = nullptr; - QLabel *messageLabel = nullptr; - QCheckBox *checkBox = nullptr; - QDialogButtonBox *buttonBox = nullptr; - QAbstractButton *clickedButton = nullptr; - QPushButton *detailsButton = nullptr; - QTextEdit *detailsText = nullptr; - QMessageBox::Icon icon = QMessageBox::NoIcon; -}; - -CheckableMessageBox::CheckableMessageBox(QWidget *parent) : - QDialog(parent), - d(new CheckableMessageBoxPrivate(this)) -{ - setModal(true); - connect(d->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(d->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - connect(d->buttonBox, &QDialogButtonBox::clicked, - this, [this](QAbstractButton *b) { d->clickedButton = b; }); -} - -CheckableMessageBox::~CheckableMessageBox() -{ - delete d; -} - -QAbstractButton *CheckableMessageBox::clickedButton() const -{ - return d->clickedButton; -} - -QDialogButtonBox::StandardButton CheckableMessageBox::clickedStandardButton() const -{ - if (d->clickedButton) - return d->buttonBox->standardButton(d->clickedButton); - return QDialogButtonBox::NoButton; -} - -QString CheckableMessageBox::text() const -{ - return d->messageLabel->text(); -} - -void CheckableMessageBox::setText(const QString &t) -{ - d->messageLabel->setText(t); -} - -QMessageBox::Icon CheckableMessageBox::icon() const -{ - return d->icon; -} - -// See QMessageBoxPrivate::standardIcon -static QPixmap pixmapForIcon(QMessageBox::Icon icon, QWidget *w) -{ - const QStyle *style = w ? w->style() : QApplication::style(); - const int iconSize = style->pixelMetric(QStyle::PM_MessageBoxIconSize, nullptr, w); - QIcon tmpIcon; - switch (icon) { - case QMessageBox::Information: - tmpIcon = style->standardIcon(QStyle::SP_MessageBoxInformation, nullptr, w); - break; - case QMessageBox::Warning: - tmpIcon = style->standardIcon(QStyle::SP_MessageBoxWarning, nullptr, w); - break; - case QMessageBox::Critical: - tmpIcon = style->standardIcon(QStyle::SP_MessageBoxCritical, nullptr, w); - break; - case QMessageBox::Question: - tmpIcon = style->standardIcon(QStyle::SP_MessageBoxQuestion, nullptr, w); - break; - default: - break; - } - if (!tmpIcon.isNull()) { - QWindow *window = nullptr; - if (w) { - window = w->windowHandle(); - if (!window) { - if (const QWidget *nativeParent = w->nativeParentWidget()) - window = nativeParent->windowHandle(); - } + if (HostOsInfo::isMacHost()) { + // Message boxes on macOS cannot display links. + // If the message contains a link, we need to disable native dialogs. + if (text.contains("= QT_VERSION_CHECK(6, 6, 0) + msgBox.setOptions(QMessageBox::Option::DontUseNativeDialog); +#endif } - return tmpIcon.pixmap(window, QSize(iconSize, iconSize)); } - return QPixmap(); -} -void CheckableMessageBox::setIcon(QMessageBox::Icon icon) -{ - d->icon = icon; - const QPixmap pixmap = pixmapForIcon(icon, this); - d->pixmapLabel->setPixmap(pixmap); - d->pixmapLabel->setVisible(!pixmap.isNull()); -} + if (decider.shouldAskAgain) { + if (!decider.shouldAskAgain()) + return acceptButton; -bool CheckableMessageBox::isChecked() const -{ - return d->checkBox->isChecked(); -} - -void CheckableMessageBox::setChecked(bool s) -{ - d->checkBox->setChecked(s); -} - -QString CheckableMessageBox::checkBoxText() const -{ - return d->checkBox->text(); -} - -void CheckableMessageBox::setCheckBoxText(const QString &t) -{ - d->checkBox->setText(t); -} - -bool CheckableMessageBox::isCheckBoxVisible() const -{ - return d->checkBox->isVisible(); -} - -void CheckableMessageBox::setCheckBoxVisible(bool v) -{ - d->checkBox->setVisible(v); -} - -QString CheckableMessageBox::detailedText() const -{ - return d->detailsText->toPlainText(); -} - -void CheckableMessageBox::setDetailedText(const QString &text) -{ - d->detailsText->setText(text); - if (!text.isEmpty()) - d->detailsButton->setVisible(true); -} - -QDialogButtonBox::StandardButtons CheckableMessageBox::standardButtons() const -{ - return d->buttonBox->standardButtons(); -} - -void CheckableMessageBox::setStandardButtons(QDialogButtonBox::StandardButtons s) -{ - d->buttonBox->setStandardButtons(s); -} - -QPushButton *CheckableMessageBox::button(QDialogButtonBox::StandardButton b) const -{ - return d->buttonBox->button(b); -} - -QPushButton *CheckableMessageBox::addButton(const QString &text, QDialogButtonBox::ButtonRole role) -{ - return d->buttonBox->addButton(text, role); -} - -QDialogButtonBox::StandardButton CheckableMessageBox::defaultButton() const -{ - const QList buttons = d->buttonBox->buttons(); - for (QAbstractButton *b : buttons) - if (auto *pb = qobject_cast(b)) - if (pb->isDefault()) - return d->buttonBox->standardButton(pb); - return QDialogButtonBox::NoButton; -} - -void CheckableMessageBox::setDefaultButton(QDialogButtonBox::StandardButton s) -{ - if (QPushButton *b = d->buttonBox->button(s)) { - b->setDefault(true); - b->setFocus(); + msgBox.setCheckBox(new QCheckBox); + msgBox.checkBox()->setChecked(false); + msgBox.checkBox()->setText(msg); } + + msgBox.setStandardButtons(buttons); + msgBox.setDefaultButton(defaultButton); + for (auto it = buttonTextOverrides.constBegin(); it != buttonTextOverrides.constEnd(); ++it) + msgBox.button(it.key())->setText(it.value()); + msgBox.exec(); + + QMessageBox::StandardButton clickedBtn = msgBox.standardButton(msgBox.clickedButton()); + + if (decider.doNotAskAgain && msgBox.checkBox()->isChecked() + && (acceptButton == QMessageBox::NoButton || clickedBtn == acceptButton)) + decider.doNotAskAgain(); + return clickedBtn; } -QDialogButtonBox::StandardButton -CheckableMessageBox::question(QWidget *parent, - const QString &title, - const QString &question, - const QString &checkBoxText, - bool *checkBoxSetting, - QDialogButtonBox::StandardButtons buttons, - QDialogButtonBox::StandardButton defaultButton) +CheckableDecider::CheckableDecider(const QString &settingsSubKey) { - CheckableMessageBox mb(parent); - mb.setWindowTitle(title); - mb.setIcon(QMessageBox::Question); - mb.setText(question); - mb.setCheckBoxText(checkBoxText); - mb.setChecked(*checkBoxSetting); - mb.setStandardButtons(buttons); - mb.setDefaultButton(defaultButton); - mb.exec(); - *checkBoxSetting = mb.isChecked(); - return mb.clickedStandardButton(); + QTC_ASSERT(theSettings, return); + shouldAskAgain = [settingsSubKey] { + theSettings->beginGroup(QLatin1String(kDoNotAskAgainKey)); + bool shouldNotAsk = theSettings->value(settingsSubKey, false).toBool(); + theSettings->endGroup(); + return !shouldNotAsk; + }; + doNotAskAgain = [settingsSubKey] { + theSettings->beginGroup(QLatin1String(kDoNotAskAgainKey)); + theSettings->setValue(settingsSubKey, true); + theSettings->endGroup(); + }; } -QDialogButtonBox::StandardButton -CheckableMessageBox::information(QWidget *parent, - const QString &title, - const QString &text, - const QString &checkBoxText, - bool *checkBoxSetting, - QDialogButtonBox::StandardButtons buttons, - QDialogButtonBox::StandardButton defaultButton) +CheckableDecider::CheckableDecider(bool *storage) { - CheckableMessageBox mb(parent); - mb.setWindowTitle(title); - mb.setIcon(QMessageBox::Information); - mb.setText(text); - mb.setCheckBoxText(checkBoxText); - mb.setChecked(*checkBoxSetting); - mb.setStandardButtons(buttons); - mb.setDefaultButton(defaultButton); - mb.exec(); - *checkBoxSetting = mb.isChecked(); - return mb.clickedStandardButton(); + shouldAskAgain = [storage] { return !*storage; }; + doNotAskAgain = [storage] { *storage = true; }; } -QMessageBox::StandardButton CheckableMessageBox::dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton db) +QMessageBox::StandardButton CheckableMessageBox::question( + QWidget *parent, + const QString &title, + const QString &question, + const CheckableDecider &decider, + QMessageBox::StandardButtons buttons, + QMessageBox::StandardButton defaultButton, + QMessageBox::StandardButton acceptButton, + QMap buttonTextOverrides, + const QString &msg) { - return static_cast(int(db)); + return exec(parent, + QMessageBox::Question, + title, + question, + decider, + buttons, + defaultButton, + acceptButton, + buttonTextOverrides, + msg.isEmpty() ? msgDoNotAskAgain() : msg); } -bool CheckableMessageBox::shouldAskAgain(QSettings *settings, const QString &settingsSubKey) +QMessageBox::StandardButton CheckableMessageBox::information( + QWidget *parent, + const QString &title, + const QString &text, + const CheckableDecider &decider, + QMessageBox::StandardButtons buttons, + QMessageBox::StandardButton defaultButton, + QMap buttonTextOverrides, + const QString &msg) { - if (QTC_GUARD(settings)) { - settings->beginGroup(QLatin1String(kDoNotAskAgainKey)); - bool shouldNotAsk = settings->value(settingsSubKey, false).toBool(); - settings->endGroup(); - if (shouldNotAsk) - return false; - } - return true; -} - -enum DoNotAskAgainType{Question, Information}; - -void initDoNotAskAgainMessageBox(CheckableMessageBox &messageBox, const QString &title, - const QString &text, QDialogButtonBox::StandardButtons buttons, - QDialogButtonBox::StandardButton defaultButton, - DoNotAskAgainType type) -{ - messageBox.setWindowTitle(title); - messageBox.setIcon(type == Information ? QMessageBox::Information : QMessageBox::Question); - messageBox.setText(text); - messageBox.setCheckBoxVisible(true); - messageBox.setCheckBoxText(type == Information ? CheckableMessageBox::msgDoNotShowAgain() - : CheckableMessageBox::msgDoNotAskAgain()); - messageBox.setChecked(false); - messageBox.setStandardButtons(buttons); - messageBox.setDefaultButton(defaultButton); -} - -void CheckableMessageBox::doNotAskAgain(QSettings *settings, const QString &settingsSubKey) -{ - if (!settings) - return; - - settings->beginGroup(QLatin1String(kDoNotAskAgainKey)); - settings->setValue(settingsSubKey, true); - settings->endGroup(); + return exec(parent, + QMessageBox::Information, + title, + text, + decider, + buttons, + defaultButton, + QMessageBox::NoButton, + buttonTextOverrides, + msg.isEmpty() ? msgDoNotShowAgain() : msg); } /*! - Shows a message box with given \a title and \a text, and a \gui {Do not ask again} check box. - If the user checks the check box and accepts the dialog with the \a acceptButton, - further invocations of this function with the same \a settings and \a settingsSubKey will not - show the dialog, but instantly return \a acceptButton. - - Returns the clicked button, or QDialogButtonBox::NoButton if the user rejects the dialog - with the escape key, or \a acceptButton if the dialog is suppressed. -*/ -QDialogButtonBox::StandardButton -CheckableMessageBox::doNotAskAgainQuestion(QWidget *parent, const QString &title, - const QString &text, QSettings *settings, - const QString &settingsSubKey, - QDialogButtonBox::StandardButtons buttons, - QDialogButtonBox::StandardButton defaultButton, - QDialogButtonBox::StandardButton acceptButton) - -{ - if (!shouldAskAgain(settings, settingsSubKey)) - return acceptButton; - - CheckableMessageBox messageBox(parent); - initDoNotAskAgainMessageBox(messageBox, title, text, buttons, defaultButton, Question); - messageBox.exec(); - if (messageBox.isChecked() && (messageBox.clickedStandardButton() == acceptButton)) - doNotAskAgain(settings, settingsSubKey); - - return messageBox.clickedStandardButton(); -} - -/*! - Shows a message box with given \a title and \a text, and a \gui {Do not show again} check box. - If the user checks the check box and quits the dialog, further invocations of this - function with the same \a settings and \a settingsSubKey will not show the dialog, but instantly return. - - Returns the clicked button, or QDialogButtonBox::NoButton if the user rejects the dialog - with the escape key, or \a defaultButton if the dialog is suppressed. -*/ -QDialogButtonBox::StandardButton -CheckableMessageBox::doNotShowAgainInformation(QWidget *parent, const QString &title, - const QString &text, QSettings *settings, - const QString &settingsSubKey, - QDialogButtonBox::StandardButtons buttons, - QDialogButtonBox::StandardButton defaultButton) - -{ - if (!shouldAskAgain(settings, settingsSubKey)) - return defaultButton; - - CheckableMessageBox messageBox(parent); - initDoNotAskAgainMessageBox(messageBox, title, text, buttons, defaultButton, Information); - messageBox.exec(); - if (messageBox.isChecked()) - doNotAskAgain(settings, settingsSubKey); - - return messageBox.clickedStandardButton(); -} - -/*! - Resets all suppression settings for doNotAskAgainQuestion() found in \a settings, + Resets all suppression settings for doNotAskAgainQuestion() so all these message boxes are shown again. */ -void CheckableMessageBox::resetAllDoNotAskAgainQuestions(QSettings *settings) +void CheckableMessageBox::resetAllDoNotAskAgainQuestions() { - QTC_ASSERT(settings, return); - settings->beginGroup(QLatin1String(kDoNotAskAgainKey)); - settings->remove(QString()); - settings->endGroup(); + QTC_ASSERT(theSettings, return); + theSettings->beginGroup(QLatin1String(kDoNotAskAgainKey)); + theSettings->remove(QString()); + theSettings->endGroup(); } /*! Returns whether any message boxes from doNotAskAgainQuestion() are suppressed - in the \a settings. + in the settings. */ -bool CheckableMessageBox::hasSuppressedQuestions(QSettings *settings) +bool CheckableMessageBox::hasSuppressedQuestions() { - QTC_ASSERT(settings, return false); - bool hasSuppressed = false; - settings->beginGroup(QLatin1String(kDoNotAskAgainKey)); - const QStringList childKeys = settings->childKeys(); - for (const QString &subKey : childKeys) { - if (settings->value(subKey, false).toBool()) { - hasSuppressed = true; - break; - } - } - settings->endGroup(); + QTC_ASSERT(theSettings, return false); + theSettings->beginGroup(QLatin1String(kDoNotAskAgainKey)); + const bool hasSuppressed = !theSettings->childKeys().isEmpty() + || !theSettings->childGroups().isEmpty(); + theSettings->endGroup(); return hasSuppressed; } /*! - Returns the standard \gui {Do not ask again} check box text. - \sa doNotAskAgainQuestion() + Returns the standard \uicontrol {Do not ask again} check box text. */ QString CheckableMessageBox::msgDoNotAskAgain() { @@ -466,12 +188,16 @@ QString CheckableMessageBox::msgDoNotAskAgain() } /*! - Returns the standard \gui {Do not show again} check box text. - \sa doNotShowAgainInformation() + Returns the standard \uicontrol {Do not show again} check box text. */ QString CheckableMessageBox::msgDoNotShowAgain() { return Tr::tr("Do not &show again"); } +void CheckableMessageBox::initialize(QSettings *settings) +{ + theSettings = settings; +} + } // namespace Utils diff --git a/src/libs/utils/checkablemessagebox.h b/src/libs/utils/checkablemessagebox.h index eb80e15eac5..297ff22b623 100644 --- a/src/libs/utils/checkablemessagebox.h +++ b/src/libs/utils/checkablemessagebox.h @@ -5,7 +5,7 @@ #include "utils_global.h" -#include +#include #include QT_BEGIN_NAMESPACE @@ -14,102 +14,51 @@ QT_END_NAMESPACE namespace Utils { -class CheckableMessageBoxPrivate; - -class QTCREATOR_UTILS_EXPORT CheckableMessageBox : public QDialog +class QTCREATOR_UTILS_EXPORT CheckableDecider { - Q_OBJECT - Q_PROPERTY(QString text READ text WRITE setText) - Q_PROPERTY(QMessageBox::Icon icon READ icon WRITE setIcon) - Q_PROPERTY(bool isChecked READ isChecked WRITE setChecked) - Q_PROPERTY(QString checkBoxText READ checkBoxText WRITE setCheckBoxText) - Q_PROPERTY(QDialogButtonBox::StandardButtons buttons READ standardButtons WRITE setStandardButtons) - Q_PROPERTY(QDialogButtonBox::StandardButton defaultButton READ defaultButton WRITE setDefaultButton) - public: - explicit CheckableMessageBox(QWidget *parent); - ~CheckableMessageBox() override; + CheckableDecider() = default; + CheckableDecider(const QString &settingsSubKey); + CheckableDecider(bool *doNotAskAgain); + CheckableDecider(const std::function &should, const std::function &doNot) + : shouldAskAgain(should), doNotAskAgain(doNot) + {} - static QDialogButtonBox::StandardButton - question(QWidget *parent, - const QString &title, - const QString &question, - const QString &checkBoxText, - bool *checkBoxSetting, - QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Yes|QDialogButtonBox::No, - QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::No); + std::function shouldAskAgain; + std::function doNotAskAgain; +}; - static QDialogButtonBox::StandardButton - information(QWidget *parent, - const QString &title, - const QString &text, - const QString &checkBoxText, - bool *checkBoxSetting, - QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Ok, - QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::NoButton); +class QTCREATOR_UTILS_EXPORT CheckableMessageBox +{ +public: + static QMessageBox::StandardButton question( + QWidget *parent, + const QString &title, + const QString &question, + const CheckableDecider &decider, + QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No, + QMessageBox::StandardButton defaultButton = QMessageBox::No, + QMessageBox::StandardButton acceptButton = QMessageBox::Yes, + QMap buttonTextOverrides = {}, + const QString &msg = {}); - static QDialogButtonBox::StandardButton - doNotAskAgainQuestion(QWidget *parent, - const QString &title, - const QString &text, - QSettings *settings, - const QString &settingsSubKey, - QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Yes|QDialogButtonBox::No, - QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::No, - QDialogButtonBox::StandardButton acceptButton = QDialogButtonBox::Yes); - - static QDialogButtonBox::StandardButton - doNotShowAgainInformation(QWidget *parent, - const QString &title, - const QString &text, - QSettings *settings, - const QString &settingsSubKey, - QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Ok, - QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::NoButton); - - QString text() const; - void setText(const QString &); - - bool isChecked() const; - void setChecked(bool s); - - QString checkBoxText() const; - void setCheckBoxText(const QString &); - - bool isCheckBoxVisible() const; - void setCheckBoxVisible(bool); - - QString detailedText() const; - void setDetailedText(const QString &text); - - QDialogButtonBox::StandardButtons standardButtons() const; - void setStandardButtons(QDialogButtonBox::StandardButtons s); - QPushButton *button(QDialogButtonBox::StandardButton b) const; - QPushButton *addButton(const QString &text, QDialogButtonBox::ButtonRole role); - - QDialogButtonBox::StandardButton defaultButton() const; - void setDefaultButton(QDialogButtonBox::StandardButton s); - - QMessageBox::Icon icon() const; - void setIcon(QMessageBox::Icon icon); - - // Query the result - QAbstractButton *clickedButton() const; - QDialogButtonBox::StandardButton clickedStandardButton() const; - - // check and set "ask again" status - static bool shouldAskAgain(QSettings *settings, const QString &settingsSubKey); - static void doNotAskAgain(QSettings *settings, const QString &settingsSubKey); + static QMessageBox::StandardButton information( + QWidget *parent, + const QString &title, + const QString &text, + const CheckableDecider &decider, + QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton, + QMap buttonTextOverrides = {}, + const QString &msg = {}); // Conversion convenience - static QMessageBox::StandardButton dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton); - static void resetAllDoNotAskAgainQuestions(QSettings *settings); - static bool hasSuppressedQuestions(QSettings *settings); + static void resetAllDoNotAskAgainQuestions(); + static bool hasSuppressedQuestions(); static QString msgDoNotAskAgain(); static QString msgDoNotShowAgain(); -private: - CheckableMessageBoxPrivate *d; + static void initialize(QSettings *settings); }; } // namespace Utils diff --git a/src/libs/utils/clangutils.cpp b/src/libs/utils/clangutils.cpp index 3f2fb69909d..6cf265820e7 100644 --- a/src/libs/utils/clangutils.cpp +++ b/src/libs/utils/clangutils.cpp @@ -4,7 +4,7 @@ #include "clangutils.h" #include "filepath.h" -#include "qtcprocess.h" +#include "process.h" #include "utilstr.h" #include @@ -13,7 +13,7 @@ namespace Utils { static QVersionNumber getClangdVersion(const FilePath &clangdFilePath) { - QtcProcess clangdProc; + Process clangdProc; clangdProc.setCommand({clangdFilePath, {"--version"}}); clangdProc.runBlocking(); if (clangdProc.result() != ProcessResult::FinishedWithSuccess) @@ -45,6 +45,11 @@ QVersionNumber clangdVersion(const FilePath &clangd) bool checkClangdVersion(const FilePath &clangd, QString *error) { + if (clangd.isEmpty()) { + *error = Tr::tr("No clangd executable specified."); + return false; + } + const QVersionNumber version = clangdVersion(clangd); if (version >= minimumClangdVersion()) return true; diff --git a/src/libs/utils/classnamevalidatinglineedit.cpp b/src/libs/utils/classnamevalidatinglineedit.cpp index 01235d57e32..3515abef023 100644 --- a/src/libs/utils/classnamevalidatinglineedit.cpp +++ b/src/libs/utils/classnamevalidatinglineedit.cpp @@ -10,6 +10,7 @@ /*! \class Utils::ClassNameValidatingLineEdit + \inmodule QtCreator \brief The ClassNameValidatingLineEdit class implements a line edit that validates a C++ class name and emits a signal diff --git a/src/libs/utils/commandline.cpp b/src/libs/utils/commandline.cpp index 6f8d4a4f5d9..ba1cc4edc46 100644 --- a/src/libs/utils/commandline.cpp +++ b/src/libs/utils/commandline.cpp @@ -41,6 +41,7 @@ namespace Utils { /*! \class Utils::ProcessArgs + \inmodule QtCreator \brief The ProcessArgs class provides functionality for dealing with shell-quoted process arguments. @@ -195,6 +196,7 @@ static QStringList doSplitArgsWin(const QString &args, ProcessArgs::SplitError * } /*! + \internal Splits \a _args according to system shell word splitting and quoting rules. \section1 Unix @@ -217,7 +219,7 @@ static QStringList doSplitArgsWin(const QString &args, ProcessArgs::SplitError * If \a err is not NULL, stores a status code at the pointer target. For more information, see \l SplitError. - If \env is not NULL, performs variable substitution with the + If \a env is not NULL, performs variable substitution with the given environment. Returns a list of unquoted words or an empty list if an error occurred. @@ -253,7 +255,6 @@ static QStringList doSplitArgsWin(const QString &args, ProcessArgs::SplitError * \c{foo " bar}. */ - static QStringList splitArgsWin(const QString &_args, bool abortOnMeta, ProcessArgs::SplitError *err, const Environment *env, const QString *pwd) @@ -362,12 +363,12 @@ static QStringList splitArgsUnix(const QString &args, bool abortOnMeta, if (var == pwdName && pwd && !pwd->isEmpty()) { cret += *pwd; } else { - Environment::const_iterator vit = env->constFind(var); - if (vit == env->constEnd()) { + const Environment::FindResult res = env->find(var); + if (!res) { if (abortOnMeta) goto metaerr; // Assume this is a shell builtin } else { - cret += env->expandedValueForKey(env->key(vit)); + cret += env->expandedValueForKey(res->key); } } if (!braced) @@ -412,12 +413,12 @@ static QStringList splitArgsUnix(const QString &args, bool abortOnMeta, if (var == pwdName && pwd && !pwd->isEmpty()) { val = *pwd; } else { - Environment::const_iterator vit = env->constFind(var); - if (vit == env->constEnd()) { + const Environment::FindResult res = env->find(var); + if (!res) { if (abortOnMeta) goto metaerr; // Assume this is a shell builtin } else { - val = env->expandedValueForKey(env->key(vit)); + val = env->expandedValueForKey(res->key); } } for (int i = 0; i < val.length(); i++) { @@ -893,7 +894,7 @@ bool ProcessArgs::expandMacros(QString *cmd, AbstractMacroExpander *mx, OsType o break; case CrtClosed: // Two consecutive quotes make a literal quote - and - // still close quoting. See QtcProcess::quoteArg(). + // still close quoting. See Process::quoteArg(). crtState = CrtInWord; break; case CrtHadQuote: @@ -1398,6 +1399,7 @@ QString ProcessArgs::toString() const /*! \class Utils::CommandLine + \inmodule QtCreator \brief The CommandLine class represents a command line of a QProcess or similar utility. @@ -1409,6 +1411,8 @@ CommandLine::CommandLine(const FilePath &executable) : m_executable(executable) {} +CommandLine::~CommandLine() = default; + CommandLine::CommandLine(const FilePath &exe, const QStringList &args) : m_executable(exe) { @@ -1429,16 +1433,20 @@ CommandLine::CommandLine(const FilePath &exe, const QString &args, RawType) CommandLine CommandLine::fromUserInput(const QString &cmdline, MacroExpander *expander) { - CommandLine cmd; - const int pos = cmdline.indexOf(' '); - if (pos == -1) { - cmd.m_executable = FilePath::fromString(cmdline); - } else { - cmd.m_executable = FilePath::fromString(cmdline.left(pos)); - cmd.m_arguments = cmdline.right(cmdline.length() - pos - 1); - if (expander) - cmd.m_arguments = expander->expand(cmd.m_arguments); - } + if (cmdline.isEmpty()) + return {}; + + QString input = cmdline.trimmed(); + + QStringList result = ProcessArgs::splitArgs(cmdline, HostOsInfo::hostOs()); + + if (result.isEmpty()) + return {}; + + auto cmd = CommandLine(FilePath::fromUserInput(result.value(0)), result.mid(1)); + if (expander) + cmd.m_arguments = expander->expand(cmd.m_arguments); + return cmd; } diff --git a/src/libs/utils/commandline.h b/src/libs/utils/commandline.h index 23585adb8af..d7fc0a066be 100644 --- a/src/libs/utils/commandline.h +++ b/src/libs/utils/commandline.h @@ -55,7 +55,7 @@ public: //! Append already quoted arguments to a shell command static void addArgs(QString *args, const QString &inArgs); //! Split a shell command into separate arguments. - static QStringList splitArgs(const QString &cmd, OsType osType = HostOsInfo::hostOs(), + static QStringList splitArgs(const QString &cmd, OsType osType, bool abortOnMeta = false, SplitError *err = nullptr, const Environment *env = nullptr, const QString *pwd = nullptr); //! Safely replace the expandos in a shell command @@ -119,6 +119,8 @@ public: enum RawType { Raw }; CommandLine(); + ~CommandLine(); + explicit CommandLine(const FilePath &executable); CommandLine(const FilePath &exe, const QStringList &args); CommandLine(const FilePath &exe, const QStringList &args, OsType osType); diff --git a/src/libs/utils/completingtextedit.cpp b/src/libs/utils/completingtextedit.cpp index 66d1e7e49f5..0fbdd2068bc 100644 --- a/src/libs/utils/completingtextedit.cpp +++ b/src/libs/utils/completingtextedit.cpp @@ -13,7 +13,9 @@ static bool isEndOfWordChar(const QChar &c) return !c.isLetterOrNumber() && c.category() != QChar::Punctuation_Connector; } -/*! \class Utils::CompletingTextEdit +/*! + \class Utils::CompletingTextEdit + \inmodule QtCreator \brief The CompletingTextEdit class is a QTextEdit with auto-completion support. diff --git a/src/libs/utils/delegates.h b/src/libs/utils/delegates.h index 97f1c774c38..ff012152f5f 100644 --- a/src/libs/utils/delegates.h +++ b/src/libs/utils/delegates.h @@ -62,18 +62,14 @@ private: class QTCREATOR_UTILS_EXPORT CompleterDelegate : public QStyledItemDelegate { + Q_DISABLE_COPY_MOVE(CompleterDelegate) + public: CompleterDelegate(const QStringList &candidates, QObject *parent = nullptr); CompleterDelegate(QAbstractItemModel *model, QObject *parent = nullptr); CompleterDelegate(QCompleter *completer, QObject *parent = nullptr); ~CompleterDelegate() override; - CompleterDelegate(const CompleterDelegate &other) = delete; - CompleterDelegate(CompleterDelegate &&other) = delete; - - CompleterDelegate &operator=(const CompleterDelegate &other) = delete; - CompleterDelegate &operator=(CompleterDelegate &&other) = delete; - QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void setEditorData(QWidget *editor, const QModelIndex &index) const override; diff --git a/src/libs/utils/detailswidget.cpp b/src/libs/utils/detailswidget.cpp index 420ed11b40f..ec7a3d22ebb 100644 --- a/src/libs/utils/detailswidget.cpp +++ b/src/libs/utils/detailswidget.cpp @@ -20,6 +20,7 @@ /*! \class Utils::DetailsWidget + \inmodule QtCreator \brief The DetailsWidget class implements a button to expand a \e Details area. diff --git a/src/libs/utils/devicefileaccess.cpp b/src/libs/utils/devicefileaccess.cpp index 3d8354d7a0f..bd139d9b168 100644 --- a/src/libs/utils/devicefileaccess.cpp +++ b/src/libs/utils/devicefileaccess.cpp @@ -8,11 +8,12 @@ #include "environment.h" #include "expected.h" #include "hostosinfo.h" +#include "osspecificaspects.h" #include "qtcassert.h" #include "utilstr.h" #ifndef UTILS_STATIC_LIBRARY -#include "qtcprocess.h" +#include "process.h" #endif #include @@ -105,6 +106,13 @@ bool DeviceFileAccess::isSymLink(const FilePath &filePath) const return false; } +bool DeviceFileAccess::hasHardLinks(const FilePath &filePath) const +{ + Q_UNUSED(filePath) + QTC_CHECK(false); + return false; +} + bool DeviceFileAccess::ensureWritableDirectory(const FilePath &filePath) const { if (isWritableDirectory(filePath)) @@ -153,7 +161,7 @@ expected_str DeviceFileAccess::copyFile(const FilePath &filePath, const Fi Q_UNUSED(target) QTC_CHECK(false); return make_unexpected( - Tr::tr("copyFile is not implemented for \"%1\"").arg(filePath.toUserOutput())); + Tr::tr("copyFile is not implemented for \"%1\".").arg(filePath.toUserOutput())); } expected_str copyRecursively_fallback(const FilePath &src, const FilePath &target) @@ -189,25 +197,22 @@ expected_str DeviceFileAccess::copyRecursively(const FilePath &src, const FilePath &target) const { if (!src.isDir()) { - return make_unexpected(Tr::tr("Cannot copy from %1, it is not a directory.") - .arg(src.toUserOutput()) - .arg(target.toUserOutput())); + return make_unexpected( + Tr::tr("Cannot copy from \"%1\", it is not a directory.").arg(src.toUserOutput())); } if (!target.ensureWritableDir()) { - return make_unexpected(Tr::tr("Cannot copy %1 to %2, it is not a writable directory.") - .arg(src.toUserOutput()) - .arg(target.toUserOutput())); + return make_unexpected( + Tr::tr("Cannot copy \"%1\" to \"%2\", it is not a writable directory.") + .arg(src.toUserOutput()) + .arg(target.toUserOutput())); } #ifdef UTILS_STATIC_LIBRARY return copyRecursively_fallback(src, target); #else - const FilePath tar = FilePath::fromString("tar").onDevice(target); - const FilePath targetTar = tar.searchInPath(); - - const FilePath sTar = FilePath::fromString("tar").onDevice(src); - const FilePath sourceTar = sTar.searchInPath(); + const FilePath targetTar = target.withNewPath("tar").searchInPath(); + const FilePath sourceTar = src.withNewPath("tar").searchInPath(); const bool isSrcOrTargetQrc = src.toFSPathString().startsWith(":/") || target.toFSPathString().startsWith(":/"); @@ -215,13 +220,13 @@ expected_str DeviceFileAccess::copyRecursively(const FilePath &src, if (isSrcOrTargetQrc || !targetTar.isExecutableFile() || !sourceTar.isExecutableFile()) return copyRecursively_fallback(src, target); - QtcProcess srcProcess; - QtcProcess targetProcess; + Process srcProcess; + Process targetProcess; targetProcess.setProcessMode(ProcessMode::Writer); QObject::connect(&srcProcess, - &QtcProcess::readyReadStandardOutput, + &Process::readyReadStandardOutput, &targetProcess, [&srcProcess, &targetProcess]() { targetProcess.writeRaw(srcProcess.readAllRawStandardOutput()); @@ -268,12 +273,6 @@ bool DeviceFileAccess::renameFile(const FilePath &filePath, const FilePath &targ return false; } -OsType DeviceFileAccess::osType(const FilePath &filePath) const -{ - Q_UNUSED(filePath) - return OsTypeOther; -} - FilePath DeviceFileAccess::symLinkTarget(const FilePath &filePath) const { Q_UNUSED(filePath) @@ -306,7 +305,7 @@ expected_str DeviceFileAccess::fileContents(const FilePath &filePath Q_UNUSED(offset) QTC_CHECK(false); return make_unexpected( - Tr::tr("fileContents is not implemented for \"%1\"").arg(filePath.toUserOutput())); + Tr::tr("fileContents is not implemented for \"%1\".").arg(filePath.toUserOutput())); } expected_str DeviceFileAccess::writeFileContents(const FilePath &filePath, @@ -318,7 +317,7 @@ expected_str DeviceFileAccess::writeFileContents(const FilePath &filePat Q_UNUSED(offset) QTC_CHECK(false); return make_unexpected( - Tr::tr("writeFileContents is not implemented for \"%1\"").arg(filePath.toUserOutput())); + Tr::tr("writeFileContents is not implemented for \"%1\".").arg(filePath.toUserOutput())); } FilePathInfo DeviceFileAccess::filePathInfo(const FilePath &filePath) const @@ -379,35 +378,12 @@ std::optional DeviceFileAccess::refersToExecutableFile( return {}; } -void DeviceFileAccess::asyncFileContents(const FilePath &filePath, - const Continuation> &cont, - qint64 limit, - qint64 offset) const -{ - cont(fileContents(filePath, limit, offset)); -} - -void DeviceFileAccess::asyncWriteFileContents(const FilePath &filePath, - const Continuation> &cont, - const QByteArray &data, - qint64 offset) const -{ - cont(writeFileContents(filePath, data, offset)); -} - -void DeviceFileAccess::asyncCopyFile(const FilePath &filePath, - const Continuation> &cont, - const FilePath &target) const -{ - cont(copyFile(filePath, target)); -} - expected_str DeviceFileAccess::createTempFile(const FilePath &filePath) { Q_UNUSED(filePath) QTC_CHECK(false); - return make_unexpected(Tr::tr("createTempFile is not implemented for \"%1\"") - .arg(filePath.toUserOutput())); + return make_unexpected( + Tr::tr("createTempFile is not implemented for \"%1\".").arg(filePath.toUserOutput())); } @@ -506,6 +482,37 @@ bool DesktopDeviceFileAccess::isSymLink(const FilePath &filePath) const return fi.isSymLink(); } +bool DesktopDeviceFileAccess::hasHardLinks(const FilePath &filePath) const +{ +#ifdef Q_OS_UNIX + struct stat s + {}; + const int r = stat(filePath.absoluteFilePath().toString().toLocal8Bit().constData(), &s); + if (r == 0) { + // check for hardlinks because these would break without the atomic write implementation + if (s.st_nlink > 1) + return true; + } +#elif defined(Q_OS_WIN) + const HANDLE handle = CreateFile((wchar_t *) filePath.toUserOutput().utf16(), + 0, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + if (handle == INVALID_HANDLE_VALUE) + return false; + + FILE_STANDARD_INFO info; + if (GetFileInformationByHandleEx(handle, FileStandardInfo, &info, sizeof(info))) + return info.NumberOfLinks > 1; +#else + Q_UNUSED(filePath) +#endif + return false; +} + bool DesktopDeviceFileAccess::ensureWritableDirectory(const FilePath &filePath) const { const QFileInfo fi(filePath.path()); @@ -587,10 +594,13 @@ bool DesktopDeviceFileAccess::removeRecursively(const FilePath &filePath, QStrin expected_str DesktopDeviceFileAccess::copyFile(const FilePath &filePath, const FilePath &target) const { - if (QFile::copy(filePath.path(), target.path())) + QFile srcFile(filePath.path()); + + if (srcFile.copy(target.path())) return {}; - return make_unexpected(Tr::tr("Failed to copy file \"%1\" to \"%2\".") - .arg(filePath.toUserOutput(), target.toUserOutput())); + return make_unexpected( + Tr::tr("Failed to copy file \"%1\" to \"%2\": %3") + .arg(filePath.toUserOutput(), target.toUserOutput(), srcFile.errorString())); } bool DesktopDeviceFileAccess::renameFile(const FilePath &filePath, const FilePath &target) const @@ -662,10 +672,10 @@ expected_str DesktopDeviceFileAccess::fileContents(const FilePath &f const QString path = filePath.path(); QFile f(path); if (!f.exists()) - return make_unexpected(Tr::tr("File \"%1\" does not exist").arg(path)); + return make_unexpected(Tr::tr("File \"%1\" does not exist.").arg(path)); if (!f.open(QFile::ReadOnly)) - return make_unexpected(Tr::tr("Could not open File \"%1\"").arg(path)); + return make_unexpected(Tr::tr("Could not open File \"%1\".").arg(path)); if (offset != 0) f.seek(offset); @@ -690,14 +700,14 @@ expected_str DesktopDeviceFileAccess::writeFileContents(const FilePath & const bool isOpened = file.open(QFile::WriteOnly | QFile::Truncate); if (!isOpened) return make_unexpected( - Tr::tr("Could not open file \"%1\" for writing").arg(filePath.toUserOutput())); + Tr::tr("Could not open file \"%1\" for writing.").arg(filePath.toUserOutput())); if (offset != 0) file.seek(offset); qint64 res = file.write(data); if (res != data.size()) return make_unexpected( - Tr::tr("Could not write to file \"%1\" (only %2 of %3 bytes written)") + Tr::tr("Could not write to file \"%1\" (only %2 of %3 bytes written).") .arg(filePath.toUserOutput()) .arg(res) .arg(data.size())); @@ -708,12 +718,14 @@ expected_str DesktopDeviceFileAccess::createTempFile(const FilePath &f { QTemporaryFile file(filePath.path()); file.setAutoRemove(false); - if (!file.open()) - return make_unexpected(Tr::tr("Could not create temporary file in \"%1\" (%2)").arg(filePath.toUserOutput()).arg(file.errorString())); - return FilePath::fromString(file.fileName()).onDevice(filePath); + if (!file.open()) { + return make_unexpected(Tr::tr("Could not create temporary file in \"%1\" (%2).") + .arg(filePath.toUserOutput()) + .arg(file.errorString())); + } + return filePath.withNewPath(file.fileName()); } - QDateTime DesktopDeviceFileAccess::lastModified(const FilePath &filePath) const { return QFileInfo(filePath.path()).lastModified(); @@ -818,12 +830,6 @@ QByteArray DesktopDeviceFileAccess::fileId(const FilePath &filePath) const return result; } -OsType DesktopDeviceFileAccess::osType(const FilePath &filePath) const -{ - Q_UNUSED(filePath); - return HostOsInfo::hostOs(); -} - // UnixDeviceAccess UnixDeviceFileAccess::~UnixDeviceFileAccess() = default; @@ -882,6 +888,13 @@ bool UnixDeviceFileAccess::isSymLink(const FilePath &filePath) const return runInShellSuccess({"test", {"-h", path}, OsType::OsTypeLinux}); } +bool UnixDeviceFileAccess::hasHardLinks(const FilePath &filePath) const +{ + const QStringList args = statArgs(filePath, "%h", "%l"); + const RunResult result = runInShell({"stat", args, OsType::OsTypeLinux}); + return result.stdOut.toLongLong() > 1; +} + bool UnixDeviceFileAccess::ensureExistingFile(const FilePath &filePath) const { const QString path = filePath.path(); @@ -964,7 +977,7 @@ expected_str UnixDeviceFileAccess::fileContents(const FilePath &file #ifndef UTILS_STATIC_LIBRARY const FilePath dd = filePath.withNewPath("dd"); - QtcProcess p; + Process p; p.setCommand({dd, args, OsType::OsTypeLinux}); p.runBlocking(); if (p.exitCode() != 0) { @@ -1015,7 +1028,7 @@ expected_str UnixDeviceFileAccess::createTempFile(const FilePath &file .arg(filePath.toUserOutput(), QString::fromUtf8(result.stdErr))); } - return FilePath::fromString(QString::fromUtf8(result.stdOut.trimmed())).onDevice(filePath); + return filePath.withNewPath(QString::fromUtf8(result.stdOut.trimmed())); } // Manually create a temporary/unique file. @@ -1039,9 +1052,9 @@ expected_str UnixDeviceFileAccess::createTempFile(const FilePath &file for (QChar *it = firstX.base(); it != std::end(tmplate); ++it) { *it = chars[dist(*QRandomGenerator::global())]; } - newPath = FilePath::fromString(tmplate).onDevice(filePath); + newPath = filePath.withNewPath(tmplate); if (--maxTries == 0) { - return make_unexpected(Tr::tr("Failed creating temporary file \"%1\" (too many tries)") + return make_unexpected(Tr::tr("Failed creating temporary file \"%1\" (too many tries).") .arg(filePath.toUserOutput())); } } while (newPath.exists()); @@ -1054,12 +1067,6 @@ expected_str UnixDeviceFileAccess::createTempFile(const FilePath &file return newPath; } -OsType UnixDeviceFileAccess::osType(const FilePath &filePath) const -{ - Q_UNUSED(filePath) - return OsTypeLinux; -} - QDateTime UnixDeviceFileAccess::lastModified(const FilePath &filePath) const { const RunResult result = runInShell( @@ -1069,10 +1076,19 @@ QDateTime UnixDeviceFileAccess::lastModified(const FilePath &filePath) const return dt; } +QStringList UnixDeviceFileAccess::statArgs(const FilePath &filePath, + const QString &linuxFormat, + const QString &macFormat) const +{ + return (filePath.osType() == OsTypeMac ? QStringList{"-f", macFormat} : QStringList{"-c", linuxFormat}) + << "-L" << filePath.path(); +} + QFile::Permissions UnixDeviceFileAccess::permissions(const FilePath &filePath) const { - const RunResult result = runInShell( - {"stat", {"-L", "-c", "%a", filePath.path()}, OsType::OsTypeLinux}); + QStringList args = statArgs(filePath, "%a", "%p"); + + const RunResult result = runInShell({"stat", args, OsType::OsTypeLinux}); const uint bits = result.stdOut.toUInt(nullptr, 8); QFileDevice::Permissions perm = {}; #define BIT(n, p) \ @@ -1100,8 +1116,8 @@ bool UnixDeviceFileAccess::setPermissions(const FilePath &filePath, QFile::Permi qint64 UnixDeviceFileAccess::fileSize(const FilePath &filePath) const { - const RunResult result = runInShell( - {"stat", {"-L", "-c", "%s", filePath.path()}, OsType::OsTypeLinux}); + const QStringList args = statArgs(filePath, "%s", "%z"); + const RunResult result = runInShell({"stat", args, OsType::OsTypeLinux}); return result.stdOut.toLongLong(); } @@ -1113,8 +1129,9 @@ qint64 UnixDeviceFileAccess::bytesAvailable(const FilePath &filePath) const QByteArray UnixDeviceFileAccess::fileId(const FilePath &filePath) const { - const RunResult result = runInShell( - {"stat", {"-L", "-c", "%D:%i", filePath.path()}, OsType::OsTypeLinux}); + const QStringList args = statArgs(filePath, "%D:%i", "%d:%i"); + + const RunResult result = runInShell({"stat", args, OsType::OsTypeLinux}); if (result.exitCode != 0) return {}; @@ -1136,9 +1153,12 @@ FilePathInfo UnixDeviceFileAccess::filePathInfo(const FilePath &filePath) const return r; } - const RunResult stat = runInShell( - {"stat", {"-L", "-c", "%f %Y %s", filePath.path()}, OsType::OsTypeLinux}); - return FileUtils::filePathInfoFromTriple(QString::fromLatin1(stat.stdOut)); + + const QStringList args = statArgs(filePath, "%f %Y %s", "%p %m %z"); + + const RunResult stat = runInShell({"stat", args, OsType::OsTypeLinux}); + return FileUtils::filePathInfoFromTriple(QString::fromLatin1(stat.stdOut), + filePath.osType() == OsTypeMac ? 8 : 16); } // returns whether 'find' could be used. @@ -1152,8 +1172,13 @@ bool UnixDeviceFileAccess::iterateWithFind(const FilePath &filePath, // TODO: Using stat -L will always return the link target, not the link itself. // We may wan't to add the information that it is a link at some point. + + const QString statFormat = filePath.osType() == OsTypeMac + ? QLatin1String("-f \"%p %m %z\"") : QLatin1String("-c \"%f %Y %s\""); + if (callBack.index() == 1) - cmdLine.addArgs(R"(-exec echo -n \"{}\"" " \; -exec stat -L -c "%f %Y %s" "{}" \;)", + cmdLine.addArgs(QString(R"(-exec echo -n \"{}\"" " \; -exec stat -L %1 "{}" \;)") + .arg(statFormat), CommandLine::Raw); const RunResult result = runInShell(cmdLine); @@ -1175,23 +1200,26 @@ bool UnixDeviceFileAccess::iterateWithFind(const FilePath &filePath, if (entries.isEmpty()) return true; - const auto toFilePath = [&filePath, &callBack](const QString &entry) { - if (callBack.index() == 0) - return std::get<0>(callBack)(filePath.withNewPath(entry)); + const int modeBase = filePath.osType() == OsTypeMac ? 8 : 16; - const QString fileName = entry.mid(1, entry.lastIndexOf('\"') - 1); - const QString infos = entry.mid(fileName.length() + 3); + const auto toFilePath = + [&filePath, &callBack, modeBase](const QString &entry) { + if (callBack.index() == 0) + return std::get<0>(callBack)(filePath.withNewPath(entry)); - const FilePathInfo fi = FileUtils::filePathInfoFromTriple(infos); - if (!fi.fileFlags) - return IterationPolicy::Continue; + const QString fileName = entry.mid(1, entry.lastIndexOf('\"') - 1); + const QString infos = entry.mid(fileName.length() + 3); - const FilePath fp = filePath.withNewPath(fileName); - // Do not return the entry for the directory we are searching in. - if (fp.path() == filePath.path()) - return IterationPolicy::Continue; - return std::get<1>(callBack)(fp, fi); - }; + const FilePathInfo fi = FileUtils::filePathInfoFromTriple(infos, modeBase); + if (!fi.fileFlags) + return IterationPolicy::Continue; + + const FilePath fp = filePath.withNewPath(fileName); + // Do not return the entry for the directory we are searching in. + if (fp.path() == filePath.path()) + return IterationPolicy::Continue; + return std::get<1>(callBack)(fp, fi); + }; // Remove the first line, this can be the directory we are searching in. // as long as we do not specify "mindepth > 0" @@ -1210,7 +1238,7 @@ void UnixDeviceFileAccess::findUsingLs(const QString ¤t, const FileFilter &filter, QStringList *found) const { - const RunResult result = runInShell({"ls", {"-1", "-p", "--", current}, OsType::OsTypeLinux}); + const RunResult result = runInShell({"ls", {"-1", "-a", "-p", "--", current}, OsType::OsTypeLinux}); const QStringList entries = QString::fromUtf8(result.stdOut).split('\n', Qt::SkipEmptyParts); for (QString entry : entries) { const QChar last = entry.back(); @@ -1272,8 +1300,8 @@ void UnixDeviceFileAccess::iterateDirectory(const FilePath &filePath, if (m_tryUseFind) { if (iterateWithFind(filePath, filter, callBack)) return; - m_tryUseFind - = false; // remember the failure for the next time and use the 'ls' fallback below. + // Remember the failure for the next time and use the 'ls' fallback below. + m_tryUseFind = false; } // if we do not have find - use ls as fallback diff --git a/src/libs/utils/devicefileaccess.h b/src/libs/utils/devicefileaccess.h index 8fe8201e088..0f8282477f6 100644 --- a/src/libs/utils/devicefileaccess.h +++ b/src/libs/utils/devicefileaccess.h @@ -3,10 +3,13 @@ #pragma once +#include "hostosinfo.h" #include "utils_global.h" #include "fileutils.h" +class tst_unixdevicefileaccess; // For testing. + namespace Utils { // Base class including dummy implementation usable as fallback. @@ -19,6 +22,7 @@ public: protected: friend class FilePath; + friend class ::tst_unixdevicefileaccess; // For testing. virtual QString mapToDevicePath(const QString &hostPath) const; @@ -30,6 +34,7 @@ protected: virtual bool isFile(const FilePath &filePath) const; virtual bool isDirectory(const FilePath &filePath) const; virtual bool isSymLink(const FilePath &filePath) const; + virtual bool hasHardLinks(const FilePath &filePath) const; virtual bool ensureWritableDirectory(const FilePath &filePath) const; virtual bool ensureExistingFile(const FilePath &filePath) const; virtual bool createDirectory(const FilePath &filePath) const; @@ -41,7 +46,6 @@ protected: const FilePath &target) const; virtual bool renameFile(const FilePath &filePath, const FilePath &target) const; - virtual OsType osType(const FilePath &filePath) const; virtual FilePath symLinkTarget(const FilePath &filePath) const; virtual FilePathInfo filePathInfo(const FilePath &filePath) const; virtual QDateTime lastModified(const FilePath &filePath) const; @@ -68,20 +72,6 @@ protected: const QByteArray &data, qint64 offset) const; - virtual void asyncFileContents(const FilePath &filePath, - const Continuation> &cont, - qint64 limit, - qint64 offset) const; - - virtual void asyncWriteFileContents(const FilePath &filePath, - const Continuation> &cont, - const QByteArray &data, - qint64 offset) const; - - virtual void asyncCopyFile(const FilePath &filePath, - const Continuation> &cont, - const FilePath &target) const; - virtual expected_str createTempFile(const FilePath &filePath); }; @@ -101,6 +91,7 @@ protected: bool isFile(const FilePath &filePath) const override; bool isDirectory(const FilePath &filePath) const override; bool isSymLink(const FilePath &filePath) const override; + bool hasHardLinks(const FilePath &filePath) const override; bool ensureWritableDirectory(const FilePath &filePath) const override; bool ensureExistingFile(const FilePath &filePath) const override; bool createDirectory(const FilePath &filePath) const override; @@ -110,7 +101,6 @@ protected: expected_str copyFile(const FilePath &filePath, const FilePath &target) const override; bool renameFile(const FilePath &filePath, const FilePath &target) const override; - OsType osType(const FilePath &filePath) const override; FilePath symLinkTarget(const FilePath &filePath) const override; FilePathInfo filePathInfo(const FilePath &filePath) const override; QDateTime lastModified(const FilePath &filePath) const override; @@ -160,6 +150,7 @@ protected: bool isFile(const FilePath &filePath) const override; bool isDirectory(const FilePath &filePath) const override; bool isSymLink(const FilePath &filePath) const override; + bool hasHardLinks(const FilePath &filePath) const override; bool ensureExistingFile(const FilePath &filePath) const override; bool createDirectory(const FilePath &filePath) const override; bool exists(const FilePath &filePath) const override; @@ -169,7 +160,6 @@ protected: bool renameFile(const FilePath &filePath, const FilePath &target) const override; FilePathInfo filePathInfo(const FilePath &filePath) const override; - OsType osType(const FilePath &filePath) const override; FilePath symLinkTarget(const FilePath &filePath) const override; QDateTime lastModified(const FilePath &filePath) const override; QFile::Permissions permissions(const FilePath &filePath) const override; @@ -204,6 +194,10 @@ private: const FileFilter &filter, QStringList *found) const; + QStringList statArgs(const FilePath &filePath, + const QString &linuxFormat, + const QString &macFormat) const; + mutable bool m_tryUseFind = true; mutable std::optional m_hasMkTemp; }; diff --git a/src/libs/utils/deviceshell.cpp b/src/libs/utils/deviceshell.cpp index 18e816d38b2..f96c5da8f2d 100644 --- a/src/libs/utils/deviceshell.cpp +++ b/src/libs/utils/deviceshell.cpp @@ -3,9 +3,9 @@ #include "deviceshell.h" +#include "process.h" #include "processinterface.h" #include "qtcassert.h" -#include "qtcprocess.h" #include #include @@ -14,10 +14,10 @@ Q_LOGGING_CATEGORY(deviceShellLog, "qtc.utils.deviceshell", QtWarningMsg) namespace Utils { -/*! +/* * The multiplex script waits for input via stdin. * - * To start a command, a message is send with + * To start a command, a message is sent with * the format " "" \n" * To stop the script, simply send "exit\n" via stdin * @@ -73,15 +73,15 @@ QStringList DeviceShell::missingFeatures() const { return m_missingFeatures; } RunResult DeviceShell::run(const CommandLine &cmd, const QByteArray &stdInData) { - // If the script failed to install, use QtcProcess directly instead. + // If the script failed to install, use Process directly instead. bool useProcess = m_shellScriptState == State::Failed; // Transferring large amounts of stdInData is slow via the shell script. - // Use QtcProcess directly if the size exceeds 100kb. + // Use Process directly if the size exceeds 100kb. useProcess |= stdInData.size() > (1024 * 100); if (useProcess) { - QtcProcess proc; + Process proc; const CommandLine fallbackCmd = createFallbackCommand(cmd); qCDebug(deviceShellLog) << "Running fallback:" << fallbackCmd; proc.setCommand(fallbackCmd); @@ -97,13 +97,14 @@ RunResult DeviceShell::run(const CommandLine &cmd, const QByteArray &stdInData) const RunResult errorResult{-1, {}, {}}; QTC_ASSERT(m_shellProcess, return errorResult); + QTC_ASSERT(m_shellProcess->isRunning(), return errorResult); QTC_ASSERT(m_shellScriptState == State::Succeeded, return errorResult); QMutexLocker lk(&m_commandMutex); QWaitCondition waiter; const int id = ++m_currentId; - const auto it = m_commandOutput.insert(id, CommandRun{{-1, {}, {}}, &waiter}); + m_commandOutput.insert(id, CommandRun{{-1, {}, {}}, &waiter}); QMetaObject::invokeMethod(m_shellProcess.get(), [this, id, cmd, stdInData] { const QString command = QString("%1 \"%2\" %3\n").arg(id) @@ -114,6 +115,7 @@ RunResult DeviceShell::run(const CommandLine &cmd, const QByteArray &stdInData) waiter.wait(&m_commandMutex); + const auto it = m_commandOutput.constFind(id); const RunResult result = *it; m_commandOutput.erase(it); @@ -135,7 +137,7 @@ void DeviceShell::close() * Override this function to setup the shell process. * The default implementation just sets the command line to "bash" */ -void DeviceShell::setupShellProcess(QtcProcess *shellProcess) +void DeviceShell::setupShellProcess(Process *shellProcess) { shellProcess->setCommand(CommandLine{"bash"}); } @@ -170,8 +172,8 @@ void DeviceShell::startupFailed(const CommandLine &cmdLine) */ bool DeviceShell::start() { - m_shellProcess = std::make_unique(); - connect(m_shellProcess.get(), &QtcProcess::done, m_shellProcess.get(), + m_shellProcess = std::make_unique(); + connect(m_shellProcess.get(), &Process::done, m_shellProcess.get(), [this] { emit done(m_shellProcess->resultData()); }); connect(&m_thread, &QThread::finished, m_shellProcess.get(), [this] { closeShellProcess(); }, Qt::DirectConnection); @@ -198,11 +200,11 @@ bool DeviceShell::start() if (installShellScript()) { connect(m_shellProcess.get(), - &QtcProcess::readyReadStandardOutput, + &Process::readyReadStandardOutput, m_shellProcess.get(), [this] { onReadyRead(); }); connect(m_shellProcess.get(), - &QtcProcess::readyReadStandardError, + &Process::readyReadStandardError, m_shellProcess.get(), [this] { const QByteArray stdErr = m_shellProcess->readAllRawStandardError(); @@ -215,7 +217,7 @@ bool DeviceShell::start() return false; } - connect(m_shellProcess.get(), &QtcProcess::done, m_shellProcess.get(), [this] { + connect(m_shellProcess.get(), &Process::done, m_shellProcess.get(), [this] { if (m_shellProcess->resultData().m_exitCode != EXIT_SUCCESS || m_shellProcess->resultData().m_exitStatus != QProcess::NormalExit) { qCWarning(deviceShellLog) << "Shell exited with error code:" diff --git a/src/libs/utils/deviceshell.h b/src/libs/utils/deviceshell.h index 83af1db365e..e5bc4ad7afe 100644 --- a/src/libs/utils/deviceshell.h +++ b/src/libs/utils/deviceshell.h @@ -7,7 +7,7 @@ #include "fileutils.h" -#include +#include #include #include #include @@ -19,7 +19,7 @@ namespace Utils { class CommandLine; class ProcessResultData; -class QtcProcess; +class Process; class DeviceShellImpl; @@ -57,7 +57,7 @@ protected: void close(); private: - virtual void setupShellProcess(QtcProcess *shellProcess); + virtual void setupShellProcess(Process *shellProcess); virtual CommandLine createFallbackCommand(const CommandLine &cmdLine); bool installShellScript(); @@ -73,13 +73,12 @@ private: QWaitCondition *waiter; }; - std::unique_ptr m_shellProcess; + std::unique_ptr m_shellProcess; QThread m_thread; int m_currentId{0}; QMutex m_commandMutex; - // QMap is used here to preserve iterators - QMap m_commandOutput; + QHash m_commandOutput; QByteArray m_commandBuffer; State m_shellScriptState = State::Unknown; diff --git a/src/libs/utils/differ.cpp b/src/libs/utils/differ.cpp index 538aa6657ab..9dc2ce8454e 100644 --- a/src/libs/utils/differ.cpp +++ b/src/libs/utils/differ.cpp @@ -14,11 +14,10 @@ publication by Neil Fraser: http://neil.fraser.name/writing/diff/ #include "utilstr.h" #include -#include -#include #include #include -#include +#include +#include namespace Utils { @@ -937,10 +936,9 @@ QString Diff::toString() const /////////////// -Differ::Differ(QFutureInterfaceBase *jobController) - : m_jobController(jobController) +Differ::Differ(const std::optional> &future) + : m_future(future) { - } QList Differ::diff(const QString &text1, const QString &text2) @@ -1075,7 +1073,7 @@ QList Differ::diffMyers(const QString &text1, const QString &text2) int kMinReverse = -D; int kMaxReverse = D; for (int d = 0; d <= D; d++) { - if (m_jobController && m_jobController->isCanceled()) { + if (m_future && m_future->isCanceled()) { delete [] forwardV; delete [] reverseV; return QList(); @@ -1193,17 +1191,10 @@ QList Differ::diffNonCharMode(const QString &text1, const QString &text2) QString lastDelete; QString lastInsert; QList newDiffList; - if (m_jobController) { - m_jobController->setProgressRange(0, diffList.count()); - m_jobController->setProgressValue(0); - } for (int i = 0; i <= diffList.count(); i++) { - if (m_jobController) { - if (m_jobController->isCanceled()) { - m_currentDiffMode = diffMode; - return QList(); - } - m_jobController->setProgressValue(i + 1); + if (m_future && m_future->isCanceled()) { + m_currentDiffMode = diffMode; + return {}; } const Diff diffItem = i < diffList.count() ? diffList.at(i) diff --git a/src/libs/utils/differ.h b/src/libs/utils/differ.h index 62b8bc7ec46..dc6d74f30da 100644 --- a/src/libs/utils/differ.h +++ b/src/libs/utils/differ.h @@ -5,12 +5,14 @@ #include "utils_global.h" +#include #include +#include + QT_BEGIN_NAMESPACE template class QMap; -class QFutureInterfaceBase; QT_END_NAMESPACE namespace Utils { @@ -42,7 +44,7 @@ public: WordMode, LineMode }; - Differ(QFutureInterfaceBase *jobController = nullptr); + Differ(const std::optional> &future = {}); QList diff(const QString &text1, const QString &text2); QList unifiedDiff(const QString &text1, const QString &text2); void setDiffMode(DiffMode mode); @@ -90,7 +92,7 @@ private: int subTextStart); DiffMode m_diffMode = Differ::LineMode; DiffMode m_currentDiffMode = Differ::LineMode; - QFutureInterfaceBase *m_jobController = nullptr; + std::optional> m_future; }; } // namespace Utils diff --git a/src/libs/utils/dropsupport.cpp b/src/libs/utils/dropsupport.cpp index 9d914e0f084..6def3fd1aff 100644 --- a/src/libs/utils/dropsupport.cpp +++ b/src/libs/utils/dropsupport.cpp @@ -40,10 +40,6 @@ static bool isFileDropMime(const QMimeData *d, QList *fil const QList::const_iterator cend = urls.constEnd(); for (QList::const_iterator it = urls.constBegin(); it != cend; ++it) { QUrl url = *it; -#ifdef Q_OS_OSX - // for file drops from Finder, working around QTBUG-40449 - url = Internal::filePathUrl(url); -#endif const QString fileName = url.toLocalFile(); if (!fileName.isEmpty()) { hasFiles = true; diff --git a/src/libs/utils/elidinglabel.cpp b/src/libs/utils/elidinglabel.cpp index cf74d5f3d92..0b328a82b16 100644 --- a/src/libs/utils/elidinglabel.cpp +++ b/src/libs/utils/elidinglabel.cpp @@ -9,6 +9,7 @@ /*! \class Utils::ElidingLabel + \inmodule QtCreator \brief The ElidingLabel class is a label suitable for displaying elided text. diff --git a/src/libs/utils/environment.cpp b/src/libs/utils/environment.cpp index c99293329c1..b9af2dea928 100644 --- a/src/libs/utils/environment.cpp +++ b/src/libs/utils/environment.cpp @@ -4,6 +4,7 @@ #include "environment.h" #include "algorithm.h" +#include "filepath.h" #include "qtcassert.h" #include @@ -11,6 +12,13 @@ #include #include +/*! + \class Utils::Environment + \inmodule QtCreator + + \brief The Environment class sets \QC's system environment. +*/ + namespace Utils { static QReadWriteLock s_envMutex; @@ -18,83 +26,149 @@ Q_GLOBAL_STATIC_WITH_ARGS(Environment, staticSystemEnvironment, (QProcessEnvironment::systemEnvironment().toStringList())) Q_GLOBAL_STATIC(QVector, environmentProviders) +Environment::Environment() + : m_dict(HostOsInfo::hostOs()) +{} + +Environment::Environment(OsType osType) + : m_dict(osType) +{} + +Environment::Environment(const QStringList &env, OsType osType) + : m_dict(osType) +{ + m_changeItems.append(NameValueDictionary(env, osType)); +} + +Environment::Environment(const NameValuePairs &nameValues) +{ + m_changeItems.append(NameValueDictionary(nameValues)); +} + +Environment::Environment(const NameValueDictionary &dict) +{ + m_changeItems.append(dict); +} + NameValueItems Environment::diff(const Environment &other, bool checkAppendPrepend) const { - return m_dict.diff(other.m_dict, checkAppendPrepend); + const NameValueDictionary &dict = resolved(); + const NameValueDictionary &otherDict = other.resolved(); + return dict.diff(otherDict, checkAppendPrepend); +} + +Environment::FindResult Environment::find(const QString &name) const +{ + const NameValueDictionary &dict = resolved(); + const auto it = dict.constFind(name); + if (it == dict.constEnd()) + return {}; + return Entry{it.key().name, it.value().first, it.value().second}; +} + +void Environment::forEachEntry(const std::function &callBack) const +{ + const NameValueDictionary &dict = resolved(); + for (auto it = dict.m_values.constBegin(); it != dict.m_values.constEnd(); ++it) + callBack(it.key().name, it.value().first, it.value().second); +} + +bool Environment::operator==(const Environment &other) const +{ + const NameValueDictionary &dict = resolved(); + const NameValueDictionary &otherDict = other.resolved(); + return dict == otherDict; +} + +bool Environment::operator!=(const Environment &other) const +{ + const NameValueDictionary &dict = resolved(); + const NameValueDictionary &otherDict = other.resolved(); + return dict != otherDict; +} + +QString Environment::value(const QString &key) const +{ + const NameValueDictionary &dict = resolved(); + return dict.value(key); +} + +QString Environment::value_or(const QString &key, const QString &defaultValue) const +{ + const NameValueDictionary &dict = resolved(); + return dict.hasKey(key) ? dict.value(key) : defaultValue; +} + +bool Environment::hasKey(const QString &key) const +{ + const NameValueDictionary &dict = resolved(); + return dict.hasKey(key); } bool Environment::hasChanges() const { - return m_dict.size() != 0; + const NameValueDictionary &dict = resolved(); + return dict.size() != 0; +} + +OsType Environment::osType() const +{ + return m_dict.m_osType; +} + +QStringList Environment::toStringList() const +{ + const NameValueDictionary &dict = resolved(); + return dict.toStringList(); } QProcessEnvironment Environment::toProcessEnvironment() const { + const NameValueDictionary &dict = resolved(); QProcessEnvironment result; - for (auto it = m_dict.m_values.constBegin(); it != m_dict.m_values.constEnd(); ++it) { + for (auto it = dict.m_values.constBegin(); it != dict.m_values.constEnd(); ++it) { if (it.value().second) - result.insert(it.key().name, expandedValueForKey(key(it))); + result.insert(it.key().name, expandedValueForKey(dict.key(it))); } return result; } void Environment::appendOrSetPath(const FilePath &value) { - QTC_CHECK(value.osType() == osType()); + QTC_CHECK(value.osType() == m_dict.m_osType); if (value.isEmpty()) return; - appendOrSet("PATH", value.nativePath(), - QString(OsSpecificAspects::pathListSeparator(osType()))); + appendOrSet("PATH", value.nativePath(), OsSpecificAspects::pathListSeparator(osType())); } void Environment::prependOrSetPath(const FilePath &value) { - QTC_CHECK(value.osType() == osType()); + QTC_CHECK(value.osType() == m_dict.m_osType); if (value.isEmpty()) return; - prependOrSet("PATH", value.nativePath(), - QString(OsSpecificAspects::pathListSeparator(osType()))); + prependOrSet("PATH", value.nativePath(), OsSpecificAspects::pathListSeparator(osType())); } void Environment::appendOrSet(const QString &key, const QString &value, const QString &sep) { - QTC_ASSERT(!key.contains('='), return ); - const auto it = m_dict.findKey(key); - if (it == m_dict.m_values.end()) { - m_dict.m_values.insert(DictKey(key, m_dict.nameCaseSensitivity()), {value, true}); - } else { - // Append unless it is already there - const QString toAppend = sep + value; - if (!it.value().first.endsWith(toAppend)) - it.value().first.append(toAppend); - } + addItem(Item{std::in_place_index_t(), key, value, sep}); } void Environment::prependOrSet(const QString &key, const QString &value, const QString &sep) { - QTC_ASSERT(!key.contains('='), return ); - const auto it = m_dict.findKey(key); - if (it == m_dict.m_values.end()) { - m_dict.m_values.insert(DictKey(key, m_dict.nameCaseSensitivity()), {value, true}); - } else { - // Prepend unless it is already there - const QString toPrepend = value + sep; - if (!it.value().first.startsWith(toPrepend)) - it.value().first.prepend(toPrepend); - } + addItem(Item{std::in_place_index_t(), key, value, sep}); } void Environment::prependOrSetLibrarySearchPath(const FilePath &value) { QTC_CHECK(value.osType() == osType()); + const QChar sep = OsSpecificAspects::pathListSeparator(osType()); switch (osType()) { case OsTypeWindows: { - const QChar sep = ';'; - prependOrSet("PATH", value.nativePath(), QString(sep)); + prependOrSet("PATH", value.nativePath(), sep); break; } case OsTypeMac: { - const QString sep = ":"; const QString nativeValue = value.nativePath(); prependOrSet("DYLD_LIBRARY_PATH", nativeValue, sep); prependOrSet("DYLD_FRAMEWORK_PATH", nativeValue, sep); @@ -102,8 +176,7 @@ void Environment::prependOrSetLibrarySearchPath(const FilePath &value) } case OsTypeLinux: case OsTypeOtherUnix: { - const QChar sep = ':'; - prependOrSet("LD_LIBRARY_PATH", value.nativePath(), QString(sep)); + prependOrSet("LD_LIBRARY_PATH", value.nativePath(), sep); break; } default: @@ -118,6 +191,14 @@ void Environment::prependOrSetLibrarySearchPaths(const FilePaths &values) }); } +/*! + Returns \QC's system environment. + + This can be different from the system environment that \QC started in if the + user changed it in \uicontrol Preferences > \uicontrol Environment > + \uicontrol System > \uicontrol Environment. +*/ + Environment Environment::systemEnvironment() { QReadLocker lock(&s_envMutex); @@ -126,144 +207,24 @@ Environment Environment::systemEnvironment() void Environment::setupEnglishOutput() { - m_dict.set("LC_MESSAGES", "en_US.utf8"); - m_dict.set("LANGUAGE", "en_US:en"); + addItem(Item{std::in_place_index_t()}); } -static FilePath searchInDirectory(const QStringList &execs, - const FilePath &directory, - QSet &alreadyChecked) -{ - const int checkedCount = alreadyChecked.count(); - alreadyChecked.insert(directory); - - if (directory.isEmpty() || alreadyChecked.count() == checkedCount) - return FilePath(); - - for (const QString &exec : execs) { - const FilePath filePath = directory.pathAppended(exec); - if (filePath.isExecutableFile()) - return filePath; - } - return FilePath(); -} - -static QStringList appendExeExtensions(const Environment &env, const QString &executable) -{ - QStringList execs(executable); - if (env.osType() == OsTypeWindows) { - const QFileInfo fi(executable); - // Check all the executable extensions on windows: - // PATHEXT is only used if the executable has no extension - if (fi.suffix().isEmpty()) { - const QStringList extensions = env.expandedValueForKey("PATHEXT").split(';'); - - for (const QString &ext : extensions) - execs << executable + ext.toLower(); - } - } - return execs; -} +using SearchResultCallback = std::function; QString Environment::expandedValueForKey(const QString &key) const { - return expandVariables(m_dict.value(key)); -} - -static FilePath searchInDirectoriesHelper(const Environment &env, - const QString &executable, - const FilePaths &dirs, - const Environment::PathFilter &func, - bool usePath) -{ - if (executable.isEmpty()) - return FilePath(); - - const QString exec = QDir::cleanPath(env.expandVariables(executable)); - const QFileInfo fi(exec); - - const QStringList execs = appendExeExtensions(env, exec); - - if (fi.isAbsolute()) { - for (const QString &path : execs) { - QFileInfo pfi = QFileInfo(path); - if (pfi.isFile() && pfi.isExecutable()) - return FilePath::fromString(path); - } - return FilePath::fromString(exec); - } - - QSet alreadyChecked; - for (const FilePath &dir : dirs) { - FilePath tmp = searchInDirectory(execs, dir, alreadyChecked); - if (!tmp.isEmpty() && (!func || func(tmp))) - return tmp; - } - - if (usePath) { - if (executable.contains('/')) - return FilePath(); - - for (const FilePath &p : env.path()) { - FilePath tmp = searchInDirectory(execs, p, alreadyChecked); - if (!tmp.isEmpty() && (!func || func(tmp))) - return tmp; - } - } - return FilePath(); -} - -FilePath Environment::searchInDirectories(const QString &executable, - const FilePaths &dirs, - const PathFilter &func) const -{ - return searchInDirectoriesHelper(*this, executable, dirs, func, false); + const NameValueDictionary &dict = resolved(); + return expandVariables(dict.value(key)); } FilePath Environment::searchInPath(const QString &executable, const FilePaths &additionalDirs, - const PathFilter &func) const + const FilePathPredicate &filter) const { - return searchInDirectoriesHelper(*this, executable, additionalDirs, func, true); -} - -FilePaths Environment::findAllInPath(const QString &executable, - const FilePaths &additionalDirs, - const Environment::PathFilter &func) const -{ - if (executable.isEmpty()) - return {}; - - const QString exec = QDir::cleanPath(expandVariables(executable)); - const QFileInfo fi(exec); - - const QStringList execs = appendExeExtensions(*this, exec); - - if (fi.isAbsolute()) { - for (const QString &path : execs) { - QFileInfo pfi = QFileInfo(path); - if (pfi.isFile() && pfi.isExecutable()) - return {FilePath::fromString(path)}; - } - return {FilePath::fromString(exec)}; - } - - QSet result; - QSet alreadyChecked; - for (const FilePath &dir : additionalDirs) { - FilePath tmp = searchInDirectory(execs, dir, alreadyChecked); - if (!tmp.isEmpty() && (!func || func(tmp))) - result << tmp; - } - - if (!executable.contains('/')) { - for (const FilePath &p : path()) { - FilePath tmp = searchInDirectory(execs, p, alreadyChecked); - if (!tmp.isEmpty() && (!func || func(tmp))) - result << tmp; - } - } - return result.values(); + const FilePath exec = FilePath::fromUserInput(expandVariables(executable)); + const FilePaths dirs = path() + additionalDirs; + return exec.searchInDirectories(dirs, filter, FilePath::WithAnySuffix); } FilePaths Environment::path() const @@ -299,14 +260,16 @@ void Environment::setSystemEnvironment(const Environment &environment) */ QString Environment::expandVariables(const QString &input) const { + const NameValueDictionary &dict = resolved(); + QString result = input; if (osType() == OsTypeWindows) { for (int vStart = -1, i = 0; i < result.length(); ) { if (result.at(i++) == '%') { if (vStart > 0) { - const auto it = m_dict.findKey(result.mid(vStart, i - vStart - 1)); - if (it != m_dict.m_values.constEnd()) { + const auto it = dict.findKey(result.mid(vStart, i - vStart - 1)); + if (it != dict.m_values.constEnd()) { result.replace(vStart - 1, i - vStart + 1, it->first); i = vStart - 1 + it->first.length(); vStart = -1; @@ -339,28 +302,30 @@ QString Environment::expandVariables(const QString &input) const } } else if (state == BRACEDVARIABLE) { if (c == '}') { - const_iterator it = constFind(result.mid(vStart, i - 1 - vStart)); - if (it != constEnd()) { - result.replace(vStart - 2, i - vStart + 2, it->first); - i = vStart - 2 + it->first.length(); + const QString key = result.mid(vStart, i - 1 - vStart); + const Environment::FindResult res = find(key); + if (res) { + result.replace(vStart - 2, i - vStart + 2, res->value); + i = vStart - 2 + res->value.length(); } state = BASE; } } else if (state == VARIABLE) { if (!c.isLetterOrNumber() && c != '_') { - const_iterator it = constFind(result.mid(vStart, i - vStart - 1)); - if (it != constEnd()) { - result.replace(vStart - 1, i - vStart, it->first); - i = vStart - 1 + it->first.length(); + const QString key = result.mid(vStart, i - vStart - 1); + const Environment::FindResult res = find(key); + if (res) { + result.replace(vStart - 1, i - vStart, res->value); + i = vStart - 1 + res->value.length(); } state = BASE; } } } if (state == VARIABLE) { - const_iterator it = constFind(result.mid(vStart)); - if (it != constEnd()) - result.replace(vStart - 1, result.length() - vStart + 1, it->first); + const Environment::FindResult res = find(result.mid(vStart)); + if (res) + result.replace(vStart - 1, result.length() - vStart + 1, res->value); } } return result; @@ -376,6 +341,12 @@ QStringList Environment::expandVariables(const QStringList &variables) const return transform(variables, [this](const QString &i) { return expandVariables(i); }); } +NameValueDictionary Environment::toDictionary() const +{ + const NameValueDictionary &dict = resolved(); + return dict; +} + void EnvironmentProvider::addProvider(EnvironmentProvider &&provider) { environmentProviders->append(std::move(provider)); @@ -394,63 +365,145 @@ std::optional EnvironmentProvider::provider(const QByteArra return std::nullopt; } -void EnvironmentChange::addSetValue(const QString &key, const QString &value) +void Environment::addItem(const Item &item) { - m_changeItems.append(Item{std::in_place_index_t(), QPair{key, value}}); + m_dict.clear(); + m_changeItems.append(item); } -void EnvironmentChange::addUnsetValue(const QString &key) +void Environment::set(const QString &key, const QString &value, bool enabled) { - m_changeItems.append(Item{std::in_place_index_t(), key}); + addItem(Item{std::in_place_index_t(), + std::tuple{key, value, enabled}}); } -void EnvironmentChange::addPrependToPath(const FilePaths &values) +void Environment::setFallback(const QString &key, const QString &value) { + addItem(Item{std::in_place_index_t(), + std::tuple{key, value}}); +} + +void Environment::unset(const QString &key) +{ + addItem(Item{std::in_place_index_t(), key}); +} + +void Environment::modify(const NameValueItems &items) +{ + addItem(Item{std::in_place_index_t(), items}); +} + +void Environment::prependToPath(const FilePaths &values) +{ + m_dict.clear(); for (int i = values.size(); --i >= 0; ) { const FilePath value = values.at(i); - m_changeItems.append(Item{std::in_place_index_t(), value}); + m_changeItems.append(Item{ + std::in_place_index_t(), + QString("PATH"), + value.nativePath(), + value.pathListSeparator() + }); } } -void EnvironmentChange::addAppendToPath(const FilePaths &values) +void Environment::appendToPath(const FilePaths &values) { - for (const FilePath &value : values) - m_changeItems.append(Item{std::in_place_index_t(), value}); + m_dict.clear(); + for (const FilePath &value : values) { + m_changeItems.append(Item{ + std::in_place_index_t(), + QString("PATH"), + value.nativePath(), + value.pathListSeparator() + }); + } } -EnvironmentChange EnvironmentChange::fromDictionary(const NameValueDictionary &dict) +const NameValueDictionary &Environment::resolved() const { - EnvironmentChange change; - change.m_changeItems.append(Item{std::in_place_index_t(), dict}); - return change; -} + if (m_dict.size() != 0) + return m_dict; -void EnvironmentChange::applyToEnvironment(Environment &env) const -{ + m_fullDict = false; for (const Item &item : m_changeItems) { switch (item.index()) { case SetSystemEnvironment: - env = Environment::systemEnvironment(); + m_dict = Environment::systemEnvironment().toDictionary(); + m_fullDict = true; break; case SetFixedDictionary: - env = Environment(std::get(item)); + m_dict = std::get(item); + m_fullDict = true; break; case SetValue: { - const QPair data = std::get(item); - env.set(data.first, data.second); + auto [key, value, enabled] = std::get(item); + m_dict.set(key, value, enabled); + break; + } + case SetFallbackValue: { + auto [key, value] = std::get(item); + if (m_fullDict) { + if (m_dict.value(key).isEmpty()) + m_dict.set(key, value, true); + } else { + QTC_ASSERT(false, qDebug() << "operating on partial dictionary"); + m_dict.set(key, value, true); + } break; } case UnsetValue: - env.unset(std::get(item)); + m_dict.unset(std::get(item)); break; - case PrependToPath: - env.prependOrSetPath(std::get(item)); + case PrependOrSet: { + auto [key, value, sep] = std::get(item); + QTC_ASSERT(!key.contains('='), return m_dict); + const auto it = m_dict.findKey(key); + if (it == m_dict.m_values.end()) { + m_dict.m_values.insert(DictKey(key, m_dict.nameCaseSensitivity()), {value, true}); + } else { + // Prepend unless it is already there + const QString toPrepend = value + sep; + if (!it.value().first.startsWith(toPrepend)) + it.value().first.prepend(toPrepend); + } break; - case AppendToPath: - env.appendOrSetPath(std::get(item)); + } + case AppendOrSet: { + auto [key, value, sep] = std::get(item); + QTC_ASSERT(!key.contains('='), return m_dict); + const auto it = m_dict.findKey(key); + if (it == m_dict.m_values.end()) { + m_dict.m_values.insert(DictKey(key, m_dict.nameCaseSensitivity()), {value, true}); + } else { + // Prepend unless it is already there + const QString toAppend = sep + value; + if (!it.value().first.endsWith(toAppend)) + it.value().first.append(toAppend); + } + break; + } + case Modify: { + NameValueItems items = std::get(item); + m_dict.modify(items); + break; + } + case SetupEnglishOutput: + m_dict.set("LC_MESSAGES", "en_US.utf8"); + m_dict.set("LANGUAGE", "en_US:en"); break; } } + + return m_dict; +} + +Environment Environment::appliedToEnvironment(const Environment &base) const +{ + Environment res = base; + res.m_dict.clear(); + res.m_changeItems.append(m_changeItems); + return res; } /*! diff --git a/src/libs/utils/environment.h b/src/libs/utils/environment.h index 2671e972cf3..63fe697bd6e 100644 --- a/src/libs/utils/environment.h +++ b/src/libs/utils/environment.h @@ -8,6 +8,7 @@ #include "environmentfwd.h" #include "filepath.h" #include "namevaluedictionary.h" +#include "utiltypes.h" #include #include @@ -21,28 +22,25 @@ namespace Utils { class QTCREATOR_UTILS_EXPORT Environment final { public: - Environment() : m_dict(HostOsInfo::hostOs()) {} - explicit Environment(OsType osType) : m_dict(osType) {} - explicit Environment(const QStringList &env, OsType osType = HostOsInfo::hostOs()) - : m_dict(env, osType) {} - explicit Environment(const NameValuePairs &nameValues) : m_dict(nameValues) {} - explicit Environment(const NameValueDictionary &dict) : m_dict(dict) {} + Environment(); + explicit Environment(OsType osType); + explicit Environment(const QStringList &env, OsType osType = HostOsInfo::hostOs()); + explicit Environment(const NameValuePairs &nameValues); + explicit Environment(const NameValueDictionary &dict); - QString value(const QString &key) const { return m_dict.value(key); } - QString value_or(const QString &key, const QString &defaultValue) const - { - return m_dict.hasKey(key) ? m_dict.value(key) : defaultValue; - } - bool hasKey(const QString &key) const { return m_dict.hasKey(key); } + QString value(const QString &key) const; + QString value_or(const QString &key, const QString &defaultValue) const; + bool hasKey(const QString &key) const; - void set(const QString &key, const QString &value, bool enabled = true) { m_dict.set(key, value, enabled); } - void unset(const QString &key) { m_dict.unset(key); } - void modify(const NameValueItems &items) { m_dict.modify(items); } + void set(const QString &key, const QString &value, bool enabled = true); + void setFallback(const QString &key, const QString &value); + void unset(const QString &key); + void modify(const NameValueItems &items); bool hasChanges() const; - void clear() { return m_dict.clear(); } - QStringList toStringList() const { return m_dict.toStringList(); } + OsType osType() const; + QStringList toStringList() const; QProcessEnvironment toProcessEnvironment() const; void appendOrSet(const QString &key, const QString &value, const QString &sep = QString()); @@ -54,18 +52,15 @@ public: void prependOrSetLibrarySearchPath(const FilePath &value); void prependOrSetLibrarySearchPaths(const FilePaths &values); - void setupEnglishOutput(); + void prependToPath(const FilePaths &values); + void appendToPath(const FilePaths &values); + + void setupEnglishOutput(); + void setupSudoAskPass(const FilePath &askPass); - using PathFilter = std::function; FilePath searchInPath(const QString &executable, const FilePaths &additionalDirs = FilePaths(), - const PathFilter &func = PathFilter()) const; - FilePath searchInDirectories(const QString &executable, - const FilePaths &dirs, - const PathFilter &func = {}) const; - FilePaths findAllInPath(const QString &executable, - const FilePaths &additionalDirs = {}, - const PathFilter &func = {}) const; + const FilePathPredicate &func = {}) const; FilePaths path() const; FilePaths pathListValue(const QString &varName) const; @@ -75,80 +70,62 @@ public: FilePath expandVariables(const FilePath &input) const; QStringList expandVariables(const QStringList &input) const; - OsType osType() const { return m_dict.osType(); } - QString userName() const; - - using const_iterator = NameValueMap::const_iterator; // FIXME: avoid - NameValueDictionary toDictionary() const { return m_dict; } // FIXME: avoid + NameValueDictionary toDictionary() const; // FIXME: avoid NameValueItems diff(const Environment &other, bool checkAppendPrepend = false) const; // FIXME: avoid - QString key(const_iterator it) const { return m_dict.key(it); } // FIXME: avoid - QString value(const_iterator it) const { return m_dict.value(it); } // FIXME: avoid - bool isEnabled(const_iterator it) const { return m_dict.isEnabled(it); } // FIXME: avoid + struct Entry { QString key; QString value; bool enabled; }; + using FindResult = std::optional; + FindResult find(const QString &name) const; // Note res->key may differ in case from name. - void setCombineWithDeviceEnvironment(bool combine) { m_combineWithDeviceEnvironment = combine; } - bool combineWithDeviceEnvironment() const { return m_combineWithDeviceEnvironment; } + void forEachEntry(const std::function &callBack) const; - const_iterator constBegin() const { return m_dict.constBegin(); } // FIXME: avoid - const_iterator constEnd() const { return m_dict.constEnd(); } // FIXME: avoid - const_iterator constFind(const QString &name) const { return m_dict.constFind(name); } // FIXME: avoid - - friend bool operator!=(const Environment &first, const Environment &second) - { - return first.m_dict != second.m_dict; - } - - friend bool operator==(const Environment &first, const Environment &second) - { - return first.m_dict == second.m_dict; - } + bool operator!=(const Environment &other) const; + bool operator==(const Environment &other) const; static Environment systemEnvironment(); static void modifySystemEnvironment(const EnvironmentItems &list); // use with care!!! static void setSystemEnvironment(const Environment &environment); // don't use at all!!! -private: - NameValueDictionary m_dict; - bool m_combineWithDeviceEnvironment = true; -}; - -class QTCREATOR_UTILS_EXPORT EnvironmentChange final -{ -public: - EnvironmentChange() = default; - enum Type { SetSystemEnvironment, SetFixedDictionary, SetValue, + SetFallbackValue, UnsetValue, - PrependToPath, - AppendToPath, + PrependOrSet, + AppendOrSet, + Modify, + SetupEnglishOutput }; using Item = std::variant< - std::monostate, // SetSystemEnvironment dummy - NameValueDictionary, // SetFixedDictionary - QPair, // SetValue - QString, // UnsetValue - FilePath, // PrependToPath - FilePath // AppendToPath + std::monostate, // SetSystemEnvironment dummy + NameValueDictionary, // SetFixedDictionary + std::tuple, // SetValue (key, value, enabled) + std::tuple, // SetFallbackValue (key, value) + QString, // UnsetValue (key) + std::tuple, // PrependOrSet (key, value, separator) + std::tuple, // AppendOrSet (key, value, separator) + NameValueItems, // Modify + std::monostate, // SetupEnglishOutput + FilePath // SetupSudoAskPass (file path of qtc-askpass or ssh-askpass) >; - static EnvironmentChange fromDictionary(const NameValueDictionary &dict); + void addItem(const Item &item); - void applyToEnvironment(Environment &) const; + Environment appliedToEnvironment(const Environment &base) const; - void addSetValue(const QString &key, const QString &value); - void addUnsetValue(const QString &key); - void addPrependToPath(const FilePaths &values); - void addAppendToPath(const FilePaths &values); + const NameValueDictionary &resolved() const; private: - QList m_changeItems; + mutable QList m_changeItems; + mutable NameValueDictionary m_dict; // Latest resolved. + mutable bool m_fullDict = false; }; +using EnviromentChange = Environment; + class QTCREATOR_UTILS_EXPORT EnvironmentProvider { public: diff --git a/src/libs/utils/externalterminalprocessimpl.cpp b/src/libs/utils/externalterminalprocessimpl.cpp new file mode 100644 index 00000000000..efdbc9aca31 --- /dev/null +++ b/src/libs/utils/externalterminalprocessimpl.cpp @@ -0,0 +1,92 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "externalterminalprocessimpl.h" +#include "process.h" +#include "terminalcommand.h" +#include "utilstr.h" + +#include + +namespace Utils { + +ExternalTerminalProcessImpl::ExternalTerminalProcessImpl() +{ + setStubCreator(new ProcessStubCreator(this)); +} + +ProcessStubCreator::ProcessStubCreator(TerminalInterface *interface) + : m_interface(interface) +{} + +expected_str ProcessStubCreator::startStubProcess(const ProcessSetupData &setupData) +{ + const TerminalCommand terminal = TerminalCommand::terminalEmulator(); + + if (HostOsInfo::isMacHost() && terminal.command == "Terminal.app") { + QTemporaryFile f; + f.setAutoRemove(false); + f.open(); + f.setPermissions(QFile::ExeUser | QFile::ReadUser | QFile::WriteUser); + f.write("#!/bin/sh\n"); + f.write(QString("cd %1\n").arg(setupData.m_workingDirectory.nativePath()).toUtf8()); + f.write("clear\n"); + f.write(QString("exec '%1' %2\n") + .arg(setupData.m_commandLine.executable().nativePath()) + .arg(setupData.m_commandLine.arguments()) + .toUtf8()); + f.close(); + + const QString path = f.fileName(); + const QString exe + = QString("tell app \"Terminal\" to do script \"'%1'; rm -f '%1'; exit\"").arg(path); + + Process process; + + process.setCommand({"osascript", {"-e", "tell app \"Terminal\" to activate", "-e", exe}}); + process.runBlocking(); + + if (process.exitCode() != 0) { + return make_unexpected( + Tr::tr("Failed to start terminal process: \"%1\"").arg(process.errorString())); + } + + return 0; + } + + bool detached = setupData.m_terminalMode == TerminalMode::Detached; + + Process *process = new Process(detached ? nullptr : this); + if (detached) + QObject::connect(process, &Process::done, process, &Process::deleteLater); + + QObject::connect(process, &Process::done, m_interface, &TerminalInterface::onStubExited); + + process->setWorkingDirectory(setupData.m_workingDirectory); + + if constexpr (HostOsInfo::isWindowsHost()) { + process->setCommand(setupData.m_commandLine); + process->setCreateConsoleOnWindows(true); + process->setProcessMode(ProcessMode::Writer); + } else { + QString extraArgsFromOptions = terminal.executeArgs; + CommandLine cmdLine = {terminal.command, {}}; + if (!extraArgsFromOptions.isEmpty()) + cmdLine.addArgs(extraArgsFromOptions, CommandLine::Raw); + cmdLine.addCommandLineAsArgs(setupData.m_commandLine, CommandLine::Raw); + process->setCommand(cmdLine); + } + + process->start(); + process->waitForStarted(); + if (process->error() != QProcess::UnknownError) { + return make_unexpected( + Tr::tr("Failed to start terminal process: \"%1\"").arg(process->errorString())); + } + + qint64 pid = process->processId(); + + return pid; +} + +} // namespace Utils diff --git a/src/libs/utils/externalterminalprocessimpl.h b/src/libs/utils/externalterminalprocessimpl.h new file mode 100644 index 00000000000..cbb3370071b --- /dev/null +++ b/src/libs/utils/externalterminalprocessimpl.h @@ -0,0 +1,29 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "terminalinterface.h" + +namespace Utils { + +class ProcessStubCreator; + +class QTCREATOR_UTILS_EXPORT ExternalTerminalProcessImpl final : public TerminalInterface +{ +public: + ExternalTerminalProcessImpl(); +}; + +class QTCREATOR_UTILS_EXPORT ProcessStubCreator : public StubCreator +{ +public: + ProcessStubCreator(TerminalInterface *interface); + ~ProcessStubCreator() override = default; + + expected_str startStubProcess(const ProcessSetupData &setupData) override; + + TerminalInterface *m_interface; +}; + +} // namespace Utils diff --git a/src/libs/utils/faketooltip.cpp b/src/libs/utils/faketooltip.cpp index 571d2027639..c94ce528e02 100644 --- a/src/libs/utils/faketooltip.cpp +++ b/src/libs/utils/faketooltip.cpp @@ -8,6 +8,7 @@ /*! \class Utils::FakeToolTip + \inmodule QtCreator \brief The FakeToolTip class is a widget that pretends to be a tooltip. diff --git a/src/libs/utils/fancylineedit.cpp b/src/libs/utils/fancylineedit.cpp index a0d1fd9b8aa..08b480c88cf 100644 --- a/src/libs/utils/fancylineedit.cpp +++ b/src/libs/utils/fancylineedit.cpp @@ -11,6 +11,7 @@ #include "utilsicons.h" #include "utilstr.h" +#include #include #include #include @@ -26,6 +27,7 @@ /*! \class Utils::FancyLineEdit + \inmodule QtCreator \brief The FancyLineEdit class is an enhanced line edit with several opt-in features. @@ -115,6 +117,7 @@ public: const QColor m_okTextColor; const QColor m_errorTextColor; + const QColor m_placeholderTextColor; QString m_errorMessage; }; @@ -123,7 +126,9 @@ FancyLineEditPrivate::FancyLineEditPrivate(FancyLineEdit *parent) : m_lineEdit(parent), m_completionShortcut(completionShortcut()->key(), parent), m_okTextColor(creatorTheme()->color(Theme::TextColorNormal)), - m_errorTextColor(creatorTheme()->color(Theme::TextColorError)) + m_errorTextColor(creatorTheme()->color(Theme::TextColorError)), + m_placeholderTextColor(QApplication::palette().color(QPalette::PlaceholderText)) + { m_completionShortcut.setContext(Qt::WidgetShortcut); connect(completionShortcut(), &CompletionShortcut::keyChanged, @@ -172,7 +177,6 @@ FancyLineEdit::FancyLineEdit(QWidget *parent) : CompletingLineEdit(parent), d(new FancyLineEditPrivate(this)) { - ensurePolished(); updateMargins(); connect(d->m_iconbutton[Left], &QAbstractButton::clicked, this, [this] { @@ -486,15 +490,18 @@ void FancyLineEdit::validate() setToolTip(d->m_errorMessage); d->m_toolTipSet = true; } - // Changed..figure out if valid changed. DisplayingPlaceholderText is not valid, - // but should not show error color. Also trigger on the first change. + // Changed..figure out if valid changed. Also trigger on the first change. + // Invalid DisplayingPlaceholderText shows also error color. if (newState != d->m_state || d->m_firstChange) { const bool validHasChanged = (d->m_state == Valid) != (newState == Valid); d->m_state = newState; d->m_firstChange = false; QPalette p = palette(); - p.setColor(QPalette::Active, QPalette::Text, newState == Invalid ? d->m_errorTextColor : d->m_okTextColor); + p.setColor(QPalette::Active, QPalette::Text, + newState == Invalid ? d->m_errorTextColor : d->m_okTextColor); + p.setColor(QPalette::Active, QPalette::PlaceholderText, + validates ? d->m_placeholderTextColor : d->m_errorTextColor); setPalette(p); if (validHasChanged) diff --git a/src/libs/utils/fancymainwindow.cpp b/src/libs/utils/fancymainwindow.cpp index a19fc6c3c76..323e2508eba 100644 --- a/src/libs/utils/fancymainwindow.cpp +++ b/src/libs/utils/fancymainwindow.cpp @@ -311,7 +311,9 @@ void DockWidget::handleToplevelChanged(bool floating) -/*! \class Utils::FancyMainWindow +/*! + \class Utils::FancyMainWindow + \inmodule QtCreator \brief The FancyMainWindow class is a MainWindow with dock widgets and additional "lock" functionality diff --git a/src/libs/utils/fileinprojectfinder.cpp b/src/libs/utils/fileinprojectfinder.cpp index 6ee3c34becd..957f694179b 100644 --- a/src/libs/utils/fileinprojectfinder.cpp +++ b/src/libs/utils/fileinprojectfinder.cpp @@ -40,6 +40,7 @@ static bool checkPath(const FilePath &candidate, int matchLength, /*! \class Utils::FileInProjectFinder + \inmodule QtCreator \brief The FileInProjectFinder class is a helper class to find the \e original file in the project directory for a given file URL. @@ -109,13 +110,16 @@ void FileInProjectFinder::addMappedPath(const FilePath &localFilePath, const QSt } /*! - Returns the best match for the given file URL in the project directory. + Returns the best match for the file URL \a fileUrl in the project directory. The function first checks whether the file inside the project directory exists. If not, the leading directory in the path is stripped, and the - now shorter - path is checked for existence, and so on. Second, it tries to locate the file in the sysroot - folder specified. Third, we walk the list of project files, and search for a file name match - there. If all fails, it returns the original path from the file URL. + folder specified. Third, it walks the list of project files and searches for a file name match + there. + + If all fails, the function returns the original path from the file URL. To + indicate that no match was found in the project, \a success is set to false. */ FilePaths FileInProjectFinder::findFile(const QUrl &fileUrl, bool *success) const { diff --git a/src/libs/utils/filenamevalidatinglineedit.cpp b/src/libs/utils/filenamevalidatinglineedit.cpp index 58112ceb6c0..5602e72d1a1 100644 --- a/src/libs/utils/filenamevalidatinglineedit.cpp +++ b/src/libs/utils/filenamevalidatinglineedit.cpp @@ -10,6 +10,7 @@ /*! \class Utils::FileNameValidatingLineEdit + \inmodule QtCreator \brief The FileNameValidatingLineEdit class is a control that lets the user choose a (base) file name, based on a QLineEdit. diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 88c239a2ee5..9059a37ecc3 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -6,6 +6,7 @@ #include "algorithm.h" #include "devicefileaccess.h" #include "environment.h" +#include "filestreamermanager.h" #include "fileutils.h" #include "hostosinfo.h" #include "qtcassert.h" @@ -36,7 +37,9 @@ static DeviceFileHooks s_deviceHooks; inline bool isWindowsDriveLetter(QChar ch); -/*! \class Utils::FilePath +/*! + \class Utils::FilePath + \inmodule QtCreator \brief The FilePath class is an abstraction for handles to objects in a (possibly remote) file system, similar to a URL or, in the local @@ -90,7 +93,7 @@ inline bool isWindowsDriveLetter(QChar ch); executed on the associated OS. \note The FilePath passed as executable to a CommandLine is typically - not touched by user code. QtcProcess will use it to determine + not touched by user code. The Process will use it to determine the remote system and apply the necessary conversions internally. \li FilePath::toFSPathString() @@ -116,7 +119,7 @@ inline bool isWindowsDriveLetter(QChar ch); Converts the FilePath to the slash convention of the associated OS and adds the scheme and host as a " on " suffix. - This is useful for static user-facing output in he GUI + This is useful for static user-facing output in the GUI. \li FilePath::fromVariant(), FilePath::toVariant() @@ -154,7 +157,7 @@ FilePath::FilePath() } /*! - Constructs a FilePath from \a info + Constructs a FilePath from \a info. */ FilePath FilePath::fromFileInfo(const QFileInfo &info) { @@ -162,13 +165,18 @@ FilePath FilePath::fromFileInfo(const QFileInfo &info) } /*! - \returns a QFileInfo + Returns a QFileInfo. */ QFileInfo FilePath::toFileInfo() const { return QFileInfo(toFSPathString()); } +/*! + Constructs a FilePath from \a variant. + + \sa toVariant() +*/ FilePath FilePath::fromVariant(const QVariant &variant) { return fromSettings(variant); // FIXME: Use variant.value() @@ -214,7 +222,7 @@ QString decodeHost(QString host) } /*! - \returns a QString for passing through QString based APIs + Returns a QString for passing through QString based APIs. \note This is obsolete API and should be replaced by extended use of proper \c FilePath, or, in case this is not possible by \c toFSPathString(). @@ -233,13 +241,16 @@ QString FilePath::toString() const if (!needsDevice()) return path(); + if (pathView().isEmpty()) + return scheme() + "://" + encodedHost(); + if (isRelativePath()) return scheme() + "://" + encodedHost() + "/./" + pathView(); return scheme() + "://" + encodedHost() + pathView(); } /*! - \returns a QString for passing on to QString based APIs + Returns a QString for passing on to QString based APIs. This uses a /__qtc_devices__/host/path setup. @@ -255,6 +266,9 @@ QString FilePath::toFSPathString() const if (scheme().isEmpty()) return path(); + if (pathView().isEmpty()) + return specialRootPath() + '/' + scheme() + '/' + encodedHost(); + if (isRelativePath()) return specialRootPath() + '/' + scheme() + '/' + encodedHost() + "/./" + pathView(); return specialRootPath() + '/' + scheme() + '/' + encodedHost() + pathView(); @@ -272,7 +286,7 @@ QUrl FilePath::toUrl() const } /*! - returns a QString to display to the user, including the device prefix + Returns a QString to display to the user, including the device prefix. Converts the separators to the native format of the system this path belongs to. @@ -289,7 +303,7 @@ QString FilePath::toUserOutput() const } /*! - \returns a QString to pass to target system native commands, without the device prefix. + Returns a QString to pass to target system native commands, without the device prefix. Converts the separators to the native format of the system this path belongs to. @@ -343,7 +357,7 @@ QString FilePath::fileNameWithPathComponents(int pathComponents) const } /*! - \returns the base name of the file without the path. + Returns the base name of the file without the path. The base name consists of all characters in the file up to (but not including) the first '.' character. @@ -355,7 +369,7 @@ QString FilePath::baseName() const } /*! - \returns the complete base name of the file without the path. + Returns the complete base name of the file without the path. The complete base name consists of all characters in the file up to (but not including) the last '.' character. In case of ".ui.qml" @@ -370,7 +384,7 @@ QString FilePath::completeBaseName() const } /*! - \returns the suffix (extension) of the file. + Returns the suffix (extension) of the file. The suffix consists of all characters in the file after (but not including) the last '.'. In case of ".ui.qml" it will @@ -393,7 +407,7 @@ QString FilePath::suffix() const } /*! - \returns the complete suffix (extension) of the file. + Returns the complete suffix (extension) of the file. The complete suffix consists of all characters in the file after (but not including) the first '.'. @@ -431,9 +445,10 @@ void FilePath::setParts(const QStringView scheme, const QStringView host, QStrin { QTC_CHECK(!scheme.contains('/')); - if (path.startsWith(u"/./")) + if (path.length() >= 3 && path[0] == '/' && path[1] == '.' && path[2] == '/') path = path.mid(3); + m_hash = 0; m_data = path.toString() + scheme.toString() + host.toString(); m_schemeLen = scheme.size(); m_hostLen = host.size(); @@ -441,7 +456,7 @@ void FilePath::setParts(const QStringView scheme, const QStringView host, QStrin } /*! - \returns a bool indicating whether a file or directory with this FilePath exists. + Returns a bool indicating whether a file or directory with this FilePath exists. */ bool FilePath::exists() const { @@ -449,7 +464,7 @@ bool FilePath::exists() const } /*! - \returns a bool indicating whether this is a writable directory. + Returns a bool indicating whether this is a writable directory. */ bool FilePath::isWritableDir() const { @@ -457,13 +472,20 @@ bool FilePath::isWritableDir() const } /*! - \returns a bool indicating whether this is a writable file. + Returns a bool indicating whether this is a writable file. */ bool FilePath::isWritableFile() const { return fileAccess()->isWritableFile(*this); } +/*! + \brief Re-uses or creates a directory in this location. + + Returns true if the directory is writable afterwards. + + \sa createDir() +*/ bool FilePath::ensureWritableDir() const { return fileAccess()->ensureWritableDirectory(*this); @@ -474,17 +496,21 @@ bool FilePath::ensureExistingFile() const return fileAccess()->ensureExistingFile(*this); } +/*! + Returns a bool indicating whether this is an executable file. +*/ bool FilePath::isExecutableFile() const { return fileAccess()->isExecutableFile(*this); } /*! - \returns a bool indicating on whether a process with this FilePath's - .nativePath() is likely to start. + Returns a bool indicating on whether a process with this FilePath's + native path is likely to start. - This is equivalent to \c isExecutableFile() in general. - On Windows, it will check appending various suffixes, too. + This is equivalent to \l isExecutableFile() in general. + On Windows, it might append various suffixes depending on + \a matchScope. */ std::optional FilePath::refersToExecutableFile(MatchScope matchScope) const { @@ -496,14 +522,14 @@ expected_str FilePath::tmpDir() const if (needsDevice()) { const Environment env = deviceEnvironment(); if (env.hasKey("TMPDIR")) - return FilePath::fromUserInput(env.value("TMPDIR")).onDevice(*this); + return withNewPath(env.value("TMPDIR")).cleanPath(); if (env.hasKey("TEMP")) - return FilePath::fromUserInput(env.value("TEMP")).onDevice(*this); + return withNewPath(env.value("TEMP")).cleanPath(); if (env.hasKey("TMP")) - return FilePath::fromUserInput(env.value("TMP")).onDevice(*this); + return withNewPath(env.value("TMP")).cleanPath(); if (osType() != OsTypeWindows) - return FilePath("/tmp").onDevice(*this); + return withNewPath("/tmp"); return make_unexpected(QString("Could not find temporary directory on device %1") .arg(displayName())); } @@ -550,6 +576,19 @@ bool FilePath::isSymLink() const return fileAccess()->isSymLink(*this); } +bool FilePath::hasHardLinks() const +{ + return fileAccess()->hasHardLinks(*this); +} + +/*! + \brief Creates a directory in this location. + + Returns true if the directory could be created, false if not, + even if it existed before. + + \sa ensureWritableDir() +*/ bool FilePath::createDir() const { return fileAccess()->createDirectory(*this); @@ -586,9 +625,7 @@ FilePaths FilePath::dirEntries(QDir::Filters filters) const } /*! - This runs \a callBack on each directory entry matching all \a filters and - either of the specified \a nameFilters. - An empty \nameFilters list matches every name. + Runs \a callBack on each directory entry matching the \a filter. */ void FilePath::iterateDirectory(const IterateDirCallback &callBack, const FileFilter &filter) const @@ -620,13 +657,6 @@ bool FilePath::ensureReachable(const FilePath &other) const return false; } -void FilePath::asyncFileContents(const Continuation &> &cont, - qint64 maxSize, - qint64 offset) const -{ - return fileAccess()->asyncFileContents(*this, cont, maxSize, offset); -} - expected_str FilePath::writeFileContents(const QByteArray &data, qint64 offset) const { return fileAccess()->writeFileContents(*this, data, offset); @@ -637,11 +667,21 @@ FilePathInfo FilePath::filePathInfo() const return fileAccess()->filePathInfo(*this); } -void FilePath::asyncWriteFileContents(const Continuation &> &cont, - const QByteArray &data, - qint64 offset) const +FileStreamHandle FilePath::asyncCopy(const FilePath &target, QObject *context, + const CopyContinuation &cont) const { - return fileAccess()->asyncWriteFileContents(*this, cont, data, offset); + return FileStreamerManager::copy(*this, target, context, cont); +} + +FileStreamHandle FilePath::asyncRead(QObject *context, const ReadContinuation &cont) const +{ + return FileStreamerManager::read(*this, context, cont); +} + +FileStreamHandle FilePath::asyncWrite(const QByteArray &data, QObject *context, + const WriteContinuation &cont) const +{ + return FileStreamerManager::write(*this, data, context, cont); } bool FilePath::needsDevice() const @@ -679,20 +719,39 @@ bool FilePath::isSameFile(const FilePath &other) const return false; } -static FilePaths appendExeExtensions(const Environment &env, const FilePath &executable) +static FilePaths appendExeExtensions(const FilePath &executable, + FilePath::MatchScope matchScope) { - FilePaths execs = {executable}; - if (executable.osType() == OsTypeWindows) { - // Check all the executable extensions on windows: - // PATHEXT is only used if the executable has no extension - if (executable.suffixView().isEmpty()) { - const QStringList extensions = env.expandedValueForKey("PATHEXT").split(';'); - - for (const QString &ext : extensions) - execs << executable.stringAppended(ext.toLower()); + FilePaths result = {executable}; + const QStringView suffix = executable.suffixView(); + if (executable.osType() == OsTypeWindows && suffix.isEmpty()) { + switch (matchScope) { + case FilePath::ExactMatchOnly: + break; + case FilePath::WithExeSuffix: + result.append(executable.stringAppended(".exe")); + break; + case FilePath::WithBatSuffix: + result.append(executable.stringAppended(".bat")); + break; + case FilePath::WithExeOrBatSuffix: + result.append(executable.stringAppended(".exe")); + result.append(executable.stringAppended(".bat")); + break; + case FilePath::WithAnySuffix: { + // Check all the executable extensions on windows: + // PATHEXT is only used if the executable has no extension + static const QStringList extensions = Environment::systemEnvironment() + .expandedValueForKey("PATHEXT").split(';'); + for (const QString &ext : extensions) + result.append(executable.stringAppended(ext.toLower())); + break; + } + default: + break; } } - return execs; + return result; } bool FilePath::isSameExecutable(const FilePath &other) const @@ -703,9 +762,8 @@ bool FilePath::isSameExecutable(const FilePath &other) const if (!isSameDevice(other)) return false; - const Environment env = other.deviceEnvironment(); - const FilePaths exe1List = appendExeExtensions(env, *this); - const FilePaths exe2List = appendExeExtensions(env, other); + const FilePaths exe1List = appendExeExtensions(*this, WithAnySuffix); + const FilePaths exe2List = appendExeExtensions(other, WithAnySuffix); for (const FilePath &f1 : exe1List) { for (const FilePath &f2 : exe2List) { if (f1.isSameFile(f2)) @@ -716,7 +774,7 @@ bool FilePath::isSameExecutable(const FilePath &other) const } /*! - \returns an empty FilePath if this is not a symbolic linl + Returns an empty FilePath if this is not a symbolic link. */ FilePath FilePath::symLinkTarget() const { @@ -774,13 +832,154 @@ int FilePath::schemeAndHostLength(const QStringView path) return pos + 1; // scheme://host/ plus something } +static QString normalizePathSegmentHelper(const QString &name) +{ + const int len = name.length(); -/*! Find the parent directory of a given directory. + if (len == 0 || name.contains("%{")) + return name; - Returns an empty FilePath if the current directory is already + int i = len - 1; + QVarLengthArray outVector(len); + int used = len; + char16_t *out = outVector.data(); + const ushort *p = reinterpret_cast(name.data()); + const ushort *prefix = p; + int up = 0; + + const int prefixLength = name.at(0) == u'/' ? 1 : 0; + + p += prefixLength; + i -= prefixLength; + + // replicate trailing slash (i > 0 checks for emptiness of input string p) + // except for remote paths because there can be /../ or /./ ending + if (i > 0 && p[i] == '/') { + out[--used] = '/'; + --i; + } + + while (i >= 0) { + if (p[i] == '/') { + --i; + continue; + } + + // remove current directory + if (p[i] == '.' && (i == 0 || p[i - 1] == '/')) { + --i; + continue; + } + + // detect up dir + if (i >= 1 && p[i] == '.' && p[i - 1] == '.' && (i < 2 || p[i - 2] == '/')) { + ++up; + i -= i >= 2 ? 3 : 2; + continue; + } + + // prepend a slash before copying when not empty + if (!up && used != len && out[used] != '/') + out[--used] = '/'; + + // skip or copy + while (i >= 0) { + if (p[i] == '/') { + --i; + break; + } + + // actual copy + if (!up) + out[--used] = p[i]; + --i; + } + + // decrement up after copying/skipping + if (up) + --up; + } + + // Indicate failure when ".." are left over for an absolute path. + // if (ok) + // *ok = prefixLength == 0 || up == 0; + + // add remaining '..' + while (up) { + if (used != len && out[used] != '/') // is not empty and there isn't already a '/' + out[--used] = '/'; + out[--used] = '.'; + out[--used] = '.'; + --up; + } + + bool isEmpty = used == len; + + if (prefixLength) { + if (!isEmpty && out[used] == '/') { + // Even though there is a prefix the out string is a slash. This happens, if the input + // string only consists of a prefix followed by one or more slashes. Just skip the slash. + ++used; + } + for (int i = prefixLength - 1; i >= 0; --i) + out[--used] = prefix[i]; + } else { + if (isEmpty) { + // After resolving the input path, the resulting string is empty (e.g. "foo/.."). Return + // a dot in that case. + out[--used] = '.'; + } else if (out[used] == '/') { + // After parsing the input string, out only contains a slash. That happens whenever all + // parts are resolved and there is a trailing slash ("./" or "foo/../" for example). + // Prepend a dot to have the correct return value. + out[--used] = '.'; + } + } + + // If path was not modified return the original value + if (used == 0) + return name; + return QString::fromUtf16(out + used, len - used); +} + +QString doCleanPath(const QString &input_) +{ + QString input = input_; + if (input.contains('\\')) + input.replace('\\', '/'); + + if (input.startsWith("//?/")) { + input = input.mid(4); + if (input.startsWith("UNC/")) + input = '/' + input.mid(3); // trick it into reporting two slashs at start + } + + int prefixLen = 0; + const int shLen = FilePath::schemeAndHostLength(input); + if (shLen > 0) { + prefixLen = shLen + FilePath::rootLength(input.mid(shLen)); + } else { + prefixLen = FilePath::rootLength(input); + if (prefixLen > 0 && input.at(prefixLen - 1) == '/') + --prefixLen; + } + + QString path = normalizePathSegmentHelper(input.mid(prefixLen)); + + // Strip away last slash except for root directories + if (path.size() > 1 && path.endsWith(u'/')) + path.chop(1); + + return input.left(prefixLen) + path; +} + +/*! + Finds the parent directory of the file path. + + Returns an empty file path if the file path is already a root level directory. - \returns \a FilePath with the last segment removed. + Returns a file path with the last segment removed. */ FilePath FilePath::parentDir() const { @@ -848,6 +1047,15 @@ FilePath FilePath::normalizedPathName() const return result; } +/*! + Converts the file path to the slash convention of the associated + OS and adds the scheme and host as a " on " suffix. + + This is useful for static user-facing output in the GUI. + + If \a args is not empty, it is added to the output after the file path: + " on ". +*/ QString FilePath::displayName(const QString &args) const { QString deviceName; @@ -1010,10 +1218,10 @@ bool FilePath::hasFileAccess() const } /*! - Constructs a FilePath from \a filePath. The \a defaultExtension is appended - to \a filePath if that does not have an extension already. + Constructs a FilePath from \a filepath. The \a defaultExtension is appended + to \a filepath if that does not have an extension already. - \a filePath is not checked for validity. + \a filepath is not checked for validity. */ FilePath FilePath::fromStringWithExtension(const QString &filepath, const QString &defaultExtension) { @@ -1034,20 +1242,21 @@ FilePath FilePath::fromStringWithExtension(const QString &filepath, const QStrin /*! Constructs a FilePath from \a filePath - The path \a filePath is cleaned and ~ replaces by the home path. + The path \a filePath is cleaned, and ~ is replaced by the home path. */ FilePath FilePath::fromUserInput(const QString &filePath) { - QString clean = doCleanPath(filePath); - if (clean.startsWith(QLatin1String("~/"))) - return FileUtils::homePath().pathAppended(clean.mid(2)); - return FilePath::fromString(clean); + const QString expandedPath = filePath.startsWith("~/") + ? (QDir::homePath() + "/" + filePath.mid(2)) + : filePath; + return FilePath::fromString(doCleanPath(expandedPath)); } /*! - Constructs a FilePath from \a filePath, which is encoded as UTF-8. + Constructs a FilePath from \a filename with \a filenameSize, which is + encoded as UTF-8. - \a filePath is not checked for validity. + \a filename is not checked for validity. */ FilePath FilePath::fromUtf8(const char *filename, int filenameSize) { @@ -1068,6 +1277,12 @@ QVariant FilePath::toSettings() const return toString(); } +/*! + Returns the FilePath as a variant. + + To be used for type-agnostic internal interfaces like storage in + QAbstractItemModels. +*/ QVariant FilePath::toVariant() const { // FIXME: Use qVariantFromValue @@ -1075,7 +1290,7 @@ QVariant FilePath::toVariant() const } /*! - \returns whether FilePath is a child of \a s + Returns whether FilePath is a child of \a s. */ bool FilePath::isChildOf(const FilePath &s) const { @@ -1097,7 +1312,7 @@ bool FilePath::isChildOf(const FilePath &s) const } /*! - \returns whether \c path() starts with \a s. + Returns whether \c path() starts with \a s. */ bool FilePath::startsWith(const QString &s) const { @@ -1105,7 +1320,7 @@ bool FilePath::startsWith(const QString &s) const } /*! - \returns whether \c path() ends with \a s. + Returns whether \c path() ends with \a s. */ bool FilePath::endsWith(const QString &s) const { @@ -1113,7 +1328,7 @@ bool FilePath::endsWith(const QString &s) const } /*! - \returns whether \c path() contains \a s. + Returns whether \c path() contains \a s. */ bool FilePath::contains(const QString &s) const { @@ -1124,7 +1339,8 @@ bool FilePath::contains(const QString &s) const \brief Checks whether the FilePath starts with a drive letter. Defaults to \c false if it is a non-Windows host or represents a path on device - \returns whether FilePath starts with a drive letter + + Returns whether FilePath starts with a drive letter */ bool FilePath::startsWithDriveLetter() const { @@ -1135,10 +1351,11 @@ bool FilePath::startsWithDriveLetter() const /*! \brief Relative path from \a parent to this. - Returns a empty FilePath if this is not a child of \p parent. + Returns a empty \c FilePath if this is not a child of \a parent. + \a parent is the Parent to calculate the relative path to. That is, this never returns a path starting with "../" - \param parent The Parent to calculate the relative path to. - \returns The relative path of this to \p parent if this is a child of \p parent. + + Returns the relative path of this to \a parent if this is a child of \a parent. */ FilePath FilePath::relativeChildPath(const FilePath &parent) const { @@ -1153,7 +1370,7 @@ FilePath FilePath::relativeChildPath(const FilePath &parent) const } /*! - \returns the relative path of FilePath from a given \a anchor. + Returns the relative path of FilePath from a given \a anchor. Both, FilePath and anchor may be files or directories. Example usage: @@ -1198,8 +1415,10 @@ FilePath FilePath::relativePathFrom(const FilePath &anchor) const } /*! - \returns the relativePath of \a absolutePath to given \a absoluteAnchorPath. - Both paths must be an absolute path to a directory. Example usage: + Returns the relative path of \a absolutePath to given \a absoluteAnchorPath. + Both paths must be an absolute path to a directory. + + Example usage: \code qDebug() << FilePath::calcRelativePath("/foo/b/ar", "/foo/c"); @@ -1207,7 +1426,7 @@ FilePath FilePath::relativePathFrom(const FilePath &anchor) const The debug output will be "../b/ar". - \see FilePath::relativePath + \see FilePath::isRelativePath(), FilePath::relativePathFrom(), FilePath::relativeChildPath() */ QString FilePath::calcRelativePath(const QString &absolutePath, const QString &absoluteAnchorPath) { @@ -1247,33 +1466,31 @@ QString FilePath::calcRelativePath(const QString &absolutePath, const QString &a } /*! - \brief Returns a path corresponding to the current object on the + Returns a path corresponding to \a newPath object on the + same device as the current object. - same device as \a deviceTemplate. The FilePath needs to be local. + This may involve device-specific translations like converting + windows style paths to unix style paths with suitable file + system case or handling of drive letters: C:/dev/src -> /c/dev/src Example usage: \code localDir = FilePath("/tmp/workingdir"); executable = FilePath::fromUrl("docker://123/bin/ls") - realDir = localDir.onDevice(executable) + realDir = executable.withNewMappedPath(localDir) assert(realDir == FilePath::fromUrl("docker://123/tmp/workingdir")) \endcode - - \param deviceTemplate A path from which the host and scheme is taken. - - \returns A path on the same device as \a deviceTemplate. */ -FilePath FilePath::onDevice(const FilePath &deviceTemplate) const +FilePath FilePath::withNewMappedPath(const FilePath &newPath) const { - isSameDevice(deviceTemplate); - const bool sameDevice = scheme() == deviceTemplate.scheme() && host() == deviceTemplate.host(); + const bool sameDevice = newPath.scheme() == scheme() && newPath.host() == host(); if (sameDevice) - return *this; + return newPath; // TODO: converting paths between different non local devices is still unsupported - QTC_CHECK(!needsDevice() || !deviceTemplate.needsDevice()); - return fromParts(deviceTemplate.scheme(), - deviceTemplate.host(), - deviceTemplate.fileAccess()->mapToDevicePath(path())); + QTC_CHECK(!newPath.needsDevice() || !needsDevice()); + FilePath res; + res.setParts(scheme(), host(), fileAccess()->mapToDevicePath(newPath.path())); + return res; } /*! @@ -1295,8 +1512,9 @@ FilePath FilePath::withNewPath(const QString &newPath) const } /*! - Search for a binary corresponding to this object in the PATH of - the device implied by this object's scheme and host. + Search for a binary corresponding to this object on each directory entry + specified by \a dirs matching the \a filter with the \a matchScope of the + file path. Example usage: \code @@ -1305,32 +1523,121 @@ FilePath FilePath::withNewPath(const QString &newPath) const assert(fullPath == FilePath::fromUrl("docker://123/usr/bin/make")) \endcode */ -FilePath FilePath::searchInDirectories(const FilePaths &dirs, const PathFilter &filter) const + +FilePath FilePath::searchInDirectories(const FilePaths &dirs, + const FilePathPredicate &filter, + MatchScope matchScope) const { - if (isAbsolutePath()) - return *this; - return deviceEnvironment().searchInDirectories(path(), dirs, filter); + if (isEmpty()) + return {}; + + const FilePaths execs = appendExeExtensions(*this, matchScope); + + if (isAbsolutePath()) { + for (const FilePath &filePath : execs) { + if (filePath.isExecutableFile() && (!filter || filter(filePath))) + return filePath; + } + return {}; + } + + QSet alreadyCheckedDirectories; + + for (const FilePath &dir : dirs) { + // Compare the initial size of the set with the size after insertion to check + // if the directory was already checked. + const int initialCount = alreadyCheckedDirectories.count(); + alreadyCheckedDirectories.insert(dir); + const bool wasAlreadyChecked = alreadyCheckedDirectories.count() == initialCount; + + if (dir.isEmpty() || wasAlreadyChecked) + continue; + + for (const FilePath &exe : execs) { + const FilePath filePath = dir / exe.path(); + if (filePath.isExecutableFile() && (!filter || filter(filePath))) + return filePath; + } + } + + return {}; } -FilePath FilePath::searchInPath(const FilePaths &additionalDirs, - PathAmending amending, - const PathFilter &filter) const +FilePaths FilePath::searchAllInDirectories(const FilePaths &dirs, + const FilePathPredicate &filter, + MatchScope matchScope) const { - if (isAbsolutePath()) - return *this; - FilePaths directories = deviceEnvironment().path(); - if (needsDevice()) { - directories = Utils::transform(directories, [this](const FilePath &path) { - return path.onDevice(*this); - }); + if (isEmpty()) + return {}; + + const FilePaths execs = appendExeExtensions(*this, matchScope); + + FilePaths result; + if (isAbsolutePath()) { + for (const FilePath &filePath : execs) { + if (filePath.isExecutableFile() && (!filter || filter(filePath))) + result.append(filePath); + } + return result; } + + QSet alreadyCheckedDirectories; + + for (const FilePath &dir : dirs) { + // Compare the initial size of the set with the size after insertion to check + // if the directory was already checked. + const int initialCount = alreadyCheckedDirectories.count(); + alreadyCheckedDirectories.insert(dir); + const bool wasAlreadyChecked = alreadyCheckedDirectories.count() == initialCount; + + if (dir.isEmpty() || wasAlreadyChecked) + continue; + + for (const FilePath &exe : execs) { + const FilePath filePath = dir / exe.path(); + if (filePath.isExecutableFile() && (!filter || filter(filePath))) + result.append(filePath); + } + } + + return result; +} + +static FilePaths dirsFromPath(const FilePath &anchor, + const FilePaths &additionalDirs, + FilePath::PathAmending amending) +{ + FilePaths directories = anchor.devicePathEnvironmentVariable(); + if (!additionalDirs.isEmpty()) { - if (amending == AppendToPath) + if (amending == FilePath::AppendToPath) directories.append(additionalDirs); else directories = additionalDirs + directories; } - return searchInDirectories(directories, filter); + + return directories; +} + +FilePath FilePath::searchInPath(const FilePaths &additionalDirs, + PathAmending amending, + const FilePathPredicate &filter, + MatchScope matchScope) const +{ + if (isAbsolutePath()) + return *this; + + const FilePaths directories = dirsFromPath(*this, additionalDirs, amending); + return searchInDirectories(directories, filter, matchScope); +} + +FilePaths FilePath::searchAllInPath(const FilePaths &additionalDirs, + PathAmending amending, + const FilePathPredicate &filter, + MatchScope matchScope) const +{ + const FilePaths directories = dirsFromPath(*this, additionalDirs, amending); + return searchAllInDirectories(directories, filter, matchScope); } Environment FilePath::deviceEnvironment() const @@ -1342,6 +1649,16 @@ Environment FilePath::deviceEnvironment() const return Environment::systemEnvironment(); } +FilePaths FilePath::devicePathEnvironmentVariable() const +{ + FilePaths result = deviceEnvironment().path(); + if (needsDevice()) { + for (FilePath &dir : result) + dir.setParts(this->scheme(), this->host(), dir.path()); + } + return result; +} + QString FilePath::formatFilePaths(const FilePaths &files, const QString &separator) { const QStringList nativeFiles = transform(files, &FilePath::toUserOutput); @@ -1359,11 +1676,17 @@ void FilePath::removeDuplicates(FilePaths &files) void FilePath::sort(FilePaths &files) { - // FIXME: Improve. - // FIXME: This drops the osType information, which is not correct. - QStringList list = transform(files, &FilePath::toString); - list.sort(); - files = FileUtils::toFilePathList(list); + std::sort(files.begin(), files.end(), [](const FilePath &a, const FilePath &b) { + const int scheme = a.scheme().compare(b.scheme()); + if (scheme != 0) + return scheme < 0; + + const int host = a.host().compare(b.host()); + if (host != 0) + return host < 0; + + return a.pathView() < b.pathView(); + }); } void join(QString &left, const QString &right) @@ -1416,7 +1739,11 @@ bool FilePath::setPermissions(QFile::Permissions permissions) const OsType FilePath::osType() const { - return fileAccess()->osType(*this); + if (!needsDevice()) + return HostOsInfo::hostOs(); + + QTC_ASSERT(s_deviceHooks.osType, return HostOsInfo::hostOs()); + return s_deviceHooks.osType(*this); } bool FilePath::removeFile() const @@ -1429,7 +1756,7 @@ bool FilePath::removeFile() const \note The \a error parameter is optional. - \returns A bool indicating whether the operation succeeded. + Returns a Bool indicating whether the operation succeeded. */ bool FilePath::removeRecursively(QString *error) const { @@ -1468,26 +1795,6 @@ expected_str FilePath::copyFile(const FilePath &target) const return fileAccess()->copyFile(*this, target); } -void FilePath::asyncCopyFile(const Continuation &> &cont, - const FilePath &target) const -{ - if (host() != target.host()) { - asyncFileContents([cont, target](const expected_str &contents) { - if (contents) - target.asyncWriteFileContents( - [cont](const expected_str &result) { - if (result) - cont({}); - else - cont(make_unexpected(result.error())); - }, - *contents); - }); - return; - } - return fileAccess()->asyncCopyFile(*this, cont, target); -} - bool FilePath::renameFile(const FilePath &target) const { return fileAccess()->renameFile(*this, target); @@ -1504,16 +1811,16 @@ qint64 FilePath::bytesAvailable() const } /*! - \brief Checks if this is newer than \p timeStamp + \brief Checks if this is newer than \a timeStamp. - \param timeStamp The time stamp to compare with - \returns true if this is newer than \p timeStamp. - If this is a directory, the function will recursively check all files and return - true if one of them is newer than \a timeStamp. If this is a single file, true will - be returned if the file is newer than \a timeStamp. + The time stamp \a timeStamp to compare with. + Returns \c true if this is newer than \a timeStamp. + If this is a directory, the function will recursively check all files and return + \c true if one of them is newer than \a timeStamp. If this is a single file, \c true will + be returned if the file is newer than \a timeStamp. - Returns whether at least one file in \a filePath has a newer date than - \p timeStamp. + Returns whether at least one file in the file path has a newer date than + \a timeStamp. */ bool FilePath::isNewerThan(const QDateTime &timeStamp) const { @@ -1532,7 +1839,6 @@ bool FilePath::isNewerThan(const QDateTime &timeStamp) const /*! \brief Returns the caseSensitivity of the path. - \returns The caseSensitivity of the path. This is currently only based on the Host OS. For device paths, \c Qt::CaseSensitive is always returned. */ @@ -1551,7 +1857,7 @@ Qt::CaseSensitivity FilePath::caseSensitivity() const /*! \brief Returns the separator of path components for this path. - \returns The path separator of the path. + Returns the path separator of the path. */ QChar FilePath::pathComponentSeparator() const { @@ -1561,7 +1867,7 @@ QChar FilePath::pathComponentSeparator() const /*! \brief Returns the path list separator for the device this path belongs to. - \returns The path list separator of the device for this path + Returns the path list separator of the device for this path. */ QChar FilePath::pathListSeparator() const { @@ -1577,7 +1883,7 @@ QChar FilePath::pathListSeparator() const \note Maximum recursion depth == 16. - \returns the symlink target file path. + Returns the symlink target file path. */ FilePath FilePath::resolveSymlinks() const { @@ -1593,12 +1899,13 @@ FilePath FilePath::resolveSymlinks() const } /*! -* \brief Recursively resolves possibly present symlinks in this file name. -* On Windows, also resolves SUBST and re-mounted NTFS drives. -* Unlike QFileInfo::canonicalFilePath(), this function will not return an empty -* string if path doesn't exist. -* -* \returns the canonical path. + Recursively resolves possibly present symlinks in this file name. + + On Windows, also resolves SUBST and re-mounted NTFS drives. + Unlike QFileInfo::canonicalFilePath(), this function will not return an empty + string if path doesn't exist. + + Returns the canonical path. */ FilePath FilePath::canonicalPath() const { @@ -1607,7 +1914,7 @@ FilePath FilePath::canonicalPath() const return *this; } -#ifdef Q_OS_WINDOWS +#ifdef Q_OS_WIN DWORD flagsAndAttrs = FILE_ATTRIBUTE_NORMAL; if (isDir()) flagsAndAttrs |= FILE_FLAG_BACKUP_SEMANTICS; @@ -1655,7 +1962,7 @@ void FilePath::clear() /*! \brief Checks if the path() is empty. - \returns true if the path() is empty. + Returns true if the path() is empty. The Host and Scheme of the part are ignored. */ bool FilePath::isEmpty() const @@ -1669,7 +1976,7 @@ bool FilePath::isEmpty() const Like QDir::toNativeSeparators(), but use prefix '~' instead of $HOME on unix systems when an absolute path is given. - \returns the possibly shortened path with native separators. + Returns the possibly shortened path with native separators. */ QString FilePath::shortNativePath() const { @@ -1684,9 +1991,9 @@ QString FilePath::shortNativePath() const } /*! - \brief Checks whether the path is relative + \brief Checks whether the path is relative. - \returns true if the path is relative. + Returns true if the path is relative. */ bool FilePath::isRelativePath() const { @@ -1701,10 +2008,9 @@ bool FilePath::isRelativePath() const } /*! - \brief Appends the tail to this, if the tail is a relative path. + \brief Appends the \a tail to this, if the tail is a relative path. - \param tail The tail to append. - \returns Returns tail if tail is absolute, otherwise this + tail. + Returns the tail if the tail is absolute, otherwise this + tail. */ FilePath FilePath::resolvePath(const FilePath &tail) const { @@ -1716,10 +2022,9 @@ FilePath FilePath::resolvePath(const FilePath &tail) const } /*! - \brief Appends the tail to this, if the tail is a relative path. + \brief Appends the \a tail to this, if the tail is a relative path. - \param tail The tail to append. - \returns Returns tail if tail is absolute, otherwise this + tail. + Returns the tail if the tail is absolute, otherwise this + tail. */ FilePath FilePath::resolvePath(const QString &tail) const { @@ -1737,7 +2042,7 @@ expected_str FilePath::localSource() const } /*! - \brief Cleans path part similar to QDir::cleanPath() + \brief Cleans path part similar to \c QDir::cleanPath(). \list \li directory separators normalized (that is, platform-native @@ -1782,150 +2087,7 @@ QTextStream &operator<<(QTextStream &s, const FilePath &fn) return s << fn.toString(); } -static QString normalizePathSegmentHelper(const QString &name) -{ - const int len = name.length(); - - if (len == 0 || name.contains("%{")) - return name; - - int i = len - 1; - QVarLengthArray outVector(len); - int used = len; - char16_t *out = outVector.data(); - const ushort *p = reinterpret_cast(name.data()); - const ushort *prefix = p; - int up = 0; - - const int prefixLength = name.at(0) == u'/' ? 1 : 0; - - p += prefixLength; - i -= prefixLength; - - // replicate trailing slash (i > 0 checks for emptiness of input string p) - // except for remote paths because there can be /../ or /./ ending - if (i > 0 && p[i] == '/') { - out[--used] = '/'; - --i; - } - - while (i >= 0) { - if (p[i] == '/') { - --i; - continue; - } - - // remove current directory - if (p[i] == '.' && (i == 0 || p[i-1] == '/')) { - --i; - continue; - } - - // detect up dir - if (i >= 1 && p[i] == '.' && p[i-1] == '.' && (i < 2 || p[i - 2] == '/')) { - ++up; - i -= i >= 2 ? 3 : 2; - continue; - } - - // prepend a slash before copying when not empty - if (!up && used != len && out[used] != '/') - out[--used] = '/'; - - // skip or copy - while (i >= 0) { - if (p[i] == '/') { - --i; - break; - } - - // actual copy - if (!up) - out[--used] = p[i]; - --i; - } - - // decrement up after copying/skipping - if (up) - --up; - } - - // Indicate failure when ".." are left over for an absolute path. -// if (ok) -// *ok = prefixLength == 0 || up == 0; - - // add remaining '..' - while (up) { - if (used != len && out[used] != '/') // is not empty and there isn't already a '/' - out[--used] = '/'; - out[--used] = '.'; - out[--used] = '.'; - --up; - } - - bool isEmpty = used == len; - - if (prefixLength) { - if (!isEmpty && out[used] == '/') { - // Even though there is a prefix the out string is a slash. This happens, if the input - // string only consists of a prefix followed by one or more slashes. Just skip the slash. - ++used; - } - for (int i = prefixLength - 1; i >= 0; --i) - out[--used] = prefix[i]; - } else { - if (isEmpty) { - // After resolving the input path, the resulting string is empty (e.g. "foo/.."). Return - // a dot in that case. - out[--used] = '.'; - } else if (out[used] == '/') { - // After parsing the input string, out only contains a slash. That happens whenever all - // parts are resolved and there is a trailing slash ("./" or "foo/../" for example). - // Prepend a dot to have the correct return value. - out[--used] = '.'; - } - } - - // If path was not modified return the original value - if (used == 0) - return name; - return QString::fromUtf16(out + used, len - used); -} - -QString doCleanPath(const QString &input_) -{ - QString input = input_; - if (input.contains('\\')) - input.replace('\\', '/'); - - if (input.startsWith("//?/")) { - input = input.mid(4); - if (input.startsWith("UNC/")) - input = '/' + input.mid(3); // trick it into reporting two slashs at start - } - - int prefixLen = 0; - const int shLen = FilePath::schemeAndHostLength(input); - if (shLen > 0) { - prefixLen = shLen + FilePath::rootLength(input.mid(shLen)); - } else { - prefixLen = FilePath::rootLength(input); - if (prefixLen > 0 && input.at(prefixLen - 1) == '/') - --prefixLen; - } - - QString path = normalizePathSegmentHelper(input.mid(prefixLen)); - - // Strip away last slash except for root directories - if (path.size() > 1 && path.endsWith(u'/')) - path.chop(1); - - return input.left(prefixLen) + path; -} - - // FileFilter - FileFilter::FileFilter(const QStringList &nameFilters, const QDir::Filters fileFilters, const QDirIterator::IteratorFlags flags) @@ -2021,6 +2183,9 @@ DeviceFileHooks &DeviceFileHooks::instance() QTCREATOR_UTILS_EXPORT bool operator==(const FilePath &first, const FilePath &second) { + if (first.m_hash != 0 && second.m_hash != 0 && first.m_hash != second.m_hash) + return false; + return first.pathView().compare(second.pathView(), first.caseSensitivity()) == 0 && first.host() == second.host() && first.scheme() == second.scheme(); @@ -2058,9 +2223,16 @@ QTCREATOR_UTILS_EXPORT bool operator>=(const FilePath &first, const FilePath &se QTCREATOR_UTILS_EXPORT size_t qHash(const FilePath &filePath, uint seed) { - if (filePath.caseSensitivity() == Qt::CaseInsensitive) - return qHash(filePath.path().toCaseFolded(), seed); - return qHash(filePath.path(), seed); + Q_UNUSED(seed); + + if (filePath.m_hash == 0) { + if (filePath.caseSensitivity() == Qt::CaseSensitive) + filePath.m_hash = qHash(QStringView(filePath.m_data), 0); + else + filePath.m_hash = qHash(filePath.m_data.toCaseFolded(), 0); + } + + return filePath.m_hash; } QTCREATOR_UTILS_EXPORT size_t qHash(const FilePath &filePath) diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index 4c61786eba4..f313ba5ff92 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -8,6 +8,7 @@ #include "expected.h" #include "filepathinfo.h" #include "osspecificaspects.h" +#include "utiltypes.h" #include #include @@ -31,9 +32,12 @@ namespace Utils { class DeviceFileAccess; class Environment; -class EnvironmentChange; +enum class FileStreamHandle; template using Continuation = std::function; +using CopyContinuation = Continuation &>; +using ReadContinuation = Continuation &>; +using WriteContinuation = Continuation &>; class QTCREATOR_UTILS_EXPORT FileFilter { @@ -51,8 +55,6 @@ public: using FilePaths = QList; -enum class IterationPolicy { Stop, Continue }; - class QTCREATOR_UTILS_EXPORT FilePath { public: @@ -116,6 +118,7 @@ public: bool isFile() const; bool isDir() const; bool isSymLink() const; + bool hasHardLinks() const; bool isRootPath() const; bool isNewerThan(const QDateTime &timeStamp) const; QDateTime lastModified() const; @@ -158,11 +161,10 @@ public: [[nodiscard]] FilePath withExecutableSuffix() const; [[nodiscard]] FilePath relativeChildPath(const FilePath &parent) const; [[nodiscard]] FilePath relativePathFrom(const FilePath &anchor) const; - [[nodiscard]] FilePath searchInDirectories(const FilePaths &dirs, - const PathFilter &filter = {}) const; [[nodiscard]] Environment deviceEnvironment() const; - [[nodiscard]] FilePath onDevice(const FilePath &deviceTemplate) const; + [[nodiscard]] FilePaths devicePathEnvironmentVariable() const; [[nodiscard]] FilePath withNewPath(const QString &newPath) const; + [[nodiscard]] FilePath withNewMappedPath(const FilePath &newPath) const; using IterateDirCallback = std::variant< @@ -180,12 +182,24 @@ public: const FileFilter &filter); enum PathAmending { AppendToPath, PrependToPath }; - [[nodiscard]] FilePath searchInPath(const FilePaths &additionalDirs = {}, - PathAmending = AppendToPath, - const PathFilter &filter = {}) const; - enum MatchScope { ExactMatchOnly, WithExeSuffix, WithBatSuffix, WithExeOrBatSuffix, WithAnySuffix }; + + [[nodiscard]] FilePath searchInDirectories(const FilePaths &dirs, + const FilePathPredicate &filter = {}, + MatchScope matchScope = WithAnySuffix) const; + [[nodiscard]] FilePaths searchAllInDirectories(const FilePaths &dirs, + const FilePathPredicate &filter = {}, + MatchScope matchScope = WithAnySuffix) const; + [[nodiscard]] FilePath searchInPath(const FilePaths &additionalDirs = {}, + PathAmending = AppendToPath, + const FilePathPredicate &filter = {}, + MatchScope matchScope = WithAnySuffix) const; + [[nodiscard]] FilePaths searchAllInPath(const FilePaths &additionalDirs = {}, + PathAmending = AppendToPath, + const FilePathPredicate &filter = {}, + MatchScope matchScope = WithAnySuffix) const; + std::optional refersToExecutableFile(MatchScope considerScript) const; [[nodiscard]] expected_str tmpDir() const; @@ -207,14 +221,11 @@ public: static void sort(FilePaths &files); // Asynchronous interface - void asyncCopyFile(const Continuation &> &cont, - const FilePath &target) const; - void asyncFileContents(const Continuation &> &cont, - qint64 maxSize = -1, - qint64 offset = 0) const; - void asyncWriteFileContents(const Continuation &> &cont, - const QByteArray &data, - qint64 offset = 0) const; + FileStreamHandle asyncCopy(const FilePath &target, QObject *context, + const CopyContinuation &cont = {}) const; + FileStreamHandle asyncRead(QObject *context, const ReadContinuation &cont = {}) const; + FileStreamHandle asyncWrite(const QByteArray &data, QObject *context, + const WriteContinuation &cont = {}) const; // Prefer not to use // Using needsDevice() in "user" code is likely to result in code that @@ -277,6 +288,7 @@ private: unsigned int m_pathLen = 0; unsigned short m_schemeLen = 0; unsigned short m_hostLen = 0; + mutable size_t m_hash = 0; }; class QTCREATOR_UTILS_EXPORT DeviceFileHooks @@ -291,8 +303,12 @@ public: std::function isSameDevice; std::function(const FilePath &)> localSource; std::function openTerminal; + std::function osType; }; +// For testing +QTCREATOR_UTILS_EXPORT QString doCleanPath(const QString &input); + } // Utils Q_DECLARE_METATYPE(Utils::FilePath) diff --git a/src/libs/utils/filesearch.cpp b/src/libs/utils/filesearch.cpp index ccbcfbfb9f9..a72ddc6ab04 100644 --- a/src/libs/utils/filesearch.cpp +++ b/src/libs/utils/filesearch.cpp @@ -7,6 +7,7 @@ #include "filepath.h" #include "mapreduce.h" #include "qtcassert.h" +#include "searchresultitem.h" #include "stringutils.h" #include "utilstr.h" @@ -68,7 +69,7 @@ public: FileSearch(const QString &searchTerm, QTextDocument::FindFlags flags, const QMap &fileToContentsMap); - void operator()(QFutureInterface &futureInterface, + void operator()(QFutureInterface &futureInterface, const FileIterator::Item &item) const; private: @@ -90,7 +91,7 @@ public: QTextDocument::FindFlags flags, const QMap &fileToContentsMap); FileSearchRegExp(const FileSearchRegExp &other); - void operator()(QFutureInterface &futureInterface, + void operator()(QFutureInterface &futureInterface, const FileIterator::Item &item) const; private: @@ -116,7 +117,7 @@ FileSearch::FileSearch(const QString &searchTerm, termDataUpper = searchTermUpper.constData(); } -void FileSearch::operator()(QFutureInterface &futureInterface, +void FileSearch::operator()(QFutureInterface &futureInterface, const FileIterator::Item &item) const { if (futureInterface.isCanceled()) @@ -124,7 +125,7 @@ void FileSearch::operator()(QFutureInterface &futureInterf qCDebug(searchLog) << "Searching in" << item.filePath; futureInterface.setProgressRange(0, 1); futureInterface.setProgressValue(0); - FileSearchResultList results; + SearchResultItems results; QString tempString; if (!getFileContent(item.filePath, item.encoding, &tempString, fileToContentsMap)) { qCDebug(searchLog) << "- failed to get content for" << item.filePath; @@ -189,13 +190,13 @@ void FileSearch::operator()(QFutureInterface &futureInterf } } if (equal) { - const QString resultItemText = clippedText(chunk, MAX_LINE_SIZE); - results << FileSearchResult(item.filePath, - lineNr, - resultItemText, - regionPtr - chunkPtr, - termMaxIndex + 1, - QStringList()); + SearchResultItem result; + result.setFilePath(item.filePath); + result.setMainRange(lineNr, regionPtr - chunkPtr, termMaxIndex + 1); + result.setDisplayText(clippedText(chunk, MAX_LINE_SIZE)); + result.setUserData(QStringList()); + result.setUseTextEditorFont(true); + results << result; regionPtr += termMaxIndex; // another +1 done by for-loop } } @@ -237,7 +238,7 @@ QRegularExpressionMatch FileSearchRegExp::doGuardedMatch(const QString &line, in return expression.match(line, offset); } -void FileSearchRegExp::operator()(QFutureInterface &futureInterface, +void FileSearchRegExp::operator()(QFutureInterface &futureInterface, const FileIterator::Item &item) const { if (!expression.isValid()) { @@ -249,7 +250,7 @@ void FileSearchRegExp::operator()(QFutureInterface &future qCDebug(searchLog) << "Searching in" << item.filePath; futureInterface.setProgressRange(0, 1); futureInterface.setProgressValue(0); - FileSearchResultList results; + SearchResultItems results; QString tempString; if (!getFileContent(item.filePath, item.encoding, &tempString, fileToContentsMap)) { qCDebug(searchLog) << "- failed to get content for" << item.filePath; @@ -269,12 +270,13 @@ void FileSearchRegExp::operator()(QFutureInterface &future int pos = 0; while ((match = doGuardedMatch(line, pos)).hasMatch()) { pos = match.capturedStart(); - results << FileSearchResult(item.filePath, - lineNr, - resultItemText, - pos, - match.capturedLength(), - match.capturedTexts()); + SearchResultItem result; + result.setFilePath(item.filePath); + result.setMainRange(lineNr, pos, match.capturedLength()); + result.setDisplayText(resultItemText); + result.setUserData(match.capturedTexts()); + result.setUseTextEditorFont(true); + results << result; if (match.capturedLength() == 0) break; pos += match.capturedLength(); @@ -298,12 +300,12 @@ struct SearchState SearchState(const QString &term, FileIterator *iterator) : searchTerm(term), files(iterator) {} QString searchTerm; FileIterator *files = nullptr; - FileSearchResultList cachedResults; + SearchResultItems cachedResults; int numFilesSearched = 0; int numMatches = 0; }; -SearchState initFileSearch(QFutureInterface &futureInterface, +SearchState initFileSearch(QFutureInterface &futureInterface, const QString &searchTerm, FileIterator *files) { futureInterface.setProgressRange(0, files->maxProgress()); @@ -311,9 +313,9 @@ SearchState initFileSearch(QFutureInterface &futureInterfa return SearchState(searchTerm, files); } -void collectSearchResults(QFutureInterface &futureInterface, +void collectSearchResults(QFutureInterface &futureInterface, SearchState &state, - const FileSearchResultList &results) + const SearchResultItems &results) { state.numMatches += results.size(); state.cachedResults << results; @@ -332,7 +334,7 @@ void collectSearchResults(QFutureInterface &futureInterfac } } -void cleanUpFileSearch(QFutureInterface &futureInterface, +void cleanUpFileSearch(QFutureInterface &futureInterface, SearchState &state) { if (!state.cachedResults.isEmpty()) { @@ -355,13 +357,13 @@ void cleanUpFileSearch(QFutureInterface &futureInterface, } // namespace -QFuture Utils::findInFiles(const QString &searchTerm, - FileIterator *files, - QTextDocument::FindFlags flags, - const QMap &fileToContentsMap) +QFuture Utils::findInFiles(const QString &searchTerm, + FileIterator *files, + QTextDocument::FindFlags flags, + const QMap &fileToContentsMap) { return mapReduce(files->begin(), files->end(), - [searchTerm, files](QFutureInterface &futureInterface) { + [searchTerm, files](QFutureInterface &futureInterface) { return initFileSearch(futureInterface, searchTerm, files); }, FileSearch(searchTerm, flags, fileToContentsMap), @@ -369,14 +371,14 @@ QFuture Utils::findInFiles(const QString &searchTerm, &cleanUpFileSearch); } -QFuture Utils::findInFilesRegExp( +QFuture Utils::findInFilesRegExp( const QString &searchTerm, FileIterator *files, QTextDocument::FindFlags flags, const QMap &fileToContentsMap) { return mapReduce(files->begin(), files->end(), - [searchTerm, files](QFutureInterface &futureInterface) { + [searchTerm, files](QFutureInterface &futureInterface) { return initFileSearch(futureInterface, searchTerm, files); }, FileSearchRegExp(searchTerm, flags, fileToContentsMap), @@ -496,16 +498,6 @@ static bool isFileIncluded(const QList &filterRegs, return isIncluded && (exclusionRegs.isEmpty() || !matches(exclusionRegs, filePath)); } -std::function filterFileFunction(const QStringList &filters, - const QStringList &exclusionFilters) -{ - const QList filterRegs = filtersToRegExps(filters); - const QList exclusionRegs = filtersToRegExps(exclusionFilters); - return [filterRegs, exclusionRegs](const FilePath &filePath) { - return isFileIncluded(filterRegs, exclusionRegs, filePath); - }; -} - std::function filterFilesFunction(const QStringList &filters, const QStringList &exclusionFilters) { @@ -538,10 +530,12 @@ QString msgExclusionPatternLabel() return Tr::tr("Excl&usion pattern:"); } -QString msgFilePatternToolTip() +QString msgFilePatternToolTip(InclusionType inclusionType) { - return Tr::tr("List of comma separated wildcard filters. " - "Files with file name or full file path matching any filter are included."); + return Tr::tr("List of comma separated wildcard filters. ") + + (inclusionType == InclusionType::Included + ? Tr::tr("Files with file name or full file path matching any filter are included.") + : Tr::tr("Files with file name or full file path matching any filter are excluded.")); } QString matchCaseReplacement(const QString &originalText, const QString &replaceText) @@ -598,19 +592,20 @@ FileIterator::const_iterator FileIterator::end() const // #pragma mark -- FileListIterator -QTextCodec *encodingAt(const QList &encodings, int index) +QList constructItems(const FilePaths &fileList, + const QList &encodings) { - if (index >= 0 && index < encodings.size()) - return encodings.at(index); - return QTextCodec::codecForLocale(); + QList items; + items.reserve(fileList.size()); + QTextCodec *defaultEncoding = QTextCodec::codecForLocale(); + for (int i = 0; i < fileList.size(); ++i) + items.append(FileIterator::Item(fileList.at(i), encodings.value(i, defaultEncoding))); + return items; } -FileListIterator::FileListIterator(const FilePaths &fileList, const QList encodings) - : m_maxIndex(-1) +FileListIterator::FileListIterator(const FilePaths &fileList, const QList &encodings) + : m_items(constructItems(fileList, encodings)) { - m_items.reserve(fileList.size()); - for (int i = 0; i < fileList.size(); ++i) - m_items.append(Item(fileList.at(i), encodingAt(encodings, i))); } void FileListIterator::update(int requestedIndex) diff --git a/src/libs/utils/filesearch.h b/src/libs/utils/filesearch.h index 9d427f5946e..fc6ce62ab0b 100644 --- a/src/libs/utils/filesearch.h +++ b/src/libs/utils/filesearch.h @@ -6,8 +6,8 @@ #include "utils_global.h" #include "filepath.h" +#include "searchresultitem.h" -#include #include #include #include @@ -23,10 +23,6 @@ QT_END_NAMESPACE namespace Utils { -QTCREATOR_UTILS_EXPORT -std::function filterFileFunction(const QStringList &filterRegs, - const QStringList &exclusionRegs); - QTCREATOR_UTILS_EXPORT std::function filterFilesFunction(const QStringList &filters, const QStringList &exclusionFilters); @@ -40,8 +36,13 @@ QString msgFilePatternLabel(); QTCREATOR_UTILS_EXPORT QString msgExclusionPatternLabel(); +enum class InclusionType { + Included, + Excluded +}; + QTCREATOR_UTILS_EXPORT -QString msgFilePatternToolTip(); +QString msgFilePatternToolTip(InclusionType inclusionType = InclusionType::Included); class QTCREATOR_UTILS_EXPORT FileIterator { @@ -107,7 +108,8 @@ protected: class QTCREATOR_UTILS_EXPORT FileListIterator : public FileIterator { public: - explicit FileListIterator(const FilePaths &fileList, const QList encodings); + explicit FileListIterator(const FilePaths &fileList = {}, + const QList &encodings = {}); int maxProgress() const override; int currentProgress() const override; @@ -118,8 +120,8 @@ protected: const Item &itemAt(int index) const override; private: - QVector m_items; - int m_maxIndex; + const QList m_items; + int m_maxIndex = -1; }; class QTCREATOR_UTILS_EXPORT SubDirFileIterator : public FileIterator @@ -151,54 +153,20 @@ private: QList m_items; }; -class QTCREATOR_UTILS_EXPORT FileSearchResult -{ -public: - FileSearchResult() = default; - FileSearchResult(const FilePath &fileName, int lineNumber, const QString &matchingLine, - int matchStart, int matchLength, - const QStringList ®expCapturedTexts) - : fileName(fileName), - lineNumber(lineNumber), - matchingLine(matchingLine), - matchStart(matchStart), - matchLength(matchLength), - regexpCapturedTexts(regexpCapturedTexts) - {} +QTCREATOR_UTILS_EXPORT QFuture findInFiles(const QString &searchTerm, + FileIterator *files, + QTextDocument::FindFlags flags, + const QMap &fileToContentsMap = {}); - bool operator==(const FileSearchResult &o) const - { - return fileName == o.fileName && lineNumber == o.lineNumber - && matchingLine == o.matchingLine && matchStart == o.matchStart - && matchLength == o.matchLength && regexpCapturedTexts == o.regexpCapturedTexts; - } - bool operator!=(const FileSearchResult &o) const { return !(*this == o); } - - FilePath fileName; - int lineNumber; - QString matchingLine; - int matchStart; - int matchLength; - QStringList regexpCapturedTexts; -}; - -using FileSearchResultList = QList; - -QTCREATOR_UTILS_EXPORT QFuture findInFiles( +QTCREATOR_UTILS_EXPORT QFuture findInFilesRegExp( const QString &searchTerm, FileIterator *files, QTextDocument::FindFlags flags, - const QMap &fileToContentsMap = QMap()); + const QMap &fileToContentsMap = {}); -QTCREATOR_UTILS_EXPORT QFuture findInFilesRegExp( - const QString &searchTerm, - FileIterator *files, - QTextDocument::FindFlags flags, - const QMap &fileToContentsMap = QMap()); - -QTCREATOR_UTILS_EXPORT QString expandRegExpReplacement(const QString &replaceText, const QStringList &capturedTexts); -QTCREATOR_UTILS_EXPORT QString matchCaseReplacement(const QString &originalText, const QString &replaceText); +QTCREATOR_UTILS_EXPORT QString expandRegExpReplacement(const QString &replaceText, + const QStringList &capturedTexts); +QTCREATOR_UTILS_EXPORT QString matchCaseReplacement(const QString &originalText, + const QString &replaceText); } // namespace Utils - -Q_DECLARE_METATYPE(Utils::FileSearchResultList) diff --git a/src/libs/utils/filestreamer.cpp b/src/libs/utils/filestreamer.cpp new file mode 100644 index 00000000000..693849b7c59 --- /dev/null +++ b/src/libs/utils/filestreamer.cpp @@ -0,0 +1,491 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "filestreamer.h" + +#include "async.h" +#include "process.h" + +#include + +#include +#include +#include +#include + +namespace Utils { + +using namespace Tasking; + +// TODO: Adjust according to time spent on single buffer read so that it's not more than ~50 ms +// in case of local read / write. Should it be adjusted dynamically / automatically? +static const qint64 s_bufferSize = 0x1 << 20; // 1048576 + +class FileStreamBase : public QObject +{ + Q_OBJECT + +public: + void setFilePath(const FilePath &filePath) { m_filePath = filePath; } + void start() { + QTC_ASSERT(!m_taskTree, return); + + const TaskItem task = m_filePath.needsDevice() ? remoteTask() : localTask(); + m_taskTree.reset(new TaskTree({task})); + const auto finalize = [this](bool success) { + m_taskTree.release()->deleteLater(); + emit done(success); + }; + connect(m_taskTree.get(), &TaskTree::done, this, [=] { finalize(true); }); + connect(m_taskTree.get(), &TaskTree::errorOccurred, this, [=] { finalize(false); }); + m_taskTree->start(); + } + +signals: + void done(bool success); + +protected: + FilePath m_filePath; + std::unique_ptr m_taskTree; + +private: + virtual TaskItem remoteTask() = 0; + virtual TaskItem localTask() = 0; +}; + +static void localRead(QPromise &promise, const FilePath &filePath) +{ + if (promise.isCanceled()) + return; + + QFile file(filePath.path()); + if (!file.exists()) { + promise.future().cancel(); + return; + } + + if (!file.open(QFile::ReadOnly)) { + promise.future().cancel(); + return; + } + + while (int chunkSize = qMin(s_bufferSize, file.bytesAvailable())) { + if (promise.isCanceled()) + return; + promise.addResult(file.read(chunkSize)); + } +} + +class FileStreamReader : public FileStreamBase +{ + Q_OBJECT + +signals: + void readyRead(const QByteArray &newData); + +private: + TaskItem remoteTask() final { + const auto setup = [this](Process &process) { + const QStringList args = {"if=" + m_filePath.path()}; + const FilePath dd = m_filePath.withNewPath("dd"); + process.setCommand({dd, args, OsType::OsTypeLinux}); + Process *processPtr = &process; + connect(processPtr, &Process::readyReadStandardOutput, this, [this, processPtr] { + emit readyRead(processPtr->readAllRawStandardOutput()); + }); + }; + return ProcessTask(setup); + } + TaskItem localTask() final { + const auto setup = [this](Async &async) { + async.setConcurrentCallData(localRead, m_filePath); + Async *asyncPtr = &async; + connect(asyncPtr, &AsyncBase::resultReadyAt, this, [=](int index) { + emit readyRead(asyncPtr->resultAt(index)); + }); + }; + return AsyncTask(setup); + } +}; + +class WriteBuffer : public QObject +{ + Q_OBJECT + +public: + WriteBuffer(bool isConcurrent, QObject *parent) + : QObject(parent) + , m_isConcurrent(isConcurrent) {} + struct Data { + QByteArray m_writeData; + bool m_closeWriteChannel = false; + bool m_canceled = false; + bool hasNewData() const { return m_closeWriteChannel || !m_writeData.isEmpty(); } + }; + + void write(const QByteArray &newData) { + if (m_isConcurrent) { + QMutexLocker locker(&m_mutex); + QTC_ASSERT(!m_data.m_closeWriteChannel, return); + QTC_ASSERT(!m_data.m_canceled, return); + m_data.m_writeData += newData; + m_waitCondition.wakeOne(); + return; + } + emit writeRequested(newData); + } + void closeWriteChannel() { + if (m_isConcurrent) { + QMutexLocker locker(&m_mutex); + QTC_ASSERT(!m_data.m_canceled, return); + m_data.m_closeWriteChannel = true; + m_waitCondition.wakeOne(); + return; + } + emit closeWriteChannelRequested(); + } + void cancel() { + if (m_isConcurrent) { + QMutexLocker locker(&m_mutex); + m_data.m_canceled = true; + m_waitCondition.wakeOne(); + return; + } + emit closeWriteChannelRequested(); + } + Data waitForData() { + QTC_ASSERT(m_isConcurrent, return {}); + QMutexLocker locker(&m_mutex); + if (!m_data.hasNewData()) + m_waitCondition.wait(&m_mutex); + return std::exchange(m_data, {}); + } + +signals: + void writeRequested(const QByteArray &newData); + void closeWriteChannelRequested(); + +private: + QMutex m_mutex; + QWaitCondition m_waitCondition; + Data m_data; + bool m_isConcurrent = false; // Depends on whether FileStreamWriter::m_writeData is empty or not +}; + +static void localWrite(QPromise &promise, const FilePath &filePath, + const QByteArray &initialData, WriteBuffer *buffer) +{ + if (promise.isCanceled()) + return; + + QFile file(filePath.path()); + + if (!file.open(QFile::WriteOnly | QFile::Truncate)) { + promise.future().cancel(); + return; + } + + if (!initialData.isEmpty()) { + const qint64 res = file.write(initialData); + if (res != initialData.size()) + promise.future().cancel(); + return; + } + + while (true) { + if (promise.isCanceled()) { + promise.future().cancel(); + return; + } + const WriteBuffer::Data data = buffer->waitForData(); + if (data.m_canceled || promise.isCanceled()) { + promise.future().cancel(); + return; + } + if (!data.m_writeData.isEmpty()) { + // TODO: Write in chunks of s_bufferSize and check for promise.isCanceled() + const qint64 res = file.write(data.m_writeData); + if (res != data.m_writeData.size()) { + promise.future().cancel(); + return; + } + } + if (data.m_closeWriteChannel) + return; + } +} + +class FileStreamWriter : public FileStreamBase +{ + Q_OBJECT + +public: + ~FileStreamWriter() { // TODO: should d'tor remove unfinished file write leftovers? + if (m_writeBuffer && isBuffered()) + m_writeBuffer->cancel(); + // m_writeBuffer is a child of either Process or Async. So, if m_writeBuffer + // is still alive now (in case when TaskTree::stop() was called), the FileStreamBase + // d'tor is going to delete m_writeBuffer later. In case of Async, coming from + // localTask(), the d'tor of Async, run by FileStreamBase, busy waits for the + // already canceled here WriteBuffer to finish before deleting WriteBuffer child. + } + + void setWriteData(const QByteArray &writeData) { + QTC_ASSERT(!m_taskTree, return); + m_writeData = writeData; + } + void write(const QByteArray &newData) { + QTC_ASSERT(m_taskTree, return); + QTC_ASSERT(m_writeData.isEmpty(), return); + QTC_ASSERT(m_writeBuffer, return); + m_writeBuffer->write(newData); + } + void closeWriteChannel() { + QTC_ASSERT(m_taskTree, return); + QTC_ASSERT(m_writeData.isEmpty(), return); + QTC_ASSERT(m_writeBuffer, return); + m_writeBuffer->closeWriteChannel(); + } + +signals: + void started(); + +private: + TaskItem remoteTask() final { + const auto setup = [this](Process &process) { + m_writeBuffer = new WriteBuffer(false, &process); + connect(m_writeBuffer, &WriteBuffer::writeRequested, &process, &Process::writeRaw); + connect(m_writeBuffer, &WriteBuffer::closeWriteChannelRequested, + &process, &Process::closeWriteChannel); + const QStringList args = {"of=" + m_filePath.path()}; + const FilePath dd = m_filePath.withNewPath("dd"); + process.setCommand({dd, args, OsType::OsTypeLinux}); + if (isBuffered()) + process.setProcessMode(ProcessMode::Writer); + else + process.setWriteData(m_writeData); + connect(&process, &Process::started, this, [this] { emit started(); }); + }; + const auto finalize = [this](const Process &) { + delete m_writeBuffer; + m_writeBuffer = nullptr; + }; + return ProcessTask(setup, finalize, finalize); + } + TaskItem localTask() final { + const auto setup = [this](Async &async) { + m_writeBuffer = new WriteBuffer(isBuffered(), &async); + async.setConcurrentCallData(localWrite, m_filePath, m_writeData, m_writeBuffer); + emit started(); + }; + const auto finalize = [this](const Async &) { + delete m_writeBuffer; + m_writeBuffer = nullptr; + }; + return AsyncTask(setup, finalize, finalize); + } + + bool isBuffered() const { return m_writeData.isEmpty(); } + QByteArray m_writeData; + WriteBuffer *m_writeBuffer = nullptr; +}; + +class FileStreamReaderAdapter : public TaskAdapter +{ +public: + FileStreamReaderAdapter() { connect(task(), &FileStreamBase::done, this, &TaskInterface::done); } + void start() override { task()->start(); } +}; + +class FileStreamWriterAdapter : public TaskAdapter +{ +public: + FileStreamWriterAdapter() { connect(task(), &FileStreamBase::done, this, &TaskInterface::done); } + void start() override { task()->start(); } +}; + +} // namespace Utils + +TASKING_DECLARE_TASK(FileStreamReaderTask, Utils::FileStreamReaderAdapter); +TASKING_DECLARE_TASK(FileStreamWriterTask, Utils::FileStreamWriterAdapter); + +namespace Utils { + +static Group sameRemoteDeviceTransferTask(const FilePath &source, const FilePath &destination) +{ + QTC_CHECK(source.needsDevice()); + QTC_CHECK(destination.needsDevice()); + QTC_CHECK(source.isSameDevice(destination)); + + const auto setup = [source, destination](Process &process) { + const QStringList args = {source.path(), destination.path()}; + const FilePath cp = source.withNewPath("cp"); + process.setCommand({cp, args, OsType::OsTypeLinux}); + }; + return {ProcessTask(setup)}; +} + +static Group interDeviceTransferTask(const FilePath &source, const FilePath &destination) +{ + struct TransferStorage { QPointer writer; }; + SingleBarrier writerReadyBarrier; + TreeStorage storage; + + const auto setupReader = [=](FileStreamReader &reader) { + reader.setFilePath(source); + QTC_CHECK(storage->writer != nullptr); + QObject::connect(&reader, &FileStreamReader::readyRead, + storage->writer, &FileStreamWriter::write); + }; + const auto finalizeReader = [=](const FileStreamReader &) { + if (storage->writer) // writer may be deleted before the reader on TaskTree::stop(). + storage->writer->closeWriteChannel(); + }; + const auto setupWriter = [=](FileStreamWriter &writer) { + writer.setFilePath(destination); + QObject::connect(&writer, &FileStreamWriter::started, + writerReadyBarrier->barrier(), &Barrier::advance); + QTC_CHECK(storage->writer == nullptr); + storage->writer = &writer; + }; + + const Group root { + Storage(writerReadyBarrier), + parallel, + Storage(storage), + FileStreamWriterTask(setupWriter), + Group { + WaitForBarrierTask(writerReadyBarrier), + FileStreamReaderTask(setupReader, finalizeReader, finalizeReader) + } + }; + + return root; +} + +static Group transferTask(const FilePath &source, const FilePath &destination) +{ + if (source.needsDevice() && destination.needsDevice() && source.isSameDevice(destination)) + return sameRemoteDeviceTransferTask(source, destination); + return interDeviceTransferTask(source, destination); +} + +static void transfer(QPromise &promise, const FilePath &source, const FilePath &destination) +{ + if (promise.isCanceled()) + return; + + if (!TaskTree::runBlocking(transferTask(source, destination), promise.future())) + promise.future().cancel(); +} + +class FileStreamerPrivate : public QObject +{ +public: + StreamMode m_streamerMode = StreamMode::Transfer; + FilePath m_source; + FilePath m_destination; + QByteArray m_readBuffer; + QByteArray m_writeBuffer; + StreamResult m_streamResult = StreamResult::FinishedWithError; + std::unique_ptr m_taskTree; + + TaskItem task() { + if (m_streamerMode == StreamMode::Reader) + return readerTask(); + if (m_streamerMode == StreamMode::Writer) + return writerTask(); + return transferTask(); + } + +private: + TaskItem readerTask() { + const auto setup = [this](FileStreamReader &reader) { + m_readBuffer.clear(); + reader.setFilePath(m_source); + connect(&reader, &FileStreamReader::readyRead, this, [this](const QByteArray &data) { + m_readBuffer += data; + }); + }; + return FileStreamReaderTask(setup); + } + TaskItem writerTask() { + const auto setup = [this](FileStreamWriter &writer) { + writer.setFilePath(m_destination); + writer.setWriteData(m_writeBuffer); + }; + return FileStreamWriterTask(setup); + } + TaskItem transferTask() { + const auto setup = [this](Async &async) { + async.setConcurrentCallData(transfer, m_source, m_destination); + }; + return AsyncTask(setup); + } +}; + +FileStreamer::FileStreamer(QObject *parent) + : QObject(parent) + , d(new FileStreamerPrivate) +{ +} + +FileStreamer::~FileStreamer() +{ + delete d; +} + +void FileStreamer::setSource(const FilePath &source) +{ + d->m_source = source; +} + +void FileStreamer::setDestination(const FilePath &destination) +{ + d->m_destination = destination; +} + +void FileStreamer::setStreamMode(StreamMode mode) +{ + d->m_streamerMode = mode; +} + +QByteArray FileStreamer::readData() const +{ + return d->m_readBuffer; +} + +void FileStreamer::setWriteData(const QByteArray &writeData) +{ + d->m_writeBuffer = writeData; +} + +StreamResult FileStreamer::result() const +{ + return d->m_streamResult; +} + +void FileStreamer::start() +{ + // TODO: Preliminary check if local source exists? + QTC_ASSERT(!d->m_taskTree, return); + d->m_taskTree.reset(new TaskTree({d->task()})); + const auto finalize = [this](bool success) { + d->m_streamResult = success ? StreamResult::FinishedWithSuccess + : StreamResult::FinishedWithError; + d->m_taskTree.release()->deleteLater(); + emit done(); + }; + connect(d->m_taskTree.get(), &TaskTree::done, this, [=] { finalize(true); }); + connect(d->m_taskTree.get(), &TaskTree::errorOccurred, this, [=] { finalize(false); }); + d->m_taskTree->start(); +} + +void FileStreamer::stop() +{ + d->m_taskTree.reset(); +} + +} // namespace Utils + +#include "filestreamer.moc" diff --git a/src/libs/utils/filestreamer.h b/src/libs/utils/filestreamer.h new file mode 100644 index 00000000000..a5b6ff4f256 --- /dev/null +++ b/src/libs/utils/filestreamer.h @@ -0,0 +1,63 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "utils_global.h" + +#include "filepath.h" + +#include + +#include + +QT_BEGIN_NAMESPACE +class QByteArray; +QT_END_NAMESPACE + +namespace Utils { + +enum class StreamMode { Reader, Writer, Transfer }; + +enum class StreamResult { FinishedWithSuccess, FinishedWithError }; + +class QTCREATOR_UTILS_EXPORT FileStreamer final : public QObject +{ + Q_OBJECT + +public: + FileStreamer(QObject *parent = nullptr); + ~FileStreamer(); + + void setSource(const FilePath &source); + void setDestination(const FilePath &destination); + void setStreamMode(StreamMode mode); // Transfer by default + + // Only for Reader mode + QByteArray readData() const; + // Only for Writer mode + void setWriteData(const QByteArray &writeData); + + StreamResult result() const; + + void start(); + void stop(); + +signals: + void done(); + +private: + class FileStreamerPrivate *d = nullptr; +}; + +class FileStreamerTaskAdapter : public Tasking::TaskAdapter +{ +public: + FileStreamerTaskAdapter() { connect(task(), &FileStreamer::done, this, + [this] { emit done(task()->result() == StreamResult::FinishedWithSuccess); }); } + void start() override { task()->start(); } +}; + +} // namespace Utils + +TASKING_DECLARE_TASK(FileStreamerTask, Utils::FileStreamerTaskAdapter); diff --git a/src/libs/utils/filestreamermanager.cpp b/src/libs/utils/filestreamermanager.cpp new file mode 100644 index 00000000000..11d05faee57 --- /dev/null +++ b/src/libs/utils/filestreamermanager.cpp @@ -0,0 +1,201 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "filestreamermanager.h" + +#include "filestreamer.h" +#include "threadutils.h" +#include "utilstr.h" + +#include +#include +#include +#include + +#include + +namespace Utils { + +// TODO: destruct the instance before destructing ProjectExplorer::DeviceManager (?) + +static FileStreamHandle generateUniqueHandle() +{ + static std::atomic_int handleCounter = 1; + return FileStreamHandle(handleCounter.fetch_add(1)); +} + +static QMutex s_mutex = {}; +static QWaitCondition s_waitCondition = {}; +static std::unordered_map s_fileStreamers = {}; + +static void addStreamer(FileStreamHandle handle, FileStreamer *streamer) +{ + QMutexLocker locker(&s_mutex); + const bool added = s_fileStreamers.try_emplace(handle, streamer).second; + QTC_CHECK(added); +} + +static void removeStreamer(FileStreamHandle handle) +{ + QMutexLocker locker(&s_mutex); + auto it = s_fileStreamers.find(handle); + QTC_ASSERT(it != s_fileStreamers.end(), return); + QTC_ASSERT(QThread::currentThread() == it->second->thread(), return); + s_fileStreamers.erase(it); + s_waitCondition.wakeAll(); +} + +static void deleteStreamer(FileStreamHandle handle) +{ + QMutexLocker locker(&s_mutex); + auto it = s_fileStreamers.find(handle); + if (it == s_fileStreamers.end()) + return; + if (QThread::currentThread() == it->second->thread()) { + delete it->second; + s_fileStreamers.erase(it); + s_waitCondition.wakeAll(); + } else { + QMetaObject::invokeMethod(it->second, [handle] { + deleteStreamer(handle); + }); + s_waitCondition.wait(&s_mutex); + QTC_CHECK(s_fileStreamers.find(handle) == s_fileStreamers.end()); + } +} + +static void deleteAllStreamers() +{ + QMutexLocker locker(&s_mutex); + QTC_ASSERT(Utils::isMainThread(), return); + while (s_fileStreamers.size()) { + auto it = s_fileStreamers.begin(); + if (QThread::currentThread() == it->second->thread()) { + delete it->second; + s_fileStreamers.erase(it); + s_waitCondition.wakeAll(); + } else { + const FileStreamHandle handle = it->first; + QMetaObject::invokeMethod(it->second, [handle] { + deleteStreamer(handle); + }); + s_waitCondition.wait(&s_mutex); + QTC_CHECK(s_fileStreamers.find(handle) == s_fileStreamers.end()); + } + } +} + +static FileStreamHandle checkHandle(FileStreamHandle handle) +{ + QMutexLocker locker(&s_mutex); + return s_fileStreamers.find(handle) != s_fileStreamers.end() ? handle : FileStreamHandle(0); +} + +FileStreamHandle execute(const std::function &onSetup, + const std::function &onDone, + QObject *context) +{ + FileStreamer *streamer = new FileStreamer; + onSetup(streamer); + const FileStreamHandle handle = generateUniqueHandle(); + QTC_CHECK(context == nullptr || context->thread() == QThread::currentThread()); + if (onDone) { + QObject *finalContext = context ? context : streamer; + QObject::connect(streamer, &FileStreamer::done, finalContext, [=] { onDone(streamer); }); + } + QObject::connect(streamer, &FileStreamer::done, streamer, [=] { + removeStreamer(handle); + streamer->deleteLater(); + }); + addStreamer(handle, streamer); + streamer->start(); + return checkHandle(handle); // The handle could have been already removed +} + +FileStreamHandle FileStreamerManager::copy(const FilePath &source, const FilePath &destination, + const CopyContinuation &cont) +{ + return copy(source, destination, nullptr, cont); +} + +FileStreamHandle FileStreamerManager::copy(const FilePath &source, const FilePath &destination, + QObject *context, const CopyContinuation &cont) +{ + const auto onSetup = [=](FileStreamer *streamer) { + streamer->setSource(source); + streamer->setDestination(destination); + }; + if (!cont) + return execute(onSetup, {}, context); + + const auto onDone = [=](FileStreamer *streamer) { + if (streamer->result() == StreamResult::FinishedWithSuccess) + cont({}); + else + cont(make_unexpected(Tr::tr("Failed copying file"))); + }; + return execute(onSetup, onDone, context); +} + +FileStreamHandle FileStreamerManager::read(const FilePath &source, const ReadContinuation &cont) +{ + return read(source, nullptr, cont); +} + +FileStreamHandle FileStreamerManager::read(const FilePath &source, QObject *context, + const ReadContinuation &cont) +{ + const auto onSetup = [=](FileStreamer *streamer) { + streamer->setStreamMode(StreamMode::Reader); + streamer->setSource(source); + }; + if (!cont) + return execute(onSetup, {}, context); + + const auto onDone = [=](FileStreamer *streamer) { + if (streamer->result() == StreamResult::FinishedWithSuccess) + cont(streamer->readData()); + else + cont(make_unexpected(Tr::tr("Failed reading file"))); + }; + return execute(onSetup, onDone, context); +} + +FileStreamHandle FileStreamerManager::write(const FilePath &destination, const QByteArray &data, + const WriteContinuation &cont) +{ + return write(destination, data, nullptr, cont); +} + +FileStreamHandle FileStreamerManager::write(const FilePath &destination, const QByteArray &data, + QObject *context, const WriteContinuation &cont) +{ + const auto onSetup = [=](FileStreamer *streamer) { + streamer->setStreamMode(StreamMode::Writer); + streamer->setDestination(destination); + streamer->setWriteData(data); + }; + if (!cont) + return execute(onSetup, {}, context); + + const auto onDone = [=](FileStreamer *streamer) { + if (streamer->result() == StreamResult::FinishedWithSuccess) + cont(0); // TODO: return write count? + else + cont(make_unexpected(Tr::tr("Failed writing file"))); + }; + return execute(onSetup, onDone, context); +} + +void FileStreamerManager::stop(FileStreamHandle handle) +{ + deleteStreamer(handle); +} + +void FileStreamerManager::stopAll() +{ + deleteAllStreamers(); +} + +} // namespace Utils + diff --git a/src/libs/utils/filestreamermanager.h b/src/libs/utils/filestreamermanager.h new file mode 100644 index 00000000000..f86a3db480f --- /dev/null +++ b/src/libs/utils/filestreamermanager.h @@ -0,0 +1,42 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "utils_global.h" + +#include "filepath.h" + +#include + +QT_BEGIN_NAMESPACE +class QByteArray; +QT_END_NAMESPACE + +namespace Utils { + +enum class FileStreamHandle : int {}; + +class QTCREATOR_UTILS_EXPORT FileStreamerManager +{ +public: + static FileStreamHandle copy(const FilePath &source, const FilePath &destination, + const CopyContinuation &cont); + static FileStreamHandle copy(const FilePath &source, const FilePath &destination, + QObject *context, const CopyContinuation &cont); + + static FileStreamHandle read(const FilePath &source, const ReadContinuation &cont = {}); + static FileStreamHandle read(const FilePath &source, QObject *context, + const ReadContinuation &cont = {}); + + static FileStreamHandle write(const FilePath &destination, const QByteArray &data, + const WriteContinuation &cont = {}); + static FileStreamHandle write(const FilePath &destination, const QByteArray &data, + QObject *context, const WriteContinuation &cont = {}); + + // If called from the same thread that started the task, no continuation is going to be called. + static void stop(FileStreamHandle handle); + static void stopAll(); +}; + +} // namespace Utils diff --git a/src/libs/utils/filesystemmodel.cpp b/src/libs/utils/filesystemmodel.cpp index 60961c35994..9a10f3b91fc 100644 --- a/src/libs/utils/filesystemmodel.cpp +++ b/src/libs/utils/filesystemmodel.cpp @@ -209,7 +209,8 @@ private: }; /*! - Creates thread + \internal + Creates a thread. */ FileInfoGatherer::FileInfoGatherer(QObject *parent) : QThread(parent) @@ -219,7 +220,8 @@ FileInfoGatherer::FileInfoGatherer(QObject *parent) } /*! - Destroys thread + \internal + Destroys a thread. */ FileInfoGatherer::~FileInfoGatherer() { @@ -265,6 +267,7 @@ QFileIconProvider *FileInfoGatherer::iconProvider() const } /*! + \internal Fetch extended information for all \a files in \a path \sa updateFile(), update(), resolvedName() @@ -295,6 +298,7 @@ void FileInfoGatherer::fetchExtendedInformation(const QString &path, const QStri } /*! + \internal Fetch extended information for all \a filePath \sa fetchExtendedInformation() @@ -1719,7 +1723,7 @@ QStringList FileSystemModel::mimeTypes() const \a indexes. The format used to describe the items corresponding to the indexes is obtained from the mimeTypes() function. - If the list of indexes is empty, \nullptr is returned rather than a + If the list of indexes is empty, \c nullptr is returned rather than a serialized empty list. */ QMimeData *FileSystemModel::mimeData(const QModelIndexList &indexes) const @@ -1799,7 +1803,8 @@ QHash FileSystemModel::roleNames() const } /*! - \enum FileSystemModel::Option + \internal + \enum Utils::FileSystemModel::Option \since 5.14 \value DontWatchForChanges Do not add file watchers to the paths. @@ -1847,6 +1852,7 @@ bool FileSystemModel::testOption(Option option) const } /*! + \internal \property FileSystemModel::options \brief the various options that affect the model \since 5.14 @@ -2121,6 +2127,7 @@ QDir::Filters FileSystemModel::filter() const } /*! + \internal \property FileSystemModel::resolveSymlinks \brief Whether the directory model should resolve symbolic links @@ -2146,6 +2153,7 @@ bool FileSystemModel::resolveSymlinks() const } /*! + \internal \property FileSystemModel::readOnly \brief Whether the directory model allows writing to the file system @@ -2165,6 +2173,7 @@ bool FileSystemModel::isReadOnly() const } /*! + \internal \property FileSystemModel::nameFilterDisables \brief Whether files that don't pass the name filter are hidden or disabled diff --git a/src/libs/utils/filesystemwatcher.cpp b/src/libs/utils/filesystemwatcher.cpp index e0cef5aed99..155f83fba03 100644 --- a/src/libs/utils/filesystemwatcher.cpp +++ b/src/libs/utils/filesystemwatcher.cpp @@ -28,6 +28,7 @@ static inline quint64 getFileLimit() /*! \class Utils::FileSystemWatcher + \inmodule QtCreator \brief The FileSystemWatcher class is a file watcher that internally uses a centralized QFileSystemWatcher and enforces limits on Mac OS. @@ -187,7 +188,7 @@ void FileSystemWatcherPrivate::autoReloadPostponed(bool postponed) } /*! - Adds directories to watcher 0. + Creates a file system watcher with the ID 0 and the owner \a parent. */ FileSystemWatcher::FileSystemWatcher(QObject *parent) : @@ -197,7 +198,7 @@ FileSystemWatcher::FileSystemWatcher(QObject *parent) : } /*! - Adds directories to a watcher with the specified \a id. + Creates a file system watcher with the ID \a id and the owner \a parent. */ FileSystemWatcher::FileSystemWatcher(int id, QObject *parent) : @@ -276,15 +277,19 @@ void FileSystemWatcher::addFiles(const QStringList &files, WatchMode wm) const int count = ++d->m_staticData->m_fileCount[file]; Q_ASSERT(count > 0); - if (count == 1) + if (count == 1) { toAdd << file; - const QString directory = QFileInfo(file).path(); - const int dirCount = ++d->m_staticData->m_directoryCount[directory]; - Q_ASSERT(dirCount > 0); + QFileInfo fi(file); + if (!fi.exists()) { + const QString directory = fi.path(); + const int dirCount = ++d->m_staticData->m_directoryCount[directory]; + Q_ASSERT(dirCount > 0); - if (dirCount == 1) - toAdd << directory; + if (dirCount == 1) + toAdd << directory; + } + } } if (!toAdd.isEmpty()) @@ -312,15 +317,19 @@ void FileSystemWatcher::removeFiles(const QStringList &files) const int count = --(d->m_staticData->m_fileCount[file]); Q_ASSERT(count >= 0); - if (!count) + if (!count) { toRemove << file; - const QString directory = QFileInfo(file).path(); - const int dirCount = --d->m_staticData->m_directoryCount[directory]; - Q_ASSERT(dirCount >= 0); + QFileInfo fi(file); + if (!fi.exists()) { + const QString directory = fi.path(); + const int dirCount = --d->m_staticData->m_directoryCount[directory]; + Q_ASSERT(dirCount >= 0); - if (!dirCount) - toRemove << directory; + if (!dirCount) + toRemove << directory; + } + } } if (!toRemove.isEmpty()) @@ -419,13 +428,27 @@ QStringList FileSystemWatcher::directories() const void FileSystemWatcher::slotFileChanged(const QString &path) { const auto it = d->m_files.find(path); + QStringList toAdd; if (it != d->m_files.end() && it.value().trigger(path)) { qCDebug(fileSystemWatcherLog) << this << "triggers on file" << it.key() << it.value().watchMode << it.value().modifiedTime.toString(Qt::ISODate); d->fileChanged(path); + + QFileInfo fi(path); + if (!fi.exists()) { + const QString directory = fi.path(); + const int dirCount = ++d->m_staticData->m_directoryCount[directory]; + Q_ASSERT(dirCount > 0); + + if (dirCount == 1) + toAdd << directory; + } } + + if (!toAdd.isEmpty()) + d->m_staticData->m_watcher->addPaths(toAdd); } void FileSystemWatcher::slotDirectoryChanged(const QString &path) @@ -451,9 +474,20 @@ void FileSystemWatcher::slotDirectoryChanged(const QString &path) for (const QString &rejected : d->m_staticData->m_watcher->addPaths(toReadd)) toReadd.removeOne(rejected); + QStringList toRemove; // If we've successfully added the file, that means it was deleted and replaced. - for (const QString &reAdded : std::as_const(toReadd)) + for (const QString &reAdded : std::as_const(toReadd)) { d->fileChanged(reAdded); + const QString directory = QFileInfo(reAdded).path(); + const int dirCount = --d->m_staticData->m_directoryCount[directory]; + Q_ASSERT(dirCount >= 0); + + if (!dirCount) + toRemove << directory; + } + + if (!toRemove.isEmpty()) + d->m_staticData->m_watcher->removePaths(toRemove); } } diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index 1f6d89c43ae..2cecc2810b9 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -5,7 +5,7 @@ #include "savefile.h" #include "algorithm.h" -#include "hostosinfo.h" +#include "devicefileaccess.h" #include "qtcassert.h" #include "utilstr.h" @@ -194,12 +194,13 @@ FileSaver::FileSaver(const FilePath &filePath, QIODevice::OpenMode mode) auto tf = new QTemporaryFile(QDir::tempPath() + "/remotefilesaver-XXXXXX"); tf->setAutoRemove(false); m_file.reset(tf); - } else if (mode & (QIODevice::ReadOnly | QIODevice::Append)) { - m_file.reset(new QFile{filePath.path()}); - m_isSafe = false; } else { - m_file.reset(new SaveFile(filePath)); - m_isSafe = true; + const bool readOnlyOrAppend = mode & (QIODevice::ReadOnly | QIODevice::Append); + m_isSafe = !readOnlyOrAppend && !filePath.hasHardLinks(); + if (m_isSafe) + m_file.reset(new SaveFile(filePath)); + else + m_file.reset(new QFile{filePath.path()}); } if (!m_file->open(QIODevice::WriteOnly | mode)) { QString err = filePath.exists() ? @@ -258,7 +259,9 @@ TempFileSaver::~TempFileSaver() QFile::remove(m_filePath.toString()); } -/*! \class Utils::FileUtils +/*! + \class Utils::FileUtils + \inmodule QtCreator \brief The FileUtils class contains file and directory related convenience functions. @@ -590,8 +593,7 @@ FilePaths FileUtils::getOpenFilePaths(QWidget *parent, #endif // QT_WIDGETS_LIB -// Converts a hex string of the st_mode field of a stat structure to FileFlags. -FilePathInfo::FileFlags fileInfoFlagsfromStatRawModeHex(const QString &hexString) +FilePathInfo::FileFlags fileInfoFlagsfromStatMode(const QString &hexString, int modeBase) { // Copied from stat.h enum st_mode { @@ -621,18 +623,24 @@ FilePathInfo::FileFlags fileInfoFlagsfromStatRawModeHex(const QString &hexString }; bool ok = false; - uint mode = hexString.toUInt(&ok, 16); + uint mode = hexString.toUInt(&ok, modeBase); QTC_ASSERT(ok, return {}); FilePathInfo::FileFlags result; - if (mode & IRUSR) + if (mode & IRUSR) { result |= FilePathInfo::ReadOwnerPerm; - if (mode & IWUSR) + result |= FilePathInfo::ReadUserPerm; + } + if (mode & IWUSR) { result |= FilePathInfo::WriteOwnerPerm; - if (mode & IXUSR) + result |= FilePathInfo::WriteUserPerm; + } + if (mode & IXUSR) { result |= FilePathInfo::ExeOwnerPerm; + result |= FilePathInfo::ExeUserPerm; + } if (mode & IRGRP) result |= FilePathInfo::ReadGroupPerm; if (mode & IWGRP) @@ -661,13 +669,13 @@ FilePathInfo::FileFlags fileInfoFlagsfromStatRawModeHex(const QString &hexString return result; } -FilePathInfo FileUtils::filePathInfoFromTriple(const QString &infos) +FilePathInfo FileUtils::filePathInfoFromTriple(const QString &infos, int modeBase) { const QStringList parts = infos.split(' ', Qt::SkipEmptyParts); if (parts.size() != 3) return {}; - FilePathInfo::FileFlags flags = fileInfoFlagsfromStatRawModeHex(parts[0]); + FilePathInfo::FileFlags flags = fileInfoFlagsfromStatMode(parts[0], modeBase); const QDateTime dt = QDateTime::fromSecsSinceEpoch(parts[1].toLongLong(), Qt::UTC); qint64 size = parts[2].toLongLong(); @@ -806,14 +814,14 @@ FilePath FileUtils::commonPath(const FilePath &oldCommonPath, const FilePath &fi FilePath FileUtils::homePath() { - return FilePath::fromString(doCleanPath(QDir::homePath())); + return FilePath::fromUserInput(QDir::homePath()); } -FilePaths FileUtils::toFilePathList(const QStringList &paths) { - return transform(paths, [](const QString &path) { return FilePath::fromString(path); }); +FilePaths FileUtils::toFilePathList(const QStringList &paths) +{ + return transform(paths, &FilePath::fromString); } - qint64 FileUtils::bytesAvailableFromDFOutput(const QByteArray &dfOutput) { const auto lines = filtered(dfOutput.split('\n'), diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index f34206d8b77..a1a7ffef977 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -83,7 +83,7 @@ public: static qint64 bytesAvailableFromDFOutput(const QByteArray &dfOutput); - static FilePathInfo filePathInfoFromTriple(const QString &infos); + static FilePathInfo filePathInfoFromTriple(const QString &infos, int modeBase); #ifdef QT_WIDGETS_LIB static void setDialogParentGetter(const std::function &getter); @@ -121,7 +121,6 @@ public: QString *selectedFilter = nullptr, QFileDialog::Options options = {}); #endif - }; // for actually finding out if e.g. directories are writable on Windows @@ -232,9 +231,5 @@ QTCREATOR_UTILS_EXPORT QTextStream &operator<<(QTextStream &s, const FilePath &f bool isRelativePathHelper(const QString &path, OsType osType); -// For testing -QTCREATOR_UTILS_EXPORT QString doCleanPath(const QString &input); -QTCREATOR_UTILS_EXPORT QString cleanPathHelper(const QString &path); - } // namespace Utils diff --git a/src/libs/utils/fileutils_mac.h b/src/libs/utils/fileutils_mac.h index 4c9e7557e10..c7ddd0d2cdc 100644 --- a/src/libs/utils/fileutils_mac.h +++ b/src/libs/utils/fileutils_mac.h @@ -8,7 +8,6 @@ namespace Utils { namespace Internal { -QUrl filePathUrl(const QUrl &url); QString normalizePathName(const QString &filePath); } // Internal diff --git a/src/libs/utils/fileutils_mac.mm b/src/libs/utils/fileutils_mac.mm index e7ff062f66b..c3d494991d4 100644 --- a/src/libs/utils/fileutils_mac.mm +++ b/src/libs/utils/fileutils_mac.mm @@ -14,17 +14,6 @@ namespace Utils { namespace Internal { -QUrl filePathUrl(const QUrl &url) -{ - QUrl ret = url; - @autoreleasepool { - NSURL *nsurl = url.toNSURL(); - if ([nsurl isFileReferenceURL]) - ret = QUrl::fromNSURL([nsurl filePathURL]); - } - return ret; -} - QString normalizePathName(const QString &filePath) { QString result; diff --git a/src/libs/utils/filewizardpage.cpp b/src/libs/utils/filewizardpage.cpp index aae79b1a497..a883f960566 100644 --- a/src/libs/utils/filewizardpage.cpp +++ b/src/libs/utils/filewizardpage.cpp @@ -8,6 +8,7 @@ /*! \class Utils::FileWizardPage + \inmodule QtCreator \brief The FileWizardPage class is a standard wizard page for a single file letting the user choose name @@ -45,7 +46,6 @@ FileWizardPage::FileWizardPage(QWidget *parent) : d(new FileWizardPagePrivate) { setTitle(Tr::tr("Choose the Location")); - resize(368, 102); d->m_defaultSuffixLabel = new QLabel; d->m_nameLabel = new QLabel; diff --git a/src/libs/utils/fsengine/diriterator.h b/src/libs/utils/fsengine/diriterator.h index 9daebbebaaa..54e9d5d2dd3 100644 --- a/src/libs/utils/fsengine/diriterator.h +++ b/src/libs/utils/fsengine/diriterator.h @@ -39,6 +39,9 @@ public: QString currentFileName() const override { const QString result = it->fileName(); + if (result.isEmpty() && !it->host().isEmpty()) { + return it->host().toString(); + } return chopIfEndsWith(result, '/'); } diff --git a/src/libs/utils/fsengine/fileiconprovider.cpp b/src/libs/utils/fsengine/fileiconprovider.cpp index e5ed4a1281f..cd2cc060a21 100644 --- a/src/libs/utils/fsengine/fileiconprovider.cpp +++ b/src/libs/utils/fsengine/fileiconprovider.cpp @@ -28,6 +28,7 @@ Q_LOGGING_CATEGORY(fileIconProvider, "qtc.core.fileiconprovider", QtWarningMsg) /*! \class Utils::FileIconProvider + \internal \inmodule QtCreator \brief Provides functions for registering custom overlay icons for system icons. @@ -216,49 +217,11 @@ QIcon FileIconProviderImplementation::icon(const FilePath &filePath) const { qCDebug(fileIconProvider) << "FileIconProvider::icon" << filePath.absoluteFilePath(); - if (filePath.isEmpty()) - return unknownFileIcon(); - - // Check if its one of the virtual devices directories - if (filePath.path().startsWith(FilePath::specialRootPath())) { - // If the filepath does not need a device, it is a virtual device directory - if (!filePath.needsDevice()) - return dirIcon(); - } - - bool isDir = filePath.isDir(); - - // Check for cached overlay icons by file suffix. - const QString filename = !isDir ? filePath.fileName() : QString(); - if (!filename.isEmpty()) { - const std::optional icon = getIcon(m_filenameCache, filename); - if (icon) - return *icon; - } - - const QString suffix = !isDir ? filePath.suffix() : QString(); - if (!suffix.isEmpty()) { - const std::optional icon = getIcon(m_suffixCache, suffix); - if (icon) - return *icon; - } - - if (filePath.needsDevice()) - return isDir ? dirIcon() : unknownFileIcon(); - - // Get icon from OS (and cache it based on suffix!) - QIcon icon; - if (HostOsInfo::isWindowsHost() || HostOsInfo::isMacHost()) - icon = QFileIconProvider::icon(filePath.toFileInfo()); - else // File icons are unknown on linux systems. - icon = isDir ? QFileIconProvider::icon(filePath.toFileInfo()) : unknownFileIcon(); - - if (!isDir && !suffix.isEmpty()) - m_suffixCache.insert(suffix, icon); - return icon; + return icon(QFileInfo(filePath.toFSPathString())); } /*! + \internal Returns the icon associated with the file suffix in \a filePath. If there is none, the default icon of the operating system is returned. */ @@ -269,14 +232,16 @@ QIcon icon(const FilePath &filePath) } /*! - * \overload - */ + \internal + \overload +*/ QIcon icon(QFileIconProvider::IconType type) { return instance()->icon(type); } /*! + \internal Creates a pixmap with \a baseIcon and lays \a overlayIcon over it. */ QPixmap overlayIcon(const QPixmap &baseIcon, const QIcon &overlayIcon) @@ -288,6 +253,7 @@ QPixmap overlayIcon(const QPixmap &baseIcon, const QIcon &overlayIcon) } /*! + \internal Creates a pixmap with \a baseIcon at \a size and \a overlay. */ QPixmap overlayIcon(QStyle::StandardPixmap baseIcon, const QIcon &overlay, const QSize &size) @@ -296,6 +262,7 @@ QPixmap overlayIcon(QStyle::StandardPixmap baseIcon, const QIcon &overlay, const } /*! + \internal Registers an icon at \a path for a given \a suffix, overlaying the system file icon. */ @@ -305,6 +272,7 @@ void registerIconOverlayForSuffix(const QString &path, const QString &suffix) } /*! + \internal Registers \a icon for all the suffixes of a the mime type \a mimeType, overlaying the system file icon. */ @@ -314,7 +282,8 @@ void registerIconOverlayForMimeType(const QIcon &icon, const QString &mimeType) } /*! - * \overload + \internal + \overload */ void registerIconOverlayForMimeType(const QString &path, const QString &mimeType) { diff --git a/src/libs/utils/fsengine/fileiteratordevicesappender.h b/src/libs/utils/fsengine/fileiteratordevicesappender.h index c08c6cb3f70..9aec917d6b7 100644 --- a/src/libs/utils/fsengine/fileiteratordevicesappender.h +++ b/src/libs/utils/fsengine/fileiteratordevicesappender.h @@ -93,7 +93,10 @@ private: void setPath() const { if (!m_hasSetPath) { - const QString p = path(); + // path() can be "/somedir/.." so we need to clean it first. + // We only need QDir::cleanPath here, as the path is always + // a fs engine path and will not contain scheme:// etc. + const QString p = QDir::cleanPath(path()); if (p.compare(QDir::rootPath(), Qt::CaseInsensitive) == 0) m_status = State::IteratingRoot; diff --git a/src/libs/utils/fsengine/fixedlistfsengine.h b/src/libs/utils/fsengine/fixedlistfsengine.h index 991bc08b1bf..3518b5d0b5a 100644 --- a/src/libs/utils/fsengine/fixedlistfsengine.h +++ b/src/libs/utils/fsengine/fixedlistfsengine.h @@ -43,6 +43,8 @@ public: return chopIfEndsWith(m_filePath.toString(), '/'); break; case QAbstractFileEngine::BaseName: + if (m_filePath.fileName().isEmpty()) + return m_filePath.host().toString(); return m_filePath.fileName(); break; case QAbstractFileEngine::PathName: diff --git a/src/libs/utils/fsengine/fsengine.cpp b/src/libs/utils/fsengine/fsengine.cpp index cc622b87860..fd343aaad11 100644 --- a/src/libs/utils/fsengine/fsengine.cpp +++ b/src/libs/utils/fsengine/fsengine.cpp @@ -10,6 +10,8 @@ class Utils::Internal::FSEngineHandler {}; #endif +#include + #include namespace Utils { @@ -29,46 +31,65 @@ bool FSEngine::isAvailable() #endif } +template +class Locked +{ +public: + Locked(QMutex *mutex, T &object) + : m_object(object) + , m_locker(mutex) + {} + + T *operator->() const noexcept { return &m_object; } + const T operator*() const noexcept { return m_object; } + +private: + T &m_object; + QMutexLocker m_locker; +}; + +static Locked deviceRoots() +{ + static FilePaths g_deviceRoots; + static QMutex mutex; + return {&mutex, g_deviceRoots}; +} + +static Locked deviceSchemes() +{ + static QStringList g_deviceSchemes{"device"}; + static QMutex mutex; + return {&mutex, g_deviceSchemes}; +} + FilePaths FSEngine::registeredDeviceRoots() { - return FSEngine::deviceRoots(); + return *deviceRoots(); } void FSEngine::addDevice(const FilePath &deviceRoot) { - deviceRoots().append(deviceRoot); + deviceRoots()->append(deviceRoot); } void FSEngine::removeDevice(const FilePath &deviceRoot) { - deviceRoots().removeAll(deviceRoot); -} - -FilePaths &FSEngine::deviceRoots() -{ - static FilePaths g_deviceRoots; - return g_deviceRoots; -} - -QStringList &FSEngine::deviceSchemes() -{ - static QStringList g_deviceSchemes{"device"}; - return g_deviceSchemes; + deviceRoots()->removeAll(deviceRoot); } void FSEngine::registerDeviceScheme(const QStringView scheme) { - deviceSchemes().append(scheme.toString()); + deviceSchemes()->append(scheme.toString()); } void FSEngine::unregisterDeviceScheme(const QStringView scheme) { - deviceSchemes().removeAll(scheme.toString()); + deviceSchemes()->removeAll(scheme.toString()); } QStringList FSEngine::registeredDeviceSchemes() { - return FSEngine::deviceSchemes(); + return *deviceSchemes(); } } // namespace Utils diff --git a/src/libs/utils/fsengine/fsengine.h b/src/libs/utils/fsengine/fsengine.h index 52c03e9cee5..4c486ead6ee 100644 --- a/src/libs/utils/fsengine/fsengine.h +++ b/src/libs/utils/fsengine/fsengine.h @@ -33,10 +33,6 @@ public: static void unregisterDeviceScheme(const QStringView scheme); static QStringList registeredDeviceSchemes(); -private: - static Utils::FilePaths &deviceRoots(); - static QStringList &deviceSchemes(); - private: std::unique_ptr m_engineHandler; }; diff --git a/src/libs/utils/fsengine/fsengine_impl.cpp b/src/libs/utils/fsengine/fsengine_impl.cpp index 6654e741ace..ade8236d5e1 100644 --- a/src/libs/utils/fsengine/fsengine_impl.cpp +++ b/src/libs/utils/fsengine/fsengine_impl.cpp @@ -238,6 +238,8 @@ QString FSEngineImpl::fileName(FileName file) const return m_filePath.toFSPathString(); break; case QAbstractFileEngine::BaseName: + if (m_filePath.fileName().isEmpty()) + return m_filePath.host().toString(); return m_filePath.fileName(); break; case QAbstractFileEngine::PathName: diff --git a/src/libs/utils/fsengine/fsenginehandler.cpp b/src/libs/utils/fsengine/fsenginehandler.cpp index a713315ae39..e020eebddac 100644 --- a/src/libs/utils/fsengine/fsenginehandler.cpp +++ b/src/libs/utils/fsengine/fsenginehandler.cpp @@ -13,6 +13,26 @@ namespace Utils::Internal { +static FilePath removeDoubleSlash(const QString &fileName) +{ + // Reduce every two or more slashes to a single slash. + QString result; + const QChar slash = QChar('/'); + bool lastWasSlash = false; + for (const QChar &ch : fileName) { + if (ch == slash) { + if (!lastWasSlash) + result.append(ch); + lastWasSlash = true; + } else { + result.append(ch); + lastWasSlash = false; + } + } + // We use fromString() here to not normalize / clean the path anymore. + return FilePath::fromString(result); +} + QAbstractFileEngine *FSEngineHandler::create(const QString &fileName) const { if (fileName.startsWith(':')) @@ -29,29 +49,30 @@ QAbstractFileEngine *FSEngineHandler::create(const QString &fileName) const return rootFilePath.pathAppended(scheme); }); - return new FixedListFSEngine(rootFilePath, paths); + return new FixedListFSEngine(removeDoubleSlash(fileName), paths); } if (fixedFileName.startsWith(rootPath)) { const QStringList deviceSchemes = FSEngine::registeredDeviceSchemes(); for (const QString &scheme : deviceSchemes) { if (fixedFileName == rootFilePath.pathAppended(scheme).toString()) { - const FilePaths filteredRoots = Utils::filtered(FSEngine::deviceRoots(), + const FilePaths filteredRoots = Utils::filtered(FSEngine::registeredDeviceRoots(), [scheme](const FilePath &root) { return root.scheme() == scheme; }); - return new FixedListFSEngine(rootFilePath.pathAppended(scheme), filteredRoots); + return new FixedListFSEngine(removeDoubleSlash(fileName), filteredRoots); } } - FilePath filePath = FilePath::fromString(fixedFileName); - if (filePath.needsDevice()) - return new FSEngineImpl(filePath); + FilePath fixedPath = FilePath::fromString(fixedFileName); + + if (fixedPath.needsDevice()) + return new FSEngineImpl(removeDoubleSlash(fileName)); } if (fixedFileName.compare(QDir::rootPath(), Qt::CaseInsensitive) == 0) - return new RootInjectFSEngine(fixedFileName); + return new RootInjectFSEngine(fileName); return nullptr; } diff --git a/src/libs/utils/futuresynchronizer.cpp b/src/libs/utils/futuresynchronizer.cpp index baef71f4ef3..da3347f35e3 100644 --- a/src/libs/utils/futuresynchronizer.cpp +++ b/src/libs/utils/futuresynchronizer.cpp @@ -3,7 +3,9 @@ #include "futuresynchronizer.h" -/*! \class Utils::FutureSynchronizer +/*! + \class Utils::FutureSynchronizer + \inmodule QtCreator \brief The FutureSynchronizer is an enhanced version of QFutureSynchronizer. */ diff --git a/src/libs/utils/futuresynchronizer.h b/src/libs/utils/futuresynchronizer.h index 6a8192f0eb0..29b1f5e4563 100644 --- a/src/libs/utils/futuresynchronizer.h +++ b/src/libs/utils/futuresynchronizer.h @@ -31,14 +31,15 @@ public: void clearFutures(); void setCancelOnWait(bool enabled); - bool isCancelOnWait() const; // TODO: The original contained cancelOnWait, what suggests action, not a getter + // Note: The QFutureSynchronizer contains cancelOnWait(), what suggests action, not a getter. + bool isCancelOnWait() const; void flushFinishedFutures(); private: - QList> m_futures; - bool m_cancelOnWait = false; // TODO: True default makes more sense... + // Note: This default value is different than QFutureSynchronizer's one. True makes more sense. + bool m_cancelOnWait = true; }; } // namespace Utils diff --git a/src/libs/utils/guard.cpp b/src/libs/utils/guard.cpp index a70faf2c405..0273e7c9b8d 100644 --- a/src/libs/utils/guard.cpp +++ b/src/libs/utils/guard.cpp @@ -4,7 +4,9 @@ #include "guard.h" #include "qtcassert.h" -/*! \class Utils::Guard +/*! + \class Utils::Guard + \inmodule QtCreator \brief The Guard class implements a recursive guard with locking mechanism. diff --git a/src/libs/utils/headerviewstretcher.cpp b/src/libs/utils/headerviewstretcher.cpp index 3bc5c1d515c..2c0dd00ca02 100644 --- a/src/libs/utils/headerviewstretcher.cpp +++ b/src/libs/utils/headerviewstretcher.cpp @@ -10,6 +10,7 @@ using namespace Utils; /*! \class Utils::HeaderViewStretcher + \inmodule QtCreator \brief The HeaderViewStretcher class fixes QHeaderView to resize all columns to contents, except one diff --git a/src/libs/utils/images/iconoverlay_close_small.png b/src/libs/utils/images/iconoverlay_close_small.png new file mode 100644 index 00000000000..e39b67cfbb3 Binary files /dev/null and b/src/libs/utils/images/iconoverlay_close_small.png differ diff --git a/src/libs/utils/images/iconoverlay_close_small@2x.png b/src/libs/utils/images/iconoverlay_close_small@2x.png new file mode 100644 index 00000000000..e8a02dff03a Binary files /dev/null and b/src/libs/utils/images/iconoverlay_close_small@2x.png differ diff --git a/src/libs/utils/images/pinned_small.png b/src/libs/utils/images/pinned_small.png new file mode 100644 index 00000000000..10f96f30954 Binary files /dev/null and b/src/libs/utils/images/pinned_small.png differ diff --git a/src/libs/utils/images/pinned_small@2x.png b/src/libs/utils/images/pinned_small@2x.png new file mode 100644 index 00000000000..672af736fa5 Binary files /dev/null and b/src/libs/utils/images/pinned_small@2x.png differ diff --git a/src/libs/utils/itemviews.cpp b/src/libs/utils/itemviews.cpp index 8af7eba5a18..27f7cbb4281 100644 --- a/src/libs/utils/itemviews.cpp +++ b/src/libs/utils/itemviews.cpp @@ -5,6 +5,7 @@ /*! \class Utils::TreeView + \inmodule QtCreator \brief The TreeView adds setActivationMode to QTreeView to allow for single click/double click behavior on @@ -15,6 +16,7 @@ /*! \class Utils::TreeWidget + \inmodule QtCreator \brief The TreeWidget adds setActivationMode to QTreeWidget to allow for single click/double click behavior on @@ -25,6 +27,7 @@ /*! \class Utils::ListView + \inmodule QtCreator \brief The ListView adds setActivationMode to QListView to allow for single click/double click behavior on @@ -35,6 +38,7 @@ /*! \class Utils::ListWidget + \inmodule QtCreator \brief The ListWidget adds setActivationMode to QListWidget to allow for single click/double click behavior on diff --git a/src/libs/utils/launcherinterface.cpp b/src/libs/utils/launcherinterface.cpp index e71b09b0edc..218286521db 100644 --- a/src/libs/utils/launcherinterface.cpp +++ b/src/libs/utils/launcherinterface.cpp @@ -21,20 +21,6 @@ namespace Utils { namespace Internal { -class LauncherProcess : public QProcess -{ -public: - LauncherProcess(QObject *parent) : QProcess(parent) - { -#ifdef Q_OS_UNIX - setChildProcessModifier([this] { - const auto pid = static_cast(processId()); - setpgid(pid, pid); - }); -#endif - } -}; - static QString launcherSocketName() { return TemporaryDirectory::masterDirectoryPath() @@ -64,7 +50,7 @@ signals: private: QLocalServer * const m_server; Internal::LauncherSocket *const m_socket; - Internal::LauncherProcess *m_process = nullptr; + QProcess *m_process = nullptr; QString m_pathToLauncher; }; @@ -89,12 +75,18 @@ void LauncherInterfacePrivate::doStart() emit errorOccurred(m_server->errorString()); return; } - m_process = new LauncherProcess(this); + m_process = new QProcess(this); connect(m_process, &QProcess::errorOccurred, this, &LauncherInterfacePrivate::handleProcessError); connect(m_process, &QProcess::finished, this, &LauncherInterfacePrivate::handleProcessFinished); connect(m_process, &QProcess::readyReadStandardError, this, &LauncherInterfacePrivate::handleProcessStderr); +#ifdef Q_OS_UNIX + m_process->setChildProcessModifier([] { + setpgid(0, 0); + }); +#endif + m_process->start(launcherFilePath(), QStringList(m_server->fullServerName())); } diff --git a/src/libs/utils/launcherpackets.cpp b/src/libs/utils/launcherpackets.cpp index 13c3a1560d4..37261224ef5 100644 --- a/src/libs/utils/launcherpackets.cpp +++ b/src/libs/utils/launcherpackets.cpp @@ -48,7 +48,8 @@ void StartProcessPacket::doSerialize(QDataStream &stream) const << lowPriority << unixTerminalDisabled << useCtrlCStub - << reaperTimeout; + << reaperTimeout + << createConsoleOnWindows; } void StartProcessPacket::doDeserialize(QDataStream &stream) @@ -68,7 +69,8 @@ void StartProcessPacket::doDeserialize(QDataStream &stream) >> lowPriority >> unixTerminalDisabled >> useCtrlCStub - >> reaperTimeout; + >> reaperTimeout + >> createConsoleOnWindows; processMode = Utils::ProcessMode(processModeInt); processChannelMode = QProcess::ProcessChannelMode(processChannelModeInt); } diff --git a/src/libs/utils/launcherpackets.h b/src/libs/utils/launcherpackets.h index 2f0bae2915e..27e98a74e5b 100644 --- a/src/libs/utils/launcherpackets.h +++ b/src/libs/utils/launcherpackets.h @@ -98,6 +98,7 @@ public: bool unixTerminalDisabled = false; bool useCtrlCStub = false; int reaperTimeout = 500; + bool createConsoleOnWindows = false; private: void doSerialize(QDataStream &stream) const override; diff --git a/src/libs/utils/launchersocket.cpp b/src/libs/utils/launchersocket.cpp index 07ebc84df9a..b1ffb77055f 100644 --- a/src/libs/utils/launchersocket.cpp +++ b/src/libs/utils/launchersocket.cpp @@ -228,7 +228,7 @@ void CallerHandle::start(const QString &program, const QStringList &arguments) auto startWhenRunning = [&program, &oldProgram = m_command] { qWarning() << "Trying to start" << program << "while" << oldProgram - << "is still running for the same QtcProcess instance." + << "is still running for the same Process instance." << "The current call will be ignored."; }; QTC_ASSERT(m_processState == QProcess::NotRunning, startWhenRunning(); return); @@ -246,6 +246,8 @@ void CallerHandle::start(const QString &program, const QStringList &arguments) p.command = m_command; p.arguments = m_arguments; p.env = m_setup->m_environment.toStringList(); + if (p.env.isEmpty()) + p.env = Environment::systemEnvironment().toStringList(); p.workingDir = m_setup->m_workingDirectory.path(); p.processMode = m_setup->m_processMode; p.writeData = m_setup->m_writeData; @@ -257,6 +259,7 @@ void CallerHandle::start(const QString &program, const QStringList &arguments) p.unixTerminalDisabled = m_setup->m_unixTerminalDisabled; p.useCtrlCStub = m_setup->m_useCtrlCStub; p.reaperTimeout = m_setup->m_reaperTimeout; + p.createConsoleOnWindows = m_setup->m_createConsoleOnWindows; sendPacket(p); } @@ -619,7 +622,7 @@ void LauncherSocket::handleSocketDataAvailable() } } else { // qDebug() << "No handler for token" << m_packetParser.token() << m_handles; - // in this case the QtcProcess was canceled and deleted + // in this case the Process was canceled and deleted } handleSocketDataAvailable(); } diff --git a/src/libs/utils/launchersocket.h b/src/libs/utils/launchersocket.h index 3a9be73e65e..be1b7f7f9ba 100644 --- a/src/libs/utils/launchersocket.h +++ b/src/libs/utils/launchersocket.h @@ -124,7 +124,7 @@ private: // Moved to the launcher thread, returned to caller's thread. // It's assumed that this object will be alive at least -// as long as the corresponding QtcProcess is alive. +// as long as the corresponding Process is alive. class LauncherHandle : public QObject { diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index e7136c5a900..bea7faafd3b 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -3,36 +3,188 @@ #include "layoutbuilder.h" -#include "aspects.h" -#include "qtcassert.h" - +#include #include #include #include +#include #include #include +#include +#include #include #include #include -#include +#include +#include -namespace Utils::Layouting { +namespace Layouting { + +// That's cut down qtcassert.{c,h} to avoid the dependency. +#define QTC_STRINGIFY_HELPER(x) #x +#define QTC_STRINGIFY(x) QTC_STRINGIFY_HELPER(x) +#define QTC_STRING(cond) qDebug("SOFT ASSERT: \"%s\" in %s: %s", cond, __FILE__, QTC_STRINGIFY(__LINE__)) +#define QTC_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QTC_STRING(#cond); action; } do {} while (0) +#define QTC_CHECK(cond) if (cond) {} else { QTC_STRING(#cond); } do {} while (0) + +class FlowLayout final : public QLayout +{ + Q_OBJECT + +public: + explicit FlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1) + : QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing) + { + setContentsMargins(margin, margin, margin, margin); + } + + FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1) + : m_hSpace(hSpacing), m_vSpace(vSpacing) + { + setContentsMargins(margin, margin, margin, margin); + } + + ~FlowLayout() override + { + QLayoutItem *item; + while ((item = takeAt(0))) + delete item; + } + + void addItem(QLayoutItem *item) override { itemList.append(item); } + + int horizontalSpacing() const + { + if (m_hSpace >= 0) + return m_hSpace; + else + return smartSpacing(QStyle::PM_LayoutHorizontalSpacing); + } + + int verticalSpacing() const + { + if (m_vSpace >= 0) + return m_vSpace; + else + return smartSpacing(QStyle::PM_LayoutVerticalSpacing); + } + + Qt::Orientations expandingDirections() const override + { + return {}; + } + + bool hasHeightForWidth() const override { return true; } + + int heightForWidth(int width) const override + { + int height = doLayout(QRect(0, 0, width, 0), true); + return height; + } + + int count() const override { return itemList.size(); } + + QLayoutItem *itemAt(int index) const override + { + return itemList.value(index); + } + + QSize minimumSize() const override + { + QSize size; + for (QLayoutItem *item : itemList) + size = size.expandedTo(item->minimumSize()); + + int left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + size += QSize(left + right, top + bottom); + return size; + } + + void setGeometry(const QRect &rect) override + { + QLayout::setGeometry(rect); + doLayout(rect, false); + } + + QSize sizeHint() const override + { + return minimumSize(); + } + + QLayoutItem *takeAt(int index) override + { + if (index >= 0 && index < itemList.size()) + return itemList.takeAt(index); + else + return nullptr; + } + +private: + int doLayout(const QRect &rect, bool testOnly) const + { + int left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom); + int x = effectiveRect.x(); + int y = effectiveRect.y(); + int lineHeight = 0; + + for (QLayoutItem *item : itemList) { + QWidget *wid = item->widget(); + int spaceX = horizontalSpacing(); + if (spaceX == -1) + spaceX = wid->style()->layoutSpacing( + QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal); + int spaceY = verticalSpacing(); + if (spaceY == -1) + spaceY = wid->style()->layoutSpacing( + QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical); + int nextX = x + item->sizeHint().width() + spaceX; + if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) { + x = effectiveRect.x(); + y = y + lineHeight + spaceY; + nextX = x + item->sizeHint().width() + spaceX; + lineHeight = 0; + } + + if (!testOnly) + item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); + + x = nextX; + lineHeight = qMax(lineHeight, item->sizeHint().height()); + } + return y + lineHeight - rect.y() + bottom; + } + + int smartSpacing(QStyle::PixelMetric pm) const + { + QObject *parent = this->parent(); + if (!parent) { + return -1; + } else if (parent->isWidgetType()) { + auto pw = static_cast(parent); + return pw->style()->pixelMetric(pm, nullptr, pw); + } else { + return static_cast(parent)->spacing(); + } + } + + QList itemList; + int m_hSpace; + int m_vSpace; +}; /*! - \enum Utils::LayoutBuilder::LayoutType + \namespace Layouting \inmodule QtCreator - The LayoutType enum describes the type of \c QLayout a layout builder - operates on. - - \value Form - \value Grid - \value HBox - \value VBox + \brief The Layouting namespace contains classes for use with layout builders. */ + /*! - \class Utils::LayoutBuilder::LayoutItem + \class Layouting::LayoutItem \inmodule QtCreator \brief The LayoutItem class represents widgets, layouts, and aggregate @@ -46,83 +198,59 @@ namespace Utils::Layouting { /*! Constructs a layout item instance representing an empty cell. */ -LayoutItem::LayoutItem() -{} +LayoutItem::LayoutItem() = default; + +LayoutItem::~LayoutItem() = default; /*! - Constructs a layout item proxy for \a layout. + \fn template LayoutItem(const T &t) + \internal + + Constructs a layout item proxy for \a t. + + T could be + \list + \li \c {QString} + \li \c {QWidget *} + \li \c {QLayout *} + \endlist */ -LayoutItem::LayoutItem(QLayout *layout) - : layout(layout) -{} -/*! - Constructs a layout item proxy for \a widget. - */ -LayoutItem::LayoutItem(QWidget *widget) - : widget(widget) -{} - -/*! - Constructs a layout item representing a \c BaseAspect. - - This ultimately uses the \a aspect's \c addToLayout(LayoutBuilder &) function, - which in turn can add one or more layout items to the target layout. - - \sa BaseAspect::addToLayout() - */ -LayoutItem::LayoutItem(BaseAspect &aspect) - : aspect(&aspect) -{} - -LayoutItem::LayoutItem(BaseAspect *aspect) - : aspect(aspect) -{} - -/*! - Constructs a layout item containing some static \a text. - */ -LayoutItem::LayoutItem(const QString &text) - : text(text) -{} - -QLayout *LayoutBuilder::createLayout() const +struct ResultItem { + ResultItem() = default; + explicit ResultItem(QLayout *l) : layout(l) {} + explicit ResultItem(QWidget *w) : widget(w) {} + + QString text; QLayout *layout = nullptr; - switch (m_layoutType) { - case LayoutBuilder::FormLayout: { - auto formLayout = new QFormLayout; - formLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); - layout = formLayout; - break; - } - case LayoutBuilder::GridLayout: { - auto gridLayout = new QGridLayout; - layout = gridLayout; - break; - } - case LayoutBuilder::HBoxLayout: { - auto hboxLayout = new QHBoxLayout; - layout = hboxLayout; - break; - } - case LayoutBuilder::VBoxLayout: { - auto vboxLayout = new QVBoxLayout; - layout = vboxLayout; - break; - } - case LayoutBuilder::StackLayout: { - auto stackLayout = new QStackedLayout; - layout = stackLayout; - break; - } - } - QTC_ASSERT(layout, return nullptr); - if (m_spacing) - layout->setSpacing(*m_spacing); - return layout; -} + QWidget *widget = nullptr; + int space = -1; + int stretch = -1; + int span = 1; +}; + +struct Slice +{ + Slice() = default; + Slice(QLayout *l) : layout(l) {} + Slice(QWidget *w) : widget(w) {} + + QLayout *layout = nullptr; + QWidget *widget = nullptr; + + void flush(); + + // Grid-specific + int currentGridColumn = 0; + int currentGridRow = 0; + bool isFormAlignment = false; + Qt::Alignment align = {}; // Can be changed to + + // Grid or Form + QList pendingItems; +}; static QWidget *widgetForItem(QLayoutItem *item) { @@ -147,18 +275,16 @@ static QLabel *createLabel(const QString &text) return label; } -static void addItemToBoxLayout(QBoxLayout *layout, const LayoutItem &item) +static void addItemToBoxLayout(QBoxLayout *layout, const ResultItem &item) { if (QWidget *w = item.widget) { layout->addWidget(w); } else if (QLayout *l = item.layout) { layout->addLayout(l); - } else if (item.specialType == LayoutItem::SpecialType::Stretch) { - layout->addStretch(item.specialValue.toInt()); - } else if (item.specialType == LayoutItem::SpecialType::Space) { - layout->addSpacing(item.specialValue.toInt()); - } else if (item.specialType == LayoutItem::SpecialType::HorizontalRule) { - layout->addWidget(Layouting::createHr()); + } else if (item.stretch != -1) { + layout->addStretch(item.stretch); + } else if (item.space != -1) { + layout->addSpacing(item.space); } else if (!item.text.isEmpty()) { layout->addWidget(createLabel(item.text)); } else { @@ -166,138 +292,205 @@ static void addItemToBoxLayout(QBoxLayout *layout, const LayoutItem &item) } } -static void flushPendingFormItems(QFormLayout *formLayout, - LayoutBuilder::LayoutItems &pendingFormItems) +static void addItemToFlowLayout(FlowLayout *layout, const ResultItem &item) { - QTC_ASSERT(formLayout, return); + if (QWidget *w = item.widget) { + layout->addWidget(w); + } else if (QLayout *l = item.layout) { + layout->addItem(l); +// } else if (item.stretch != -1) { +// layout->addStretch(item.stretch); +// } else if (item.space != -1) { +// layout->addSpacing(item.space); + } else if (!item.text.isEmpty()) { + layout->addWidget(createLabel(item.text)); + } else { + QTC_CHECK(false); + } +} - if (pendingFormItems.empty()) +void Slice::flush() +{ + if (pendingItems.empty()) return; - // If there are more than two items, we cram the last ones in one hbox. - if (pendingFormItems.size() > 2) { - auto hbox = new QHBoxLayout; - hbox->setContentsMargins(0, 0, 0, 0); - for (int i = 1; i < pendingFormItems.size(); ++i) - addItemToBoxLayout(hbox, pendingFormItems.at(i)); - while (pendingFormItems.size() >= 2) - pendingFormItems.pop_back(); - pendingFormItems.append(LayoutItem(hbox)); - } + if (auto formLayout = qobject_cast(layout)) { - if (pendingFormItems.size() == 1) { // One one item given, so this spans both columns. - if (auto layout = pendingFormItems.at(0).layout) - formLayout->addRow(layout); - else if (auto widget = pendingFormItems.at(0).widget) - formLayout->addRow(widget); - } else if (pendingFormItems.size() == 2) { // Normal case, both columns used. - if (auto label = pendingFormItems.at(0).widget) { - if (auto layout = pendingFormItems.at(1).layout) - formLayout->addRow(label, layout); - else if (auto widget = pendingFormItems.at(1).widget) - formLayout->addRow(label, widget); - } else { - if (auto layout = pendingFormItems.at(1).layout) - formLayout->addRow(pendingFormItems.at(0).text, layout); - else if (auto widget = pendingFormItems.at(1).widget) - formLayout->addRow(pendingFormItems.at(0).text, widget); + // If there are more than two items, we cram the last ones in one hbox. + if (pendingItems.size() > 2) { + auto hbox = new QHBoxLayout; + hbox->setContentsMargins(0, 0, 0, 0); + for (int i = 1; i < pendingItems.size(); ++i) + addItemToBoxLayout(hbox, pendingItems.at(i)); + while (pendingItems.size() > 1) + pendingItems.pop_back(); + pendingItems.append(ResultItem(hbox)); } + + if (pendingItems.size() == 1) { // One one item given, so this spans both columns. + const ResultItem &f0 = pendingItems.at(0); + if (auto layout = f0.layout) + formLayout->addRow(layout); + else if (auto widget = f0.widget) + formLayout->addRow(widget); + } else if (pendingItems.size() == 2) { // Normal case, both columns used. + ResultItem &f1 = pendingItems[1]; + const ResultItem &f0 = pendingItems.at(0); + if (!f1.widget && !f1.layout && !f1.text.isEmpty()) + f1.widget = createLabel(f1.text); + + if (f0.widget) { + if (f1.layout) + formLayout->addRow(f0.widget, f1.layout); + else if (f1.widget) + formLayout->addRow(f0.widget, f1.widget); + } else { + if (f1.layout) + formLayout->addRow(f0.text, f1.layout); + else if (f1.widget) + formLayout->addRow(f0.text, f1.widget); + } + } else { + QTC_CHECK(false); + } + + // Set up label as buddy if possible. + const int lastRow = formLayout->rowCount() - 1; + QLayoutItem *l = formLayout->itemAt(lastRow, QFormLayout::LabelRole); + QLayoutItem *f = formLayout->itemAt(lastRow, QFormLayout::FieldRole); + if (l && f) { + if (QLabel *label = qobject_cast(l->widget())) { + if (QWidget *widget = widgetForItem(f)) + label->setBuddy(widget); + } + } + + } else if (auto gridLayout = qobject_cast(layout)) { + + for (const ResultItem &item : std::as_const(pendingItems)) { + Qt::Alignment a = currentGridColumn == 0 ? align : Qt::Alignment(); + if (item.widget) + gridLayout->addWidget(item.widget, currentGridRow, currentGridColumn, 1, item.span, a); + else if (item.layout) + gridLayout->addLayout(item.layout, currentGridRow, currentGridColumn, 1, item.span, a); + else if (!item.text.isEmpty()) + gridLayout->addWidget(createLabel(item.text), currentGridRow, currentGridColumn, 1, 1, a); + currentGridColumn += item.span; + } + ++currentGridRow; + currentGridColumn = 0; + + } else if (auto boxLayout = qobject_cast(layout)) { + + for (const ResultItem &item : std::as_const(pendingItems)) + addItemToBoxLayout(boxLayout, item); + + } else if (auto flowLayout = qobject_cast(layout)) { + + for (const ResultItem &item : std::as_const(pendingItems)) + addItemToFlowLayout(flowLayout, item); + + } else if (auto stackLayout = qobject_cast(layout)) { + for (const ResultItem &item : std::as_const(pendingItems)) { + if (item.widget) + stackLayout->addWidget(item.widget); + else + QTC_CHECK(false); + } + } else { QTC_CHECK(false); } - // Set up label as buddy if possible. - const int lastRow = formLayout->rowCount() - 1; - QLayoutItem *l = formLayout->itemAt(lastRow, QFormLayout::LabelRole); - QLayoutItem *f = formLayout->itemAt(lastRow, QFormLayout::FieldRole); - if (l && f) { - if (QLabel *label = qobject_cast(l->widget())) { - if (QWidget *widget = widgetForItem(f)) - label->setBuddy(widget); - } + pendingItems.clear(); +} + +// LayoutBuilder + +class LayoutBuilder +{ + Q_DISABLE_COPY_MOVE(LayoutBuilder) + +public: + LayoutBuilder(); + ~LayoutBuilder(); + + void addItem(const LayoutItem &item); + void addItems(const LayoutItems &items); + + QList stack; +}; + +static void addItemHelper(LayoutBuilder &builder, const LayoutItem &item) +{ + if (item.onAdd) + item.onAdd(builder); + + if (item.setter) { + if (QWidget *widget = builder.stack.last().widget) + item.setter(widget); + else if (QLayout *layout = builder.stack.last().layout) + item.setter(layout); + else + QTC_CHECK(false); } - pendingFormItems.clear(); + for (const LayoutItem &subItem : item.subItems) + addItemHelper(builder, subItem); + + if (item.onExit) + item.onExit(builder); } -static void doLayoutHelper(QLayout *layout, - const LayoutBuilder::LayoutItems &items, - const Layouting::AttachType attachType, - int currentGridRow = 0) +void doAddText(LayoutBuilder &builder, const QString &text) { - int currentGridColumn = 0; - LayoutBuilder::LayoutItems pendingFormItems; + ResultItem fi; + fi.text = text; + builder.stack.last().pendingItems.append(fi); +} - auto formLayout = qobject_cast(layout); - auto gridLayout = qobject_cast(layout); - auto boxLayout = qobject_cast(layout); - auto stackLayout = qobject_cast(layout); +void doAddSpace(LayoutBuilder &builder, const Space &space) +{ + ResultItem fi; + fi.space = space.space; + builder.stack.last().pendingItems.append(fi); +} - for (const LayoutItem &item : items) { - if (item.specialType == LayoutItem::SpecialType::Break) { - if (formLayout) - flushPendingFormItems(formLayout, pendingFormItems); - else if (gridLayout) { - if (currentGridColumn != 0) { - ++currentGridRow; - currentGridColumn = 0; - } - } - continue; - } +void doAddStretch(LayoutBuilder &builder, const Stretch &stretch) +{ + ResultItem fi; + fi.stretch = stretch.stretch; + builder.stack.last().pendingItems.append(fi); +} - QWidget *widget = item.widget; +void doAddLayout(LayoutBuilder &builder, QLayout *layout) +{ + builder.stack.last().pendingItems.append(ResultItem(layout)); +} - if (gridLayout) { - Qt::Alignment align = {}; - if (attachType == Layouting::WithFormAlignment && currentGridColumn == 0) - align = Qt::Alignment(widget->style()->styleHint(QStyle::SH_FormLayoutLabelAlignment)); - if (widget) - gridLayout->addWidget(widget, currentGridRow, currentGridColumn, 1, item.span, align); - else if (item.layout) - gridLayout->addLayout(item.layout, currentGridRow, currentGridColumn, 1, item.span, align); - else if (!item.text.isEmpty()) - gridLayout->addWidget(createLabel(item.text), currentGridRow, currentGridColumn, 1, 1, align); - currentGridColumn += item.span; - } else if (boxLayout) { - addItemToBoxLayout(boxLayout, item); - } else if (stackLayout) { - stackLayout->addWidget(item.widget); - } else { - pendingFormItems.append(item); - } - } - - if (formLayout) - flushPendingFormItems(formLayout, pendingFormItems); +void doAddWidget(LayoutBuilder &builder, QWidget *widget) +{ + builder.stack.last().pendingItems.append(ResultItem(widget)); } /*! - Constructs a layout item from the contents of another LayoutBuilder - */ -LayoutItem::LayoutItem(const LayoutBuilder &builder) -{ - layout = builder.createLayout(); - doLayoutHelper(layout, builder.m_items, Layouting::WithoutMargins); -} - -/*! - \class Utils::LayoutBuilder::Space + \class Layouting::Space \inmodule QtCreator - \brief The LayoutBuilder::Space class represents some empty space in a layout. + \brief The Space class represents some empty space in a layout. */ /*! - \class Utils::LayoutBuilder::Stretch + \class Layouting::Stretch \inmodule QtCreator - \brief The LayoutBuilder::Stretch class represents some stretch in a layout. + \brief The Stretch class represents some stretch in a layout. */ /*! - \class Utils::LayoutBuilder + \class Layouting::LayoutBuilder + \internal \inmodule QtCreator \brief The LayoutBuilder class provides a convenient way to fill \c QFormLayout @@ -307,263 +500,350 @@ LayoutItem::LayoutItem(const LayoutBuilder &builder) A LayoutBuilder instance is typically used locally within a function and never stored. - \sa addItem(), addItems(), addRow(), finishRow() + \sa addItem(), addItems() */ -LayoutBuilder::LayoutBuilder(LayoutType layoutType, const LayoutItems &items) - : m_layoutType(layoutType) -{ - m_items.reserve(items.size() * 2); - for (const LayoutItem &item : items) - addItem(item); -} - -LayoutBuilder &LayoutBuilder::setSpacing(int spacing) -{ - m_spacing = spacing; - return *this; -} LayoutBuilder::LayoutBuilder() = default; /*! + \internal Destructs a layout builder. */ LayoutBuilder::~LayoutBuilder() = default; -/*! - Instructs a layout builder to finish the current row. - This is implicitly called by LayoutBuilder's destructor. - */ -LayoutBuilder &LayoutBuilder::finishRow() +void LayoutBuilder::addItem(const LayoutItem &item) { - addItem(Break()); - return *this; + addItemHelper(*this, item); } -/*! - This starts a new row containing the \a item. The row can be further extended by - other items using \c addItem() or \c addItems(). - - \sa finishRow(), addItem(), addItems() - */ -LayoutBuilder &LayoutBuilder::addRow(const LayoutItem &item) -{ - return finishRow().addItem(item); -} - -/*! - This starts a new row containing \a items. The row can be further extended by - other items using \c addItem() or \c addItems(). - - \sa finishRow(), addItem(), addItems() - */ -LayoutBuilder &LayoutBuilder::addRow(const LayoutItems &items) -{ - return finishRow().addItems(items); -} - -/*! - Adds the layout item \a item to the current row. - */ -LayoutBuilder &LayoutBuilder::addItem(const LayoutItem &item) -{ - if (item.aspect) { - item.aspect->addToLayout(*this); - if (m_layoutType == FormLayout || m_layoutType == VBoxLayout) - finishRow(); - } else { - m_items.push_back(item); - } - return *this; -} - -void LayoutBuilder::doLayout(QWidget *parent, Layouting::AttachType attachType) const -{ - QLayout *layout = createLayout(); - parent->setLayout(layout); - - doLayoutHelper(layout, m_items, attachType); - if (attachType == Layouting::WithoutMargins) - layout->setContentsMargins(0, 0, 0, 0); -} - -/*! - Adds the layout item \a items to the current row. - */ -LayoutBuilder &LayoutBuilder::addItems(const LayoutItems &items) +void LayoutBuilder::addItems(const LayoutItems &items) { for (const LayoutItem &item : items) - addItem(item); - return *this; + addItemHelper(*this, item); } /*! - Attach the constructed layout to the provided \c QWidget \a parent. + Starts a new row containing \a items. The row can be further extended by + other items using \c addItem() or \c addItems(). + + \sa addItem(), addItems() + */ +void LayoutItem::addRow(const LayoutItems &items) +{ + addItem(br); + addItems(items); +} + +/*! + Adds the layout item \a item as sub items. + */ +void LayoutItem::addItem(const LayoutItem &item) +{ + subItems.append(item); +} + +/*! + Adds the layout items \a items as sub items. + */ +void LayoutItem::addItems(const LayoutItems &items) +{ + subItems.append(items); +} + +/*! + Attaches the constructed layout to the provided QWidget \a w. This operation can only be performed once per LayoutBuilder instance. */ -void LayoutBuilder::attachTo(QWidget *w, Layouting::AttachType attachType) const + +void LayoutItem::attachTo(QWidget *w) const { - doLayout(w, attachType); + LayoutBuilder builder; + + builder.stack.append(w); + addItemHelper(builder, *this); } -QWidget *LayoutBuilder::emerge(Layouting::AttachType attachType) +QWidget *LayoutItem::emerge() { auto w = new QWidget; - doLayout(w, attachType); + attachTo(w); return w; } -/*! - Constructs a layout extender to extend an existing \a layout. - - This constructor can be used to continue the work of previous layout building. - The type of the underlying layout and previous contents will be retained, - new items will be added below existing ones. - */ - -LayoutExtender::LayoutExtender(QLayout *layout, Layouting::AttachType attachType) - : m_layout(layout), m_attachType(attachType) -{} - -LayoutExtender::~LayoutExtender() +static void layoutExit(LayoutBuilder &builder) { - QTC_ASSERT(m_layout, return); - int currentGridRow = 0; - if (auto gridLayout = qobject_cast(m_layout)) - currentGridRow = gridLayout->rowCount(); - doLayoutHelper(m_layout, m_items, m_attachType, currentGridRow); + builder.stack.last().flush(); + QLayout *layout = builder.stack.last().layout; + builder.stack.pop_back(); + + if (QWidget *widget = builder.stack.last().widget) + widget->setLayout(layout); + else + builder.stack.last().pendingItems.append(ResultItem(layout)); } -// Special items - -Break::Break() +static void widgetExit(LayoutBuilder &builder) { - specialType = SpecialType::Break; + QWidget *widget = builder.stack.last().widget; + builder.stack.pop_back(); + builder.stack.last().pendingItems.append(ResultItem(widget)); } -Stretch::Stretch(int stretch) +Column::Column(std::initializer_list items) { - specialType = SpecialType::Stretch; - specialValue = stretch; + subItems = items; + onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QVBoxLayout); }; + onExit = layoutExit; } -Space::Space(int space) +Row::Row(std::initializer_list items) { - specialType = SpecialType::Space; - specialValue = space; + subItems = items; + onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QHBoxLayout); }; + onExit = layoutExit; } -Span::Span(int span_, const LayoutItem &item) +Flow::Flow(std::initializer_list items) { - LayoutItem::operator=(item); - span = span_; + subItems = items; + onAdd = [](LayoutBuilder &builder) { builder.stack.append(new FlowLayout); }; + onExit = layoutExit; } -Tab::Tab(const QString &tabName, const LayoutBuilder &item) +Grid::Grid(std::initializer_list items) { - text = tabName; - widget = new QWidget; - item.attachTo(widget); + subItems = items; + onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QGridLayout); }; + onExit = layoutExit; } -HorizontalRule::HorizontalRule() +static QFormLayout *newFormLayout() { - specialType = SpecialType::HorizontalRule; + auto formLayout = new QFormLayout; + formLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); + return formLayout; +} + +Form::Form(std::initializer_list items) +{ + subItems = items; + onAdd = [](LayoutBuilder &builder) { builder.stack.append(newFormLayout()); }; + onExit = layoutExit; +} + +Stack::Stack(std::initializer_list items) +{ + subItems = items; + onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QStackedLayout); }; + onExit = layoutExit; +} + +LayoutItem br() +{ + LayoutItem item; + item.onAdd = [](LayoutBuilder &builder) { + builder.stack.last().flush(); + }; + return item; +} + +LayoutItem empty() +{ + LayoutItem item; + item.onAdd = [](LayoutBuilder &builder) { + ResultItem ri; + ri.span = 1; + builder.stack.last().pendingItems.append(ResultItem()); + }; + return item; +} + +LayoutItem hr() +{ + LayoutItem item; + item.onAdd = [](LayoutBuilder &builder) { doAddWidget(builder, createHr()); }; + return item; +} + +LayoutItem st() +{ + LayoutItem item; + item.onAdd = [](LayoutBuilder &builder) { doAddStretch(builder, Stretch(1)); }; + return item; +} + +LayoutItem noMargin() +{ + LayoutItem item; + item.onAdd = [](LayoutBuilder &builder) { + if (auto layout = builder.stack.last().layout) + layout->setContentsMargins(0, 0, 0, 0); + else if (auto widget = builder.stack.last().widget) + widget->setContentsMargins(0, 0, 0, 0); + }; + return item; +} + +LayoutItem normalMargin() +{ + LayoutItem item; + item.onAdd = [](LayoutBuilder &builder) { + if (auto layout = builder.stack.last().layout) + layout->setContentsMargins(9, 9, 9, 9); + else if (auto widget = builder.stack.last().widget) + widget->setContentsMargins(9, 9, 9, 9); + }; + return item; +} + +LayoutItem withFormAlignment() +{ + LayoutItem item; + item.onAdd = [](LayoutBuilder &builder) { + if (builder.stack.size() >= 2) { + if (auto widget = builder.stack.at(builder.stack.size() - 2).widget) { + const Qt::Alignment align(widget->style()->styleHint(QStyle::SH_FormLayoutLabelAlignment)); + builder.stack.last().align = align; + } + } + }; + return item; } // "Widgets" -static void applyItems(QWidget *widget, const QList &items) +template +void setupWidget(LayoutItem *item) { - bool hadLayout = false; - for (const LayoutItem &item : items) { - if (item.setter) { - item.setter(widget); - } else if (item.layout && !hadLayout) { - hadLayout = true; - widget->setLayout(item.layout); - } else { - QTC_CHECK(false); - } - } + item->onAdd = [](LayoutBuilder &builder) { builder.stack.append(new T); }; + item->onExit = widgetExit; +}; + +Widget::Widget(std::initializer_list items) +{ + this->subItems = items; + setupWidget(this); } Group::Group(std::initializer_list items) { - widget = new QGroupBox; - applyItems(widget, items); + this->subItems = items; + setupWidget(this); } PushButton::PushButton(std::initializer_list items) { - widget = new QPushButton; - applyItems(widget, items); + this->subItems = items; + setupWidget(this); +} + +SpinBox::SpinBox(std::initializer_list items) +{ + this->subItems = items; + setupWidget(this); +} + +TextEdit::TextEdit(std::initializer_list items) +{ + this->subItems = items; + setupWidget(this); } Splitter::Splitter(std::initializer_list items) - : Splitter(new QSplitter(Qt::Vertical), items) {} - -Splitter::Splitter(QSplitter *splitter, std::initializer_list items) { - widget = splitter; - for (const LayoutItem &item : items) - splitter->addWidget(item.widget); + subItems = items; + onAdd = [](LayoutBuilder &builder) { + auto splitter = new QSplitter; + splitter->setOrientation(Qt::Vertical); + builder.stack.append(splitter); + }; + onExit = [](LayoutBuilder &builder) { + const Slice slice = builder.stack.last(); + QSplitter *splitter = qobject_cast(slice.widget); + for (const ResultItem &ri : slice.pendingItems) { + if (ri.widget) + splitter->addWidget(ri.widget); + } + builder.stack.pop_back(); + builder.stack.last().pendingItems.append(ResultItem(splitter)); + }; } -TabWidget::TabWidget(std::initializer_list tabs) - : TabWidget(new QTabWidget, tabs) {} - -TabWidget::TabWidget(QTabWidget *tabWidget, std::initializer_list tabs) +TabWidget::TabWidget(std::initializer_list items) { - widget = tabWidget; - for (const Tab &tab : tabs) - tabWidget->addTab(tab.widget, tab.text); + this->subItems = items; + setupWidget(this); +} + +// Special Tab + +Tab::Tab(const QString &tabName, const LayoutItem &item) +{ + onAdd = [item](LayoutBuilder &builder) { + auto tab = new QWidget; + builder.stack.append(tab); + item.attachTo(tab); + }; + onExit = [tabName](LayoutBuilder &builder) { + QWidget *inner = builder.stack.last().widget; + builder.stack.pop_back(); + auto tabWidget = qobject_cast(builder.stack.last().widget); + QTC_ASSERT(tabWidget, return); + tabWidget->addTab(inner, tabName); + }; +} + +// Special Application + +Application::Application(std::initializer_list items) +{ + subItems = items; + setupWidget(this); + onExit = {}; // Hack: Don't dropp the last slice, we need the resulting widget. +} + +int Application::exec(int &argc, char *argv[]) +{ + QApplication app(argc, argv); + LayoutBuilder builder; + addItemHelper(builder, *this); + if (QWidget *widget = builder.stack.last().widget) + widget->show(); + return app.exec(); } // "Properties" -LayoutItem::Setter title(const QString &title, BoolAspect *checker) +LayoutItem title(const QString &title) { - return [title, checker](QObject *target) { + return [title](QObject *target) { if (auto groupBox = qobject_cast(target)) { groupBox->setTitle(title); groupBox->setObjectName(title); - if (checker) { - groupBox->setCheckable(true); - groupBox->setChecked(checker->value()); - checker->setHandlesGroup(groupBox); - } + } else if (auto widget = qobject_cast(target)) { + widget->setWindowTitle(title); } else { QTC_CHECK(false); } }; } -LayoutItem::Setter onClicked(const std::function &func, QObject *guard) -{ - return [func, guard](QObject *target) { - if (auto button = qobject_cast(target)) { - QObject::connect(button, &QAbstractButton::clicked, guard ? guard : target, func); - } else { - QTC_CHECK(false); - } - }; -} - -LayoutItem::Setter text(const QString &text) +LayoutItem text(const QString &text) { return [text](QObject *target) { if (auto button = qobject_cast(target)) { button->setText(text); + } else if (auto textEdit = qobject_cast(target)) { + textEdit->setText(text); } else { QTC_CHECK(false); } }; } -LayoutItem::Setter tooltip(const QString &toolTip) +LayoutItem tooltip(const QString &toolTip) { return [toolTip](QObject *target) { if (auto widget = qobject_cast(target)) { @@ -574,6 +854,89 @@ LayoutItem::Setter tooltip(const QString &toolTip) }; } +LayoutItem spacing(int spacing) +{ + return [spacing](QObject *target) { + if (auto layout = qobject_cast(target)) { + layout->setSpacing(spacing); + } else { + QTC_CHECK(false); + } + }; +} + +LayoutItem resize(int w, int h) +{ + return [w, h](QObject *target) { + if (auto widget = qobject_cast(target)) { + widget->resize(w, h); + } else { + QTC_CHECK(false); + } + }; +} + +LayoutItem columnStretch(int column, int stretch) +{ + return [column, stretch](QObject *target) { + if (auto grid = qobject_cast(target)) { + grid->setColumnStretch(column, stretch); + } else { + QTC_CHECK(false); + } + }; +} + +// Id based setters + +LayoutItem id(ID &out) +{ + return [&out](QObject *target) { out.ob = target; }; +} + +void setText(ID id, const QString &text) +{ + if (auto textEdit = qobject_cast(id.ob)) + textEdit->setText(text); +} + +// Signals + +LayoutItem onClicked(const std::function &func, QObject *guard) +{ + return [func, guard](QObject *target) { + if (auto button = qobject_cast(target)) { + QObject::connect(button, &QAbstractButton::clicked, guard ? guard : target, func); + } else { + QTC_CHECK(false); + } + }; +} + +LayoutItem onTextChanged(const std::function &func, QObject *guard) +{ + return [func, guard](QObject *target) { + if (auto button = qobject_cast(target)) { + QObject::connect(button, &QSpinBox::textChanged, guard ? guard : target, func); + } else { + QTC_CHECK(false); + } + }; +} + +LayoutItem onValueChanged(const std::function &func, QObject *guard) +{ + return [func, guard](QObject *target) { + if (auto button = qobject_cast(target)) { + QObject::connect(button, &QSpinBox::valueChanged, guard ? guard : target, func); + } else { + QTC_CHECK(false); + } + }; +} + +// Convenience + QWidget *createHr(QWidget *parent) { auto frame = new QFrame(parent); @@ -583,9 +946,55 @@ QWidget *createHr(QWidget *parent) } // Singletons. -Break br; -Stretch st; -Space empty(0); -HorizontalRule hr; -} // Utils::Layouting +LayoutItem::LayoutItem(const LayoutItem &t) +{ + operator=(t); +} + +void createItem(LayoutItem *item, LayoutItem(*t)()) +{ + *item = t(); +} + +void createItem(LayoutItem *item, const std::function &t) +{ + item->setter = t; +} + +void createItem(LayoutItem *item, QWidget *t) +{ + item->onAdd = [t](LayoutBuilder &builder) { doAddWidget(builder, t); }; +} + +void createItem(LayoutItem *item, QLayout *t) +{ + item->onAdd = [t](LayoutBuilder &builder) { doAddLayout(builder, t); }; +} + +void createItem(LayoutItem *item, const QString &t) +{ + item->onAdd = [t](LayoutBuilder &builder) { doAddText(builder, t); }; +} + +void createItem(LayoutItem *item, const Space &t) +{ + item->onAdd = [t](LayoutBuilder &builder) { doAddSpace(builder, t); }; +} + +void createItem(LayoutItem *item, const Stretch &t) +{ + item->onAdd = [t](LayoutBuilder &builder) { doAddStretch(builder, t); }; +} + +void createItem(LayoutItem *item, const Span &t) +{ + item->onAdd = [t](LayoutBuilder &builder) { + addItemHelper(builder, t.item); + builder.stack.last().pendingItems.last().span = t.span; + }; +} + +} // Layouting + +#include "layoutbuilder.moc" diff --git a/src/libs/utils/layoutbuilder.h b/src/libs/utils/layoutbuilder.h index 2f534cb488d..1f774ba1461 100644 --- a/src/libs/utils/layoutbuilder.h +++ b/src/libs/utils/layoutbuilder.h @@ -3,110 +3,140 @@ #pragma once -#include "utils_global.h" - #include #include -#include +#include #include +#if defined(UTILS_LIBRARY) +# define QTCREATOR_UTILS_EXPORT Q_DECL_EXPORT +#elif defined(UTILS_STATIC_LIBRARY) +# define QTCREATOR_UTILS_EXPORT +#else +# define QTCREATOR_UTILS_EXPORT Q_DECL_IMPORT +#endif + QT_BEGIN_NAMESPACE class QLayout; -class QSplitter; -class QTabWidget; +class QObject; class QWidget; +template T qobject_cast(QObject *object); QT_END_NAMESPACE -namespace Utils { -class BaseAspect; -class BoolAspect; -} // Utils - -namespace Utils::Layouting { - -enum AttachType { - WithMargins, - WithoutMargins, - WithFormAlignment, // Handle Grid similar to QFormLayout, i.e. use special alignment for the first column on Mac -}; - -class LayoutBuilder; +namespace Layouting { // LayoutItem +class LayoutBuilder; +class LayoutItem; +using LayoutItems = QList; + class QTCREATOR_UTILS_EXPORT LayoutItem { public: - enum class AlignmentType { - DefaultAlignment, - AlignAsFormLabel, - }; - - enum class SpecialType { - NotSpecial, - Space, - Stretch, - Break, - HorizontalRule, - }; - using Setter = std::function; + LayoutItem(); - LayoutItem(QLayout *layout); - LayoutItem(QWidget *widget); - LayoutItem(BaseAspect *aspect); // Remove - LayoutItem(BaseAspect &aspect); - LayoutItem(const QString &text); - LayoutItem(const LayoutBuilder &builder); - LayoutItem(const Setter &setter) { this->setter = setter; } + ~LayoutItem(); - QLayout *layout = nullptr; - QWidget *widget = nullptr; - BaseAspect *aspect = nullptr; + LayoutItem(const LayoutItem &t); + LayoutItem &operator=(const LayoutItem &t) = default; - QString text; // FIXME: Use specialValue for that - int span = 1; - AlignmentType align = AlignmentType::DefaultAlignment; - Setter setter; - SpecialType specialType = SpecialType::NotSpecial; - QVariant specialValue; + template LayoutItem(const T &t) + { + if constexpr (std::is_base_of_v) + LayoutItem::operator=(t); + else + createItem(this, t); + } + + void attachTo(QWidget *w) const; + QWidget *emerge(); + + void addItem(const LayoutItem &item); + void addItems(const LayoutItems &items); + void addRow(const LayoutItems &items); + + std::function onAdd; + std::function onExit; + std::function setter; + LayoutItems subItems; }; -class QTCREATOR_UTILS_EXPORT Space : public LayoutItem +// Special items + +class QTCREATOR_UTILS_EXPORT Space { public: - explicit Space(int space); + explicit Space(int space) : space(space) {} + const int space; }; -class QTCREATOR_UTILS_EXPORT Span : public LayoutItem +class QTCREATOR_UTILS_EXPORT Stretch { public: - Span(int span, const LayoutItem &item); + explicit Stretch(int stretch = 1) : stretch(stretch) {} + const int stretch; }; -class QTCREATOR_UTILS_EXPORT Stretch : public LayoutItem +class QTCREATOR_UTILS_EXPORT Span { public: - explicit Stretch(int stretch = 1); + Span(int span, const LayoutItem &item) : span(span), item(item) {} + const int span; + LayoutItem item; +}; + +class QTCREATOR_UTILS_EXPORT Column : public LayoutItem +{ +public: + Column(std::initializer_list items); +}; + +class QTCREATOR_UTILS_EXPORT Row : public LayoutItem +{ +public: + Row(std::initializer_list items); +}; + +class QTCREATOR_UTILS_EXPORT Flow : public LayoutItem +{ +public: + Flow(std::initializer_list items); +}; + +class QTCREATOR_UTILS_EXPORT Grid : public LayoutItem +{ +public: + Grid() : Grid({}) {} + Grid(std::initializer_list items); +}; + +class QTCREATOR_UTILS_EXPORT Form : public LayoutItem +{ +public: + Form() : Form({}) {} + Form(std::initializer_list items); +}; + +class QTCREATOR_UTILS_EXPORT Widget : public LayoutItem +{ +public: + Widget(std::initializer_list items); +}; + +class QTCREATOR_UTILS_EXPORT Stack : public LayoutItem +{ +public: + Stack() : Stack({}) {} + Stack(std::initializer_list items); }; class QTCREATOR_UTILS_EXPORT Tab : public LayoutItem { public: - Tab(const QString &tabName, const LayoutBuilder &item); -}; - -class QTCREATOR_UTILS_EXPORT Break : public LayoutItem -{ -public: - Break(); -}; - -class QTCREATOR_UTILS_EXPORT HorizontalRule : public LayoutItem -{ -public: - HorizontalRule(); + Tab(const QString &tabName, const LayoutItem &item); }; class QTCREATOR_UTILS_EXPORT Group : public LayoutItem @@ -115,144 +145,108 @@ public: Group(std::initializer_list items); }; +class QTCREATOR_UTILS_EXPORT TextEdit : public LayoutItem +{ +public: + TextEdit(std::initializer_list items); +}; + class QTCREATOR_UTILS_EXPORT PushButton : public LayoutItem { public: PushButton(std::initializer_list items); }; +class QTCREATOR_UTILS_EXPORT SpinBox : public LayoutItem +{ +public: + SpinBox(std::initializer_list items); +}; + class QTCREATOR_UTILS_EXPORT Splitter : public LayoutItem { public: Splitter(std::initializer_list items); - Splitter(QSplitter *splitter, std::initializer_list items); }; class QTCREATOR_UTILS_EXPORT TabWidget : public LayoutItem { public: - TabWidget(std::initializer_list tabs); - TabWidget(QTabWidget *tabWidget, std::initializer_list tabs); + TabWidget(std::initializer_list items); }; -// Singleton items. +class QTCREATOR_UTILS_EXPORT Application : public LayoutItem +{ +public: + Application(std::initializer_list items); -QTCREATOR_UTILS_EXPORT extern Break br; -QTCREATOR_UTILS_EXPORT extern Stretch st; -QTCREATOR_UTILS_EXPORT extern Space empty; -QTCREATOR_UTILS_EXPORT extern HorizontalRule hr; + int exec(int &argc, char *argv[]); +}; -// "Properties" -QTCREATOR_UTILS_EXPORT LayoutItem::Setter title(const QString &title, - BoolAspect *checker = nullptr); +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const std::function &t); +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, QWidget *t); +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, QLayout *t); +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, LayoutItem(*t)()); +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const QString &t); +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const Span &t); +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const Space &t); +void QTCREATOR_UTILS_EXPORT createItem(LayoutItem *item, const Stretch &t); -QTCREATOR_UTILS_EXPORT LayoutItem::Setter text(const QString &text); -QTCREATOR_UTILS_EXPORT LayoutItem::Setter tooltip(const QString &toolTip); -QTCREATOR_UTILS_EXPORT LayoutItem::Setter onClicked(const std::function &func, - QObject *guard = nullptr); +// "Singletons" + +QTCREATOR_UTILS_EXPORT LayoutItem br(); +QTCREATOR_UTILS_EXPORT LayoutItem st(); +QTCREATOR_UTILS_EXPORT LayoutItem empty(); +QTCREATOR_UTILS_EXPORT LayoutItem hr(); +QTCREATOR_UTILS_EXPORT LayoutItem noMargin(); +QTCREATOR_UTILS_EXPORT LayoutItem normalMargin(); +QTCREATOR_UTILS_EXPORT LayoutItem withFormAlignment(); + +// "Setters" + +QTCREATOR_UTILS_EXPORT LayoutItem title(const QString &title); +QTCREATOR_UTILS_EXPORT LayoutItem text(const QString &text); +QTCREATOR_UTILS_EXPORT LayoutItem tooltip(const QString &toolTip); +QTCREATOR_UTILS_EXPORT LayoutItem resize(int, int); +QTCREATOR_UTILS_EXPORT LayoutItem columnStretch(int column, int stretch); +QTCREATOR_UTILS_EXPORT LayoutItem spacing(int); +QTCREATOR_UTILS_EXPORT LayoutItem windowTitle(const QString &windowTitle); + +// "Getters" + +class ID +{ +public: + QObject *ob = nullptr; +}; + +QTCREATOR_UTILS_EXPORT LayoutItem id(ID &out); + +QTCREATOR_UTILS_EXPORT void setText(ID id, const QString &text); + + +// "Signals" + +QTCREATOR_UTILS_EXPORT LayoutItem onClicked(const std::function &, + QObject *guard = nullptr); +QTCREATOR_UTILS_EXPORT LayoutItem onTextChanged(const std::function &, + QObject *guard = nullptr); +QTCREATOR_UTILS_EXPORT LayoutItem onValueChanged(const std::function &, + QObject *guard = nullptr); + +QTCREATOR_UTILS_EXPORT LayoutItem onTextChanged(ID &id, QVariant(*sig)(QObject *)); // Convenience QTCREATOR_UTILS_EXPORT QWidget *createHr(QWidget *parent = nullptr); - -// LayoutBuilder - -class QTCREATOR_UTILS_EXPORT LayoutBuilder +template +LayoutItem bindTo(T **out) { -public: - enum LayoutType { - HBoxLayout, - VBoxLayout, - FormLayout, - GridLayout, - StackLayout, - }; + return [out](QObject *target) { *out = qobject_cast(target); }; +} - using LayoutItems = QList; - explicit LayoutBuilder(LayoutType layoutType, const LayoutItems &items = {}); - - LayoutBuilder(const LayoutBuilder &) = delete; - LayoutBuilder(LayoutBuilder &&) = default; - LayoutBuilder &operator=(const LayoutBuilder &) = delete; - LayoutBuilder &operator=(LayoutBuilder &&) = default; - - ~LayoutBuilder(); - - LayoutBuilder &setSpacing(int spacing); - - LayoutBuilder &addItem(const LayoutItem &item); - LayoutBuilder &addItems(const LayoutItems &items); - - LayoutBuilder &finishRow(); - LayoutBuilder &addRow(const LayoutItem &item); - LayoutBuilder &addRow(const LayoutItems &items); - - LayoutType layoutType() const { return m_layoutType; } - - void attachTo(QWidget *w, Layouting::AttachType attachType = Layouting::WithMargins) const; - QWidget *emerge(Layouting::AttachType attachType = Layouting::WithMargins); - -protected: - friend class LayoutItem; - - explicit LayoutBuilder(); // Adds to existing layout. - - QLayout *createLayout() const; - void doLayout(QWidget *parent, Layouting::AttachType attachType) const; - - LayoutItems m_items; - LayoutType m_layoutType; - std::optional m_spacing; -}; - -class QTCREATOR_UTILS_EXPORT LayoutExtender : public LayoutBuilder -{ -public: - explicit LayoutExtender(QLayout *layout, Layouting::AttachType attachType); - ~LayoutExtender(); - -private: - QLayout *m_layout = nullptr; - Layouting::AttachType m_attachType = {}; -}; - -class QTCREATOR_UTILS_EXPORT Column : public LayoutBuilder -{ -public: - Column() : LayoutBuilder(VBoxLayout) {} - Column(std::initializer_list items) : LayoutBuilder(VBoxLayout, items) {} -}; - -class QTCREATOR_UTILS_EXPORT Row : public LayoutBuilder -{ -public: - Row() : LayoutBuilder(HBoxLayout) {} - Row(std::initializer_list items) : LayoutBuilder(HBoxLayout, items) {} -}; - -class QTCREATOR_UTILS_EXPORT Grid : public LayoutBuilder -{ -public: - Grid() : LayoutBuilder(GridLayout) {} - Grid(std::initializer_list items) : LayoutBuilder(GridLayout, items) {} -}; - -class QTCREATOR_UTILS_EXPORT Form : public LayoutBuilder -{ -public: - Form() : LayoutBuilder(FormLayout) {} - Form(std::initializer_list items) : LayoutBuilder(FormLayout, items) {} -}; - -class QTCREATOR_UTILS_EXPORT Stack : public LayoutBuilder -{ -public: - Stack() : LayoutBuilder(StackLayout) {} - Stack(std::initializer_list items) : LayoutBuilder(StackLayout, items) {} -}; - -} // Utils::Layouting +} // Layouting diff --git a/src/libs/utils/linecolumn.cpp b/src/libs/utils/linecolumn.cpp deleted file mode 100644 index 50a24ada62c..00000000000 --- a/src/libs/utils/linecolumn.cpp +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "linecolumn.h" - -#include - -namespace Utils { - -/*! - Returns the line and column of a \a fileName and sets the \a postfixPos if - it can find a positional postfix. - - The following patterns are supported: \c {filepath.txt:19}, - \c{filepath.txt:19:12}, \c {filepath.txt+19}, - \c {filepath.txt+19+12}, and \c {filepath.txt(19)}. -*/ - -LineColumn LineColumn::extractFromFileName(QStringView fileName, int &postfixPos) -{ - static const auto regexp = QRegularExpression("[:+](\\d+)?([:+](\\d+)?)?$"); - // (10) MSVC-style - static const auto vsRegexp = QRegularExpression("[(]((\\d+)[)]?)?$"); - const QRegularExpressionMatch match = regexp.match(fileName); - LineColumn lineColumn; - if (match.hasMatch()) { - postfixPos = match.capturedStart(0); - lineColumn.line = 0; // for the case that there's only a : at the end - if (match.lastCapturedIndex() > 0) { - lineColumn.line = match.captured(1).toInt(); - if (match.lastCapturedIndex() > 2) // index 2 includes the + or : for the column number - lineColumn.column = match.captured(3).toInt() - 1; //column is 0 based, despite line being 1 based - } - } else { - const QRegularExpressionMatch vsMatch = vsRegexp.match(fileName); - postfixPos = vsMatch.capturedStart(0); - if (vsMatch.lastCapturedIndex() > 1) // index 1 includes closing ) - lineColumn.line = vsMatch.captured(2).toInt(); - } - return lineColumn; -} - -} // namespace Utils diff --git a/src/libs/utils/linecolumn.h b/src/libs/utils/linecolumn.h deleted file mode 100644 index 78a881d7f46..00000000000 --- a/src/libs/utils/linecolumn.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "utils_global.h" - -#include - -#include - -namespace Utils { - -class QTCREATOR_UTILS_EXPORT LineColumn -{ -public: - constexpr LineColumn() = default; - constexpr LineColumn(int line, int column) : line(line), column(column) {} - - bool isValid() const - { - return line >= 0 && column >= 0; - } - - friend bool operator==(LineColumn first, LineColumn second) - { - return first.isValid() && first.line == second.line && first.column == second.column; - } - - friend bool operator!=(LineColumn first, LineColumn second) - { - return !(first == second); - } - - static LineColumn extractFromFileName(QStringView fileName, int &postfixPos); - -public: - int line = -1; - int column = -1; -}; - -using OptionalLineColumn = std::optional; - -} // namespace Utils - -Q_DECLARE_METATYPE(Utils::LineColumn) diff --git a/src/libs/utils/link.cpp b/src/libs/utils/link.cpp index e4c7032eeb7..1a4b4f9f9af 100644 --- a/src/libs/utils/link.cpp +++ b/src/libs/utils/link.cpp @@ -3,7 +3,7 @@ #include "link.h" -#include "linecolumn.h" +#include "textutils.h" namespace Utils { @@ -24,10 +24,10 @@ Link Link::fromString(const QString &filePathWithNumbers, bool canContainLineNum link.targetFilePath = FilePath::fromUserInput(filePathWithNumbers); } else { int postfixPos = -1; - const LineColumn lineColumn = LineColumn::extractFromFileName(filePathWithNumbers, postfixPos); + const Text::Position pos = Text::Position::fromFileName(filePathWithNumbers, postfixPos); link.targetFilePath = FilePath::fromUserInput(filePathWithNumbers.left(postfixPos)); - link.targetLine = lineColumn.line; - link.targetColumn = lineColumn.column; + link.targetLine = pos.line; + link.targetColumn = pos.column; } return link; } diff --git a/src/libs/utils/link.h b/src/libs/utils/link.h index 6f01b484348..00194654c97 100644 --- a/src/libs/utils/link.h +++ b/src/libs/utils/link.h @@ -17,7 +17,8 @@ namespace Utils { class QTCREATOR_UTILS_EXPORT Link { public: - Link(const FilePath &filePath = FilePath(), int line = 0, int column = 0) + Link() = default; + Link(const FilePath &filePath, int line = 0, int column = 0) : targetFilePath(filePath) , targetLine(line) , targetColumn(column) @@ -26,7 +27,11 @@ public: static Link fromString(const QString &filePathWithNumbers, bool canContainLineNumber = false); bool hasValidTarget() const - { return !targetFilePath.isEmpty(); } + { + if (!targetFilePath.isEmpty()) + return true; + return !targetFilePath.scheme().isEmpty() || !targetFilePath.host().isEmpty(); + } bool hasValidLinkText() const { return linkTextStart != linkTextEnd; } @@ -48,8 +53,8 @@ public: int linkTextEnd = -1; FilePath targetFilePath; - int targetLine; - int targetColumn; + int targetLine = 0; + int targetColumn = 0; }; using LinkHandler = std::function; @@ -58,3 +63,12 @@ using Links = QList; } // namespace Utils Q_DECLARE_METATYPE(Utils::Link) + +namespace std { + +template<> struct hash +{ + size_t operator()(const Utils::Link &fn) const { return qHash(fn); } +}; + +} // std diff --git a/src/libs/utils/macroexpander.cpp b/src/libs/utils/macroexpander.cpp index 4e24247833c..4dc18b23f6b 100644 --- a/src/libs/utils/macroexpander.cpp +++ b/src/libs/utils/macroexpander.cpp @@ -104,6 +104,7 @@ using namespace Internal; /*! \class Utils::MacroExpander + \inmodule QtCreator \brief The MacroExpander class manages \QC wide variables, that a user can enter into many string settings. The variables are replaced by an actual value when the string is used, similar to how environment variables are expanded by a shell. @@ -140,7 +141,7 @@ using namespace Internal; MacroExpander::registerVariable( "MyVariable", Tr::tr("The current value of whatever I want.")); - []() -> QString { + [] { QString value; // do whatever is necessary to retrieve the value [...] @@ -196,7 +197,7 @@ using namespace Internal; you would use the one provided by the variable manager). Mostly the same as MacroExpander::expandedString(), but also has a variant that does the replacement inline instead of returning a new string. - \li Using Utils::QtcProcess::expandMacros(). This expands the string while conforming to the + \li Using Utils::CommandLine::expandMacros(). This expands the string while conforming to the quoting rules of the platform it is run on. Use this function with the variable manager's macro expander if your string will be passed as a command line parameter string to an external command. @@ -246,7 +247,6 @@ QString MacroExpander::value(const QByteArray &variable, bool *found) const * See the MacroExpander overview documentation for other ways to expand variables. * * \sa MacroExpander - * \sa macroExpander() */ QString MacroExpander::expand(const QString &stringWithVariables) const { @@ -274,7 +274,7 @@ QString MacroExpander::expand(const QString &stringWithVariables) const FilePath MacroExpander::expand(const FilePath &fileNameWithVariables) const { // We want single variables to expand to fully qualified strings. - return FilePath::fromString(expand(fileNameWithVariables.toString())); + return FilePath::fromUserInput(expand(fileNameWithVariables.toString())); } QByteArray MacroExpander::expand(const QByteArray &stringWithVariables) const @@ -322,10 +322,11 @@ static QByteArray fullPrefix(const QByteArray &prefix) * Makes the given string-valued \a prefix known to the variable manager, * together with a localized \a description. * - * The \a value PrefixFunction will be called and gets the full variable name - * with the prefix stripped as input. + * The \a value \c PrefixFunction will be called and gets the full variable name + * with the prefix stripped as input. It is displayed to users if \a visible is + * \c true. * - * \sa registerVariables(), registerIntVariable(), registerFileVariables() + * \sa registerVariable(), registerIntVariable(), registerFileVariables() */ void MacroExpander::registerPrefix(const QByteArray &prefix, const QString &description, const MacroExpander::PrefixFunction &value, bool visible) @@ -340,6 +341,9 @@ void MacroExpander::registerPrefix(const QByteArray &prefix, const QString &desc * Makes the given string-valued \a variable known to the variable manager, * together with a localized \a description. * + * The \a value \c StringFunction is called to retrieve the current value of the + * variable. It is displayed to users if \a visibleInChooser is \c true. + * * \sa registerFileVariables(), registerIntVariable(), registerPrefix() */ void MacroExpander::registerVariable(const QByteArray &variable, @@ -354,6 +358,9 @@ void MacroExpander::registerVariable(const QByteArray &variable, * Makes the given integral-valued \a variable known to the variable manager, * together with a localized \a description. * + * The \a value \c IntFunction is called to retrieve the current value of the + * variable. + * * \sa registerVariable(), registerFileVariables(), registerPrefix() */ void MacroExpander::registerIntVariable(const QByteArray &variable, @@ -372,46 +379,54 @@ void MacroExpander::registerIntVariable(const QByteArray &variable, * variables such as \c{CurrentDocument:FilePath} with description * "Current Document: Full path including file name." * + * Takes a function that returns a FilePath as a \a base. + * + * The variable is displayed to users if \a visibleInChooser is \c true. + * * \sa registerVariable(), registerIntVariable(), registerPrefix() */ void MacroExpander::registerFileVariables(const QByteArray &prefix, const QString &heading, const FileFunction &base, bool visibleInChooser) { - registerVariable(prefix + kFilePathPostfix, - Tr::tr("%1: Full path including file name.").arg(heading), - [base]() -> QString { QString tmp = base().toString(); return tmp.isEmpty() ? QString() : QFileInfo(tmp).filePath(); }, - visibleInChooser); + registerVariable( + prefix + kFilePathPostfix, + Tr::tr("%1: Full path including file name.").arg(heading), + [base] { return base().path(); }, + visibleInChooser); - registerVariable(prefix + kPathPostfix, - Tr::tr("%1: Full path excluding file name.").arg(heading), - [base]() -> QString { QString tmp = base().toString(); return tmp.isEmpty() ? QString() : QFileInfo(tmp).path(); }, - visibleInChooser); + registerVariable( + prefix + kPathPostfix, + Tr::tr("%1: Full path excluding file name.").arg(heading), + [base] { return base().parentDir().path(); }, + visibleInChooser); - registerVariable(prefix + kNativeFilePathPostfix, - Tr::tr("%1: Full path including file name, with native path separator (backslash on Windows).").arg(heading), - [base]() -> QString { - QString tmp = base().toString(); - return tmp.isEmpty() ? QString() : QDir::toNativeSeparators(QFileInfo(tmp).filePath()); - }, - visibleInChooser); + registerVariable( + prefix + kNativeFilePathPostfix, + Tr::tr( + "%1: Full path including file name, with native path separator (backslash on Windows).") + .arg(heading), + [base] { return base().nativePath(); }, + visibleInChooser); - registerVariable(prefix + kNativePathPostfix, - Tr::tr("%1: Full path excluding file name, with native path separator (backslash on Windows).").arg(heading), - [base]() -> QString { - QString tmp = base().toString(); - return tmp.isEmpty() ? QString() : QDir::toNativeSeparators(QFileInfo(tmp).path()); - }, - visibleInChooser); + registerVariable( + prefix + kNativePathPostfix, + Tr::tr( + "%1: Full path excluding file name, with native path separator (backslash on Windows).") + .arg(heading), + [base] { return base().parentDir().nativePath(); }, + visibleInChooser); - registerVariable(prefix + kFileNamePostfix, - Tr::tr("%1: File name without path.").arg(heading), - [base]() -> QString { QString tmp = base().toString(); return tmp.isEmpty() ? QString() : FilePath::fromString(tmp).fileName(); }, - visibleInChooser); + registerVariable( + prefix + kFileNamePostfix, + Tr::tr("%1: File name without path.").arg(heading), + [base] { return base().fileName(); }, + visibleInChooser); - registerVariable(prefix + kFileBaseNamePostfix, - Tr::tr("%1: File base name without path and suffix.").arg(heading), - [base]() -> QString { QString tmp = base().toString(); return tmp.isEmpty() ? QString() : QFileInfo(tmp).baseName(); }, - visibleInChooser); + registerVariable( + prefix + kFileBaseNamePostfix, + Tr::tr("%1: File base name without path and suffix.").arg(heading), + [base] { return base().baseName(); }, + visibleInChooser); } void MacroExpander::registerExtraResolver(const MacroExpander::ResolverFunction &value) diff --git a/src/libs/utils/mathutils.cpp b/src/libs/utils/mathutils.cpp index 77d9d5e5b27..a25f4515b0b 100644 --- a/src/libs/utils/mathutils.cpp +++ b/src/libs/utils/mathutils.cpp @@ -5,12 +5,22 @@ #include +/*! + \namespace Utils::MathUtils + \inmodule QtCreator + + \brief Contains functions for interpolation. +*/ + namespace Utils::MathUtils { /*! Linear interpolation: - For x = x1 it returns y1. - For x = x2 it returns y2. + + \list + \li For \a x = \a x1 it returns \a y1. + \li For \a x = \a x2 it returns \a y2. + \endlist */ int interpolateLinear(int x, int x1, int x2, int y1, int y2) { @@ -29,9 +39,13 @@ int interpolateLinear(int x, int x1, int x2, int y1, int y2) /*! Tangential interpolation: - For x = 0 it returns y1. - For x = xHalfLife it returns 50 % of the distance between y1 and y2. - For x = infinity it returns y2. + + \list + \li For \a x = 0 it returns \a y1. + \li For \a x = \a xHalfLife it returns 50 % of the distance between + \a y1 and \a y2. + \li For \a x = infinity it returns \a y2. + \endlist */ int interpolateTangential(int x, int xHalfLife, int y1, int y2) { @@ -46,9 +60,13 @@ int interpolateTangential(int x, int xHalfLife, int y1, int y2) /*! Exponential interpolation: - For x = 0 it returns y1. - For x = xHalfLife it returns 50 % of the distance between y1 and y2. - For x = infinity it returns y2. + + \list + \li For \a x = 0 it returns \a y1. + \li For \a x = \a xHalfLife it returns 50 % of the distance between + \a y1 and \a y2. + \li For \a x = infinity it returns \a y2. + \endlist */ int interpolateExponential(int x, int xHalfLife, int y1, int y2) { diff --git a/src/libs/utils/multitextcursor.cpp b/src/libs/utils/multitextcursor.cpp index bb2e38f5eb8..0c2e5b07925 100644 --- a/src/libs/utils/multitextcursor.cpp +++ b/src/libs/utils/multitextcursor.cpp @@ -16,187 +16,42 @@ namespace Utils { MultiTextCursor::MultiTextCursor() {} MultiTextCursor::MultiTextCursor(const QList &cursors) - : m_cursors(cursors) { - mergeCursors(); + setCursors(cursors); } -void MultiTextCursor::addCursor(const QTextCursor &cursor) +void MultiTextCursor::fillMapWithList() { - QTC_ASSERT(!cursor.isNull(), return); - m_cursors.append(cursor); - mergeCursors(); + m_cursorMap.clear(); + for (auto it = m_cursorList.begin(); it != m_cursorList.end(); ++it) + m_cursorMap[it->selectionStart()] = it; } -void MultiTextCursor::addCursors(const QList &cursors) +MultiTextCursor& MultiTextCursor::operator=(const MultiTextCursor &multiCursor) { - m_cursors.append(cursors); - mergeCursors(); + m_cursorList = multiCursor.m_cursorList; + fillMapWithList(); + return *this; } -void MultiTextCursor::setCursors(const QList &cursors) +MultiTextCursor::MultiTextCursor(const MultiTextCursor &multiCursor) { - m_cursors = cursors; - mergeCursors(); + *this = multiCursor; } -const QList MultiTextCursor::cursors() const +MultiTextCursor& MultiTextCursor::operator=(const MultiTextCursor &&multiCursor) { - return m_cursors; + m_cursorList = std::move(multiCursor.m_cursorList); + fillMapWithList(); + return *this; } -void MultiTextCursor::replaceMainCursor(const QTextCursor &cursor) +MultiTextCursor::MultiTextCursor(const MultiTextCursor &&multiCursor) { - QTC_ASSERT(!cursor.isNull(), return); - takeMainCursor(); - addCursor(cursor); + *this = std::move(multiCursor); } -QTextCursor MultiTextCursor::mainCursor() const -{ - if (m_cursors.isEmpty()) - return {}; - return m_cursors.last(); -} - -QTextCursor MultiTextCursor::takeMainCursor() -{ - if (m_cursors.isEmpty()) - return {}; - return m_cursors.takeLast(); -} - -void MultiTextCursor::beginEditBlock() -{ - QTC_ASSERT(!m_cursors.empty(), return); - m_cursors.last().beginEditBlock(); -} - -void MultiTextCursor::endEditBlock() -{ - QTC_ASSERT(!m_cursors.empty(), return); - m_cursors.last().endEditBlock(); -} - -bool MultiTextCursor::isNull() const -{ - return m_cursors.isEmpty(); -} - -bool MultiTextCursor::hasMultipleCursors() const -{ - return m_cursors.size() > 1; -} - -int MultiTextCursor::cursorCount() const -{ - return static_cast(m_cursors.size()); -} - -void MultiTextCursor::movePosition(QTextCursor::MoveOperation operation, - QTextCursor::MoveMode mode, - int n) -{ - for (QTextCursor &cursor : m_cursors) - cursor.movePosition(operation, mode, n); - mergeCursors(); -} - -bool MultiTextCursor::hasSelection() const -{ - return Utils::anyOf(m_cursors, &QTextCursor::hasSelection); -} - -QString MultiTextCursor::selectedText() const -{ - QString text; - const QList cursors = Utils::sorted(m_cursors); - for (const QTextCursor &cursor : cursors) { - const QString &cursorText = cursor.selectedText(); - if (cursorText.isEmpty()) - continue; - if (!text.isEmpty()) { - if (text.endsWith(QChar::ParagraphSeparator)) - text.chop(1); - text.append('\n'); - } - text.append(cursorText); - } - return text; -} - -void MultiTextCursor::removeSelectedText() -{ - beginEditBlock(); - for (QTextCursor &c : m_cursors) - c.removeSelectedText(); - endEditBlock(); - mergeCursors(); -} - -static void insertAndSelect(QTextCursor &cursor, const QString &text, bool selectNewText) -{ - if (selectNewText) { - const int anchor = cursor.position(); - cursor.insertText(text); - const int pos = cursor.position(); - cursor.setPosition(anchor); - cursor.setPosition(pos, QTextCursor::KeepAnchor); - } else { - cursor.insertText(text); - } -} - -void MultiTextCursor::insertText(const QString &text, bool selectNewText) -{ - if (m_cursors.isEmpty()) - return; - m_cursors.last().beginEditBlock(); - if (hasMultipleCursors()) { - QStringList lines = text.split('\n'); - if (!lines.isEmpty() && lines.last().isEmpty()) - lines.pop_back(); - int index = 0; - if (lines.count() == m_cursors.count()) { - QList cursors = Utils::sorted(m_cursors); - for (QTextCursor &cursor : cursors) - insertAndSelect(cursor, lines.at(index++), selectNewText); - m_cursors.last().endEditBlock(); - return; - } - } - for (QTextCursor &cursor : m_cursors) - insertAndSelect(cursor, text, selectNewText); - m_cursors.last().endEditBlock(); -} - -bool equalCursors(const QTextCursor &lhs, const QTextCursor &rhs) -{ - return lhs == rhs && lhs.anchor() == rhs.anchor(); -} - -bool MultiTextCursor::operator==(const MultiTextCursor &other) const -{ - if (m_cursors.size() != other.m_cursors.size()) - return false; - if (m_cursors.isEmpty()) - return true; - QList thisCursors = m_cursors; - QList otherCursors = other.m_cursors; - if (!equalCursors(thisCursors.takeLast(), otherCursors.takeLast())) - return false; - for (const QTextCursor &oc : otherCursors) { - auto compare = [oc](const QTextCursor &c) { return equalCursors(oc, c); }; - if (!Utils::contains(thisCursors, compare)) - return false; - } - return true; -} - -bool MultiTextCursor::operator!=(const MultiTextCursor &other) const -{ - return !operator==(other); -} +MultiTextCursor::~MultiTextCursor() = default; static bool cursorsOverlap(const QTextCursor &c1, const QTextCursor &c2) { @@ -243,25 +98,221 @@ static void mergeCursors(QTextCursor &c1, const QTextCursor &c2) } } -void MultiTextCursor::mergeCursors() +void MultiTextCursor::addCursor(const QTextCursor &cursor) { - std::list cursors(m_cursors.begin(), m_cursors.end()); - cursors = Utils::filtered(cursors, [](const QTextCursor &c){ - return !c.isNull(); - }); - for (auto it = cursors.begin(); it != cursors.end(); ++it) { - QTextCursor &c1 = *it; - for (auto other = std::next(it); other != cursors.end();) { - const QTextCursor &c2 = *other; - if (cursorsOverlap(c1, c2)) { - Utils::mergeCursors(c1, c2); - other = cursors.erase(other); - continue; + QTC_ASSERT(!cursor.isNull(), return); + + QTextCursor c1 = cursor; + const int pos = c1.selectionStart(); + + auto found = m_cursorMap.lower_bound(pos); + if (found != m_cursorMap.begin()) + --found; + + for (; !m_cursorMap.empty() && found != m_cursorMap.end() + && found->second->selectionStart() <= cursor.selectionEnd();) { + const QTextCursor &c2 = *found->second; + if (cursorsOverlap(c1, c2)) { + Utils::mergeCursors(c1, c2); + m_cursorList.erase(found->second); + found = m_cursorMap.erase(found); + continue; + } + ++found; + } + + m_cursorMap[pos] = m_cursorList.insert(m_cursorList.end(), c1); +} + +void MultiTextCursor::addCursors(const QList &cursors) +{ + for (const QTextCursor &c : cursors) + addCursor(c); +} + +void MultiTextCursor::setCursors(const QList &cursors) +{ + m_cursorList.clear(); + m_cursorMap.clear(); + addCursors(cursors); +} + +const QList MultiTextCursor::cursors() const +{ + return QList(m_cursorList.begin(), m_cursorList.end()); +} + +void MultiTextCursor::replaceMainCursor(const QTextCursor &cursor) +{ + QTC_ASSERT(!cursor.isNull(), return); + takeMainCursor(); + addCursor(cursor); +} + +QTextCursor MultiTextCursor::mainCursor() const +{ + if (m_cursorList.empty()) + return {}; + return m_cursorList.back(); +} + +QTextCursor MultiTextCursor::takeMainCursor() +{ + if (m_cursorList.empty()) + return {}; + + QTextCursor cursor = m_cursorList.back(); + auto it = m_cursorList.end(); + --it; + m_cursorMap.erase(it->selectionStart()); + m_cursorList.erase(it); + + return cursor; +} + +void MultiTextCursor::beginEditBlock() +{ + QTC_ASSERT(!m_cursorList.empty(), return); + m_cursorList.back().beginEditBlock(); +} + +void MultiTextCursor::endEditBlock() +{ + QTC_ASSERT(!m_cursorList.empty(), return); + m_cursorList.back().endEditBlock(); +} + +bool MultiTextCursor::isNull() const +{ + return m_cursorList.empty(); +} + +bool MultiTextCursor::hasMultipleCursors() const +{ + return m_cursorList.size() > 1; +} + +int MultiTextCursor::cursorCount() const +{ + return static_cast(m_cursorList.size()); +} + +void MultiTextCursor::movePosition(QTextCursor::MoveOperation operation, + QTextCursor::MoveMode mode, + int n) +{ + for (auto &cursor : m_cursorList) + cursor.movePosition(operation, mode, n); + + mergeCursors(); +} + +bool MultiTextCursor::hasSelection() const +{ + return Utils::anyOf(m_cursorList, &QTextCursor::hasSelection); +} + +QString MultiTextCursor::selectedText() const +{ + QString text; + for (const auto &element : std::as_const(m_cursorMap)) { + const QTextCursor &cursor = *element.second; + const QString &cursorText = cursor.selectedText(); + if (cursorText.isEmpty()) + continue; + if (!text.isEmpty()) { + if (text.endsWith(QChar::ParagraphSeparator)) + text.chop(1); + text.append('\n'); + } + text.append(cursorText); + } + return text; +} + +void MultiTextCursor::removeSelectedText() +{ + beginEditBlock(); + for (auto cursor = m_cursorList.begin(); cursor != m_cursorList.end(); ++cursor) + cursor->removeSelectedText(); + endEditBlock(); + mergeCursors(); +} + +static void insertAndSelect(QTextCursor &cursor, const QString &text, bool selectNewText) +{ + if (selectNewText) { + const int anchor = cursor.position(); + cursor.insertText(text); + const int pos = cursor.position(); + cursor.setPosition(anchor); + cursor.setPosition(pos, QTextCursor::KeepAnchor); + } else { + cursor.insertText(text); + } +} + +void MultiTextCursor::insertText(const QString &text, bool selectNewText) +{ + if (m_cursorList.empty()) + return; + + m_cursorList.back().beginEditBlock(); + if (hasMultipleCursors()) { + QStringList lines = text.split('\n'); + if (!lines.isEmpty() && lines.last().isEmpty()) + lines.pop_back(); + int index = 0; + if (static_cast(lines.count()) == m_cursorList.size()) { + for (const auto &element : std::as_const(m_cursorMap)) { + QTextCursor &cursor = *element.second; + insertAndSelect(cursor, lines.at(index++), selectNewText); } - ++other; + m_cursorList.back().endEditBlock(); + return; } } - m_cursors = QList(cursors.begin(), cursors.end()); + for (auto cursor = m_cursorList.begin(); cursor != m_cursorList.end(); ++cursor) + insertAndSelect(*cursor, text, selectNewText); + m_cursorList.back().endEditBlock(); +} + +bool equalCursors(const QTextCursor &lhs, const QTextCursor &rhs) +{ + return lhs == rhs && lhs.anchor() == rhs.anchor(); +} + +bool MultiTextCursor::operator==(const MultiTextCursor &other) const +{ + if (m_cursorList.size() != other.m_cursorList.size()) + return false; + if (m_cursorList.empty()) + return true; + + if (!equalCursors(m_cursorList.back(), other.m_cursorList.back())) + return false; + + auto it = m_cursorMap.begin(); + auto otherIt = other.m_cursorMap.begin(); + for (;it != m_cursorMap.end() && otherIt != other.m_cursorMap.end(); ++it, ++otherIt) { + const QTextCursor &cursor = *it->second; + const QTextCursor &otherCursor = *otherIt->second; + if (it->first != otherIt->first || cursor != otherCursor + || cursor.anchor() != otherCursor.anchor()) + return false; + } + return true; +} + +bool MultiTextCursor::operator!=(const MultiTextCursor &other) const +{ + return !operator==(other); +} + +void MultiTextCursor::mergeCursors() +{ + QList cursors(m_cursorList.begin(), m_cursorList.end()); + setCursors(cursors); } // could go into QTextCursor... @@ -321,7 +372,7 @@ bool MultiTextCursor::handleMoveKeyEvent(QKeyEvent *e, return false; } - const QList cursors = m_cursors; + const std::list cursors = m_cursorList; for (QTextCursor cursor : cursors) { if (camelCaseNavigationEnabled && op == QTextCursor::WordRight) CamelCaseCursor::right(&cursor, edit, QTextCursor::MoveAnchor); @@ -329,14 +380,14 @@ bool MultiTextCursor::handleMoveKeyEvent(QKeyEvent *e, CamelCaseCursor::left(&cursor, edit, QTextCursor::MoveAnchor); else cursor.movePosition(op, QTextCursor::MoveAnchor); - m_cursors << cursor; - } - mergeCursors(); + addCursor(cursor); + } return true; } - for (QTextCursor &cursor : m_cursors) { + for (auto it = m_cursorList.begin(); it != m_cursorList.end(); ++it) { + QTextCursor &cursor = *it; QTextCursor::MoveMode mode = QTextCursor::MoveAnchor; QTextCursor::MoveOperation op = QTextCursor::NoMove; diff --git a/src/libs/utils/multitextcursor.h b/src/libs/utils/multitextcursor.h index 390082e63ad..dc554dd2276 100644 --- a/src/libs/utils/multitextcursor.h +++ b/src/libs/utils/multitextcursor.h @@ -21,15 +21,22 @@ public: MultiTextCursor(); explicit MultiTextCursor(const QList &cursors); - /// replace all cursors with \param cursors and the last one will be the new main cursors + MultiTextCursor(const MultiTextCursor &multiCursor); + MultiTextCursor &operator=(const MultiTextCursor &multiCursor); + MultiTextCursor(const MultiTextCursor &&multiCursor); + MultiTextCursor &operator=(const MultiTextCursor &&multiCursor); + + ~MultiTextCursor(); + + /// Replaces all cursors with \param cursors and the last one will be the new main cursors. void setCursors(const QList &cursors); const QList cursors() const; - /// \returns whether this multi cursor contains any cursor + /// Returns whether this multi cursor contains any cursor. bool isNull() const; - /// \returns whether this multi cursor contains more than one cursor + /// Returns whether this multi cursor contains more than one cursor. bool hasMultipleCursors() const; - /// \returns the number of cursors handled by this cursor + /// Returns the number of cursors handled by this cursor. int cursorCount() const; /// the \param cursor that is appended by added by \brief addCursor @@ -39,9 +46,9 @@ public: /// convenience function that removes the old main cursor and appends /// \param cursor as the new main cursor void replaceMainCursor(const QTextCursor &cursor); - /// \returns the main cursor + /// Returns the main cursor. QTextCursor mainCursor() const; - /// \returns the main cursor and removes it from this multi cursor + /// Returns the main cursor and removes it from this multi cursor. QTextCursor takeMainCursor(); void beginEditBlock(); @@ -55,10 +62,10 @@ public: /// with the move \param mode void movePosition(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode, int n = 1); - /// \returns whether any cursor has a selection + /// Returns whether any cursor has a selection. bool hasSelection() const; - /// \returns the selected text of all cursors that have a selection separated by - /// a newline character + /// Returns the selected text of all cursors that have a selection separated by + /// a newline character. QString selectedText() const; /// removes the selected text of all cursors that have a selection from the document void removeSelectedText(); @@ -69,20 +76,45 @@ public: bool operator==(const MultiTextCursor &other) const; bool operator!=(const MultiTextCursor &other) const; - using iterator = QList::iterator; - using const_iterator = QList::const_iterator; + template + class BaseIterator { + public: + using iterator_category = std::input_iterator_tag; + using difference_type = int; + using value_type = T; + using pointer = T *; + using reference = T &; + BaseIterator(const mapit &it) : internalit(it) {} + BaseIterator &operator++() { ++internalit; return *this; } + BaseIterator operator++(int) { auto result = *this; ++(*this); return result; } + bool operator==(BaseIterator other) const { return internalit == other.internalit; } + bool operator!=(BaseIterator other) const { return !(*this == other); } + reference operator*() const { return *(internalit->second); } - iterator begin() { return m_cursors.begin(); } - iterator end() { return m_cursors.end(); } - const_iterator begin() const { return m_cursors.begin(); } - const_iterator end() const { return m_cursors.end(); } - const_iterator constBegin() const { return m_cursors.constBegin(); } - const_iterator constEnd() const { return m_cursors.constEnd(); } + private: + mapit internalit; + }; + + using iterator + = BaseIterator::iterator>::iterator>; + using const_iterator + = BaseIterator::iterator>::const_iterator>; + + iterator begin() { return m_cursorMap.begin(); } + iterator end() { return m_cursorMap.end(); } + const_iterator begin() const { return m_cursorMap.begin(); } + const_iterator end() const { return m_cursorMap.end(); } + const_iterator constBegin() const { return m_cursorMap.cbegin(); } + const_iterator constEnd() const { return m_cursorMap.cend(); } static bool multiCursorAddEvent(QKeyEvent *e, QKeySequence::StandardKey matchKey); private: - QList m_cursors; + std::list m_cursorList; + std::map::iterator> m_cursorMap; + + void fillMapWithList(); }; } // namespace Utils diff --git a/src/libs/utils/namevalueitem.cpp b/src/libs/utils/namevalueitem.cpp index 5fd3bc39eb9..adc7ff5afc9 100644 --- a/src/libs/utils/namevalueitem.cpp +++ b/src/libs/utils/namevalueitem.cpp @@ -118,14 +118,6 @@ static QString expand(const NameValueDictionary *dictionary, QString value) return value; } -enum : char { -#ifdef Q_OS_WIN - pathSepC = ';' -#else - pathSepC = ':' -#endif -}; - void NameValueItem::apply(NameValueDictionary *dictionary, Operation op) const { switch (op) { @@ -142,7 +134,7 @@ void NameValueItem::apply(NameValueDictionary *dictionary, Operation op) const const NameValueDictionary::const_iterator it = dictionary->constFind(name); if (it != dictionary->constEnd()) { QString v = dictionary->value(it); - const QChar pathSep{QLatin1Char(pathSepC)}; + const QChar pathSep = HostOsInfo::pathListSeparator(); int sepCount = 0; if (v.startsWith(pathSep)) ++sepCount; @@ -162,7 +154,7 @@ void NameValueItem::apply(NameValueDictionary *dictionary, Operation op) const const NameValueDictionary::const_iterator it = dictionary->constFind(name); if (it != dictionary->constEnd()) { QString v = dictionary->value(it); - const QChar pathSep{QLatin1Char(pathSepC)}; + const QChar pathSep = HostOsInfo::pathListSeparator(); int sepCount = 0; if (v.endsWith(pathSep)) ++sepCount; diff --git a/src/libs/utils/navigationtreeview.cpp b/src/libs/utils/navigationtreeview.cpp index 8394388f17d..7a6bd4be234 100644 --- a/src/libs/utils/navigationtreeview.cpp +++ b/src/libs/utils/navigationtreeview.cpp @@ -9,6 +9,7 @@ /*! \class Utils::NavigationTreeView + \inmodule QtCreator \brief The NavigationTreeView class implements a general TreeView for any sidebar widget. diff --git a/src/libs/utils/optionpushbutton.cpp b/src/libs/utils/optionpushbutton.cpp index f98f9ee5a33..c3993fef4b0 100644 --- a/src/libs/utils/optionpushbutton.cpp +++ b/src/libs/utils/optionpushbutton.cpp @@ -11,6 +11,7 @@ namespace Utils { /*! \class Utils::OptionPushButton + \inmodule QtCreator \brief The OptionPushButton class implements a QPushButton for which the menu is only opened if the user presses the menu indicator. diff --git a/src/libs/utils/osspecificaspects.h b/src/libs/utils/osspecificaspects.h index 0cc22efe5fe..c735f313abc 100644 --- a/src/libs/utils/osspecificaspects.h +++ b/src/libs/utils/osspecificaspects.h @@ -14,6 +14,36 @@ namespace Utils { // Add more as needed. enum OsType { OsTypeWindows, OsTypeLinux, OsTypeMac, OsTypeOtherUnix, OsTypeOther }; +inline QString osTypeToString(OsType osType) +{ + switch (osType) { + case OsTypeWindows: + return "Windows"; + case OsTypeLinux: + return "Linux"; + case OsTypeMac: + return "Mac"; + case OsTypeOtherUnix: + return "Other Unix"; + case OsTypeOther: + default: + return "Other"; + } +} + +inline OsType osTypeFromString(const QString &string) +{ + if (string == "Windows") + return OsTypeWindows; + if (string == "Linux") + return OsTypeLinux; + if (string == "Mac") + return OsTypeMac; + if (string == "Other Unix") + return OsTypeOtherUnix; + return OsTypeOther; +} + namespace OsSpecificAspects { inline QString withExecutableSuffix(OsType osType, const QString &executable) diff --git a/src/libs/utils/parameteraction.cpp b/src/libs/utils/parameteraction.cpp index 13efd9fa013..a77154b41f2 100644 --- a/src/libs/utils/parameteraction.cpp +++ b/src/libs/utils/parameteraction.cpp @@ -5,6 +5,7 @@ /*! \class Utils::ParameterAction + \inmodule QtCreator \brief The ParameterAction class is intended for actions that act on a 'current', string-type parameter (typically a file name), for example 'Save file %1'. diff --git a/src/libs/utils/pathchooser.cpp b/src/libs/utils/pathchooser.cpp index 53825931b97..1fe1e962560 100644 --- a/src/libs/utils/pathchooser.cpp +++ b/src/libs/utils/pathchooser.cpp @@ -10,8 +10,8 @@ #include "hostosinfo.h" #include "macroexpander.h" #include "optionpushbutton.h" +#include "process.h" #include "qtcassert.h" -#include "qtcprocess.h" #include "utilstr.h" #include @@ -130,7 +130,7 @@ QString BinaryVersionToolTipEventFilter::toolVersion(const CommandLine &cmd) { if (cmd.executable().isEmpty()) return QString(); - QtcProcess proc; + Process proc; proc.setTimeoutS(1); proc.setCommand(cmd); proc.runBlocking(); @@ -171,7 +171,7 @@ public: FilePath m_initialBrowsePathOverride; QString m_defaultValue; FilePath m_baseDirectory; - EnvironmentChange m_environmentChange; + Environment m_environment; BinaryVersionToolTipEventFilter *m_binaryVersionToolTipEventFilter = nullptr; QList m_buttons; const MacroExpander *m_macroExpander = globalMacroExpander(); @@ -196,8 +196,7 @@ FilePath PathChooserPrivate::expandedPath(const FilePath &input) const FilePath path = input; - Environment env = path.deviceEnvironment(); - m_environmentChange.applyToEnvironment(env); + Environment env = m_environment.appliedToEnvironment(path.deviceEnvironment()); path = env.expandVariables(path); if (m_macroExpander) @@ -324,20 +323,15 @@ void PathChooser::setBaseDirectory(const FilePath &base) triggerChanged(); } -void PathChooser::setEnvironment(const Environment &env) -{ - setEnvironmentChange(EnvironmentChange::fromDictionary(env.toDictionary())); -} - FilePath PathChooser::baseDirectory() const { return d->m_baseDirectory; } -void PathChooser::setEnvironmentChange(const EnvironmentChange &env) +void PathChooser::setEnvironment(const Environment &env) { QString oldExpand = filePath().toString(); - d->m_environmentChange = env; + d->m_environment = env; if (filePath().toString() != oldExpand) { triggerChanged(); emit rawPathChanged(); @@ -619,8 +613,8 @@ bool PathChooser::validatePath(FancyLineEdit *edit, QString *errorMessage) const *errorMessage = Tr::tr("The path \"%1\" is not a directory.").arg(filePath.toUserOutput()); return false; } - if (HostOsInfo::isWindowsHost() && !filePath.startsWithDriveLetter() - && !filePath.startsWith("\\\\") && !filePath.startsWith("//")) { + if (filePath.osType() == OsTypeWindows && !filePath.startsWithDriveLetter() + && !filePath.startsWith("\\\\") && !filePath.startsWith("//")) { if (errorMessage) *errorMessage = Tr::tr("Invalid path \"%1\".").arg(filePath.toUserOutput()); return false; diff --git a/src/libs/utils/pathchooser.h b/src/libs/utils/pathchooser.h index 92b5973b2de..62a370185da 100644 --- a/src/libs/utils/pathchooser.h +++ b/src/libs/utils/pathchooser.h @@ -20,7 +20,6 @@ namespace Utils { class CommandLine; class MacroExpander; class Environment; -class EnvironmentChange; class PathChooserPrivate; class QTCREATOR_UTILS_EXPORT PathChooser : public QWidget @@ -77,7 +76,6 @@ public: void setBaseDirectory(const FilePath &base); void setEnvironment(const Environment &env); - void setEnvironmentChange(const EnvironmentChange &change); /** Returns the suggested label title when used in a form layout. */ static QString label(); diff --git a/src/libs/utils/pathlisteditor.cpp b/src/libs/utils/pathlisteditor.cpp index fc7beb75977..f2349b275fd 100644 --- a/src/libs/utils/pathlisteditor.cpp +++ b/src/libs/utils/pathlisteditor.cpp @@ -16,6 +16,7 @@ /*! \class Utils::PathListEditor + \inmodule QtCreator \brief The PathListEditor class is a control that lets the user edit a list of (directory) paths diff --git a/src/libs/utils/persistentsettings.cpp b/src/libs/utils/persistentsettings.cpp index 1db834bff7f..2daf2934c51 100644 --- a/src/libs/utils/persistentsettings.cpp +++ b/src/libs/utils/persistentsettings.cpp @@ -50,6 +50,7 @@ static QRect stringToRectangle(const QString &v) /*! \class Utils::PersistentSettingsReader + \inmodule QtCreator \brief The PersistentSettingsReader class reads a QVariantMap of arbitrary, nested data structures from an XML file. @@ -349,6 +350,7 @@ FilePath PersistentSettingsReader::filePath() /*! \class Utils::PersistentSettingsWriter + \inmodule QtCreator \brief The PersistentSettingsWriter class serializes a QVariantMap of arbitrary, nested data structures to an XML file. diff --git a/src/libs/utils/port.cpp b/src/libs/utils/port.cpp index 3f9166a1a9d..7ddd3ea0da4 100644 --- a/src/libs/utils/port.cpp +++ b/src/libs/utils/port.cpp @@ -6,9 +6,13 @@ #include "qtcassert.h" #include "stringutils.h" +#include + #include -/*! \class Utils::Port +/*! + \class Utils::Port + \inmodule QtCreator \brief The Port class implements a wrapper around a 16 bit port number to be used in conjunction with IP addresses. @@ -31,27 +35,7 @@ quint16 Port::number() const QTC_ASSERT(isValid(), return -1); return quint16(m_port); } -QList Port::parseFromSedOutput(const QByteArray &output) -{ - QList ports; - const QList portStrings = output.split('\n'); - for (const QByteArray &portString : portStrings) { - if (portString.size() != 4) - continue; - bool ok; - const Port port(portString.toInt(&ok, 16)); - if (ok) { - if (!ports.contains(port)) - ports << port; - } else { - qWarning("%s: Unexpected string '%s' is not a port.", - Q_FUNC_INFO, portString.data()); - } - } - return ports; -} - -QList Port::parseFromNetstatOutput(const QByteArray &output) +QList Port::parseFromCommandOutput(const QByteArray &output) { QList ports; const QList lines = output.split('\n'); diff --git a/src/libs/utils/port.h b/src/libs/utils/port.h index 851b41ceaf4..c4b46631de2 100644 --- a/src/libs/utils/port.h +++ b/src/libs/utils/port.h @@ -24,8 +24,8 @@ public: QString toString() const { return QString::number(m_port); } - static QList parseFromSedOutput(const QByteArray &output); - static QList parseFromNetstatOutput(const QByteArray &output); + // Parses the output of "netstat -an" and "cat /proc/net/tcp" + static QList parseFromCommandOutput(const QByteArray &output); friend bool operator<(const Port &p1, const Port &p2) { return p1.number() < p2.number(); } friend bool operator<=(const Port &p1, const Port &p2) { return p1.number() <= p2.number(); } diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/process.cpp similarity index 83% rename from src/libs/utils/qtcprocess.cpp rename to src/libs/utils/process.cpp index 74bd28560b3..85d3fa54ed0 100644 --- a/src/libs/utils/qtcprocess.cpp +++ b/src/libs/utils/process.cpp @@ -1,7 +1,7 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "qtcprocess.h" +#include "process.h" #include "algorithm.h" #include "environment.h" @@ -12,10 +12,13 @@ #include "processreaper.h" #include "processutils.h" #include "stringutils.h" -#include "terminalprocess_p.h" +#include "terminalhooks.h" #include "threadutils.h" #include "utilstr.h" +#include +#include + #include #include #include @@ -168,9 +171,9 @@ enum { syncDebug = 0 }; enum { defaultMaxHangTimerCount = 10 }; -static Q_LOGGING_CATEGORY(processLog, "qtc.utils.qtcprocess", QtWarningMsg) -static Q_LOGGING_CATEGORY(processStdoutLog, "qtc.utils.qtcprocess.stdout", QtWarningMsg) -static Q_LOGGING_CATEGORY(processStderrLog, "qtc.utils.qtcprocess.stderr", QtWarningMsg) +static Q_LOGGING_CATEGORY(processLog, "qtc.utils.process", QtWarningMsg) +static Q_LOGGING_CATEGORY(processStdoutLog, "qtc.utils.process.stdout", QtWarningMsg) +static Q_LOGGING_CATEGORY(processStderrLog, "qtc.utils.process.stderr", QtWarningMsg) static DeviceProcessHooks s_deviceHooks; @@ -304,6 +307,107 @@ private: QProcess *m_process = nullptr; }; +class PtyProcessImpl final : public DefaultImpl +{ +public: + ~PtyProcessImpl() { QTC_CHECK(m_setup.m_ptyData); m_setup.m_ptyData->setResizeHandler({}); } + + qint64 write(const QByteArray &data) final + { + if (m_ptyProcess) + return m_ptyProcess->write(data); + return -1; + } + + void sendControlSignal(ControlSignal controlSignal) final + { + if (!m_ptyProcess) + return; + + switch (controlSignal) { + case ControlSignal::Terminate: + m_ptyProcess.reset(); + break; + case ControlSignal::Kill: + m_ptyProcess->kill(); + break; + default: + QTC_CHECK(false); + } + } + + void doDefaultStart(const QString &program, const QStringList &arguments) final + { + QTC_CHECK(m_setup.m_ptyData); + m_setup.m_ptyData->setResizeHandler([this](const QSize &size) { + if (m_ptyProcess) + m_ptyProcess->resize(size.width(), size.height()); + }); + m_ptyProcess.reset(PtyQt::createPtyProcess(IPtyProcess::AutoPty)); + if (!m_ptyProcess) { + const ProcessResultData result = {-1, + QProcess::CrashExit, + QProcess::FailedToStart, + "Failed to create pty process"}; + emit done(result); + return; + } + + QProcessEnvironment penv = m_setup.m_environment.toProcessEnvironment(); + if (penv.isEmpty()) + penv = Environment::systemEnvironment().toProcessEnvironment(); + const QStringList senv = penv.toStringList(); + + bool startResult + = m_ptyProcess->startProcess(program, + HostOsInfo::isWindowsHost() + ? QStringList{m_setup.m_nativeArguments} << arguments + : arguments, + m_setup.m_workingDirectory.nativePath(), + senv, + m_setup.m_ptyData->size().width(), + m_setup.m_ptyData->size().height()); + + if (!startResult) { + const ProcessResultData result = {-1, + QProcess::CrashExit, + QProcess::FailedToStart, + "Failed to start pty process: " + + m_ptyProcess->lastError()}; + emit done(result); + return; + } + + if (!m_ptyProcess->lastError().isEmpty()) { + const ProcessResultData result + = {-1, QProcess::CrashExit, QProcess::FailedToStart, m_ptyProcess->lastError()}; + emit done(result); + return; + } + + connect(m_ptyProcess->notifier(), &QIODevice::readyRead, this, [this] { + emit readyRead(m_ptyProcess->readAll(), {}); + }); + + connect(m_ptyProcess->notifier(), &QIODevice::aboutToClose, this, [this] { + if (m_ptyProcess) { + const ProcessResultData result + = {m_ptyProcess->exitCode(), QProcess::NormalExit, QProcess::UnknownError, {}}; + emit done(result); + return; + } + + const ProcessResultData result = {0, QProcess::NormalExit, QProcess::UnknownError, {}}; + emit done(result); + }); + + emit started(m_ptyProcess->pid()); + } + +private: + std::unique_ptr m_ptyProcess; +}; + class QProcessImpl final : public DefaultImpl { public: @@ -350,15 +454,18 @@ private: void doDefaultStart(const QString &program, const QStringList &arguments) final { QTC_ASSERT(QThread::currentThread()->eventDispatcher(), - qWarning("QtcProcess::start(): Starting a process in a non QThread thread " + qWarning("Process::start(): Starting a process in a non QThread thread " "may cause infinite hang when destroying the running process.")); ProcessStartHandler *handler = m_process->processStartHandler(); handler->setProcessMode(m_setup.m_processMode); handler->setWriteData(m_setup.m_writeData); - if (m_setup.m_belowNormalPriority) - handler->setBelowNormalPriority(); handler->setNativeArguments(m_setup.m_nativeArguments); - m_process->setProcessEnvironment(m_setup.m_environment.toProcessEnvironment()); + handler->setWindowsSpecificStartupFlags(m_setup.m_belowNormalPriority, + m_setup.m_createConsoleOnWindows); + + const QProcessEnvironment penv = m_setup.m_environment.toProcessEnvironment(); + if (!penv.isEmpty()) + m_process->setProcessEnvironment(penv); m_process->setWorkingDirectory(m_setup.m_workingDirectory.path()); m_process->setStandardInputFile(m_setup.m_standardInputFile); m_process->setProcessChannelMode(m_setup.m_processChannelMode); @@ -577,7 +684,7 @@ private: class GeneralProcessBlockingImpl : public ProcessBlockingInterface { public: - GeneralProcessBlockingImpl(QtcProcessPrivate *parent); + GeneralProcessBlockingImpl(ProcessPrivate *parent); void flush() { flushSignals(takeAllSignals()); } bool flushFor(ProcessSignalType signalType) { @@ -601,21 +708,20 @@ private: void handleReadyReadSignal(const ReadyReadSignal *launcherSignal); void handleDoneSignal(const DoneSignal *launcherSignal); - QtcProcessPrivate *m_caller = nullptr; + ProcessPrivate *m_caller = nullptr; std::unique_ptr m_processHandler; mutable QMutex m_mutex; QList m_signals; }; -class QtcProcessPrivate : public QObject +class ProcessPrivate : public QObject { public: - explicit QtcProcessPrivate(QtcProcess *parent) + explicit ProcessPrivate(Process *parent) : QObject(parent) , q(parent) , m_killTimer(this) { - m_setup.m_controlEnvironment = Environment::systemEnvironment(); m_killTimer.setSingleShot(true); connect(&m_killTimer, &QTimer::timeout, this, [this] { m_killTimer.stop(); @@ -629,14 +735,16 @@ public: ProcessInterface *createProcessInterface() { + if (m_setup.m_ptyData) + return new PtyProcessImpl; if (m_setup.m_terminalMode != TerminalMode::Off) - return new TerminalImpl(); + return Terminal::Hooks::instance().createTerminalProcessInterface(); const ProcessImpl impl = m_setup.m_processImpl == ProcessImpl::Default ? defaultProcessImpl() : m_setup.m_processImpl; if (impl == ProcessImpl::QProcess) - return new QProcessImpl(); - return new ProcessLauncherImpl(); + return new QProcessImpl; + return new ProcessLauncherImpl; } void setProcessInterface(ProcessInterface *process) @@ -646,11 +754,11 @@ public: m_process.reset(process); m_process->setParent(this); connect(m_process.get(), &ProcessInterface::started, - this, &QtcProcessPrivate::handleStarted); + this, &ProcessPrivate::handleStarted); connect(m_process.get(), &ProcessInterface::readyRead, - this, &QtcProcessPrivate::handleReadyRead); + this, &ProcessPrivate::handleReadyRead); connect(m_process.get(), &ProcessInterface::done, - this, &QtcProcessPrivate::handleDone); + this, &ProcessPrivate::handleDone); m_blockingInterface.reset(process->processBlockingInterface()); if (!m_blockingInterface) @@ -667,23 +775,7 @@ public: return rootCommand; } - Environment fullEnvironment() const - { - Environment env = m_setup.m_environment; - if (!env.hasChanges() && env.combineWithDeviceEnvironment()) { - // FIXME: Either switch to using EnvironmentChange instead of full Environments, or - // feed the full environment into the QtcProcess instead of fixing it up here. - // qWarning("QtcProcess::start: Empty environment set when running '%s'.", - // qPrintable(m_setup.m_commandLine.executable().toString())); - env = m_setup.m_commandLine.executable().deviceEnvironment(); - } - // TODO: needs SshSettings - // if (m_runAsRoot) - // RunControl::provideAskPassEntry(env); - return env; - } - - QtcProcess *q; + Process *q; std::unique_ptr m_blockingInterface; std::unique_ptr m_process; ProcessSetupData m_setup; @@ -694,7 +786,7 @@ public: void handleDone(const ProcessResultData &data); void clearForRun(); - void emitGuardedSignal(void (QtcProcess::* signalName)()) { + void emitGuardedSignal(void (Process::* signalName)()) { GuardLocker locker(m_guard); emit (q->*signalName)(); } @@ -807,7 +899,7 @@ void ProcessInterfaceHandler::appendSignal(ProcessInterfaceSignal *newSignal) QMetaObject::invokeMethod(m_caller, &GeneralProcessBlockingImpl::flush); } -GeneralProcessBlockingImpl::GeneralProcessBlockingImpl(QtcProcessPrivate *parent) +GeneralProcessBlockingImpl::GeneralProcessBlockingImpl(ProcessPrivate *parent) : m_caller(parent) , m_processHandler(new ProcessInterfaceHandler(this, parent->m_process.get())) { @@ -815,7 +907,7 @@ GeneralProcessBlockingImpl::GeneralProcessBlockingImpl(QtcProcessPrivate *parent parent->m_process.get()->setParent(m_processHandler.get()); m_processHandler->setParent(this); // So the hierarchy looks like: - // QtcProcessPrivate + // ProcessPrivate // | // +- GeneralProcessBlockingImpl // | @@ -929,7 +1021,7 @@ void GeneralProcessBlockingImpl::appendSignal(ProcessInterfaceSignal *newSignal) m_signals.append(newSignal); } -bool QtcProcessPrivate::waitForSignal(ProcessSignalType newSignal, int msecs) +bool ProcessPrivate::waitForSignal(ProcessSignalType newSignal, int msecs) { const QDeadlineTimer timeout(msecs); const QDeadlineTimer currentKillTimeout(m_killTimer.remainingTime()); @@ -945,13 +1037,13 @@ bool QtcProcessPrivate::waitForSignal(ProcessSignalType newSignal, int msecs) return result; } -Qt::ConnectionType QtcProcessPrivate::connectionType() const +Qt::ConnectionType ProcessPrivate::connectionType() const { return (m_process->thread() == thread()) ? Qt::DirectConnection : Qt::BlockingQueuedConnection; } -void QtcProcessPrivate::sendControlSignal(ControlSignal controlSignal) +void ProcessPrivate::sendControlSignal(ControlSignal controlSignal) { QTC_ASSERT(QThread::currentThread() == thread(), return); if (!m_process || (m_state == QProcess::NotRunning)) @@ -965,7 +1057,7 @@ void QtcProcessPrivate::sendControlSignal(ControlSignal controlSignal) }, connectionType()); } -void QtcProcessPrivate::clearForRun() +void ProcessPrivate::clearForRun() { m_hangTimerCount = 0; m_stdOut.clearForRun(); @@ -981,7 +1073,7 @@ void QtcProcessPrivate::clearForRun() m_resultData = {}; } -ProcessResult QtcProcessPrivate::interpretExitCode(int exitCode) +ProcessResult ProcessPrivate::interpretExitCode(int exitCode) { if (m_exitCodeInterpreter) return m_exitCodeInterpreter(exitCode); @@ -993,16 +1085,17 @@ ProcessResult QtcProcessPrivate::interpretExitCode(int exitCode) } // Internal /*! - \class Utils::QtcProcess + \class Utils::Process + \inmodule QtCreator - \brief The QtcProcess class provides functionality for with processes. + \brief The Process class provides functionality for with processes. \sa Utils::ProcessArgs */ -QtcProcess::QtcProcess(QObject *parent) +Process::Process(QObject *parent) : QObject(parent), - d(new QtcProcessPrivate(this)) + d(new ProcessPrivate(this)) { qRegisterMetaType("ProcessResultData"); static int qProcessExitStatusMeta = qRegisterMetaType(); @@ -1011,61 +1104,71 @@ QtcProcess::QtcProcess(QObject *parent) Q_UNUSED(qProcessProcessErrorMeta) } -QtcProcess::~QtcProcess() +Process::~Process() { - QTC_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting QtcProcess instance directly from " + QTC_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting Process instance directly from " "one of its signal handlers will lead to crash!")); if (d->m_process) d->m_process->disconnect(); delete d; } -void QtcProcess::setProcessImpl(ProcessImpl processImpl) +void Process::setProcessImpl(ProcessImpl processImpl) { d->m_setup.m_processImpl = processImpl; } -ProcessMode QtcProcess::processMode() const +void Process::setPtyData(const std::optional &data) +{ + d->m_setup.m_ptyData = data; +} + +std::optional Process::ptyData() const +{ + return d->m_setup.m_ptyData; +} + +ProcessMode Process::processMode() const { return d->m_setup.m_processMode; } -void QtcProcess::setTerminalMode(TerminalMode mode) +void Process::setTerminalMode(TerminalMode mode) { d->m_setup.m_terminalMode = mode; } -TerminalMode QtcProcess::terminalMode() const +TerminalMode Process::terminalMode() const { return d->m_setup.m_terminalMode; } -void QtcProcess::setProcessMode(ProcessMode processMode) +void Process::setProcessMode(ProcessMode processMode) { d->m_setup.m_processMode = processMode; } -void QtcProcess::setEnvironment(const Environment &env) +void Process::setEnvironment(const Environment &env) { d->m_setup.m_environment = env; } -const Environment &QtcProcess::environment() const +const Environment &Process::environment() const { return d->m_setup.m_environment; } -void QtcProcess::setControlEnvironment(const Environment &environment) +void Process::setControlEnvironment(const Environment &environment) { d->m_setup.m_controlEnvironment = environment; } -const Environment &QtcProcess::controlEnvironment() const +const Environment &Process::controlEnvironment() const { return d->m_setup.m_controlEnvironment; } -void QtcProcess::setCommand(const CommandLine &cmdLine) +void Process::setCommand(const CommandLine &cmdLine) { if (d->m_setup.m_workingDirectory.needsDevice() && cmdLine.executable().needsDevice()) { QTC_CHECK(d->m_setup.m_workingDirectory.host() == cmdLine.executable().host()); @@ -1073,17 +1176,17 @@ void QtcProcess::setCommand(const CommandLine &cmdLine) d->m_setup.m_commandLine = cmdLine; } -const CommandLine &QtcProcess::commandLine() const +const CommandLine &Process::commandLine() const { return d->m_setup.m_commandLine; } -FilePath QtcProcess::workingDirectory() const +FilePath Process::workingDirectory() const { return d->m_setup.m_workingDirectory; } -void QtcProcess::setWorkingDirectory(const FilePath &dir) +void Process::setWorkingDirectory(const FilePath &dir) { if (dir.needsDevice() && d->m_setup.m_commandLine.executable().needsDevice()) { QTC_CHECK(dir.host() == d->m_setup.m_commandLine.executable().host()); @@ -1091,16 +1194,16 @@ void QtcProcess::setWorkingDirectory(const FilePath &dir) d->m_setup.m_workingDirectory = dir; } -void QtcProcess::setUseCtrlCStub(bool enabled) +void Process::setUseCtrlCStub(bool enabled) { d->m_setup.m_useCtrlCStub = enabled; } -void QtcProcess::start() +void Process::start() { QTC_ASSERT(state() == QProcess::NotRunning, return); QTC_ASSERT(!(d->m_process && d->m_guard.isLocked()), - qWarning("Restarting the QtcProcess directly from one of its signal handlers will " + qWarning("Restarting the Process directly from one of its signal handlers will " "lead to crash! Consider calling close() prior to direct restart.")); d->clearForRun(); ProcessInterface *processImpl = nullptr; @@ -1115,37 +1218,36 @@ void QtcProcess::start() d->m_state = QProcess::Starting; d->m_process->m_setup = d->m_setup; d->m_process->m_setup.m_commandLine = d->fullCommandLine(); - d->m_process->m_setup.m_environment = d->fullEnvironment(); - d->emitGuardedSignal(&QtcProcess::starting); + d->emitGuardedSignal(&Process::starting); d->m_process->start(); } -void QtcProcess::terminate() +void Process::terminate() { d->sendControlSignal(ControlSignal::Terminate); } -void QtcProcess::kill() +void Process::kill() { d->sendControlSignal(ControlSignal::Kill); } -void QtcProcess::interrupt() +void Process::interrupt() { d->sendControlSignal(ControlSignal::Interrupt); } -void QtcProcess::kickoffProcess() +void Process::kickoffProcess() { d->sendControlSignal(ControlSignal::KickOff); } -void QtcProcess::closeWriteChannel() +void Process::closeWriteChannel() { d->sendControlSignal(ControlSignal::CloseWriteChannel); } -bool QtcProcess::startDetached(const CommandLine &cmd, const FilePath &workingDirectory, qint64 *pid) +bool Process::startDetached(const CommandLine &cmd, const FilePath &workingDirectory, qint64 *pid) { return QProcess::startDetached(cmd.executable().toUserOutput(), cmd.splitArguments(), @@ -1153,37 +1255,37 @@ bool QtcProcess::startDetached(const CommandLine &cmd, const FilePath &workingDi pid); } -void QtcProcess::setLowPriority() +void Process::setLowPriority() { d->m_setup.m_lowPriority = true; } -void QtcProcess::setDisableUnixTerminal() +void Process::setDisableUnixTerminal() { d->m_setup.m_unixTerminalDisabled = true; } -void QtcProcess::setAbortOnMetaChars(bool abort) +void Process::setAbortOnMetaChars(bool abort) { d->m_setup.m_abortOnMetaChars = abort; } -void QtcProcess::setRunAsRoot(bool on) +void Process::setRunAsRoot(bool on) { d->m_setup.m_runAsRoot = on; } -bool QtcProcess::isRunAsRoot() const +bool Process::isRunAsRoot() const { return d->m_setup.m_runAsRoot; } -void QtcProcess::setStandardInputFile(const QString &inputFile) +void Process::setStandardInputFile(const QString &inputFile) { d->m_setup.m_standardInputFile = inputFile; } -QString QtcProcess::toStandaloneCommandLine() const +QString Process::toStandaloneCommandLine() const { QStringList parts; parts.append("/usr/bin/env"); @@ -1202,37 +1304,47 @@ QString QtcProcess::toStandaloneCommandLine() const return parts.join(" "); } -void QtcProcess::setExtraData(const QString &key, const QVariant &value) +void Process::setCreateConsoleOnWindows(bool create) +{ + d->m_setup.m_createConsoleOnWindows = create; +} + +bool Process::createConsoleOnWindows() const +{ + return d->m_setup.m_createConsoleOnWindows; +} + +void Process::setExtraData(const QString &key, const QVariant &value) { d->m_setup.m_extraData.insert(key, value); } -QVariant QtcProcess::extraData(const QString &key) const +QVariant Process::extraData(const QString &key) const { return d->m_setup.m_extraData.value(key); } -void QtcProcess::setExtraData(const QVariantHash &extraData) +void Process::setExtraData(const QVariantHash &extraData) { d->m_setup.m_extraData = extraData; } -QVariantHash QtcProcess::extraData() const +QVariantHash Process::extraData() const { return d->m_setup.m_extraData; } -void QtcProcess::setReaperTimeout(int msecs) +void Process::setReaperTimeout(int msecs) { d->m_setup.m_reaperTimeout = msecs; } -int QtcProcess::reaperTimeout() const +int Process::reaperTimeout() const { return d->m_setup.m_reaperTimeout; } -void QtcProcess::setRemoteProcessHooks(const DeviceProcessHooks &hooks) +void Process::setRemoteProcessHooks(const DeviceProcessHooks &hooks) { s_deviceHooks = hooks; } @@ -1267,7 +1379,7 @@ static bool askToKill(const CommandLine &command) // occurs on stderr/stdout as opposed to waitForFinished()). Returns false if a timeout // occurs. Checking of the process' exit state/code still has to be done. -bool QtcProcess::readDataFromProcess(QByteArray *stdOut, QByteArray *stdErr, int timeoutS) +bool Process::readDataFromProcess(QByteArray *stdOut, QByteArray *stdErr, int timeoutS) { enum { syncDebug = 0 }; if (syncDebug) @@ -1307,39 +1419,39 @@ bool QtcProcess::readDataFromProcess(QByteArray *stdOut, QByteArray *stdErr, int return finished; } -ProcessResult QtcProcess::result() const +ProcessResult Process::result() const { return d->m_result; } -ProcessResultData QtcProcess::resultData() const +ProcessResultData Process::resultData() const { return d->m_resultData; } -int QtcProcess::exitCode() const +int Process::exitCode() const { return resultData().m_exitCode; } -QProcess::ExitStatus QtcProcess::exitStatus() const +QProcess::ExitStatus Process::exitStatus() const { return resultData().m_exitStatus; } -QProcess::ProcessError QtcProcess::error() const +QProcess::ProcessError Process::error() const { return resultData().m_error; } -QString QtcProcess::errorString() const +QString Process::errorString() const { return resultData().m_errorString; } // Path utilities -Environment QtcProcess::systemEnvironmentForBinary(const FilePath &filePath) +Environment Process::systemEnvironmentForBinary(const FilePath &filePath) { if (filePath.needsDevice()) { QTC_ASSERT(s_deviceHooks.systemEnvironmentForBinary, return {}); @@ -1349,48 +1461,48 @@ Environment QtcProcess::systemEnvironmentForBinary(const FilePath &filePath) return Environment::systemEnvironment(); } -qint64 QtcProcess::applicationMainThreadId() const +qint64 Process::applicationMainThreadId() const { return d->m_applicationMainThreadId; } -QProcess::ProcessChannelMode QtcProcess::processChannelMode() const +QProcess::ProcessChannelMode Process::processChannelMode() const { return d->m_setup.m_processChannelMode; } -void QtcProcess::setProcessChannelMode(QProcess::ProcessChannelMode mode) +void Process::setProcessChannelMode(QProcess::ProcessChannelMode mode) { d->m_setup.m_processChannelMode = mode; } -QProcess::ProcessState QtcProcess::state() const +QProcess::ProcessState Process::state() const { return d->m_state; } -bool QtcProcess::isRunning() const +bool Process::isRunning() const { return state() == QProcess::Running; } -qint64 QtcProcess::processId() const +qint64 Process::processId() const { return d->m_processId; } -bool QtcProcess::waitForStarted(int msecs) +bool Process::waitForStarted(int msecs) { QTC_ASSERT(d->m_process, return false); if (d->m_state == QProcess::Running) return true; if (d->m_state == QProcess::NotRunning) return false; - return s_waitForStarted.measureAndRun(&QtcProcessPrivate::waitForSignal, d, + return s_waitForStarted.measureAndRun(&ProcessPrivate::waitForSignal, d, ProcessSignalType::Started, msecs); } -bool QtcProcess::waitForReadyRead(int msecs) +bool Process::waitForReadyRead(int msecs) { QTC_ASSERT(d->m_process, return false); if (d->m_state == QProcess::NotRunning) @@ -1398,7 +1510,7 @@ bool QtcProcess::waitForReadyRead(int msecs) return d->waitForSignal(ProcessSignalType::ReadyRead, msecs); } -bool QtcProcess::waitForFinished(int msecs) +bool Process::waitForFinished(int msecs) { QTC_ASSERT(d->m_process, return false); if (d->m_state == QProcess::NotRunning) @@ -1406,17 +1518,17 @@ bool QtcProcess::waitForFinished(int msecs) return d->waitForSignal(ProcessSignalType::Done, msecs); } -QByteArray QtcProcess::readAllRawStandardOutput() +QByteArray Process::readAllRawStandardOutput() { return d->m_stdOut.readAllData(); } -QByteArray QtcProcess::readAllRawStandardError() +QByteArray Process::readAllRawStandardError() { return d->m_stdErr.readAllData(); } -qint64 QtcProcess::write(const QString &input) +qint64 Process::write(const QString &input) { // Non-windows is assumed to be UTF-8 if (commandLine().executable().osType() != OsTypeWindows) @@ -1431,7 +1543,7 @@ qint64 QtcProcess::write(const QString &input) return writeRaw(input.toUtf8()); } -qint64 QtcProcess::writeRaw(const QByteArray &input) +qint64 Process::writeRaw(const QByteArray &input) { QTC_ASSERT(processMode() == ProcessMode::Writer, return -1); QTC_ASSERT(d->m_process, return -1); @@ -1444,7 +1556,7 @@ qint64 QtcProcess::writeRaw(const QByteArray &input) return result; } -void QtcProcess::close() +void Process::close() { QTC_ASSERT(QThread::currentThread() == thread(), return); if (d->m_process) { @@ -1464,7 +1576,7 @@ void QtcProcess::close() Calls terminate() directly and after a delay of reaperTimeout() it calls kill() if the process is still running. */ -void QtcProcess::stop() +void Process::stop() { if (state() == QProcess::NotRunning) return; @@ -1473,47 +1585,17 @@ void QtcProcess::stop() d->m_killTimer.start(d->m_process->m_setup.m_reaperTimeout); } -QString QtcProcess::readAllStandardOutput() +QString Process::readAllStandardOutput() { return QString::fromUtf8(readAllRawStandardOutput()); } -QString QtcProcess::readAllStandardError() +QString Process::readAllStandardError() { return QString::fromUtf8(readAllRawStandardError()); } -/*! - \class Utils::SynchronousProcess - - \brief The SynchronousProcess class runs a synchronous process in its own - event loop that blocks only user input events. Thus, it allows for the GUI to - repaint and append output to log windows. - - The callbacks set with setStdOutCallback(), setStdErrCallback() are called - with complete lines based on the '\\n' marker. - They would typically be used for log windows. - - Alternatively you can used setStdOutLineCallback() and setStdErrLineCallback() - to process the output line by line. - - There is a timeout handling that takes effect after the last data have been - read from stdout/stdin (as opposed to waitForFinished(), which measures time - since it was invoked). It is thus also suitable for slow processes that - continuously output data (like version system operations). - - The property timeOutMessageBoxEnabled influences whether a message box is - shown asking the user if they want to kill the process on timeout (default: false). - - There are also static utility functions for dealing with fully synchronous - processes, like reading the output with correct timeout handling. - - Caution: This class should NOT be used if there is a chance that the process - triggers opening dialog boxes (for example, by file watchers triggering), - as this will cause event loop problems. -*/ - -QString QtcProcess::exitMessage() const +QString Process::exitMessage() const { const QString fullCmd = commandLine().toUserOutput(); switch (result()) { @@ -1533,7 +1615,7 @@ QString QtcProcess::exitMessage() const return {}; } -QByteArray QtcProcess::allRawOutput() const +QByteArray Process::allRawOutput() const { QTC_CHECK(d->m_stdOut.keepRawData); QTC_CHECK(d->m_stdErr.keepRawData); @@ -1547,7 +1629,7 @@ QByteArray QtcProcess::allRawOutput() const return !d->m_stdOut.rawData.isEmpty() ? d->m_stdOut.rawData : d->m_stdErr.rawData; } -QString QtcProcess::allOutput() const +QString Process::allOutput() const { QTC_CHECK(d->m_stdOut.keepRawData); QTC_CHECK(d->m_stdErr.keepRawData); @@ -1564,30 +1646,30 @@ QString QtcProcess::allOutput() const return !out.isEmpty() ? out : err; } -QByteArray QtcProcess::rawStdOut() const +QByteArray Process::rawStdOut() const { QTC_CHECK(d->m_stdOut.keepRawData); return d->m_stdOut.rawData; } -QString QtcProcess::stdOut() const +QString Process::stdOut() const { QTC_CHECK(d->m_stdOut.keepRawData); return d->m_codec->toUnicode(d->m_stdOut.rawData); } -QString QtcProcess::stdErr() const +QString Process::stdErr() const { QTC_CHECK(d->m_stdErr.keepRawData); return d->m_codec->toUnicode(d->m_stdErr.rawData); } -QString QtcProcess::cleanedStdOut() const +QString Process::cleanedStdOut() const { return Utils::normalizeNewlines(stdOut()); } -QString QtcProcess::cleanedStdErr() const +QString Process::cleanedStdErr() const { return Utils::normalizeNewlines(stdErr()); } @@ -1602,20 +1684,20 @@ static QStringList splitLines(const QString &text) return result; } -const QStringList QtcProcess::stdOutLines() const +const QStringList Process::stdOutLines() const { return splitLines(cleanedStdOut()); } -const QStringList QtcProcess::stdErrLines() const +const QStringList Process::stdErrLines() const { return splitLines(cleanedStdErr()); } -QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const QtcProcess &r) +QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const Process &r) { QDebug nsp = str.nospace(); - nsp << "QtcProcess: result=" + nsp << "Process: result=" << int(r.d->m_result) << " ex=" << r.exitCode() << '\n' << r.d->m_stdOut.rawData.size() << " bytes stdout, stderr=" << r.d->m_stdErr.rawData << '\n'; return str; @@ -1691,7 +1773,7 @@ void ChannelBuffer::handleRest() } } -void QtcProcess::setTimeoutS(int timeoutS) +void Process::setTimeoutS(int timeoutS) { if (timeoutS > 0) d->m_maxHangTimerCount = qMax(2, timeoutS); @@ -1699,33 +1781,33 @@ void QtcProcess::setTimeoutS(int timeoutS) d->m_maxHangTimerCount = INT_MAX / 1000; } -int QtcProcess::timeoutS() const +int Process::timeoutS() const { return d->m_maxHangTimerCount; } -void QtcProcess::setCodec(QTextCodec *c) +void Process::setCodec(QTextCodec *c) { QTC_ASSERT(c, return); d->m_codec = c; } -void QtcProcess::setTimeOutMessageBoxEnabled(bool v) +void Process::setTimeOutMessageBoxEnabled(bool v) { d->m_timeOutMessageBoxEnabled = v; } -void QtcProcess::setExitCodeInterpreter(const ExitCodeInterpreter &interpreter) +void Process::setExitCodeInterpreter(const ExitCodeInterpreter &interpreter) { d->m_exitCodeInterpreter = interpreter; } -void QtcProcess::setWriteData(const QByteArray &writeData) +void Process::setWriteData(const QByteArray &writeData) { d->m_setup.m_writeData = writeData; } -void QtcProcess::runBlocking(EventLoopMode eventLoopMode) +void Process::runBlocking(EventLoopMode eventLoopMode) { // Attach a dynamic property with info about blocking type d->storeEventLoopDebugInfo(int(eventLoopMode)); @@ -1734,7 +1816,7 @@ void QtcProcess::runBlocking(EventLoopMode eventLoopMode) static const int blockingThresholdMs = qtcEnvironmentVariableIntValue("QTC_PROCESS_THRESHOLD"); if (blockingThresholdMs > 0 && isMainThread()) startTime = QDateTime::currentDateTime(); - QtcProcess::start(); + Process::start(); // Remove the dynamic property so that it's not reused in subseqent start() d->storeEventLoopDebugInfo({}); @@ -1745,7 +1827,7 @@ void QtcProcess::runBlocking(EventLoopMode eventLoopMode) // Do not start the event loop in that case. if (state() == QProcess::Starting) { QTimer timer(this); - connect(&timer, &QTimer::timeout, d, &QtcProcessPrivate::slotTimeout); + connect(&timer, &QTimer::timeout, d, &ProcessPrivate::slotTimeout); timer.setInterval(1000); timer.start(); #ifdef QT_GUI_LIB @@ -1786,33 +1868,33 @@ void QtcProcess::runBlocking(EventLoopMode eventLoopMode) } } -void QtcProcess::setStdOutCallback(const TextChannelCallback &callback) +void Process::setStdOutCallback(const TextChannelCallback &callback) { d->m_stdOut.outputCallback = callback; d->m_stdOut.emitSingleLines = false; } -void QtcProcess::setStdOutLineCallback(const TextChannelCallback &callback) +void Process::setStdOutLineCallback(const TextChannelCallback &callback) { d->m_stdOut.outputCallback = callback; d->m_stdOut.emitSingleLines = true; d->m_stdOut.keepRawData = false; } -void QtcProcess::setStdErrCallback(const TextChannelCallback &callback) +void Process::setStdErrCallback(const TextChannelCallback &callback) { d->m_stdErr.outputCallback = callback; d->m_stdErr.emitSingleLines = false; } -void QtcProcess::setStdErrLineCallback(const TextChannelCallback &callback) +void Process::setStdErrLineCallback(const TextChannelCallback &callback) { d->m_stdErr.outputCallback = callback; d->m_stdErr.emitSingleLines = true; d->m_stdErr.keepRawData = false; } -void QtcProcess::setTextChannelMode(Channel channel, TextChannelMode mode) +void Process::setTextChannelMode(Channel channel, TextChannelMode mode) { const TextChannelCallback outputCb = [this](const QString &text) { GuardLocker locker(d->m_guard); @@ -1825,7 +1907,7 @@ void QtcProcess::setTextChannelMode(Channel channel, TextChannelMode mode) const TextChannelCallback callback = (channel == Channel::Output) ? outputCb : errorCb; ChannelBuffer *buffer = channel == Channel::Output ? &d->m_stdOut : &d->m_stdErr; QTC_ASSERT(buffer->m_textChannelMode == TextChannelMode::Off, qWarning() - << "QtcProcess::setTextChannelMode(): Changing text channel mode for" + << "Process::setTextChannelMode(): Changing text channel mode for" << (channel == Channel::Output ? "Output": "Error") << "channel while it was previously set for this channel."); buffer->m_textChannelMode = mode; @@ -1848,13 +1930,13 @@ void QtcProcess::setTextChannelMode(Channel channel, TextChannelMode mode) } } -TextChannelMode QtcProcess::textChannelMode(Channel channel) const +TextChannelMode Process::textChannelMode(Channel channel) const { ChannelBuffer *buffer = channel == Channel::Output ? &d->m_stdOut : &d->m_stdErr; return buffer->m_textChannelMode; } -void QtcProcessPrivate::slotTimeout() +void ProcessPrivate::slotTimeout() { if (!m_waitingForUser && (++m_hangTimerCount > m_maxHangTimerCount)) { if (debug) @@ -1875,17 +1957,17 @@ void QtcProcessPrivate::slotTimeout() } } -void QtcProcessPrivate::handleStarted(qint64 processId, qint64 applicationMainThreadId) +void ProcessPrivate::handleStarted(qint64 processId, qint64 applicationMainThreadId) { QTC_CHECK(m_state == QProcess::Starting); m_state = QProcess::Running; m_processId = processId; m_applicationMainThreadId = applicationMainThreadId; - emitGuardedSignal(&QtcProcess::started); + emitGuardedSignal(&Process::started); } -void QtcProcessPrivate::handleReadyRead(const QByteArray &outputData, const QByteArray &errorData) +void ProcessPrivate::handleReadyRead(const QByteArray &outputData, const QByteArray &errorData) { QTC_CHECK(m_state == QProcess::Running); @@ -1900,7 +1982,7 @@ void QtcProcessPrivate::handleReadyRead(const QByteArray &outputData, const QByt std::cout << outputData.constData() << std::flush; } else { m_stdOut.append(outputData); - emitGuardedSignal(&QtcProcess::readyReadStandardOutput); + emitGuardedSignal(&Process::readyReadStandardOutput); } } if (!errorData.isEmpty()) { @@ -1909,12 +1991,12 @@ void QtcProcessPrivate::handleReadyRead(const QByteArray &outputData, const QByt std::cerr << errorData.constData() << std::flush; } else { m_stdErr.append(errorData); - emitGuardedSignal(&QtcProcess::readyReadStandardError); + emitGuardedSignal(&Process::readyReadStandardError); } } } -void QtcProcessPrivate::handleDone(const ProcessResultData &data) +void ProcessPrivate::handleDone(const ProcessResultData &data) { m_killTimer.stop(); const bool wasCanceled = m_resultData.m_canceledByUser; @@ -1966,7 +2048,7 @@ void QtcProcessPrivate::handleDone(const ProcessResultData &data) m_stdOut.handleRest(); m_stdErr.handleRest(); - emitGuardedSignal(&QtcProcess::done); + emitGuardedSignal(&Process::done); m_processId = 0; m_applicationMainThreadId = 0; } @@ -1980,7 +2062,7 @@ static QString blockingMessage(const QVariant &variant) return "blocking without event loop"; } -void QtcProcessPrivate::setupDebugLog() +void ProcessPrivate::setupDebugLog() { if (!processLog().isDebugEnabled()) return; @@ -1990,7 +2072,7 @@ void QtcProcessPrivate::setupDebugLog() return duration_cast(system_clock::now().time_since_epoch()).count(); }; - connect(q, &QtcProcess::starting, this, [=] { + connect(q, &Process::starting, this, [=] { const quint64 msNow = now(); setProperty(QTC_PROCESS_STARTTIME, msNow); @@ -2003,7 +2085,7 @@ void QtcProcessPrivate::setupDebugLog() setProperty(QTC_PROCESS_NUMBER, currentNumber); }); - connect(q, &QtcProcess::done, this, [=] { + connect(q, &Process::done, this, [=] { if (!m_process.get()) return; const QVariant n = property(QTC_PROCESS_NUMBER); @@ -2029,24 +2111,24 @@ void QtcProcessPrivate::setupDebugLog() }); } -void QtcProcessPrivate::storeEventLoopDebugInfo(const QVariant &value) +void ProcessPrivate::storeEventLoopDebugInfo(const QVariant &value) { if (processLog().isDebugEnabled()) setProperty(QTC_PROCESS_BLOCKING_TYPE, value); } -QtcProcessAdapter::QtcProcessAdapter() +ProcessTaskAdapter::ProcessTaskAdapter() { - connect(task(), &QtcProcess::done, this, [this] { + connect(task(), &Process::done, this, [this] { emit done(task()->result() == ProcessResult::FinishedWithSuccess); }); } -void QtcProcessAdapter::start() +void ProcessTaskAdapter::start() { task()->start(); } } // namespace Utils -#include "qtcprocess.moc" +#include "process.moc" diff --git a/src/libs/utils/qtcprocess.h b/src/libs/utils/process.h similarity index 85% rename from src/libs/utils/qtcprocess.h rename to src/libs/utils/process.h index 67898ba855a..83f1bb990b4 100644 --- a/src/libs/utils/qtcprocess.h +++ b/src/libs/utils/process.h @@ -1,13 +1,18 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#pragma once +#if defined(Q_CC_MINGW) && defined(WIN_PTHREADS_H) && !defined(_INC_PROCESS) + // Arrived here via which wants to include + #include_next +#elif !defined(UTILS_PROCESS_H) +#define UTILS_PROCESS_H #include "utils_global.h" #include "commandline.h" #include "processenums.h" -#include "tasktree.h" + +#include #include @@ -16,24 +21,23 @@ class QDebug; class QTextCodec; QT_END_NAMESPACE -class tst_QtcProcess; - namespace Utils { -namespace Internal { class QtcProcessPrivate; } +namespace Internal { class ProcessPrivate; } +namespace Pty { class Data; } class Environment; class DeviceProcessHooks; class ProcessInterface; class ProcessResultData; -class QTCREATOR_UTILS_EXPORT QtcProcess final : public QObject +class QTCREATOR_UTILS_EXPORT Process final : public QObject { Q_OBJECT public: - explicit QtcProcess(QObject *parent = nullptr); - ~QtcProcess(); + explicit Process(QObject *parent = nullptr); + ~Process(); // ProcessInterface related @@ -76,6 +80,9 @@ public: void setProcessImpl(ProcessImpl processImpl); + void setPtyData(const std::optional &data); + std::optional ptyData() const; + void setTerminalMode(TerminalMode mode); TerminalMode terminalMode() const; bool usesTerminal() const { return terminalMode() != TerminalMode::Off; } @@ -125,7 +132,7 @@ public: // Other enhancements. // These (or some of them) may be potentially moved outside of the class. - // For some we may aggregate in another public utils class (or subclass of QtcProcess)? + // Some of them could be aggregated in another public utils class. // TODO: Unused currently? Should it serve as a compartment for contrary of remoteEnvironment? static Environment systemEnvironmentForBinary(const FilePath &filePath); @@ -177,6 +184,9 @@ public: QString toStandaloneCommandLine() const; + void setCreateConsoleOnWindows(bool create); + bool createConsoleOnWindows() const; + signals: void starting(); // On NotRunning -> Starting state transition void started(); // On Starting -> Running state transition @@ -187,10 +197,10 @@ signals: void textOnStandardError(const QString &text); private: - friend QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const QtcProcess &r); + friend QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const Process &r); - friend class Internal::QtcProcessPrivate; - Internal::QtcProcessPrivate *d = nullptr; + friend class Internal::ProcessPrivate; + Internal::ProcessPrivate *d = nullptr; }; class DeviceProcessHooks @@ -200,13 +210,15 @@ public: std::function systemEnvironmentForBinary; }; -class QTCREATOR_UTILS_EXPORT QtcProcessAdapter : public Tasking::TaskAdapter +class QTCREATOR_UTILS_EXPORT ProcessTaskAdapter : public Tasking::TaskAdapter { public: - QtcProcessAdapter(); + ProcessTaskAdapter(); void start() final; }; } // namespace Utils -QTC_DECLARE_CUSTOM_TASK(Process, Utils::QtcProcessAdapter); +TASKING_DECLARE_TASK(ProcessTask, Utils::ProcessTaskAdapter); + +#endif // UTILS_PROCESS_H diff --git a/src/libs/utils/process_ctrlc_stub.cpp b/src/libs/utils/process_ctrlc_stub.cpp index bbfdf7ff14d..b924e74e49d 100644 --- a/src/libs/utils/process_ctrlc_stub.cpp +++ b/src/libs/utils/process_ctrlc_stub.cpp @@ -44,7 +44,7 @@ public: fwprintf(stderr, L"qtcreator_ctrlc_stub: CreateJobObject failed: 0x%x.\n", GetLastError()); return; } - JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {0}; + JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {}; jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; if (!SetInformationJobObject(m_job, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli))) { fwprintf(stderr, L"qtcreator_ctrlc_stub: SetInformationJobObject failed: 0x%x.\n", GetLastError()); @@ -85,7 +85,7 @@ int main(int argc, char **) uiShutDownWindowMessage = RegisterWindowMessage(L"qtcctrlcstub_shutdown"); uiInterruptMessage = RegisterWindowMessage(L"qtcctrlcstub_interrupt"); - WNDCLASSEX wcex = {0}; + WNDCLASSEX wcex = {}; wcex.cbSize = sizeof(wcex); wcex.lpfnWndProc = WndProc; wcex.hInstance = GetModuleHandle(nullptr); @@ -188,14 +188,14 @@ DWORD WINAPI processWatcherThread(LPVOID lpParameter) bool startProcess(wchar_t *pCommandLine, bool lowerPriority, const JobKillOnClose& job) { - SECURITY_ATTRIBUTES sa = {0}; + SECURITY_ATTRIBUTES sa = {}; sa.nLength = sizeof(sa); sa.bInheritHandle = TRUE; - STARTUPINFO si = {0}; + STARTUPINFO si = {}; si.cb = sizeof(si); - PROCESS_INFORMATION pi; + PROCESS_INFORMATION pi = {}; DWORD dwCreationFlags = lowerPriority ? BELOW_NORMAL_PRIORITY_CLASS : 0; BOOL bSuccess = CreateProcess(NULL, pCommandLine, &sa, &sa, TRUE, dwCreationFlags, NULL, NULL, &si, &pi); if (!bSuccess) { diff --git a/src/libs/utils/process_stub.qbs b/src/libs/utils/process_stub.qbs deleted file mode 100644 index 341fb57791b..00000000000 --- a/src/libs/utils/process_stub.qbs +++ /dev/null @@ -1,21 +0,0 @@ -import qbs 1.0 - -QtcTool { - name: "qtcreator_process_stub" - consoleApplication: true - - - files: { - if (qbs.targetOS.contains("windows")) { - return [ "process_stub_win.c" ] - } else { - return [ "process_stub_unix.c" ] - } - } - - cpp.dynamicLibraries: { - if (qbs.targetOS.contains("windows")) { - return [ "shell32" ] - } - } -} diff --git a/src/libs/utils/process_stub_unix.c b/src/libs/utils/process_stub_unix.c deleted file mode 100644 index 716e88d1012..00000000000 --- a/src/libs/utils/process_stub_unix.c +++ /dev/null @@ -1,340 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __linux__ -#include - -// Enable compilation with older header that doesn't contain this constant -// for running on newer libraries that do support it -#ifndef PR_SET_PTRACER -#define PR_SET_PTRACER 0x59616d61 -#endif -#endif - -/* For OpenBSD */ -#ifndef EPROTO -# define EPROTO EINVAL -#endif - -extern char **environ; - -static int qtcFd; -static char *sleepMsg; -static int chldPipe[2]; -static int blockingPipe[2]; -static int isDebug; -static volatile int isDetached; -static volatile int chldPid; - -static void __attribute__((noreturn)) doExit(int code) -{ - tcsetpgrp(0, getpid()); - puts(sleepMsg); - const char *rv = fgets(sleepMsg, 2, stdin); /* Minimal size to make it wait */ - (void)rv; // Q_UNUSED - exit(code); -} - -static void sendMsg(const char *msg, int num) -{ - int pidStrLen; - int ioRet; - char pidStr[64]; - - pidStrLen = sprintf(pidStr, msg, num); - if (!isDetached && (ioRet = write(qtcFd, pidStr, pidStrLen)) != pidStrLen) { - fprintf(stderr, "Cannot write to creator comm socket: %s\n", - (ioRet < 0) ? strerror(errno) : "short write"); - isDetached = 2; - } -} - -enum { - ArgCmd = 0, - ArgAction, - ArgSocket, - ArgMsg, - ArgDir, - ArgEnv, - ArgPid, - ArgExe -}; - -/* Handle sigchld */ -static void sigchldHandler(int sig) -{ - int chldStatus; - /* Currently we have only one child, so we exit in case of error. */ - int waitRes; - (void)sig; - for (;;) { - waitRes = waitpid(-1, &chldStatus, WNOHANG); - if (!waitRes) - break; - if (waitRes < 0) { - perror("Cannot obtain exit status of child process"); - doExit(3); - } - if (WIFSTOPPED(chldStatus)) { - /* The child stopped. This can be the result of the initial SIGSTOP handling. - * We won't need the notification pipe any more, as we know that - * the exec() succeeded. */ - close(chldPipe[0]); - close(chldPipe[1]); - chldPipe[0] = -1; - if (isDetached == 2 && isDebug) { - /* qtcreator was not informed and died while debugging, killing the child */ - kill(chldPid, SIGKILL); - } - } else if (WIFEXITED(chldStatus)) { - sendMsg("exit %d\n", WEXITSTATUS(chldStatus)); - doExit(0); - } else { - sendMsg("crash %d\n", WTERMSIG(chldStatus)); - doExit(0); - } - } -} - - -/* syntax: $0 {"run"|"debug"} */ -/* exit codes: 0 = ok, 1 = invocation error, 3 = internal error */ -int main(int argc, char *argv[]) -{ - int errNo, hadInvalidCommand = 0; - char **env = 0; - struct sockaddr_un sau; - struct sigaction act; - - memset(&act, 0, sizeof(act)); - - if (argc < ArgEnv) { - fprintf(stderr, "This is an internal helper of Qt Creator. Do not run it manually.\n"); - return 1; - } - sleepMsg = argv[ArgMsg]; - - /* Connect to the master, i.e. Creator. */ - if ((qtcFd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) { - perror("Cannot create creator comm socket"); - doExit(3); - } - memset(&sau, 0, sizeof(sau)); - sau.sun_family = AF_UNIX; - strncpy(sau.sun_path, argv[ArgSocket], sizeof(sau.sun_path) - 1); - if (connect(qtcFd, (struct sockaddr *)&sau, sizeof(sau))) { - fprintf(stderr, "Cannot connect creator comm socket %s: %s\n", sau.sun_path, strerror(errno)); - doExit(1); - } - - isDebug = !strcmp(argv[ArgAction], "debug"); - isDetached = 0; - - if (*argv[ArgDir] && chdir(argv[ArgDir])) { - /* Only expected error: no such file or direcotry */ - sendMsg("err:chdir %d\n", errno); - return 1; - } - - if (*argv[ArgEnv]) { - FILE *envFd; - char *envdata, *edp, *termEnv; - long size; - int count; - if (!(envFd = fopen(argv[ArgEnv], "r"))) { - fprintf(stderr, "Cannot read creator env file %s: %s\n", - argv[ArgEnv], strerror(errno)); - doExit(1); - } - fseek(envFd, 0, SEEK_END); - size = ftell(envFd); - if (size < 0) { - perror("Failed to get size of env file"); - doExit(1); - } - rewind(envFd); - envdata = malloc(size + 1); - if (fread(envdata, 1, size, envFd) != (size_t)size) { - perror("Failed to read env file"); - doExit(1); - } - envdata[size] = '\0'; - fclose(envFd); - assert(!size || !envdata[size - 1]); - for (count = 0, edp = envdata; edp < envdata + size; ++count) - edp += strlen(edp) + 1; - env = malloc((count + 2) * sizeof(char *)); - for (count = 0, edp = envdata; edp < envdata + size; ++count) { - env[count] = edp; - edp += strlen(edp) + 1; - } - if ((termEnv = getenv("TERM"))) - env[count++] = termEnv - 5; - env[count] = 0; - } - - /* send our pid after we read the environment file (creator will get rid of it) */ - sendMsg("spid %ld\n", (long)getpid()); - - /* - * set up the signal handlers - */ - { - /* Ignore SIGTTOU. Without this, calling tcsetpgrp() from a background - * process group (in which we will be, once as child and once as parent) - * generates the mentioned signal and stops the concerned process. */ - act.sa_handler = SIG_IGN; - if (sigaction(SIGTTOU, &act, 0)) { - perror("sigaction SIGTTOU"); - doExit(3); - } - - /* Handle SIGCHLD to keep track of what the child does without blocking */ - act.sa_handler = sigchldHandler; - if (sigaction(SIGCHLD, &act, 0)) { - perror("sigaction SIGCHLD"); - doExit(3); - } - } - - /* Create execution result notification pipe. */ - if (pipe(chldPipe)) { - perror("Cannot create status pipe"); - doExit(3); - } - - /* The debugged program is not supposed to inherit these handles. But we cannot - * close the writing end before calling exec(). Just handle both ends the same way ... */ - fcntl(chldPipe[0], F_SETFD, FD_CLOEXEC); - fcntl(chldPipe[1], F_SETFD, FD_CLOEXEC); - - if (isDebug) { - /* Create execution start notification pipe. The child waits on this until - the parent writes to it, triggered by an 'c' message from Creator */ - if (pipe(blockingPipe)) { - perror("Cannot create blocking pipe"); - doExit(3); - } - } - - switch ((chldPid = fork())) { - case -1: - perror("Cannot fork child process"); - doExit(3); - case 0: - close(qtcFd); - - /* Remove the SIGCHLD handler from the child */ - act.sa_handler = SIG_DFL; - sigaction(SIGCHLD, &act, 0); - - /* Put the process into an own process group and make it the foregroud - * group on this terminal, so it will receive ctrl-c events, etc. - * This is the main reason for *all* this stub magic in the first place. */ - /* If one of these calls fails, the world is about to end anyway, so - * don't bother checking the return values. */ - setpgid(0, 0); - tcsetpgrp(0, getpid()); - -#ifdef __linux__ - prctl(PR_SET_PTRACER, atoi(argv[ArgPid])); -#endif - /* Block to allow the debugger to attach */ - if (isDebug) { - char buf; - int res = read(blockingPipe[0], &buf, 1); - if (res < 0) - perror("Could not read from blocking pipe"); - close(blockingPipe[0]); - close(blockingPipe[1]); - } - - if (env) - environ = env; - - execvp(argv[ArgExe], argv + ArgExe); - /* Only expected error: no such file or direcotry, i.e. executable not found */ - errNo = errno; - /* Only realistic error case is SIGPIPE */ - if (write(chldPipe[1], &errNo, sizeof(errNo)) != sizeof(errNo)) - perror("Error passing errno to child"); - _exit(0); - default: - sendMsg("pid %d\n", chldPid); - for (;;) { - char buffer[100]; - int nbytes; - - nbytes = read(qtcFd, buffer, 100); - if (nbytes <= 0) { - if (nbytes < 0 && errno == EINTR) - continue; - if (!isDetached) { - isDetached = 2; - if (nbytes == 0) - fprintf(stderr, "Lost connection to QtCreator, detaching from it.\n"); - else - perror("Lost connection to QtCreator, detaching from it"); - } - break; - } else { - int i; - char c = 'i'; - for (i = 0; i < nbytes; ++i) { - switch (buffer[i]) { - case 'k': - if (chldPid > 0) { - kill(chldPid, SIGTERM); - sleep(1); - kill(chldPid, SIGKILL); - } - break; - case 'i': - if (chldPid > 0) { - int res = kill(chldPid, SIGINT); - if (res) - perror("Stub could not interrupt inferior"); - } - break; - case 'c': { - int res = write(blockingPipe[1], &c, 1); - if (res < 0) - perror("Could not write to blocking pipe"); - break; - } - case 'd': - isDetached = 1; - break; - case 's': - exit(0); - default: - if (!hadInvalidCommand) { - fprintf(stderr, "Ignoring invalid commands from QtCreator.\n"); - hadInvalidCommand = 1; - } - } - } - } - } - if (isDetached) { - for (;;) - pause(); /* will exit in the signal handler... */ - } - } - assert(0); - return 0; -} diff --git a/src/libs/utils/process_stub_win.c b/src/libs/utils/process_stub_win.c deleted file mode 100644 index 09f220a6c65..00000000000 --- a/src/libs/utils/process_stub_win.c +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#undef _WIN32_WINNT -#define _WIN32_WINNT 0x0501 /* WinXP, needed for DebugActiveProcessStop() */ - -#include -#include -#include -#include -#include -#include -#include - -static FILE *qtcFd; -static wchar_t *sleepMsg; - -enum RunMode { Run, Debug, Suspend }; - -/* Print some "press enter" message, wait for that, exit. */ -static void doExit(int code) -{ - char buf[2]; - _putws(sleepMsg); - fgets(buf, 2, stdin); /* Minimal size to make it wait */ - exit(code); -} - -/* Print an error message for unexpected Windows system errors, wait, exit. */ -static void systemError(const char *str) -{ - fprintf(stderr, str, GetLastError()); - doExit(3); -} - -/* Send a message to the master. */ -static void sendMsg(const char *msg, int num) -{ - int pidStrLen; - char pidStr[64]; - - pidStrLen = sprintf(pidStr, msg, num); - if (fwrite(pidStr, pidStrLen, 1, qtcFd) != 1 || fflush(qtcFd)) { - fprintf(stderr, "Cannot write to creator comm socket: %s\n", - strerror(errno)); - doExit(3); - } -} - -/* Ignore the first ctrl-c/break within a second. */ -static BOOL WINAPI ctrlHandler(DWORD dwCtrlType) -{ - static ULARGE_INTEGER lastTime; - ULARGE_INTEGER thisTime; - SYSTEMTIME sysTime; - FILETIME fileTime; - - if (dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_BREAK_EVENT) { - GetSystemTime(&sysTime); - SystemTimeToFileTime(&sysTime, &fileTime); - thisTime.LowPart = fileTime.dwLowDateTime; - thisTime.HighPart = fileTime.dwHighDateTime; - if (lastTime.QuadPart + 10000000 < thisTime.QuadPart) { - lastTime.QuadPart = thisTime.QuadPart; - return TRUE; - } - } - return FALSE; -} - -enum { - ArgCmd = 0, - ArgAction, - ArgSocket, - ArgDir, - ArgEnv, - ArgCmdLine, - ArgMsg, - ArgCount -}; - -/* syntax: $0 {"run"|"debug"} */ -/* exit codes: 0 = ok, 1 = invocation error, 3 = internal error */ -int main() -{ - int argc; - int creationFlags; - wchar_t **argv; - wchar_t *env = 0; - STARTUPINFOW si; - PROCESS_INFORMATION pi; - DEBUG_EVENT dbev; - enum RunMode mode = Run; - HANDLE image = NULL; - - argv = CommandLineToArgvW(GetCommandLine(), &argc); - - if (argc != ArgCount) { - fprintf(stderr, "This is an internal helper of Qt Creator. Do not run it manually.\n"); - return 1; - } - sleepMsg = argv[ArgMsg]; - - /* Connect to the master, i.e. Creator. */ - if (!(qtcFd = _wfopen(argv[ArgSocket], L"w"))) { - fprintf(stderr, "Cannot connect creator comm pipe %S: %s\n", - argv[ArgSocket], strerror(errno)); - doExit(1); - } - - if (*argv[ArgDir] && !SetCurrentDirectoryW(argv[ArgDir])) { - /* Only expected error: no such file or direcotry */ - sendMsg("err:chdir %d\n", GetLastError()); - return 1; - } - - if (*argv[ArgEnv]) { - FILE *envFd; - long size; - if (!(envFd = _wfopen(argv[ArgEnv], L"rb"))) { - fprintf(stderr, "Cannot read creator env file %S: %s\n", - argv[ArgEnv], strerror(errno)); - doExit(1); - } - fseek(envFd, 0, SEEK_END); - size = ftell(envFd); - rewind(envFd); - env = malloc(size); - if (fread(env, 1, size, envFd) != size) { - perror("Failed to read env file"); - doExit(1); - } - fclose(envFd); - } - - ZeroMemory(&pi, sizeof(pi)); - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - - creationFlags = CREATE_UNICODE_ENVIRONMENT; - if (!wcscmp(argv[ArgAction], L"debug")) { - mode = Debug; - } else if (!wcscmp(argv[ArgAction], L"suspend")) { - mode = Suspend; - } - - switch (mode) { - case Debug: - creationFlags |= DEBUG_ONLY_THIS_PROCESS; - break; - case Suspend: - creationFlags |= CREATE_SUSPENDED; - break; - default: - break; - } - if (!CreateProcessW(0, argv[ArgCmdLine], 0, 0, FALSE, creationFlags, env, 0, &si, &pi)) { - /* Only expected error: no such file or direcotry, i.e. executable not found */ - sendMsg("err:exec %d\n", GetLastError()); - doExit(1); - } - - /* This is somewhat convoluted. What we actually want is creating a - suspended process and letting gdb attach to it. Unfortunately, - the Windows kernel runs amok when we attempt this. - So instead we start a debugged process, eat all the initial - debug events, suspend the process and detach from it. If gdb - tries to attach *now*, everything goes smoothly. Yay. */ - if (mode == Debug) { - do { - if (!WaitForDebugEvent (&dbev, INFINITE)) - systemError("Cannot fetch debug event, error %d\n"); - if (dbev.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) - image = dbev.u.CreateProcessInfo.hFile; - if (dbev.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) { - /* The first exception to be delivered is a trap - which indicates completion of startup. */ - if (SuspendThread(pi.hThread) == (DWORD)-1) - systemError("Cannot suspend debugee, error %d\n"); - } - if (!ContinueDebugEvent(dbev.dwProcessId, dbev.dwThreadId, DBG_CONTINUE)) - systemError("Cannot continue debug event, error %d\n"); - } while (dbev.dwDebugEventCode != EXCEPTION_DEBUG_EVENT); - if (!DebugActiveProcessStop(dbev.dwProcessId)) - systemError("Cannot detach from debugee, error %d\n"); - if (image) - CloseHandle(image); - } - - SetConsoleCtrlHandler(ctrlHandler, TRUE); - - sendMsg("thread %d\n", pi.dwThreadId); - sendMsg("pid %d\n", pi.dwProcessId); - - if (WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_FAILED) - systemError("Wait for debugee failed, error %d\n"); - - /* Don't close the process/thread handles, so that the kernel doesn't free - the resources before ConsoleProcess is able to obtain handles to them - - this would be a problem if the child process exits very quickly. */ - doExit(0); - - return 0; -} diff --git a/src/libs/utils/processenums.h b/src/libs/utils/processenums.h index 6ea37a2d37a..84019bae530 100644 --- a/src/libs/utils/processenums.h +++ b/src/libs/utils/processenums.h @@ -24,10 +24,9 @@ enum class ProcessImpl { enum class TerminalMode { Off, - Run, - Debug, - Suspend, - On = Run // Default mode for terminal set to on + Run, // Start with process stub enabled + Debug, // Start with process stub enabled and wait for debugger to attach + Detached, // Start in a terminal, without process stub. }; // Miscellaneous, not process core diff --git a/src/libs/utils/processhandle.cpp b/src/libs/utils/processhandle.cpp index 686b1157adf..06850457d37 100644 --- a/src/libs/utils/processhandle.cpp +++ b/src/libs/utils/processhandle.cpp @@ -7,6 +7,7 @@ namespace Utils { /*! \class Utils::ProcessHandle + \inmodule QtCreator \brief The ProcessHandle class is a helper class to describe a process. Encapsulates parameters of a running process, local (PID) or remote (to be diff --git a/src/libs/utils/processinfo.cpp b/src/libs/utils/processinfo.cpp index 366800d3161..0d71e380fbf 100644 --- a/src/libs/utils/processinfo.cpp +++ b/src/libs/utils/processinfo.cpp @@ -3,14 +3,13 @@ #include "processinfo.h" -#include "qtcprocess.h" +#include "algorithm.h" +#include "process.h" + +#include +#include #if defined(Q_OS_UNIX) -#include -#include -#include -#include -#include #elif defined(Q_OS_WIN) #include "winutils.h" #ifdef QTCREATOR_PCH_H @@ -32,82 +31,64 @@ bool ProcessInfo::operator<(const ProcessInfo &other) const return commandLine < other.commandLine; } -#if defined(Q_OS_UNIX) - -static bool isUnixProcessId(const QString &procname) -{ - for (int i = 0; i != procname.size(); ++i) - if (!procname.at(i).isDigit()) - return false; - return true; -} - // Determine UNIX processes by reading "/proc". Default to ps if // it does not exist -static const char procDirC[] = "/proc/"; - -static QList getLocalProcessesUsingProc() +static QList getLocalProcessesUsingProc(const FilePath &procDir) { + static const QString execs = "-exec test -f {}/exe \\; " + "-exec test -f {}/cmdline \\; " + "-exec echo -en 'p{}\\ne' \\; " + "-exec readlink {}/exe \\; " + "-exec echo -n c \\; " + "-exec head -n 1 {}/cmdline \\; " + "-exec echo \\; " + "-exec echo __SKIP_ME__ \\;"; + + CommandLine cmd{procDir.withNewPath("find"), + {procDir.nativePath(), "-maxdepth", "1", "-type", "d", "-name", "[0-9]*"}}; + + cmd.addArgs(execs, CommandLine::Raw); + + Process procProcess; + procProcess.setCommand(cmd); + procProcess.runBlocking(); + QList processes; - const QString procDirPath = QLatin1String(procDirC); - const QDir procDir = QDir(QLatin1String(procDirC)); - const QStringList procIds = procDir.entryList(); - for (const QString &procId : procIds) { - if (!isUnixProcessId(procId)) - continue; - ProcessInfo proc; - proc.processId = procId.toInt(); - const QString root = procDirPath + procId; - const QFile exeFile(root + QLatin1String("/exe")); - proc.executable = exeFile.symLinkTarget(); + const auto lines = procProcess.readAllStandardOutput().split('\n'); + for (auto it = lines.begin(); it != lines.end(); ++it) { + if (it->startsWith('p')) { + ProcessInfo proc; + bool ok; + proc.processId = FilePath::fromUserInput(it->mid(1).trimmed()).fileName().toInt(&ok); + QTC_ASSERT(ok, continue); + ++it; - QFile cmdLineFile(root + QLatin1String("/cmdline")); - if (cmdLineFile.open(QIODevice::ReadOnly)) { // process may have exited - const QList tokens = cmdLineFile.readAll().split('\0'); - if (!tokens.isEmpty()) { - if (proc.executable.isEmpty()) - proc.executable = QString::fromLocal8Bit(tokens.front()); - for (const QByteArray &t : tokens) { - if (!proc.commandLine.isEmpty()) - proc.commandLine.append(QLatin1Char(' ')); - proc.commandLine.append(QString::fromLocal8Bit(t)); - } - } + QTC_ASSERT(it->startsWith('e'), continue); + proc.executable = it->mid(1).trimmed(); + ++it; + + QTC_ASSERT(it->startsWith('c'), continue); + proc.commandLine = it->mid(1).trimmed().replace('\0', ' '); + if (!proc.commandLine.contains("__SKIP_ME__")) + processes.append(proc); } - - if (proc.executable.isEmpty()) { - QFile statFile(root + QLatin1String("/stat")); - if (statFile.open(QIODevice::ReadOnly)) { - const QStringList data = QString::fromLocal8Bit(statFile.readAll()).split(QLatin1Char(' ')); - if (data.size() < 2) - continue; - proc.executable = data.at(1); - proc.commandLine = data.at(1); // PPID is element 3 - if (proc.executable.startsWith(QLatin1Char('(')) && proc.executable.endsWith(QLatin1Char(')'))) { - proc.executable.truncate(proc.executable.size() - 1); - proc.executable.remove(0, 1); - } - } - } - if (!proc.executable.isEmpty()) - processes.push_back(proc); } + return processes; } // Determine UNIX processes by running ps -static QMap getLocalProcessDataUsingPs(const QString &column) +static QMap getLocalProcessDataUsingPs(const FilePath &deviceRoot, + const QString &column) { - QtcProcess process; - process.setCommand({"ps", {"-e", "-o", "pid," + column}}); - process.start(); - if (!process.waitForFinished()) - return {}; + Process process; + process.setCommand({deviceRoot.withNewPath("ps"), {"-e", "-o", "pid," + column}}); + process.runBlocking(); // Split "457 /Users/foo.app arg1 arg2" - const QStringList lines = process.stdOut().split(QLatin1Char('\n')); + const QStringList lines = process.readAllStandardOutput().split(QLatin1Char('\n')); QMap result; for (int i = 1; i < lines.size(); ++i) { // Skip header const QString line = lines.at(i).trimmed(); @@ -118,14 +99,14 @@ static QMap getLocalProcessDataUsingPs(const QString &column) return result; } -static QList getLocalProcessesUsingPs() +static QList getLocalProcessesUsingPs(const FilePath &deviceRoot) { QList processes; // cmdLines are full command lines, usually with absolute path, // exeNames only the file part of the executable's path. - const QMap exeNames = getLocalProcessDataUsingPs("comm"); - const QMap cmdLines = getLocalProcessDataUsingPs("args"); + const QMap exeNames = getLocalProcessDataUsingPs(deviceRoot, "comm"); + const QMap cmdLines = getLocalProcessDataUsingPs(deviceRoot, "args"); for (auto it = exeNames.begin(), end = exeNames.end(); it != end; ++it) { const qint64 pid = it.key(); @@ -146,16 +127,68 @@ static QList getLocalProcessesUsingPs() return processes; } -QList ProcessInfo::processInfoList() +static QList getProcessesUsingPidin(const FilePath &pidin) { - const QDir procDir = QDir(QLatin1String(procDirC)); - return procDir.exists() ? getLocalProcessesUsingProc() : getLocalProcessesUsingPs(); + Process process; + process.setCommand({pidin, {"-F", "%a %A {/%n}"}}); + process.runBlocking(); + + QList processes; + QStringList lines = process.readAllStandardOutput().split(QLatin1Char('\n')); + if (lines.isEmpty()) + return processes; + + lines.pop_front(); // drop headers + const QRegularExpression re("\\s*(\\d+)\\s+(.*){(.*)}"); + + for (const QString &line : std::as_const(lines)) { + const QRegularExpressionMatch match = re.match(line); + if (match.hasMatch()) { + const QStringList captures = match.capturedTexts(); + if (captures.size() == 4) { + const int pid = captures[1].toInt(); + const QString args = captures[2]; + const QString exe = captures[3]; + ProcessInfo deviceProcess; + deviceProcess.processId = pid; + deviceProcess.executable = exe.trimmed(); + deviceProcess.commandLine = args.trimmed(); + processes.append(deviceProcess); + } + } + } + + return Utils::sorted(std::move(processes)); +} + +static QList processInfoListUnix(const FilePath &deviceRoot) +{ + const FilePath procDir = deviceRoot.withNewPath("/proc"); + const FilePath pidin = deviceRoot.withNewPath("pidin").searchInPath(); + + if (pidin.isExecutableFile()) + return getProcessesUsingPidin(pidin); + + if (procDir.isReadableDir()) + return getLocalProcessesUsingProc(procDir); + + return getLocalProcessesUsingPs(deviceRoot); +} + +#if defined(Q_OS_UNIX) + +QList ProcessInfo::processInfoList(const FilePath &deviceRoot) +{ + return processInfoListUnix(deviceRoot); } #elif defined(Q_OS_WIN) -QList ProcessInfo::processInfoList() +QList ProcessInfo::processInfoList(const FilePath &deviceRoot) { + if (deviceRoot.needsDevice()) + return processInfoListUnix(deviceRoot); + QList processes; PROCESSENTRY32 pe; diff --git a/src/libs/utils/processinfo.h b/src/libs/utils/processinfo.h index 47fd2d6d0de..90c1a97374d 100644 --- a/src/libs/utils/processinfo.h +++ b/src/libs/utils/processinfo.h @@ -5,6 +5,8 @@ #include "utils_global.h" +#include "filepath.h" + #include #include @@ -19,7 +21,7 @@ public: bool operator<(const ProcessInfo &other) const; - static QList processInfoList(); + static QList processInfoList(const Utils::FilePath &deviceRoot = Utils::FilePath()); }; } // namespace Utils diff --git a/src/libs/utils/processinterface.cpp b/src/libs/utils/processinterface.cpp index 9e13494cda1..a2dc746905c 100644 --- a/src/libs/utils/processinterface.cpp +++ b/src/libs/utils/processinterface.cpp @@ -7,6 +7,17 @@ namespace Utils { +namespace Pty { + +void Data::resize(const QSize &size) +{ + m_size = size; + if (m_data->m_handler) + m_data->m_handler(size); +} + +} // namespace Pty + /*! * \brief controlSignalToInt * \param controlSignal @@ -18,8 +29,10 @@ int ProcessInterface::controlSignalToInt(ControlSignal controlSignal) case ControlSignal::Terminate: return 15; case ControlSignal::Kill: return 9; case ControlSignal::Interrupt: return 2; - case ControlSignal::KickOff: QTC_CHECK(false); return 0; - case ControlSignal::CloseWriteChannel: QTC_CHECK(false); return 0; + case ControlSignal::KickOff: return 19; + case ControlSignal::CloseWriteChannel: + QTC_CHECK(false); + return 0; } return 0; } diff --git a/src/libs/utils/processinterface.h b/src/libs/utils/processinterface.h index d398d8fe139..a0460c5af3a 100644 --- a/src/libs/utils/processinterface.h +++ b/src/libs/utils/processinterface.h @@ -10,10 +10,38 @@ #include "processenums.h" #include +#include namespace Utils { -namespace Internal { class QtcProcessPrivate; } +namespace Internal { class ProcessPrivate; } + +namespace Pty { + +using ResizeHandler = std::function; + +class QTCREATOR_UTILS_EXPORT SharedData +{ +public: + ResizeHandler m_handler; +}; + +class QTCREATOR_UTILS_EXPORT Data +{ +public: + Data() : m_data(new SharedData) {} + + void setResizeHandler(const ResizeHandler &handler) { m_data->m_handler = handler; } + + QSize size() const { return m_size; } + void resize(const QSize &size); + +private: + QSize m_size{80, 60}; + QSharedPointer m_data; +}; + +} // namespace Pty class QTCREATOR_UTILS_EXPORT ProcessSetupData { @@ -22,6 +50,7 @@ public: ProcessMode m_processMode = ProcessMode::Reader; TerminalMode m_terminalMode = TerminalMode::Off; + std::optional m_ptyData; CommandLine m_commandLine; FilePath m_workingDirectory; Environment m_environment; @@ -39,6 +68,7 @@ public: bool m_unixTerminalDisabled = false; bool m_useCtrlCStub = false; bool m_belowNormalPriority = false; // internal, dependent on other fields and specific code path + bool m_createConsoleOnWindows = false; }; class QTCREATOR_UTILS_EXPORT ProcessResultData @@ -74,7 +104,7 @@ private: // - Done is being called in Starting or Running state. virtual bool waitForSignal(ProcessSignalType signalType, int msecs) = 0; - friend class Internal::QtcProcessPrivate; + friend class Internal::ProcessPrivate; }; class QTCREATOR_UTILS_EXPORT ProcessInterface : public QObject @@ -112,8 +142,8 @@ private: virtual ProcessBlockingInterface *processBlockingInterface() const { return nullptr; } - friend class QtcProcess; - friend class Internal::QtcProcessPrivate; + friend class Process; + friend class Internal::ProcessPrivate; }; } // namespace Utils diff --git a/src/libs/utils/processreaper.cpp b/src/libs/utils/processreaper.cpp index 117d5fcb33a..f7235d55290 100644 --- a/src/libs/utils/processreaper.cpp +++ b/src/libs/utils/processreaper.cpp @@ -33,7 +33,7 @@ never ending running process: It looks like when you call terminate() for the adb.exe, it won't stop, never, even after default 30 seconds timeout. The same happens for blocking processes tested in - tst_QtcProcess::killBlockingProcess(). It's hard to say whether any process on Windows can + tst_Process::killBlockingProcess(). It's hard to say whether any process on Windows can be finished by a call to terminate(). Until now, no such a process has been found. Further call to kill() (after a call to terminate()) finishes the process quickly. diff --git a/src/libs/utils/processutils.cpp b/src/libs/utils/processutils.cpp index c8394e37dd2..62ea514b658 100644 --- a/src/libs/utils/processutils.cpp +++ b/src/libs/utils/processutils.cpp @@ -45,16 +45,6 @@ void ProcessStartHandler::handleProcessStarted() } } -void ProcessStartHandler::setBelowNormalPriority() -{ -#ifdef Q_OS_WIN - m_process->setCreateProcessArgumentsModifier( - [](QProcess::CreateProcessArguments *args) { - args->flags |= BELOW_NORMAL_PRIORITY_CLASS; - }); -#endif // Q_OS_WIN -} - void ProcessStartHandler::setNativeArguments(const QString &arguments) { #ifdef Q_OS_WIN @@ -65,6 +55,29 @@ void ProcessStartHandler::setNativeArguments(const QString &arguments) #endif // Q_OS_WIN } +void ProcessStartHandler::setWindowsSpecificStartupFlags(bool belowNormalPriority, + bool createConsoleWindow) +{ +#ifdef Q_OS_WIN + if (!belowNormalPriority && !createConsoleWindow) + return; + + m_process->setCreateProcessArgumentsModifier( + [belowNormalPriority, createConsoleWindow](QProcess::CreateProcessArguments *args) { + if (createConsoleWindow) { + args->flags |= CREATE_NEW_CONSOLE; + args->startupInfo->dwFlags &= ~STARTF_USESTDHANDLES; + } + + if (belowNormalPriority) + args->flags |= BELOW_NORMAL_PRIORITY_CLASS; + }); +#else // Q_OS_WIN + Q_UNUSED(belowNormalPriority) + Q_UNUSED(createConsoleWindow) +#endif +} + #ifdef Q_OS_WIN static BOOL sendMessage(UINT message, HWND hwnd, LPARAM lParam) { @@ -94,7 +107,18 @@ ProcessHelper::ProcessHelper(QObject *parent) : QProcess(parent), m_processStartHandler(this) { #if defined(Q_OS_UNIX) - setChildProcessModifier([this] { setupChildProcess_impl(); }); + setChildProcessModifier([this] { + // nice value range is -20 to +19 where -20 is highest, 0 default and +19 is lowest + if (m_lowPriority) { + errno = 0; + if (::nice(5) == -1 && errno != 0) + perror("Failed to set nice value"); + } + + // Disable terminal by becoming a session leader. + if (m_unixTerminalDisabled) + setsid(); + }); #endif } @@ -140,20 +164,4 @@ void ProcessHelper::interruptProcess(QProcess *process) ProcessHelper::interruptPid(process->processId()); } -void ProcessHelper::setupChildProcess_impl() -{ -#if defined Q_OS_UNIX - // nice value range is -20 to +19 where -20 is highest, 0 default and +19 is lowest - if (m_lowPriority) { - errno = 0; - if (::nice(5) == -1 && errno != 0) - perror("Failed to set nice value"); - } - - // Disable terminal by becoming a session leader. - if (m_unixTerminalDisabled) - setsid(); -#endif -} - } // namespace Utils diff --git a/src/libs/utils/processutils.h b/src/libs/utils/processutils.h index 60161f83984..89202c0daf2 100644 --- a/src/libs/utils/processutils.h +++ b/src/libs/utils/processutils.h @@ -20,8 +20,8 @@ public: QIODevice::OpenMode openMode() const; void handleProcessStart(); void handleProcessStarted(); - void setBelowNormalPriority(); void setNativeArguments(const QString &arguments); + void setWindowsSpecificStartupFlags(bool belowNormalPriority, bool createConsoleWindow); private: ProcessMode m_processMode = ProcessMode::Reader; @@ -50,7 +50,6 @@ public: private: void terminateProcess(); - void setupChildProcess_impl(); bool m_lowPriority = false; bool m_unixTerminalDisabled = false; diff --git a/src/libs/utils/progressindicator.cpp b/src/libs/utils/progressindicator.cpp index 5e091917656..d1ce125dbee 100644 --- a/src/libs/utils/progressindicator.cpp +++ b/src/libs/utils/progressindicator.cpp @@ -185,11 +185,7 @@ void ProgressIndicatorPainter::nextAnimationStep() /*! Constructs a ProgressIndicator of the size \a size and with the parent \a parent. - Use \l attachToWidget to make the progress indicator automatically resize and center on the - parent widget. - - \sa attachToWidget - \sa setIndicatorSize + \sa setIndicatorSize() */ ProgressIndicator::ProgressIndicator(ProgressIndicatorSize size, QWidget *parent) : OverlayWidget(parent) @@ -204,7 +200,7 @@ ProgressIndicator::ProgressIndicator(ProgressIndicatorSize size, QWidget *parent /*! Changes the size of the progress indicator to \a size. - \sa indicatorSize + \sa ProgressIndicatorPainter::indicatorSize() */ void ProgressIndicator::setIndicatorSize(ProgressIndicatorSize size) { @@ -215,7 +211,7 @@ void ProgressIndicator::setIndicatorSize(ProgressIndicatorSize size) /*! Returns the size of the indicator in device independent pixels. - \sa indicatorSize + \sa ProgressIndicatorPainter::indicatorSize() */ QSize ProgressIndicator::sizeHint() const { diff --git a/src/libs/utils/projectintropage.cpp b/src/libs/utils/projectintropage.cpp index 8df64b97f06..dba9a801e4b 100644 --- a/src/libs/utils/projectintropage.cpp +++ b/src/libs/utils/projectintropage.cpp @@ -22,6 +22,7 @@ /*! \class Utils::ProjectIntroPage + \inmodule QtCreator \brief The ProjectIntroPage class is the standard wizard page for a project, letting the user choose its name @@ -66,7 +67,6 @@ ProjectIntroPage::ProjectIntroPage(QWidget *parent) : WizardPage(parent), d(new ProjectIntroPagePrivate) { - resize(355, 289); setTitle(Tr::tr("Introduction and Project Location")); d->m_descriptionLabel = new QLabel(this); diff --git a/src/libs/utils/qrcparser.h b/src/libs/utils/qrcparser.h index 42cd9b6666f..14352e26b8d 100644 --- a/src/libs/utils/qrcparser.h +++ b/src/libs/utils/qrcparser.h @@ -4,7 +4,8 @@ #pragma once #include "utils_global.h" -#include "utils/filepath.h" + +#include "filepath.h" #include #include diff --git a/src/libs/utils/qtcolorbutton.cpp b/src/libs/utils/qtcolorbutton.cpp index ca0aa631ba5..6a8537bd5fa 100644 --- a/src/libs/utils/qtcolorbutton.cpp +++ b/src/libs/utils/qtcolorbutton.cpp @@ -3,12 +3,15 @@ #include "qtcolorbutton.h" -#include +#include "theme/theme.h" + #include #include -#include -#include #include +#include +#include +#include +#include namespace Utils { @@ -157,51 +160,52 @@ bool QtColorButton::isDialogOpen() const void QtColorButton::paintEvent(QPaintEvent *event) { - QToolButton::paintEvent(event); - if (!isEnabled()) - return; + Q_UNUSED(event) - const int pixSize = 10; - QBrush br(d_ptr->shownColor()); - if (d_ptr->m_backgroundCheckered) { - QPixmap pm(2 * pixSize, 2 * pixSize); - QPainter pmp(&pm); - pmp.fillRect(0, 0, pixSize, pixSize, Qt::white); - pmp.fillRect(pixSize, pixSize, pixSize, pixSize, Qt::white); - pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::black); - pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::black); - pmp.fillRect(0, 0, 2 * pixSize, 2 * pixSize, d_ptr->shownColor()); - br = QBrush(pm); - } + constexpr Theme::Color overlayColor = Theme::TextColorNormal; + constexpr qreal overlayOpacity = 0.25; QPainter p(this); - const int corr = 5; - QRect r = rect().adjusted(corr, corr, -corr, -corr); - p.setBrushOrigin((r.width() % pixSize + pixSize) / 2 + corr, (r.height() % pixSize + pixSize) / 2 + corr); - p.fillRect(r, br); + const QColor color = d_ptr->shownColor(); + if (!color.isValid()) { + constexpr int size = 11; + const qreal horPadding = (width() - size) / 2.0; + const qreal verPadding = (height() - size) / 2.0; + const QPen pen(creatorTheme()->color(overlayColor), 2); - //const int adjX = qRound(r.width() / 4.0); - //const int adjY = qRound(r.height() / 4.0); - //p.fillRect(r.adjusted(adjX, adjY, -adjX, -adjY), - // QColor(d_ptr->shownColor().rgb())); - /* - p.fillRect(r.adjusted(0, r.height() * 3 / 4, 0, 0), - QColor(d_ptr->shownColor().rgb())); - p.fillRect(r.adjusted(0, 0, 0, -r.height() * 3 / 4), - QColor(d_ptr->shownColor().rgb())); - */ - /* - const QColor frameColor0(0, 0, 0, qRound(0.2 * (0xFF - d_ptr->shownColor().alpha()))); - p.setPen(frameColor0); - p.drawRect(r.adjusted(adjX, adjY, -adjX - 1, -adjY - 1)); - */ + p.save(); + p.setOpacity(overlayOpacity); + p.setPen(pen); + p.setRenderHint(QPainter::Antialiasing); + p.drawLine(QLineF(horPadding, height() - verPadding, width() - horPadding, verPadding)); + p.restore(); + } else if (isEnabled()) { + QBrush br(color); + if (d_ptr->m_backgroundCheckered) { + const int pixSize = 10; + QPixmap pm(2 * pixSize, 2 * pixSize); + pm.fill(Qt::white); + QPainter pmp(&pm); + pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::black); + pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::black); + pmp.fillRect(pm.rect(), color); + br = QBrush(pm); + p.setBrushOrigin((width() - pixSize) / 2, (height() - pixSize) / 2); + } + p.fillRect(rect(), br); + } - const QColor frameColor1(0, 0, 0, 26); - p.setPen(frameColor1); - p.drawRect(r.adjusted(1, 1, -2, -2)); - const QColor frameColor2(0, 0, 0, 51); - p.setPen(frameColor2); - p.drawRect(r.adjusted(0, 0, -1, -1)); + if (hasFocus()) { + QPen pen; + pen.setBrush(Qt::white); + pen.setStyle(Qt::DotLine); + p.setPen(pen); + p.setCompositionMode(QPainter::CompositionMode_Difference); + } else { + p.setPen(creatorTheme()->color(overlayColor)); + p.setOpacity(overlayOpacity); + } + p.drawRect(rect().adjusted(0, 0, -1, -1)); } void QtColorButton::mousePressEvent(QMouseEvent *event) diff --git a/src/libs/utils/runextensions.h b/src/libs/utils/runextensions.h index f81eb56699c..6505b12a9cf 100644 --- a/src/libs/utils/runextensions.h +++ b/src/libs/utils/runextensions.h @@ -476,113 +476,4 @@ runAsync(QThreadPool *pool, Function &&function, Args&&... args) std::forward(args)...); } - -/*! - Adds a handler for when a result is ready. - This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions - or create a QFutureWatcher already for other reasons. -*/ -template -const QFuture &onResultReady(const QFuture &future, R *receiver, void(R::*member)(const T &)) -{ - auto watcher = new QFutureWatcher(); - QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); - QObject::connect(watcher, &QFutureWatcherBase::resultReadyAt, receiver, - [receiver, member, watcher](int index) { - (receiver->*member)(watcher->future().resultAt(index)); - }); - watcher->setFuture(future); - return future; -} - -/*! - Adds a handler for when a result is ready. The guard object determines the lifetime of - the connection. - This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions - or create a QFutureWatcher already for other reasons. -*/ -template -const QFuture &onResultReady(const QFuture &future, QObject *guard, Function f) -{ - auto watcher = new QFutureWatcher(); - QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); - QObject::connect(watcher, &QFutureWatcherBase::resultReadyAt, guard, [f, watcher](int index) { - f(watcher->future().resultAt(index)); - }); - watcher->setFuture(future); - return future; -} - -/*! - Adds a handler for when a result is ready. - This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions - or create a QFutureWatcher already for other reasons. -*/ -template -const QFuture &onResultReady(const QFuture &future, Function f) -{ - auto watcher = new QFutureWatcher(); - QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); - QObject::connect(watcher, &QFutureWatcherBase::resultReadyAt, [f, watcher](int index) { - f(watcher->future().resultAt(index)); - }); - watcher->setFuture(future); - return future; -} - -/*! - Adds a handler for when the future is finished. - This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions - or create a QFutureWatcher already for other reasons. -*/ -template -const QFuture &onFinished(const QFuture &future, - R *receiver, - void (R::*member)(const QFuture &)) -{ - auto watcher = new QFutureWatcher(); - QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); - QObject::connect(watcher, - &QFutureWatcherBase::finished, - receiver, - [receiver, member, watcher] { (receiver->*member)(watcher->future()); }); - watcher->setFuture(future); - return future; -} - -/*! - Adds a handler for when the future is finished. The guard object determines the lifetime of - the connection. - This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions - or create a QFutureWatcher already for other reasons. -*/ -template -const QFuture &onFinished(const QFuture &future, QObject *guard, Function f) -{ - auto watcher = new QFutureWatcher(); - QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); - QObject::connect(watcher, &QFutureWatcherBase::finished, guard, [f, watcher] { - f(watcher->future()); - }); - watcher->setFuture(future); - return future; -} - -/*! - Adds a handler for when the future is finished. - This creates a new QFutureWatcher. Do not use if you intend to react on multiple conditions - or create a QFutureWatcher already for other reasons. -*/ -template -const QFuture &onFinished(const QFuture &future, Function f) -{ - auto watcher = new QFutureWatcher(); - QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); - QObject::connect(watcher, &QFutureWatcherBase::finished, [f, watcher] { - f(watcher->future()); - }); - watcher->setFuture(future); - return future; -} - } // namespace Utils diff --git a/src/libs/utils/scopedtimer.cpp b/src/libs/utils/scopedtimer.cpp new file mode 100644 index 00000000000..6a4522e28d0 --- /dev/null +++ b/src/libs/utils/scopedtimer.cpp @@ -0,0 +1,67 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "scopedtimer.h" + +#include +#include + +#include + +namespace Utils { + +static QString currentTime() { return QTime::currentTime().toString(Qt::ISODateWithMs); } + +using namespace std::chrono; + +static const char s_scoped[] = "SCOPED TIMER"; +static const char s_scopedCumulative[] = "STATIC SCOPED TIMER"; + +class ScopedTimerPrivate +{ +public: + QString header() const { + const char *scopedTimerType = m_data.m_cumulative ? s_scopedCumulative : s_scoped; + const QString prefix = QLatin1String(scopedTimerType) + " [" + currentTime() + "] "; + const QString infix = m_data.m_message.isEmpty() + ? QLatin1String(m_data.m_fileName) + ':' + QString::number(m_data.m_line) + : m_data.m_message; + return prefix + infix + ' '; + } + + const ScopedTimerData m_data; + const time_point m_start = system_clock::now(); +}; + +ScopedTimer::ScopedTimer(const ScopedTimerData &data) + : d(new ScopedTimerPrivate{data}) +{ + if (d->m_data.m_cumulative) + return; + qDebug().noquote().nospace() << d->header() << "started"; +} + +static int64_t toMs(int64_t ns) { return ns / 1000000; } + +ScopedTimer::~ScopedTimer() +{ + const auto elapsed = duration_cast(system_clock::now() - d->m_start); + QString suffix; + if (d->m_data.m_cumulative) { + const int64_t nsOld = d->m_data.m_cumulative->fetch_add(elapsed.count()); + const int64_t msOld = toMs(nsOld); + const int64_t nsNew = nsOld + elapsed.count(); + const int64_t msNew = toMs(nsNew); + // Always report the first hit, and later, as not to clog the debug output, + // only at 10ms resolution. + if (nsOld != 0 && msOld / 10 == msNew / 10) + return; + + suffix = "cumulative timeout: " + QString::number(msNew) + "ms"; + } else { + suffix = "stopped with timeout: " + QString::number(toMs(elapsed.count())) + "ms"; + } + qDebug().noquote().nospace() << d->header() << suffix; +} + +} // namespace Utils diff --git a/src/libs/utils/scopedtimer.h b/src/libs/utils/scopedtimer.h new file mode 100644 index 00000000000..d66ef158419 --- /dev/null +++ b/src/libs/utils/scopedtimer.h @@ -0,0 +1,52 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "utils_global.h" + +#include + +#include +#include + +namespace Utils { + +class ScopedTimerPrivate; + +class QTCREATOR_UTILS_EXPORT ScopedTimerData +{ +public: + QString m_message; + const char *m_fileName = nullptr; + int m_line = 0; + std::atomic *m_cumulative = nullptr; +}; + +class QTCREATOR_UTILS_EXPORT ScopedTimer +{ +public: + ScopedTimer(const ScopedTimerData &data); + ~ScopedTimer(); + +private: + std::unique_ptr d; +}; + +} // Utils + +// The "message" argument of QTC_SCOPED_TIMER() and QTC_STATIC_SCOPED_TIMER() macros is optional. +// When provided, it should evaluate to QString. + +#define QTC_CONCAT_HELPER(x, y) x ## y +#define QTC_CONCAT(x, y) QTC_CONCAT_HELPER(x, y) +#define QTC_SCOPED_TIMER(message) ::Utils::ScopedTimer QTC_CONCAT(_qtc_scoped_timer_, __LINE__)\ +({{message}, __FILE__, __LINE__}) + +// The macro below expands as follows (in one line): +// static std::atomic _qtc_static_scoped_timer___LINE__ = 0; +// ScopedTimer _qtc_scoped_timer___LINE__(__FILE__, __LINE__, &_qtc_static_scoped_timer___LINE__) +#define QTC_STATIC_SCOPED_TIMER(message) static std::atomic \ +QTC_CONCAT(_qtc_static_scoped_timer_, __LINE__) = 0; \ +::Utils::ScopedTimer QTC_CONCAT(_qtc_scoped_timer_, __LINE__)\ +({{message}, __FILE__, __LINE__, &QTC_CONCAT(_qtc_static_scoped_timer_, __LINE__)}) diff --git a/src/libs/utils/searchresultitem.cpp b/src/libs/utils/searchresultitem.cpp new file mode 100644 index 00000000000..2574473c933 --- /dev/null +++ b/src/libs/utils/searchresultitem.cpp @@ -0,0 +1,64 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "searchresultitem.h" + +namespace Utils { + +SearchResultColor::SearchResultColor(const QColor &textBg, const QColor &textFg, + const QColor &highlightBg, const QColor &highlightFg, + const QColor &functionBg, const QColor &functionFg) + : textBackground(textBg) + , textForeground(textFg) + , highlightBackground(highlightBg) + , highlightForeground(highlightFg) + , containingFunctionBackground(functionBg) + , containingFunctionForeground(functionFg) +{ + if (!highlightBackground.isValid()) + highlightBackground = textBackground; + if (!highlightForeground.isValid()) + highlightForeground = textForeground; + if (!containingFunctionBackground.isValid()) + containingFunctionBackground = textBackground; + if (!containingFunctionForeground.isValid()) + containingFunctionForeground = textForeground; +} + +static QString displayText(const QString &line) +{ + QString result = line; + auto end = result.end(); + for (auto it = result.begin(); it != end; ++it) { + if (!it->isSpace() && !it->isPrint()) + *it = QChar('?'); + } + return result; +} + +void SearchResultItem::setDisplayText(const QString &text) +{ + setLineText(displayText(text)); +} + +void SearchResultItem::setMainRange(int line, int column, int length) +{ + m_mainRange = {{line, column}, {line, column + length}}; +} + +QTCREATOR_UTILS_EXPORT size_t qHash(SearchResultColor::Style style, uint seed) +{ + int a = int(style); + return ::qHash(a, seed); +} + +bool SearchResultItem::operator==(const SearchResultItem &other) const +{ + return m_path == other.m_path && m_lineText == other.m_lineText + && m_userData == other.m_userData && m_mainRange == other.m_mainRange + && m_useTextEditorFont == other.m_useTextEditorFont + && m_selectForReplacement == other.m_selectForReplacement && m_style == other.m_style + && m_containingFunctionName == other.m_containingFunctionName; +} + +} // namespace Utils diff --git a/src/libs/utils/searchresultitem.h b/src/libs/utils/searchresultitem.h new file mode 100644 index 00000000000..ea9d0332d5a --- /dev/null +++ b/src/libs/utils/searchresultitem.h @@ -0,0 +1,102 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "utils_global.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace Utils { + +class QTCREATOR_UTILS_EXPORT SearchResultColor +{ +public: + enum class Style { Default, Alt1, Alt2 }; + + SearchResultColor() = default; + SearchResultColor(const QColor &textBg, const QColor &textFg, + const QColor &highlightBg, const QColor &highlightFg, + const QColor &functionBg, const QColor &functionFg); + + QColor textBackground; + QColor textForeground; + QColor highlightBackground; + QColor highlightForeground; + QColor containingFunctionBackground; + QColor containingFunctionForeground; + +private: + QTCREATOR_UTILS_EXPORT friend size_t qHash(Style style, uint seed); +}; + +using SearchResultColors = QHash; + +class QTCREATOR_UTILS_EXPORT SearchResultItem +{ +public: + QStringList path() const { return m_path; } + void setPath(const QStringList &path) { m_path = path; } + void setFilePath(const Utils::FilePath &filePath) { m_path = {filePath.toUserOutput()}; } + + QString lineText() const { return m_lineText; } + void setLineText(const QString &text) { m_lineText = text; } + void setDisplayText(const QString &text); + + QIcon icon() const { return m_icon; } + void setIcon(const QIcon &icon) { m_icon = icon; } + + QVariant userData() const { return m_userData; } + void setUserData(const QVariant &userData) { m_userData = userData; } + + Text::Range mainRange() const { return m_mainRange; } + void setMainRange(const Text::Range &mainRange) { m_mainRange = mainRange; } + void setMainRange(int line, int column, int length); + + bool useTextEditorFont() const { return m_useTextEditorFont; } + void setUseTextEditorFont(bool useTextEditorFont) { m_useTextEditorFont = useTextEditorFont; } + + SearchResultColor::Style style() const { return m_style; } + void setStyle(SearchResultColor::Style style) { m_style = style; } + + bool selectForReplacement() const { return m_selectForReplacement; } + void setSelectForReplacement(bool select) { m_selectForReplacement = select; } + + std::optional containingFunctionName() const { return m_containingFunctionName; } + + void setContainingFunctionName(const std::optional &containingFunctionName) + { + m_containingFunctionName = containingFunctionName; + } + + bool operator==(const SearchResultItem &other) const; + bool operator!=(const SearchResultItem &other) const { return !(operator==(other)); } + +private: + QStringList m_path; // hierarchy to the parent item of this item + QString m_lineText; // text to show for the item itself + QIcon m_icon; // icon to show in front of the item (by be null icon to hide) + QVariant m_userData; // user data for identification of the item + Text::Range m_mainRange; + bool m_useTextEditorFont = false; + bool m_selectForReplacement = true; + SearchResultColor::Style m_style = SearchResultColor::Style::Default; + std::optional m_containingFunctionName; +}; + +using SearchResultItems = QList; + +} // namespace Utils + +Q_DECLARE_METATYPE(Utils::SearchResultItem) +Q_DECLARE_METATYPE(Utils::SearchResultItems) diff --git a/src/libs/utils/settingsaccessor.cpp b/src/libs/utils/settingsaccessor.cpp index dc3cd0711e4..275290674fd 100644 --- a/src/libs/utils/settingsaccessor.cpp +++ b/src/libs/utils/settingsaccessor.cpp @@ -39,17 +39,7 @@ QMessageBox::StandardButtons SettingsAccessor::Issue::allButtons() const /*! * The SettingsAccessor can be used to read/write settings in XML format. */ -SettingsAccessor::SettingsAccessor(const QString &docType, - const QString &displayName, - const QString &applicationDisplayName) : -docType(docType), -displayName(displayName), -applicationDisplayName(applicationDisplayName) -{ - QTC_CHECK(!docType.isEmpty()); - QTC_CHECK(!displayName.isEmpty()); - QTC_CHECK(!applicationDisplayName.isEmpty()); -} +SettingsAccessor::SettingsAccessor() = default; SettingsAccessor::~SettingsAccessor() = default; @@ -68,6 +58,9 @@ QVariantMap SettingsAccessor::restoreSettings(QWidget *parent) const */ bool SettingsAccessor::saveSettings(const QVariantMap &data, QWidget *parent) const { + QTC_CHECK(!m_docType.isEmpty()); + QTC_CHECK(!m_applicationDisplayName.isEmpty()); + const std::optional result = writeData(m_baseFilePath, data, parent); const ProceedInfo pi = result ? reportIssues(result.value(), m_baseFilePath, parent) : ProceedInfo::Continue; @@ -99,6 +92,9 @@ std::optional SettingsAccessor::writeData(const FilePat QVariantMap SettingsAccessor::restoreSettings(const FilePath &settingsPath, QWidget *parent) const { + QTC_CHECK(!m_docType.isEmpty()); + QTC_CHECK(!m_applicationDisplayName.isEmpty()); + const RestoreData result = readData(settingsPath, parent); const ProceedInfo pi = result.hasIssue() ? reportIssues(result.issue.value(), result.path, parent) @@ -123,7 +119,7 @@ SettingsAccessor::RestoreData SettingsAccessor::readFile(const FilePath &path) c const QVariantMap data = reader.restoreValues(); if (!m_readOnly && path == m_baseFilePath) { if (!m_writer) - m_writer = std::make_unique(m_baseFilePath, docType); + m_writer = std::make_unique(m_baseFilePath, m_docType); m_writer->setContents(data); } @@ -146,7 +142,7 @@ std::optional SettingsAccessor::writeFile(const FilePat QString errorMessage; if (!m_readOnly && (!m_writer || m_writer->fileName() != path)) - m_writer = std::make_unique(path, docType); + m_writer = std::make_unique(path, m_docType); if (!m_writer->save(data, &errorMessage)) { return Issue(Tr::tr("Failed to Write File"), @@ -156,8 +152,7 @@ std::optional SettingsAccessor::writeFile(const FilePat } SettingsAccessor::ProceedInfo -SettingsAccessor::reportIssues(const SettingsAccessor::Issue &issue, const FilePath &path, - QWidget *parent) +SettingsAccessor::reportIssues(const Issue &issue, const FilePath &path, QWidget *parent) { if (!path.exists()) return Continue; @@ -226,18 +221,8 @@ std::optional BackUpStrategy::backupName(const QVariantMap &oldData, return path.stringAppended(".bak"); } -BackingUpSettingsAccessor::BackingUpSettingsAccessor(const QString &docType, - const QString &displayName, - const QString &applicationDisplayName) : - BackingUpSettingsAccessor(std::make_unique(), docType, displayName, applicationDisplayName) -{ } - -BackingUpSettingsAccessor::BackingUpSettingsAccessor(std::unique_ptr &&strategy, - const QString &docType, - const QString &displayName, - const QString &applicationDisplayName) : - SettingsAccessor(docType, displayName, applicationDisplayName), - m_strategy(std::move(strategy)) +BackingUpSettingsAccessor::BackingUpSettingsAccessor() + : m_strategy(std::make_unique()) { } SettingsAccessor::RestoreData @@ -259,7 +244,7 @@ BackingUpSettingsAccessor::readData(const FilePath &path, QWidget *parent) const "for instance because they were written by an incompatible " "version of %2, or because a different settings path " "was used.

") - .arg(path.toUserOutput(), applicationDisplayName), Issue::Type::ERROR); + .arg(path.toUserOutput(), m_applicationDisplayName), Issue::Type::ERROR); i.buttons.insert(QMessageBox::Ok, DiscardAndContinue); result.issue = i; } @@ -279,6 +264,11 @@ std::optional BackingUpSettingsAccessor::writeData(cons return SettingsAccessor::writeData(path, data, parent); } +void BackingUpSettingsAccessor::setStrategy(std::unique_ptr &&strategy) +{ + m_strategy = std::move(strategy); +} + FilePaths BackingUpSettingsAccessor::readFileCandidates(const FilePath &path) const { FilePaths result = filteredUnique(m_strategy->readFileCandidates(path)); @@ -410,19 +400,10 @@ QVariantMap VersionUpgrader::renameKeys(const QList &changes, QVariantMa * The UpgradingSettingsAccessor keeps version information in the settings file and will * upgrade the settings on load to the latest supported version (if possible). */ -UpgradingSettingsAccessor::UpgradingSettingsAccessor(const QString &docType, - const QString &displayName, - const QString &applicationDisplayName) : - UpgradingSettingsAccessor(std::make_unique(this), docType, - displayName, applicationDisplayName) -{ } - -UpgradingSettingsAccessor::UpgradingSettingsAccessor(std::unique_ptr &&strategy, - const QString &docType, - const QString &displayName, - const QString &applicationDisplayName) : - BackingUpSettingsAccessor(std::move(strategy), docType, displayName, applicationDisplayName) -{ } +UpgradingSettingsAccessor::UpgradingSettingsAccessor() +{ + setStrategy(std::make_unique(this)); +} int UpgradingSettingsAccessor::currentVersion() const { @@ -541,7 +522,7 @@ UpgradingSettingsAccessor::validateVersionRange(const RestoreData &data) const "version of %2 was used are ignored, and " "changes made now will not be propagated to " "the newer version.

") - .arg(result.path.toUserOutput(), applicationDisplayName), Issue::Type::WARNING); + .arg(result.path.toUserOutput(), m_applicationDisplayName), Issue::Type::WARNING); i.buttons.insert(QMessageBox::Ok, Continue); result.issue = i; return result; @@ -550,13 +531,13 @@ UpgradingSettingsAccessor::validateVersionRange(const RestoreData &data) const const QByteArray readId = settingsIdFromMap(result.data); if (!settingsId().isEmpty() && !readId.isEmpty() && readId != settingsId()) { Issue i(Tr::tr("Settings File for \"%1\" from a Different Environment?") - .arg(applicationDisplayName), + .arg(m_applicationDisplayName), Tr::tr("

No settings file created by this instance " "of %1 was found.

" "

Did you work with this project on another machine or " "using a different settings path before?

" "

Do you still want to load the settings file \"%2\"?

") - .arg(applicationDisplayName, result.path.toUserOutput()), Issue::Type::WARNING); + .arg(m_applicationDisplayName, result.path.toUserOutput()), Issue::Type::WARNING); i.defaultButton = QMessageBox::No; i.escapeButton = QMessageBox::No; i.buttons.clear(); @@ -577,12 +558,7 @@ UpgradingSettingsAccessor::validateVersionRange(const RestoreData &data) const * MergingSettingsAccessor allows to merge secondary settings into the main settings. * This is useful to e.g. handle .shared files together with .user files. */ -MergingSettingsAccessor::MergingSettingsAccessor(std::unique_ptr &&strategy, - const QString &docType, - const QString &displayName, - const QString &applicationDisplayName) : - UpgradingSettingsAccessor(std::move(strategy), docType, displayName, applicationDisplayName) -{ } +MergingSettingsAccessor::MergingSettingsAccessor() = default; SettingsAccessor::RestoreData MergingSettingsAccessor::readData(const FilePath &path, QWidget *parent) const @@ -614,7 +590,7 @@ SettingsAccessor::RestoreData MergingSettingsAccessor::readData(const FilePath & secondaryData.issue = Issue(Tr::tr("Unsupported Merge Settings File"), Tr::tr("\"%1\" is not supported by %2. " "Do you want to try loading it anyway?") - .arg(secondaryData.path.toUserOutput(), applicationDisplayName), + .arg(secondaryData.path.toUserOutput(), m_applicationDisplayName), Issue::Type::WARNING); secondaryData.issue->buttons.clear(); secondaryData.issue->buttons.insert(QMessageBox::Yes, Continue); diff --git a/src/libs/utils/settingsaccessor.h b/src/libs/utils/settingsaccessor.h index f1bbb3594e8..683743ac62f 100644 --- a/src/libs/utils/settingsaccessor.h +++ b/src/libs/utils/settingsaccessor.h @@ -51,8 +51,7 @@ using SettingsMergeResult = std::optional>; class QTCREATOR_UTILS_EXPORT SettingsAccessor { public: - SettingsAccessor(const QString &docType, const QString &displayName, - const QString &applicationDisplayName); + SettingsAccessor(); virtual ~SettingsAccessor(); enum ProceedInfo { Continue, DiscardAndContinue }; @@ -95,10 +94,6 @@ public: QVariantMap restoreSettings(QWidget *parent) const; bool saveSettings(const QVariantMap &data, QWidget *parent) const; - const QString docType; - const QString displayName; - const QString applicationDisplayName; - void setBaseFilePath(const FilePath &baseFilePath) { m_baseFilePath = baseFilePath; } void setReadOnly() { m_readOnly = true; } FilePath baseFilePath() const { return m_baseFilePath; } @@ -108,6 +103,9 @@ public: const QVariantMap &data, QWidget *parent) const; + void setDocType(const QString &docType) { m_docType = docType; } + void setApplicationDisplayName(const QString &name) { m_applicationDisplayName = name; } + protected: // Report errors: QVariantMap restoreSettings(const FilePath &settingsPath, QWidget *parent) const; @@ -119,6 +117,9 @@ protected: virtual RestoreData readFile(const FilePath &path) const; virtual std::optional writeFile(const FilePath &path, const QVariantMap &data) const; + QString m_docType; + QString m_applicationDisplayName; + private: FilePath m_baseFilePath; mutable std::unique_ptr m_writer; @@ -148,10 +149,7 @@ public: class QTCREATOR_UTILS_EXPORT BackingUpSettingsAccessor : public SettingsAccessor { public: - BackingUpSettingsAccessor(const QString &docType, const QString &displayName, - const QString &applicationDisplayName); - BackingUpSettingsAccessor(std::unique_ptr &&strategy, const QString &docType, - const QString &displayName, const QString &applicationDisplayName); + BackingUpSettingsAccessor(); RestoreData readData(const FilePath &path, QWidget *parent) const override; std::optional writeData(const FilePath &path, @@ -159,6 +157,7 @@ public: QWidget *parent) const override; BackUpStrategy *strategy() const { return m_strategy.get(); } + void setStrategy(std::unique_ptr &&strategy); private: FilePaths readFileCandidates(const FilePath &path) const; @@ -220,10 +219,7 @@ class MergingSettingsAccessor; class QTCREATOR_UTILS_EXPORT UpgradingSettingsAccessor : public BackingUpSettingsAccessor { public: - UpgradingSettingsAccessor(const QString &docType, - const QString &displayName, const QString &applicationDisplayName); - UpgradingSettingsAccessor(std::unique_ptr &&strategy, const QString &docType, - const QString &displayName, const QString &appDisplayName); + UpgradingSettingsAccessor(); int currentVersion() const; int firstSupportedVersion() const; @@ -263,9 +259,7 @@ public: QString key; }; - MergingSettingsAccessor(std::unique_ptr &&strategy, - const QString &docType, const QString &displayName, - const QString &applicationDisplayName); + MergingSettingsAccessor(); RestoreData readData(const FilePath &path, QWidget *parent) const final; diff --git a/src/libs/utils/statuslabel.cpp b/src/libs/utils/statuslabel.cpp index 32db235b66c..26d5a5b3614 100644 --- a/src/libs/utils/statuslabel.cpp +++ b/src/libs/utils/statuslabel.cpp @@ -7,6 +7,7 @@ /*! \class Utils::StatusLabel + \inmodule QtCreator \brief The StatusLabel class displays messages for a while with a timeout. */ diff --git a/src/libs/utils/stringtable.cpp b/src/libs/utils/stringtable.cpp index 6345b80c32e..73cad02588a 100644 --- a/src/libs/utils/stringtable.cpp +++ b/src/libs/utils/stringtable.cpp @@ -3,7 +3,7 @@ #include "stringtable.h" -#include "runextensions.h" +#include "async.h" #include #include @@ -34,7 +34,7 @@ public: void cancelAndWait(); QString insert(const QString &string); void startGC(); - void GC(QFutureInterface &futureInterface); + void GC(QPromise &promise); QFuture m_future; QMutex m_lock; @@ -90,7 +90,7 @@ void StringTablePrivate::startGC() { QMutexLocker locker(&m_lock); cancelAndWait(); - m_future = Utils::runAsync(&StringTablePrivate::GC, this); + m_future = Utils::asyncRun(&StringTablePrivate::GC, this); } QTCREATOR_UTILS_EXPORT void scheduleGC() @@ -113,7 +113,7 @@ static inline bool isQStringInUse(const QString &string) return data_ptr->isShared() || !data_ptr->isMutable() /* QStringLiteral ? */; } -void StringTablePrivate::GC(QFutureInterface &futureInterface) +void StringTablePrivate::GC(QPromise &promise) { int initialSize = 0; bytesSaved = 0; @@ -125,7 +125,7 @@ void StringTablePrivate::GC(QFutureInterface &futureInterface) // Collect all QStrings which have refcount 1. (One reference in m_strings and nowhere else.) for (QSet::iterator i = m_strings.begin(); i != m_strings.end();) { - if (futureInterface.isCanceled()) + if (promise.isCanceled()) return; if (!isQStringInUse(*i)) diff --git a/src/libs/utils/stringutils.cpp b/src/libs/utils/stringutils.cpp index 3a1afe1fa18..a33a08e3d99 100644 --- a/src/libs/utils/stringutils.cpp +++ b/src/libs/utils/stringutils.cpp @@ -3,10 +3,9 @@ #include "stringutils.h" -#include "algorithm.h" -#include "hostosinfo.h" -#include "qtcassert.h" #include "filepath.h" +#include "qtcassert.h" +#include "theme/theme.h" #include "utilstr.h" #ifdef QT_WIDGETS_LIB @@ -15,11 +14,14 @@ #endif #include +#include #include #include #include #include #include +#include +#include #include #include @@ -456,7 +458,7 @@ QTCREATOR_UTILS_EXPORT QString normalizeNewlines(const QString &text) /*! Joins all the not empty string list's \a strings into a single string with each element - separated by the given separator (which can be an empty string). + separated by the given \a separator (which can be an empty string). */ QTCREATOR_UTILS_EXPORT QString joinStrings(const QStringList &strings, QChar separator) { @@ -527,4 +529,101 @@ QTCREATOR_UTILS_EXPORT FilePath appendHelper(const FilePath &base, int n) return base.stringAppended(QString::number(n)); } +QTCREATOR_UTILS_EXPORT QPair splitAtFirst(const QStringView &stringView, + QChar ch) +{ + int splitIdx = stringView.indexOf(ch); + if (splitIdx == -1) + return {stringView, {}}; + + QStringView left = stringView.mid(0, splitIdx); + QStringView right = stringView.mid(splitIdx + 1); + + return {left, right}; +} + +QTCREATOR_UTILS_EXPORT QPair splitAtFirst(const QString &string, QChar ch) +{ + QStringView view = string; + return splitAtFirst(view, ch); +} + +QTCREATOR_UTILS_EXPORT int endOfNextWord(const QString &string, int position) +{ + QTC_ASSERT(string.size() > position, return -1); + + static const QString wordSeparators = QStringLiteral(" \t\n\r()[]{}<>"); + + const auto predicate = [](const QChar &c) { return wordSeparators.contains(c); }; + + auto it = string.begin() + position; + if (predicate(*it)) + it = std::find_if_not(it, string.end(), predicate); + + if (it == string.end()) + return -1; + + it = std::find_if(it, string.end(), predicate); + if (it == string.end()) + return -1; + + return std::distance(string.begin(), it); +} + +MarkdownHighlighter::MarkdownHighlighter(QTextDocument *parent) + : QSyntaxHighlighter(parent) + , h2Brush(Qt::NoBrush) +{ + parent->setIndentWidth(30); // default value is 40 +} + +void MarkdownHighlighter::highlightBlock(const QString &text) +{ + if (text.isEmpty()) + return; + + QTextBlockFormat fmt = currentBlock().blockFormat(); + QTextCursor cur(currentBlock()); + if (fmt.hasProperty(QTextFormat::HeadingLevel)) { + fmt.setTopMargin(10); + fmt.setBottomMargin(10); + + // Draw an underline for Heading 2, by creating a texture brush + // with the last pixel visible + if (fmt.property(QTextFormat::HeadingLevel) == 2) { + QTextCharFormat charFmt = currentBlock().charFormat(); + charFmt.setBaselineOffset(15); + setFormat(0, text.length(), charFmt); + + if (h2Brush.style() == Qt::NoBrush) { + const int height = QFontMetrics(charFmt.font()).height(); + QImage image(1, height, QImage::Format_ARGB32); + + image.fill(QColor(0, 0, 0, 0).rgba()); + image.setPixel(0, + height - 1, + Utils::creatorTheme()->color(Theme::TextColorDisabled).rgba()); + + h2Brush = QBrush(image); + } + fmt.setBackground(h2Brush); + } + cur.setBlockFormat(fmt); + } else if (fmt.hasProperty(QTextFormat::BlockCodeLanguage) && fmt.indent() == 0) { + // set identation for code blocks + fmt.setIndent(1); + cur.setBlockFormat(fmt); + } + + // Show the bulet points as filled circles + QTextList *list = cur.currentList(); + if (list) { + QTextListFormat listFmt = list->format(); + if (listFmt.indent() == 1 && listFmt.style() == QTextListFormat::ListCircle) { + listFmt.setStyle(QTextListFormat::ListDisc); + list->setFormat(listFmt); + } + } +} + } // namespace Utils diff --git a/src/libs/utils/stringutils.h b/src/libs/utils/stringutils.h index 5cf2978c07b..3bab6110cf3 100644 --- a/src/libs/utils/stringutils.h +++ b/src/libs/utils/stringutils.h @@ -5,8 +5,10 @@ #include "utils_global.h" +#include #include #include +#include #include @@ -115,4 +117,20 @@ QTCREATOR_UTILS_EXPORT QString trimFront(const QString &string, QChar ch); QTCREATOR_UTILS_EXPORT QString trimBack(const QString &string, QChar ch); QTCREATOR_UTILS_EXPORT QString trim(const QString &string, QChar ch); +QTCREATOR_UTILS_EXPORT QPair splitAtFirst(const QString &string, QChar ch); +QTCREATOR_UTILS_EXPORT QPair splitAtFirst(const QStringView &stringView, + QChar ch); + +QTCREATOR_UTILS_EXPORT int endOfNextWord(const QString &string, int position = 0); + +class QTCREATOR_UTILS_EXPORT MarkdownHighlighter : public QSyntaxHighlighter +{ +public: + MarkdownHighlighter(QTextDocument *parent); + void highlightBlock(const QString &text); + +private: + QBrush h2Brush; +}; + } // namespace Utils diff --git a/src/libs/utils/styledbar.cpp b/src/libs/utils/styledbar.cpp index a20aba6e1ae..4e7ec489fbd 100644 --- a/src/libs/utils/styledbar.cpp +++ b/src/libs/utils/styledbar.cpp @@ -3,6 +3,8 @@ #include "styledbar.h" +#include "stylehelper.h" + #include #include @@ -11,26 +13,26 @@ using namespace Utils; StyledBar::StyledBar(QWidget *parent) : QWidget(parent) { - setProperty("panelwidget", true); - setProperty("panelwidget_singlerow", true); - setProperty("lightColored", false); + StyleHelper::setPanelWidget(this); + StyleHelper::setPanelWidgetSingleRow(this); + setProperty(StyleHelper::C_LIGHT_COLORED, false); } void StyledBar::setSingleRow(bool singleRow) { - setProperty("panelwidget_singlerow", singleRow); + StyleHelper::setPanelWidgetSingleRow(this, singleRow); } bool StyledBar::isSingleRow() const { - return property("panelwidget_singlerow").toBool(); + return property(StyleHelper::C_PANEL_WIDGET_SINGLE_ROW).toBool(); } void StyledBar::setLightColored(bool lightColored) { if (isLightColored() == lightColored) return; - setProperty("lightColored", lightColored); + setProperty(StyleHelper::C_LIGHT_COLORED, lightColored); const QList children = findChildren(); for (QWidget *childWidget : children) childWidget->style()->polish(childWidget); @@ -38,7 +40,7 @@ void StyledBar::setLightColored(bool lightColored) bool StyledBar::isLightColored() const { - return property("lightColored").toBool(); + return property(StyleHelper::C_LIGHT_COLORED).toBool(); } void StyledBar::paintEvent(QPaintEvent *event) diff --git a/src/libs/utils/stylehelper.cpp b/src/libs/utils/stylehelper.cpp index 9f849b9a612..cc860e715a9 100644 --- a/src/libs/utils/stylehelper.cpp +++ b/src/libs/utils/stylehelper.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -36,6 +37,11 @@ static int range(float x, int min, int max) namespace Utils { +static StyleHelper::ToolbarStyle m_toolbarStyle = StyleHelper::defaultToolbarStyle; +// Invalid by default, setBaseColor needs to be called at least once +static QColor m_baseColor; +static QColor m_requestedBaseColor; + QColor StyleHelper::mergedColors(const QColor &colorA, const QColor &colorB, int factor) { const int maxFactor = 100; @@ -58,6 +64,36 @@ QColor StyleHelper::alphaBlendedColors(const QColor &colorA, const QColor &color ); } +QColor StyleHelper::sidebarHighlight() +{ + return QColor(255, 255, 255, 40); +} + +QColor StyleHelper::sidebarShadow() +{ + return QColor(0, 0, 0, 40); +} + +QColor StyleHelper::toolBarDropShadowColor() +{ + return QColor(0, 0, 0, 70); +} + +int StyleHelper::navigationWidgetHeight() +{ + return m_toolbarStyle == ToolbarStyleCompact ? 24 : 30; +} + +void StyleHelper::setToolbarStyle(ToolbarStyle style) +{ + m_toolbarStyle = style; +} + +StyleHelper::ToolbarStyle StyleHelper::toolbarStyle() +{ + return m_toolbarStyle; +} + qreal StyleHelper::sidebarFontSize() { return HostOsInfo::isMacHost() ? 10 : 7.5; @@ -89,10 +125,6 @@ QColor StyleHelper::panelTextColor(bool lightColored) return Qt::black; } -// Invalid by default, setBaseColor needs to be called at least once -QColor StyleHelper::m_baseColor; -QColor StyleHelper::m_requestedBaseColor; - QColor StyleHelper::baseColor(bool lightColored) { static const QColor windowColor = QApplication::palette().color(QPalette::Window); @@ -101,6 +133,11 @@ QColor StyleHelper::baseColor(bool lightColored) return (lightColored || windowColorAsBase) ? windowColor : m_baseColor; } +QColor StyleHelper::requestedBaseColor() +{ + return m_requestedBaseColor; +} + QColor StyleHelper::toolbarBaseColor(bool lightColored) { if (creatorTheme()->flag(Theme::QDSTheme)) @@ -149,6 +186,11 @@ QColor StyleHelper::toolBarBorderColor() clamp(base.value() * 0.80f)); } +QColor StyleHelper::buttonTextColor() +{ + return QColor(0x4c4c4c); +} + // We try to ensure that the actual color used are within // reasonalbe bounds while generating the actual baseColor // from the users request. @@ -173,7 +215,7 @@ void StyleHelper::setBaseColor(const QColor &newcolor) if (color.isValid() && color != m_baseColor) { m_baseColor = color; - const QList widgets = QApplication::topLevelWidgets(); + const QWidgetList widgets = QApplication::allWidgets(); for (QWidget *w : widgets) w->update(); } @@ -439,6 +481,22 @@ void StyleHelper::drawMinimalArrow(QStyle::PrimitiveElement element, QPainter *p painter->drawPixmap(xOffset, yOffset, pixmap); } +void StyleHelper::drawPanelBgRect(QPainter *painter, const QRectF &rect, const QBrush &brush) +{ + if (toolbarStyle() == ToolbarStyleCompact) { + painter->fillRect(rect.toRect(), brush); + } else { + constexpr int margin = 2; + constexpr int radius = 5; + QPainterPath path; + path.addRoundedRect(rect.adjusted(margin, margin, -margin, -margin), radius, radius); + painter->save(); + painter->setRenderHint(QPainter::Antialiasing); + painter->fillPath(path, brush); + painter->restore(); + } +} + void StyleHelper::menuGradient(QPainter *painter, const QRect &spanRect, const QRect &clipRect) { if (StyleHelper::usePixmapCache()) { @@ -462,6 +520,11 @@ void StyleHelper::menuGradient(QPainter *painter, const QRect &spanRect, const Q } } +bool StyleHelper::usePixmapCache() +{ + return true; +} + QPixmap StyleHelper::disabledSideBarIcon(const QPixmap &enabledicon) { QImage im = enabledicon.toImage().convertToFormat(QImage::Format_ARGB32); @@ -634,6 +697,16 @@ QLinearGradient StyleHelper::statusBarGradient(const QRect &statusBarRect) return grad; } +void StyleHelper::setPanelWidget(QWidget *widget, bool value) +{ + widget->setProperty(C_PANEL_WIDGET, value); +} + +void StyleHelper::setPanelWidgetSingleRow(QWidget *widget, bool value) +{ + widget->setProperty(C_PANEL_WIDGET_SINGLE_ROW, value); +} + bool StyleHelper::isQDSTheme() { return creatorTheme() ? creatorTheme()->flag(Theme::QDSTheme) : false; diff --git a/src/libs/utils/stylehelper.h b/src/libs/utils/stylehelper.h index da93bb3c3a5..2b898cb1049 100644 --- a/src/libs/utils/stylehelper.h +++ b/src/libs/utils/stylehelper.h @@ -13,115 +13,150 @@ class QPainter; class QRect; // Note, this is exported but in a private header as qtopengl depends on it. // We should consider adding this as a public helper function. -void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, int transposed = 0); +void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, + int transposed = 0); QT_END_NAMESPACE // Helper class holding all custom color values +namespace Utils::StyleHelper { -namespace Utils { -class QTCREATOR_UTILS_EXPORT StyleHelper +const unsigned int DEFAULT_BASE_COLOR = 0x666666; +const int progressFadeAnimationDuration = 600; + +constexpr char C_ALIGN_ARROW[] = "alignarrow"; +constexpr char C_DRAW_LEFT_BORDER[] = "drawleftborder"; +constexpr char C_ELIDE_MODE[] = "elidemode"; +constexpr char C_HIDE_BORDER[] = "hideborder"; +constexpr char C_HIDE_ICON[] = "hideicon"; +constexpr char C_HIGHLIGHT_WIDGET[] = "highlightWidget"; +constexpr char C_LIGHT_COLORED[] = "lightColored"; +constexpr char C_MINI_SPLITTER[] = "minisplitter"; +constexpr char C_NOT_ELIDE_ASTERISK[] = "notelideasterisk"; +constexpr char C_NO_ARROW[] = "noArrow"; +constexpr char C_PANEL_WIDGET[] = "panelwidget"; +constexpr char C_PANEL_WIDGET_SINGLE_ROW[] = "panelwidget_singlerow"; +constexpr char C_SHOW_BORDER[] = "showborder"; +constexpr char C_TOP_BORDER[] = "topBorder"; + +enum ToolbarStyle { + ToolbarStyleCompact, + ToolbarStyleRelaxed, +}; +constexpr ToolbarStyle defaultToolbarStyle = ToolbarStyleCompact; + +// Height of the project explorer navigation bar +QTCREATOR_UTILS_EXPORT int navigationWidgetHeight(); +QTCREATOR_UTILS_EXPORT void setToolbarStyle(ToolbarStyle style); +QTCREATOR_UTILS_EXPORT ToolbarStyle toolbarStyle(); +QTCREATOR_UTILS_EXPORT qreal sidebarFontSize(); +QTCREATOR_UTILS_EXPORT QPalette sidebarFontPalette(const QPalette &original); + +// This is our color table, all colors derive from baseColor +QTCREATOR_UTILS_EXPORT QColor requestedBaseColor(); +QTCREATOR_UTILS_EXPORT QColor baseColor(bool lightColored = false); +QTCREATOR_UTILS_EXPORT QColor toolbarBaseColor(bool lightColored = false); +QTCREATOR_UTILS_EXPORT QColor panelTextColor(bool lightColored = false); +QTCREATOR_UTILS_EXPORT QColor highlightColor(bool lightColored = false); +QTCREATOR_UTILS_EXPORT QColor shadowColor(bool lightColored = false); +QTCREATOR_UTILS_EXPORT QColor borderColor(bool lightColored = false); +QTCREATOR_UTILS_EXPORT QColor toolBarBorderColor(); +QTCREATOR_UTILS_EXPORT QColor buttonTextColor(); +QTCREATOR_UTILS_EXPORT QColor mergedColors(const QColor &colorA, const QColor &colorB, + int factor = 50); +QTCREATOR_UTILS_EXPORT QColor alphaBlendedColors(const QColor &colorA, const QColor &colorB); + +QTCREATOR_UTILS_EXPORT QColor sidebarHighlight(); +QTCREATOR_UTILS_EXPORT QColor sidebarShadow(); +QTCREATOR_UTILS_EXPORT QColor toolBarDropShadowColor(); +QTCREATOR_UTILS_EXPORT QColor notTooBrightHighlightColor(); + +// Sets the base color and makes sure all top level widgets are updated +QTCREATOR_UTILS_EXPORT void setBaseColor(const QColor &color); + +// Draws a shaded anti-aliased arrow +QTCREATOR_UTILS_EXPORT void drawArrow(QStyle::PrimitiveElement element, QPainter *painter, + const QStyleOption *option); +QTCREATOR_UTILS_EXPORT void drawMinimalArrow(QStyle::PrimitiveElement element, QPainter *painter, + const QStyleOption *option); + +QTCREATOR_UTILS_EXPORT void drawPanelBgRect(QPainter *painter, const QRectF &rect, + const QBrush &brush); + +// Gradients used for panels +QTCREATOR_UTILS_EXPORT void horizontalGradient(QPainter *painter, const QRect &spanRect, + const QRect &clipRect, bool lightColored = false); +QTCREATOR_UTILS_EXPORT void verticalGradient(QPainter *painter, const QRect &spanRect, + const QRect &clipRect, bool lightColored = false); +QTCREATOR_UTILS_EXPORT void menuGradient(QPainter *painter, const QRect &spanRect, + const QRect &clipRect); +QTCREATOR_UTILS_EXPORT bool usePixmapCache(); + +QTCREATOR_UTILS_EXPORT QPixmap disabledSideBarIcon(const QPixmap &enabledicon); +QTCREATOR_UTILS_EXPORT void drawIconWithShadow(const QIcon &icon, const QRect &rect, QPainter *p, + QIcon::Mode iconMode, int dipRadius = 3, + const QColor &color = QColor(0, 0, 0, 130), + const QPoint &dipOffset = QPoint(1, -2)); +QTCREATOR_UTILS_EXPORT void drawCornerImage(const QImage &img, QPainter *painter, const QRect &rect, + int left = 0, int top = 0, int right = 0, + int bottom = 0); + +QTCREATOR_UTILS_EXPORT void tintImage(QImage &img, const QColor &tintColor); +QTCREATOR_UTILS_EXPORT QLinearGradient statusBarGradient(const QRect &statusBarRect); +QTCREATOR_UTILS_EXPORT void setPanelWidget(QWidget *widget, bool value = true); +QTCREATOR_UTILS_EXPORT void setPanelWidgetSingleRow(QWidget *widget, bool value = true); + +QTCREATOR_UTILS_EXPORT bool isQDSTheme(); + +class IconFontHelper { public: - static const unsigned int DEFAULT_BASE_COLOR = 0x666666; - static const int progressFadeAnimationDuration = 600; + IconFontHelper(const QString &iconSymbol, + const QColor &color, + const QSize &size, + QIcon::Mode mode = QIcon::Normal, + QIcon::State state = QIcon::Off) + : m_iconSymbol(iconSymbol) + , m_color(color) + , m_size(size) + , m_mode(mode) + , m_state(state) + {} - // Height of the project explorer navigation bar - static int navigationWidgetHeight() { return 24; } - static qreal sidebarFontSize(); - static QPalette sidebarFontPalette(const QPalette &original); - - // This is our color table, all colors derive from baseColor - static QColor requestedBaseColor() { return m_requestedBaseColor; } - static QColor baseColor(bool lightColored = false); - static QColor toolbarBaseColor(bool lightColored = false); - static QColor panelTextColor(bool lightColored = false); - static QColor highlightColor(bool lightColored = false); - static QColor shadowColor(bool lightColored = false); - static QColor borderColor(bool lightColored = false); - static QColor toolBarBorderColor(); - static QColor buttonTextColor() { return QColor(0x4c4c4c); } - static QColor mergedColors(const QColor &colorA, const QColor &colorB, int factor = 50); - static QColor alphaBlendedColors(const QColor &colorA, const QColor &colorB); - - static QColor sidebarHighlight() { return QColor(255, 255, 255, 40); } - static QColor sidebarShadow() { return QColor(0, 0, 0, 40); } - - static QColor toolBarDropShadowColor() { return QColor(0, 0, 0, 70); } - - static QColor notTooBrightHighlightColor(); - - // Sets the base color and makes sure all top level widgets are updated - static void setBaseColor(const QColor &color); - - // Draws a shaded anti-aliased arrow - static void drawArrow(QStyle::PrimitiveElement element, QPainter *painter, const QStyleOption *option); - static void drawMinimalArrow(QStyle::PrimitiveElement element, QPainter *painter, const QStyleOption *option); - - // Gradients used for panels - static void horizontalGradient(QPainter *painter, const QRect &spanRect, const QRect &clipRect, bool lightColored = false); - static void verticalGradient(QPainter *painter, const QRect &spanRect, const QRect &clipRect, bool lightColored = false); - static void menuGradient(QPainter *painter, const QRect &spanRect, const QRect &clipRect); - static bool usePixmapCache() { return true; } - - static QPixmap disabledSideBarIcon(const QPixmap &enabledicon); - static void drawIconWithShadow(const QIcon &icon, const QRect &rect, QPainter *p, QIcon::Mode iconMode, - int dipRadius = 3, const QColor &color = QColor(0, 0, 0, 130), - const QPoint &dipOffset = QPoint(1, -2)); - static void drawCornerImage(const QImage &img, QPainter *painter, const QRect &rect, - int left = 0, int top = 0, int right = 0, int bottom = 0); - - static void tintImage(QImage &img, const QColor &tintColor); - static QLinearGradient statusBarGradient(const QRect &statusBarRect); - - static bool isQDSTheme(); - - class IconFontHelper - { - public: - IconFontHelper(const QString &iconSymbol, - const QColor &color, - const QSize &size, - QIcon::Mode mode = QIcon::Normal, - QIcon::State state = QIcon::Off) - : m_iconSymbol(iconSymbol) - , m_color(color) - , m_size(size) - , m_mode(mode) - , m_state(state) - {} - - QString iconSymbol() const { return m_iconSymbol; } - QColor color() const { return m_color; } - QSize size() const { return m_size; } - QIcon::Mode mode() const { return m_mode; } - QIcon::State state() const { return m_state; } - - private: - QString m_iconSymbol; - QColor m_color; - QSize m_size; - QIcon::Mode m_mode; - QIcon::State m_state; - }; - - static QIcon getIconFromIconFont(const QString &fontName, const QList ¶meters); - static QIcon getIconFromIconFont(const QString &fontName, const QString &iconSymbol, int fontSize, int iconSize, QColor color); - static QIcon getIconFromIconFont(const QString &fontName, const QString &iconSymbol, int fontSize, int iconSize); - static QIcon getCursorFromIconFont(const QString &fontname, const QString &cursorFill, const QString &cursorOutline, - int fontSize, int iconSize); - - static QString dpiSpecificImageFile(const QString &fileName); - static QString imageFileWithResolution(const QString &fileName, int dpr); - static QList availableImageResolutions(const QString &fileName); - - static double luminance(const QColor &color); - static bool isReadableOn(const QColor &background, const QColor &foreground); - // returns a foreground color readable on background (desiredForeground if already readable or adaption fails) - static QColor ensureReadableOn(const QColor &background, const QColor &desiredForeground); + QString iconSymbol() const { return m_iconSymbol; } + QColor color() const { return m_color; } + QSize size() const { return m_size; } + QIcon::Mode mode() const { return m_mode; } + QIcon::State state() const { return m_state; } private: - static QColor m_baseColor; - static QColor m_requestedBaseColor; + QString m_iconSymbol; + QColor m_color; + QSize m_size; + QIcon::Mode m_mode; + QIcon::State m_state; }; -} // namespace Utils +QTCREATOR_UTILS_EXPORT QIcon getIconFromIconFont(const QString &fontName, + const QList ¶meters); +QTCREATOR_UTILS_EXPORT QIcon getIconFromIconFont(const QString &fontName, + const QString &iconSymbol, int fontSize, + int iconSize, QColor color); +QTCREATOR_UTILS_EXPORT QIcon getIconFromIconFont(const QString &fontName, + const QString &iconSymbol, int fontSize, + int iconSize); +QTCREATOR_UTILS_EXPORT QIcon getCursorFromIconFont(const QString &fontname, + const QString &cursorFill, + const QString &cursorOutline, + int fontSize, int iconSize); + +QTCREATOR_UTILS_EXPORT QString dpiSpecificImageFile(const QString &fileName); +QTCREATOR_UTILS_EXPORT QString imageFileWithResolution(const QString &fileName, int dpr); +QTCREATOR_UTILS_EXPORT QList availableImageResolutions(const QString &fileName); + +QTCREATOR_UTILS_EXPORT double luminance(const QColor &color); +QTCREATOR_UTILS_EXPORT bool isReadableOn(const QColor &background, const QColor &foreground); +// returns a foreground color readable on background (desiredForeground if already readable or adaption fails) +QTCREATOR_UTILS_EXPORT QColor ensureReadableOn(const QColor &background, + const QColor &desiredForeground); + +} // namespace Utils::StyleHelper diff --git a/src/libs/utils/tasktree.cpp b/src/libs/utils/tasktree.cpp deleted file mode 100644 index 90e4073e033..00000000000 --- a/src/libs/utils/tasktree.cpp +++ /dev/null @@ -1,1401 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "tasktree.h" - -#include "guard.h" -#include "qtcassert.h" - -#include - -namespace Utils { -namespace Tasking { - -static TaskAction toTaskAction(bool success) -{ - return success ? TaskAction::StopWithDone : TaskAction::StopWithError; -} - -bool TreeStorageBase::isValid() const -{ - return m_storageData && m_storageData->m_constructor && m_storageData->m_destructor; -} - -TreeStorageBase::TreeStorageBase(StorageConstructor ctor, StorageDestructor dtor) - : m_storageData(new StorageData{ctor, dtor}) { } - -TreeStorageBase::StorageData::~StorageData() -{ - QTC_CHECK(m_storageHash.isEmpty()); - for (void *ptr : std::as_const(m_storageHash)) - m_destructor(ptr); -} - -void *TreeStorageBase::activeStorageVoid() const -{ - QTC_ASSERT(m_storageData->m_activeStorage, return nullptr); - const auto it = m_storageData->m_storageHash.constFind(m_storageData->m_activeStorage); - QTC_ASSERT(it != m_storageData->m_storageHash.constEnd(), return nullptr); - return it.value(); -} - -int TreeStorageBase::createStorage() const -{ - QTC_ASSERT(m_storageData->m_constructor, return 0); // TODO: add isValid()? - QTC_ASSERT(m_storageData->m_destructor, return 0); - QTC_ASSERT(m_storageData->m_activeStorage == 0, return 0); // TODO: should be allowed? - const int newId = ++m_storageData->m_storageCounter; - m_storageData->m_storageHash.insert(newId, m_storageData->m_constructor()); - return newId; -} - -void TreeStorageBase::deleteStorage(int id) const -{ - QTC_ASSERT(m_storageData->m_constructor, return); // TODO: add isValid()? - QTC_ASSERT(m_storageData->m_destructor, return); - QTC_ASSERT(m_storageData->m_activeStorage == 0, return); // TODO: should be allowed? - const auto it = m_storageData->m_storageHash.constFind(id); - QTC_ASSERT(it != m_storageData->m_storageHash.constEnd(), return); - m_storageData->m_destructor(it.value()); - m_storageData->m_storageHash.erase(it); -} - -// passing 0 deactivates currently active storage -void TreeStorageBase::activateStorage(int id) const -{ - if (id == 0) { - QTC_ASSERT(m_storageData->m_activeStorage, return); - m_storageData->m_activeStorage = 0; - return; - } - QTC_ASSERT(m_storageData->m_activeStorage == 0, return); - const auto it = m_storageData->m_storageHash.find(id); - QTC_ASSERT(it != m_storageData->m_storageHash.end(), return); - m_storageData->m_activeStorage = id; -} - -ParallelLimit sequential(1); -ParallelLimit parallel(0); -Workflow stopOnError(WorkflowPolicy::StopOnError); -Workflow continueOnError(WorkflowPolicy::ContinueOnError); -Workflow stopOnDone(WorkflowPolicy::StopOnDone); -Workflow continueOnDone(WorkflowPolicy::ContinueOnDone); -Workflow optional(WorkflowPolicy::Optional); - -void TaskItem::addChildren(const QList &children) -{ - QTC_ASSERT(m_type == Type::Group, qWarning("Only Task may have children, skipping..."); return); - for (const TaskItem &child : children) { - switch (child.m_type) { - case Type::Group: - m_children.append(child); - break; - case Type::Limit: - QTC_ASSERT(m_type == Type::Group, - qWarning("Mode may only be a child of Group, skipping..."); return); - m_parallelLimit = child.m_parallelLimit; // TODO: Assert on redefinition? - break; - case Type::Policy: - QTC_ASSERT(m_type == Type::Group, - qWarning("Workflow Policy may only be a child of Group, skipping..."); return); - m_workflowPolicy = child.m_workflowPolicy; // TODO: Assert on redefinition? - break; - case Type::TaskHandler: - QTC_ASSERT(child.m_taskHandler.m_createHandler, - qWarning("Task Create Handler can't be null, skipping..."); return); - QTC_ASSERT(child.m_taskHandler.m_setupHandler, - qWarning("Task Setup Handler can't be null, skipping..."); return); - m_children.append(child); - break; - case Type::GroupHandler: - QTC_ASSERT(m_type == Type::Group, qWarning("Group Handler may only be a " - "child of Group, skipping..."); break); - QTC_ASSERT(!child.m_groupHandler.m_setupHandler - || !m_groupHandler.m_setupHandler, - qWarning("Group Setup Handler redefinition, overriding...")); - QTC_ASSERT(!child.m_groupHandler.m_doneHandler - || !m_groupHandler.m_doneHandler, - qWarning("Group Done Handler redefinition, overriding...")); - QTC_ASSERT(!child.m_groupHandler.m_errorHandler - || !m_groupHandler.m_errorHandler, - qWarning("Group Error Handler redefinition, overriding...")); - if (child.m_groupHandler.m_setupHandler) - m_groupHandler.m_setupHandler = child.m_groupHandler.m_setupHandler; - if (child.m_groupHandler.m_doneHandler) - m_groupHandler.m_doneHandler = child.m_groupHandler.m_doneHandler; - if (child.m_groupHandler.m_errorHandler) - m_groupHandler.m_errorHandler = child.m_groupHandler.m_errorHandler; - break; - case Type::Storage: - m_storageList.append(child.m_storageList); - break; - } - } -} - -} // namespace Tasking - -using namespace Tasking; - -class TaskTreePrivate; -class TaskNode; - -class TaskContainer -{ -public: - TaskContainer(TaskTreePrivate *taskTreePrivate, const TaskItem &task, - TaskContainer *parentContainer) - : m_constData(taskTreePrivate, task, parentContainer, this) {} - TaskAction start(); - TaskAction continueStart(TaskAction startAction, int nextChild); - TaskAction startChildren(int nextChild); - TaskAction childDone(bool success); - void stop(); - void invokeEndHandler(); - bool isRunning() const { return m_runtimeData.has_value(); } - bool isStarting() const { return isRunning() && m_runtimeData->m_startGuard.isLocked(); } - - struct ConstData { - ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task, - TaskContainer *parentContainer, TaskContainer *thisContainer); - ~ConstData() { qDeleteAll(m_children); } - TaskTreePrivate * const m_taskTreePrivate = nullptr; - TaskContainer * const m_parentContainer = nullptr; - - const int m_parallelLimit = 1; - const WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError; - const TaskItem::GroupHandler m_groupHandler; - const QList m_storageList; - const QList m_children; - const int m_taskCount = 0; - }; - - struct RuntimeData { - RuntimeData(const ConstData &constData); - ~RuntimeData(); - - static QList createStorages(const TaskContainer::ConstData &constData); - void callStorageDoneHandlers(); - bool updateSuccessBit(bool success); - int currentLimit() const; - - const ConstData &m_constData; - const QList m_storageIdList; - int m_doneCount = 0; - bool m_successBit = true; - Guard m_startGuard; - }; - - const ConstData m_constData; - std::optional m_runtimeData; -}; - -class TaskNode : public QObject -{ -public: - TaskNode(TaskTreePrivate *taskTreePrivate, const TaskItem &task, - TaskContainer *parentContainer) - : m_taskHandler(task.taskHandler()) - , m_container(taskTreePrivate, task, parentContainer) - {} - - // If returned value != Continue, childDone() needs to be called in parent container (in caller) - // in order to unwind properly. - TaskAction start(); - void stop(); - void invokeEndHandler(bool success); - bool isRunning() const { return m_task || m_container.isRunning(); } - bool isTask() const { return m_taskHandler.m_createHandler && m_taskHandler.m_setupHandler; } - int taskCount() const { return isTask() ? 1 : m_container.m_constData.m_taskCount; } - TaskContainer *parentContainer() const { return m_container.m_constData.m_parentContainer; } - -private: - const TaskItem::TaskHandler m_taskHandler; - TaskContainer m_container; - std::unique_ptr m_task; -}; - -class TaskTreePrivate -{ -public: - TaskTreePrivate(TaskTree *taskTree) - : q(taskTree) {} - - void start() { - QTC_ASSERT(m_root, return); - m_progressValue = 0; - emitStartedAndProgress(); - // TODO: check storage handlers for not existing storages in tree - for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) { - QTC_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't " - "exist in task tree. Its handlers will never be called.")); - } - m_root->start(); - } - void stop() { - QTC_ASSERT(m_root, return); - if (!m_root->isRunning()) - return; - // TODO: should we have canceled flag (passed to handler)? - // Just one done handler with result flag: - // FinishedWithSuccess, FinishedWithError, Canceled, TimedOut. - // Canceled either directly by user, or by workflow policy - doesn't matter, in both - // cases canceled from outside. - m_root->stop(); - emitError(); - } - void advanceProgress(int byValue) { - if (byValue == 0) - return; - QTC_CHECK(byValue > 0); - QTC_CHECK(m_progressValue + byValue <= m_root->taskCount()); - m_progressValue += byValue; - emitProgress(); - } - void emitStartedAndProgress() { - GuardLocker locker(m_guard); - emit q->started(); - emit q->progressValueChanged(m_progressValue); - } - void emitProgress() { - GuardLocker locker(m_guard); - emit q->progressValueChanged(m_progressValue); - } - void emitDone() { - QTC_CHECK(m_progressValue == m_root->taskCount()); - GuardLocker locker(m_guard); - emit q->done(); - } - void emitError() { - QTC_CHECK(m_progressValue == m_root->taskCount()); - GuardLocker locker(m_guard); - emit q->errorOccurred(); - } - QList addStorages(const QList &storages) { - QList addedStorages; - for (const TreeStorageBase &storage : storages) { - QTC_ASSERT(!m_storages.contains(storage), qWarning("Can't add the same storage into " - "one TaskTree twice, skipping..."); continue); - addedStorages << storage; - m_storages << storage; - } - return addedStorages; - } - void callSetupHandler(TreeStorageBase storage, int storageId) { - callStorageHandler(storage, storageId, &StorageHandler::m_setupHandler); - } - void callDoneHandler(TreeStorageBase storage, int storageId) { - callStorageHandler(storage, storageId, &StorageHandler::m_doneHandler); - } - struct StorageHandler { - TaskTree::StorageVoidHandler m_setupHandler = {}; - TaskTree::StorageVoidHandler m_doneHandler = {}; - }; - typedef TaskTree::StorageVoidHandler StorageHandler::*HandlerPtr; // ptr to class member - void callStorageHandler(TreeStorageBase storage, int storageId, HandlerPtr ptr) - { - const auto it = m_storageHandlers.constFind(storage); - if (it == m_storageHandlers.constEnd()) - return; - GuardLocker locker(m_guard); - const StorageHandler storageHandler = *it; - storage.activateStorage(storageId); - if (storageHandler.*ptr) - (storageHandler.*ptr)(storage.activeStorageVoid()); - storage.activateStorage(0); - } - - TaskTree *q = nullptr; - Guard m_guard; - int m_progressValue = 0; - QSet m_storages; - QHash m_storageHandlers; - std::unique_ptr m_root = nullptr; // Keep me last in order to destruct first -}; - -class StorageActivator -{ -public: - StorageActivator(TaskContainer *container) - : m_container(container) { activateStorages(m_container); } - ~StorageActivator() { deactivateStorages(m_container); } - -private: - static void activateStorages(TaskContainer *container) - { - QTC_ASSERT(container && container->isRunning(), return); - const TaskContainer::ConstData &constData = container->m_constData; - if (constData.m_parentContainer) - activateStorages(constData.m_parentContainer); - for (int i = 0; i < constData.m_storageList.size(); ++i) - constData.m_storageList[i].activateStorage(container->m_runtimeData->m_storageIdList.value(i)); - } - static void deactivateStorages(TaskContainer *container) - { - QTC_ASSERT(container && container->isRunning(), return); - const TaskContainer::ConstData &constData = container->m_constData; - for (int i = constData.m_storageList.size() - 1; i >= 0; --i) // iterate in reverse order - constData.m_storageList[i].activateStorage(0); - if (constData.m_parentContainer) - deactivateStorages(constData.m_parentContainer); - } - TaskContainer *m_container = nullptr; -}; - -template > -ReturnType invokeHandler(TaskContainer *container, Handler &&handler, Args &&...args) -{ - StorageActivator activator(container); - GuardLocker locker(container->m_constData.m_taskTreePrivate->m_guard); - return std::invoke(std::forward(handler), std::forward(args)...); -} - -static QList createChildren(TaskTreePrivate *taskTreePrivate, TaskContainer *container, - const TaskItem &task) -{ - QList result; - const QList &children = task.children(); - for (const TaskItem &child : children) - result.append(new TaskNode(taskTreePrivate, child, container)); - return result; -} - -TaskContainer::ConstData::ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task, - TaskContainer *parentContainer, TaskContainer *thisContainer) - : m_taskTreePrivate(taskTreePrivate) - , m_parentContainer(parentContainer) - , m_parallelLimit(task.parallelLimit()) - , m_workflowPolicy(task.workflowPolicy()) - , m_groupHandler(task.groupHandler()) - , m_storageList(taskTreePrivate->addStorages(task.storageList())) - , m_children(createChildren(taskTreePrivate, thisContainer, task)) - , m_taskCount(std::accumulate(m_children.cbegin(), m_children.cend(), 0, - [](int r, TaskNode *n) { return r + n->taskCount(); })) -{} - -QList TaskContainer::RuntimeData::createStorages(const TaskContainer::ConstData &constData) -{ - QList storageIdList; - for (const TreeStorageBase &storage : constData.m_storageList) { - const int storageId = storage.createStorage(); - storageIdList.append(storageId); - constData.m_taskTreePrivate->callSetupHandler(storage, storageId); - } - return storageIdList; -} - -void TaskContainer::RuntimeData::callStorageDoneHandlers() -{ - for (int i = m_constData.m_storageList.size() - 1; i >= 0; --i) { // iterate in reverse order - const TreeStorageBase storage = m_constData.m_storageList[i]; - const int storageId = m_storageIdList.value(i); - m_constData.m_taskTreePrivate->callDoneHandler(storage, storageId); - } -} - -TaskContainer::RuntimeData::RuntimeData(const ConstData &constData) - : m_constData(constData) - , m_storageIdList(createStorages(constData)) -{ - m_successBit = m_constData.m_workflowPolicy != WorkflowPolicy::StopOnDone - && m_constData.m_workflowPolicy != WorkflowPolicy::ContinueOnDone; -} - -TaskContainer::RuntimeData::~RuntimeData() -{ - for (int i = m_constData.m_storageList.size() - 1; i >= 0; --i) { // iterate in reverse order - const TreeStorageBase storage = m_constData.m_storageList[i]; - const int storageId = m_storageIdList.value(i); - storage.deleteStorage(storageId); - } -} - -bool TaskContainer::RuntimeData::updateSuccessBit(bool success) -{ - if (m_constData.m_workflowPolicy == WorkflowPolicy::Optional) - return m_successBit; - - const bool donePolicy = m_constData.m_workflowPolicy == WorkflowPolicy::StopOnDone - || m_constData.m_workflowPolicy == WorkflowPolicy::ContinueOnDone; - m_successBit = donePolicy ? (m_successBit || success) : (m_successBit && success); - return m_successBit; -} - -int TaskContainer::RuntimeData::currentLimit() const -{ - const int childCount = m_constData.m_children.size(); - return m_constData.m_parallelLimit - ? qMin(m_doneCount + m_constData.m_parallelLimit, childCount) : childCount; -} - -TaskAction TaskContainer::start() -{ - QTC_CHECK(!isRunning()); - m_runtimeData.emplace(m_constData); - - TaskAction startAction = TaskAction::Continue; - if (m_constData.m_groupHandler.m_setupHandler) { - startAction = invokeHandler(this, m_constData.m_groupHandler.m_setupHandler); - if (startAction != TaskAction::Continue) - m_constData.m_taskTreePrivate->advanceProgress(m_constData.m_taskCount); - } - if (m_constData.m_children.isEmpty() && startAction == TaskAction::Continue) - startAction = TaskAction::StopWithDone; - return continueStart(startAction, 0); -} - -TaskAction TaskContainer::continueStart(TaskAction startAction, int nextChild) -{ - const TaskAction groupAction = startAction == TaskAction::Continue ? startChildren(nextChild) - : startAction; - QTC_CHECK(isRunning()); // TODO: superfluous - if (groupAction != TaskAction::Continue) { - const bool success = m_runtimeData->updateSuccessBit(groupAction == TaskAction::StopWithDone); - invokeEndHandler(); - if (TaskContainer *parentContainer = m_constData.m_parentContainer) { - QTC_CHECK(parentContainer->isRunning()); - if (!parentContainer->isStarting()) - parentContainer->childDone(success); - } else if (success) { - m_constData.m_taskTreePrivate->emitDone(); - } else { - m_constData.m_taskTreePrivate->emitError(); - } - } - return groupAction; -} - -TaskAction TaskContainer::startChildren(int nextChild) -{ - QTC_CHECK(isRunning()); - GuardLocker locker(m_runtimeData->m_startGuard); - for (int i = nextChild; i < m_constData.m_children.size(); ++i) { - const int limit = m_runtimeData->currentLimit(); - if (i >= limit) - break; - - const TaskAction startAction = m_constData.m_children.at(i)->start(); - if (startAction == TaskAction::Continue) - continue; - - const TaskAction finalizeAction = childDone(startAction == TaskAction::StopWithDone); - if (finalizeAction == TaskAction::Continue) - continue; - - int skippedTaskCount = 0; - // Skip scheduled but not run yet. The current (i) was already notified. - for (int j = i + 1; j < limit; ++j) - skippedTaskCount += m_constData.m_children.at(j)->taskCount(); - m_constData.m_taskTreePrivate->advanceProgress(skippedTaskCount); - return finalizeAction; - } - return TaskAction::Continue; -} - -TaskAction TaskContainer::childDone(bool success) -{ - QTC_CHECK(isRunning()); - const int limit = m_runtimeData->currentLimit(); // Read before bumping m_doneCount and stop() - const bool shouldStop = (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnDone && success) - || (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnError && !success); - if (shouldStop) - stop(); - - ++m_runtimeData->m_doneCount; - const bool updatedSuccess = m_runtimeData->updateSuccessBit(success); - const TaskAction startAction - = (shouldStop || m_runtimeData->m_doneCount == m_constData.m_children.size()) - ? toTaskAction(updatedSuccess) : TaskAction::Continue; - - if (isStarting()) - return startAction; - return continueStart(startAction, limit); -} - -void TaskContainer::stop() -{ - if (!isRunning()) - return; - - const int limit = m_runtimeData->currentLimit(); - for (int i = 0; i < limit; ++i) - m_constData.m_children.at(i)->stop(); - - int skippedTaskCount = 0; - for (int i = limit; i < m_constData.m_children.size(); ++i) - skippedTaskCount += m_constData.m_children.at(i)->taskCount(); - - m_constData.m_taskTreePrivate->advanceProgress(skippedTaskCount); -} - -void TaskContainer::invokeEndHandler() -{ - const TaskItem::GroupHandler &groupHandler = m_constData.m_groupHandler; - if (m_runtimeData->m_successBit && groupHandler.m_doneHandler) - invokeHandler(this, groupHandler.m_doneHandler); - else if (!m_runtimeData->m_successBit && groupHandler.m_errorHandler) - invokeHandler(this, groupHandler.m_errorHandler); - m_runtimeData->callStorageDoneHandlers(); - m_runtimeData.reset(); -} - -TaskAction TaskNode::start() -{ - QTC_CHECK(!isRunning()); - if (!isTask()) - return m_container.start(); - - m_task.reset(m_taskHandler.m_createHandler()); - const TaskAction startAction = invokeHandler(parentContainer(), m_taskHandler.m_setupHandler, - *m_task.get()); - if (startAction != TaskAction::Continue) { - m_container.m_constData.m_taskTreePrivate->advanceProgress(1); - m_task.reset(); - return startAction; - } - const std::shared_ptr unwindAction - = std::make_shared(TaskAction::Continue); - connect(m_task.get(), &TaskInterface::done, this, [=](bool success) { - invokeEndHandler(success); - disconnect(m_task.get(), &TaskInterface::done, this, nullptr); - m_task.release()->deleteLater(); - QTC_ASSERT(parentContainer() && parentContainer()->isRunning(), return); - if (parentContainer()->isStarting()) - *unwindAction = toTaskAction(success); - else - parentContainer()->childDone(success); - }); - - m_task->start(); - return *unwindAction; -} - -void TaskNode::stop() -{ - if (!isRunning()) - return; - - if (!m_task) { - m_container.stop(); - m_container.invokeEndHandler(); - return; - } - - // TODO: cancelHandler? - // TODO: call TaskInterface::stop() ? - invokeEndHandler(false); - m_task.reset(); -} - -void TaskNode::invokeEndHandler(bool success) -{ - if (success && m_taskHandler.m_doneHandler) - invokeHandler(parentContainer(), m_taskHandler.m_doneHandler, *m_task.get()); - else if (!success && m_taskHandler.m_errorHandler) - invokeHandler(parentContainer(), m_taskHandler.m_errorHandler, *m_task.get()); - m_container.m_constData.m_taskTreePrivate->advanceProgress(1); -} - -/*! - \class Utils::TaskTree - \inheaderfile utils/tasktree.h - \inmodule QtCreator - \ingroup mainclasses - \brief The TaskTree class runs an async task tree structure defined in a - declarative way. - - Use the Tasking namespace to build extensible, declarative task tree - structures that contain possibly asynchronous tasks, such as QtcProcess, - FileTransfer, or AsyncTask. TaskTree structures enable you - to create a sophisticated mixture of a parallel or sequential flow of tasks - in the form of a tree and to run it any time later. - - \section1 Root Element and Tasks - - The TaskTree has a mandatory Group root element, which may contain - any number of tasks of various types, such as Process, FileTransfer, - or AsyncTask: - - \code - using namespace Utils; - using namespace Tasking; - - const Group root { - Process(...), - Async(...), - Transfer(...) - }; - - TaskTree *taskTree = new TaskTree(root); - connect(taskTree, &TaskTree::done, ...); // a successfully finished handler - connect(taskTree, &TaskTree::errorOccurred, ...); // an erroneously finished handler - taskTree->start(); - \endcode - - The task tree above has a top level element of the Group type that contains - tasks of the type QtcProcess, FileTransfer, and AsyncTask. - After taskTree->start() is called, the tasks are run in a chain, starting - with Process. When Process finishes successfully, the Async task is - started. Finally, when the asynchronous task finishes successfully, the - FileTransfer task is started. - - When the last running task finishes with success, the task tree is considered - to have run successfully and the TaskTree::done() signal is emitted. - When a task finishes with an error, the execution of the task tree is stopped - and the remaining tasks are skipped. The task tree finishes with an error and - sends the TaskTree::errorOccurred() signal. - - \section1 Groups - - The parent of the Group sees it as a single task. Like other tasks, - the group can be started and it can finish with success or an error. - The Group elements can be nested to create a tree structure: - - \code - const Group root { - Group { - parallel, - Process(...), - Async(...) - }, - Transfer(...) - }; - \endcode - - The example above differs from the first example in that the root element has - a subgroup that contains the Process and Async tasks. The subgroup is a - sibling element of the Transfer task in the root. The subgroup contains an - additional \e parallel element that instructs its Group to execute its tasks - in parallel. - - So, when the tree above is started, the Process and Async tasks start - immediately and run in parallel. Since the root group doesn't contain a - \e parallel element, its direct child tasks are run in sequence. Thus, the - Transfer task starts when the whole subgroup finishes. The group is - considered as finished when all its tasks have finished. The order in which - the tasks finish is not relevant. - - So, depending on which task lasts longer (Process or Async), the - following scenarios can take place: - - \table - \header - \li Scenario 1 - \li Scenario 2 - \row - \li Root Group starts - \li Root Group starts - \row - \li Sub Group starts - \li Sub Group starts - \row - \li Process starts - \li Process starts - \row - \li Async starts - \li Async starts - \row - \li ... - \li ... - \row - \li \b {Process finishes} - \li \b {Async finishes} - \row - \li ... - \li ... - \row - \li \b {Async finishes} - \li \b {Process finishes} - \row - \li Sub Group finishes - \li Sub Group finishes - \row - \li Transfer starts - \li Transfer starts - \row - \li ... - \li ... - \row - \li Transfer finishes - \li Transfer finishes - \row - \li Root Group finishes - \li Root Group finishes - \endtable - - The differences between the scenarios are marked with bold. Three dots mean - that an unspecified amount of time passes between previous and next events - (a task or tasks continue to run). No dots between events - means that they occur synchronously. - - The presented scenarios assume that all tasks run successfully. If a task - fails during execution, the task tree finishes with an error. In particular, - when Process finishes with an error while Async is still being executed, - Async is automatically stopped, the subgroup finishes with an error, - Transfer is skipped, and the tree finishes with an error. - - \section1 Task Types - - Each task type is associated with its corresponding task class that executes - the task. For example, a Process task inside a task tree is associated with - the QtcProcess class that executes the process. The associated objects are - automatically created, started, and destructed exclusively by the task tree - at the appropriate time. - - If a root group consists of five sequential Process tasks, and the task tree - executes the group, it creates an instance of QtcProcess for the first - Process task and starts it. If the QtcProcess instance finishes successfully, - the task tree destructs it and creates a new QtcProcess instance for the - second Process, and so on. If the first task finishes with an error, the task - tree stops creating QtcProcess instances, and the root group finishes with an - error. - - The following table shows examples of task types and their corresponding task - classes: - - \table - \header - \li Task Type (Tasking Namespace) - \li Associated Task Class - \li Brief Description - \row - \li Process - \li Utils::QtcProcess - \li Starts processes. - \row - \li Async - \li Utils::AsyncTask - \li Starts asynchronous tasks; run in separate thread. - \row - \li Tree - \li Utils::TaskTree - \li Starts a nested task tree. - \row - \li Transfer - \li ProjectExplorer::FileTransfer - \li Starts file transfer between different devices. - \endtable - - \section1 Task Handlers - - Use Task handlers to set up a task for execution and to enable reading - the output data from the task when it finishes with success or an error. - - \section2 Task Start Handler - - When a corresponding task class object is created and before it's started, - the task tree invokes a mandatory user-provided setup handler. The setup - handler should always take a \e reference to the associated task class object: - - \code - const auto onSetup = [](QtcProcess &process) { - process.setCommand({"sleep", {"3"}}); - }; - const Group root { - Process(onSetup) - }; - \endcode - - You can modify the passed QtcProcess in the setup handler, so that the task - tree can start the process according to your configuration. - You do not need to call \e {process.start();} in the setup handler, - as the task tree calls it when needed. The setup handler is mandatory - and must be the first argument of the task's constructor. - - Optionally, the setup handler may return a TaskAction. The returned - TaskAction influences the further start behavior of a given task. The - possible values are: - - \table - \header - \li TaskAction Value - \li Brief Description - \row - \li Continue - \li The task is started normally. This is the default behavior when the - setup handler doesn't return TaskAction (that is, its return type is - void). - \row - \li StopWithDone - \li The task won't be started and it will report success to its parent. - \row - \li StopWithError - \li The task won't be started and it will report an error to its parent. - \endtable - - This is useful for running a task only when a condition is met and the data - needed to evaluate this condition is not known until previously started tasks - finish. This way, the setup handler dynamically decides whether to start the - corresponding task normally or skip it and report success or an error. - For more information about inter-task data exchange, see \l Storage. - - \section2 Task's Done and Error Handlers - - When a running task finishes, the task tree invokes an optionally provided - done or error handler. Both handlers should always take a \e {const reference} - to the associated task class object: - - \code - const auto onSetup = [](QtcProcess &process) { - process.setCommand({"sleep", {"3"}}); - }; - const auto onDone = [](const QtcProcess &process) { - qDebug() << "Success" << process.cleanedStdOut(); - }; - const auto onError = [](const QtcProcess &process) { - qDebug() << "Failure" << process.cleanedStdErr(); - }; - const Group root { - Process(onSetup, onDone, onError) - }; - \endcode - - The done and error handlers may collect output data from QtcProcess, and store it - for further processing or perform additional actions. The done handler is optional. - When used, it must be the second argument of the task constructor. - The error handler must always be the third argument. - You can omit the handlers or substitute the ones that you do not need with curly braces ({}). - - \note If the task setup handler returns StopWithDone or StopWithError, - neither the done nor error handler is invoked. - - \section1 Group Handlers - - Similarly to task handlers, group handlers enable you to set up a group to - execute and to apply more actions when the whole group finishes with - success or an error. - - \section2 Group's Start Handler - - The task tree invokes the group start handler before it starts the child - tasks. The group handler doesn't take any arguments: - - \code - const auto onGroupSetup = [] { - qDebug() << "Entering the group"; - }; - const Group root { - OnGroupSetup(onGroupSetup), - Process(...) - }; - \endcode - - The group setup handler is optional. To define a group setup handler, add an - OnGroupSetup element to a group. The argument of OnGroupSetup is a user - handler. If you add more than one OnGroupSetup element to a group, an assert - is triggered at runtime that includes an error message. - - Like the task start handler, the group start handler may return TaskAction. - The returned TaskAction value affects the start behavior of the - whole group. If you do not specify a group start handler or its return type - is void, the default group's action is TaskAction::Continue, so that all - tasks are started normally. Otherwise, when the start handler returns - TaskAction::StopWithDone or TaskAction::StopWithError, the tasks are not - started (they are skipped) and the group itself reports success or failure, - depending on the returned value, respectively. - - \code - const Group root { - OnGroupSetup([] { qDebug() << "Root setup"; }), - Group { - OnGroupSetup([] { qDebug() << "Group 1 setup"; return TaskAction::Continue; }), - Process(...) // Process 1 - }, - Group { - OnGroupSetup([] { qDebug() << "Group 2 setup"; return TaskAction::StopWithDone; }), - Process(...) // Process 2 - }, - Group { - OnGroupSetup([] { qDebug() << "Group 3 setup"; return TaskAction::StopWithError; }), - Process(...) // Process 3 - }, - Process(...) // Process 4 - }; - \endcode - - In the above example, all subgroups of a root group define their setup handlers. - The following scenario assumes that all started processes finish with success: - - \table - \header - \li Scenario - \li Comment - \row - \li Root Group starts - \li Doesn't return TaskAction, so its tasks are executed. - \row - \li Group 1 starts - \li Returns Continue, so its tasks are executed. - \row - \li Process 1 starts - \li - \row - \li ... - \li ... - \row - \li Process 1 finishes (success) - \li - \row - \li Group 1 finishes (success) - \li - \row - \li Group 2 starts - \li Returns StopWithDone, so Process 2 is skipped and Group 2 reports - success. - \row - \li Group 2 finishes (success) - \li - \row - \li Group 3 starts - \li Returns StopWithError, so Process 3 is skipped and Group 3 reports - an error. - \row - \li Group 3 finishes (error) - \li - \row - \li Root Group finishes (error) - \li Group 3, which is a direct child of the root group, finished with an - error, so the root group stops executing, skips Process 4, which has - not started yet, and reports an error. - \endtable - - \section2 Groups's Done and Error Handlers - - A Group's done or error handler is executed after the successful or failed - execution of its tasks, respectively. The final value reported by the - group depends on its \l {Workflow Policy}. The handlers can apply other - necessary actions. The done and error handlers are defined inside the - OnGroupDone and OnGroupError elements of a group, respectively. They do not - take arguments: - - \code - const Group root { - OnGroupSetup([] { qDebug() << "Root setup"; }), - Process(...), - OnGroupDone([] { qDebug() << "Root finished with success"; }), - OnGroupError([] { qDebug() << "Root finished with error"; }) - }; - \endcode - - The group done and error handlers are optional. If you add more than one - OnGroupDone or OnGroupError each to a group, an assert is triggered at - runtime that includes an error message. - - \note Even if the group setup handler returns StopWithDone or StopWithError, - one of the task's done or error handlers is invoked. This behavior differs - from that of task handlers and might change in the future. - - \section1 Other Group Elements - - A group can contain other elements that describe the processing flow, such as - the execution mode or workflow policy. It can also contain storage elements - that are responsible for collecting and sharing custom common data gathered - during group execution. - - \section2 Execution Mode - - The execution mode element in a Group specifies how the direct child tasks of - the Group are started. - - \table - \header - \li Execution Mode - \li Description - \row - \li sequential - \li Default. When a Group has no execution mode, it runs in the - sequential mode. All the direct child tasks of a group are started - in a chain, so that when one task finishes, the next one starts. - This enables you to pass the results from the previous task - as input to the next task before it starts. This mode guarantees - that the next task is started only after the previous task finishes. - \row - \li parallel - \li All the direct child tasks of a group are started after the group is - started, without waiting for the previous tasks to finish. In this - mode, all tasks run simultaneously. - \row - \li ParallelLimit(int limit) - \li In this mode, a limited number of direct child tasks run simultaneously. - The \e limit defines the maximum number of tasks running in parallel - in a group. When the group is started, the first batch tasks is - started (the number of tasks in batch equals to passed limit, at most), - while the others are kept waiting. When a running task finishes, - the group starts the next remaining one, so that the \e limit - of simultaneously running tasks inside a group isn't exceeded. - This repeats on every child task's finish until all child tasks are started. - This enables you to limit the maximum number of tasks that - run simultaneously, for example if running too many processes might - block the machine for a long time. The value 1 means \e sequential - execution. The value 0 means unlimited and equals \e parallel. - \endtable - - In all execution modes, a group starts tasks in the oder in which they appear. - - If a child of a group is also a group (in a nested tree), the child group - runs its tasks according to its own execution mode. - - \section2 Workflow Policy - - The workflow policy element in a Group specifies how the group should behave - when its direct child tasks finish: - - \table - \header - \li Workflow Policy - \li Description - \row - \li stopOnError - \li Default. If a task finishes with an error, the group: - \list 1 - \li Stops the running tasks (if any - for example, in parallel - mode). - \li Skips executing tasks it has not started (for example, in the - sequential mode). - \li Immediately finishes with an error. - \endlist - If all child tasks finish successfully or the group is empty, the group - finishes with success. - \row - \li continueOnError - \li Similar to stopOnError, but in case any child finishes with - an error, the execution continues until all tasks finish, - and the group reports an error afterwards, even when some other - tasks in group finished with success. - If a task finishes with an error, the group: - \list 1 - \li Continues executing the tasks that are running or have not - started yet. - \li Finishes with an error when all tasks finish. - \endlist - If all tasks finish successfully or the group is empty, the group - finishes with success. - \row - \li stopOnDone - \li If a task finishes with success, the group: - \list 1 - \li Stops running tasks and skips those that it has not started. - \li Immediately finishes with success. - \endlist - If all tasks finish with an error or the group is empty, the group - finishes with an error. - \row - \li continueOnDone - \li Similar to stopOnDone, but in case any child finishes - successfully, the execution continues until all tasks finish, - and the group reports success afterwards, even when some other - tasks in group finished with an error. - If a task finishes with success, the group: - \list 1 - \li Continues executing the tasks that are running or have not - started yet. - \li Finishes with success when all tasks finish. - \endlist - If all tasks finish with an error or the group is empty, the group - finishes with an error. - \row - \li optional - \li The group executes all tasks and ignores their return state. If all - tasks finish or the group is empty, the group finishes with success. - \endtable - - If a child of a group is also a group (in a nested tree), the child group - runs its tasks according to its own workflow policy. - - \section2 Storage - - Use the Storage element to exchange information between tasks. Especially, - in the sequential execution mode, when a task needs data from another task - before it can start. For example, a task tree that copies data by reading - it from a source and writing it to a destination might look as follows: - - \code - static QByteArray load(const FilePath &fileName) { ... } - static void save(const FilePath &fileName, const QByteArray &array) { ... } - - static TaskItem diffRecipe(const FilePath &source, const FilePath &destination) - { - struct CopyStorage { // [1] custom inter-task struct - QByteArray content; // [2] custom inter-task data - }; - - // [3] instance of custom inter-task struct manageable by task tree - const TreeStorage storage; - - const auto onLoaderSetup = [source](Async &async) { - async.setAsyncCallData(&load, source); - }; - // [4] runtime: task tree activates the instance from [5] before invoking handler - const auto onLoaderDone = [storage](const Async &async) { - storage->content = async.result(); - }; - - // [4] runtime: task tree activates the instance from [5] before invoking handler - const auto onSaverSetup = [storage, destination](Async &async) { - async.setAsyncCallData(&save, destination, storage->content); - }; - const auto onSaverDone = [](const Async &async) { - qDebug() << "Save done successfully"; - }; - - const Group root { - // [5] runtime: task tree creates an instance of CopyStorage when root is entered - Storage(storage), - Async(onLoaderSetup, onLoaderDone), - Async(onSaverSetup, onSaverDone) - }; - return root; - } - \endcode - - In the example above, the inter-task data consists of a QByteArray content - variable [2] enclosed in a CopyStorage custom struct [1]. If the loader - finishes successfully, it stores the data in a CopyStorage::content - variable. The saver then uses the variable to configure the saving task. - - To enable a task tree to manage the CopyStorage struct, an instance of - TreeStorage is created [3]. If a copy of this object is - inserted as group's child task [5], an instance of CopyStorage struct is - created dynamically when the task tree enters this group. When the task - tree leaves this group, the existing instance of CopyStorage struct is - destructed as it's no longer needed. - - If several task trees that hold a copy of the common TreeStorage - instance run simultaneously, each task tree contains its own copy of the - CopyStorage struct. - - You can access CopyStorage from any handler in the group with a storage object. - This includes all handlers of all descendant tasks of the group with - a storage object. To access the custom struct in a handler, pass the - copy of the TreeStorage object to the handler (for example, in - a lambda capture) [4]. - - When the task tree invokes a handler in a subtree containing the storage [5], - the task tree activates its own CopyStorage instance inside the - TreeStorage object. Therefore, the CopyStorage struct may be - accessed only from within the handler body. To access the currently active - CopyStorage from within TreeStorage, use the TreeStorage::operator->() - or TreeStorage::activeStorage() method. - - The following list summarizes how to employ a Storage object into the task - tree: - \list 1 - \li Define the custom structure MyStorage with custom data [1], [2] - \li Create an instance of TreeStorage storage [3] - \li Pass the TreeStorage instance to handlers [4] - \li Insert the TreeStorage instance into a group [5] - \endlist - - \note The current implementation assumes that all running task trees - containing copies of the same TreeStorage run in the same thread. Otherwise, - the behavior is undefined. - - \section1 TaskTree - - TaskTree executes the tree structure of asynchronous tasks according to the - recipe described by the Group root element. - - As TaskTree is also an asynchronous task, it can be a part of another TaskTree. - To place a nested TaskTree inside another TaskTree, insert the Tasking::Tree - element into other tree's Group element. - - TaskTree reports progress of completed tasks when running. The progress value - is increased when a task finishes or is skipped or stopped. - When TaskTree is finished and the TaskTree::done() or TaskTree::errorOccurred() - signal is emitted, the current value of the progress equals the maximum - progress value. Maximum progress equals the total number of tasks in a tree. - A nested TaskTree is counted as a single task, and its child tasks are not - counted in the top level tree. Groups themselves are not counted as tasks, - but their tasks are counted. - - To set additional initial data for the running tree, modify the storage - instances in a tree when it creates them by installing a storage setup - handler: - - \code - TreeStorage storage; - Group root = ...; // storage placed inside root's group and inside handlers - TaskTree taskTree(root); - auto initStorage = [](CopyStorage *storage){ - storage->content = "initial content"; - }; - taskTree.onStorageSetup(storage, initStorage); - taskTree.start(); - \endcode - - When the running task tree creates a CopyStorage instance, and before any - handler inside a tree is called, the task tree calls the initStorage handler, - to enable setting up initial data of the storage, unique to this particular - run of taskTree. - - Similarly, to collect some additional result data from the running tree, - read it from storage instances in the tree when they are about to be - destroyed. To do this, install a storage done handler: - - \code - TreeStorage storage; - Group root = ...; // storage placed inside root's group and inside handlers - TaskTree taskTree(root); - auto collectStorage = [](CopyStorage *storage){ - qDebug() << "final content" << storage->content; - }; - taskTree.onStorageDone(storage, collectStorage); - taskTree.start(); - \endcode - - When the running task tree is about to destroy a CopyStorage instance, the - task tree calls the collectStorage handler, to enable reading the final data - from the storage, unique to this particular run of taskTree. - - \section1 Task Adapters - - To extend a TaskTree with new a task type, implement a simple adapter class - derived from the TaskAdapter class template. The following class is an - adapter for a single shot timer, which may be considered as a new - asynchronous task: - - \code - class TimeoutAdapter : public Utils::Tasking::TaskAdapter - { - public: - TimeoutAdapter() { - task()->setSingleShot(true); - task()->setInterval(1000); - connect(task(), &QTimer::timeout, this, [this] { emit done(true); }); - } - void start() final { task()->start(); } - }; - - QTC_DECLARE_CUSTOM_TASK(Timeout, TimeoutAdapter); - \endcode - - You must derive the custom adapter from the TaskAdapter class template - instantiated with a template parameter of the class implementing a running - task. The code above uses QTimer to run the task. This class appears - later as an argument to the task's handlers. The instance of this class - parameter automatically becomes a member of the TaskAdapter template, and is - accessible through the TaskAdapter::task() method. The constructor - of TimeoutAdapter initially configures the QTimer object and connects - to the QTimer::timeout signal. When the signal is triggered, TimeoutAdapter - emits the done(true) signal to inform the task tree that the task finished - successfully. If it emits done(false), the task finished with an error. - The TaskAdapter::start() method starts the timer. - - To make QTimer accessible inside TaskTree under the \e Timeout name, - register it with QTC_DECLARE_CUSTOM_TASK(Timeout, TimeoutAdapter). Timeout - becomes a new task type inside Utils::Tasking namespace, using TimeoutAdapter. - - The new task type is now registered, and you can use it in TaskTree: - - \code - const auto onTimeoutSetup = [](QTimer &task) { - task.setInterval(2000); - }; - const auto onTimeoutDone = [](const QTimer &task) { - qDebug() << "timeout triggered"; - }; - - const Group root { - Timeout(onTimeoutSetup, onTimeoutDone) - }; - \endcode - - When a task tree containing the root from the above example is started, it - prints a debug message within two seconds and then finishes successfully. - - \note The class implementing the running task should have a default constructor, - and objects of this class should be freely destructible. It should be allowed - to destroy a running object, preferably without waiting for the running task - to finish (that is, safe non-blocking destructor of a running task). -*/ - -TaskTree::TaskTree() - : d(new TaskTreePrivate(this)) -{ -} - -TaskTree::TaskTree(const Group &root) : TaskTree() -{ - setupRoot(root); -} - -TaskTree::~TaskTree() -{ - QTC_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting TaskTree instance directly from " - "one of its handlers will lead to crash!")); - delete d; -} - -void TaskTree::setupRoot(const Tasking::Group &root) -{ - QTC_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return); - QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The setupRoot() is called from one of the" - "TaskTree handlers, ingoring..."); return); - d->m_storages.clear(); - d->m_root.reset(new TaskNode(d, root, nullptr)); -} - -void TaskTree::start() -{ - QTC_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return); - QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The start() is called from one of the" - "TaskTree handlers, ingoring..."); return); - d->start(); -} - -void TaskTree::stop() -{ - QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The stop() is called from one of the" - "TaskTree handlers, ingoring..."); return); - d->stop(); -} - -bool TaskTree::isRunning() const -{ - return d->m_root && d->m_root->isRunning(); -} - -int TaskTree::taskCount() const -{ - return d->m_root ? d->m_root->taskCount() : 0; -} - -int TaskTree::progressValue() const -{ - return d->m_progressValue; -} - -void TaskTree::setupStorageHandler(const Tasking::TreeStorageBase &storage, - StorageVoidHandler setupHandler, - StorageVoidHandler doneHandler) -{ - auto it = d->m_storageHandlers.find(storage); - if (it == d->m_storageHandlers.end()) { - d->m_storageHandlers.insert(storage, {setupHandler, doneHandler}); - return; - } - if (setupHandler) { - QTC_ASSERT(!it->m_setupHandler, - qWarning("The storage has its setup handler defined, overriding...")); - it->m_setupHandler = setupHandler; - } - if (doneHandler) { - QTC_ASSERT(!it->m_doneHandler, - qWarning("The storage has its done handler defined, overriding...")); - it->m_doneHandler = doneHandler; - } -} - -TaskTreeAdapter::TaskTreeAdapter() -{ - connect(task(), &TaskTree::done, this, [this] { emit done(true); }); - connect(task(), &TaskTree::errorOccurred, this, [this] { emit done(false); }); -} - -void TaskTreeAdapter::start() -{ - task()->start(); -} - -} // namespace Utils diff --git a/src/libs/utils/tasktree.h b/src/libs/utils/tasktree.h deleted file mode 100644 index 54a0cfda534..00000000000 --- a/src/libs/utils/tasktree.h +++ /dev/null @@ -1,385 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "utils_global.h" - -#include -#include -#include - -namespace Utils { - -class StorageActivator; -class TaskContainer; -class TaskTreePrivate; - -namespace Tasking { - -class QTCREATOR_UTILS_EXPORT TaskInterface : public QObject -{ - Q_OBJECT - -public: - TaskInterface() = default; - virtual void start() = 0; - -signals: - void done(bool success); -}; - -class QTCREATOR_UTILS_EXPORT TreeStorageBase -{ -public: - bool isValid() const; - -protected: - using StorageConstructor = std::function; - using StorageDestructor = std::function; - - TreeStorageBase(StorageConstructor ctor, StorageDestructor dtor); - void *activeStorageVoid() const; - -private: - int createStorage() const; - void deleteStorage(int id) const; - void activateStorage(int id) const; - - friend bool operator==(const TreeStorageBase &first, const TreeStorageBase &second) - { return first.m_storageData == second.m_storageData; } - - friend bool operator!=(const TreeStorageBase &first, const TreeStorageBase &second) - { return first.m_storageData != second.m_storageData; } - - friend size_t qHash(const TreeStorageBase &storage, uint seed = 0) - { return size_t(storage.m_storageData.get()) ^ seed; } - - struct StorageData { - ~StorageData(); - StorageConstructor m_constructor = {}; - StorageDestructor m_destructor = {}; - QHash m_storageHash = {}; - int m_activeStorage = 0; // 0 means no active storage - int m_storageCounter = 0; - }; - QSharedPointer m_storageData; - friend TaskContainer; - friend TaskTreePrivate; - friend StorageActivator; -}; - -template -class TreeStorage : public TreeStorageBase -{ -public: - TreeStorage() : TreeStorageBase(TreeStorage::ctor(), TreeStorage::dtor()) {} - StorageStruct *operator->() const noexcept { return activeStorage(); } - StorageStruct *activeStorage() const { - return static_cast(activeStorageVoid()); - } - -private: - static StorageConstructor ctor() { return [] { return new StorageStruct; }; } - static StorageDestructor dtor() { - return [](void *storage) { delete static_cast(storage); }; - } -}; - -// 4 policies: -// 1. When all children finished with done -> report done, otherwise: -// a) Report error on first error and stop executing other children (including their subtree) -// b) On first error - wait for all children to be finished and report error afterwards -// 2. When all children finished with error -> report error, otherwise: -// a) Report done on first done and stop executing other children (including their subtree) -// b) On first done - wait for all children to be finished and report done afterwards - -enum class WorkflowPolicy { - StopOnError, // 1a - Will report error on any child error, otherwise done (if all children were done) - ContinueOnError, // 1b - the same. When no children it reports done. - StopOnDone, // 2a - Will report done on any child done, otherwise error (if all children were error) - ContinueOnDone, // 2b - the same. When no children it reports done. (?) - Optional // Returns always done after all children finished -}; - -enum class TaskAction -{ - Continue, - StopWithDone, - StopWithError -}; - -class QTCREATOR_UTILS_EXPORT TaskItem -{ -public: - // Internal, provided by QTC_DECLARE_CUSTOM_TASK - using TaskCreateHandler = std::function; - // Called prior to task start, just after createHandler - using TaskSetupHandler = std::function; - // Called on task done / error - using TaskEndHandler = std::function; - // Called when group entered - using GroupSetupHandler = std::function; - // Called when group done / error - using GroupEndHandler = std::function; - - struct TaskHandler { - TaskCreateHandler m_createHandler; - TaskSetupHandler m_setupHandler; - TaskEndHandler m_doneHandler; - TaskEndHandler m_errorHandler; - }; - - struct GroupHandler { - GroupSetupHandler m_setupHandler; - GroupEndHandler m_doneHandler = {}; - GroupEndHandler m_errorHandler = {}; - }; - - int parallelLimit() const { return m_parallelLimit; } - WorkflowPolicy workflowPolicy() const { return m_workflowPolicy; } - TaskHandler taskHandler() const { return m_taskHandler; } - GroupHandler groupHandler() const { return m_groupHandler; } - QList children() const { return m_children; } - QList storageList() const { return m_storageList; } - -protected: - enum class Type { - Group, - Storage, - Limit, - Policy, - TaskHandler, - GroupHandler - }; - - TaskItem() = default; - TaskItem(int parallelLimit) - : m_type(Type::Limit) - , m_parallelLimit(parallelLimit) {} - TaskItem(WorkflowPolicy policy) - : m_type(Type::Policy) - , m_workflowPolicy(policy) {} - TaskItem(const TaskHandler &handler) - : m_type(Type::TaskHandler) - , m_taskHandler(handler) {} - TaskItem(const GroupHandler &handler) - : m_type(Type::GroupHandler) - , m_groupHandler(handler) {} - TaskItem(const TreeStorageBase &storage) - : m_type(Type::Storage) - , m_storageList{storage} {} - void addChildren(const QList &children); - -private: - Type m_type = Type::Group; - int m_parallelLimit = 1; // 0 means unlimited - WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError; - TaskHandler m_taskHandler; - GroupHandler m_groupHandler; - QList m_storageList; - QList m_children; -}; - -class QTCREATOR_UTILS_EXPORT Group : public TaskItem -{ -public: - Group(const QList &children) { addChildren(children); } - Group(std::initializer_list children) { addChildren(children); } -}; - -class QTCREATOR_UTILS_EXPORT Storage : public TaskItem -{ -public: - Storage(const TreeStorageBase &storage) : TaskItem(storage) { } -}; - -class QTCREATOR_UTILS_EXPORT ParallelLimit : public TaskItem -{ -public: - ParallelLimit(int parallelLimit) : TaskItem(qMax(parallelLimit, 0)) {} -}; - -class QTCREATOR_UTILS_EXPORT Workflow : public TaskItem -{ -public: - Workflow(WorkflowPolicy policy) : TaskItem(policy) {} -}; - -class QTCREATOR_UTILS_EXPORT OnGroupSetup : public TaskItem -{ -public: - template - OnGroupSetup(SetupFunction &&function) - : TaskItem({wrapSetup(std::forward(function))}) {} - -private: - template - static TaskItem::GroupSetupHandler wrapSetup(SetupFunction &&function) { - static constexpr bool isDynamic = std::is_same_v>>; - constexpr bool isVoid = std::is_same_v>>; - static_assert(isDynamic || isVoid, - "Group setup handler needs to take no arguments and has to return " - "void or TaskAction. The passed handler doesn't fulfill these requirements."); - return [=] { - if constexpr (isDynamic) - return std::invoke(function); - std::invoke(function); - return TaskAction::Continue; - }; - }; -}; - -class QTCREATOR_UTILS_EXPORT OnGroupDone : public TaskItem -{ -public: - OnGroupDone(const GroupEndHandler &handler) : TaskItem({{}, handler}) {} -}; - -class QTCREATOR_UTILS_EXPORT OnGroupError : public TaskItem -{ -public: - OnGroupError(const GroupEndHandler &handler) : TaskItem({{}, {}, handler}) {} -}; - -QTCREATOR_UTILS_EXPORT extern ParallelLimit sequential; -QTCREATOR_UTILS_EXPORT extern ParallelLimit parallel; -QTCREATOR_UTILS_EXPORT extern Workflow stopOnError; -QTCREATOR_UTILS_EXPORT extern Workflow continueOnError; -QTCREATOR_UTILS_EXPORT extern Workflow stopOnDone; -QTCREATOR_UTILS_EXPORT extern Workflow continueOnDone; -QTCREATOR_UTILS_EXPORT extern Workflow optional; - -template -class TaskAdapter : public TaskInterface -{ -public: - using Type = Task; - TaskAdapter() = default; - Task *task() { return &m_task; } - const Task *task() const { return &m_task; } -private: - Task m_task; -}; - -template -class CustomTask : public TaskItem -{ -public: - using Task = typename Adapter::Type; - using EndHandler = std::function; - static Adapter *createAdapter() { return new Adapter; } - template - CustomTask(SetupFunction &&function, const EndHandler &done = {}, const EndHandler &error = {}) - : TaskItem({&createAdapter, wrapSetup(std::forward(function)), - wrapEnd(done), wrapEnd(error)}) {} - -private: - template - static TaskItem::TaskSetupHandler wrapSetup(SetupFunction &&function) { - static constexpr bool isDynamic = std::is_same_v, typename Adapter::Type &>>; - constexpr bool isVoid = std::is_same_v, typename Adapter::Type &>>; - static_assert(isDynamic || isVoid, - "Task setup handler needs to take (Task &) as an argument and has to return " - "void or TaskAction. The passed handler doesn't fulfill these requirements."); - return [=](TaskInterface &taskInterface) { - Adapter &adapter = static_cast(taskInterface); - if constexpr (isDynamic) - return std::invoke(function, *adapter.task()); - std::invoke(function, *adapter.task()); - return TaskAction::Continue; - }; - }; - - static TaskEndHandler wrapEnd(const EndHandler &handler) { - if (!handler) - return {}; - return [handler](const TaskInterface &taskInterface) { - const Adapter &adapter = static_cast(taskInterface); - handler(*adapter.task()); - }; - }; -}; - -} // namespace Tasking - -class TaskTreePrivate; - -class QTCREATOR_UTILS_EXPORT TaskTree : public QObject -{ - Q_OBJECT - -public: - TaskTree(); - TaskTree(const Tasking::Group &root); - ~TaskTree(); - - void setupRoot(const Tasking::Group &root); - - void start(); - void stop(); - bool isRunning() const; - - int taskCount() const; - int progressMaximum() const { return taskCount(); } - int progressValue() const; // all finished / skipped / stopped tasks, groups itself excluded - - template - void onStorageSetup(const Tasking::TreeStorage &storage, - StorageHandler &&handler) { - setupStorageHandler(storage, - wrapHandler(std::forward(handler)), {}); - } - template - void onStorageDone(const Tasking::TreeStorage &storage, - StorageHandler &&handler) { - setupStorageHandler(storage, - {}, wrapHandler(std::forward(handler))); - } - -signals: - void started(); - void done(); - void errorOccurred(); - void progressValueChanged(int value); // updated whenever task finished / skipped / stopped - -private: - using StorageVoidHandler = std::function; - void setupStorageHandler(const Tasking::TreeStorageBase &storage, - StorageVoidHandler setupHandler, - StorageVoidHandler doneHandler); - template - StorageVoidHandler wrapHandler(StorageHandler &&handler) { - return [=](void *voidStruct) { - StorageStruct *storageStruct = static_cast(voidStruct); - std::invoke(handler, storageStruct); - }; - } - - friend class TaskTreePrivate; - TaskTreePrivate *d; -}; - -class QTCREATOR_UTILS_EXPORT TaskTreeAdapter : public Tasking::TaskAdapter -{ -public: - TaskTreeAdapter(); - void start() final; -}; - -} // namespace Utils - -#define QTC_DECLARE_CUSTOM_TASK(CustomTaskName, TaskAdapterClass)\ -namespace Utils::Tasking { using CustomTaskName = CustomTask; } - -#define QTC_DECLARE_CUSTOM_TEMPLATE_TASK(CustomTaskName, TaskAdapterClass)\ -namespace Utils::Tasking {\ -template \ -using CustomTaskName = CustomTask>;\ -} // namespace Utils::Tasking - -QTC_DECLARE_CUSTOM_TASK(Tree, Utils::TaskTreeAdapter); diff --git a/src/libs/utils/terminalcommand.cpp b/src/libs/utils/terminalcommand.cpp index c25b379f321..102fc42e05d 100644 --- a/src/libs/utils/terminalcommand.cpp +++ b/src/libs/utils/terminalcommand.cpp @@ -65,13 +65,7 @@ TerminalCommand TerminalCommand::defaultTerminalEmulator() if (defaultTerm.command.isEmpty()) { if (HostOsInfo::isMacHost()) { - const FilePath termCmd = FilePath::fromString(QCoreApplication::applicationDirPath()) - / "../Resources/scripts/openTerminal.py"; - if (termCmd.exists()) - defaultTerm = {termCmd, "", ""}; - else - defaultTerm = {"/usr/X11/bin/xterm", "", "-e"}; - + return {"Terminal.app", "", ""}; } else if (HostOsInfo::isAnyUnixHost()) { defaultTerm = {"xterm", "", "-e"}; const Environment env = Environment::systemEnvironment(); diff --git a/src/libs/utils/terminalhooks.cpp b/src/libs/utils/terminalhooks.cpp new file mode 100644 index 00000000000..3bda25b109c --- /dev/null +++ b/src/libs/utils/terminalhooks.cpp @@ -0,0 +1,129 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "terminalhooks.h" + +#include "externalterminalprocessimpl.h" +#include "filepath.h" +#include "process.h" + +#include + +namespace Utils::Terminal { + +FilePath defaultShellForDevice(const FilePath &deviceRoot) +{ + if (deviceRoot.osType() == OsTypeWindows) + return deviceRoot.withNewPath("cmd.exe").searchInPath(); + + const Environment env = deviceRoot.deviceEnvironment(); + FilePath shell = FilePath::fromUserInput(env.value_or("SHELL", "/bin/sh")); + + if (!shell.isAbsolutePath()) + shell = env.searchInPath(shell.nativePath()); + + if (shell.isEmpty()) + return shell; + + return deviceRoot.withNewMappedPath(shell); +} + +class HooksPrivate +{ +public: + HooksPrivate() + : m_getTerminalCommandsForDevicesHook([] { return QList{}; }) + { + auto openTerminal = [](const OpenTerminalParameters ¶meters) { + DeviceFileHooks::instance().openTerminal(parameters.workingDirectory.value_or( + FilePath{}), + parameters.environment.value_or(Environment{})); + }; + auto createProcessInterface = []() { return new ExternalTerminalProcessImpl(); }; + + addCallbackSet("External", {openTerminal, createProcessInterface}); + } + + void addCallbackSet(const QString &name, const Hooks::CallbackSet &callbackSet) + { + QMutexLocker lk(&m_mutex); + m_callbackSets.push_back(qMakePair(name, callbackSet)); + + m_createTerminalProcessInterface + = m_callbackSets.back().second.createTerminalProcessInterface; + m_openTerminal = m_callbackSets.back().second.openTerminal; + } + + void removeCallbackSet(const QString &name) + { + if (name == "External") + return; + + QMutexLocker lk(&m_mutex); + m_callbackSets.removeIf([name](const auto &pair) { return pair.first == name; }); + + m_createTerminalProcessInterface + = m_callbackSets.back().second.createTerminalProcessInterface; + m_openTerminal = m_callbackSets.back().second.openTerminal; + } + + Hooks::CreateTerminalProcessInterface createTerminalProcessInterface() + { + QMutexLocker lk(&m_mutex); + return m_createTerminalProcessInterface; + } + + Hooks::OpenTerminal openTerminal() + { + QMutexLocker lk(&m_mutex); + return m_openTerminal; + } + + Hooks::GetTerminalCommandsForDevicesHook m_getTerminalCommandsForDevicesHook; + +private: + Hooks::OpenTerminal m_openTerminal; + Hooks::CreateTerminalProcessInterface m_createTerminalProcessInterface; + + QMutex m_mutex; + QList> m_callbackSets; +}; + +Hooks &Hooks::instance() +{ + static Hooks manager; + return manager; +} + +Hooks::Hooks() + : d(new HooksPrivate()) +{} + +Hooks::~Hooks() = default; + +void Hooks::openTerminal(const OpenTerminalParameters ¶meters) const +{ + d->openTerminal()(parameters); +} + +ProcessInterface *Hooks::createTerminalProcessInterface() const +{ + return d->createTerminalProcessInterface()(); +} + +Hooks::GetTerminalCommandsForDevicesHook &Hooks::getTerminalCommandsForDevicesHook() +{ + return d->m_getTerminalCommandsForDevicesHook; +} + +void Hooks::addCallbackSet(const QString &name, const CallbackSet &callbackSet) +{ + d->addCallbackSet(name, callbackSet); +} + +void Hooks::removeCallbackSet(const QString &name) +{ + d->removeCallbackSet(name); +} + +} // namespace Utils::Terminal diff --git a/src/libs/utils/terminalhooks.h b/src/libs/utils/terminalhooks.h new file mode 100644 index 00000000000..b37c51517c0 --- /dev/null +++ b/src/libs/utils/terminalhooks.h @@ -0,0 +1,91 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "commandline.h" +#include "environment.h" +#include "filepath.h" +#include "id.h" + +#include +#include + +namespace Utils { +class ProcessInterface; + +template +class Hook +{ + Q_DISABLE_COPY_MOVE(Hook) + +public: + using Callback = std::function; + +public: + Hook() = delete; + + explicit Hook(Callback defaultCallback) { set(defaultCallback); } + + void set(Callback cb) { m_callback = cb; } + R operator()(Params &&...params) { return m_callback(std::forward(params)...); } + +private: + Callback m_callback; +}; + +namespace Terminal { +class HooksPrivate; + +enum class ExitBehavior { Close, Restart, Keep }; + +struct OpenTerminalParameters +{ + std::optional shellCommand; + std::optional workingDirectory; + std::optional environment; + ExitBehavior m_exitBehavior{ExitBehavior::Close}; + std::optional identifier{std::nullopt}; +}; + +struct NameAndCommandLine +{ + QString name; + CommandLine commandLine; +}; + +QTCREATOR_UTILS_EXPORT FilePath defaultShellForDevice(const FilePath &deviceRoot); + +class QTCREATOR_UTILS_EXPORT Hooks +{ +public: + using OpenTerminal = std::function; + using CreateTerminalProcessInterface = std::function; + + struct CallbackSet + { + OpenTerminal openTerminal; + CreateTerminalProcessInterface createTerminalProcessInterface; + }; + + using GetTerminalCommandsForDevicesHook = Hook>; + +public: + static Hooks &instance(); + ~Hooks(); + + GetTerminalCommandsForDevicesHook &getTerminalCommandsForDevicesHook(); + + void openTerminal(const OpenTerminalParameters ¶meters) const; + ProcessInterface *createTerminalProcessInterface() const; + + void addCallbackSet(const QString &name, const CallbackSet &callbackSet); + void removeCallbackSet(const QString &name); + +private: + Hooks(); + std::unique_ptr d; +}; + +} // namespace Terminal +} // namespace Utils diff --git a/src/libs/utils/terminalinterface.cpp b/src/libs/utils/terminalinterface.cpp new file mode 100644 index 00000000000..6767c16c61f --- /dev/null +++ b/src/libs/utils/terminalinterface.cpp @@ -0,0 +1,433 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "terminalinterface.h" + +#include "utilstr.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(terminalInterfaceLog, "qtc.terminalinterface", QtWarningMsg) + +namespace Utils { + +static QString msgCommChannelFailed(const QString &error) +{ + return Tr::tr("Cannot set up communication channel: %1").arg(error); +} + +static QString msgCannotCreateTempFile(const QString &why) +{ + return Tr::tr("Cannot create temporary file: %1").arg(why); +} + +static QString msgCannotWriteTempFile() +{ + return Tr::tr("Cannot write temporary file. Disk full?"); +} + +static QString msgCannotCreateTempDir(const QString &dir, const QString &why) +{ + return Tr::tr("Cannot create temporary directory \"%1\": %2").arg(dir, why); +} + +static QString msgUnexpectedOutput(const QByteArray &what) +{ + return Tr::tr("Unexpected output from helper program (%1).").arg(QString::fromLatin1(what)); +} + +static QString msgCannotChangeToWorkDir(const FilePath &dir, const QString &why) +{ + return Tr::tr("Cannot change to working directory \"%1\": %2").arg(dir.toString(), why); +} + +static QString msgCannotExecute(const QString &p, const QString &why) +{ + return Tr::tr("Cannot execute \"%1\": %2").arg(p, why); +} + +static QString msgPromptToClose() +{ + // Shown in a terminal which might have a different character set on Windows. + return Tr::tr("Press to close this window..."); +} + +class TerminalInterfacePrivate : public QObject +{ + Q_OBJECT +public: + TerminalInterfacePrivate(TerminalInterface *p, bool waitOnExit) + : q(p) + , waitOnExit(waitOnExit) + { + connect(&stubServer, + &QLocalServer::newConnection, + q, + &TerminalInterface::onNewStubConnection); + } + +public: + QLocalServer stubServer; + QLocalSocket *stubSocket = nullptr; + + int stubProcessId = 0; + int inferiorProcessId = 0; + int inferiorThreadId = 0; + + std::unique_ptr envListFile; + QTemporaryDir tempDir; + + std::unique_ptr stubConnectTimeoutTimer; + + ProcessResultData processResultData; + TerminalInterface *q; + + StubCreator *stubCreator{nullptr}; + + const bool waitOnExit; +}; + +TerminalInterface::TerminalInterface(bool waitOnExit) + : d(new TerminalInterfacePrivate(this, waitOnExit)) +{} + +TerminalInterface::~TerminalInterface() +{ + if (d->stubSocket && d->stubSocket->state() == QLocalSocket::ConnectedState) { + if (d->inferiorProcessId) + killInferiorProcess(); + killStubProcess(); + } + if (d->stubCreator) + d->stubCreator->deleteLater(); + delete d; +} + +void TerminalInterface::setStubCreator(StubCreator *creator) +{ + d->stubCreator = creator; +} + +int TerminalInterface::inferiorProcessId() const +{ + return d->inferiorProcessId; +} + +int TerminalInterface::inferiorThreadId() const +{ + return d->inferiorThreadId; +} + +static QString errnoToString(int code) +{ + return QString::fromLocal8Bit(strerror(code)); +} + +void TerminalInterface::onNewStubConnection() +{ + d->stubConnectTimeoutTimer.reset(); + + d->stubSocket = d->stubServer.nextPendingConnection(); + if (!d->stubSocket) + return; + + connect(d->stubSocket, &QIODevice::readyRead, this, &TerminalInterface::onStubReadyRead); + + if (HostOsInfo::isAnyUnixHost()) + connect(d->stubSocket, &QLocalSocket::disconnected, this, &TerminalInterface::onStubExited); +} + +void TerminalInterface::onStubExited() +{ + // The stub exit might get noticed before we read the pid for the kill on Windows + // or the error status elsewhere. + if (d->stubSocket && d->stubSocket->state() == QLocalSocket::ConnectedState) + d->stubSocket->waitForDisconnected(); + + shutdownStubServer(); + d->envListFile.reset(); + + if (d->inferiorProcessId) + emitFinished(-1, QProcess::CrashExit); +} + +void TerminalInterface::onStubReadyRead() +{ + while (d->stubSocket && d->stubSocket->canReadLine()) { + QByteArray out = d->stubSocket->readLine(); + out.chop(1); // remove newline + if (out.startsWith("err:chdir ")) { + emitError(QProcess::FailedToStart, + msgCannotChangeToWorkDir(m_setup.m_workingDirectory, + errnoToString(out.mid(10).toInt()))); + } else if (out.startsWith("err:exec ")) { + emitError(QProcess::FailedToStart, + msgCannotExecute(m_setup.m_commandLine.executable().toString(), + errnoToString(out.mid(9).toInt()))); + } else if (out.startsWith("spid ")) { + d->envListFile.reset(); + d->envListFile = nullptr; + } else if (out.startsWith("pid ")) { + d->inferiorProcessId = out.mid(4).toInt(); + emit started(d->inferiorProcessId, d->inferiorThreadId); + } else if (out.startsWith("thread ")) { // Windows only + d->inferiorThreadId = out.mid(7).toLongLong(); + } else if (out.startsWith("exit ")) { + emitFinished(out.mid(5).toInt(), QProcess::NormalExit); + } else if (out.startsWith("crash ")) { + emitFinished(out.mid(6).toInt(), QProcess::CrashExit); + } else { + emitError(QProcess::UnknownError, msgUnexpectedOutput(out)); + break; + } + } +} + +expected_str TerminalInterface::startStubServer() +{ + if (HostOsInfo::isWindowsHost()) { + if (d->stubServer.listen(QString::fromLatin1("creator-%1-%2") + .arg(QCoreApplication::applicationPid()) + .arg(rand()))) + return {}; + return make_unexpected(d->stubServer.errorString()); + } + + // We need to put the socket in a private directory, as some systems simply do not + // check the file permissions of sockets. + if (!QDir(d->tempDir.path()) + .mkdir("socket")) { // QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner + return make_unexpected(msgCannotCreateTempDir(d->tempDir.filePath("socket"), + QString::fromLocal8Bit(strerror(errno)))); + } + + if (!QFile::setPermissions(d->tempDir.filePath("socket"), + QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)) { + return make_unexpected(Tr::tr("Cannot set permissions on temporary directory \"%1\": %2") + .arg(d->tempDir.filePath("socket")) + .arg(QString::fromLocal8Bit(strerror(errno)))); + } + + const QString socketPath = d->tempDir.filePath("socket/stub-socket"); + if (!d->stubServer.listen(socketPath)) { + return make_unexpected( + Tr::tr("Cannot create socket \"%1\": %2").arg(socketPath, d->stubServer.errorString())); + } + return {}; +} + +void TerminalInterface::shutdownStubServer() +{ + if (d->stubSocket) { + // Read potentially remaining data + onStubReadyRead(); + // avoid getting queued readyRead signals + d->stubSocket->disconnect(); + // we might be called from the disconnected signal of stubSocket + d->stubSocket->deleteLater(); + } + d->stubSocket = nullptr; + if (d->stubServer.isListening()) + d->stubServer.close(); +} + +void TerminalInterface::emitError(QProcess::ProcessError error, const QString &errorString) +{ + d->processResultData.m_error = error; + d->processResultData.m_errorString = errorString; + if (error == QProcess::FailedToStart) + emit done(d->processResultData); +} + +void TerminalInterface::emitFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + d->inferiorProcessId = 0; + d->inferiorThreadId = 0; + d->processResultData.m_exitCode = exitCode; + d->processResultData.m_exitStatus = exitStatus; + emit done(d->processResultData); +} + +bool TerminalInterface::isRunning() const +{ + return d->stubSocket && d->stubSocket->isOpen(); +} + +void TerminalInterface::cleanupAfterStartFailure(const QString &errorMessage) +{ + shutdownStubServer(); + emitError(QProcess::FailedToStart, errorMessage); + d->envListFile.reset(); +} + +void TerminalInterface::sendCommand(char c) +{ + if (d->stubSocket && d->stubSocket->isWritable()) { + d->stubSocket->write(&c, 1); + d->stubSocket->flush(); + } +} + +void TerminalInterface::killInferiorProcess() +{ + sendCommand('k'); + if (d->stubSocket) + d->stubSocket->waitForReadyRead(); +} + +void TerminalInterface::killStubProcess() +{ + if (!isRunning()) + return; + + sendCommand('s'); + if (d->stubSocket) + d->stubSocket->waitForReadyRead(); + shutdownStubServer(); +} + +void TerminalInterface::start() +{ + if (isRunning()) + return; + + if (m_setup.m_terminalMode == TerminalMode::Detached) { + expected_str result; + QMetaObject::invokeMethod( + d->stubCreator, + [this, &result] { result = d->stubCreator->startStubProcess(m_setup); }, + d->stubCreator->thread() == QThread::currentThread() ? Qt::DirectConnection + : Qt::BlockingQueuedConnection); + + if (result) { + emit started(*result, 0); + emitFinished(0, QProcess::NormalExit); + } else { + emitError(QProcess::FailedToStart, result.error()); + } + return; + } + + const expected_str result = startStubServer(); + if (!result) { + emitError(QProcess::FailedToStart, msgCommChannelFailed(result.error())); + return; + } + + Environment finalEnv = m_setup.m_environment; + + if (HostOsInfo::isWindowsHost()) { + if (!finalEnv.hasKey("PATH")) { + const QString path = qtcEnvironmentVariable("PATH"); + if (!path.isEmpty()) + finalEnv.set("PATH", path); + } + if (!finalEnv.hasKey("SystemRoot")) { + const QString systemRoot = qtcEnvironmentVariable("SystemRoot"); + if (!systemRoot.isEmpty()) + finalEnv.set("SystemRoot", systemRoot); + } + } else if (HostOsInfo::isMacHost()) { + finalEnv.set("TERM", "xterm-256color"); + } + + if (finalEnv.hasChanges()) { + d->envListFile = std::make_unique(this); + if (!d->envListFile->open()) { + cleanupAfterStartFailure(msgCannotCreateTempFile(d->envListFile->errorString())); + return; + } + QTextStream stream(d->envListFile.get()); + finalEnv.forEachEntry([&stream](const QString &key, const QString &value, bool) { + stream << key << '=' << value << '\0'; + }); + + if (d->envListFile->error() != QFile::NoError) { + cleanupAfterStartFailure(msgCannotWriteTempFile()); + return; + } + } + + const FilePath stubPath = FilePath::fromUserInput(QCoreApplication::applicationDirPath()) + .pathAppended(QLatin1String(RELATIVE_LIBEXEC_PATH)) + .pathAppended((HostOsInfo::isWindowsHost() + ? QLatin1String("qtcreator_process_stub.exe") + : QLatin1String("qtcreator_process_stub"))); + + CommandLine cmd{stubPath, {"-s", d->stubServer.fullServerName()}}; + + if (!m_setup.m_workingDirectory.isEmpty()) + cmd.addArgs({"-w", m_setup.m_workingDirectory.nativePath()}); + + if (m_setup.m_terminalMode == TerminalMode::Debug) + cmd.addArg("-d"); + + if (terminalInterfaceLog().isDebugEnabled()) + cmd.addArg("-v"); + + if (d->envListFile) + cmd.addArgs({"-e", d->envListFile->fileName()}); + + cmd.addArgs({"--wait", d->waitOnExit ? msgPromptToClose() : ""}); + + cmd.addArgs({"--", m_setup.m_commandLine.executable().nativePath()}); + cmd.addArgs(m_setup.m_commandLine.arguments(), CommandLine::Raw); + + QTC_ASSERT(d->stubCreator, return); + + ProcessSetupData stubSetupData = m_setup; + stubSetupData.m_commandLine = cmd; + + QMetaObject::invokeMethod( + d->stubCreator, + [stubSetupData, this] { d->stubCreator->startStubProcess(stubSetupData); }, + d->stubCreator->thread() == QThread::currentThread() ? Qt::DirectConnection + : Qt::BlockingQueuedConnection); + + d->stubConnectTimeoutTimer = std::make_unique(); + + connect(d->stubConnectTimeoutTimer.get(), &QTimer::timeout, this, [this] { + killInferiorProcess(); + killStubProcess(); + }); + d->stubConnectTimeoutTimer->setSingleShot(true); + d->stubConnectTimeoutTimer->start(10000); +} + +qint64 TerminalInterface::write(const QByteArray &data) +{ + Q_UNUSED(data); + QTC_CHECK(false); + return -1; +} +void TerminalInterface::sendControlSignal(ControlSignal controlSignal) +{ + QTC_ASSERT(m_setup.m_terminalMode != TerminalMode::Detached, return); + + switch (controlSignal) { + case ControlSignal::Terminate: + case ControlSignal::Kill: + killInferiorProcess(); + break; + case ControlSignal::Interrupt: + sendCommand('i'); + break; + case ControlSignal::KickOff: + sendCommand('c'); + break; + case ControlSignal::CloseWriteChannel: + QTC_CHECK(false); + break; + } +} + +} // namespace Utils + +#include "terminalinterface.moc" diff --git a/src/libs/utils/terminalinterface.h b/src/libs/utils/terminalinterface.h new file mode 100644 index 00000000000..a1960e7b966 --- /dev/null +++ b/src/libs/utils/terminalinterface.h @@ -0,0 +1,61 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "commandline.h" +#include "expected.h" +#include "processinterface.h" + +namespace Utils { + +class TerminalInterfacePrivate; + +class StubCreator : public QObject +{ +public: + virtual expected_str startStubProcess(const ProcessSetupData &setup) = 0; +}; + +class QTCREATOR_UTILS_EXPORT TerminalInterface : public ProcessInterface +{ + friend class TerminalInterfacePrivate; + friend class StubCreator; + +public: + TerminalInterface(bool waitOnExit = true); + ~TerminalInterface() override; + + int inferiorProcessId() const; + int inferiorThreadId() const; + + void setStubCreator(StubCreator *creator); + + void emitError(QProcess::ProcessError error, const QString &errorString); + void emitFinished(int exitCode, QProcess::ExitStatus exitStatus); + void onStubExited(); + +protected: + void onNewStubConnection(); + void onStubReadyRead(); + + void sendCommand(char c); + + void killInferiorProcess(); + void killStubProcess(); + + expected_str startStubServer(); + void shutdownStubServer(); + void cleanupAfterStartFailure(const QString &errorMessage); + + bool isRunning() const; + +private: + void start() override; + qint64 write(const QByteArray &data) override; + void sendControlSignal(ControlSignal controlSignal) override; + + TerminalInterfacePrivate *d{nullptr}; +}; + +} // namespace Utils diff --git a/src/libs/utils/terminalprocess.cpp b/src/libs/utils/terminalprocess.cpp deleted file mode 100644 index bd0333c9d74..00000000000 --- a/src/libs/utils/terminalprocess.cpp +++ /dev/null @@ -1,721 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "terminalprocess_p.h" - -#include "commandline.h" -#include "environment.h" -#include "hostosinfo.h" -#include "qtcassert.h" -#include "qtcprocess.h" -#include "terminalcommand.h" -#include "utilstr.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef Q_OS_WIN - -#include "winutils.h" - -#include -#include -#include - -#else - -#include -#include -#include -#include -#include - -#endif - -namespace Utils { -namespace Internal { - -static QString modeOption(TerminalMode m) -{ - switch (m) { - case TerminalMode::Run: - return QLatin1String("run"); - case TerminalMode::Debug: - return QLatin1String("debug"); - case TerminalMode::Suspend: - return QLatin1String("suspend"); - case TerminalMode::Off: - QTC_CHECK(false); - break; - } - return {}; -} - -static QString msgCommChannelFailed(const QString &error) -{ - return Tr::tr("Cannot set up communication channel: %1").arg(error); -} - -static QString msgPromptToClose() -{ - // Shown in a terminal which might have a different character set on Windows. - return Tr::tr("Press to close this window..."); -} - -static QString msgCannotCreateTempFile(const QString &why) -{ - return Tr::tr("Cannot create temporary file: %1").arg(why); -} - -static QString msgCannotWriteTempFile() -{ - return Tr::tr("Cannot write temporary file. Disk full?"); -} - -static QString msgCannotCreateTempDir(const QString & dir, const QString &why) -{ - return Tr::tr("Cannot create temporary directory \"%1\": %2").arg(dir, why); -} - -static QString msgUnexpectedOutput(const QByteArray &what) -{ - return Tr::tr("Unexpected output from helper program (%1).") - .arg(QString::fromLatin1(what)); -} - -static QString msgCannotChangeToWorkDir(const FilePath &dir, const QString &why) -{ - return Tr::tr("Cannot change to working directory \"%1\": %2").arg(dir.toString(), why); -} - -static QString msgCannotExecute(const QString & p, const QString &why) -{ - return Tr::tr("Cannot execute \"%1\": %2").arg(p, why); -} - -class TerminalProcessPrivate -{ -public: - TerminalProcessPrivate(QObject *parent) - : m_stubServer(parent) - , m_process(parent) {} - - qint64 m_processId = 0; - ProcessResultData m_result; - QLocalServer m_stubServer; - QLocalSocket *m_stubSocket = nullptr; - QTemporaryFile *m_tempFile = nullptr; - - // Used on Unix only - QtcProcess m_process; - QTimer *m_stubConnectTimer = nullptr; - QByteArray m_stubServerDir; - - // Used on Windows only - qint64 m_appMainThreadId = 0; - -#ifdef Q_OS_WIN - PROCESS_INFORMATION *m_pid = nullptr; - HANDLE m_hInferior = NULL; - QWinEventNotifier *inferiorFinishedNotifier = nullptr; - QWinEventNotifier *processFinishedNotifier = nullptr; -#endif -}; - -TerminalImpl::TerminalImpl() - : d(new TerminalProcessPrivate(this)) -{ - connect(&d->m_stubServer, &QLocalServer::newConnection, - this, &TerminalImpl::stubConnectionAvailable); - - d->m_process.setProcessChannelMode(QProcess::ForwardedChannels); -} - -TerminalImpl::~TerminalImpl() -{ - stopProcess(); - delete d; -} - -void TerminalImpl::start() -{ - if (isRunning()) - return; - - d->m_result = {}; - -#ifdef Q_OS_WIN - - QString pcmd; - QString pargs; - if (m_setup.m_terminalMode != TerminalMode::Run) { // The debugger engines already pre-process the arguments. - pcmd = m_setup.m_commandLine.executable().toString(); - pargs = m_setup.m_commandLine.arguments(); - } else { - ProcessArgs outArgs; - ProcessArgs::prepareCommand(m_setup.m_commandLine, &pcmd, &outArgs, - &m_setup.m_environment, &m_setup.m_workingDirectory); - pargs = outArgs.toWindowsArgs(); - } - - const QString err = stubServerListen(); - if (!err.isEmpty()) { - emitError(QProcess::FailedToStart, msgCommChannelFailed(err)); - return; - } - - QStringList env = m_setup.m_environment.toStringList(); - if (!env.isEmpty()) { - d->m_tempFile = new QTemporaryFile(); - if (!d->m_tempFile->open()) { - cleanupAfterStartFailure(msgCannotCreateTempFile(d->m_tempFile->errorString())); - return; - } - QString outString; - QTextStream out(&outString); - // Add PATH and SystemRoot environment variables in case they are missing - const QStringList fixedEnvironment = [env] { - QStringList envStrings = env; - // add PATH if necessary (for DLL loading) - if (envStrings.filter(QRegularExpression("^PATH=.*", QRegularExpression::CaseInsensitiveOption)).isEmpty()) { - const QString path = qtcEnvironmentVariable("PATH"); - if (!path.isEmpty()) - envStrings.prepend(QString::fromLatin1("PATH=%1").arg(path)); - } - // add systemroot if needed - if (envStrings.filter(QRegularExpression("^SystemRoot=.*", QRegularExpression::CaseInsensitiveOption)).isEmpty()) { - const QString systemRoot = qtcEnvironmentVariable("SystemRoot"); - if (!systemRoot.isEmpty()) - envStrings.prepend(QString::fromLatin1("SystemRoot=%1").arg(systemRoot)); - } - return envStrings; - }(); - - for (const QString &var : fixedEnvironment) - out << var << QChar(0); - out << QChar(0); - const QTextCodec *textCodec = QTextCodec::codecForName("UTF-16LE"); - QTC_CHECK(textCodec); - const QByteArray outBytes = textCodec ? textCodec->fromUnicode(outString) : QByteArray(); - if (!textCodec || d->m_tempFile->write(outBytes) < 0) { - cleanupAfterStartFailure(msgCannotWriteTempFile()); - return; - } - d->m_tempFile->flush(); - } - - STARTUPINFO si; - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - - d->m_pid = new PROCESS_INFORMATION; - ZeroMemory(d->m_pid, sizeof(PROCESS_INFORMATION)); - - QString workDir = m_setup.m_workingDirectory.toUserOutput(); - if (!workDir.isEmpty() && !workDir.endsWith(QLatin1Char('\\'))) - workDir.append(QLatin1Char('\\')); - - // Quote a Windows command line correctly for the "CreateProcess" API - static const auto quoteWinCommand = [](const QString &program) { - const QChar doubleQuote = QLatin1Char('"'); - - // add the program as the first arg ... it works better - QString programName = program; - programName.replace(QLatin1Char('/'), QLatin1Char('\\')); - if (!programName.startsWith(doubleQuote) && !programName.endsWith(doubleQuote) - && programName.contains(QLatin1Char(' '))) { - programName.prepend(doubleQuote); - programName.append(doubleQuote); - } - return programName; - }; - static const auto quoteWinArgument = [](const QString &arg) { - if (arg.isEmpty()) - return QString::fromLatin1("\"\""); - - QString ret(arg); - // Quotes are escaped and their preceding backslashes are doubled. - ret.replace(QRegularExpression("(\\\\*)\""), "\\1\\1\\\""); - if (ret.contains(QRegularExpression("\\s"))) { - // The argument must not end with a \ since this would be interpreted - // as escaping the quote -- rather put the \ behind the quote: e.g. - // rather use "foo"\ than "foo\" - int i = ret.length(); - while (i > 0 && ret.at(i - 1) == QLatin1Char('\\')) - --i; - ret.insert(i, QLatin1Char('"')); - ret.prepend(QLatin1Char('"')); - } - return ret; - }; - static const auto createWinCommandlineMultiArgs = [](const QString &program, const QStringList &args) { - QString programName = quoteWinCommand(program); - for (const QString &arg : args) { - programName += QLatin1Char(' '); - programName += quoteWinArgument(arg); - } - return programName; - }; - static const auto createWinCommandlineSingleArg = [](const QString &program, const QString &args) - { - QString programName = quoteWinCommand(program); - if (!args.isEmpty()) { - programName += QLatin1Char(' '); - programName += args; - } - return programName; - }; - - QStringList stubArgs; - stubArgs << modeOption(m_setup.m_terminalMode) - << d->m_stubServer.fullServerName() - << workDir - << (d->m_tempFile ? d->m_tempFile->fileName() : QString()) - << createWinCommandlineSingleArg(pcmd, pargs) - << msgPromptToClose(); - - const QString cmdLine = createWinCommandlineMultiArgs( - QCoreApplication::applicationDirPath() + QLatin1String("/qtcreator_process_stub.exe"), stubArgs); - - bool success = CreateProcessW(0, (WCHAR*)cmdLine.utf16(), - 0, 0, FALSE, CREATE_NEW_CONSOLE, - 0, 0, - &si, d->m_pid); - - if (!success) { - delete d->m_pid; - d->m_pid = nullptr; - const QString msg = Tr::tr("The process \"%1\" could not be started: %2") - .arg(cmdLine, winErrorMessage(GetLastError())); - cleanupAfterStartFailure(msg); - return; - } - - d->processFinishedNotifier = new QWinEventNotifier(d->m_pid->hProcess, this); - connect(d->processFinishedNotifier, &QWinEventNotifier::activated, - this, &TerminalImpl::stubExited); - -#else - - ProcessArgs::SplitError perr; - ProcessArgs pargs = ProcessArgs::prepareArgs(m_setup.m_commandLine.arguments(), - &perr, - HostOsInfo::hostOs(), - &m_setup.m_environment, - &m_setup.m_workingDirectory, - m_setup.m_abortOnMetaChars); - - QString pcmd; - if (perr == ProcessArgs::SplitOk) { - pcmd = m_setup.m_commandLine.executable().toString(); - } else { - if (perr != ProcessArgs::FoundMeta) { - emitError(QProcess::FailedToStart, Tr::tr("Quoting error in command.")); - return; - } - if (m_setup.m_terminalMode == TerminalMode::Debug) { - // FIXME: QTCREATORBUG-2809 - emitError(QProcess::FailedToStart, - Tr::tr("Debugging complex shell commands in a terminal" - " is currently not supported.")); - return; - } - pcmd = qtcEnvironmentVariable("SHELL", "/bin/sh"); - pargs = ProcessArgs::createUnixArgs( - {"-c", (ProcessArgs::quoteArg(m_setup.m_commandLine.executable().toString()) - + ' ' + m_setup.m_commandLine.arguments())}); - } - - ProcessArgs::SplitError qerr; - const TerminalCommand terminal = TerminalCommand::terminalEmulator(); - const ProcessArgs terminalArgs = ProcessArgs::prepareArgs(terminal.executeArgs, - &qerr, - HostOsInfo::hostOs(), - &m_setup.m_environment, - &m_setup.m_workingDirectory); - if (qerr != ProcessArgs::SplitOk) { - emitError(QProcess::FailedToStart, - qerr == ProcessArgs::BadQuoting - ? Tr::tr("Quoting error in terminal command.") - : Tr::tr("Terminal command may not be a shell command.")); - return; - } - - const QString err = stubServerListen(); - if (!err.isEmpty()) { - emitError(QProcess::FailedToStart, msgCommChannelFailed(err)); - return; - } - - m_setup.m_environment.unset(QLatin1String("TERM")); - - const QStringList env = m_setup.m_environment.toStringList(); - if (!env.isEmpty()) { - d->m_tempFile = new QTemporaryFile(this); - if (!d->m_tempFile->open()) { - cleanupAfterStartFailure(msgCannotCreateTempFile(d->m_tempFile->errorString())); - return; - } - QByteArray contents; - for (const QString &var : env) { - const QByteArray l8b = var.toLocal8Bit(); - contents.append(l8b.constData(), l8b.size() + 1); - } - if (d->m_tempFile->write(contents) != contents.size() || !d->m_tempFile->flush()) { - cleanupAfterStartFailure(msgCannotWriteTempFile()); - return; - } - } - - const QString stubPath = QCoreApplication::applicationDirPath() - + QLatin1String("/" RELATIVE_LIBEXEC_PATH "/qtcreator_process_stub"); - - QStringList allArgs = terminalArgs.toUnixArgs(); - - allArgs << stubPath - << modeOption(m_setup.m_terminalMode) - << d->m_stubServer.fullServerName() - << msgPromptToClose() - << m_setup.m_workingDirectory.path() - << (d->m_tempFile ? d->m_tempFile->fileName() : QString()) - << QString::number(getpid()) - << pcmd - << pargs.toUnixArgs(); - - if (terminal.needsQuotes) - allArgs = QStringList { ProcessArgs::joinArgs(allArgs) }; - - d->m_process.setEnvironment(m_setup.m_environment); - d->m_process.setCommand({terminal.command, allArgs}); - d->m_process.setProcessImpl(m_setup.m_processImpl); - d->m_process.setReaperTimeout(m_setup.m_reaperTimeout); - - d->m_process.start(); - if (!d->m_process.waitForStarted()) { - const QString msg = Tr::tr("Cannot start the terminal emulator \"%1\", change the " - "setting in the Environment preferences. (%2)") - .arg(terminal.command.toUserOutput(), d->m_process.errorString()); - cleanupAfterStartFailure(msg); - return; - } - d->m_stubConnectTimer = new QTimer(this); - connect(d->m_stubConnectTimer, &QTimer::timeout, this, &TerminalImpl::stopProcess); - d->m_stubConnectTimer->setSingleShot(true); - d->m_stubConnectTimer->start(10000); - -#endif -} - -void TerminalImpl::cleanupAfterStartFailure(const QString &errorMessage) -{ - stubServerShutdown(); - emitError(QProcess::FailedToStart, errorMessage); - delete d->m_tempFile; - d->m_tempFile = nullptr; -} - -void TerminalImpl::sendControlSignal(ControlSignal controlSignal) -{ - switch (controlSignal) { - case ControlSignal::Terminate: - case ControlSignal::Kill: - killProcess(); - if (HostOsInfo::isWindowsHost()) - killStub(); - break; - case ControlSignal::Interrupt: - sendCommand('i'); - break; - case ControlSignal::KickOff: - sendCommand('c'); - break; - case ControlSignal::CloseWriteChannel: - QTC_CHECK(false); - break; - } -} - -void TerminalImpl::sendCommand(char c) -{ -#ifdef Q_OS_WIN - Q_UNUSED(c) -#else - if (d->m_stubSocket && d->m_stubSocket->isWritable()) { - d->m_stubSocket->write(&c, 1); - d->m_stubSocket->flush(); - } -#endif -} - -void TerminalImpl::killProcess() -{ -#ifdef Q_OS_WIN - if (d->m_hInferior != NULL) { - TerminateProcess(d->m_hInferior, (unsigned)-1); - cleanupInferior(); - } -#else - sendCommand('k'); -#endif - d->m_processId = 0; -} - -void TerminalImpl::killStub() -{ - if (!isRunning()) - return; - -#ifdef Q_OS_WIN - TerminateProcess(d->m_pid->hProcess, (unsigned)-1); - WaitForSingleObject(d->m_pid->hProcess, INFINITE); - cleanupStub(); - emitFinished(-1, QProcess::CrashExit); -#else - sendCommand('s'); - stubServerShutdown(); - d->m_process.stop(); - d->m_process.waitForFinished(); -#endif -} - -void TerminalImpl::stopProcess() -{ - killProcess(); - killStub(); -} - -bool TerminalImpl::isRunning() const -{ -#ifdef Q_OS_WIN - return d->m_pid != nullptr; -#else - return d->m_process.state() != QProcess::NotRunning - || (d->m_stubSocket && d->m_stubSocket->isOpen()); -#endif -} - -QString TerminalImpl::stubServerListen() -{ -#ifdef Q_OS_WIN - if (d->m_stubServer.listen(QString::fromLatin1("creator-%1-%2") - .arg(QCoreApplication::applicationPid()) - .arg(rand()))) - return QString(); - return d->m_stubServer.errorString(); -#else - // We need to put the socket in a private directory, as some systems simply do not - // check the file permissions of sockets. - QString stubFifoDir; - while (true) { - { - QTemporaryFile tf; - if (!tf.open()) - return msgCannotCreateTempFile(tf.errorString()); - stubFifoDir = tf.fileName(); - } - // By now the temp file was deleted again - d->m_stubServerDir = QFile::encodeName(stubFifoDir); - if (!::mkdir(d->m_stubServerDir.constData(), 0700)) - break; - if (errno != EEXIST) - return msgCannotCreateTempDir(stubFifoDir, QString::fromLocal8Bit(strerror(errno))); - } - const QString stubServer = stubFifoDir + QLatin1String("/stub-socket"); - if (!d->m_stubServer.listen(stubServer)) { - ::rmdir(d->m_stubServerDir.constData()); - return Tr::tr("Cannot create socket \"%1\": %2") - .arg(stubServer, d->m_stubServer.errorString()); - } - return {}; -#endif -} - -void TerminalImpl::stubServerShutdown() -{ -#ifdef Q_OS_WIN - delete d->m_stubSocket; - d->m_stubSocket = nullptr; - if (d->m_stubServer.isListening()) - d->m_stubServer.close(); -#else - if (d->m_stubSocket) { - readStubOutput(); // we could get the shutdown signal before emptying the buffer - d->m_stubSocket->disconnect(); // avoid getting queued readyRead signals - d->m_stubSocket->deleteLater(); // we might be called from the disconnected signal of m_stubSocket - } - d->m_stubSocket = nullptr; - if (d->m_stubServer.isListening()) { - d->m_stubServer.close(); - ::rmdir(d->m_stubServerDir.constData()); - } -#endif -} - -void TerminalImpl::stubConnectionAvailable() -{ - if (d->m_stubConnectTimer) { - delete d->m_stubConnectTimer; - d->m_stubConnectTimer = nullptr; - } - - d->m_stubSocket = d->m_stubServer.nextPendingConnection(); - connect(d->m_stubSocket, &QIODevice::readyRead, this, &TerminalImpl::readStubOutput); - - if (HostOsInfo::isAnyUnixHost()) - connect(d->m_stubSocket, &QLocalSocket::disconnected, this, &TerminalImpl::stubExited); -} - -static QString errorMsg(int code) -{ - return QString::fromLocal8Bit(strerror(code)); -} - -void TerminalImpl::readStubOutput() -{ - while (d->m_stubSocket->canReadLine()) { - QByteArray out = d->m_stubSocket->readLine(); -#ifdef Q_OS_WIN - out.chop(2); // \r\n - if (out.startsWith("err:chdir ")) { - emitError(QProcess::FailedToStart, - msgCannotChangeToWorkDir(m_setup.m_workingDirectory, winErrorMessage(out.mid(10).toInt()))); - } else if (out.startsWith("err:exec ")) { - emitError(QProcess::FailedToStart, - msgCannotExecute(m_setup.m_commandLine.executable().toUserOutput(), winErrorMessage(out.mid(9).toInt()))); - } else if (out.startsWith("thread ")) { // Windows only - // TODO: ensure that it comes before "pid " comes - d->m_appMainThreadId = out.mid(7).toLongLong(); - } else if (out.startsWith("pid ")) { - // Will not need it any more - delete d->m_tempFile; - d->m_tempFile = nullptr; - d->m_processId = out.mid(4).toLongLong(); - - d->m_hInferior = OpenProcess( - SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_TERMINATE, - FALSE, d->m_processId); - if (d->m_hInferior == NULL) { - emitError(QProcess::FailedToStart, - Tr::tr("Cannot obtain a handle to the inferior: %1") - .arg(winErrorMessage(GetLastError()))); - // Uhm, and now what? - continue; - } - d->inferiorFinishedNotifier = new QWinEventNotifier(d->m_hInferior, this); - connect(d->inferiorFinishedNotifier, &QWinEventNotifier::activated, this, [this] { - DWORD chldStatus; - - if (!GetExitCodeProcess(d->m_hInferior, &chldStatus)) - emitError(QProcess::UnknownError, - Tr::tr("Cannot obtain exit status from inferior: %1") - .arg(winErrorMessage(GetLastError()))); - cleanupInferior(); - emitFinished(chldStatus, QProcess::NormalExit); - }); - - emit started(d->m_processId, d->m_appMainThreadId); - } else { - emitError(QProcess::UnknownError, msgUnexpectedOutput(out)); - TerminateProcess(d->m_pid->hProcess, (unsigned)-1); - break; - } -#else - out.chop(1); // \n - if (out.startsWith("err:chdir ")) { - emitError(QProcess::FailedToStart, - msgCannotChangeToWorkDir(m_setup.m_workingDirectory, errorMsg(out.mid(10).toInt()))); - } else if (out.startsWith("err:exec ")) { - emitError(QProcess::FailedToStart, - msgCannotExecute(m_setup.m_commandLine.executable().toString(), errorMsg(out.mid(9).toInt()))); - } else if (out.startsWith("spid ")) { - delete d->m_tempFile; - d->m_tempFile = nullptr; - } else if (out.startsWith("pid ")) { - d->m_processId = out.mid(4).toInt(); - emit started(d->m_processId); - } else if (out.startsWith("exit ")) { - emitFinished(out.mid(5).toInt(), QProcess::NormalExit); - } else if (out.startsWith("crash ")) { - emitFinished(out.mid(6).toInt(), QProcess::CrashExit); - } else { - emitError(QProcess::UnknownError, msgUnexpectedOutput(out)); - d->m_process.terminate(); - break; - } -#endif - } // while -} - -void TerminalImpl::stubExited() -{ - // The stub exit might get noticed before we read the pid for the kill on Windows - // or the error status elsewhere. - if (d->m_stubSocket && d->m_stubSocket->state() == QLocalSocket::ConnectedState) - d->m_stubSocket->waitForDisconnected(); - -#ifdef Q_OS_WIN - cleanupStub(); - if (d->m_hInferior != NULL) { - TerminateProcess(d->m_hInferior, (unsigned)-1); - cleanupInferior(); - emitFinished(-1, QProcess::CrashExit); - } -#else - stubServerShutdown(); - delete d->m_tempFile; - d->m_tempFile = nullptr; - if (d->m_processId) - emitFinished(-1, QProcess::CrashExit); -#endif -} - -void TerminalImpl::cleanupInferior() -{ -#ifdef Q_OS_WIN - delete d->inferiorFinishedNotifier; - d->inferiorFinishedNotifier = nullptr; - CloseHandle(d->m_hInferior); - d->m_hInferior = NULL; -#endif -} - -void TerminalImpl::cleanupStub() -{ -#ifdef Q_OS_WIN - stubServerShutdown(); - delete d->processFinishedNotifier; - d->processFinishedNotifier = nullptr; - CloseHandle(d->m_pid->hThread); - CloseHandle(d->m_pid->hProcess); - delete d->m_pid; - d->m_pid = nullptr; - delete d->m_tempFile; - d->m_tempFile = nullptr; -#endif -} - -void TerminalImpl::emitError(QProcess::ProcessError error, const QString &errorString) -{ - d->m_result.m_error = error; - d->m_result.m_errorString = errorString; - if (error == QProcess::FailedToStart) - emit done(d->m_result); -} - -void TerminalImpl::emitFinished(int exitCode, QProcess::ExitStatus exitStatus) -{ - d->m_processId = 0; - d->m_result.m_exitCode = exitCode; - d->m_result.m_exitStatus = exitStatus; - emit done(d->m_result); -} - - -} // Internal -} // Utils diff --git a/src/libs/utils/terminalprocess_p.h b/src/libs/utils/terminalprocess_p.h deleted file mode 100644 index 27c99cee26e..00000000000 --- a/src/libs/utils/terminalprocess_p.h +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "processenums.h" -#include "processinterface.h" -#include "qtcassert.h" - -#include - -namespace Utils { - -class CommandLine; -class Environment; -class FilePath; - -namespace Internal { - -class TerminalImpl final : public ProcessInterface -{ -public: - TerminalImpl(); - ~TerminalImpl() final; - -private: - void start() final; - qint64 write(const QByteArray &) final { QTC_CHECK(false); return -1; } - void sendControlSignal(ControlSignal controlSignal) final; - - // OK, however, impl looks a bit different (!= NotRunning vs == Running). - // Most probably changing it into (== Running) should be OK. - bool isRunning() const; - - void stopProcess(); - void stubConnectionAvailable(); - void readStubOutput(); - void stubExited(); - void cleanupAfterStartFailure(const QString &errorMessage); - void killProcess(); - void killStub(); - void emitError(QProcess::ProcessError error, const QString &errorString); - void emitFinished(int exitCode, QProcess::ExitStatus exitStatus); - QString stubServerListen(); - void stubServerShutdown(); - void cleanupStub(); - void cleanupInferior(); - void sendCommand(char c); - - class TerminalProcessPrivate *d; -}; - -} // Internal -} // Utils diff --git a/src/libs/utils/textfieldcheckbox.cpp b/src/libs/utils/textfieldcheckbox.cpp index 5dae8538ae6..a00f840422a 100644 --- a/src/libs/utils/textfieldcheckbox.cpp +++ b/src/libs/utils/textfieldcheckbox.cpp @@ -7,6 +7,7 @@ namespace Utils { /*! \class Utils::TextFieldCheckBox + \inmodule QtCreator \brief The TextFieldCheckBox class is a aheckbox that plays with \c QWizard::registerField. diff --git a/src/libs/utils/textfieldcombobox.cpp b/src/libs/utils/textfieldcombobox.cpp index 5d72f523a57..790358a1df9 100644 --- a/src/libs/utils/textfieldcombobox.cpp +++ b/src/libs/utils/textfieldcombobox.cpp @@ -9,6 +9,7 @@ namespace Utils { /*! \class Utils::TextFieldComboBox + \inmodule QtCreator \brief The TextFieldComboBox class is a non-editable combo box for text editing purposes that plays with \c QWizard::registerField (providing a settable 'text' property). diff --git a/src/libs/utils/textfileformat.cpp b/src/libs/utils/textfileformat.cpp index 28fb243abf7..264c6c4351f 100644 --- a/src/libs/utils/textfileformat.cpp +++ b/src/libs/utils/textfileformat.cpp @@ -33,6 +33,7 @@ QDebug operator<<(QDebug d, const TextFileFormat &format) /*! \class Utils::TextFileFormat + \inmodule QtCreator \brief The TextFileFormat class describes the format of a text file and provides autodetection. @@ -51,7 +52,7 @@ QDebug operator<<(QDebug d, const TextFileFormat &format) TextFileFormat::TextFileFormat() = default; /*! - Detects the format of text data. + Detects the format of text \a data. */ TextFileFormat TextFileFormat::detect(const QByteArray &data) @@ -84,7 +85,8 @@ TextFileFormat TextFileFormat::detect(const QByteArray &data) } /*! - Returns a piece of text suitable as display for a encoding error. + Returns a piece of text specified by \a data suitable as display for + an encoding error. */ QByteArray TextFileFormat::decodingErrorSample(const QByteArray &data) @@ -152,7 +154,7 @@ bool decodeTextFileContent(const QByteArray &dataBA, } /*! - Decodes data to a plain string. + Returns \a data decoded to a plain string, \a target. */ bool TextFileFormat::decode(const QByteArray &data, QString *target) const @@ -162,7 +164,7 @@ bool TextFileFormat::decode(const QByteArray &data, QString *target) const } /*! - Decodes data to a list of strings. + Returns \a data decoded to a list of strings, \a target. Intended for use with progress bars loading large files. */ @@ -211,7 +213,12 @@ TextFileFormat::ReadResult readTextFile(const FilePath &filePath, const QTextCod } /*! - Reads a text file into a list of strings. + Reads a text file from \a filePath into a list of strings, \a plainTextList + using \a defaultCodec and text file format \a format. + + Returns whether decoding was possible without errors. If errors occur, + returns an error message, \a errorString and a sample error, + \a decodingErrorSample. */ TextFileFormat::ReadResult @@ -229,7 +236,11 @@ TextFileFormat::ReadResult } /*! - Reads a text file into a string. + Reads a text file from \a filePath into a string, \a plainText using + \a defaultCodec and text file format \a format. + + Returns whether decoding was possible without errors. + */ TextFileFormat::ReadResult @@ -278,7 +289,10 @@ TextFileFormat::ReadResult TextFileFormat::readFileUTF8(const FilePath &filePath } /*! - Writes out a text file. + Writes out a text file to \a filePath into a string, \a plainText. + + Returns whether decoding was possible without errors. If errors occur, + returns an error message, \a errorString. */ bool TextFileFormat::writeFile(const FilePath &filePath, QString plainText, QString *errorString) const diff --git a/src/libs/utils/textutils.cpp b/src/libs/utils/textutils.cpp index 1222038039f..28a9e12e1bb 100644 --- a/src/libs/utils/textutils.cpp +++ b/src/libs/utils/textutils.cpp @@ -2,40 +2,116 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "textutils.h" +#include "qtcassert.h" -#include +#include #include +#include -namespace Utils { -namespace Text { +namespace Utils::Text { + +bool Position::operator==(const Position &other) const +{ + return line == other.line && column == other.column; +} + +/*! + Returns the text position of a \a fileName and sets the \a postfixPos if + it can find a positional postfix. + + The following patterns are supported: \c {filepath.txt:19}, + \c{filepath.txt:19:12}, \c {filepath.txt+19}, + \c {filepath.txt+19+12}, and \c {filepath.txt(19)}. +*/ + +Position Position::fromFileName(QStringView fileName, int &postfixPos) +{ + static const auto regexp = QRegularExpression("[:+](\\d+)?([:+](\\d+)?)?$"); + // (10) MSVC-style + static const auto vsRegexp = QRegularExpression("[(]((\\d+)[)]?)?$"); + const QRegularExpressionMatch match = regexp.match(fileName); + Position pos; + if (match.hasMatch()) { + postfixPos = match.capturedStart(0); + if (match.lastCapturedIndex() > 0) { + pos.line = match.captured(1).toInt(); + if (match.lastCapturedIndex() > 2) // index 2 includes the + or : for the column number + pos.column = match.captured(3).toInt() - 1; //column is 0 based, despite line being 1 based + } + } else { + const QRegularExpressionMatch vsMatch = vsRegexp.match(fileName); + postfixPos = vsMatch.capturedStart(0); + if (vsMatch.lastCapturedIndex() > 1) // index 1 includes closing ) + pos.line = vsMatch.captured(2).toInt(); + } + if (pos.line > 0 && pos.column < 0) + pos.column = 0; // if we got a valid line make sure to return a valid TextPosition + return pos; +} + +Position Position::fromPositionInDocument(const QTextDocument *document, int pos) +{ + QTC_ASSERT(document, return {}); + const QTextBlock block = document->findBlock(pos); + if (block.isValid()) + return {block.blockNumber() + 1, pos - block.position()}; + + return {}; +} + +Position Position::fromCursor(const QTextCursor &c) +{ + return c.isNull() ? Position{} : Position{c.blockNumber() + 1, c.positionInBlock()}; +} + +int Range::length(const QString &text) const +{ + if (end.line < begin.line) + return -1; + + if (begin.line == end.line) + return end.column - begin.column; + + int index = 0; + int currentLine = 1; + while (currentLine < begin.line) { + index = text.indexOf(QChar::LineFeed, index); + if (index < 0) + return -1; + ++index; + ++currentLine; + } + const int beginIndex = index + begin.column; + while (currentLine < end.line) { + index = text.indexOf(QChar::LineFeed, index); + if (index < 0) + return -1; + ++index; + ++currentLine; + } + return index + end.column - beginIndex; +} + +bool Range::operator==(const Range &other) const +{ + return begin == other.begin && end == other.end; +} bool convertPosition(const QTextDocument *document, int pos, int *line, int *column) { QTextBlock block = document->findBlock(pos); if (!block.isValid()) { (*line) = -1; - (*column) = -1; + (*column) = 0; return false; } else { // line and column are both 1-based (*line) = block.blockNumber() + 1; - (*column) = pos - block.position() + 1; + (*column) = pos - block.position(); return true; } } -OptionalLineColumn convertPosition(const QTextDocument *document, int pos) -{ - OptionalLineColumn optional; - - QTextBlock block = document->findBlock(pos); - - if (block.isValid()) - optional.emplace(block.blockNumber() + 1, pos - block.position() + 1); - - return optional; -} - int positionInText(const QTextDocument *textDocument, int line, int column) { // Deduct 1 from line and column since they are 1-based. @@ -141,21 +217,6 @@ int utf8NthLineOffset(const QTextDocument *textDocument, const QByteArray &buffe return utf8Offset; } -LineColumn utf16LineColumn(const QByteArray &utf8Buffer, int utf8Offset) -{ - LineColumn lineColumn; - lineColumn.line = static_cast( - std::count(utf8Buffer.begin(), utf8Buffer.begin() + utf8Offset, '\n')) - + 1; - const int startOfLineOffset = utf8Offset ? (utf8Buffer.lastIndexOf('\n', utf8Offset - 1) + 1) - : 0; - lineColumn.column = QString::fromUtf8( - utf8Buffer.mid(startOfLineOffset, utf8Offset - startOfLineOffset)) - .length() - + 1; - return lineColumn; -} - QString utf16LineTextInUtf8Buffer(const QByteArray &utf8Buffer, int currentUtf8Offset) { const int lineStartUtf8Offset = currentUtf8Offset @@ -211,5 +272,10 @@ void applyReplacements(QTextDocument *doc, const Replacements &replacements) editCursor.endEditBlock(); } -} // Text -} // Utils +QDebug &operator<<(QDebug &stream, const Position &pos) +{ + stream << "line: " << pos.line << ", column: " << pos.column; + return stream; +} + +} // namespace Utils::Text diff --git a/src/libs/utils/textutils.h b/src/libs/utils/textutils.h index e214f746598..80e4150c1d2 100644 --- a/src/libs/utils/textutils.h +++ b/src/libs/utils/textutils.h @@ -5,8 +5,7 @@ #include "utils_global.h" -#include "linecolumn.h" - +#include #include QT_BEGIN_NAMESPACE @@ -17,6 +16,39 @@ QT_END_NAMESPACE namespace Utils { namespace Text { +class QTCREATOR_UTILS_EXPORT Position +{ +public: + int line = 0; // 1-based + int column = -1; // 0-based + + bool operator<(const Position &other) const + { return line < other.line || (line == other.line && column < other.column); } + bool operator==(const Position &other) const; + + bool operator!=(const Position &other) const { return !(operator==(other)); } + + bool isValid() const { return line > 0 && column >= 0; } + + static Position fromFileName(QStringView fileName, int &postfixPos); + static Position fromPositionInDocument(const QTextDocument *document, int pos); + static Position fromCursor(const QTextCursor &cursor); +}; + +class QTCREATOR_UTILS_EXPORT Range +{ +public: + int length(const QString &text) const; + + Position begin; + Position end; + + bool operator<(const Range &other) const { return begin < other.begin; } + bool operator==(const Range &other) const; + + bool operator!=(const Range &other) const { return !(operator==(other)); } +}; + struct Replacement { Replacement() = default; @@ -36,12 +68,10 @@ using Replacements = std::vector; QTCREATOR_UTILS_EXPORT void applyReplacements(QTextDocument *doc, const Replacements &replacements); -// line is 1-based, column is 1-based +// line is 1-based, column is 0-based QTCREATOR_UTILS_EXPORT bool convertPosition(const QTextDocument *document, int pos, int *line, int *column); -QTCREATOR_UTILS_EXPORT -OptionalLineColumn convertPosition(const QTextDocument *document, int pos); // line and column are 1-based QTCREATOR_UTILS_EXPORT int positionInText(const QTextDocument *textDocument, int line, int column); @@ -60,9 +90,13 @@ QTCREATOR_UTILS_EXPORT int utf8NthLineOffset(const QTextDocument *textDocument, const QByteArray &buffer, int line); -QTCREATOR_UTILS_EXPORT LineColumn utf16LineColumn(const QByteArray &utf8Buffer, int utf8Offset); QTCREATOR_UTILS_EXPORT QString utf16LineTextInUtf8Buffer(const QByteArray &utf8Buffer, int currentUtf8Offset); +QTCREATOR_UTILS_EXPORT QDebug &operator<<(QDebug &stream, const Position &pos); + } // Text } // Utils + +Q_DECLARE_METATYPE(Utils::Text::Position) +Q_DECLARE_METATYPE(Utils::Text::Range) diff --git a/src/libs/utils/theme/theme.h b/src/libs/utils/theme/theme.h index 46acd2cf06a..2604780a2d5 100644 --- a/src/libs/utils/theme/theme.h +++ b/src/libs/utils/theme/theme.h @@ -36,7 +36,6 @@ public: BadgeLabelBackgroundColorUnchecked, BadgeLabelTextColorChecked, BadgeLabelTextColorUnchecked, - CanceledSearchTextColor, ComboBoxArrowColor, ComboBoxArrowColorDisabled, ComboBoxTextColor, @@ -438,6 +437,28 @@ public: DSstatePanelBackground, DSstateHighlight, + + TerminalForeground, + TerminalBackground, + TerminalSelection, + TerminalFindMatch, + + TerminalAnsi0, + TerminalAnsi1, + TerminalAnsi2, + TerminalAnsi3, + TerminalAnsi4, + TerminalAnsi5, + TerminalAnsi6, + TerminalAnsi7, + TerminalAnsi8, + TerminalAnsi9, + TerminalAnsi10, + TerminalAnsi11, + TerminalAnsi12, + TerminalAnsi13, + TerminalAnsi14, + TerminalAnsi15, }; enum ImageFile { diff --git a/src/libs/utils/tooltip/tips.cpp b/src/libs/utils/tooltip/tips.cpp index ea20c735d80..180b8f960f2 100644 --- a/src/libs/utils/tooltip/tips.cpp +++ b/src/libs/utils/tooltip/tips.cpp @@ -133,9 +133,13 @@ TextTip::TextTip(QWidget *parent) : TipLabel(parent) setWindowOpacity(style()->styleHint(QStyle::SH_ToolTipLabel_Opacity, nullptr, this) / 255.0); } -static bool likelyContainsLink(const QString &s) +static bool likelyContainsLink(const QString &s, const Qt::TextFormat &format) { - return s.contains(QLatin1String("href"), Qt::CaseInsensitive); + if (s.contains(QLatin1String("href"), Qt::CaseInsensitive)) + return true; + if (format == Qt::MarkdownText) + return s.contains("]("); + return false; } void TextTip::setContent(const QVariant &content) @@ -148,13 +152,13 @@ void TextTip::setContent(const QVariant &content) m_format = item.second; } - bool containsLink = likelyContainsLink(m_text); + bool containsLink = likelyContainsLink(m_text, m_format); setOpenExternalLinks(containsLink); } bool TextTip::isInteractive() const { - return likelyContainsLink(m_text); + return likelyContainsLink(m_text, m_format); } void TextTip::configure(const QPoint &pos) diff --git a/src/libs/utils/treemodel.cpp b/src/libs/utils/treemodel.cpp index 344a77d837c..e5e5aa59b99 100644 --- a/src/libs/utils/treemodel.cpp +++ b/src/libs/utils/treemodel.cpp @@ -72,6 +72,7 @@ private: }; /*! + \internal Connect to all of the models signals. Whenever anything happens recheck everything. */ @@ -135,6 +136,7 @@ void ModelTest::runAllTests() } /*! + \internal nonDestructiveBasicTest tries to call a number of the basic functions (not all) to make sure the model doesn't outright segfault, testing the functions that makes sense. */ @@ -173,6 +175,7 @@ void ModelTest::nonDestructiveBasicTest() } /*! + \internal Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren() Models that are dynamically populated are not as fully tested here. @@ -200,6 +203,7 @@ void ModelTest::rowCount() } /*! + \internal Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren() */ void ModelTest::columnCount() @@ -218,6 +222,7 @@ void ModelTest::columnCount() } /*! + \internal Tests model's implementation of QAbstractItemModel::hasIndex() */ void ModelTest::hasIndex() @@ -242,6 +247,7 @@ void ModelTest::hasIndex() } /*! + \internal Tests model's implementation of QAbstractItemModel::index() */ void ModelTest::index() @@ -274,6 +280,7 @@ void ModelTest::index() } /*! + \internal Tests model's implementation of QAbstractItemModel::parent() */ void ModelTest::parent() @@ -322,6 +329,7 @@ void ModelTest::parent() } /*! + \internal Called from the parent() test. A model that returns an index of parent X should also return X when asking @@ -430,6 +438,7 @@ void ModelTest::checkChildren(const QModelIndex &parent, int currentDepth) } /*! + \internal Tests model's implementation of QAbstractItemModel::data() */ void ModelTest::data() @@ -494,6 +503,7 @@ void ModelTest::data() } /*! + \internal Store what is about to be inserted to make sure it actually happens \sa rowsInserted() @@ -510,6 +520,7 @@ void ModelTest::rowsAboutToBeInserted(const QModelIndex &parent, int start, int } /*! + \internal Confirm that what was said was going to happen actually did \sa rowsAboutToBeInserted() @@ -547,6 +558,7 @@ void ModelTest::layoutChanged() } /*! + \internal Store what is about to be inserted to make sure it actually happens \sa rowsRemoved() @@ -562,6 +574,7 @@ void ModelTest::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int e } /*! + \internal Confirm that what was said was going to happen actually did \sa rowsAboutToBeRemoved() @@ -895,6 +908,7 @@ void TreeItem::propagateModel(BaseTreeModel *m) /*! \class Utils::TreeModel + \inmodule QtCreator \brief The TreeModel class is a convienience base class for models to use in a QTreeView. diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index 451a9f34943..df23fc3ba24 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -35,7 +35,9 @@ Project { Depends { name: "Qt"; submodules: ["concurrent", "core-private", "network", "qml", "widgets", "xml"] } Depends { name: "Qt.macextras"; condition: Qt.core.versionMajor < 6 && qbs.targetOS.contains("macos") } + Depends { name: "Tasking" } Depends { name: "app_version_header" } + Depends { name: "ptyqt" } files: [ "QtConcurrentTools", @@ -48,8 +50,8 @@ Project { "archive.h", "aspects.cpp", "aspects.h", - "asynctask.cpp", - "asynctask.h", + "async.cpp", + "async.h", "basetreeview.cpp", "basetreeview.h", "benchmarker.cpp", @@ -108,6 +110,8 @@ Project { "execmenu.cpp", "execmenu.h", "executeondestruction.h", + "externalterminalprocessimpl.cpp", + "externalterminalprocessimpl.h", "fadingindicator.cpp", "fadingindicator.h", "faketooltip.cpp", @@ -126,6 +130,10 @@ Project { "filepath.h", "filesearch.cpp", "filesearch.h", + "filestreamer.cpp", + "filestreamer.h", + "filestreamermanager.cpp", + "filestreamermanager.h", "filesystemmodel.cpp", "filesystemmodel.h", "filesystemwatcher.cpp", @@ -178,8 +186,6 @@ Project { "launchersocket.h", "layoutbuilder.cpp", "layoutbuilder.h", - "linecolumn.cpp", - "linecolumn.h", "link.cpp", "link.h", "listmodel.h", @@ -232,6 +238,8 @@ Project { "port.h", "portlist.cpp", "portlist.h", + "process.cpp", + "process.h", "processenums.h", "processhandle.cpp", "processhandle.h", @@ -255,8 +263,6 @@ Project { "qtcassert.h", "qtcolorbutton.cpp", "qtcolorbutton.h", - "qtcprocess.cpp", - "qtcprocess.h", "qtcsettings.cpp", "qtcsettings.h", "reloadpromptutils.cpp", @@ -268,6 +274,10 @@ Project { "savefile.cpp", "savefile.h", "scopedswap.h", + "scopedtimer.cpp", + "scopedtimer.h", + "searchresultitem.cpp", + "searchresultitem.h", "set_algorithm.h", "settingsaccessor.cpp", "settingsaccessor.h", @@ -299,8 +309,6 @@ Project { "styledbar.h", "stylehelper.cpp", "stylehelper.h", - "tasktree.cpp", - "tasktree.h", "templateengine.cpp", "templateengine.h", "temporarydirectory.cpp", @@ -309,8 +317,10 @@ Project { "temporaryfile.h", "terminalcommand.cpp", "terminalcommand.h", - "terminalprocess.cpp", - "terminalprocess_p.h", + "terminalhooks.cpp", + "terminalhooks.h", + "terminalinterface.cpp", + "terminalinterface.h", "textfieldcheckbox.cpp", "textfieldcheckbox.h", "textfieldcombobox.cpp", @@ -329,7 +339,7 @@ Project { "headerviewstretcher.h", "uncommentselection.cpp", "uncommentselection.h", - "uniqueobjectptr.h" + "uniqueobjectptr.h", "unixutils.cpp", "unixutils.h", "url.cpp", @@ -464,6 +474,7 @@ Project { Export { Depends { name: "Qt"; submodules: ["concurrent", "widgets" ] } + Depends { name: "Tasking" } cpp.includePaths: base.concat("mimetypes2") } } diff --git a/src/libs/utils/utils.qdoc b/src/libs/utils/utils.qdoc index c0717043314..bc9f843498c 100644 --- a/src/libs/utils/utils.qdoc +++ b/src/libs/utils/utils.qdoc @@ -3,7 +3,8 @@ /*! \namespace Utils + \inmodule QtCreator - The Utils namespace contains a collection of utility classes and functions for use by all + \brief The Utils namespace contains a collection of utility classes and functions for use by all plugins. */ diff --git a/src/libs/utils/utils.qrc b/src/libs/utils/utils.qrc index 1f2ef64898e..c0f2d2559a1 100644 --- a/src/libs/utils/utils.qrc +++ b/src/libs/utils/utils.qrc @@ -36,6 +36,8 @@ images/unlocked@2x.png images/pinned.png images/pinned@2x.png + images/pinned_small.png + images/pinned_small@2x.png images/broken.png images/broken@2x.png images/notloaded.png @@ -173,6 +175,8 @@ images/iconoverlay_add@2x.png images/iconoverlay_add_background.png images/iconoverlay_add_background@2x.png + images/iconoverlay_close_small.png + images/iconoverlay_close_small@2x.png images/iconoverlay_error.png images/iconoverlay_error@2x.png images/iconoverlay_error_background.png diff --git a/src/libs/utils/utilsicons.cpp b/src/libs/utils/utilsicons.cpp index 7263b285946..01b1f0c1dbc 100644 --- a/src/libs/utils/utilsicons.cpp +++ b/src/libs/utils/utilsicons.cpp @@ -24,6 +24,8 @@ const Icon UNLOCKED({ {":/utils/images/unlocked.png", Theme::PanelTextColorDark}}, Icon::Tint); const Icon PINNED({ {":/utils/images/pinned.png", Theme::PanelTextColorDark}}, Icon::Tint); +const Icon PINNED_SMALL({ + {":/utils/images/pinned_small.png", Theme::PanelTextColorDark}}, Icon::Tint); const Icon NEXT({ {":/utils/images/next.png", Theme::IconsWarningColor}}, Icon::MenuTintedStyle); const Icon NEXT_TOOLBAR({ @@ -225,6 +227,8 @@ const Icon INTERRUPT_SMALL_TOOLBAR({ {":/utils/images/interrupt_small.png", Theme::IconsInterruptToolBarColor}}); const Icon BOUNDING_RECT({ {":/utils/images/boundingrect.png", Theme::IconsBaseColor}}); +const Icon EYE_OPEN({ + {":/utils/images/eye_open.png", Theme::PanelTextColorMid}}, Icon::Tint); const Icon EYE_OPEN_TOOLBAR({ {":/utils/images/eye_open.png", Theme::IconsBaseColor}}); const Icon EYE_CLOSED_TOOLBAR({ diff --git a/src/libs/utils/utilsicons.h b/src/libs/utils/utilsicons.h index b8fda0c53fd..5a75267a363 100644 --- a/src/libs/utils/utilsicons.h +++ b/src/libs/utils/utilsicons.h @@ -19,6 +19,7 @@ QTCREATOR_UTILS_EXPORT extern const Icon LOCKED; QTCREATOR_UTILS_EXPORT extern const Icon UNLOCKED_TOOLBAR; QTCREATOR_UTILS_EXPORT extern const Icon UNLOCKED; QTCREATOR_UTILS_EXPORT extern const Icon PINNED; +QTCREATOR_UTILS_EXPORT extern const Icon PINNED_SMALL; QTCREATOR_UTILS_EXPORT extern const Icon NEXT; QTCREATOR_UTILS_EXPORT extern const Icon NEXT_TOOLBAR; QTCREATOR_UTILS_EXPORT extern const Icon PREV; @@ -120,6 +121,7 @@ QTCREATOR_UTILS_EXPORT extern const Icon STOP_SMALL_TOOLBAR; QTCREATOR_UTILS_EXPORT extern const Icon INTERRUPT_SMALL; QTCREATOR_UTILS_EXPORT extern const Icon INTERRUPT_SMALL_TOOLBAR; QTCREATOR_UTILS_EXPORT extern const Icon BOUNDING_RECT; +QTCREATOR_UTILS_EXPORT extern const Icon EYE_OPEN; QTCREATOR_UTILS_EXPORT extern const Icon EYE_OPEN_TOOLBAR; QTCREATOR_UTILS_EXPORT extern const Icon EYE_CLOSED_TOOLBAR; QTCREATOR_UTILS_EXPORT extern const Icon REPLACE; diff --git a/src/libs/utils/utiltypes.h b/src/libs/utils/utiltypes.h new file mode 100644 index 00000000000..967eecb5a5f --- /dev/null +++ b/src/libs/utils/utiltypes.h @@ -0,0 +1,14 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace Utils { +class FilePath; + +enum class IterationPolicy { Stop, Continue }; + +using FilePathPredicate = std::function; +} // namespace Utils diff --git a/src/libs/utils/variablechooser.h b/src/libs/utils/variablechooser.h index 1fe08a1e59b..257dcea5e2c 100644 --- a/src/libs/utils/variablechooser.h +++ b/src/libs/utils/variablechooser.h @@ -13,6 +13,8 @@ namespace Utils { class MacroExpander; +using MacroExpanderProvider = std::function; + namespace Internal { class VariableChooserPrivate; } class QTCREATOR_UTILS_EXPORT VariableChooser : public QWidget @@ -23,7 +25,7 @@ public: explicit VariableChooser(QWidget *parent = nullptr); ~VariableChooser() override; - void addMacroExpanderProvider(const std::function &provider); + void addMacroExpanderProvider(const MacroExpanderProvider &provider); void addSupportedWidget(QWidget *textcontrol, const QByteArray &ownName = QByteArray()); static void addSupportForChildWidgets(QWidget *parent, MacroExpander *expander); diff --git a/src/libs/utils/wizard.cpp b/src/libs/utils/wizard.cpp index 9fca25eaead..ce37d80705e 100644 --- a/src/libs/utils/wizard.cpp +++ b/src/libs/utils/wizard.cpp @@ -23,7 +23,9 @@ #include -/*! \class Utils::Wizard +/*! + \class Utils::Wizard + \inmodule QtCreator \brief The Wizard class implements a wizard with a progress bar on the left. diff --git a/src/libs/utils/wizardpage.cpp b/src/libs/utils/wizardpage.cpp index 740f46e8fc9..c4730b70115 100644 --- a/src/libs/utils/wizardpage.cpp +++ b/src/libs/utils/wizardpage.cpp @@ -5,7 +5,9 @@ #include "wizard.h" -/*! \class Utils::WizardPage +/*! + \class Utils::WizardPage + \inmodule QtCreator \brief QWizardPage with a couple of improvements. diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index 0ec8eb2de76..4fd8b655730 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -20,6 +20,7 @@ add_subdirectory(projectexplorer) add_subdirectory(silversearcher) # Level 3: (only depends on Level 2 and below) +add_subdirectory(axivion) add_subdirectory(bookmarks) add_subdirectory(cppeditor) add_subdirectory(haskell) @@ -27,6 +28,7 @@ add_subdirectory(help) add_subdirectory(resourceeditor) add_subdirectory(nim) add_subdirectory(conan) +add_subdirectory(vcpkg) # Level 4: (only depends on Level 3 and below) add_subdirectory(classview) @@ -108,3 +110,5 @@ add_subdirectory(qnx) add_subdirectory(webassembly) add_subdirectory(mcusupport) add_subdirectory(saferenderer) +add_subdirectory(copilot) +add_subdirectory(terminal) diff --git a/src/plugins/android/android.qbs b/src/plugins/android/android.qbs index 5c9b6564c7b..c6d1611636f 100644 --- a/src/plugins/android/android.qbs +++ b/src/plugins/android/android.qbs @@ -116,9 +116,7 @@ Project { "sdkmanageroutputparser.h" ] - Group { - name: "Unit tests" - condition: qtc.testsEnabled + QtcTestFiles { files: [ "android_tst.qrc", "androidsdkmanager_test.cpp", diff --git a/src/plugins/android/androidavdmanager.cpp b/src/plugins/android/androidavdmanager.cpp index 7cf62fadf86..67403703025 100644 --- a/src/plugins/android/androidavdmanager.cpp +++ b/src/plugins/android/androidavdmanager.cpp @@ -10,25 +10,21 @@ #include #include +#include +#include #include -#include -#include -#include #include #include #include -#include #include -#include using namespace Utils; +using namespace std; namespace Android::Internal { -using namespace std; - const int avdCreateTimeoutMs = 30000; static Q_LOGGING_CATEGORY(avdManagerLog, "qtc.android.avdManager", QtWarningMsg) @@ -41,7 +37,7 @@ static Q_LOGGING_CATEGORY(avdManagerLog, "qtc.android.avdManager", QtWarningMsg) bool AndroidAvdManager::avdManagerCommand(const AndroidConfig &config, const QStringList &args, QString *output) { CommandLine cmd(config.avdManagerToolPath(), args); - QtcProcess proc; + Process proc; Environment env = AndroidConfigurations::toolsEnvironment(config); proc.setEnvironment(env); qCDebug(avdManagerLog).noquote() << "Running AVD Manager command:" << cmd.toUserOutput(); @@ -89,7 +85,7 @@ static CreateAvdInfo createAvdCommand(const AndroidConfig &config, const CreateA avdManager.addArg("-f"); qCDebug(avdManagerLog).noquote() << "Running AVD Manager command:" << avdManager.toUserOutput(); - QtcProcess proc; + Process proc; proc.setProcessMode(ProcessMode::Writer); proc.setEnvironment(AndroidConfigurations::toolsEnvironment(config)); proc.setCommand(avdManager); @@ -143,14 +139,14 @@ AndroidAvdManager::~AndroidAvdManager() = default; QFuture AndroidAvdManager::createAvd(CreateAvdInfo info) const { - return runAsync(&createAvdCommand, m_config, info); + return Utils::asyncRun(&createAvdCommand, m_config, info); } bool AndroidAvdManager::removeAvd(const QString &name) const { const CommandLine command(m_config.avdManagerToolPath(), {"delete", "avd", "-n", name}); qCDebug(avdManagerLog).noquote() << "Running command (removeAvd):" << command.toUserOutput(); - QtcProcess proc; + Process proc; proc.setTimeoutS(5); proc.setEnvironment(AndroidConfigurations::toolsEnvironment(m_config)); proc.setCommand(command); @@ -221,14 +217,14 @@ static AndroidDeviceInfoList listVirtualDevices(const AndroidConfig &config) QFuture AndroidAvdManager::avdList() const { - return runAsync(listVirtualDevices, m_config); + return Utils::asyncRun(listVirtualDevices, m_config); } QString AndroidAvdManager::startAvd(const QString &name) const { if (!findAvd(name).isEmpty() || startAvdAsync(name)) return waitForAvd(name); - return QString(); + return {}; } static bool is32BitUserSpace() @@ -236,7 +232,7 @@ static bool is32BitUserSpace() // Do a similar check as android's emulator is doing: if (HostOsInfo::isLinuxHost()) { if (QSysInfo::WordSize == 32) { - QtcProcess proc; + Process proc; proc.setTimeoutS(3); proc.setCommand({"getconf", {"LONG_BIT"}}); proc.runBlocking(); @@ -262,13 +258,13 @@ bool AndroidAvdManager::startAvdAsync(const QString &avdName) const return false; } - // TODO: Here we are potentially leaking QtcProcess instance in case when shutdown happens + // TODO: Here we are potentially leaking Process instance in case when shutdown happens // after the avdProcess has started and before it has finished. Giving a parent object here // should solve the issue. However, AndroidAvdManager is not a QObject, so no clue what parent // would be the most appropriate. Preferably some object taken form android plugin... - QtcProcess *avdProcess = new QtcProcess; + Process *avdProcess = new Process; avdProcess->setProcessChannelMode(QProcess::MergedChannels); - QObject::connect(avdProcess, &QtcProcess::done, avdProcess, [avdProcess] { + QObject::connect(avdProcess, &Process::done, avdProcess, [avdProcess] { if (avdProcess->exitCode()) { const QString errorOutput = QString::fromLatin1(avdProcess->readAllRawStandardOutput()); QMetaObject::invokeMethod(Core::ICore::mainWindow(), [errorOutput] { @@ -301,21 +297,21 @@ QString AndroidAvdManager::findAvd(const QString &avdName) const if (device.avdName == avdName) return device.serialNumber; } - return QString(); + return {}; } QString AndroidAvdManager::waitForAvd(const QString &avdName, - const QFutureInterfaceBase &fi) const + const std::optional> &future) const { // we cannot use adb -e wait-for-device, since that doesn't work if a emulator is already running // 60 rounds of 2s sleeping, two minutes for the avd to start QString serialNumber; for (int i = 0; i < 60; ++i) { - if (fi.isCanceled()) + if (future && future->isCanceled()) return {}; serialNumber = findAvd(avdName); if (!serialNumber.isEmpty()) - return waitForBooted(serialNumber, fi) ? serialNumber : QString(); + return waitForBooted(serialNumber, future) ? serialNumber : QString(); QThread::sleep(2); } return {}; @@ -328,7 +324,7 @@ bool AndroidAvdManager::isAvdBooted(const QString &device) const const CommandLine command({m_config.adbToolPath(), arguments}); qCDebug(avdManagerLog).noquote() << "Running command (isAvdBooted):" << command.toUserOutput(); - QtcProcess adbProc; + Process adbProc; adbProc.setTimeoutS(10); adbProc.setCommand(command); adbProc.runBlocking(); @@ -339,11 +335,11 @@ bool AndroidAvdManager::isAvdBooted(const QString &device) const } bool AndroidAvdManager::waitForBooted(const QString &serialNumber, - const QFutureInterfaceBase &fi) const + const std::optional> &future) const { // found a serial number, now wait until it's done booting... for (int i = 0; i < 60; ++i) { - if (fi.isCanceled()) + if (future && future->isCanceled()) return false; if (isAvdBooted(serialNumber)) return true; diff --git a/src/plugins/android/androidavdmanager.h b/src/plugins/android/androidavdmanager.h index cad3a2efe73..545dedbfe21 100644 --- a/src/plugins/android/androidavdmanager.h +++ b/src/plugins/android/androidavdmanager.h @@ -4,8 +4,9 @@ #include "androidconfigurations.h" -#include -#include +#include + +#include namespace Android::Internal { @@ -22,14 +23,14 @@ public: QString startAvd(const QString &name) const; bool startAvdAsync(const QString &avdName) const; QString findAvd(const QString &avdName) const; - QString waitForAvd(const QString &avdName, const QFutureInterfaceBase &fi = {}) const; + QString waitForAvd(const QString &avdName, const std::optional> &future = {}) const; bool isAvdBooted(const QString &device) const; static bool avdManagerCommand(const AndroidConfig &config, const QStringList &args, QString *output); private: - bool waitForBooted(const QString &serialNumber, const QFutureInterfaceBase &fi = {}) const; + bool waitForBooted(const QString &serialNumber, const std::optional> &future = {}) const; private: const AndroidConfig &m_config; diff --git a/src/plugins/android/androidbuildapkstep.cpp b/src/plugins/android/androidbuildapkstep.cpp index adf96eb68d7..b27730b45b0 100644 --- a/src/plugins/android/androidbuildapkstep.cpp +++ b/src/plugins/android/androidbuildapkstep.cpp @@ -36,7 +36,7 @@ #include #include #include -#include +#include #include #include @@ -137,8 +137,9 @@ AndroidBuildApkWidget::AndroidBuildApkWidget(AndroidBuildApkStep *step) createSignPackageGroup(), createApplicationGroup(), createAdvancedGroup(), - createAdditionalLibrariesGroup() - }.attachTo(this, WithoutMargins); + createAdditionalLibrariesGroup(), + noMargin + }.attachTo(this); connect(m_step->buildConfiguration(), &BuildConfiguration::buildTypeChanged, this, &AndroidBuildApkWidget::updateSigningWarning); @@ -711,6 +712,13 @@ void AndroidBuildApkStep::doRun() return; } + if (AndroidManager::skipInstallationAndPackageSteps(target())) { + reportWarningOrError(Tr::tr("Product type is not an application, not building an APK."), + Task::Warning); + emit finished(true); + return; + } + auto setup = [this] { const auto androidAbis = AndroidManager::applicationAbis(target()); const QString buildKey = target()->activeBuildKey(); @@ -863,7 +871,7 @@ void AndroidBuildApkStep::updateBuildToolsVersionInJsonFile() if (!contents) return; - QRegularExpression regex(QLatin1String("\"sdkBuildToolsRevision\":.\"[0-9.]+\"")); + static const QRegularExpression regex(R"("sdkBuildToolsRevision":."[0-9.]+")"); QRegularExpressionMatch match = regex.match(QString::fromUtf8(contents.value())); const QString version = buildToolsVersion().toString(); if (match.hasMatch() && !version.isEmpty()) { @@ -925,7 +933,8 @@ void AndroidBuildApkStep::setBuildToolsVersion(const QVersionNumber &version) void AndroidBuildApkStep::stdError(const QString &output) { QString newOutput = output; - newOutput.remove(QRegularExpression("^(\\n)+")); + static const QRegularExpression re("^(\\n)+"); + newOutput.remove(re); if (newOutput.isEmpty()) return; @@ -1041,7 +1050,7 @@ QAbstractItemModel *AndroidBuildApkStep::keystoreCertificates() const QStringList params = {"-list", "-v", "-keystore", m_keystorePath.toUserOutput(), "-storepass", m_keystorePasswd, "-J-Duser.language=en"}; - QtcProcess keytoolProc; + Process keytoolProc; keytoolProc.setTimeoutS(30); keytoolProc.setCommand({AndroidConfigurations::currentConfig().keytoolPath(), params}); keytoolProc.runBlocking(EventLoopMode::On); diff --git a/src/plugins/android/androidconfigurations.cpp b/src/plugins/android/androidconfigurations.cpp index 5477bf14bfe..9c444f6e756 100644 --- a/src/plugins/android/androidconfigurations.cpp +++ b/src/plugins/android/androidconfigurations.cpp @@ -19,7 +19,7 @@ #include #include #include -#include +#include #include #include @@ -34,8 +34,8 @@ #include #include #include +#include #include -#include #include #include @@ -306,7 +306,7 @@ void AndroidConfig::parseDependenciesJson() auto fillQtVersionsRange = [](const QString &shortVersion) { QList versions; - const QRegularExpression re(R"(([0-9]\.[0-9]+\.)\[([0-9]+)\-([0-9]+)\])"); + static const QRegularExpression re(R"(([0-9]\.[0-9]+\.)\[([0-9]+)\-([0-9]+)\])"); QRegularExpressionMatch match = re.match(shortVersion); if (match.hasMatch() && match.lastCapturedIndex() == 3) for (int i = match.captured(2).toInt(); i <= match.captured(3).toInt(); ++i) @@ -432,8 +432,12 @@ QStringList AndroidConfig::apiLevelNamesFor(const SdkPlatformList &platforms) QString AndroidConfig::apiLevelNameFor(const SdkPlatform *platform) { - return platform && platform->apiLevel() > 0 ? - QString("android-%1").arg(platform->apiLevel()) : ""; + if (platform && platform->apiLevel() > 0) { + QString sdkStylePath = platform->sdkStylePath(); + return sdkStylePath.remove("platforms;"); + } + + return {}; } FilePath AndroidConfig::adbToolPath() const @@ -597,7 +601,7 @@ FilePath AndroidConfig::keytoolPath() const QVector AndroidConfig::connectedDevices(QString *error) const { QVector devices; - QtcProcess adbProc; + Process adbProc; adbProc.setTimeoutS(30); CommandLine cmd{adbToolPath(), {"devices"}}; adbProc.setCommand(cmd); @@ -666,7 +670,7 @@ QString AndroidConfig::getDeviceProperty(const QString &device, const QString &p AndroidDeviceInfo::adbSelector(device)); cmd.addArgs({"shell", "getprop", property}); - QtcProcess adbProc; + Process adbProc; adbProc.setTimeoutS(10); adbProc.setCommand(cmd); adbProc.runBlocking(); @@ -743,7 +747,7 @@ QStringList AndroidConfig::getAbis(const QString &device) // First try via ro.product.cpu.abilist QStringList arguments = AndroidDeviceInfo::adbSelector(device); arguments << "shell" << "getprop" << "ro.product.cpu.abilist"; - QtcProcess adbProc; + Process adbProc; adbProc.setTimeoutS(10); adbProc.setCommand({adbTool, arguments}); adbProc.runBlocking(); @@ -766,7 +770,7 @@ QStringList AndroidConfig::getAbis(const QString &device) else arguments << QString::fromLatin1("ro.product.cpu.abi%1").arg(i); - QtcProcess abiProc; + Process abiProc; abiProc.setTimeoutS(10); abiProc.setCommand({adbTool, arguments}); abiProc.runBlocking(); @@ -892,7 +896,7 @@ QVersionNumber AndroidConfig::ndkVersion(const FilePath &ndkPath) // r6a // r10e (64 bit) QString content = QString::fromUtf8(reader.data()); - QRegularExpression re("(r)(?[0-9]{1,2})(?[a-z]{1,1})"); + static const QRegularExpression re("(r)(?[0-9]{1,2})(?[a-z]{1,1})"); QRegularExpressionMatch match = re.match(content); if (match.hasMatch()) { QString major = match.captured("major"); @@ -1169,7 +1173,7 @@ void AndroidConfigurations::removeUnusedDebuggers() uniqueNdks.append(ndkLocation); } - uniqueNdks.append(FileUtils::toFilePathList(currentConfig().getCustomNdkList()).toVector()); + uniqueNdks.append(FileUtils::toFilePathList(currentConfig().getCustomNdkList())); const QList allDebuggers = Debugger::DebuggerItemManager::debuggers(); for (const Debugger::DebuggerItem &debugger : allDebuggers) { @@ -1526,7 +1530,7 @@ FilePath AndroidConfig::getJdkPath() args << "-c" << "readlink -f $(which java)"; - QtcProcess findJdkPathProc; + Process findJdkPathProc; findJdkPathProc.setCommand({"sh", args}); findJdkPathProc.start(); findJdkPathProc.waitForFinished(); diff --git a/src/plugins/android/androidcreatekeystorecertificate.cpp b/src/plugins/android/androidcreatekeystorecertificate.cpp index 003eb7e9b98..67e6c8ff83d 100644 --- a/src/plugins/android/androidcreatekeystorecertificate.cpp +++ b/src/plugins/android/androidcreatekeystorecertificate.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include #include @@ -217,7 +217,8 @@ bool AndroidCreateKeystoreCertificate::checkCertificateAlias() bool AndroidCreateKeystoreCertificate::checkCountryCode() { - if (!m_countryLineEdit->text().contains(QRegularExpression("[A-Z]{2}"))) { + static const QRegularExpression re("[A-Z]{2}"); + if (!m_countryLineEdit->text().contains(re)) { m_infoLabel->show(); m_infoLabel->setText(Tr::tr("Invalid country code.")); return false; @@ -271,7 +272,7 @@ void AndroidCreateKeystoreCertificate::buttonBoxAccepted() "-keypass", certificatePassword(), "-dname", distinguishedNames}); - QtcProcess genKeyCertProc; + Process genKeyCertProc; genKeyCertProc.setTimeoutS(15); genKeyCertProc.setCommand(command); genKeyCertProc.runBlocking(EventLoopMode::On); diff --git a/src/plugins/android/androiddebugsupport.cpp b/src/plugins/android/androiddebugsupport.cpp index 138740e4a3a..bb4fc51b984 100644 --- a/src/plugins/android/androiddebugsupport.cpp +++ b/src/plugins/android/androiddebugsupport.cpp @@ -21,7 +21,7 @@ #include #include -#include +#include #include #include diff --git a/src/plugins/android/androiddeployqtstep.cpp b/src/plugins/android/androiddeployqtstep.cpp index c305987a502..a9e9596e772 100644 --- a/src/plugins/android/androiddeployqtstep.cpp +++ b/src/plugins/android/androiddeployqtstep.cpp @@ -2,10 +2,11 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "androiddeployqtstep.h" + #include "androidavdmanager.h" #include "androidbuildapkstep.h" #include "androidconstants.h" -#include "androiddeployqtstep.h" #include "androiddevice.h" #include "androidmanager.h" #include "androidqtversion.h" @@ -16,6 +17,7 @@ #include #include +#include #include #include #include @@ -27,13 +29,17 @@ #include #include +#include #include #include +#include +#include +#include +#include #include +#include #include -#include -#include #include #include @@ -59,23 +65,94 @@ const QLatin1String InstallFailedVersionDowngrade("INSTALL_FAILED_VERSION_DOWNGR // AndroidDeployQtStep -AndroidDeployQtStep::AndroidDeployQtStep(BuildStepList *parent, Utils::Id id) +class AndroidDeployQtStep : public BuildStep +{ + Q_OBJECT + + enum DeployErrorCode + { + NoError = 0, + InconsistentCertificates = 0x0001, + UpdateIncompatible = 0x0002, + PermissionModelDowngrade = 0x0004, + VersionDowngrade = 0x0008, + Failure = 0x0010 + }; + +public: + AndroidDeployQtStep(BuildStepList *bc, Id id); + +signals: + void askForUninstall(DeployErrorCode errorCode); + +private: + void runCommand(const CommandLine &command); + + bool init() override; + void doRun() override; + void doCancel() override; + void gatherFilesToPull(); + DeployErrorCode runDeploy(); + void slotAskForUninstall(DeployErrorCode errorCode); + + void runImpl(QPromise &promise); + + QWidget *createConfigWidget() override; + + void processReadyReadStdOutput(DeployErrorCode &errorCode); + void stdOutput(const QString &line); + void processReadyReadStdError(DeployErrorCode &errorCode); + void stdError(const QString &line); + DeployErrorCode parseDeployErrors(const QString &deployOutputLine) const; + + friend void operator|=(DeployErrorCode &e1, const DeployErrorCode &e2) { + e1 = static_cast((int)e1 | (int)e2); + } + + friend DeployErrorCode operator|(const DeployErrorCode &e1, const DeployErrorCode &e2) { + return static_cast((int)e1 | (int)e2); + } + + void reportWarningOrError(const QString &message, Task::TaskType type); + + FilePath m_manifestName; + QString m_serialNumber; + QString m_avdName; + FilePath m_apkPath; + QMap m_filesToPull; + + QStringList m_androidABIs; + BoolAspect m_uninstallPreviousPackage{this}; + bool m_uninstallPreviousPackageRun = false; + bool m_useAndroiddeployqt = false; + bool m_askForUninstall = false; + CommandLine m_androiddeployqtArgs; + FilePath m_adbPath; + FilePath m_command; + FilePath m_workingDirectory; + Environment m_environment; + AndroidDeviceInfo m_deviceInfo; + + // The synchronizer has cancelOnWait set to true by default. + FutureSynchronizer m_synchronizer; +}; + +AndroidDeployQtStep::AndroidDeployQtStep(BuildStepList *parent, Id id) : BuildStep(parent, id) { setImmutable(true); setUserExpanded(true); - m_uninstallPreviousPackage = addAspect(); - m_uninstallPreviousPackage->setSettingsKey(UninstallPreviousPackageKey); - m_uninstallPreviousPackage->setLabel(Tr::tr("Uninstall the existing app before deployment"), + m_uninstallPreviousPackage.setSettingsKey(UninstallPreviousPackageKey); + m_uninstallPreviousPackage.setLabel(Tr::tr("Uninstall the existing app before deployment"), BoolAspect::LabelPlacement::AtCheckBox); - m_uninstallPreviousPackage->setValue(false); + m_uninstallPreviousPackage.setValue(false); const QtSupport::QtVersion * const qt = QtSupport::QtKitAspect::qtVersion(kit()); const bool forced = qt && qt->qtVersion() < QVersionNumber(5, 4, 0); if (forced) { - m_uninstallPreviousPackage->setValue(true); - m_uninstallPreviousPackage->setEnabled(false); + m_uninstallPreviousPackage.setValue(true); + m_uninstallPreviousPackage.setEnabled(false); } connect(this, &AndroidDeployQtStep::askForUninstall, @@ -157,8 +234,7 @@ bool AndroidDeployQtStep::init() return false; } - const bool abiListNotEmpty = !selectedAbis.isEmpty() && !dev->supportedAbis().isEmpty(); - if (abiListNotEmpty && !dev->canSupportAbis(selectedAbis)) { + if (!dev->canSupportAbis(selectedAbis)) { const QString error = Tr::tr("The deployment device \"%1\" does not support the " "architectures used by the kit.\n" "The kit supports \"%2\", but the device uses \"%3\".") @@ -197,7 +273,7 @@ bool AndroidDeployQtStep::init() emit addOutput(Tr::tr("Deploying to %1").arg(m_serialNumber), OutputFormat::NormalMessage); - m_uninstallPreviousPackageRun = m_uninstallPreviousPackage->value(); + m_uninstallPreviousPackageRun = m_uninstallPreviousPackage(); if (m_uninstallPreviousPackageRun) m_manifestName = AndroidManager::manifestPath(target()); @@ -209,9 +285,9 @@ bool AndroidDeployQtStep::init() reportWarningOrError(Tr::tr("The deployment step's project node is invalid."), Task::Error); return false; } - m_apkPath = Utils::FilePath::fromString(node->data(Constants::AndroidApk).toString()); + m_apkPath = FilePath::fromString(node->data(Constants::AndroidApk).toString()); if (!m_apkPath.isEmpty()) { - m_manifestName = Utils::FilePath::fromString(node->data(Constants::AndroidManifest).toString()); + m_manifestName = FilePath::fromString(node->data(Constants::AndroidManifest).toString()); m_command = AndroidConfigurations::currentConfig().adbToolPath(); AndroidManager::setManifestPath(target(), m_manifestName); } else { @@ -254,7 +330,7 @@ bool AndroidDeployQtStep::init() m_apkPath = AndroidManager::packagePath(target()); m_workingDirectory = bc ? AndroidManager::buildDirectory(target()): FilePath(); } - m_environment = bc ? bc->environment() : Utils::Environment(); + m_environment = bc ? bc->environment() : Environment(); m_adbPath = AndroidConfigurations::currentConfig().adbToolPath(); @@ -303,7 +379,7 @@ AndroidDeployQtStep::DeployErrorCode AndroidDeployQtStep::runDeploy() cmd.addArgs({"install", "-r", m_apkPath.toString()}); } - QtcProcess process; + Process process; process.setCommand(cmd); process.setWorkingDirectory(m_workingDirectory); process.setEnvironment(m_environment); @@ -401,15 +477,16 @@ void AndroidDeployQtStep::slotAskForUninstall(DeployErrorCode errorCode) m_askForUninstall = button == QMessageBox::Yes; } -void AndroidDeployQtStep::runImpl(QFutureInterface &fi) +void AndroidDeployQtStep::runImpl(QPromise &promise) { if (!m_avdName.isEmpty()) { - QString serialNumber = AndroidAvdManager().waitForAvd(m_avdName, fi); + const QString serialNumber = AndroidAvdManager().waitForAvd(m_avdName, + QFuture(promise.future())); qCDebug(deployStepLog) << "Deploying to AVD:" << m_avdName << serialNumber; if (serialNumber.isEmpty()) { reportWarningOrError(Tr::tr("The deployment AVD \"%1\" cannot be started.") .arg(m_avdName), Task::Error); - fi.reportResult(false); + promise.addResult(false); return; } m_serialNumber = serialNumber; @@ -445,7 +522,7 @@ void AndroidDeployQtStep::runImpl(QFutureInterface &fi) reportWarningOrError(error, Task::Error); } } - fi.reportResult(returnValue == NoError); + promise.addResult(returnValue == NoError); } void AndroidDeployQtStep::gatherFilesToPull() @@ -485,7 +562,7 @@ void AndroidDeployQtStep::doRun() emit finished(success); watcher->deleteLater(); }); - auto future = Utils::runAsync(&AndroidDeployQtStep::runImpl, this); + auto future = Utils::asyncRun(&AndroidDeployQtStep::runImpl, this); watcher->setFuture(future); m_synchronizer.addFuture(future); } @@ -497,7 +574,7 @@ void AndroidDeployQtStep::doCancel() void AndroidDeployQtStep::runCommand(const CommandLine &command) { - QtcProcess buildProc; + Process buildProc; buildProc.setTimeoutS(2 * 60); emit addOutput(Tr::tr("Package deploy: Running command \"%1\".").arg(command.toUserOutput()), OutputFormat::NormalMessage); @@ -524,10 +601,13 @@ QWidget *AndroidDeployQtStep::createConfigWidget() AndroidManager::installQASIPackage(target(), packagePath); }); - Layouting::Form builder; - builder.addRow(m_uninstallPreviousPackage); - builder.addRow(installCustomApkButton); - builder.attachTo(widget, Layouting::WithoutMargins); + using namespace Layouting; + + Form { + m_uninstallPreviousPackage, br, + installCustomApkButton, + noMargin + }.attachTo(widget); return widget; } @@ -542,7 +622,8 @@ void AndroidDeployQtStep::stdError(const QString &line) emit addOutput(line, BuildStep::OutputFormat::Stderr, BuildStep::DontAppendNewline); QString newOutput = line; - newOutput.remove(QRegularExpression("^(\\n)+")); + static const QRegularExpression re("^(\\n)+"); + newOutput.remove(re); if (newOutput.isEmpty()) return; @@ -592,3 +673,5 @@ AndroidDeployQtStepFactory::AndroidDeployQtStepFactory() } } // Android::Internal + +#include "androiddeployqtstep.moc" diff --git a/src/plugins/android/androiddeployqtstep.h b/src/plugins/android/androiddeployqtstep.h index 334be8ea22a..dda4304b6af 100644 --- a/src/plugins/android/androiddeployqtstep.h +++ b/src/plugins/android/androiddeployqtstep.h @@ -4,16 +4,7 @@ #pragma once -#include "androiddeviceinfo.h" - -#include -#include - -#include -#include -#include - -namespace Utils { class QtcProcess; } +#include namespace Android::Internal { @@ -23,76 +14,4 @@ public: AndroidDeployQtStepFactory(); }; -class AndroidDeployQtStep : public ProjectExplorer::BuildStep -{ - Q_OBJECT - - enum DeployErrorCode - { - NoError = 0, - InconsistentCertificates = 0x0001, - UpdateIncompatible = 0x0002, - PermissionModelDowngrade = 0x0004, - VersionDowngrade = 0x0008, - Failure = 0x0010 - }; - -public: - AndroidDeployQtStep(ProjectExplorer::BuildStepList *bc, Utils::Id id); - -signals: - void askForUninstall(DeployErrorCode errorCode); - -private: - void runCommand(const Utils::CommandLine &command); - - bool init() override; - void doRun() override; - void doCancel() override; - void gatherFilesToPull(); - DeployErrorCode runDeploy(); - void slotAskForUninstall(DeployErrorCode errorCode); - - void runImpl(QFutureInterface &fi); - - QWidget *createConfigWidget() override; - - void processReadyReadStdOutput(DeployErrorCode &errorCode); - void stdOutput(const QString &line); - void processReadyReadStdError(DeployErrorCode &errorCode); - void stdError(const QString &line); - DeployErrorCode parseDeployErrors(const QString &deployOutputLine) const; - - friend void operator|=(DeployErrorCode &e1, const DeployErrorCode &e2) { - e1 = static_cast((int)e1 | (int)e2); - } - - friend DeployErrorCode operator|(const DeployErrorCode &e1, const DeployErrorCode &e2) { - return static_cast((int)e1 | (int)e2); - } - - void reportWarningOrError(const QString &message, ProjectExplorer::Task::TaskType type); - - Utils::FilePath m_manifestName; - QString m_serialNumber; - QString m_avdName; - Utils::FilePath m_apkPath; - QMap m_filesToPull; - - QStringList m_androidABIs; - Utils::BoolAspect *m_uninstallPreviousPackage = nullptr; - bool m_uninstallPreviousPackageRun = false; - bool m_useAndroiddeployqt = false; - bool m_askForUninstall = false; - static const Utils::Id Id; - Utils::CommandLine m_androiddeployqtArgs; - Utils::FilePath m_adbPath; - Utils::FilePath m_command; - Utils::FilePath m_workingDirectory; - Utils::Environment m_environment; - AndroidDeviceInfo m_deviceInfo; - - Utils::FutureSynchronizer m_synchronizer; -}; - } // Android::Internal diff --git a/src/plugins/android/androiddevice.cpp b/src/plugins/android/androiddevice.cpp index 4c034c30af1..ce2f4c11ee7 100644 --- a/src/plugins/android/androiddevice.cpp +++ b/src/plugins/android/androiddevice.cpp @@ -18,15 +18,14 @@ #include #include #include -#include +#include #include +#include +#include #include -#include -#include #include -#include #include #include #include @@ -388,11 +387,6 @@ IDeviceWidget *AndroidDevice::createWidget() return new AndroidDeviceWidget(sharedFromThis()); } -bool AndroidDevice::canAutoDetectPorts() const -{ - return true; -} - DeviceProcessSignalOperation::Ptr AndroidDevice::signalOperation() const { return DeviceProcessSignalOperation::Ptr(new AndroidSignalOperation()); @@ -454,7 +448,7 @@ void AndroidDeviceManager::startAvd(const ProjectExplorer::IDevice::Ptr &device, const AndroidDevice *androidDev = static_cast(device.data()); const QString name = androidDev->avdName(); qCDebug(androidDeviceLog, "Starting Android AVD id \"%s\".", qPrintable(name)); - runAsync([this, name, device] { + auto future = Utils::asyncRun([this, name, device] { const QString serialNumber = m_avdManager.startAvd(name); // Mark the AVD as ReadyToUse once we know it's started if (!serialNumber.isEmpty()) { @@ -462,6 +456,7 @@ void AndroidDeviceManager::startAvd(const ProjectExplorer::IDevice::Ptr &device, devMgr->setDeviceState(device->id(), IDevice::DeviceReadyToUse); } }); + // TODO: use future! } void AndroidDeviceManager::eraseAvd(const IDevice::Ptr &device, QWidget *parent) @@ -479,7 +474,7 @@ void AndroidDeviceManager::eraseAvd(const IDevice::Ptr &device, QWidget *parent) return; qCDebug(androidDeviceLog) << QString("Erasing Android AVD \"%1\" from the system.").arg(name); - m_removeAvdFutureWatcher.setFuture(runAsync([this, name, device] { + m_removeAvdFutureWatcher.setFuture(Utils::asyncRun([this, name, device] { QPair pair; pair.first = device; pair.second = false; @@ -618,20 +613,20 @@ void AndroidDeviceManager::setupDevicesWatcher() } if (!m_adbDeviceWatcherProcess) - m_adbDeviceWatcherProcess.reset(new QtcProcess(this)); + m_adbDeviceWatcherProcess.reset(new Process(this)); if (m_adbDeviceWatcherProcess->isRunning()) { qCDebug(androidDeviceLog) << "ADB device watcher is already running."; return; } - connect(m_adbDeviceWatcherProcess.get(), &QtcProcess::done, this, [this] { + connect(m_adbDeviceWatcherProcess.get(), &Process::done, this, [this] { if (m_adbDeviceWatcherProcess->error() != QProcess::UnknownError) { qCDebug(androidDeviceLog) << "ADB device watcher encountered an error:" << m_adbDeviceWatcherProcess->errorString(); if (!m_adbDeviceWatcherProcess->isRunning()) { qCDebug(androidDeviceLog) << "Restarting the ADB device watcher now."; - QTimer::singleShot(0, m_adbDeviceWatcherProcess.get(), &QtcProcess::start); + QTimer::singleShot(0, m_adbDeviceWatcherProcess.get(), &Process::start); } } qCDebug(androidDeviceLog) << "ADB device watcher finished."; @@ -847,7 +842,6 @@ AndroidDeviceFactory::AndroidDeviceFactory() setDisplayName(Tr::tr("Android Device")); setCombinedIcon(":/android/images/androiddevicesmall.png", ":/android/images/androiddevice.png"); - setConstructionFunction(&AndroidDevice::create); if (m_androidConfig.sdkToolsOk()) { setCreator([this] { diff --git a/src/plugins/android/androiddevice.h b/src/plugins/android/androiddevice.h index 0d17c73ec81..cf8046cfa95 100644 --- a/src/plugins/android/androiddevice.h +++ b/src/plugins/android/androiddevice.h @@ -15,7 +15,7 @@ #include #include -namespace Utils { class QtcProcess; } +namespace Utils { class Process; } namespace Android { namespace Internal { @@ -61,7 +61,6 @@ private: void addActionsIfNotFound(); ProjectExplorer::IDevice::DeviceInfo deviceInformation() const override; ProjectExplorer::IDeviceWidget *createWidget() override; - bool canAutoDetectPorts() const override; ProjectExplorer::DeviceProcessSignalOperation::Ptr signalOperation() const override; QUrl toolControlChannel(const ControlChannelHint &) const override; @@ -109,7 +108,7 @@ private: QFutureWatcher m_avdsFutureWatcher; QFutureWatcher> m_removeAvdFutureWatcher; QFileSystemWatcher m_avdFileSystemWatcher; - std::unique_ptr m_adbDeviceWatcherProcess; + std::unique_ptr m_adbDeviceWatcherProcess; AndroidConfig &m_androidConfig; AndroidAvdManager m_avdManager; diff --git a/src/plugins/android/androidmanager.cpp b/src/plugins/android/androidmanager.cpp index a15da300483..78b6202a65c 100644 --- a/src/plugins/android/androidmanager.cpp +++ b/src/plugins/android/androidmanager.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include @@ -36,8 +36,8 @@ #include #include +#include #include -#include #include #include @@ -359,6 +359,29 @@ Abi AndroidManager::androidAbi2Abi(const QString &androidAbi) } } +bool AndroidManager::skipInstallationAndPackageSteps(const Target *target) +{ + // For projects using Qt 5.15 and Qt 6, the deployment settings file + // is generated by CMake/qmake and not Qt Creator, so if such file doesn't exist + // or it's been generated by Qt Creator, we can assume the project is not + // an android app. + const FilePath inputFile = AndroidQtVersion::androidDeploymentSettings(target); + if (!inputFile.exists() || AndroidManager::isQtCreatorGenerated(inputFile)) + return true; + + const Project *p = target->project(); + + const Core::Context cmakeCtx = Core::Context(CMakeProjectManager::Constants::CMAKE_PROJECT_ID); + const bool isCmakeProject = p->projectContext() == cmakeCtx; + if (isCmakeProject) + return false; // CMake reports ProductType::Other for Android Apps + + const ProjectNode *n = p->rootProjectNode()->findProjectNode([] (const ProjectNode *n) { + return n->productType() == ProductType::App; + }); + return n == nullptr; // If no Application target found, then skip steps +} + FilePath AndroidManager::manifestSourcePath(const Target *target) { if (const ProjectNode *node = currentProjectNode(target)) { @@ -519,9 +542,9 @@ QString AndroidManager::androidNameForApiLevel(int x) case 31: return QLatin1String("Android 12.0 (S)"); case 32: - return QLatin1String("Android 12L (API 32)"); + return QLatin1String("Android 12L (Sv2, API 32)"); case 33: - return QLatin1String("Android Tiramisu"); + return QLatin1String("Android 13.0 (Tiramisu)"); default: return Tr::tr("Unknown Android version. API Level: %1").arg(x); } @@ -597,7 +620,7 @@ bool AndroidManager::checkKeystorePassword(const FilePath &keystorePath, const CommandLine cmd(AndroidConfigurations::currentConfig().keytoolPath(), {"-list", "-keystore", keystorePath.toUserOutput(), "--storepass", keystorePasswd}); - QtcProcess proc; + Process proc; proc.setTimeoutS(10); proc.setCommand(cmd); proc.runBlocking(EventLoopMode::On); @@ -617,7 +640,7 @@ bool AndroidManager::checkCertificatePassword(const FilePath &keystorePath, else arguments << certificatePasswd; - QtcProcess proc; + Process proc; proc.setTimeoutS(10); proc.setCommand({AndroidConfigurations::currentConfig().keytoolPath(), arguments}); proc.runBlocking(EventLoopMode::On); @@ -631,7 +654,7 @@ bool AndroidManager::checkCertificateExists(const FilePath &keystorePath, QStringList arguments = { "-list", "-keystore", keystorePath.toUserOutput(), "--storepass", keystorePasswd, "-alias", alias }; - QtcProcess proc; + Process proc; proc.setTimeoutS(10); proc.setCommand({AndroidConfigurations::currentConfig().keytoolPath(), arguments}); proc.runBlocking(EventLoopMode::On); @@ -666,7 +689,7 @@ SdkToolResult AndroidManager::runCommand(const CommandLine &command, const QByteArray &writeData, int timeoutS) { Android::SdkToolResult cmdResult; - QtcProcess cmdProc; + Process cmdProc; cmdProc.setTimeoutS(timeoutS); cmdProc.setWriteData(writeData); qCDebug(androidManagerLog) << "Running command (sync):" << command.toUserOutput(); diff --git a/src/plugins/android/androidmanager.h b/src/plugins/android/androidmanager.h index d468ec3279e..ec6aff85bc6 100644 --- a/src/plugins/android/androidmanager.h +++ b/src/plugins/android/androidmanager.h @@ -82,6 +82,7 @@ public: static bool matchedAbis(const QStringList &deviceAbis, const QStringList &appAbis); static QString devicePreferredAbi(const QStringList &deviceAbis, const QStringList &appAbis); static ProjectExplorer::Abi androidAbi2Abi(const QString &androidAbi); + static bool skipInstallationAndPackageSteps(const ProjectExplorer::Target *target); static QString androidNameForApiLevel(int x); diff --git a/src/plugins/android/androidmanifesteditorwidget.cpp b/src/plugins/android/androidmanifesteditorwidget.cpp index 009a0219430..9c6a616abe3 100644 --- a/src/plugins/android/androidmanifesteditorwidget.cpp +++ b/src/plugins/android/androidmanifesteditorwidget.cpp @@ -19,8 +19,8 @@ #include #include #include +#include #include -#include #include #include #include @@ -73,7 +73,7 @@ static bool checkPackageName(const QString &packageName) static Target *androidTarget(const FilePath &fileName) { - for (Project *project : SessionManager::projects()) { + for (Project *project : ProjectManager::projects()) { if (Target *target = project->activeTarget()) { Kit *kit = target->kit(); if (DeviceTypeKitAspect::deviceTypeId(kit) == Android::Constants::ANDROID_DEVICE_TYPE diff --git a/src/plugins/android/androidpackageinstallationstep.cpp b/src/plugins/android/androidpackageinstallationstep.cpp index 3b8a83fa578..e29bac3476b 100644 --- a/src/plugins/android/androidpackageinstallationstep.cpp +++ b/src/plugins/android/androidpackageinstallationstep.cpp @@ -22,7 +22,7 @@ #include #include -#include +#include #include #include @@ -122,6 +122,14 @@ void AndroidPackageInstallationStep::setupOutputFormatter(OutputFormatter *forma void AndroidPackageInstallationStep::doRun() { + if (AndroidManager::skipInstallationAndPackageSteps(target())) { + reportWarningOrError(Tr::tr("Product type is not an application, not running the " + "Make install step."), + Task::Warning); + emit finished(true); + return; + } + QString error; for (const QString &dir : std::as_const(m_androidDirsToClean)) { FilePath androidDir = FilePath::fromString(dir); diff --git a/src/plugins/android/androidplugin.cpp b/src/plugins/android/androidplugin.cpp index b8ff5dbe0b8..916600c1df7 100644 --- a/src/plugins/android/androidplugin.cpp +++ b/src/plugins/android/androidplugin.cpp @@ -34,7 +34,6 @@ #endif #include -#include #include #include @@ -45,11 +44,13 @@ #include #include #include -#include +#include #include #include +#include + using namespace ProjectExplorer; using namespace ProjectExplorer::Constants; @@ -69,17 +70,6 @@ public: } }; -class AndroidRunConfigurationFactory : public RunConfigurationFactory -{ -public: - AndroidRunConfigurationFactory() - { - registerRunConfiguration - ("Qt4ProjectManager.AndroidRunConfiguration:"); - addSupportedTargetDeviceType(Android::Constants::ANDROID_DEVICE_TYPE); - } -}; - class AndroidPluginPrivate : public QObject { public: @@ -151,8 +141,7 @@ void AndroidPlugin::kitsRestored() void AndroidPlugin::askUserAboutAndroidSetup() { - if (!Utils::CheckableMessageBox::shouldAskAgain(Core::ICore::settings(), kSetupAndroidSetting) - || !Core::ICore::infoBar()->canInfoBeAdded(kSetupAndroidSetting)) + if (!Core::ICore::infoBar()->canInfoBeAdded(kSetupAndroidSetting)) return; Utils::InfoBarEntry diff --git a/src/plugins/android/androidpotentialkit.cpp b/src/plugins/android/androidpotentialkit.cpp index 914cbc0acd5..4d95f485d0e 100644 --- a/src/plugins/android/androidpotentialkit.cpp +++ b/src/plugins/android/androidpotentialkit.cpp @@ -8,23 +8,35 @@ #include -#include -#include - #include #include + #include #include #include + #include #include +#include +#include + #include #include #include -using namespace Android; -using namespace Android::Internal; + +namespace Android::Internal { + +class AndroidPotentialKitWidget : public Utils::DetailsWidget +{ +public: + AndroidPotentialKitWidget(QWidget *parent); + +private: + void openOptions(); + void recheck(); +}; QString AndroidPotentialKit::displayName() const { @@ -102,3 +114,5 @@ void AndroidPotentialKitWidget::recheck() } } } + +} // Android::Internal diff --git a/src/plugins/android/androidpotentialkit.h b/src/plugins/android/androidpotentialkit.h index 9e1ed417423..d1110294756 100644 --- a/src/plugins/android/androidpotentialkit.h +++ b/src/plugins/android/androidpotentialkit.h @@ -4,14 +4,11 @@ #pragma once #include -#include -namespace Android { -namespace Internal { +namespace Android::Internal { class AndroidPotentialKit : public ProjectExplorer::IPotentialKit { - Q_OBJECT public: QString displayName() const override; void executeFromMenu() override; @@ -19,15 +16,4 @@ public: bool isEnabled() const override; }; -class AndroidPotentialKitWidget : public Utils::DetailsWidget -{ - Q_OBJECT -public: - AndroidPotentialKitWidget(QWidget *parent); -private: - void openOptions(); - void recheck(); -}; - -} -} +} // Android::Internal diff --git a/src/plugins/android/androidqmlpreviewworker.cpp b/src/plugins/android/androidqmlpreviewworker.cpp index ab9fb34de5a..d296dfe677e 100644 --- a/src/plugins/android/androidqmlpreviewworker.cpp +++ b/src/plugins/android/androidqmlpreviewworker.cpp @@ -27,8 +27,8 @@ #include #include -#include -#include +#include +#include #include #include @@ -117,7 +117,7 @@ private: QStringList m_avdAbis; int m_viewerPid = -1; QFutureWatcher m_pidFutureWatcher; - Utils::QtcProcess m_logcatProcess; + Utils::Process m_logcatProcess; QString m_logcatStartTimeStamp; UploadInfo m_uploadInfo; }; @@ -163,7 +163,7 @@ bool AndroidQmlPreviewWorker::isPreviewRunning(int lastKnownPid) const void AndroidQmlPreviewWorker::startPidWatcher() { - m_pidFutureWatcher.setFuture(runAsync([this] { + m_pidFutureWatcher.setFuture(Utils::asyncRun([this] { // wait for started const int sleepTimeMs = 2000; QDeadlineTimer deadline(20000); @@ -226,7 +226,7 @@ AndroidQmlPreviewWorker::AndroidQmlPreviewWorker(RunControl *runControl) connect(this, &AndroidQmlPreviewWorker::previewPidChanged, this, &AndroidQmlPreviewWorker::startLogcat); - connect(this, &RunWorker::stopped, &m_logcatProcess, &QtcProcess::stop); + connect(this, &RunWorker::stopped, &m_logcatProcess, &Process::stop); m_logcatProcess.setStdOutCallback([this](const QString &stdOut) { filterLogcatAndAppendMessage(stdOut); }); @@ -376,7 +376,7 @@ FilePath AndroidQmlPreviewWorker::createQmlrcFile(const FilePath &workFolder, { const QtSupport::QtVersion *qtVersion = QtSupport::QtKitAspect::qtVersion(m_rc->kit()); const FilePath rccBinary = qtVersion->rccFilePath(); - QtcProcess rccProcess; + Process rccProcess; FilePath qrcPath = FilePath::fromString(basename + ".qrc4viewer"); const FilePath qmlrcPath = FilePath::fromString(QDir::tempPath()) / (basename + packageSuffix); diff --git a/src/plugins/android/androidrunconfiguration.cpp b/src/plugins/android/androidrunconfiguration.cpp index ef469da2b62..45f991ca747 100644 --- a/src/plugins/android/androidrunconfiguration.cpp +++ b/src/plugins/android/androidrunconfiguration.cpp @@ -18,8 +18,8 @@ #include #include +#include #include -#include #include using namespace ProjectExplorer; @@ -46,49 +46,59 @@ public: } }; -AndroidRunConfiguration::AndroidRunConfiguration(Target *target, Utils::Id id) - : RunConfiguration(target, id) +class AndroidRunConfiguration : public RunConfiguration { - auto envAspect = addAspect(); - envAspect->addSupportedBaseEnvironment(Tr::tr("Clean Environment"), {}); +public: + AndroidRunConfiguration(Target *target, Id id) + : RunConfiguration(target, id) + { + auto envAspect = addAspect(); + envAspect->addSupportedBaseEnvironment(Tr::tr("Clean Environment"), {}); - auto extraAppArgsAspect = addAspect(macroExpander()); + auto extraAppArgsAspect = addAspect(macroExpander()); - connect(extraAppArgsAspect, &BaseAspect::changed, this, [target, extraAppArgsAspect] { - if (target->buildConfigurations().first()->buildType() == BuildConfiguration::BuildType::Release) { - const QString buildKey = target->activeBuildKey(); - target->buildSystem()->setExtraData(buildKey, - Android::Constants::AndroidApplicationArgs, - extraAppArgsAspect->arguments()); - } - }); + connect(extraAppArgsAspect, &BaseAspect::changed, this, [target, extraAppArgsAspect] { + if (target->buildConfigurations().first()->buildType() == BuildConfiguration::BuildType::Release) { + const QString buildKey = target->activeBuildKey(); + target->buildSystem()->setExtraData(buildKey, + Android::Constants::AndroidApplicationArgs, + extraAppArgsAspect->arguments()); + } + }); - auto amStartArgsAspect = addAspect(); - amStartArgsAspect->setId(Constants::ANDROID_AM_START_ARGS); - amStartArgsAspect->setSettingsKey("Android.AmStartArgsKey"); - amStartArgsAspect->setLabelText(Tr::tr("Activity manager start arguments:")); - amStartArgsAspect->setDisplayStyle(StringAspect::LineEditDisplay); - amStartArgsAspect->setHistoryCompleter("Android.AmStartArgs.History"); + auto amStartArgsAspect = addAspect(); + amStartArgsAspect->setId(Constants::ANDROID_AM_START_ARGS); + amStartArgsAspect->setSettingsKey("Android.AmStartArgsKey"); + amStartArgsAspect->setLabelText(Tr::tr("Activity manager start arguments:")); + amStartArgsAspect->setDisplayStyle(StringAspect::LineEditDisplay); + amStartArgsAspect->setHistoryCompleter("Android.AmStartArgs.History"); - auto preStartShellCmdAspect = addAspect(); - preStartShellCmdAspect->setDisplayStyle(StringAspect::TextEditDisplay); - preStartShellCmdAspect->setId(Constants::ANDROID_PRESTARTSHELLCMDLIST); - preStartShellCmdAspect->setSettingsKey("Android.PreStartShellCmdListKey"); - preStartShellCmdAspect->setLabelText(Tr::tr("Pre-launch on-device shell commands:")); + auto preStartShellCmdAspect = addAspect(); + preStartShellCmdAspect->setDisplayStyle(StringAspect::TextEditDisplay); + preStartShellCmdAspect->setId(Constants::ANDROID_PRESTARTSHELLCMDLIST); + preStartShellCmdAspect->setSettingsKey("Android.PreStartShellCmdListKey"); + preStartShellCmdAspect->setLabelText(Tr::tr("Pre-launch on-device shell commands:")); - auto postStartShellCmdAspect = addAspect(); - postStartShellCmdAspect->setDisplayStyle(StringAspect::TextEditDisplay); - postStartShellCmdAspect->setId(Constants::ANDROID_POSTFINISHSHELLCMDLIST); - postStartShellCmdAspect->setSettingsKey("Android.PostStartShellCmdListKey"); - postStartShellCmdAspect->setLabelText(Tr::tr("Post-quit on-device shell commands:")); + auto postStartShellCmdAspect = addAspect(); + postStartShellCmdAspect->setDisplayStyle(StringAspect::TextEditDisplay); + postStartShellCmdAspect->setId(Constants::ANDROID_POSTFINISHSHELLCMDLIST); + postStartShellCmdAspect->setSettingsKey("Android.PostStartShellCmdListKey"); + postStartShellCmdAspect->setLabelText(Tr::tr("Post-quit on-device shell commands:")); - setUpdater([this] { - const BuildTargetInfo bti = buildTargetInfo(); - setDisplayName(bti.displayName); - setDefaultDisplayName(bti.displayName); - }); + setUpdater([this] { + const BuildTargetInfo bti = buildTargetInfo(); + setDisplayName(bti.displayName); + setDefaultDisplayName(bti.displayName); + }); - connect(target, &Target::buildSystemUpdated, this, &RunConfiguration::update); + connect(target, &Target::buildSystemUpdated, this, &RunConfiguration::update); + } +}; + +AndroidRunConfigurationFactory::AndroidRunConfigurationFactory() +{ + registerRunConfiguration("Qt4ProjectManager.AndroidRunConfiguration:"); + addSupportedTargetDeviceType(Android::Constants::ANDROID_DEVICE_TYPE); } } // namespace Android diff --git a/src/plugins/android/androidrunconfiguration.h b/src/plugins/android/androidrunconfiguration.h index 57576c555a9..9ad677aeef3 100644 --- a/src/plugins/android/androidrunconfiguration.h +++ b/src/plugins/android/androidrunconfiguration.h @@ -9,11 +9,10 @@ namespace Android { -class ANDROID_EXPORT AndroidRunConfiguration : public ProjectExplorer::RunConfiguration +class AndroidRunConfigurationFactory : public ProjectExplorer::RunConfigurationFactory { - Q_OBJECT public: - explicit AndroidRunConfiguration(ProjectExplorer::Target *target, Utils::Id id); + AndroidRunConfigurationFactory(); }; } // namespace Android diff --git a/src/plugins/android/androidruncontrol.h b/src/plugins/android/androidruncontrol.h index a466de72269..f96c19443b0 100644 --- a/src/plugins/android/androidruncontrol.h +++ b/src/plugins/android/androidruncontrol.h @@ -3,14 +3,10 @@ #pragma once -#include "androidrunner.h" - -#include +#include namespace Android::Internal { -class AndroidRunner; - class AndroidRunWorkerFactory final : public ProjectExplorer::RunWorkerFactory { public: diff --git a/src/plugins/android/androidrunnerworker.cpp b/src/plugins/android/androidrunnerworker.cpp index 2676fa6305c..65ef8695580 100644 --- a/src/plugins/android/androidrunnerworker.cpp +++ b/src/plugins/android/androidrunnerworker.cpp @@ -11,8 +11,8 @@ #include #include -#include #include +#include #include #include #include @@ -20,10 +20,10 @@ #include #include +#include #include #include -#include -#include +#include #include #include #include @@ -78,8 +78,10 @@ static qint64 extractPID(const QString &output, const QString &packageName) return pid; } -static void findProcessPIDAndUser(QFutureInterface &fi, QStringList selector, - const QString &packageName, bool preNougat) +static void findProcessPIDAndUser(QPromise &promise, + QStringList selector, + const QString &packageName, + bool preNougat) { if (packageName.isEmpty()) return; @@ -96,7 +98,7 @@ static void findProcessPIDAndUser(QFutureInterface &fi, QStringList chrono::high_resolution_clock::time_point start = chrono::high_resolution_clock::now(); do { QThread::msleep(200); - QtcProcess proc; + Process proc; proc.setCommand({adbPath, args}); proc.runBlocking(); const QString out = proc.allOutput(); @@ -106,16 +108,16 @@ static void findProcessPIDAndUser(QFutureInterface &fi, QStringList if (!out.isEmpty()) processPID = out.trimmed().toLongLong(); } - } while ((processPID == -1 || processPID == 0) && !isTimedOut(start) && !fi.isCanceled()); + } while ((processPID == -1 || processPID == 0) && !isTimedOut(start) && !promise.isCanceled()); qCDebug(androidRunWorkerLog) << "PID found:" << processPID << ", PreNougat:" << preNougat; qint64 processUser = 0; - if (processPID > 0 && !fi.isCanceled()) { + if (processPID > 0 && !promise.isCanceled()) { args = {selector}; args.append({"shell", "ps", "-o", "user", "-p"}); args.append(QString::number(processPID)); - QtcProcess proc; + Process proc; proc.setCommand({adbPath, args}); proc.runBlocking(); const QString out = proc.allOutput(); @@ -133,8 +135,8 @@ static void findProcessPIDAndUser(QFutureInterface &fi, QStringList qCDebug(androidRunWorkerLog) << "USER found:" << processUser; - if (!fi.isCanceled()) - fi.reportResult(PidUserPair(processPID, processUser)); + if (!promise.isCanceled()) + promise.addResult(PidUserPair(processPID, processUser)); } static void deleter(QProcess *p) @@ -724,8 +726,11 @@ void AndroidRunnerWorker::asyncStart() { asyncStartHelper(); - m_pidFinder = Utils::onResultReady(Utils::runAsync(findProcessPIDAndUser, selector(), - m_packageName, m_isPreNougat), + m_pidFinder = Utils::onResultReady(Utils::asyncRun(findProcessPIDAndUser, + selector(), + m_packageName, + m_isPreNougat), + this, bind(&AndroidRunnerWorker::onProcessIdChanged, this, _1)); } diff --git a/src/plugins/android/androidsdkmanager.cpp b/src/plugins/android/androidsdkmanager.cpp index 4494f4837fa..1839f7c21c4 100644 --- a/src/plugins/android/androidsdkmanager.cpp +++ b/src/plugins/android/androidsdkmanager.cpp @@ -8,9 +8,9 @@ #include "sdkmanageroutputparser.h" #include +#include +#include #include -#include -#include #include #include @@ -34,7 +34,7 @@ namespace Internal { const int sdkManagerCmdTimeoutS = 60; const int sdkManagerOperationTimeoutS = 600; -using SdkCmdFutureInterface = QFutureInterface; +using SdkCmdPromise = QPromise; static const QRegularExpression &assertionRegExp() { @@ -93,7 +93,7 @@ static bool sdkManagerCommand(const AndroidConfig &config, const QStringList &ar qCDebug(sdkManagerLog).noquote() << "Running SDK Manager command (sync):" << CommandLine(config.sdkManagerToolPath(), newArgs) .toUserOutput(); - QtcProcess proc; + Process proc; proc.setEnvironment(AndroidConfigurations::toolsEnvironment(config)); proc.setTimeoutS(timeout); proc.setTimeOutMessageBoxEnabled(true); @@ -111,7 +111,7 @@ static bool sdkManagerCommand(const AndroidConfig &config, const QStringList &ar after the lapse of \a timeout seconds. The function blocks the calling thread. */ static void sdkManagerCommand(const AndroidConfig &config, const QStringList &args, - AndroidSdkManager &sdkManager, SdkCmdFutureInterface &fi, + AndroidSdkManager &sdkManager, SdkCmdPromise &promise, AndroidSdkManager::OperationOutput &output, double progressQuota, bool interruptible = true, int timeout = sdkManagerOperationTimeoutS) { @@ -120,19 +120,19 @@ static void sdkManagerCommand(const AndroidConfig &config, const QStringList &ar qCDebug(sdkManagerLog).noquote() << "Running SDK Manager command (async):" << CommandLine(config.sdkManagerToolPath(), newArgs) .toUserOutput(); - int offset = fi.progressValue(); - QtcProcess proc; + int offset = promise.future().progressValue(); + Process proc; proc.setEnvironment(AndroidConfigurations::toolsEnvironment(config)); bool assertionFound = false; proc.setTimeoutS(timeout); - proc.setStdOutCallback([offset, progressQuota, &proc, &assertionFound, &fi](const QString &out) { + proc.setStdOutCallback([offset, progressQuota, &proc, &assertionFound, &promise](const QString &out) { int progressPercent = parseProgress(out, assertionFound); if (assertionFound) { proc.stop(); proc.waitForFinished(); } if (progressPercent != -1) - fi.setProgressValue(offset + qRound((progressPercent / 100.0) * progressQuota)); + promise.setProgressValue(offset + qRound((progressPercent / 100.0) * progressQuota)); }); proc.setStdErrCallback([&output](const QString &err) { output.stdError = err; @@ -168,12 +168,12 @@ public: const AndroidSdkPackageList &allPackages(bool forceUpdate = false); void refreshSdkPackages(bool forceReload = false); - void parseCommonArguments(QFutureInterface &fi); - void updateInstalled(SdkCmdFutureInterface &fi); - void update(SdkCmdFutureInterface &fi, const QStringList &install, + void parseCommonArguments(QPromise &promise); + void updateInstalled(SdkCmdPromise &fi); + void update(SdkCmdPromise &fi, const QStringList &install, const QStringList &uninstall); - void checkPendingLicense(SdkCmdFutureInterface &fi); - void getPendingLicense(SdkCmdFutureInterface &fi); + void checkPendingLicense(SdkCmdPromise &fi); + void getPendingLicense(SdkCmdPromise &fi); void addWatcher(const QFuture &future); void setLicenseInput(bool acceptLicense); @@ -186,7 +186,7 @@ private: void reloadSdkPackages(); void clearPackages(); bool onLicenseStdOut(const QString &output, bool notify, - AndroidSdkManager::OperationOutput &result, SdkCmdFutureInterface &fi); + AndroidSdkManager::OperationOutput &result, SdkCmdPromise &fi); AndroidSdkManager &m_sdkManager; const AndroidConfig &m_config; @@ -308,7 +308,7 @@ bool AndroidSdkManager::packageListingSuccessful() const QFuture AndroidSdkManager::availableArguments() const { - return Utils::runAsync(&AndroidSdkManagerPrivate::parseCommonArguments, m_d.get()); + return Utils::asyncRun(&AndroidSdkManagerPrivate::parseCommonArguments, m_d.get()); } QFuture AndroidSdkManager::updateAll() @@ -316,7 +316,7 @@ QFuture AndroidSdkManager::updateAll() if (isBusy()) { return QFuture(); } - auto future = Utils::runAsync(&AndroidSdkManagerPrivate::updateInstalled, m_d.get()); + auto future = Utils::asyncRun(&AndroidSdkManagerPrivate::updateInstalled, m_d.get()); m_d->addWatcher(future); return future; } @@ -326,7 +326,7 @@ AndroidSdkManager::update(const QStringList &install, const QStringList &uninsta { if (isBusy()) return QFuture(); - auto future = Utils::runAsync(&AndroidSdkManagerPrivate::update, m_d.get(), install, uninstall); + auto future = Utils::asyncRun(&AndroidSdkManagerPrivate::update, m_d.get(), install, uninstall); m_d->addWatcher(future); return future; } @@ -335,7 +335,7 @@ QFuture AndroidSdkManager::checkPendingLicen { if (isBusy()) return QFuture(); - auto future = Utils::runAsync(&AndroidSdkManagerPrivate::checkPendingLicense, m_d.get()); + auto future = Utils::asyncRun(&AndroidSdkManagerPrivate::checkPendingLicense, m_d.get()); m_d->addWatcher(future); return future; } @@ -344,7 +344,7 @@ QFuture AndroidSdkManager::runLicenseCommand { if (isBusy()) return QFuture(); - auto future = Utils::runAsync(&AndroidSdkManagerPrivate::getPendingLicense, m_d.get()); + auto future = Utils::asyncRun(&AndroidSdkManagerPrivate::getPendingLicense, m_d.get()); m_d->addWatcher(future); return future; } @@ -422,29 +422,29 @@ void AndroidSdkManagerPrivate::refreshSdkPackages(bool forceReload) reloadSdkPackages(); } -void AndroidSdkManagerPrivate::updateInstalled(SdkCmdFutureInterface &fi) +void AndroidSdkManagerPrivate::updateInstalled(SdkCmdPromise &promise) { - fi.setProgressRange(0, 100); - fi.setProgressValue(0); + promise.setProgressRange(0, 100); + promise.setProgressValue(0); AndroidSdkManager::OperationOutput result; result.type = AndroidSdkManager::UpdateAll; result.stdOutput = Tr::tr("Updating installed packages."); - fi.reportResult(result); + promise.addResult(result); QStringList args("--update"); args << m_config.sdkManagerToolArgs(); - if (!fi.isCanceled()) - sdkManagerCommand(m_config, args, m_sdkManager, fi, result, 100); + if (!promise.isCanceled()) + sdkManagerCommand(m_config, args, m_sdkManager, promise, result, 100); else qCDebug(sdkManagerLog) << "Update: Operation cancelled before start"; if (result.stdError.isEmpty() && !result.success) result.stdError = Tr::tr("Failed."); result.stdOutput = Tr::tr("Done\n\n"); - fi.reportResult(result); - fi.setProgressValue(100); + promise.addResult(result); + promise.setProgressValue(100); } -void AndroidSdkManagerPrivate::update(SdkCmdFutureInterface &fi, const QStringList &install, +void AndroidSdkManagerPrivate::update(SdkCmdPromise &fi, const QStringList &install, const QStringList &uninstall) { fi.setProgressRange(0, 100); @@ -461,7 +461,7 @@ void AndroidSdkManagerPrivate::update(SdkCmdFutureInterface &fi, const QStringLi result.type = AndroidSdkManager::UpdatePackage; result.stdOutput = QString("%1 %2").arg(isInstall ? installTag : uninstallTag) .arg(packagePath); - fi.reportResult(result); + fi.addResult(result); if (fi.isCanceled()) qCDebug(sdkManagerLog) << args << "Update: Operation cancelled before start"; else @@ -471,7 +471,7 @@ void AndroidSdkManagerPrivate::update(SdkCmdFutureInterface &fi, const QStringLi if (result.stdError.isEmpty() && !result.success) result.stdError = Tr::tr("AndroidSdkManager", "Failed"); result.stdOutput = Tr::tr("AndroidSdkManager", "Done\n\n"); - fi.reportResult(result); + fi.addResult(result); return fi.isCanceled(); }; @@ -495,7 +495,7 @@ void AndroidSdkManagerPrivate::update(SdkCmdFutureInterface &fi, const QStringLi fi.setProgressValue(100); } -void AndroidSdkManagerPrivate::checkPendingLicense(SdkCmdFutureInterface &fi) +void AndroidSdkManagerPrivate::checkPendingLicense(SdkCmdPromise &fi) { fi.setProgressRange(0, 100); fi.setProgressValue(0); @@ -509,11 +509,11 @@ void AndroidSdkManagerPrivate::checkPendingLicense(SdkCmdFutureInterface &fi) qCDebug(sdkManagerLog) << "Update: Operation cancelled before start"; } - fi.reportResult(result); + fi.addResult(result); fi.setProgressValue(100); } -void AndroidSdkManagerPrivate::getPendingLicense(SdkCmdFutureInterface &fi) +void AndroidSdkManagerPrivate::getPendingLicense(SdkCmdPromise &fi) { fi.setProgressRange(0, 100); fi.setProgressValue(0); @@ -521,7 +521,7 @@ void AndroidSdkManagerPrivate::getPendingLicense(SdkCmdFutureInterface &fi) AndroidSdkManager::OperationOutput result; result.type = AndroidSdkManager::LicenseWorkflow; - QtcProcess licenseCommand; + Process licenseCommand; licenseCommand.setProcessMode(ProcessMode::Writer); licenseCommand.setEnvironment(AndroidConfigurations::toolsEnvironment(m_config)); bool reviewingLicenses = false; @@ -549,7 +549,7 @@ void AndroidSdkManagerPrivate::getPendingLicense(SdkCmdFutureInterface &fi) } else if (assertionFound) { // The first assertion is to start reviewing licenses. Always accept. reviewingLicenses = true; - QRegularExpression reg("(\\d+\\sof\\s)(?\\d+)"); + static const QRegularExpression reg(R"((\d+\sof\s)(?\d+))"); QRegularExpressionMatch match = reg.match(stdOut); if (match.hasMatch()) steps = match.captured("steps").toInt(); @@ -571,7 +571,7 @@ void AndroidSdkManagerPrivate::getPendingLicense(SdkCmdFutureInterface &fi) result.success = licenseCommand.exitStatus() == QProcess::NormalExit; if (!result.success) result.stdError = Tr::tr("License command failed.\n\n"); - fi.reportResult(result); + fi.addResult(result); fi.setProgressValue(100); } @@ -595,14 +595,14 @@ void AndroidSdkManagerPrivate::clearUserInput() bool AndroidSdkManagerPrivate::onLicenseStdOut(const QString &output, bool notify, AndroidSdkManager::OperationOutput &result, - SdkCmdFutureInterface &fi) + SdkCmdPromise &fi) { m_licenseTextCache.append(output); const QRegularExpressionMatch assertionMatch = assertionRegExp().match(m_licenseTextCache); if (assertionMatch.hasMatch()) { if (notify) { result.stdOutput = m_licenseTextCache; - fi.reportResult(result); + fi.addResult(result); } // Clear the current contents. The found license text is dispatched. Continue collecting the // next license text. @@ -620,7 +620,7 @@ void AndroidSdkManagerPrivate::addWatcher(const QFuturesetFuture(QFuture(future)); } -void AndroidSdkManagerPrivate::parseCommonArguments(QFutureInterface &fi) +void AndroidSdkManagerPrivate::parseCommonArguments(QPromise &promise) { QString argumentDetails; QString output; @@ -628,7 +628,7 @@ void AndroidSdkManagerPrivate::parseCommonArguments(QFutureInterface &f bool foundTag = false; const auto lines = output.split('\n'); for (const QString& line : lines) { - if (fi.isCanceled()) + if (promise.isCanceled()) break; if (foundTag) argumentDetails.append(line + "\n"); @@ -636,8 +636,8 @@ void AndroidSdkManagerPrivate::parseCommonArguments(QFutureInterface &f foundTag = true; } - if (!fi.isCanceled()) - fi.reportResult(argumentDetails); + if (!promise.isCanceled()) + promise.addResult(argumentDetails); } void AndroidSdkManagerPrivate::clearPackages() diff --git a/src/plugins/android/androidsdkmanagerwidget.cpp b/src/plugins/android/androidsdkmanagerwidget.cpp index c2b1ce6d6b3..8627e0c64ed 100644 --- a/src/plugins/android/androidsdkmanagerwidget.cpp +++ b/src/plugins/android/androidsdkmanagerwidget.cpp @@ -9,10 +9,10 @@ #include +#include #include #include #include -#include #include #include @@ -141,14 +141,16 @@ AndroidSdkManagerWidget::AndroidSdkManagerWidget(AndroidConfig &config, } }, optionsButton - } - }.attachTo(m_packagesStack, WithoutMargins); + }, + noMargin + }.attachTo(m_packagesStack); Column { m_outputEdit, Row { m_sdkLicenseLabel, m_sdkLicenseButtonBox }, m_operationProgress, - }.attachTo(m_outputStack, WithoutMargins); + noMargin + }.attachTo(m_outputStack); Column { m_viewStack, @@ -644,7 +646,7 @@ OptionsDialog::OptionsDialog(AndroidSdkManager *sdkManager, const QStringList &a } }; m_optionsFuture = sdkManager->availableArguments(); - Utils::onResultReady(m_optionsFuture, populateOptions); + Utils::onResultReady(m_optionsFuture, this, populateOptions); auto dialogButtons = new QDialogButtonBox(this); dialogButtons->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); diff --git a/src/plugins/android/androidsettingswidget.cpp b/src/plugins/android/androidsettingswidget.cpp index ae2d28ae1aa..dfb14c49cfc 100644 --- a/src/plugins/android/androidsettingswidget.cpp +++ b/src/plugins/android/androidsettingswidget.cpp @@ -17,8 +17,8 @@ #include #include #include +#include #include -#include #include #include @@ -145,8 +145,6 @@ public: ~AndroidSettingsWidget() final; private: - void apply() final { AndroidConfigurations::setConfig(m_androidConfig); } - void showEvent(QShowEvent *event) override; void validateJdk(); @@ -450,6 +448,8 @@ AndroidSettingsWidget::AndroidSettingsWidget() delete openSslOneShot; }); }); + + setOnApply([this] { AndroidConfigurations::setConfig(m_androidConfig); }); } AndroidSettingsWidget::~AndroidSettingsWidget() @@ -659,7 +659,7 @@ void AndroidSettingsWidget::downloadOpenSslRepo(const bool silent) openSslProgressDialog->setFixedSize(openSslProgressDialog->sizeHint()); const QString openSslRepo("https://github.com/KDAB/android_openssl.git"); - QtcProcess *gitCloner = new QtcProcess(this); + Process *gitCloner = new Process(this); CommandLine gitCloneCommand("git", {"clone", "--depth=1", openSslRepo, openSslPath.toString()}); gitCloner->setCommand(gitCloneCommand); @@ -684,7 +684,7 @@ void AndroidSettingsWidget::downloadOpenSslRepo(const bool silent) openButton->deleteLater(); }; - connect(gitCloner, &QtcProcess::done, this, [=] { + connect(gitCloner, &Process::done, this, [=] { openSslProgressDialog->close(); if (gitCloner->error() != QProcess::UnknownError) { if (gitCloner->error() == QProcess::FailedToStart) { @@ -718,7 +718,7 @@ void AndroidSettingsWidget::updateUI() const bool openSslOk = m_openSslSummary->allRowsOk(); const QListWidgetItem *currentItem = m_ndkListWidget->currentItem(); - const FilePath currentNdk = FilePath::fromString(currentItem ? currentItem->text() : ""); + const FilePath currentNdk = FilePath::fromUserInput(currentItem ? currentItem->text() : ""); const QString infoText = Tr::tr("(SDK Version: %1, NDK Version: %2)") .arg(m_androidConfig.sdkToolsVersion().toString()) .arg(currentNdk.isEmpty() ? "" : m_androidConfig.ndkVersion(currentNdk).toString()); diff --git a/src/plugins/android/androidsignaloperation.cpp b/src/plugins/android/androidsignaloperation.cpp index 426b63c1441..b6a7699dd4f 100644 --- a/src/plugins/android/androidsignaloperation.cpp +++ b/src/plugins/android/androidsignaloperation.cpp @@ -4,8 +4,8 @@ #include "androidconfigurations.h" #include "androidsignaloperation.h" +#include #include -#include using namespace Utils; @@ -90,8 +90,8 @@ void AndroidSignalOperation::startAdbProcess(State state, const Utils::CommandLi { m_state = state; m_timeout->start(); - m_adbProcess.reset(new QtcProcess); - connect(m_adbProcess.get(), &QtcProcess::done, this, handler); + m_adbProcess.reset(new Process); + connect(m_adbProcess.get(), &Process::done, this, handler); m_adbProcess->setCommand(commandLine); m_adbProcess->start(); } diff --git a/src/plugins/android/androidsignaloperation.h b/src/plugins/android/androidsignaloperation.h index 06a88b67057..1c3bb48454c 100644 --- a/src/plugins/android/androidsignaloperation.h +++ b/src/plugins/android/androidsignaloperation.h @@ -42,7 +42,7 @@ private: void startAdbProcess(State state, const Utils::CommandLine &commandLine, FinishHandler handler); Utils::FilePath m_adbPath; - std::unique_ptr m_adbProcess; + std::unique_ptr m_adbProcess; QTimer *m_timeout; State m_state = Idle; diff --git a/src/plugins/autotest/autotest.qbs b/src/plugins/autotest/autotest.qbs index f0bccd79c33..90f1728c4ae 100644 --- a/src/plugins/autotest/autotest.qbs +++ b/src/plugins/autotest/autotest.qbs @@ -125,9 +125,7 @@ QtcPlugin { ] } - Group { - name: "Test sources" - condition: qtc.testsEnabled + QtcTestFiles { files: [ "autotestunittests.cpp", "autotestunittests.h", diff --git a/src/plugins/autotest/autotestplugin.cpp b/src/plugins/autotest/autotestplugin.cpp index 98b804b16d3..d6bfc6f9a88 100644 --- a/src/plugins/autotest/autotestplugin.cpp +++ b/src/plugins/autotest/autotestplugin.cpp @@ -32,22 +32,28 @@ #include #include #include + #include #include #include + #include #include + #include + #include #include #include #include +#include #include #include -#include #include + #include #include + #include #include #include @@ -90,7 +96,7 @@ public: void onRunUnderCursorTriggered(TestRunMode mode); TestSettings m_settings; - TestSettingsPage m_testSettingPage{&m_settings}; + TestSettingsPage m_testSettingPage; TestCodeParser m_testCodeParser; TestTreeModel m_testTreeModel{&m_testCodeParser}; @@ -148,11 +154,11 @@ AutotestPluginPrivate::AutotestPluginPrivate() m_testTreeModel.synchronizeTestFrameworks(); m_testTreeModel.synchronizeTestTools(); - auto sessionManager = ProjectExplorer::SessionManager::instance(); - connect(sessionManager, &ProjectExplorer::SessionManager::startupProjectChanged, + auto sessionManager = ProjectExplorer::ProjectManager::instance(); + connect(sessionManager, &ProjectExplorer::ProjectManager::startupProjectChanged, this, [this] { m_runconfigCache.clear(); }); - connect(sessionManager, &ProjectExplorer::SessionManager::aboutToRemoveProject, + connect(sessionManager, &ProjectExplorer::ProjectManager::aboutToRemoveProject, this, [](ProjectExplorer::Project *project) { const auto it = s_projectSettings.constFind(project); if (it != s_projectSettings.constEnd()) { @@ -172,11 +178,6 @@ AutotestPluginPrivate::~AutotestPluginPrivate() delete m_resultsPane; } -TestSettings *AutotestPlugin::settings() -{ - return &dd->m_settings; -} - TestProjectSettings *AutotestPlugin::projectSettings(ProjectExplorer::Project *project) { auto &settings = s_projectSettings[project]; @@ -471,7 +472,7 @@ void AutotestPluginPrivate::onRunUnderCursorTriggered(TestRunMode mode) TestFrameworks AutotestPlugin::activeTestFrameworks() { - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); TestFrameworks sorted; if (!project || projectSettings(project)->useGlobalSettings()) { sorted = Utils::filtered(TestFrameworkManager::registeredFrameworks(), @@ -489,7 +490,7 @@ TestFrameworks AutotestPlugin::activeTestFrameworks() void AutotestPlugin::updateMenuItemsEnabledState() { - const ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + const ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); const ProjectExplorer::Target *target = project ? project->activeTarget() : nullptr; const bool canScan = !dd->m_testRunner.isTestRunning() && dd->m_testCodeParser.state() == TestCodeParser::Idle; diff --git a/src/plugins/autotest/autotestplugin.h b/src/plugins/autotest/autotestplugin.h index 8705a8a538d..e6daa0c7390 100644 --- a/src/plugins/autotest/autotestplugin.h +++ b/src/plugins/autotest/autotestplugin.h @@ -18,7 +18,6 @@ namespace Autotest { namespace Internal { class TestProjectSettings; -struct TestSettings; struct ChoicePair { @@ -43,7 +42,6 @@ public: void extensionsInitialized() override; ShutdownFlag aboutToShutdown() override; - static TestSettings *settings(); static TestProjectSettings *projectSettings(ProjectExplorer::Project *project); static TestFrameworks activeTestFrameworks(); static void updateMenuItemsEnabledState(); diff --git a/src/plugins/autotest/autotestunittests.cpp b/src/plugins/autotest/autotestunittests.cpp index e72cc0cbbb7..50d0b496c21 100644 --- a/src/plugins/autotest/autotestunittests.cpp +++ b/src/plugins/autotest/autotestunittests.cpp @@ -99,8 +99,8 @@ void AutoTestUnitTests::testCodeParser() CppEditor::Tests::ProjectOpenerAndCloser projectManager; QVERIFY(projectManager.open(projectFilePath, true, m_kit)); - QSignalSpy parserSpy(m_model->parser(), SIGNAL(parsingFinished())); - QSignalSpy modelUpdateSpy(m_model, SIGNAL(sweepingDone())); + QSignalSpy parserSpy(m_model->parser(), &TestCodeParser::parsingFinished); + QSignalSpy modelUpdateSpy(m_model, &TestTreeModel::sweepingDone); QVERIFY(parserSpy.wait(20000)); QVERIFY(modelUpdateSpy.wait()); @@ -149,8 +149,8 @@ void AutoTestUnitTests::testCodeParserSwitchStartup() qDebug() << "Opening project" << projectFilePaths.at(i); QVERIFY(projectManager.open(projectFilePaths.at(i), true, m_kit)); - QSignalSpy parserSpy(m_model->parser(), SIGNAL(parsingFinished())); - QSignalSpy modelUpdateSpy(m_model, SIGNAL(sweepingDone())); + QSignalSpy parserSpy(m_model->parser(), &TestCodeParser::parsingFinished); + QSignalSpy modelUpdateSpy(m_model, &TestTreeModel::sweepingDone); QVERIFY(parserSpy.wait(20000)); QVERIFY(modelUpdateSpy.wait()); @@ -199,8 +199,8 @@ void AutoTestUnitTests::testCodeParserGTest() CppEditor::Tests::ProjectOpenerAndCloser projectManager; QVERIFY(projectManager.open(projectFilePath, true, m_kit)); - QSignalSpy parserSpy(m_model->parser(), SIGNAL(parsingFinished())); - QSignalSpy modelUpdateSpy(m_model, SIGNAL(sweepingDone())); + QSignalSpy parserSpy(m_model->parser(), &TestCodeParser::parsingFinished); + QSignalSpy modelUpdateSpy(m_model, &TestTreeModel::sweepingDone); QVERIFY(parserSpy.wait(20000)); QVERIFY(modelUpdateSpy.wait()); @@ -250,8 +250,8 @@ void AutoTestUnitTests::testCodeParserBoostTest() = projectManager.open(projectFilePath, true, m_kit); QVERIFY(projectInfo); - QSignalSpy parserSpy(m_model->parser(), SIGNAL(parsingFinished())); - QSignalSpy modelUpdateSpy(m_model, SIGNAL(sweepingDone())); + QSignalSpy parserSpy(m_model->parser(), &TestCodeParser::parsingFinished); + QSignalSpy modelUpdateSpy(m_model, &TestTreeModel::sweepingDone); QVERIFY(parserSpy.wait(20000)); QVERIFY(modelUpdateSpy.wait()); diff --git a/src/plugins/autotest/boost/boosttestconfiguration.cpp b/src/plugins/autotest/boost/boosttestconfiguration.cpp index 50d8a863f4a..e581cff50ee 100644 --- a/src/plugins/autotest/boost/boosttestconfiguration.cpp +++ b/src/plugins/autotest/boost/boosttestconfiguration.cpp @@ -6,23 +6,20 @@ #include "boosttestoutputreader.h" #include "boosttestsettings.h" -#include "../autotestplugin.h" #include "../itestframework.h" #include "../testsettings.h" #include -#include using namespace Utils; namespace Autotest { namespace Internal { -TestOutputReader *BoostTestConfiguration::createOutputReader( - const QFutureInterface &fi, QtcProcess *app) const +TestOutputReader *BoostTestConfiguration::createOutputReader(Process *app) const { auto settings = static_cast(framework()->testSettings()); - return new BoostTestOutputReader(fi, app, buildDirectory(), projectFile(), + return new BoostTestOutputReader(app, buildDirectory(), projectFile(), LogLevel(settings->logLevel.value()), ReportLevel(settings->reportLevel.value())); } @@ -108,7 +105,7 @@ QStringList BoostTestConfiguration::argumentsForTestRunner(QStringList *omitted) for (const QString &test : testCases()) arguments << "-t" << test; - if (AutotestPlugin::settings()->processArgs) { + if (TestSettings::instance()->processArgs()) { arguments << filterInterfering(runnable().command.arguments().split( ' ', Qt::SkipEmptyParts), omitted); } diff --git a/src/plugins/autotest/boost/boosttestconfiguration.h b/src/plugins/autotest/boost/boosttestconfiguration.h index 8afb74049fd..10e50004bc8 100644 --- a/src/plugins/autotest/boost/boosttestconfiguration.h +++ b/src/plugins/autotest/boost/boosttestconfiguration.h @@ -13,8 +13,7 @@ class BoostTestConfiguration : public DebuggableTestConfiguration public: explicit BoostTestConfiguration(ITestFramework *framework) : DebuggableTestConfiguration(framework) {} - TestOutputReader *createOutputReader(const QFutureInterface &fi, - Utils::QtcProcess *app) const override; + TestOutputReader *createOutputReader(Utils::Process *app) const override; QStringList argumentsForTestRunner(QStringList *omitted = nullptr) const override; Utils::Environment filteredEnvironment(const Utils::Environment &original) const override; }; diff --git a/src/plugins/autotest/boost/boosttestframework.h b/src/plugins/autotest/boost/boosttestframework.h index e8f4c98ed82..da015fbd6f3 100644 --- a/src/plugins/autotest/boost/boosttestframework.h +++ b/src/plugins/autotest/boost/boosttestframework.h @@ -7,8 +7,7 @@ #include "boosttestsettings.h" -namespace Autotest { -namespace Internal { +namespace Autotest::Internal { class BoostTestFramework : public ITestFramework { @@ -23,9 +22,7 @@ private: ITestParser *createTestParser() override; ITestTreeItem *createRootNode() override; - BoostTestSettings m_settings; - BoostTestSettingsPage m_settingsPage{&m_settings, settingsId()}; + BoostTestSettings m_settings{settingsId()}; }; -} // namespace Internal -} // namespace Autotest +} // Autotest::Internal diff --git a/src/plugins/autotest/boost/boosttestoutputreader.cpp b/src/plugins/autotest/boost/boosttestoutputreader.cpp index 5f549cca320..96b0dffd61b 100644 --- a/src/plugins/autotest/boost/boosttestoutputreader.cpp +++ b/src/plugins/autotest/boost/boosttestoutputreader.cpp @@ -5,11 +5,12 @@ #include "boosttestsettings.h" #include "boosttestresult.h" + #include "../autotesttr.h" #include "../testtreeitem.h" +#include #include -#include #include #include @@ -21,12 +22,11 @@ namespace Internal { static Q_LOGGING_CATEGORY(orLog, "qtc.autotest.boost.outputreader", QtWarningMsg) -BoostTestOutputReader::BoostTestOutputReader(const QFutureInterface &futureInterface, - QtcProcess *testApplication, +BoostTestOutputReader::BoostTestOutputReader(Process *testApplication, const FilePath &buildDirectory, const FilePath &projectFile, LogLevel log, ReportLevel report) - : TestOutputReader(futureInterface, testApplication, buildDirectory) + : TestOutputReader(testApplication, buildDirectory) , m_projectFile(projectFile) , m_logLevel(log) , m_reportLevel(report) @@ -34,7 +34,7 @@ BoostTestOutputReader::BoostTestOutputReader(const QFutureInterface if (!testApplication) return; - connect(testApplication, &QtcProcess::done, this, [this, testApplication] { + connect(testApplication, &Process::done, this, [this, testApplication] { onDone(testApplication->exitCode()); }); } diff --git a/src/plugins/autotest/boost/boosttestoutputreader.h b/src/plugins/autotest/boost/boosttestoutputreader.h index 8449a0708ab..648b55547f0 100644 --- a/src/plugins/autotest/boost/boosttestoutputreader.h +++ b/src/plugins/autotest/boost/boosttestoutputreader.h @@ -15,8 +15,7 @@ class BoostTestOutputReader : public TestOutputReader { Q_OBJECT public: - BoostTestOutputReader(const QFutureInterface &futureInterface, - Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory, + BoostTestOutputReader(Utils::Process *testApplication, const Utils::FilePath &buildDirectory, const Utils::FilePath &projectFile, LogLevel log, ReportLevel report); protected: void processOutputLine(const QByteArray &outputLine) override; diff --git a/src/plugins/autotest/boost/boosttestparser.cpp b/src/plugins/autotest/boost/boosttestparser.cpp index bbf6dfd56ce..8c701942ada 100644 --- a/src/plugins/autotest/boost/boosttestparser.cpp +++ b/src/plugins/autotest/boost/boosttestparser.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -96,7 +97,7 @@ static BoostTestParseResult *createParseResult(const QString &name, const FilePa } -bool BoostTestParser::processDocument(QFutureInterface &futureInterface, +bool BoostTestParser::processDocument(QPromise &promise, const FilePath &fileName) { CPlusPlus::Document::Ptr doc = document(fileName); @@ -148,7 +149,7 @@ bool BoostTestParser::processDocument(QFutureInterface &futu locationAndType.m_type, tmpInfo); currentSuite->children.append(funcResult); - futureInterface.reportResult(TestParseResultPtr(topLevelSuite)); + promise.addResult(TestParseResultPtr(topLevelSuite)); } } return true; diff --git a/src/plugins/autotest/boost/boosttestparser.h b/src/plugins/autotest/boost/boosttestparser.h index 049f42d0c93..3ea67815c95 100644 --- a/src/plugins/autotest/boost/boosttestparser.h +++ b/src/plugins/autotest/boost/boosttestparser.h @@ -22,7 +22,7 @@ class BoostTestParser : public CppParser { public: explicit BoostTestParser(ITestFramework *framework) : CppParser(framework) {} - bool processDocument(QFutureInterface &futureInterface, + bool processDocument(QPromise &promise, const Utils::FilePath &fileName) override; }; diff --git a/src/plugins/autotest/boost/boosttestsettings.cpp b/src/plugins/autotest/boost/boosttestsettings.cpp index c995cab9b6c..ed0bad6bcaf 100644 --- a/src/plugins/autotest/boost/boosttestsettings.cpp +++ b/src/plugins/autotest/boost/boosttestsettings.cpp @@ -10,17 +10,29 @@ #include +using namespace Layouting; using namespace Utils; -namespace Autotest { -namespace Internal { +namespace Autotest::Internal { -BoostTestSettings::BoostTestSettings() +BoostTestSettings::BoostTestSettings(Id settingsId) { + setId(settingsId); + setCategory(Constants::AUTOTEST_SETTINGS_CATEGORY); + setDisplayName(Tr::tr(BoostTest::Constants::FRAMEWORK_SETTINGS_CATEGORY)); setSettingsGroups("Autotest", "BoostTest"); - setAutoApply(false); - registerAspect(&logLevel); + setLayouter([this] { + return Row { Form { + logLevel, br, + reportLevel, br, + randomize, Row { seed }, br, + systemErrors, br, + fpExceptions, br, + memLeaks, + }, st}; + }); + logLevel.setSettingsKey("LogLevel"); logLevel.setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox); logLevel.addOption("All"); @@ -37,7 +49,6 @@ BoostTestSettings::BoostTestSettings() logLevel.setDefaultValue(int(LogLevel::Warning)); logLevel.setLabelText(Tr::tr("Log format:")); - registerAspect(&reportLevel); reportLevel.setSettingsKey("ReportLevel"); reportLevel.setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox); reportLevel.addOption("Confirm"); @@ -47,64 +58,35 @@ BoostTestSettings::BoostTestSettings() reportLevel.setDefaultValue(int(ReportLevel::Confirm)); reportLevel.setLabelText(Tr::tr("Report level:")); - registerAspect(&seed); seed.setSettingsKey("Seed"); seed.setEnabled(false); seed.setLabelText(Tr::tr("Seed:")); seed.setToolTip(Tr::tr("A seed of 0 means no randomization. A value of 1 uses the current " - "time, any other value is used as random seed generator.")); + "time, any other value is used as random seed generator.")); seed.setEnabler(&randomize); - registerAspect(&randomize); randomize.setSettingsKey("Randomize"); - randomize.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel); + randomize.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBox); randomize.setLabelText(Tr::tr("Randomize")); randomize.setToolTip(Tr::tr("Randomize execution order.")); - registerAspect(&systemErrors); systemErrors.setSettingsKey("SystemErrors"); - systemErrors.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel); + systemErrors.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBox); systemErrors.setLabelText(Tr::tr("Catch system errors")); systemErrors.setToolTip(Tr::tr("Catch or ignore system errors.")); - registerAspect(&fpExceptions); fpExceptions.setSettingsKey("FPExceptions"); - fpExceptions.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel); + fpExceptions.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBox); fpExceptions.setLabelText(Tr::tr("Floating point exceptions")); fpExceptions.setToolTip(Tr::tr("Enable floating point exception traps.")); - registerAspect(&memLeaks); memLeaks.setSettingsKey("MemoryLeaks"); - memLeaks.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel); + memLeaks.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBox); memLeaks.setDefaultValue(true); memLeaks.setLabelText(Tr::tr("Detect memory leaks")); memLeaks.setToolTip(Tr::tr("Enable memory leak detection.")); } -BoostTestSettingsPage::BoostTestSettingsPage(BoostTestSettings *settings, Id settingsId) -{ - setId(settingsId); - setCategory(Constants::AUTOTEST_SETTINGS_CATEGORY); - setDisplayName(Tr::tr(BoostTest::Constants::FRAMEWORK_SETTINGS_CATEGORY)); - setSettings(settings); - - setLayouter([settings](QWidget *widget) { - BoostTestSettings &s = *settings; - using namespace Layouting; - - Grid grid { - s.logLevel, br, - s.reportLevel, br, - s.randomize, Row { s.seed }, br, - s.systemErrors, br, - s.fpExceptions, br, - s.memLeaks, - }; - - Column { Row { Column { grid, st }, st } }.attachTo(widget); - }); -} - QString BoostTestSettings::logLevelToOption(const LogLevel logLevel) { switch (logLevel) { @@ -134,5 +116,4 @@ QString BoostTestSettings::reportLevelToOption(const ReportLevel reportLevel) return {}; } -} // namespace Internal -} // namespace Autotest +} // Autotest::Internal diff --git a/src/plugins/autotest/boost/boosttestsettings.h b/src/plugins/autotest/boost/boosttestsettings.h index 60a3df5042f..e95f98218c2 100644 --- a/src/plugins/autotest/boost/boosttestsettings.h +++ b/src/plugins/autotest/boost/boosttestsettings.h @@ -5,10 +5,7 @@ #include -#include - -namespace Autotest { -namespace Internal { +namespace Autotest::Internal { enum class LogLevel { @@ -33,32 +30,24 @@ enum class ReportLevel No }; -class BoostTestSettings : public Utils::AspectContainer +class BoostTestSettings : public Core::PagedSettings { public: - BoostTestSettings(); + explicit BoostTestSettings(Utils::Id settingsId); static QString logLevelToOption(const LogLevel logLevel); static QString reportLevelToOption(const ReportLevel reportLevel); - Utils::SelectionAspect logLevel; - Utils::SelectionAspect reportLevel; - Utils::IntegerAspect seed; - Utils::BoolAspect randomize; - Utils::BoolAspect systemErrors; - Utils::BoolAspect fpExceptions; - Utils::BoolAspect memLeaks; + Utils::SelectionAspect logLevel{this}; + Utils::SelectionAspect reportLevel{this}; + Utils::IntegerAspect seed{this}; + Utils::BoolAspect randomize{this}; + Utils::BoolAspect systemErrors{this}; + Utils::BoolAspect fpExceptions{this}; + Utils::BoolAspect memLeaks{this}; }; - -class BoostTestSettingsPage final : public Core::IOptionsPage -{ -public: - BoostTestSettingsPage(BoostTestSettings *settings, Utils::Id settingsId); -}; - -} // namespace Internal -} // namespace Autotest +} // Autotest::Internal Q_DECLARE_METATYPE(Autotest::Internal::LogLevel) Q_DECLARE_METATYPE(Autotest::Internal::ReportLevel) diff --git a/src/plugins/autotest/boost/boosttesttreeitem.cpp b/src/plugins/autotest/boost/boosttesttreeitem.cpp index 4b53d92754e..957ff73021a 100644 --- a/src/plugins/autotest/boost/boosttesttreeitem.cpp +++ b/src/plugins/autotest/boost/boosttesttreeitem.cpp @@ -11,7 +11,9 @@ #include "../itestframework.h" #include -#include + +#include + #include #include @@ -156,7 +158,7 @@ static QString handleSpecialFunctionNames(const QString &name) QList BoostTestTreeItem::getAllTestConfigurations() const { QList result; - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); if (!project || type() != Root) return result; @@ -200,7 +202,7 @@ QList BoostTestTreeItem::getTestConfigurations( std::function predicate) const { QList result; - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); if (!project || type() != Root) return result; @@ -261,7 +263,7 @@ QList BoostTestTreeItem::getFailedTestConfigurations() con ITestConfiguration *BoostTestTreeItem::testConfiguration() const { - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); QTC_ASSERT(project, return nullptr); const auto cppMM = CppEditor::CppModelManager::instance(); QTC_ASSERT(cppMM, return nullptr); diff --git a/src/plugins/autotest/catch/catchconfiguration.cpp b/src/plugins/autotest/catch/catchconfiguration.cpp index c08f7ac82b6..a5036398688 100644 --- a/src/plugins/autotest/catch/catchconfiguration.cpp +++ b/src/plugins/autotest/catch/catchconfiguration.cpp @@ -2,24 +2,21 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "catchconfiguration.h" + #include "catchoutputreader.h" #include "catchtestsettings.h" -#include "../autotestplugin.h" #include "../itestframework.h" #include "../testsettings.h" -#include - using namespace Utils; namespace Autotest { namespace Internal { -TestOutputReader *CatchConfiguration::createOutputReader(const QFutureInterface &fi, - QtcProcess *app) const +TestOutputReader *CatchConfiguration::createOutputReader(Process *app) const { - return new CatchOutputReader(fi, app, buildDirectory(), projectFile()); + return new CatchOutputReader(app, buildDirectory(), projectFile()); } static QStringList filterInterfering(const QStringList &provided, QStringList *omitted) @@ -82,7 +79,7 @@ QStringList CatchConfiguration::argumentsForTestRunner(QStringList *omitted) con arguments << "\"" + testCases().join("\", \"") + "\""; arguments << "--reporter" << "xml"; - if (AutotestPlugin::settings()->processArgs) { + if (TestSettings::instance()->processArgs()) { arguments << filterInterfering(runnable().command.arguments().split( ' ', Qt::SkipEmptyParts), omitted); } diff --git a/src/plugins/autotest/catch/catchconfiguration.h b/src/plugins/autotest/catch/catchconfiguration.h index bfa37f01644..63ac84dba7b 100644 --- a/src/plugins/autotest/catch/catchconfiguration.h +++ b/src/plugins/autotest/catch/catchconfiguration.h @@ -12,8 +12,7 @@ class CatchConfiguration : public DebuggableTestConfiguration { public: CatchConfiguration(ITestFramework *framework) : DebuggableTestConfiguration(framework) {} - TestOutputReader *createOutputReader(const QFutureInterface &fi, - Utils::QtcProcess *app) const override; + TestOutputReader *createOutputReader(Utils::Process *app) const override; QStringList argumentsForTestRunner(QStringList *omitted = nullptr) const override; Utils::Environment filteredEnvironment(const Utils::Environment &original) const override; }; diff --git a/src/plugins/autotest/catch/catchframework.h b/src/plugins/autotest/catch/catchframework.h index 0fe7138ce90..6bd20b44f6a 100644 --- a/src/plugins/autotest/catch/catchframework.h +++ b/src/plugins/autotest/catch/catchframework.h @@ -7,8 +7,7 @@ #include "catchtestsettings.h" -namespace Autotest { -namespace Internal { +namespace Autotest::Internal { class CatchFramework : public ITestFramework { @@ -25,9 +24,7 @@ protected: private: ITestSettings * testSettings() override { return &m_settings; } - CatchTestSettings m_settings; - CatchTestSettingsPage m_settingsPage{&m_settings, settingsId()}; + CatchTestSettings m_settings{settingsId()}; }; -} // namespace Internal -} // namespace Autotest +} // Autotest::Internal diff --git a/src/plugins/autotest/catch/catchoutputreader.cpp b/src/plugins/autotest/catch/catchoutputreader.cpp index 95e437fdc4b..7a3aa277d4b 100644 --- a/src/plugins/autotest/catch/catchoutputreader.cpp +++ b/src/plugins/autotest/catch/catchoutputreader.cpp @@ -2,13 +2,11 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "catchoutputreader.h" + #include "catchresult.h" #include "../autotesttr.h" -#include -#include - using namespace Utils; namespace Autotest { @@ -31,11 +29,10 @@ namespace CatchXml { const char TestCaseResultElement[] = "OverallResult"; } -CatchOutputReader::CatchOutputReader(const QFutureInterface &futureInterface, - QtcProcess *testApplication, +CatchOutputReader::CatchOutputReader(Process *testApplication, const FilePath &buildDirectory, const FilePath &projectFile) - : TestOutputReader (futureInterface, testApplication, buildDirectory) + : TestOutputReader(testApplication, buildDirectory) , m_projectFile(projectFile) { } diff --git a/src/plugins/autotest/catch/catchoutputreader.h b/src/plugins/autotest/catch/catchoutputreader.h index 51e8c1e3389..b46db333f80 100644 --- a/src/plugins/autotest/catch/catchoutputreader.h +++ b/src/plugins/autotest/catch/catchoutputreader.h @@ -14,8 +14,7 @@ namespace Internal { class CatchOutputReader : public TestOutputReader { public: - CatchOutputReader(const QFutureInterface &futureInterface, - Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory, + CatchOutputReader(Utils::Process *testApplication, const Utils::FilePath &buildDirectory, const Utils::FilePath &projectFile); protected: diff --git a/src/plugins/autotest/catch/catchtestparser.cpp b/src/plugins/autotest/catch/catchtestparser.cpp index dd01c01c90b..3cc7a4540be 100644 --- a/src/plugins/autotest/catch/catchtestparser.cpp +++ b/src/plugins/autotest/catch/catchtestparser.cpp @@ -11,6 +11,7 @@ #include #include +#include #include using namespace Utils; @@ -91,7 +92,7 @@ static bool hasCatchNames(const CPlusPlus::Document::Ptr &document) return false; } -bool CatchTestParser::processDocument(QFutureInterface &futureInterface, +bool CatchTestParser::processDocument(QPromise &promise, const FilePath &fileName) { CPlusPlus::Document::Ptr doc = document(fileName); @@ -144,7 +145,7 @@ bool CatchTestParser::processDocument(QFutureInterface &futu parseResult->children.append(testCase); } - futureInterface.reportResult(TestParseResultPtr(parseResult)); + promise.addResult(TestParseResultPtr(parseResult)); return !foundTests.isEmpty(); } diff --git a/src/plugins/autotest/catch/catchtestparser.h b/src/plugins/autotest/catch/catchtestparser.h index 6159ff58133..8b72073204b 100644 --- a/src/plugins/autotest/catch/catchtestparser.h +++ b/src/plugins/autotest/catch/catchtestparser.h @@ -23,7 +23,7 @@ class CatchTestParser : public CppParser public: CatchTestParser(ITestFramework *framework) : CppParser(framework) {} - bool processDocument(QFutureInterface &futureInterface, + bool processDocument(QPromise &promise, const Utils::FilePath &fileName) override; }; diff --git a/src/plugins/autotest/catch/catchtestsettings.cpp b/src/plugins/autotest/catch/catchtestsettings.cpp index 6fd0cde16d5..5ee4a6acf97 100644 --- a/src/plugins/autotest/catch/catchtestsettings.cpp +++ b/src/plugins/autotest/catch/catchtestsettings.cpp @@ -6,141 +6,105 @@ #include "../autotestconstants.h" #include "../autotesttr.h" -#include - #include +using namespace Layouting; using namespace Utils; -namespace Autotest { -namespace Internal { +namespace Autotest::Internal { -CatchTestSettings::CatchTestSettings() +CatchTestSettings::CatchTestSettings(Id settingsId) { + setId(settingsId); + setCategory(Constants::AUTOTEST_SETTINGS_CATEGORY); + setDisplayName(Tr::tr("Catch Test")); setSettingsGroups("Autotest", "Catch2"); - setAutoApply(false); - registerAspect(&abortAfter); + setLayouter([this] { + return Row { Form { + showSuccess, br, + breakOnFailure, br, + noThrow, br, + visibleWhitespace, br, + abortAfterChecked, abortAfter, br, + samplesChecked, benchmarkSamples, br, + resamplesChecked, benchmarkResamples, br, + confidenceIntervalChecked, confidenceInterval, br, + warmupChecked, benchmarkWarmupTime, br, + noAnalysis + }, st }; + }); + abortAfter.setSettingsKey("AbortAfter"); abortAfter.setRange(1, 9999); abortAfter.setEnabler(&abortAfterChecked); - registerAspect(&benchmarkSamples); benchmarkSamples.setSettingsKey("BenchSamples"); benchmarkSamples.setRange(1, 999999); benchmarkSamples.setDefaultValue(100); benchmarkSamples.setEnabler(&samplesChecked); - registerAspect(&benchmarkResamples); benchmarkResamples.setSettingsKey("BenchResamples"); benchmarkResamples.setRange(1, 9999999); benchmarkResamples.setDefaultValue(100000); benchmarkResamples.setToolTip(Tr::tr("Number of resamples for bootstrapping.")); benchmarkResamples.setEnabler(&resamplesChecked); - registerAspect(&confidenceInterval); confidenceInterval.setSettingsKey("BenchConfInt"); confidenceInterval.setRange(0., 1.); confidenceInterval.setSingleStep(0.05); confidenceInterval.setDefaultValue(0.95); confidenceInterval.setEnabler(&confidenceIntervalChecked); - registerAspect(&benchmarkWarmupTime); benchmarkWarmupTime.setSettingsKey("BenchWarmup"); benchmarkWarmupTime.setSuffix(Tr::tr(" ms")); benchmarkWarmupTime.setRange(0, 10000); benchmarkWarmupTime.setEnabler(&warmupChecked); - registerAspect(&abortAfterChecked); abortAfterChecked.setSettingsKey("AbortChecked"); abortAfterChecked.setLabelText(Tr::tr("Abort after")); abortAfterChecked.setToolTip(Tr::tr("Aborts after the specified number of failures.")); - registerAspect(&samplesChecked); samplesChecked.setSettingsKey("SamplesChecked"); samplesChecked.setLabelText(Tr::tr("Benchmark samples")); samplesChecked.setToolTip(Tr::tr("Number of samples to collect while running benchmarks.")); - registerAspect(&resamplesChecked); resamplesChecked.setSettingsKey("ResamplesChecked"); resamplesChecked.setLabelText(Tr::tr("Benchmark resamples")); resamplesChecked.setToolTip(Tr::tr("Number of resamples used for statistical bootstrapping.")); - registerAspect(&confidenceIntervalChecked); confidenceIntervalChecked.setSettingsKey("ConfIntChecked"); confidenceIntervalChecked.setToolTip(Tr::tr("Confidence interval used for statistical bootstrapping.")); confidenceIntervalChecked.setLabelText(Tr::tr("Benchmark confidence interval")); - registerAspect(&warmupChecked); warmupChecked.setSettingsKey("WarmupChecked"); warmupChecked.setLabelText(Tr::tr("Benchmark warmup time")); warmupChecked.setToolTip(Tr::tr("Warmup time for each test.")); - registerAspect(&noAnalysis); noAnalysis.setSettingsKey("NoAnalysis"); noAnalysis.setLabelText(Tr::tr("Disable analysis")); noAnalysis.setToolTip(Tr::tr("Disables statistical analysis and bootstrapping.")); - registerAspect(&showSuccess); showSuccess.setSettingsKey("ShowSuccess"); showSuccess.setLabelText(Tr::tr("Show success")); showSuccess.setToolTip(Tr::tr("Show success for tests.")); - registerAspect(&breakOnFailure); breakOnFailure.setSettingsKey("BreakOnFailure"); breakOnFailure.setDefaultValue(true); breakOnFailure.setLabelText(Tr::tr("Break on failure while debugging")); breakOnFailure.setToolTip(Tr::tr("Turns failures into debugger breakpoints.")); - registerAspect(&noThrow); noThrow.setSettingsKey("NoThrow"); noThrow.setLabelText(Tr::tr("Skip throwing assertions")); noThrow.setToolTip(Tr::tr("Skips all assertions that test for thrown exceptions.")); - registerAspect(&visibleWhitespace); visibleWhitespace.setSettingsKey("VisibleWS"); visibleWhitespace.setLabelText(Tr::tr("Visualize whitespace")); visibleWhitespace.setToolTip(Tr::tr("Makes whitespace visible.")); - registerAspect(&warnOnEmpty); warnOnEmpty.setSettingsKey("WarnEmpty"); warnOnEmpty.setLabelText(Tr::tr("Warn on empty tests")); warnOnEmpty.setToolTip(Tr::tr("Warns if a test section does not check any assertion.")); - - forEachAspect([](BaseAspect *aspect) { - // FIXME: Make the positioning part of the LayoutBuilder later - if (auto boolAspect = dynamic_cast(aspect)) - boolAspect->setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel); - }); } -CatchTestSettingsPage::CatchTestSettingsPage(CatchTestSettings *settings, Id settingsId) -{ - setId(settingsId); - setCategory(Constants::AUTOTEST_SETTINGS_CATEGORY); - setDisplayName(Tr::tr("Catch Test")); - setSettings(settings); - - setLayouter([settings](QWidget *widget) { - CatchTestSettings &s = *settings; - using namespace Layouting; - - Grid col { - s.showSuccess, br, - s.breakOnFailure, br, - s.noThrow, br, - s.visibleWhitespace, br, - s.abortAfterChecked, s.abortAfter, br, - s.samplesChecked, s.benchmarkSamples, br, - s.resamplesChecked, s.benchmarkResamples, br, - s.confidenceIntervalChecked, s.confidenceInterval, br, - s.warmupChecked, s.benchmarkWarmupTime, br, - s.noAnalysis - }; - - Column { Row { col, st }, st }.attachTo(widget); - }); -} - -} // namespace Internal -} // namespace Autotest +} // Autotest::Internal diff --git a/src/plugins/autotest/catch/catchtestsettings.h b/src/plugins/autotest/catch/catchtestsettings.h index 9e1b639fd31..71c1caf5839 100644 --- a/src/plugins/autotest/catch/catchtestsettings.h +++ b/src/plugins/autotest/catch/catchtestsettings.h @@ -5,39 +5,29 @@ #include -#include +namespace Autotest::Internal { -namespace Autotest { -namespace Internal { - -class CatchTestSettings : public Utils::AspectContainer +class CatchTestSettings : public Core::PagedSettings { public: - CatchTestSettings(); + explicit CatchTestSettings(Utils::Id settingsId); - Utils::IntegerAspect abortAfter; - Utils::IntegerAspect benchmarkSamples; - Utils::IntegerAspect benchmarkResamples; - Utils::DoubleAspect confidenceInterval; - Utils::IntegerAspect benchmarkWarmupTime; - Utils::BoolAspect abortAfterChecked; - Utils::BoolAspect samplesChecked; - Utils::BoolAspect resamplesChecked; - Utils::BoolAspect confidenceIntervalChecked; - Utils::BoolAspect warmupChecked; - Utils::BoolAspect noAnalysis; - Utils::BoolAspect showSuccess; - Utils::BoolAspect breakOnFailure; - Utils::BoolAspect noThrow; - Utils::BoolAspect visibleWhitespace; - Utils::BoolAspect warnOnEmpty; + Utils::IntegerAspect abortAfter{this}; + Utils::IntegerAspect benchmarkSamples{this}; + Utils::IntegerAspect benchmarkResamples{this}; + Utils::DoubleAspect confidenceInterval{this}; + Utils::IntegerAspect benchmarkWarmupTime{this}; + Utils::BoolAspect abortAfterChecked{this}; + Utils::BoolAspect samplesChecked{this}; + Utils::BoolAspect resamplesChecked{this}; + Utils::BoolAspect confidenceIntervalChecked{this}; + Utils::BoolAspect warmupChecked{this}; + Utils::BoolAspect noAnalysis{this}; + Utils::BoolAspect showSuccess{this}; + Utils::BoolAspect breakOnFailure{this}; + Utils::BoolAspect noThrow{this}; + Utils::BoolAspect visibleWhitespace{this}; + Utils::BoolAspect warnOnEmpty{this}; }; -class CatchTestSettingsPage : public Core::IOptionsPage -{ -public: - CatchTestSettingsPage(CatchTestSettings *settings, Utils::Id settingsId); -}; - -} // namespace Internal -} // namespace Autotest +} // Autotest::Internal diff --git a/src/plugins/autotest/catch/catchtreeitem.cpp b/src/plugins/autotest/catch/catchtreeitem.cpp index fe69fece87e..034c7ab1d04 100644 --- a/src/plugins/autotest/catch/catchtreeitem.cpp +++ b/src/plugins/autotest/catch/catchtreeitem.cpp @@ -10,8 +10,10 @@ #include "../itestframework.h" #include + #include -#include +#include + #include using namespace Utils; @@ -30,7 +32,7 @@ static QString nonRootDisplayName(const CatchTreeItem *it) { if (it->type() != TestTreeItem::TestSuite) return it->name(); - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); if (!project) return it->name(); TestTreeItem *parent = it->parentItem(); @@ -141,7 +143,7 @@ bool CatchTreeItem::canProvideDebugConfiguration() const ITestConfiguration *CatchTreeItem::testConfiguration() const { - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); QTC_ASSERT(project, return nullptr); const auto cppMM = CppEditor::CppModelManager::instance(); QTC_ASSERT(cppMM, return nullptr); @@ -244,7 +246,7 @@ QList CatchTreeItem::getSelectedTestConfigurations() const QList CatchTreeItem::getFailedTestConfigurations() const { QList result; - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); if (!project || type() != Root) return result; @@ -271,7 +273,7 @@ QList CatchTreeItem::getTestConfigurationsForFile(const Fi const auto cppMM = CppEditor::CppModelManager::instance(); QTC_ASSERT(cppMM, return result); - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); if (!project || type() != Root) return result; @@ -293,7 +295,7 @@ QList CatchTreeItem::getTestConfigurationsForFile(const Fi testConfig = new CatchConfiguration(framework()); testConfig->setTestCases(testCases); testConfig->setProjectFile(item->proFile()); - testConfig->setProject(ProjectExplorer::SessionManager::startupProject()); + testConfig->setProject(ProjectExplorer::ProjectManager::startupProject()); testConfig->setInternalTargets(cppMM->internalTargets(item->filePath())); result << testConfig; } @@ -314,7 +316,7 @@ QString CatchTreeItem::stateSuffix() const QList CatchTreeItem::getTestConfigurations(bool ignoreCheckState) const { QList result; - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); if (!project || type() != Root) return result; diff --git a/src/plugins/autotest/ctest/ctestconfiguration.cpp b/src/plugins/autotest/ctest/ctestconfiguration.cpp index 9208888033e..a2e2edfe0a7 100644 --- a/src/plugins/autotest/ctest/ctestconfiguration.cpp +++ b/src/plugins/autotest/ctest/ctestconfiguration.cpp @@ -13,10 +13,9 @@ CTestConfiguration::CTestConfiguration(ITestBase *testBase) setDisplayName("CTest"); } -TestOutputReader *CTestConfiguration::createOutputReader(const QFutureInterface &fi, - Utils::QtcProcess *app) const +TestOutputReader *CTestConfiguration::createOutputReader(Utils::Process *app) const { - return new CTestOutputReader(fi, app, workingDirectory()); + return new CTestOutputReader(app, workingDirectory()); } } // namespace Internal diff --git a/src/plugins/autotest/ctest/ctestconfiguration.h b/src/plugins/autotest/ctest/ctestconfiguration.h index db0efb17173..a298fec86d7 100644 --- a/src/plugins/autotest/ctest/ctestconfiguration.h +++ b/src/plugins/autotest/ctest/ctestconfiguration.h @@ -13,8 +13,7 @@ class CTestConfiguration final : public Autotest::TestToolConfiguration public: explicit CTestConfiguration(ITestBase *testBase); - TestOutputReader *createOutputReader(const QFutureInterface &fi, - Utils::QtcProcess *app) const final; + TestOutputReader *createOutputReader(Utils::Process *app) const final; }; } // namespace Internal diff --git a/src/plugins/autotest/ctest/ctestoutputreader.cpp b/src/plugins/autotest/ctest/ctestoutputreader.cpp index 70ec5f0659b..10f48d9d46c 100644 --- a/src/plugins/autotest/ctest/ctestoutputreader.cpp +++ b/src/plugins/autotest/ctest/ctestoutputreader.cpp @@ -5,13 +5,11 @@ #include "../autotesttr.h" #include "../testframeworkmanager.h" -#include "../testresult.h" #include "../testtreeitem.h" #include #include -#include #include @@ -52,10 +50,9 @@ public: {} }; -CTestOutputReader::CTestOutputReader(const QFutureInterface &futureInterface, - QtcProcess *testApplication, +CTestOutputReader::CTestOutputReader(Process *testApplication, const FilePath &buildDirectory) - : TestOutputReader(futureInterface, testApplication, buildDirectory) + : TestOutputReader(testApplication, buildDirectory) { } diff --git a/src/plugins/autotest/ctest/ctestoutputreader.h b/src/plugins/autotest/ctest/ctestoutputreader.h index 8a6e1f124e4..85785d44fca 100644 --- a/src/plugins/autotest/ctest/ctestoutputreader.h +++ b/src/plugins/autotest/ctest/ctestoutputreader.h @@ -5,7 +5,7 @@ #include "../testoutputreader.h" -namespace Utils { class QtcProcess; } +namespace Utils { class Process; } namespace Autotest { namespace Internal { @@ -13,8 +13,7 @@ namespace Internal { class CTestOutputReader final : public Autotest::TestOutputReader { public: - CTestOutputReader(const QFutureInterface &futureInterface, - Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory); + CTestOutputReader(Utils::Process *testApplication, const Utils::FilePath &buildDirectory); protected: void processOutputLine(const QByteArray &outputLineWithNewLine) final; diff --git a/src/plugins/autotest/ctest/ctestsettings.cpp b/src/plugins/autotest/ctest/ctestsettings.cpp index 5de02b84801..a2f3e904270 100644 --- a/src/plugins/autotest/ctest/ctestsettings.cpp +++ b/src/plugins/autotest/ctest/ctestsettings.cpp @@ -8,70 +8,85 @@ #include -namespace Autotest { -namespace Internal { +using namespace Layouting; +using namespace Utils; -CTestSettings::CTestSettings() +namespace Autotest::Internal { + +CTestSettings::CTestSettings(Id settingsId) { setSettingsGroups("Autotest", "CTest"); - setAutoApply(false); + setId(settingsId); + setCategory(Constants::AUTOTEST_SETTINGS_CATEGORY); + setDisplayName(Tr::tr("CTest")); + + setLayouter([this] { + return Row { Form { + outputOnFail, br, + scheduleRandom, br, + stopOnFailure, br, + outputMode, br, + Group { + title(Tr::tr("Repeat tests")), + repeat.groupChecker(), + Row { repetitionMode, repetitionCount}, + }, br, + Group { + title(Tr::tr("Run in parallel")), + parallel.groupChecker(), + Column { + Row { jobs }, br, + Row { testLoad, threshold} + } + } + }, st }; + }); - registerAspect(&outputOnFail); outputOnFail.setSettingsKey("OutputOnFail"); outputOnFail.setLabelText(Tr::tr("Output on failure")); outputOnFail.setDefaultValue(true); - registerAspect(&outputMode); outputMode.setSettingsKey("OutputMode"); outputMode.setLabelText(Tr::tr("Output mode")); - outputMode.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox); + outputMode.setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox); outputMode.addOption({Tr::tr("Default"), {}, 0}); outputMode.addOption({Tr::tr("Verbose"), {}, 1}); outputMode.addOption({Tr::tr("Very Verbose"), {}, 2}); - registerAspect(&repetitionMode); repetitionMode.setSettingsKey("RepetitionMode"); repetitionMode.setLabelText(Tr::tr("Repetition mode")); - repetitionMode.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox); + repetitionMode.setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox); repetitionMode.addOption({Tr::tr("Until Fail"), {}, 0}); repetitionMode.addOption({Tr::tr("Until Pass"), {}, 1}); repetitionMode.addOption({Tr::tr("After Timeout"), {}, 2}); - registerAspect(&repetitionCount); repetitionCount.setSettingsKey("RepetitionCount"); repetitionCount.setDefaultValue(1); repetitionCount.setLabelText(Tr::tr("Count")); repetitionCount.setToolTip(Tr::tr("Number of re-runs for the test.")); repetitionCount.setRange(1, 10000); - registerAspect(&repeat); repeat.setSettingsKey("Repeat"); - registerAspect(&scheduleRandom); scheduleRandom.setSettingsKey("ScheduleRandom"); scheduleRandom.setLabelText(Tr::tr("Schedule random")); - registerAspect(&stopOnFailure); stopOnFailure.setSettingsKey("StopOnFail"); stopOnFailure.setLabelText(Tr::tr("Stop on failure")); - registerAspect(¶llel); parallel.setSettingsKey("Parallel"); parallel.setToolTip(Tr::tr("Run tests in parallel mode using given number of jobs.")); - registerAspect(&jobs); jobs.setSettingsKey("Jobs"); jobs.setLabelText(Tr::tr("Jobs")); jobs.setDefaultValue(1); jobs.setRange(1, 128); - registerAspect(&testLoad); testLoad.setSettingsKey("TestLoad"); testLoad.setLabelText(Tr::tr("Test load")); testLoad.setToolTip(Tr::tr("Try not to start tests when they may cause CPU load to pass a " - "threshold.")); + "threshold.")); - registerAspect(&threshold); threshold.setSettingsKey("Threshold"); threshold.setLabelText(Tr::tr("Threshold")); threshold.setDefaultValue(1); @@ -115,39 +130,4 @@ QStringList CTestSettings::activeSettingsAsOptions() const return options; } -CTestSettingsPage::CTestSettingsPage(CTestSettings *settings, Utils::Id settingsId) -{ - setId(settingsId); - setCategory(Constants::AUTOTEST_SETTINGS_CATEGORY); - setDisplayName(Tr::tr("CTest")); - - setSettings(settings); - - setLayouter([settings](QWidget *widget) { - CTestSettings &s = *settings; - using namespace Utils::Layouting; - - Form form { - Row {s.outputOnFail}, br, - Row {s.scheduleRandom}, br, - Row {s.stopOnFailure}, br, - Row {s.outputMode}, br, - Group { - title(Tr::tr("Repeat tests"), &s.repeat), - Row {s.repetitionMode, s.repetitionCount}, - }, br, - Group { - title(Tr::tr("Run in parallel"), &s.parallel), - Column { - Row {s.jobs}, br, - Row {s.testLoad, s.threshold} - } - } - }; - - Column { Row { Column { form , st }, st } }.attachTo(widget); - }); -} - -} // namespace Internal -} // namespace Autotest +} // Autotest::Internal diff --git a/src/plugins/autotest/ctest/ctestsettings.h b/src/plugins/autotest/ctest/ctestsettings.h index f9e87f5a8e8..77de6b0aac8 100644 --- a/src/plugins/autotest/ctest/ctestsettings.h +++ b/src/plugins/autotest/ctest/ctestsettings.h @@ -5,38 +5,28 @@ #include -#include +namespace Autotest::Internal { -namespace Autotest { -namespace Internal { - -class CTestSettings : public Utils::AspectContainer +class CTestSettings : public Core::PagedSettings { public: - CTestSettings(); + explicit CTestSettings(Utils::Id settingsId); QStringList activeSettingsAsOptions() const; - Utils::IntegerAspect repetitionCount; - Utils::SelectionAspect repetitionMode; - Utils::SelectionAspect outputMode; - Utils::BoolAspect outputOnFail; - Utils::BoolAspect stopOnFailure; - Utils::BoolAspect scheduleRandom; - Utils::BoolAspect repeat; + Utils::IntegerAspect repetitionCount{this}; + Utils::SelectionAspect repetitionMode{this}; + Utils::SelectionAspect outputMode{this}; + Utils::BoolAspect outputOnFail{this}; + Utils::BoolAspect stopOnFailure{this}; + Utils::BoolAspect scheduleRandom{this}; + Utils::BoolAspect repeat{this}; // FIXME.. this makes the outputreader fail to get all results correctly for visual display - Utils::BoolAspect parallel; - Utils::IntegerAspect jobs; - Utils::BoolAspect testLoad; - Utils::IntegerAspect threshold; + Utils::BoolAspect parallel{this}; + Utils::IntegerAspect jobs{this}; + Utils::BoolAspect testLoad{this}; + Utils::IntegerAspect threshold{this}; }; -class CTestSettingsPage final : public Core::IOptionsPage -{ -public: - CTestSettingsPage(CTestSettings *settings, Utils::Id settingsId); -}; - -} // namespace Internal -} // namespace Autotest +} // Autotest::Internal diff --git a/src/plugins/autotest/ctest/ctesttool.h b/src/plugins/autotest/ctest/ctesttool.h index ad510ac9471..e7a7f74218a 100644 --- a/src/plugins/autotest/ctest/ctesttool.h +++ b/src/plugins/autotest/ctest/ctesttool.h @@ -6,8 +6,7 @@ #include "../itestframework.h" #include "ctestsettings.h" -namespace Autotest { -namespace Internal { +namespace Autotest::Internal { class CTestTool final : public Autotest::ITestTool { @@ -26,9 +25,7 @@ protected: private: ITestSettings *testSettings() override { return &m_settings; } - CTestSettings m_settings; - CTestSettingsPage m_settingsPage{&m_settings, settingsId()}; + CTestSettings m_settings{settingsId()}; }; -} // namespace Internal -} // namespace Autotest +} // Autotest::Internal diff --git a/src/plugins/autotest/ctest/ctesttreeitem.cpp b/src/plugins/autotest/ctest/ctesttreeitem.cpp index 3e59c2db81c..9ffc06b028e 100644 --- a/src/plugins/autotest/ctest/ctesttreeitem.cpp +++ b/src/plugins/autotest/ctest/ctesttreeitem.cpp @@ -14,11 +14,11 @@ #include #include #include -#include +#include #include #include -#include +#include using namespace Utils; @@ -79,7 +79,7 @@ QVariant CTestTreeItem::data(int column, int role) const QList CTestTreeItem::testConfigurationsFor(const QStringList &selected) const { - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); if (!project) return {}; @@ -88,7 +88,7 @@ QList CTestTreeItem::testConfigurationsFor(const QStringLi return {}; const ProjectExplorer::BuildSystem *buildSystem = target->buildSystem(); - QStringList options{"--timeout", QString::number(AutotestPlugin::settings()->timeout / 1000)}; + QStringList options{"--timeout", QString::number(TestSettings::instance()->timeout() / 1000)}; auto ctestSettings = static_cast(testBase()->testSettings()); options << ctestSettings->activeSettingsAsOptions(); CommandLine command = buildSystem->commandLineForTests(selected, options); diff --git a/src/plugins/autotest/gtest/gtestconfiguration.cpp b/src/plugins/autotest/gtest/gtestconfiguration.cpp index d2f869d7645..8680da1fcaf 100644 --- a/src/plugins/autotest/gtest/gtestconfiguration.cpp +++ b/src/plugins/autotest/gtest/gtestconfiguration.cpp @@ -5,22 +5,20 @@ #include "gtestoutputreader.h" #include "gtestsettings.h" -#include "../autotestplugin.h" + #include "../itestframework.h" #include "../testsettings.h" #include -#include using namespace Utils; namespace Autotest { namespace Internal { -TestOutputReader *GTestConfiguration::createOutputReader(const QFutureInterface &fi, - QtcProcess *app) const +TestOutputReader *GTestConfiguration::createOutputReader(Process *app) const { - return new GTestOutputReader(fi, app, buildDirectory(), projectFile()); + return new GTestOutputReader(app, buildDirectory(), projectFile()); } QStringList filterInterfering(const QStringList &provided, QStringList *omitted) @@ -56,7 +54,7 @@ QStringList filterInterfering(const QStringList &provided, QStringList *omitted) QStringList GTestConfiguration::argumentsForTestRunner(QStringList *omitted) const { QStringList arguments; - if (AutotestPlugin::settings()->processArgs) { + if (TestSettings::instance()->processArgs()) { arguments << filterInterfering(runnable().command.arguments().split( ' ', Qt::SkipEmptyParts), omitted); } diff --git a/src/plugins/autotest/gtest/gtestconfiguration.h b/src/plugins/autotest/gtest/gtestconfiguration.h index 97bcbd654fc..0cbfc8b0d82 100644 --- a/src/plugins/autotest/gtest/gtestconfiguration.h +++ b/src/plugins/autotest/gtest/gtestconfiguration.h @@ -14,8 +14,7 @@ public: explicit GTestConfiguration(ITestFramework *framework) : DebuggableTestConfiguration(framework) {} - TestOutputReader *createOutputReader(const QFutureInterface &fi, - Utils::QtcProcess *app) const override; + TestOutputReader *createOutputReader(Utils::Process *app) const override; QStringList argumentsForTestRunner(QStringList *omitted = nullptr) const override; Utils::Environment filteredEnvironment(const Utils::Environment &original) const override; }; diff --git a/src/plugins/autotest/gtest/gtestframework.h b/src/plugins/autotest/gtest/gtestframework.h index c9e8cbddc07..03c8a4fc0d9 100644 --- a/src/plugins/autotest/gtest/gtestframework.h +++ b/src/plugins/autotest/gtest/gtestframework.h @@ -7,8 +7,7 @@ #include "gtestconstants.h" #include "gtestsettings.h" -namespace Autotest { -namespace Internal { +namespace Autotest::Internal { class GTestFramework : public ITestFramework { @@ -28,9 +27,7 @@ private: ITestParser *createTestParser() override; ITestTreeItem *createRootNode() override; - GTestSettings m_settings; - GTestSettingsPage m_settingsPage{&m_settings, settingsId()}; + GTestSettings m_settings{settingsId()}; }; -} // namespace Internal -} // namespace Autotest +} // Autotest::Internal diff --git a/src/plugins/autotest/gtest/gtestoutputreader.cpp b/src/plugins/autotest/gtest/gtestoutputreader.cpp index a24377bef7a..1e980c06d00 100644 --- a/src/plugins/autotest/gtest/gtestoutputreader.cpp +++ b/src/plugins/autotest/gtest/gtestoutputreader.cpp @@ -8,7 +8,7 @@ #include "../autotesttr.h" #include -#include +#include #include @@ -17,15 +17,14 @@ using namespace Utils; namespace Autotest { namespace Internal { -GTestOutputReader::GTestOutputReader(const QFutureInterface &futureInterface, - QtcProcess *testApplication, +GTestOutputReader::GTestOutputReader(Process *testApplication, const FilePath &buildDirectory, const FilePath &projectFile) - : TestOutputReader(futureInterface, testApplication, buildDirectory) + : TestOutputReader(testApplication, buildDirectory) , m_projectFile(projectFile) { if (testApplication) { - connect(testApplication, &QtcProcess::done, this, [this, testApplication] { + connect(testApplication, &Process::done, this, [this, testApplication] { const int exitCode = testApplication->exitCode(); if (exitCode == 1 && !m_description.isEmpty()) { createAndReportResult(Tr::tr("Running tests failed.\n %1\nExecutable: %2") @@ -116,7 +115,7 @@ void GTestOutputReader::processOutputLine(const QByteArray &outputLine) testResult.setResult(ResultType::MessageInternal); testResult.setDescription(Tr::tr("Execution took %1.").arg(match.captured(2))); reportResult(testResult); - m_futureInterface.setProgressValue(m_futureInterface.progressValue() + 1); + // TODO: bump progress? } else if (ExactMatch match = testSetFail.match(line)) { m_testSetStarted = false; TestResult testResult = createDefaultResult(); @@ -127,7 +126,7 @@ void GTestOutputReader::processOutputLine(const QByteArray &outputLine) testResult.setResult(ResultType::MessageInternal); testResult.setDescription(Tr::tr("Execution took %1.").arg(match.captured(2))); reportResult(testResult); - m_futureInterface.setProgressValue(m_futureInterface.progressValue() + 1); + // TODO: bump progress? } else if (ExactMatch match = testSetSkipped.match(line)) { if (!m_testSetStarted) // ignore SKIPPED at summary return; @@ -210,7 +209,7 @@ void GTestOutputReader::handleDescriptionAndReportResult(const TestResult &testR } } result.setDescription(resultDescription.join('\n')); - reportResult(testResult); + reportResult(result); resultDescription.clear(); result = createDefaultResult(); diff --git a/src/plugins/autotest/gtest/gtestoutputreader.h b/src/plugins/autotest/gtest/gtestoutputreader.h index e73ab0e8236..a63c5668295 100644 --- a/src/plugins/autotest/gtest/gtestoutputreader.h +++ b/src/plugins/autotest/gtest/gtestoutputreader.h @@ -11,8 +11,7 @@ namespace Internal { class GTestOutputReader : public TestOutputReader { public: - GTestOutputReader(const QFutureInterface &futureInterface, - Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory, + GTestOutputReader(Utils::Process *testApplication, const Utils::FilePath &buildDirectory, const Utils::FilePath &projectFile); protected: void processOutputLine(const QByteArray &outputLine) override; diff --git a/src/plugins/autotest/gtest/gtestparser.cpp b/src/plugins/autotest/gtest/gtestparser.cpp index aa59bb94c2e..c8cc92005e8 100644 --- a/src/plugins/autotest/gtest/gtestparser.cpp +++ b/src/plugins/autotest/gtest/gtestparser.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -70,7 +71,7 @@ static bool hasGTestNames(const CPlusPlus::Document::Ptr &document) return false; } -bool GTestParser::processDocument(QFutureInterface &futureInterface, +bool GTestParser::processDocument(QPromise &promise, const FilePath &fileName) { CPlusPlus::Document::Ptr doc = document(fileName); @@ -124,7 +125,7 @@ bool GTestParser::processDocument(QFutureInterface &futureIn parseResult->children.append(testSet); } - futureInterface.reportResult(TestParseResultPtr(parseResult)); + promise.addResult(TestParseResultPtr(parseResult)); } return !result.isEmpty(); } diff --git a/src/plugins/autotest/gtest/gtestparser.h b/src/plugins/autotest/gtest/gtestparser.h index 665a8a496c2..52febe6b890 100644 --- a/src/plugins/autotest/gtest/gtestparser.h +++ b/src/plugins/autotest/gtest/gtestparser.h @@ -22,7 +22,7 @@ class GTestParser : public CppParser { public: explicit GTestParser(ITestFramework *framework) : CppParser(framework) {} - bool processDocument(QFutureInterface &futureInterface, + bool processDocument(QPromise &futureInterface, const Utils::FilePath &fileName) override; }; diff --git a/src/plugins/autotest/gtest/gtestsettings.cpp b/src/plugins/autotest/gtest/gtestsettings.cpp index babea83f78a..ca31c99f121 100644 --- a/src/plugins/autotest/gtest/gtestsettings.cpp +++ b/src/plugins/autotest/gtest/gtestsettings.cpp @@ -11,24 +11,35 @@ #include +using namespace Layouting; using namespace Utils; -namespace Autotest { -namespace Internal { +namespace Autotest::Internal { -GTestSettings::GTestSettings() +GTestSettings::GTestSettings(Id settingsId) { setSettingsGroups("Autotest", "GTest"); - setAutoApply(false); + setId(settingsId); + setCategory(Constants::AUTOTEST_SETTINGS_CATEGORY); + setDisplayName(Tr::tr(GTest::Constants::FRAMEWORK_SETTINGS_CATEGORY)); + + setLayouter([this] { + return Row { Form { + runDisabled, br, + breakOnFailure, br, + repeat, iterations, br, + shuffle, seed, br, + groupMode, br, + gtestFilter, br + }, st }; + }); - registerAspect(&iterations); iterations.setSettingsKey("Iterations"); iterations.setDefaultValue(1); iterations.setEnabled(false); iterations.setLabelText(Tr::tr("Iterations:")); iterations.setEnabler(&repeat); - registerAspect(&seed); seed.setSettingsKey("Seed"); seed.setSpecialValueText({}); seed.setEnabled(false); @@ -36,33 +47,28 @@ GTestSettings::GTestSettings() seed.setToolTip(Tr::tr("A seed of 0 generates a seed based on the current timestamp.")); seed.setEnabler(&shuffle); - registerAspect(&runDisabled); runDisabled.setSettingsKey("RunDisabled"); runDisabled.setLabelText(Tr::tr("Run disabled tests")); runDisabled.setToolTip(Tr::tr("Executes disabled tests when performing a test run.")); - registerAspect(&shuffle); shuffle.setSettingsKey("Shuffle"); shuffle.setLabelText(Tr::tr("Shuffle tests")); shuffle.setToolTip(Tr::tr("Shuffles tests automatically on every iteration by the given seed.")); - registerAspect(&repeat); repeat.setSettingsKey("Repeat"); repeat.setLabelText(Tr::tr("Repeat tests")); - repeat.setToolTip(Tr::tr("Repeats a test run (you might be required to increase the timeout to avoid canceling the tests).")); + repeat.setToolTip(Tr::tr("Repeats a test run (you might be required to increase the timeout to " + "avoid canceling the tests).")); - registerAspect(&throwOnFailure); throwOnFailure.setSettingsKey("ThrowOnFailure"); throwOnFailure.setLabelText(Tr::tr("Throw on failure")); throwOnFailure.setToolTip(Tr::tr("Turns assertion failures into C++ exceptions.")); - registerAspect(&breakOnFailure); breakOnFailure.setSettingsKey("BreakOnFailure"); breakOnFailure.setDefaultValue(true); breakOnFailure.setLabelText(Tr::tr("Break on failure while debugging")); breakOnFailure.setToolTip(Tr::tr("Turns failures into debugger breakpoints.")); - registerAspect(&groupMode); groupMode.setSettingsKey("GroupMode"); groupMode.setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox); groupMode.setFromSettingsTransformation([this](const QVariant &savedValue) -> QVariant { @@ -80,7 +86,6 @@ GTestSettings::GTestSettings() groupMode.setLabelText(Tr::tr("Group mode:")); groupMode.setToolTip(Tr::tr("Select on what grouping the tests should be based.")); - registerAspect(>estFilter); gtestFilter.setSettingsKey("GTestFilter"); gtestFilter.setDisplayStyle(StringAspect::LineEditDisplay); gtestFilter.setDefaultValue(GTest::Constants::DEFAULT_FILTER); @@ -93,8 +98,8 @@ GTestSettings::GTestSettings() }); gtestFilter.setEnabled(false); gtestFilter.setLabelText(Tr::tr("Active filter:")); - gtestFilter.setToolTip(Tr::tr("Set the GTest filter to be used for grouping.\n" - "See Google Test documentation for further information on GTest filters.")); + gtestFilter.setToolTip(Tr::tr("Set the GTest filter to be used for grouping.\nSee Google Test " + "documentation for further information on GTest filters.")); gtestFilter.setValidationFunction([](FancyLineEdit *edit, QString * /*error*/) { return edit && GTestUtils::isValidGTestFilter(edit->text()); @@ -104,39 +109,11 @@ GTestSettings::GTestSettings() >estFilter, [this](int val) { gtestFilter.setEnabled(groupMode.itemValueForIndex(val) == GTest::Constants::GTestFilter); }); -} -GTestSettingsPage::GTestSettingsPage(GTestSettings *settings, Id settingsId) -{ - setId(settingsId); - setCategory(Constants::AUTOTEST_SETTINGS_CATEGORY); - setDisplayName(Tr::tr(GTest::Constants::FRAMEWORK_SETTINGS_CATEGORY)); - setSettings(settings); - - QObject::connect(settings, &AspectContainer::applied, this, [] { + QObject::connect(this, &AspectContainer::applied, this, [] { Id id = Id(Constants::FRAMEWORK_PREFIX).withSuffix(GTest::Constants::FRAMEWORK_NAME); TestTreeModel::instance()->rebuild({id}); }); - - setLayouter([settings](QWidget *widget) { - GTestSettings &s = *settings; - using namespace Layouting; - - Grid grid { - s.runDisabled, br, - s.breakOnFailure, br, - s.repeat, s.iterations, br, - s.shuffle, s.seed - }; - - Form form { - s.groupMode, - s.gtestFilter - }; - - Column { Row { Column { grid, form, st }, st } }.attachTo(widget); - }); } -} // namespace Internal -} // namespace Autotest +} // Autotest::Internal diff --git a/src/plugins/autotest/gtest/gtestsettings.h b/src/plugins/autotest/gtest/gtestsettings.h index 11fb144e227..ea0ab565e6c 100644 --- a/src/plugins/autotest/gtest/gtestsettings.h +++ b/src/plugins/autotest/gtest/gtestsettings.h @@ -5,32 +5,22 @@ #include -#include +namespace Autotest::Internal { -namespace Autotest { -namespace Internal { - -class GTestSettings : public Utils::AspectContainer +class GTestSettings : public Core::PagedSettings { public: - GTestSettings(); + explicit GTestSettings(Utils::Id settingsId); - Utils::IntegerAspect iterations; - Utils::IntegerAspect seed; - Utils::BoolAspect runDisabled; - Utils::BoolAspect shuffle; - Utils::BoolAspect repeat; - Utils::BoolAspect throwOnFailure; - Utils::BoolAspect breakOnFailure; - Utils::SelectionAspect groupMode; - Utils::StringAspect gtestFilter; + Utils::IntegerAspect iterations{this}; + Utils::IntegerAspect seed{this}; + Utils::BoolAspect runDisabled{this}; + Utils::BoolAspect shuffle{this}; + Utils::BoolAspect repeat{this}; + Utils::BoolAspect throwOnFailure{this}; + Utils::BoolAspect breakOnFailure{this}; + Utils::SelectionAspect groupMode{this}; + Utils::StringAspect gtestFilter{this}; }; -class GTestSettingsPage final : public Core::IOptionsPage -{ -public: - GTestSettingsPage(GTestSettings *settings, Utils::Id settingsId); -}; - -} // namespace Internal -} // namespace Autotest +} // Autotest::Internal diff --git a/src/plugins/autotest/gtest/gtesttreeitem.cpp b/src/plugins/autotest/gtest/gtesttreeitem.cpp index c79ae93d1b9..f91b68f0e38 100644 --- a/src/plugins/autotest/gtest/gtesttreeitem.cpp +++ b/src/plugins/autotest/gtest/gtesttreeitem.cpp @@ -10,7 +10,9 @@ #include "../autotesttr.h" #include -#include + +#include +#include #include #include @@ -146,7 +148,7 @@ QVariant GTestTreeItem::data(int column, int role) const ITestConfiguration *GTestTreeItem::testConfiguration() const { - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); QTC_ASSERT(project, return nullptr); GTestConfiguration *config = nullptr; @@ -252,7 +254,7 @@ static void collectFailedTestInfo(const GTestTreeItem *item, QList GTestTreeItem::getTestConfigurations(bool ignoreCheckState) const { QList result; - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); if (!project || type() != Root) return result; @@ -291,7 +293,7 @@ QList GTestTreeItem::getSelectedTestConfigurations() const QList GTestTreeItem::getFailedTestConfigurations() const { QList result; - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); if (!project || type() != Root) return result; @@ -316,7 +318,7 @@ QList GTestTreeItem::getFailedTestConfigurations() const QList GTestTreeItem::getTestConfigurationsForFile(const FilePath &fileName) const { QList result; - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); if (!project || type() != Root) return result; @@ -503,7 +505,7 @@ QSet internalTargets(const TestTreeItem &item) { QSet result; const auto cppMM = CppEditor::CppModelManager::instance(); - const auto projectInfo = cppMM->projectInfo(ProjectExplorer::SessionManager::startupProject()); + const auto projectInfo = cppMM->projectInfo(ProjectExplorer::ProjectManager::startupProject()); if (!projectInfo) return {}; const FilePath filePath = item.filePath(); diff --git a/src/plugins/autotest/itestparser.cpp b/src/plugins/autotest/itestparser.cpp index d8f1d6953b3..79fde2a0bb1 100644 --- a/src/plugins/autotest/itestparser.cpp +++ b/src/plugins/autotest/itestparser.cpp @@ -24,7 +24,7 @@ CppParser::CppParser(ITestFramework *framework) { } -void CppParser::init(const FilePaths &filesToParse, bool fullParse) +void CppParser::init(const QSet &filesToParse, bool fullParse) { Q_UNUSED(filesToParse) Q_UNUSED(fullParse) @@ -43,8 +43,8 @@ bool CppParser::selectedForBuilding(const FilePath &fileName) QByteArray CppParser::getFileContent(const FilePath &filePath) const { QByteArray fileContent; - if (m_workingCopy.contains(filePath)) { - fileContent = m_workingCopy.source(filePath); + if (const auto source = m_workingCopy.source(filePath)) { + fileContent = *source; } else { QString error; const QTextCodec *codec = Core::EditorManager::defaultTextCodec(); diff --git a/src/plugins/autotest/itestparser.h b/src/plugins/autotest/itestparser.h index 7e16a61b90d..93233bd7a97 100644 --- a/src/plugins/autotest/itestparser.h +++ b/src/plugins/autotest/itestparser.h @@ -9,9 +9,9 @@ #include #include -#include - QT_BEGIN_NAMESPACE +template +class QPromise; class QRegularExpression; QT_END_NAMESPACE @@ -45,8 +45,8 @@ class ITestParser public: explicit ITestParser(ITestFramework *framework) : m_framework(framework) {} virtual ~ITestParser() { } - virtual void init(const Utils::FilePaths &filesToParse, bool fullParse) = 0; - virtual bool processDocument(QFutureInterface &futureInterface, + virtual void init(const QSet &filesToParse, bool fullParse) = 0; + virtual bool processDocument(QPromise &futureInterface, const Utils::FilePath &fileName) = 0; virtual QStringList supportedExtensions() const { return {}; } @@ -63,7 +63,7 @@ class CppParser : public ITestParser { public: explicit CppParser(ITestFramework *framework); - void init(const Utils::FilePaths &filesToParse, bool fullParse) override; + void init(const QSet &filesToParse, bool fullParse) override; static bool selectedForBuilding(const Utils::FilePath &fileName); QByteArray getFileContent(const Utils::FilePath &filePath) const; void release() override; diff --git a/src/plugins/autotest/qtest/qttest_utils.cpp b/src/plugins/autotest/qtest/qttest_utils.cpp index 1069280bee1..9bedaa64c37 100644 --- a/src/plugins/autotest/qtest/qttest_utils.cpp +++ b/src/plugins/autotest/qtest/qttest_utils.cpp @@ -2,9 +2,9 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qttest_utils.h" + #include "qttesttreeitem.h" -#include "../autotestplugin.h" -#include "../testframeworkmanager.h" +#include "../itestframework.h" #include "../testsettings.h" #include @@ -27,7 +27,8 @@ bool isQTestMacro(const QByteArray ¯o) return valid.contains(macro); } -QHash testCaseNamesForFiles(ITestFramework *framework, const FilePaths &files) +QHash testCaseNamesForFiles(ITestFramework *framework, + const QSet &files) { QHash result; TestTreeItem *rootNode = framework->rootNode(); @@ -49,7 +50,8 @@ QHash testCaseNamesForFiles(ITestFramework *framework, cons return result; } -QMultiHash alternativeFiles(ITestFramework *framework, const FilePaths &files) +QMultiHash alternativeFiles(ITestFramework *framework, + const QSet &files) { QMultiHash result; TestTreeItem *rootNode = framework->rootNode(); @@ -135,7 +137,7 @@ Environment prepareBasicEnvironment(const Environment &env) result.set("QT_FORCE_STDERR_LOGGING", "1"); result.set("QT_LOGGING_TO_CONSOLE", "1"); } - const int timeout = AutotestPlugin::settings()->timeout; + const int timeout = TestSettings::instance()->timeout(); if (timeout > 5 * 60 * 1000) // Qt5.5 introduced hard limit, Qt5.6.1 added env var to raise this result.set("QTEST_FUNCTION_TIMEOUT", QString::number(timeout)); return result; diff --git a/src/plugins/autotest/qtest/qttest_utils.h b/src/plugins/autotest/qtest/qttest_utils.h index 6f5a587df3d..8f077345412 100644 --- a/src/plugins/autotest/qtest/qttest_utils.h +++ b/src/plugins/autotest/qtest/qttest_utils.h @@ -26,9 +26,9 @@ namespace QTestUtils { bool isQTestMacro(const QByteArray ¯o); QHash testCaseNamesForFiles(ITestFramework *framework, - const Utils::FilePaths &files); + const QSet &files); QMultiHash alternativeFiles(ITestFramework *framework, - const Utils::FilePaths &files); + const QSet &files); QStringList filterInterfering(const QStringList &provided, QStringList *omitted, bool isQuickTest); Utils::Environment prepareBasicEnvironment(const Utils::Environment &env); diff --git a/src/plugins/autotest/qtest/qttestconfiguration.cpp b/src/plugins/autotest/qtest/qttestconfiguration.cpp index 8a77f305e34..5dcf59a48e1 100644 --- a/src/plugins/autotest/qtest/qttestconfiguration.cpp +++ b/src/plugins/autotest/qtest/qttestconfiguration.cpp @@ -2,16 +2,15 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qttestconfiguration.h" -#include "qttestconstants.h" + #include "qttestoutputreader.h" #include "qttestsettings.h" #include "qttest_utils.h" -#include "../autotestplugin.h" + #include "../itestframework.h" #include "../testsettings.h" #include -#include using namespace Utils; @@ -28,20 +27,19 @@ static QStringList quoteIfNeeded(const QStringList &testCases, bool debugMode) }); } -TestOutputReader *QtTestConfiguration::createOutputReader(const QFutureInterface &fi, - QtcProcess *app) const +TestOutputReader *QtTestConfiguration::createOutputReader(Process *app) const { auto qtSettings = static_cast(framework()->testSettings()); const QtTestOutputReader::OutputMode mode = qtSettings && qtSettings->useXMLOutput.value() ? QtTestOutputReader::XML : QtTestOutputReader::PlainText; - return new QtTestOutputReader(fi, app, buildDirectory(), projectFile(), mode, TestType::QtTest); + return new QtTestOutputReader(app, buildDirectory(), projectFile(), mode, TestType::QtTest); } QStringList QtTestConfiguration::argumentsForTestRunner(QStringList *omitted) const { QStringList arguments; - if (AutotestPlugin::settings()->processArgs) { + if (TestSettings::instance()->processArgs()) { arguments.append(QTestUtils::filterInterfering( runnable().command.arguments().split(' ', Qt::SkipEmptyParts), omitted, false)); diff --git a/src/plugins/autotest/qtest/qttestconfiguration.h b/src/plugins/autotest/qtest/qttestconfiguration.h index a99c93f0b75..d41697090e6 100644 --- a/src/plugins/autotest/qtest/qttestconfiguration.h +++ b/src/plugins/autotest/qtest/qttestconfiguration.h @@ -13,8 +13,7 @@ class QtTestConfiguration : public DebuggableTestConfiguration public: explicit QtTestConfiguration(ITestFramework *framework) : DebuggableTestConfiguration(framework) {} - TestOutputReader *createOutputReader(const QFutureInterface &fi, - Utils::QtcProcess *app) const override; + TestOutputReader *createOutputReader(Utils::Process *app) const override; QStringList argumentsForTestRunner(QStringList *omitted = nullptr) const override; Utils::Environment filteredEnvironment(const Utils::Environment &original) const override; }; diff --git a/src/plugins/autotest/qtest/qttestframework.h b/src/plugins/autotest/qtest/qttestframework.h index 4fa765ab192..c90bd3c9d8c 100644 --- a/src/plugins/autotest/qtest/qttestframework.h +++ b/src/plugins/autotest/qtest/qttestframework.h @@ -7,8 +7,7 @@ #include "qttestsettings.h" -namespace Autotest { -namespace Internal { +namespace Autotest::Internal { class QtTestFramework : public ITestFramework { @@ -24,9 +23,7 @@ private: ITestTreeItem *createRootNode() override; ITestSettings *testSettings() override { return &m_settings; } - QtTestSettings m_settings; - QtTestSettingsPage m_settingsPage{&m_settings, settingsId()}; + QtTestSettings m_settings{settingsId()}; }; -} // namespace Internal -} // namespace Autotest +} // Autotest::Internal diff --git a/src/plugins/autotest/qtest/qttestoutputreader.cpp b/src/plugins/autotest/qtest/qttestoutputreader.cpp index 073ef0e9fed..70df35e6632 100644 --- a/src/plugins/autotest/qtest/qttestoutputreader.cpp +++ b/src/plugins/autotest/qtest/qttestoutputreader.cpp @@ -12,8 +12,6 @@ #include -#include - using namespace Utils; namespace Autotest { @@ -99,18 +97,15 @@ static QString constructBenchmarkInformation(const QString &metric, double value else if (metric == "CPUCycles") // -perf metricsText = "CPU cycles"; return Tr::tr("%1 %2 per iteration (total: %3, iterations: %4)") - .arg(formatResult(value)) - .arg(metricsText) - .arg(formatResult(value * double(iterations))) + .arg(formatResult(value), metricsText, formatResult(value * double(iterations))) .arg(iterations); } -QtTestOutputReader::QtTestOutputReader(const QFutureInterface &futureInterface, - QtcProcess *testApplication, +QtTestOutputReader::QtTestOutputReader(Process *testApplication, const FilePath &buildDirectory, const FilePath &projectFile, OutputMode mode, TestType type) - : TestOutputReader(futureInterface, testApplication, buildDirectory) + : TestOutputReader(testApplication, buildDirectory) , m_projectFile(projectFile) , m_mode(mode) , m_testType(type) @@ -177,8 +172,6 @@ void QtTestOutputReader::processXMLOutput(const QByteArray &outputLine) m_xmlReader.addData("\n"); m_xmlReader.addData(QString::fromUtf8(outputLine)); while (!m_xmlReader.atEnd()) { - if (m_futureInterface.isCanceled()) - return; QXmlStreamReader::TokenType token = m_xmlReader.readNext(); switch (token) { case QXmlStreamReader::StartDocument: @@ -277,7 +270,7 @@ void QtTestOutputReader::processXMLOutput(const QByteArray &outputLine) const QStringView currentTag = m_xmlReader.name(); if (currentTag == QStringLiteral("TestFunction")) { sendFinishMessage(true); - m_futureInterface.setProgressValue(m_futureInterface.progressValue() + 1); + // TODO: bump progress? m_dataTag.clear(); m_formerTestCase = m_testCase; m_testCase.clear(); @@ -347,9 +340,6 @@ void QtTestOutputReader::processPlainTextOutput(const QByteArray &outputLine) static const QRegularExpression locationUnix(QT_TEST_FAIL_UNIX_REGEXP); static const QRegularExpression locationWin(QT_TEST_FAIL_WIN_REGEXP); - if (m_futureInterface.isCanceled()) - return; - const QString line = QString::fromUtf8(outputLine); QRegularExpressionMatch match; diff --git a/src/plugins/autotest/qtest/qttestoutputreader.h b/src/plugins/autotest/qtest/qttestoutputreader.h index 6db9aa87810..0962c8d6845 100644 --- a/src/plugins/autotest/qtest/qttestoutputreader.h +++ b/src/plugins/autotest/qtest/qttestoutputreader.h @@ -3,9 +3,10 @@ #pragma once -#include "qttestconstants.h" #include "../testoutputreader.h" +#include "qttestconstants.h" + #include namespace Autotest { @@ -22,8 +23,7 @@ public: PlainText }; - QtTestOutputReader(const QFutureInterface &futureInterface, - Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory, + QtTestOutputReader(Utils::Process *testApplication, const Utils::FilePath &buildDirectory, const Utils::FilePath &projectFile, OutputMode mode, TestType type); protected: void processOutputLine(const QByteArray &outputLine) override; diff --git a/src/plugins/autotest/qtest/qttestparser.cpp b/src/plugins/autotest/qtest/qttestparser.cpp index 2b13133f03f..f68a87d7bf3 100644 --- a/src/plugins/autotest/qtest/qttestparser.cpp +++ b/src/plugins/autotest/qtest/qttestparser.cpp @@ -10,6 +10,7 @@ #include #include +#include #include using namespace Utils; @@ -292,7 +293,7 @@ static bool isQObject(const CPlusPlus::Document::Ptr &declaringDoc) || file.endsWith("QtCore/qobject.h") || file.endsWith("kernel/qobject.h"); } -bool QtTestParser::processDocument(QFutureInterface &futureInterface, +bool QtTestParser::processDocument(QPromise &promise, const FilePath &fileName) { CPlusPlus::Document::Ptr doc = document(fileName); @@ -325,7 +326,7 @@ bool QtTestParser::processDocument(QFutureInterface &futureI data.multipleTestCases = testCase.multipleTestCases; QtTestParseResult *parseResult = createParseResult(testCase.name, data, projectParts.first()->projectFile); - futureInterface.reportResult(TestParseResultPtr(parseResult)); + promise.addResult(TestParseResultPtr(parseResult)); reported = true; } } @@ -413,7 +414,7 @@ QtTestParseResult *QtTestParser::createParseResult( return parseResult; } -void QtTestParser::init(const FilePaths &filesToParse, bool fullParse) +void QtTestParser::init(const QSet &filesToParse, bool fullParse) { if (!fullParse) { // in a full parse cached information might lead to wrong results m_testCases = QTestUtils::testCaseNamesForFiles(framework(), filesToParse); diff --git a/src/plugins/autotest/qtest/qttestparser.h b/src/plugins/autotest/qtest/qttestparser.h index db677929ec8..abd8b2b0aaa 100644 --- a/src/plugins/autotest/qtest/qttestparser.h +++ b/src/plugins/autotest/qtest/qttestparser.h @@ -34,9 +34,9 @@ class QtTestParser : public CppParser public: explicit QtTestParser(ITestFramework *framework) : CppParser(framework) {} - void init(const Utils::FilePaths &filesToParse, bool fullParse) override; + void init(const QSet &filesToParse, bool fullParse) override; void release() override; - bool processDocument(QFutureInterface &futureInterface, + bool processDocument(QPromise &promise, const Utils::FilePath &fileName) override; private: diff --git a/src/plugins/autotest/qtest/qttestsettings.cpp b/src/plugins/autotest/qtest/qttestsettings.cpp index 4be93d99379..a8953a04492 100644 --- a/src/plugins/autotest/qtest/qttestsettings.cpp +++ b/src/plugins/autotest/qtest/qttestsettings.cpp @@ -10,17 +10,33 @@ #include #include +using namespace Layouting; using namespace Utils; -namespace Autotest { -namespace Internal { +namespace Autotest::Internal { -QtTestSettings::QtTestSettings() +QtTestSettings::QtTestSettings(Id settingsId) { setSettingsGroups("Autotest", "QtTest"); - setAutoApply(false); + setId(settingsId); + setCategory(Constants::AUTOTEST_SETTINGS_CATEGORY); + setDisplayName(Tr::tr(QtTest::Constants::FRAMEWORK_SETTINGS_CATEGORY)); + + setLayouter([this] { + return Row { Form { + noCrashHandler, br, + useXMLOutput, br, + verboseBench, br, + logSignalsSlots, br, + limitWarnings, maxWarnings, br, + Group { + title(Tr::tr("Benchmark Metrics")), + Column { metrics } + }, br, + quickCheckForDerivedTests, br + }, st }; + }); - registerAspect(&metrics); metrics.setSettingsKey("Metrics"); metrics.setDefaultValue(Walltime); metrics.addOption(Tr::tr("Walltime"), Tr::tr("Uses walltime metrics for executing benchmarks (default).")); @@ -37,43 +53,36 @@ QtTestSettings::QtTestSettings() HostOsInfo::isLinuxHost() // according to docs perf Linux only }); - registerAspect(&noCrashHandler); noCrashHandler.setSettingsKey("NoCrashhandlerOnDebug"); noCrashHandler.setDefaultValue(true); noCrashHandler.setLabelText(Tr::tr("Disable crash handler while debugging")); noCrashHandler.setToolTip(Tr::tr("Enables interrupting tests on assertions.")); - registerAspect(&useXMLOutput); useXMLOutput.setSettingsKey("UseXMLOutput"); useXMLOutput.setDefaultValue(true); useXMLOutput.setLabelText(Tr::tr("Use XML output")); useXMLOutput.setToolTip(Tr::tr("XML output is recommended, because it avoids parsing issues, " - "while plain text is more human readable.\n\n" - "Warning: Plain text misses some information, such as duration.")); + "while plain text is more human readable.\n\nWarning: " + "Plain text misses some information, such as duration.")); - registerAspect(&verboseBench); verboseBench.setSettingsKey("VerboseBench"); verboseBench.setLabelText(Tr::tr("Verbose benchmarks")); - registerAspect(&logSignalsSlots); logSignalsSlots.setSettingsKey("LogSignalsSlots"); logSignalsSlots.setLabelText(Tr::tr("Log signals and slots")); logSignalsSlots.setToolTip(Tr::tr("Log every signal emission and resulting slot invocations.")); - registerAspect(&limitWarnings); limitWarnings.setSettingsKey("LimitWarnings"); limitWarnings.setLabelText(Tr::tr("Limit warnings")); limitWarnings.setToolTip(Tr::tr("Set the maximum number of warnings. 0 means that the number " "is not limited.")); - registerAspect(&maxWarnings); maxWarnings.setSettingsKey("MaxWarnings"); maxWarnings.setRange(0, 10000); maxWarnings.setDefaultValue(2000); maxWarnings.setSpecialValueText(Tr::tr("Unlimited")); maxWarnings.setEnabler(&limitWarnings); - registerAspect(&quickCheckForDerivedTests); quickCheckForDerivedTests.setSettingsKey("QuickCheckForDerivedTests"); quickCheckForDerivedTests.setDefaultValue(false); quickCheckForDerivedTests.setLabelText(Tr::tr("Check for derived Qt Quick tests")); @@ -99,36 +108,4 @@ QString QtTestSettings::metricsTypeToOption(const MetricsType type) return {}; } -QtTestSettingsPage::QtTestSettingsPage(QtTestSettings *settings, Id settingsId) -{ - setId(settingsId); - setCategory(Constants::AUTOTEST_SETTINGS_CATEGORY); - setDisplayName(Tr::tr(QtTest::Constants::FRAMEWORK_SETTINGS_CATEGORY)); - setSettings(settings); - - setLayouter([settings](QWidget *widget) { - QtTestSettings &s = *settings; - using namespace Layouting; - - Column col { - s.noCrashHandler, - s.useXMLOutput, - s.verboseBench, - s.logSignalsSlots, - Row { - s.limitWarnings, s.maxWarnings - }, - Group { - title(Tr::tr("Benchmark Metrics")), - Column { s.metrics } - }, - br, - s.quickCheckForDerivedTests, - }; - - Column { Row { col, st }, st }.attachTo(widget); - }); -} - -} // namespace Internal -} // namespace Autotest +} // Autotest::Internal diff --git a/src/plugins/autotest/qtest/qttestsettings.h b/src/plugins/autotest/qtest/qttestsettings.h index 4bbec4332df..31394ee6027 100644 --- a/src/plugins/autotest/qtest/qttestsettings.h +++ b/src/plugins/autotest/qtest/qttestsettings.h @@ -5,10 +5,7 @@ #include -#include - -namespace Autotest { -namespace Internal { +namespace Autotest::Internal { enum MetricsType { @@ -19,28 +16,21 @@ enum MetricsType Perf }; -class QtTestSettings : public Utils::AspectContainer +class QtTestSettings : public Core::PagedSettings { public: - QtTestSettings(); + explicit QtTestSettings(Utils::Id settingsId); static QString metricsTypeToOption(const MetricsType type); - Utils::SelectionAspect metrics; - Utils::BoolAspect noCrashHandler; - Utils::BoolAspect useXMLOutput; - Utils::BoolAspect verboseBench; - Utils::BoolAspect logSignalsSlots; - Utils::BoolAspect limitWarnings; - Utils::IntegerAspect maxWarnings; - Utils::BoolAspect quickCheckForDerivedTests; + Utils::SelectionAspect metrics{this}; + Utils::BoolAspect noCrashHandler{this}; + Utils::BoolAspect useXMLOutput{this}; + Utils::BoolAspect verboseBench{this}; + Utils::BoolAspect logSignalsSlots{this}; + Utils::BoolAspect limitWarnings{this}; + Utils::IntegerAspect maxWarnings{this}; + Utils::BoolAspect quickCheckForDerivedTests{this}; }; -class QtTestSettingsPage final : public Core::IOptionsPage -{ -public: - QtTestSettingsPage(QtTestSettings *settings, Utils::Id settingsId); -}; - -} // namespace Internal -} // namespace Autotest +} // Autotest::Internal diff --git a/src/plugins/autotest/qtest/qttesttreeitem.cpp b/src/plugins/autotest/qtest/qttesttreeitem.cpp index 2f846372822..81f95c17752 100644 --- a/src/plugins/autotest/qtest/qttesttreeitem.cpp +++ b/src/plugins/autotest/qtest/qttesttreeitem.cpp @@ -9,7 +9,9 @@ #include "../itestframework.h" #include -#include + +#include + #include using namespace Utils; @@ -116,7 +118,7 @@ bool QtTestTreeItem::canProvideDebugConfiguration() const ITestConfiguration *QtTestTreeItem::testConfiguration() const { - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); QTC_ASSERT(project, return nullptr); const auto cppMM = CppEditor::CppModelManager::instance(); QTC_ASSERT(cppMM, return nullptr); @@ -195,7 +197,7 @@ static void fillTestConfigurationsFromCheckState(const TestTreeItem *item, testConfig = new QtTestConfiguration(item->framework()); testConfig->setTestCases(testCases); testConfig->setProjectFile(item->proFile()); - testConfig->setProject(ProjectExplorer::SessionManager::startupProject()); + testConfig->setProject(ProjectExplorer::ProjectManager::startupProject()); testConfig->setInternalTargets(cppMM->internalTargets(item->filePath())); testConfigurations << testConfig; } @@ -229,7 +231,7 @@ static void collectFailedTestInfo(TestTreeItem *item, QListframework()); testConfig->setTestCases(testCases); testConfig->setProjectFile(item->proFile()); - testConfig->setProject(ProjectExplorer::SessionManager::startupProject()); + testConfig->setProject(ProjectExplorer::ProjectManager::startupProject()); testConfig->setInternalTargets(cppMM->internalTargets(item->filePath())); testConfigs << testConfig; } @@ -246,7 +248,7 @@ QList QtTestTreeItem::getAllTestConfigurations() const { QList result; - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); if (!project || type() != Root) return result; @@ -269,7 +271,7 @@ QList QtTestTreeItem::getAllTestConfigurations() const QList QtTestTreeItem::getSelectedTestConfigurations() const { QList result; - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); if (!project || type() != Root) return result; @@ -292,7 +294,7 @@ QList QtTestTreeItem::getTestConfigurationsForFile(const F { QList result; - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); if (!project || type() != Root) return result; diff --git a/src/plugins/autotest/quick/quicktest_utils.cpp b/src/plugins/autotest/quick/quicktest_utils.cpp index 09d2453c96a..d5ffc26038f 100644 --- a/src/plugins/autotest/quick/quicktest_utils.cpp +++ b/src/plugins/autotest/quick/quicktest_utils.cpp @@ -22,7 +22,8 @@ bool isQuickTestMacro(const QByteArray ¯o) return valid.contains(macro); } -QHash proFilesForQmlFiles(ITestFramework *framework, const FilePaths &files) +QHash proFilesForQmlFiles(ITestFramework *framework, + const QSet &files) { QHash result; TestTreeItem *rootNode = framework->rootNode(); diff --git a/src/plugins/autotest/quick/quicktest_utils.h b/src/plugins/autotest/quick/quicktest_utils.h index fb1dbb26753..731f7c6864c 100644 --- a/src/plugins/autotest/quick/quicktest_utils.h +++ b/src/plugins/autotest/quick/quicktest_utils.h @@ -16,7 +16,7 @@ namespace QuickTestUtils { bool isQuickTestMacro(const QByteArray ¯o); QHash proFilesForQmlFiles(ITestFramework *framework, - const Utils::FilePaths &files); + const QSet &files); } // namespace QuickTestUtils } // namespace Internal diff --git a/src/plugins/autotest/quick/quicktestconfiguration.cpp b/src/plugins/autotest/quick/quicktestconfiguration.cpp index c7cce31acf1..e37b208ac44 100644 --- a/src/plugins/autotest/quick/quicktestconfiguration.cpp +++ b/src/plugins/autotest/quick/quicktestconfiguration.cpp @@ -2,16 +2,13 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "quicktestconfiguration.h" -#include "../qtest/qttestconstants.h" + +#include "../itestframework.h" #include "../qtest/qttestoutputreader.h" #include "../qtest/qttestsettings.h" #include "../qtest/qttest_utils.h" -#include "../autotestplugin.h" -#include "../itestframework.h" #include "../testsettings.h" -#include - using namespace Utils; namespace Autotest { @@ -23,21 +20,19 @@ QuickTestConfiguration::QuickTestConfiguration(ITestFramework *framework) setMixedDebugging(true); } -TestOutputReader *QuickTestConfiguration::createOutputReader( - const QFutureInterface &fi, QtcProcess *app) const +TestOutputReader *QuickTestConfiguration::createOutputReader(Process *app) const { auto qtSettings = static_cast(framework()->testSettings()); const QtTestOutputReader::OutputMode mode = qtSettings && qtSettings->useXMLOutput.value() ? QtTestOutputReader::XML : QtTestOutputReader::PlainText; - return new QtTestOutputReader(fi, app, buildDirectory(), projectFile(), - mode, TestType::QuickTest); + return new QtTestOutputReader(app, buildDirectory(), projectFile(), mode, TestType::QuickTest); } QStringList QuickTestConfiguration::argumentsForTestRunner(QStringList *omitted) const { QStringList arguments; - if (AutotestPlugin::settings()->processArgs) { + if (TestSettings::instance()->processArgs()) { arguments.append(QTestUtils::filterInterfering (runnable().command.arguments().split(' ', Qt::SkipEmptyParts), omitted, true)); diff --git a/src/plugins/autotest/quick/quicktestconfiguration.h b/src/plugins/autotest/quick/quicktestconfiguration.h index 84e374ebe8a..33a07b0294f 100644 --- a/src/plugins/autotest/quick/quicktestconfiguration.h +++ b/src/plugins/autotest/quick/quicktestconfiguration.h @@ -12,8 +12,7 @@ class QuickTestConfiguration : public DebuggableTestConfiguration { public: explicit QuickTestConfiguration(ITestFramework *framework); - TestOutputReader *createOutputReader(const QFutureInterface &fi, - Utils::QtcProcess *app) const override; + TestOutputReader *createOutputReader(Utils::Process *app) const override; QStringList argumentsForTestRunner(QStringList *omitted = nullptr) const override; Utils::Environment filteredEnvironment(const Utils::Environment &original) const override; }; diff --git a/src/plugins/autotest/quick/quicktestparser.cpp b/src/plugins/autotest/quick/quicktestparser.cpp index 2c72cdf3f1a..a4cf0967b6c 100644 --- a/src/plugins/autotest/quick/quicktestparser.cpp +++ b/src/plugins/autotest/quick/quicktestparser.cpp @@ -12,14 +12,19 @@ #include #include -#include + +#include + #include #include #include + #include #include #include +#include + using namespace QmlJS; using namespace Utils; @@ -155,10 +160,9 @@ QList QuickTestParser::scanDirectoryForQuickTestQmlFiles(const Fi QStringList dirsStr({srcDir.toString()}); ModelManagerInterface *qmlJsMM = QmlJSTools::Internal::ModelManager::instance(); // make sure even files not listed in pro file are available inside the snapshot - QFutureInterface future; PathsAndLanguages paths; paths.maybeInsert(srcDir, Dialect::Qml); - ModelManagerInterface::importScan(future, ModelManagerInterface::workingCopy(), paths, qmlJsMM, + ModelManagerInterface::importScan(ModelManagerInterface::workingCopy(), paths, qmlJsMM, false /*emitDocumentChanges*/, false /*onlyTheLib*/, true /*forceRescan*/ ); const Snapshot snapshot = QmlJSTools::Internal::ModelManager::instance()->snapshot(); @@ -196,7 +200,7 @@ QList QuickTestParser::scanDirectoryForQuickTestQmlFiles(const Fi return foundDocs; } -static bool checkQmlDocumentForQuickTestCode(QFutureInterface &futureInterface, +static bool checkQmlDocumentForQuickTestCode(QPromise &promise, const Document::Ptr &qmlJSDoc, ITestFramework *framework, const FilePath &proFile = {}, @@ -240,12 +244,12 @@ static bool checkQmlDocumentForQuickTestCode(QFutureInterfacechildren.append(funcResult); } - futureInterface.reportResult(TestParseResultPtr(parseResult)); + promise.addResult(TestParseResultPtr(parseResult)); } return true; } -bool QuickTestParser::handleQtQuickTest(QFutureInterface &futureInterface, +bool QuickTestParser::handleQtQuickTest(QPromise &promise, CPlusPlus::Document::Ptr document, ITestFramework *framework) { @@ -263,17 +267,14 @@ bool QuickTestParser::handleQtQuickTest(QFutureInterface &fu if (srcDir.isEmpty()) return false; - if (futureInterface.isCanceled()) + if (promise.isCanceled()) return false; const QList qmlDocs = scanDirectoryForQuickTestQmlFiles(srcDir); bool result = false; for (const Document::Ptr &qmlJSDoc : qmlDocs) { - if (futureInterface.isCanceled()) + if (promise.isCanceled()) break; - result |= checkQmlDocumentForQuickTestCode(futureInterface, - qmlJSDoc, - framework, - proFile, + result |= checkQmlDocumentForQuickTestCode(promise, qmlJSDoc, framework, proFile, m_checkForDerivedTests); } return result; @@ -305,9 +306,8 @@ void QuickTestParser::handleDirectoryChanged(const QString &directory) m_watchedFiles[directory] = filesAndDates; PathsAndLanguages paths; paths.maybeInsert(FilePath::fromString(directory), Dialect::Qml); - QFutureInterface future; ModelManagerInterface *qmlJsMM = ModelManagerInterface::instance(); - ModelManagerInterface::importScan(future, ModelManagerInterface::workingCopy(), paths, + ModelManagerInterface::importScan(ModelManagerInterface::workingCopy(), paths, qmlJsMM, true /*emitDocumentChanges*/, false /*onlyTheLib*/, @@ -327,8 +327,8 @@ void QuickTestParser::doUpdateWatchPaths(const QStringList &directories) QuickTestParser::QuickTestParser(ITestFramework *framework) : CppParser(framework) { - connect(ProjectExplorer::SessionManager::instance(), - &ProjectExplorer::SessionManager::startupProjectChanged, this, [this] { + connect(ProjectExplorer::ProjectManager::instance(), + &ProjectExplorer::ProjectManager::startupProjectChanged, this, [this] { const QStringList &dirs = m_directoryWatcher.directories(); if (!dirs.isEmpty()) m_directoryWatcher.removePaths(dirs); @@ -338,7 +338,7 @@ QuickTestParser::QuickTestParser(ITestFramework *framework) this, &QuickTestParser::handleDirectoryChanged); } -void QuickTestParser::init(const FilePaths &filesToParse, bool fullParse) +void QuickTestParser::init(const QSet &filesToParse, bool fullParse) { m_qmlSnapshot = QmlJSTools::Internal::ModelManager::instance()->snapshot(); if (!fullParse) { @@ -370,7 +370,7 @@ void QuickTestParser::release() CppParser::release(); } -bool QuickTestParser::processDocument(QFutureInterface &futureInterface, +bool QuickTestParser::processDocument(QPromise &promise, const FilePath &fileName) { if (fileName.endsWith(".qml")) { @@ -378,7 +378,7 @@ bool QuickTestParser::processDocument(QFutureInterface &futu if (proFile.isEmpty()) return false; Document::Ptr qmlJSDoc = m_qmlSnapshot.document(fileName); - return checkQmlDocumentForQuickTestCode(futureInterface, + return checkQmlDocumentForQuickTestCode(promise, qmlJSDoc, framework(), proFile, @@ -389,7 +389,7 @@ bool QuickTestParser::processDocument(QFutureInterface &futu if (cppdoc.isNull() || !includesQtQuickTest(cppdoc, m_cppSnapshot)) return false; - return handleQtQuickTest(futureInterface, cppdoc, framework()); + return handleQtQuickTest(promise, cppdoc, framework()); } FilePath QuickTestParser::projectFileForMainCppFile(const FilePath &fileName) const diff --git a/src/plugins/autotest/quick/quicktestparser.h b/src/plugins/autotest/quick/quicktestparser.h index c0fbc5a3e83..dfd71333ead 100644 --- a/src/plugins/autotest/quick/quicktestparser.h +++ b/src/plugins/autotest/quick/quicktestparser.h @@ -24,15 +24,15 @@ class QuickTestParser : public QObject, public CppParser Q_OBJECT public: explicit QuickTestParser(ITestFramework *framework); - void init(const Utils::FilePaths &filesToParse, bool fullParse) override; + void init(const QSet &filesToParse, bool fullParse) override; void release() override; - bool processDocument(QFutureInterface &futureInterface, + bool processDocument(QPromise &promise, const Utils::FilePath &fileName) override; Utils::FilePath projectFileForMainCppFile(const Utils::FilePath &fileName) const; QStringList supportedExtensions() const override { return {"qml"}; }; private: - bool handleQtQuickTest(QFutureInterface &futureInterface, + bool handleQtQuickTest(QPromise &promise, CPlusPlus::Document::Ptr document, ITestFramework *framework); void handleDirectoryChanged(const QString &directory); diff --git a/src/plugins/autotest/quick/quicktesttreeitem.cpp b/src/plugins/autotest/quick/quicktesttreeitem.cpp index f721ce3bc0d..88f43e15419 100644 --- a/src/plugins/autotest/quick/quicktesttreeitem.cpp +++ b/src/plugins/autotest/quick/quicktesttreeitem.cpp @@ -9,7 +9,9 @@ #include "../itestframework.h" #include -#include + +#include + #include using namespace Utils; @@ -108,7 +110,7 @@ bool QuickTestTreeItem::canProvideDebugConfiguration() const ITestConfiguration *QuickTestTreeItem::testConfiguration() const { - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); QTC_ASSERT(project, return nullptr); QuickTestConfiguration *config = nullptr; @@ -147,7 +149,7 @@ static QList testConfigurationsFor( const TestTreeItem *rootNode, const std::function &predicate) { QTC_ASSERT(rootNode, return {}); - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); if (!project || rootNode->type() != TestTreeItem::Root) return {}; @@ -171,7 +173,7 @@ static QList testConfigurationsFor( if (it == configurationForProFiles.end()) { auto tc = new QuickTestConfiguration(treeItem->framework()); tc->setProjectFile(treeItem->proFile()); - tc->setProject(ProjectExplorer::SessionManager::startupProject()); + tc->setProject(ProjectExplorer::ProjectManager::startupProject()); tc->setInternalTargets(internalTargets(treeItem->proFile())); it = configurationForProFiles.insert(treeItem->proFile(), tc); } @@ -206,7 +208,7 @@ QList QuickTestTreeItem::getAllTestConfigurations() const { QList result; - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); if (!project || type() != Root) return result; @@ -369,7 +371,7 @@ QSet internalTargets(const FilePath &proFile) { QSet result; const auto cppMM = CppEditor::CppModelManager::instance(); - const auto projectInfo = cppMM->projectInfo(ProjectExplorer::SessionManager::startupProject()); + const auto projectInfo = cppMM->projectInfo(ProjectExplorer::ProjectManager::startupProject()); if (!projectInfo) return {}; for (const CppEditor::ProjectPart::ConstPtr &projectPart : projectInfo->projectParts()) { @@ -387,17 +389,19 @@ QSet internalTargets(const FilePath &proFile) return result; } -void QuickTestTreeItem::markForRemovalRecursively(const FilePath &filePath) +void QuickTestTreeItem::markForRemovalRecursively(const QSet &filePaths) { - TestTreeItem::markForRemovalRecursively(filePath); + TestTreeItem::markForRemovalRecursively(filePaths); auto parser = static_cast(framework()->testParser()); - const FilePath proFile = parser->projectFileForMainCppFile(filePath); - if (!proFile.isEmpty()) { - TestTreeItem *root = framework()->rootNode(); - root->forAllChildItems([proFile](TestTreeItem *it) { - if (it->proFile() == proFile) - it->markForRemoval(true); - }); + for (const FilePath &filePath : filePaths) { + const FilePath proFile = parser->projectFileForMainCppFile(filePath); + if (!proFile.isEmpty()) { + TestTreeItem *root = framework()->rootNode(); + root->forAllChildItems([proFile](TestTreeItem *it) { + if (it->proFile() == proFile) + it->markForRemoval(true); + }); + } } } diff --git a/src/plugins/autotest/quick/quicktesttreeitem.h b/src/plugins/autotest/quick/quicktesttreeitem.h index adbf95fa8bc..c09bd97b984 100644 --- a/src/plugins/autotest/quick/quicktesttreeitem.h +++ b/src/plugins/autotest/quick/quicktesttreeitem.h @@ -35,7 +35,7 @@ public: bool removeOnSweepIfEmpty() const override; TestTreeItem *createParentGroupNode() const override; bool isGroupable() const override; - void markForRemovalRecursively(const Utils::FilePath &filePath) override; + void markForRemovalRecursively(const QSet &filePaths) override; private: TestTreeItem *findChildByFileNameAndType(const Utils::FilePath &filePath, const QString &name, Type tType); diff --git a/src/plugins/autotest/testcodeparser.cpp b/src/plugins/autotest/testcodeparser.cpp index edf126f8d42..8553702432f 100644 --- a/src/plugins/autotest/testcodeparser.cpp +++ b/src/plugins/autotest/testcodeparser.cpp @@ -8,24 +8,21 @@ #include "testtreemodel.h" #include -#include #include +#include #include #include #include #include -#include -#include +#include #include -#include +#include #include -#include -#include -#include #include +using namespace Core; using namespace Utils; namespace Autotest { @@ -37,33 +34,30 @@ using namespace ProjectExplorer; static bool isProjectParsing() { - const BuildSystem *bs = SessionManager::startupBuildSystem(); + const BuildSystem *bs = ProjectManager::startupBuildSystem(); return bs && bs->isParsing(); } TestCodeParser::TestCodeParser() - : m_threadPool(new QThreadPool(this)) { // connect to ProgressManager to postpone test parsing when CppModelManager is parsing - auto progressManager = qobject_cast(Core::ProgressManager::instance()); - connect(progressManager, &Core::ProgressManager::taskStarted, + ProgressManager *progressManager = ProgressManager::instance(); + connect(progressManager, &ProgressManager::taskStarted, this, &TestCodeParser::onTaskStarted); - connect(progressManager, &Core::ProgressManager::allTasksFinished, + connect(progressManager, &ProgressManager::allTasksFinished, this, &TestCodeParser::onAllTasksFinished); - connect(&m_futureWatcher, &QFutureWatcher::started, - this, &TestCodeParser::parsingStarted); - connect(&m_futureWatcher, &QFutureWatcher::finished, - this, &TestCodeParser::onFinished); - connect(&m_futureWatcher, &QFutureWatcher::resultReadyAt, - this, [this](int index) { - emit testParseResultReady(m_futureWatcher.resultAt(index)); - }); connect(this, &TestCodeParser::parsingFinished, this, &TestCodeParser::releaseParserInternals); + connect(EditorManager::instance(), &EditorManager::documentClosed, this, [this](IDocument *doc){ + QTC_ASSERT(doc, return); + if (FilePath filePath = doc->filePath(); filePath.endsWith(".qml")) + m_qmlEditorRev.remove(filePath); + }); m_reparseTimer.setSingleShot(true); connect(&m_reparseTimer, &QTimer::timeout, this, &TestCodeParser::parsePostponedFiles); - m_threadPool->setMaxThreadCount(std::max(QThread::idealThreadCount()/4, 1)); } +TestCodeParser::~TestCodeParser() = default; + void TestCodeParser::setState(State state) { if (m_parserState == Shutdown) @@ -82,14 +76,14 @@ void TestCodeParser::setState(State state) } m_parserState = state; - if (m_parserState == Idle && SessionManager::startupProject()) { + if (m_parserState == Idle && ProjectManager::startupProject()) { if (m_postponedUpdateType == UpdateType::FullUpdate || m_dirty) { emitUpdateTestTree(); } else if (m_postponedUpdateType == UpdateType::PartialUpdate) { m_postponedUpdateType = UpdateType::NoUpdate; qCDebug(LOG) << "calling scanForTests with postponed files (setState)"; if (!m_reparseTimer.isActive()) - scanForTests(Utils::toList(m_postponedFiles)); + scanForTests(m_postponedFiles); } } } @@ -100,7 +94,7 @@ void TestCodeParser::syncTestFrameworks(const QList &parsers) // there's a running parse m_postponedUpdateType = UpdateType::NoUpdate; m_postponedFiles.clear(); - Core::ProgressManager::cancelTasks(Constants::TASK_PARSE); + ProgressManager::cancelTasks(Constants::TASK_PARSE); } qCDebug(LOG) << "Setting" << parsers << "as current parsers"; m_testCodeParsers = parsers; @@ -139,7 +133,7 @@ void TestCodeParser::updateTestTree(const QSet &parsers) return; } - if (!SessionManager::startupProject()) + if (!ProjectManager::startupProject()) return; m_postponedUpdateType = UpdateType::NoUpdate; @@ -158,7 +152,7 @@ void TestCodeParser::onDocumentUpdated(const FilePath &fileName, bool isQmlFile) if (isProjectParsing() || m_codeModelParsing || m_postponedUpdateType == UpdateType::FullUpdate) return; - Project *project = SessionManager::startupProject(); + Project *project = ProjectManager::startupProject(); if (!project) return; // Quick tests: qml files aren't necessarily listed inside project files @@ -177,15 +171,20 @@ void TestCodeParser::onQmlDocumentUpdated(const QmlJS::Document::Ptr &document) { static const QStringList ignoredSuffixes{ "qbs", "ui.qml" }; const FilePath fileName = document->fileName(); - if (!ignoredSuffixes.contains(fileName.suffix())) - onDocumentUpdated(fileName, true); + int editorRevision = document->editorRevision(); + if (editorRevision != m_qmlEditorRev.value(fileName, 0)) { + m_qmlEditorRev.insert(fileName, editorRevision); + if (!ignoredSuffixes.contains(fileName.suffix())) + onDocumentUpdated(fileName, true); + } } void TestCodeParser::onStartupProjectChanged(Project *project) { + m_qmlEditorRev.clear(); if (m_parserState == FullParse || m_parserState == PartialParse) { qCDebug(LOG) << "Canceling scanForTest (startup project changed)"; - Core::ProgressManager::cancelTasks(Constants::TASK_PARSE); + ProgressManager::cancelTasks(Constants::TASK_PARSE); } emit aboutToPerformFullParse(); if (project) @@ -194,7 +193,7 @@ void TestCodeParser::onStartupProjectChanged(Project *project) void TestCodeParser::onProjectPartsUpdated(Project *project) { - if (project != SessionManager::startupProject()) + if (project != ProjectManager::startupProject()) return; if (isProjectParsing() || m_codeModelParsing) m_postponedUpdateType = UpdateType::FullUpdate; @@ -205,35 +204,33 @@ void TestCodeParser::onProjectPartsUpdated(Project *project) void TestCodeParser::aboutToShutdown() { qCDebug(LOG) << "Disabling (immediately) - shutting down"; - State oldState = m_parserState; m_parserState = Shutdown; - if (oldState == PartialParse || oldState == FullParse) { - m_futureWatcher.cancel(); - m_futureWatcher.waitForFinished(); - } + m_taskTree.reset(); + m_futureSynchronizer.waitForFinished(); } -bool TestCodeParser::postponed(const FilePaths &fileList) +bool TestCodeParser::postponed(const QSet &filePaths) { switch (m_parserState) { case Idle: - if (fileList.size() == 1) { + if (filePaths.size() == 1) { if (m_reparseTimerTimedOut) return false; + const FilePath filePath = *filePaths.begin(); switch (m_postponedFiles.size()) { case 0: - m_postponedFiles.insert(fileList.first()); + m_postponedFiles.insert(filePath); m_reparseTimer.setInterval(1000); m_reparseTimer.start(); return true; case 1: - if (m_postponedFiles.contains(fileList.first())) { + if (m_postponedFiles.contains(filePath)) { m_reparseTimer.start(); return true; } Q_FALLTHROUGH(); default: - m_postponedFiles.insert(fileList.first()); + m_postponedFiles.insert(filePath); m_reparseTimer.stop(); m_reparseTimer.setInterval(0); m_reparseTimerTimedOut = false; @@ -245,18 +242,17 @@ bool TestCodeParser::postponed(const FilePaths &fileList) case PartialParse: case FullParse: // parse is running, postponing a full parse - if (fileList.isEmpty()) { + if (filePaths.isEmpty()) { m_postponedFiles.clear(); m_postponedUpdateType = UpdateType::FullUpdate; qCDebug(LOG) << "Canceling scanForTest (full parse triggered while running a scan)"; - Core::ProgressManager::cancelTasks(Constants::TASK_PARSE); + ProgressManager::cancelTasks(Constants::TASK_PARSE); } else { // partial parse triggered, but full parse is postponed already, ignoring this if (m_postponedUpdateType == UpdateType::FullUpdate) return true; // partial parse triggered, postpone or add current files to already postponed partial - for (const FilePath &file : fileList) - m_postponedFiles.insert(file); + m_postponedFiles += filePaths; m_postponedUpdateType = UpdateType::PartialUpdate; } return true; @@ -266,43 +262,43 @@ bool TestCodeParser::postponed(const FilePaths &fileList) QTC_ASSERT(false, return false); // should not happen at all } -static void parseFileForTests(const QList &parsers, - QFutureInterface &futureInterface, - const FilePath &fileName) +static void parseFileForTests(QPromise &promise, + const QList &parsers, const FilePath &fileName) { for (ITestParser *parser : parsers) { - if (futureInterface.isCanceled()) + if (promise.isCanceled()) return; - if (parser->processDocument(futureInterface, fileName)) + if (parser->processDocument(promise, fileName)) break; } } -void TestCodeParser::scanForTests(const FilePaths &fileList, const QList &parsers) +void TestCodeParser::scanForTests(const QSet &filePaths, + const QList &parsers) { if (m_parserState == Shutdown || m_testCodeParsers.isEmpty()) return; - if (postponed(fileList)) + if (postponed(filePaths)) return; + QSet files = filePaths; // avoid getting cleared if m_postponedFiles have been passed m_reparseTimer.stop(); m_reparseTimerTimedOut = false; m_postponedFiles.clear(); - bool isFullParse = fileList.isEmpty(); - Project *project = SessionManager::startupProject(); + const bool isFullParse = files.isEmpty(); + Project *project = ProjectManager::startupProject(); if (!project) return; - FilePaths list; if (isFullParse) { - list = project->files(Project::SourceFiles); - if (list.isEmpty()) { + files = Utils::toSet(project->files(Project::SourceFiles)); + if (files.isEmpty()) { // at least project file should be there, but might happen if parsing current project // takes too long, especially when opening sessions holding multiple projects qCDebug(LOG) << "File list empty (FullParse) - trying again in a sec"; emitUpdateTestTree(); return; - } else if (list.size() == 1 && list.first() == project->projectFilePath()) { + } else if (files.size() == 1 && *files.constBegin() == project->projectFilePath()) { qCDebug(LOG) << "File list contains only the project file."; return; } @@ -310,7 +306,6 @@ void TestCodeParser::scanForTests(const FilePaths &fileList, const QListupdateCheckStateCache(); if (isFullParse) { // remove qml files as they will be found automatically by the referencing cpp file - list = Utils::filtered(list, [](const FilePath &fn) { - return !fn.endsWith(".qml"); - }); + files = Utils::filtered(files, [](const FilePath &fn) { return !fn.endsWith(".qml"); }); if (!parsers.isEmpty()) { - for (ITestParser *parser : parsers) { + for (ITestParser *parser : parsers) parser->framework()->rootNode()->markForRemovalRecursively(true); - } } else { emit requestRemoveAllFrameworkItems(); } } else if (!parsers.isEmpty()) { for (ITestParser *parser: parsers) { - for (const FilePath &filePath : std::as_const(list)) - parser->framework()->rootNode()->markForRemovalRecursively(filePath); + parser->framework()->rootNode()->markForRemovalRecursively(files); } } else { - for (const FilePath &filePath : std::as_const(list)) - emit requestRemoval(filePath); + emit requestRemoval(files); } - QTC_ASSERT(!(isFullParse && list.isEmpty()), onFinished(); return); + QTC_ASSERT(!(isFullParse && files.isEmpty()), onFinished(true); return); // use only a single parser or all current active? const QList codeParsers = parsers.isEmpty() ? m_testCodeParsers : parsers; qCDebug(LOG) << QDateTime::currentDateTime().toString("hh:mm:ss.zzz") << "StartParsing"; + m_parsingTimer.restart(); QSet extensions; const auto cppSnapshot = CppEditor::CppModelManager::instance()->snapshot(); for (ITestParser *parser : codeParsers) { - parser->init(list, isFullParse); + parser->init(files, isFullParse); for (const QString &ext : parser->supportedExtensions()) extensions.insert(ext); } // We are only interested in files that have been either parsed by the c++ parser, // or have an extension that one of the parsers is specifically interested in. - const FilePaths filteredList - = Utils::filtered(list, [&extensions, &cppSnapshot](const FilePath &fn) { + const QSet filteredFiles + = Utils::filtered(files, [&extensions, &cppSnapshot](const FilePath &fn) { const bool isSupportedExtension = Utils::anyOf(extensions, [&fn](const QString &ext) { return fn.suffix() == ext; }); @@ -364,21 +355,37 @@ void TestCodeParser::scanForTests(const FilePaths &fileList, const QList future = Utils::map(filteredList, - [codeParsers](QFutureInterface &fi, const FilePath &file) { - parseFileForTests(codeParsers, fi, file); - }, - MapReduceOption::Unordered, - m_threadPool, - QThread::LowestPriority); - m_futureWatcher.setFuture(future); - if (filteredList.size() > 5) { - Core::ProgressManager::addTask(future, Tr::tr("Scanning for Tests"), - Autotest::Constants::TASK_PARSE); + using namespace Tasking; + + QList tasks{parallelLimit(std::max(QThread::idealThreadCount() / 4, 1))}; + for (const FilePath &file : filteredFiles) { + const auto setup = [this, codeParsers, file](Async &async) { + async.setConcurrentCallData(parseFileForTests, codeParsers, file); + async.setPriority(QThread::LowestPriority); + async.setFutureSynchronizer(&m_futureSynchronizer); + }; + const auto onDone = [this](const Async &async) { + const QList results = async.results(); + for (const TestParseResultPtr &result : results) + emit testParseResultReady(result); + }; + tasks.append(AsyncTask(setup, onDone)); } + m_taskTree.reset(new TaskTree{tasks}); + const auto onDone = [this] { m_taskTree.release()->deleteLater(); onFinished(true); }; + const auto onError = [this] { m_taskTree.release()->deleteLater(); onFinished(false); }; + connect(m_taskTree.get(), &TaskTree::started, this, &TestCodeParser::parsingStarted); + connect(m_taskTree.get(), &TaskTree::done, this, onDone); + connect(m_taskTree.get(), &TaskTree::errorOccurred, this, onError); + if (filteredFiles.size() > 5) { + auto progress = new TaskProgress(m_taskTree.get()); + progress->setDisplayName(Tr::tr("Scanning for Tests")); + progress->setId(Constants::TASK_PARSE); + } + m_taskTree->start(); } void TestCodeParser::onTaskStarted(Id type) @@ -390,7 +397,7 @@ void TestCodeParser::onTaskStarted(Id type) ? UpdateType::FullUpdate : UpdateType::PartialUpdate; qCDebug(LOG) << "Canceling scan for test (CppModelParsing started)"; m_parsingHasFailed = true; - Core::ProgressManager::cancelTasks(Constants::TASK_PARSE); + ProgressManager::cancelTasks(Constants::TASK_PARSE); } } } @@ -410,10 +417,9 @@ void TestCodeParser::onAllTasksFinished(Id type) setState(Idle); } -void TestCodeParser::onFinished() +void TestCodeParser::onFinished(bool success) { - if (m_futureWatcher.isCanceled()) - m_parsingHasFailed = true; + m_parsingHasFailed = !success; switch (m_parserState) { case PartialParse: qCDebug(LOG) << "setting state to Idle (onFinished, PartialParse)"; @@ -433,6 +439,7 @@ void TestCodeParser::onFinished() m_updateParsers.clear(); emit parsingFinished(); qCDebug(LOG) << QDateTime::currentDateTime().toString("hh:mm:ss.zzz") << "ParsingFin"; + qCDebug(LOG) << "Parsing took:" << m_parsingTimer.elapsed() << "ms"; } m_dirty = false; break; @@ -457,7 +464,7 @@ void TestCodeParser::onPartialParsingFinished() case UpdateType::PartialUpdate: qCDebug(LOG) << "calling scanForTests with postponed files (onPartialParsingFinished)"; if (!m_reparseTimer.isActive()) - scanForTests(Utils::toList(m_postponedFiles)); + scanForTests(m_postponedFiles); break; case UpdateType::NoUpdate: m_dirty |= m_codeModelParsing; @@ -481,7 +488,7 @@ void TestCodeParser::onPartialParsingFinished() void TestCodeParser::parsePostponedFiles() { m_reparseTimerTimedOut = true; - scanForTests(Utils::toList(m_postponedFiles)); + scanForTests(m_postponedFiles); } void TestCodeParser::releaseParserInternals() diff --git a/src/plugins/autotest/testcodeparser.h b/src/plugins/autotest/testcodeparser.h index efd0d602e4a..2dd81d93d60 100644 --- a/src/plugins/autotest/testcodeparser.h +++ b/src/plugins/autotest/testcodeparser.h @@ -6,10 +6,10 @@ #include "itestparser.h" #include + +#include #include -#include -#include #include #include @@ -18,9 +18,9 @@ class QThreadPool; QT_END_NAMESPACE namespace ProjectExplorer { class Project; } +namespace Tasking { class TaskTree; } namespace Autotest { - namespace Internal { class TestCodeParser : public QObject @@ -35,6 +35,7 @@ public: }; TestCodeParser(); + ~TestCodeParser(); void setState(State state); State state() const { return m_parserState; } @@ -48,11 +49,11 @@ public: signals: void aboutToPerformFullParse(); - void testParseResultReady(const TestParseResultPtr result); + void testParseResultReady(const TestParseResultPtr result); // TODO: pass list of results? void parsingStarted(); void parsingFinished(); void parsingFailed(); - void requestRemoval(const Utils::FilePath &filePath); + void requestRemoval(const QSet &filePaths); void requestRemoveAllFrameworkItems(); public: @@ -65,15 +66,15 @@ public: void aboutToShutdown(); private: - bool postponed(const Utils::FilePaths &fileList); - void scanForTests(const Utils::FilePaths &fileList = Utils::FilePaths(), + bool postponed(const QSet &fileList); + void scanForTests(const QSet &filePaths = {}, const QList &parsers = {}); // qml files must be handled slightly different void onDocumentUpdated(const Utils::FilePath &fileName, bool isQmlFile = false); void onTaskStarted(Utils::Id type); void onAllTasksFinished(Utils::Id type); - void onFinished(); + void onFinished(bool success); void onPartialParsingFinished(); void parsePostponedFiles(); void releaseParserInternals(); @@ -83,21 +84,21 @@ private: bool m_parsingHasFailed = false; bool m_codeModelParsing = false; - enum class UpdateType { - NoUpdate, - PartialUpdate, - FullUpdate - } m_postponedUpdateType = UpdateType::NoUpdate; + enum class UpdateType { NoUpdate, PartialUpdate, FullUpdate }; + UpdateType m_postponedUpdateType = UpdateType::NoUpdate; bool m_dirty = false; bool m_singleShotScheduled = false; bool m_reparseTimerTimedOut = false; QSet m_postponedFiles; State m_parserState = Idle; - QFutureWatcher m_futureWatcher; QList m_testCodeParsers; // ptrs are still owned by TestFrameworkManager QTimer m_reparseTimer; QSet m_updateParsers; - QThreadPool *m_threadPool = nullptr; + Utils::FutureSynchronizer m_futureSynchronizer; + std::unique_ptr m_taskTree; + QHash m_qmlEditorRev; + + QElapsedTimer m_parsingTimer; }; } // namespace Internal diff --git a/src/plugins/autotest/testconfiguration.cpp b/src/plugins/autotest/testconfiguration.cpp index 764ede697d1..c095e9e0f6a 100644 --- a/src/plugins/autotest/testconfiguration.cpp +++ b/src/plugins/autotest/testconfiguration.cpp @@ -4,22 +4,19 @@ #include "testconfiguration.h" #include "itestframework.h" -#include "testoutputreader.h" #include "testrunconfiguration.h" -#include -#include - #include #include #include #include -#include #include #include -#include +#include #include +#include + #include static Q_LOGGING_CATEGORY(LOG, "qtc.autotest.testconfiguration", QtWarningMsg) @@ -29,7 +26,6 @@ using namespace Utils; namespace Autotest { - ITestConfiguration::ITestConfiguration(ITestBase *testBase) : m_testBase(testBase) { @@ -94,7 +90,7 @@ static FilePath ensureExeEnding(const FilePath &file) return file.withExecutableSuffix(); } -void TestConfiguration::completeTestInformation(ProjectExplorer::RunConfiguration *rc, +void TestConfiguration::completeTestInformation(RunConfiguration *rc, TestRunMode runMode) { QTC_ASSERT(rc, return); @@ -104,7 +100,7 @@ void TestConfiguration::completeTestInformation(ProjectExplorer::RunConfiguratio qCDebug(LOG) << "Executable has been set already - not completing configuration again."; return; } - Project *startupProject = SessionManager::startupProject(); + Project *startupProject = ProjectManager::startupProject(); if (!startupProject || startupProject != project()) return; @@ -149,7 +145,7 @@ void TestConfiguration::completeTestInformation(TestRunMode runMode) } qCDebug(LOG) << "Failed to complete - using 'normal' way."; } - Project *startupProject = SessionManager::startupProject(); + Project *startupProject = ProjectManager::startupProject(); if (!startupProject || startupProject != project()) { setProject(nullptr); return; diff --git a/src/plugins/autotest/testconfiguration.h b/src/plugins/autotest/testconfiguration.h index d57416d0164..6292ef8204c 100644 --- a/src/plugins/autotest/testconfiguration.h +++ b/src/plugins/autotest/testconfiguration.h @@ -9,11 +9,10 @@ #include #include -#include #include #include -namespace Utils { class QtcProcess; } +namespace Utils { class Process; } namespace Autotest { namespace Internal { @@ -40,8 +39,7 @@ public: Utils::FilePath executableFilePath() const; virtual Utils::FilePath testExecutable() const { return executableFilePath(); }; - virtual TestOutputReader *createOutputReader(const QFutureInterface &fi, - Utils::QtcProcess *app) const = 0; + virtual TestOutputReader *createOutputReader(Utils::Process *app) const = 0; virtual Utils::Environment filteredEnvironment(const Utils::Environment &original) const; ITestBase *testBase() const { return m_testBase; } diff --git a/src/plugins/autotest/testframeworkmanager.cpp b/src/plugins/autotest/testframeworkmanager.cpp index 91fa3135930..3c6aff581db 100644 --- a/src/plugins/autotest/testframeworkmanager.cpp +++ b/src/plugins/autotest/testframeworkmanager.cpp @@ -3,7 +3,6 @@ #include "testframeworkmanager.h" -#include "autotestplugin.h" #include "testsettings.h" #include @@ -98,7 +97,7 @@ ITestTool *TestFrameworkManager::testToolForBuildSystemId(Id buildSystemId) void TestFrameworkManager::synchronizeSettings(QSettings *s) { - Internal::AutotestPlugin::settings()->fromSettings(s); + Internal::TestSettings::instance()->fromSettings(s); for (ITestFramework *framework : std::as_const(m_registeredFrameworks)) { if (ITestSettings *fSettings = framework->testSettings()) fSettings->readSettings(s); diff --git a/src/plugins/autotest/testframeworkmanager.h b/src/plugins/autotest/testframeworkmanager.h index 3eab62adbc8..73dfd96ff96 100644 --- a/src/plugins/autotest/testframeworkmanager.h +++ b/src/plugins/autotest/testframeworkmanager.h @@ -11,7 +11,7 @@ QT_END_NAMESPACE namespace Autotest { namespace Internal { -struct TestSettings; +class TestSettings; } class TestFrameworkManager final diff --git a/src/plugins/autotest/testnavigationwidget.cpp b/src/plugins/autotest/testnavigationwidget.cpp index 100eed89ed9..431f5a3af89 100644 --- a/src/plugins/autotest/testnavigationwidget.cpp +++ b/src/plugins/autotest/testnavigationwidget.cpp @@ -18,12 +18,15 @@ #include #include #include + #include #include -#include +#include + #include #include #include +#include #include #include @@ -87,8 +90,8 @@ TestNavigationWidget::TestNavigationWidget(QWidget *parent) : connect(m_model, &TestTreeModel::updatedActiveFrameworks, this, [this](int numberOfActive) { m_missingFrameworksWidget->setVisible(numberOfActive == 0); }); - ProjectExplorer::SessionManager *sm = ProjectExplorer::SessionManager::instance(); - connect(sm, &ProjectExplorer::SessionManager::startupProjectChanged, + ProjectExplorer::ProjectManager *sm = ProjectExplorer::ProjectManager::instance(); + connect(sm, &ProjectExplorer::ProjectManager::startupProjectChanged, this, [this](ProjectExplorer::Project * /*project*/) { m_expandedStateCache.clear(); }); @@ -190,7 +193,7 @@ QList TestNavigationWidget::createToolButtons() m_filterButton = new QToolButton(m_view); m_filterButton->setIcon(Utils::Icons::FILTER.icon()); m_filterButton->setToolTip(Tr::tr("Filter Test Tree")); - m_filterButton->setProperty("noArrow", true); + m_filterButton->setProperty(StyleHelper::C_NO_ARROW, true); m_filterButton->setPopupMode(QToolButton::InstantPopup); m_filterMenu = new QMenu(m_filterButton); initializeFilterMenu(); diff --git a/src/plugins/autotest/testoutputreader.cpp b/src/plugins/autotest/testoutputreader.cpp index 2f1dc75e485..cef36c90e76 100644 --- a/src/plugins/autotest/testoutputreader.cpp +++ b/src/plugins/autotest/testoutputreader.cpp @@ -4,17 +4,12 @@ #include "testoutputreader.h" #include "autotesttr.h" -#include "testresult.h" -#include "testresultspane.h" #include "testtreeitem.h" +#include #include -#include -#include -#include -#include -#include +#include using namespace Utils; @@ -26,11 +21,8 @@ FilePath TestOutputReader::constructSourceFilePath(const FilePath &path, const Q return filePath.isReadableFile() ? filePath : FilePath(); } -TestOutputReader::TestOutputReader(const QFutureInterface &futureInterface, - QtcProcess *testApplication, const FilePath &buildDirectory) - : m_futureInterface(futureInterface) - , m_buildDir(buildDirectory) - , m_id(testApplication ? testApplication->commandLine().executable().toUserOutput() : QString()) +TestOutputReader::TestOutputReader(Process *testApplication, const FilePath &buildDirectory) + : m_buildDir(buildDirectory) { auto chopLineBreak = [](QByteArray line) { if (line.endsWith('\n')) @@ -41,6 +33,9 @@ TestOutputReader::TestOutputReader(const QFutureInterface &futureInt }; if (testApplication) { + connect(testApplication, &Process::started, this, [this, testApplication] { + m_id = testApplication->commandLine().executable().toUserOutput(); + }); testApplication->setStdOutLineCallback([this, &chopLineBreak](const QString &line) { processStdOutput(chopLineBreak(line.toUtf8())); }); diff --git a/src/plugins/autotest/testoutputreader.h b/src/plugins/autotest/testoutputreader.h index 55c645d87c4..e87c463735a 100644 --- a/src/plugins/autotest/testoutputreader.h +++ b/src/plugins/autotest/testoutputreader.h @@ -5,11 +5,9 @@ #include "testresult.h" -#include #include -#include -namespace Utils { class QtcProcess; } +namespace Utils { class Process; } namespace Autotest { @@ -17,8 +15,7 @@ class TestOutputReader : public QObject { Q_OBJECT public: - TestOutputReader(const QFutureInterface &futureInterface, - Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory); + TestOutputReader(Utils::Process *testApplication, const Utils::FilePath &buildDirectory); virtual ~TestOutputReader(); void processStdOutput(const QByteArray &outputLine); virtual void processStdError(const QByteArray &outputLine); @@ -46,7 +43,6 @@ protected: void sendAndResetSanitizerResult(); void reportResult(const TestResult &result); - QFutureInterface m_futureInterface; Utils::FilePath m_buildDir; QString m_id; QHash m_summary; diff --git a/src/plugins/autotest/testprojectsettings.cpp b/src/plugins/autotest/testprojectsettings.cpp index 862fc8a24f4..5f745637db8 100644 --- a/src/plugins/autotest/testprojectsettings.cpp +++ b/src/plugins/autotest/testprojectsettings.cpp @@ -7,7 +7,7 @@ #include "testframeworkmanager.h" #include -#include +#include #include #include diff --git a/src/plugins/autotest/testresultdelegate.cpp b/src/plugins/autotest/testresultdelegate.cpp index 9b55623d572..c14b6c17032 100644 --- a/src/plugins/autotest/testresultdelegate.cpp +++ b/src/plugins/autotest/testresultdelegate.cpp @@ -3,7 +3,6 @@ #include "testresultdelegate.h" -#include "autotestplugin.h" #include "testresultmodel.h" #include "testsettings.h" @@ -162,10 +161,10 @@ void TestResultDelegate::clearCache() void TestResultDelegate::limitTextOutput(QString &output) const { - int maxLineCount = Internal::AutotestPlugin::settings()->resultDescriptionMaxSize; + int maxLineCount = Internal::TestSettings::instance()->resultDescriptionMaxSize(); bool limited = false; - if (Internal::AutotestPlugin::settings()->limitResultDescription && maxLineCount > 0) { + if (Internal::TestSettings::instance()->limitResultDescription() && maxLineCount > 0) { int index = -1; int lastChar = output.size() - 1; @@ -183,7 +182,7 @@ void TestResultDelegate::limitTextOutput(QString &output) const } } - if (AutotestPlugin::settings()->limitResultOutput && output.length() > outputLimit) { + if (TestSettings::instance()->limitResultOutput() && output.length() > outputLimit) { output = output.left(outputLimit); limited = true; } diff --git a/src/plugins/autotest/testresultdelegate.h b/src/plugins/autotest/testresultdelegate.h index c5a4919a71e..f7d6aa1ab27 100644 --- a/src/plugins/autotest/testresultdelegate.h +++ b/src/plugins/autotest/testresultdelegate.h @@ -38,7 +38,6 @@ private: public: LayoutPositions(QStyleOptionViewItem &options, const TestResultFilterModel *filterModel) : m_top(options.rect.top()), - m_bottom(options.rect.bottom()), m_left(options.rect.left()), m_right(options.rect.right()) { @@ -57,20 +56,15 @@ private: int top() const { return m_top + ITEM_MARGIN; } int left() const { return m_left + ITEM_MARGIN; } int right() const { return m_right - ITEM_MARGIN; } - int bottom() const { return m_bottom; } int minimumHeight() const { return ICON_SIZE + 2 * ITEM_MARGIN; } int iconSize() const { return ICON_SIZE; } - int fontHeight() const { return m_fontHeight; } int typeAreaLeft() const { return left() + ICON_SIZE + ITEM_SPACING; } - int typeAreaWidth() const { return m_typeAreaWidth; } int textAreaLeft() const { return typeAreaLeft() + m_typeAreaWidth + ITEM_SPACING; } int textAreaWidth() const { return fileAreaLeft() - ITEM_SPACING - textAreaLeft(); } int fileAreaLeft() const { return lineAreaLeft() - ITEM_SPACING - m_realFileLength; } int lineAreaLeft() const { return right() - m_maxLineLength; } - QRect typeArea() const { return QRect(typeAreaLeft(), top(), - typeAreaWidth(), m_fontHeight); } QRect textArea() const { return QRect(textAreaLeft(), top(), textAreaWidth(), m_fontHeight); } QRect fileArea() const { return QRect(fileAreaLeft(), top(), @@ -84,7 +78,6 @@ private: int m_maxLineLength; int m_realFileLength; int m_top; - int m_bottom; int m_left; int m_right; int m_fontHeight; diff --git a/src/plugins/autotest/testresultmodel.cpp b/src/plugins/autotest/testresultmodel.cpp index 434d6474103..f686692faad 100644 --- a/src/plugins/autotest/testresultmodel.cpp +++ b/src/plugins/autotest/testresultmodel.cpp @@ -4,7 +4,6 @@ #include "testresultmodel.h" #include "autotesticons.h" -#include "autotestplugin.h" #include "testrunner.h" #include "testsettings.h" #include "testtreeitem.h" @@ -274,7 +273,7 @@ void TestResultModel::addTestResult(const TestResult &testResult, bool autoExpan TestResultItem *newItem = new TestResultItem(testResult); TestResultItem *root = nullptr; - if (AutotestPlugin::settings()->displayApplication) { + if (TestSettings::instance()->displayApplication()) { const QString application = testResult.id(); if (!application.isEmpty()) { root = rootItem()->findFirstLevelChild([&application](TestResultItem *child) { diff --git a/src/plugins/autotest/testresultspane.cpp b/src/plugins/autotest/testresultspane.cpp index 87d44b0f2f9..3dbad31ea99 100644 --- a/src/plugins/autotest/testresultspane.cpp +++ b/src/plugins/autotest/testresultspane.cpp @@ -197,7 +197,7 @@ void TestResultsPane::createToolButtons() m_filterButton = new QToolButton(m_treeView); m_filterButton->setIcon(Utils::Icons::FILTER.icon()); m_filterButton->setToolTip(Tr::tr("Filter Test Results")); - m_filterButton->setProperty("noArrow", true); + m_filterButton->setProperty(StyleHelper::C_NO_ARROW, true); m_filterButton->setPopupMode(QToolButton::InstantPopup); m_filterMenu = new QMenu(m_filterButton); initializeFilterMenu(); @@ -291,7 +291,7 @@ void TestResultsPane::clearContents() setIconBadgeNumber(0); navigateStateChanged(); m_summaryWidget->setVisible(false); - m_autoScroll = AutotestPlugin::settings()->autoScroll; + m_autoScroll = TestSettings::instance()->autoScroll(); connect(m_treeView->verticalScrollBar(), &QScrollBar::rangeChanged, this, &TestResultsPane::onScrollBarRangeChanged, Qt::UniqueConnection); m_textOutput->clear(); @@ -437,7 +437,7 @@ void TestResultsPane::onRunSelectedTriggered() void TestResultsPane::initializeFilterMenu() { - const bool omitIntern = AutotestPlugin::settings()->omitInternalMssg; + const bool omitIntern = TestSettings::instance()->omitInternalMsg(); // FilterModel has all messages enabled by default if (omitIntern) m_filterModel->toggleTestResultType(ResultType::MessageInternal); @@ -553,8 +553,8 @@ void TestResultsPane::onTestRunFinished() m_model->removeCurrentTestMessage(); disconnect(m_treeView->verticalScrollBar(), &QScrollBar::rangeChanged, this, &TestResultsPane::onScrollBarRangeChanged); - if (AutotestPlugin::settings()->popupOnFinish - && (!AutotestPlugin::settings()->popupOnFail || hasFailedTests(m_model))) { + if (TestSettings::instance()->popupOnFinish() + && (!TestSettings::instance()->popupOnFail() || hasFailedTests(m_model))) { popup(IOutputPane::NoModeSwitch); } createMarks(); diff --git a/src/plugins/autotest/testrunner.cpp b/src/plugins/autotest/testrunner.cpp index cb01d0d9a2a..5c53d742e65 100644 --- a/src/plugins/autotest/testrunner.cpp +++ b/src/plugins/autotest/testrunner.cpp @@ -6,18 +6,15 @@ #include "autotestconstants.h" #include "autotestplugin.h" #include "autotesttr.h" -#include "itestframework.h" #include "testoutputreader.h" #include "testprojectsettings.h" #include "testresultspane.h" #include "testrunconfiguration.h" -#include "testsettings.h" #include "testtreeitem.h" #include "testtreemodel.h" #include -#include -#include +#include #include #include @@ -28,30 +25,28 @@ #include #include #include +#include #include -#include #include #include #include #include #include -#include +#include #include #include #include #include -#include -#include #include #include #include -#include #include -#include +using namespace Core; using namespace ProjectExplorer; +using namespace Tasking; using namespace Utils; namespace Autotest { @@ -72,14 +67,7 @@ TestRunner::TestRunner() m_cancelTimer.setSingleShot(true); connect(&m_cancelTimer, &QTimer::timeout, this, [this] { cancelCurrent(Timeout); }); - connect(&m_futureWatcher, &QFutureWatcher::finished, - this, &TestRunner::onFinished); - connect(this, &TestRunner::requestStopTestRun, - &m_futureWatcher, &QFutureWatcher::cancel); - connect(&m_futureWatcher, &QFutureWatcher::canceled, this, [this] { - cancelCurrent(UserCanceled); - reportResult(ResultType::MessageFatal, Tr::tr("Test run canceled by user.")); - }); + connect(this, &TestRunner::requestStopTestRun, this, [this] { cancelCurrent(UserCanceled); }); connect(BuildManager::instance(), &BuildManager::buildQueueFinished, this, &TestRunner::onBuildQueueFinished); } @@ -93,14 +81,14 @@ TestRunner::~TestRunner() void TestRunner::runTest(TestRunMode mode, const ITestTreeItem *item) { - QTC_ASSERT(!m_executingTests, return); + QTC_ASSERT(!isTestRunning(), return); ITestConfiguration *configuration = item->asConfiguration(mode); if (configuration) runTests(mode, {configuration}); } -static QString processInformation(const QtcProcess *proc) +static QString processInformation(const Process *proc) { QTC_ASSERT(proc, return {}); const CommandLine command = proc->commandLine(); @@ -144,180 +132,21 @@ static QString constructOmittedVariablesDetailsString(const EnvironmentItems &di + '\n' + removedVars.join('\n'); } -bool TestRunner::currentConfigValid() -{ - const FilePath commandFilePath = m_currentConfig->testExecutable(); - if (!commandFilePath.isEmpty()) - return true; - - reportResult(ResultType::MessageFatal, - Tr::tr("Executable path is empty. (%1)").arg(m_currentConfig->displayName())); - delete m_currentConfig; - m_currentConfig = nullptr; - if (m_selectedTests.isEmpty()) { - if (m_fakeFutureInterface) - m_fakeFutureInterface->reportFinished(); - onFinished(); - } else { - onProcessDone(); - } - return false; -} - -void TestRunner::setUpProcessEnv() -{ - CommandLine command = m_currentProcess->commandLine(); - if (m_currentConfig->testBase()->type() == ITestBase::Framework) { - TestConfiguration *current = static_cast(m_currentConfig); - - QStringList omitted; - command.addArgs(current->argumentsForTestRunner(&omitted).join(' '), CommandLine::Raw); - if (!omitted.isEmpty()) { - const QString &details = constructOmittedDetailsString(omitted); - reportResult(ResultType::MessageWarn, details.arg(current->displayName())); - } - } else { - TestToolConfiguration *current = static_cast(m_currentConfig); - command.setArguments(current->commandLine().arguments()); - } - m_currentProcess->setCommand(command); - - m_currentProcess->setWorkingDirectory(m_currentConfig->workingDirectory()); - const Environment &original = m_currentConfig->environment(); - Environment environment = m_currentConfig->filteredEnvironment(original); - const EnvironmentItems removedVariables = Utils::filtered( - original.diff(environment), [](const EnvironmentItem &it) { - return it.operation == EnvironmentItem::Unset; - }); - if (!removedVariables.isEmpty()) { - const QString &details = constructOmittedVariablesDetailsString(removedVariables) - .arg(m_currentConfig->displayName()); - reportResult(ResultType::MessageWarn, details); - } - m_currentProcess->setEnvironment(environment); -} - -void TestRunner::scheduleNext() -{ - QTC_ASSERT(!m_selectedTests.isEmpty(), onFinished(); return); - QTC_ASSERT(!m_currentConfig && !m_currentProcess, resetInternalPointers()); - QTC_ASSERT(m_fakeFutureInterface, onFinished(); return); - QTC_ASSERT(!m_canceled, onFinished(); return); - - m_currentConfig = m_selectedTests.takeFirst(); - - if (!currentConfigValid()) - return; - - if (!m_currentConfig->project()) - onProcessDone(); - - m_currentProcess = new QtcProcess; - m_currentProcess->setCommand({m_currentConfig->testExecutable(), {}}); - - QTC_ASSERT(!m_currentOutputReader, delete m_currentOutputReader); - m_currentOutputReader = m_currentConfig->createOutputReader(*m_fakeFutureInterface, m_currentProcess); - QTC_ASSERT(m_currentOutputReader, onProcessDone(); return); - connect(m_currentOutputReader, &TestOutputReader::newResult, this, &TestRunner::testResultReady); - connect(m_currentOutputReader, &TestOutputReader::newOutputLineAvailable, - TestResultsPane::instance(), &TestResultsPane::addOutputLine); - - setUpProcessEnv(); - - connect(m_currentProcess, &QtcProcess::done, this, &TestRunner::onProcessDone); - const int timeout = AutotestPlugin::settings()->timeout; - m_cancelTimer.setInterval(timeout); - m_cancelTimer.start(); - - qCInfo(runnerLog) << "Command:" << m_currentProcess->commandLine().executable(); - qCInfo(runnerLog) << "Arguments:" << m_currentProcess->commandLine().arguments(); - qCInfo(runnerLog) << "Working directory:" << m_currentProcess->workingDirectory(); - qCDebug(runnerLog) << "Environment:" << m_currentProcess->environment().toStringList(); - - m_currentProcess->start(); -} - void TestRunner::cancelCurrent(TestRunner::CancelReason reason) { - m_canceled = true; - - if (m_fakeFutureInterface) - m_fakeFutureInterface->reportCanceled(); - if (reason == KitChanged) reportResult(ResultType::MessageWarn, Tr::tr("Current kit has changed. Canceling test run.")); else if (reason == Timeout) reportResult(ResultType::MessageFatal, Tr::tr("Test case canceled due to timeout.\nMaybe raise the timeout?")); - - // if user or timeout cancels the current run ensure to kill the running process - if (m_currentProcess && m_currentProcess->state() != QProcess::NotRunning) { - m_currentProcess->kill(); - m_currentProcess->waitForFinished(); - } -} - -void TestRunner::onProcessDone() -{ - if (m_currentProcess->result() == ProcessResult::StartFailed) { - reportResult(ResultType::MessageFatal, - Tr::tr("Failed to start test for project \"%1\".").arg(m_currentConfig->displayName()) - + processInformation(m_currentProcess) + rcInfo(m_currentConfig)); - } - - if (m_executingTests && m_currentConfig) { - QTC_CHECK(m_fakeFutureInterface); - m_fakeFutureInterface->setProgressValue(m_fakeFutureInterface->progressValue() - + m_currentConfig->testCaseCount()); - if (m_currentProcess && !m_fakeFutureInterface->isCanceled()) { - if (m_currentProcess->exitStatus() == QProcess::CrashExit) { - if (m_currentOutputReader) - m_currentOutputReader->reportCrash(); - reportResult(ResultType::MessageFatal, - Tr::tr("Test for project \"%1\" crashed.").arg(m_currentConfig->displayName()) - + processInformation(m_currentProcess) + rcInfo(m_currentConfig)); - } else if (m_currentOutputReader && !m_currentOutputReader->hadValidOutput()) { - reportResult(ResultType::MessageFatal, - Tr::tr("Test for project \"%1\" did not produce any expected output.") - .arg(m_currentConfig->displayName()) + processInformation(m_currentProcess) - + rcInfo(m_currentConfig)); - } - } - } - if (m_currentOutputReader) { - const int disabled = m_currentOutputReader->disabledTests(); - if (disabled > 0) - emit hadDisabledTests(disabled); - if (m_currentOutputReader->hasSummary()) - emit reportSummary(m_currentOutputReader->id(), m_currentOutputReader->summary()); - - m_currentOutputReader->resetCommandlineColor(); - } - resetInternalPointers(); - - if (!m_fakeFutureInterface) { - QTC_ASSERT(!m_executingTests, m_executingTests = false); - return; - } - if (!m_selectedTests.isEmpty() && !m_fakeFutureInterface->isCanceled()) - scheduleNext(); - else - m_fakeFutureInterface->reportFinished(); -} - -void TestRunner::resetInternalPointers() -{ - delete m_currentOutputReader; - if (m_currentProcess) - m_currentProcess->deleteLater(); - delete m_currentConfig; - m_currentOutputReader = nullptr; - m_currentProcess = nullptr; - m_currentConfig = nullptr; + else if (reason == UserCanceled) + reportResult(ResultType::MessageFatal, Tr::tr("Test run canceled by user.")); + m_taskTree.reset(); + onFinished(); } void TestRunner::runTests(TestRunMode mode, const QList &selectedTests) { - QTC_ASSERT(!m_executingTests, return); + QTC_ASSERT(!isTestRunning(), return); qDeleteAll(m_selectedTests); m_selectedTests = selectedTests; @@ -332,8 +161,6 @@ void TestRunner::runTests(TestRunMode mode, const QList &s return; } - m_executingTests = true; - m_canceled = false; emit testRunStarted(); // clear old log and output pane @@ -385,7 +212,7 @@ static QString firstNonEmptyTestCaseTarget(const TestConfiguration *config) static RunConfiguration *getRunConfiguration(const QString &buildTargetKey) { - const Project *project = SessionManager::startupProject(); + const Project *project = ProjectManager::startupProject(); if (!project) return nullptr; const Target *target = project->activeTarget(); @@ -411,7 +238,7 @@ static RunConfiguration *getRunConfiguration(const QString &buildTargetKey) if (runConfigurations.size() == 1) return runConfigurations.first(); - RunConfigurationSelectionDialog dialog(buildTargetKey, Core::ICore::dialogParent()); + RunConfigurationSelectionDialog dialog(buildTargetKey, ICore::dialogParent()); if (dialog.exec() == QDialog::Accepted) { const QString dName = dialog.displayName(); if (dName.isEmpty()) @@ -431,7 +258,7 @@ static RunConfiguration *getRunConfiguration(const QString &buildTargetKey) int TestRunner::precheckTestConfigurations() { - const bool omitWarnings = AutotestPlugin::settings()->omitRunConfigWarn; + const bool omitWarnings = TestSettings::instance()->omitRunConfigWarn(); int testCaseCount = 0; for (ITestConfiguration *itc : std::as_const(m_selectedTests)) { if (itc->testBase()->type() == ITestBase::Tool) { @@ -467,7 +294,7 @@ int TestRunner::precheckTestConfigurations() void TestRunner::onBuildSystemUpdated() { - Target *target = SessionManager::startupTarget(); + Target *target = ProjectManager::startupTarget(); if (QTC_GUARD(target)) disconnect(target, &Target::buildSystemUpdated, this, &TestRunner::onBuildSystemUpdated); if (!m_skipTargetsCheck) { @@ -482,7 +309,7 @@ void TestRunner::runTestsHelper() bool projectChanged = false; for (ITestConfiguration *itc : std::as_const(m_selectedTests)) { if (itc->testBase()->type() == ITestBase::Tool) { - if (itc->project() != SessionManager::startupProject()) { + if (itc->project() != ProjectManager::startupProject()) { projectChanged = true; toBeRemoved.append(itc); } @@ -513,19 +340,135 @@ void TestRunner::runTestsHelper() return; } - int testCaseCount = precheckTestConfigurations(); + const int testCaseCount = precheckTestConfigurations(); + Q_UNUSED(testCaseCount) // TODO: may be useful for fine-grained progress reporting, when fixed - // Fake future interface - destruction will be handled by QFuture/QFutureWatcher - m_fakeFutureInterface = new QFutureInterface(QFutureInterfaceBase::Running); - QFuture future = m_fakeFutureInterface->future(); - m_fakeFutureInterface->setProgressRange(0, testCaseCount); - m_fakeFutureInterface->setProgressValue(0); - m_futureWatcher.setFuture(future); + struct TestStorage { + std::unique_ptr m_outputReader; + }; - Core::ProgressManager::addTask(future, Tr::tr("Running Tests"), Autotest::Constants::TASK_INDEX); - if (AutotestPlugin::settings()->popupOnStart) + QList tasks{finishAllAndDone}; + + for (ITestConfiguration *config : m_selectedTests) { + QTC_ASSERT(config, continue); + const TreeStorage storage; + + const auto onSetup = [this, config] { + if (!config->project()) + return TaskAction::StopWithDone; + if (config->testExecutable().isEmpty()) { + reportResult(ResultType::MessageFatal, + Tr::tr("Executable path is empty. (%1)").arg(config->displayName())); + return TaskAction::StopWithDone; + } + return TaskAction::Continue; + }; + const auto onProcessSetup = [this, config, storage](Process &process) { + TestStorage *testStorage = storage.activeStorage(); + QTC_ASSERT(testStorage, return); + testStorage->m_outputReader.reset(config->createOutputReader(&process)); + QTC_ASSERT(testStorage->m_outputReader, return); + connect(testStorage->m_outputReader.get(), &TestOutputReader::newResult, + this, &TestRunner::testResultReady); + connect(testStorage->m_outputReader.get(), &TestOutputReader::newOutputLineAvailable, + TestResultsPane::instance(), &TestResultsPane::addOutputLine); + + CommandLine command{config->testExecutable(), {}}; + if (config->testBase()->type() == ITestBase::Framework) { + TestConfiguration *current = static_cast(config); + QStringList omitted; + command.addArgs(current->argumentsForTestRunner(&omitted).join(' '), CommandLine::Raw); + if (!omitted.isEmpty()) { + const QString &details = constructOmittedDetailsString(omitted); + reportResult(ResultType::MessageWarn, details.arg(current->displayName())); + } + } else { + TestToolConfiguration *current = static_cast(config); + command.setArguments(current->commandLine().arguments()); + } + process.setCommand(command); + + process.setWorkingDirectory(config->workingDirectory()); + const Environment &original = config->environment(); + Environment environment = config->filteredEnvironment(original); + const EnvironmentItems removedVariables = Utils::filtered( + original.diff(environment), [](const EnvironmentItem &it) { + return it.operation == EnvironmentItem::Unset; + }); + if (!removedVariables.isEmpty()) { + const QString &details = constructOmittedVariablesDetailsString(removedVariables) + .arg(config->displayName()); + reportResult(ResultType::MessageWarn, details); + } + process.setEnvironment(environment); + + m_cancelTimer.setInterval(TestSettings::instance()->timeout()); + m_cancelTimer.start(); + + qCInfo(runnerLog) << "Command:" << process.commandLine().executable(); + qCInfo(runnerLog) << "Arguments:" << process.commandLine().arguments(); + qCInfo(runnerLog) << "Working directory:" << process.workingDirectory(); + qCDebug(runnerLog) << "Environment:" << process.environment().toStringList(); + }; + const auto onProcessDone = [this, config, storage](const Process &process) { + TestStorage *testStorage = storage.activeStorage(); + QTC_ASSERT(testStorage, return); + if (process.result() == ProcessResult::StartFailed) { + reportResult(ResultType::MessageFatal, + Tr::tr("Failed to start test for project \"%1\".").arg(config->displayName()) + + processInformation(&process) + rcInfo(config)); + } + + if (process.exitStatus() == QProcess::CrashExit) { + if (testStorage->m_outputReader) + testStorage->m_outputReader->reportCrash(); + reportResult(ResultType::MessageFatal, + Tr::tr("Test for project \"%1\" crashed.").arg(config->displayName()) + + processInformation(&process) + rcInfo(config)); + } else if (testStorage->m_outputReader && !testStorage->m_outputReader->hadValidOutput()) { + reportResult(ResultType::MessageFatal, + Tr::tr("Test for project \"%1\" did not produce any expected output.") + .arg(config->displayName()) + processInformation(&process) + + rcInfo(config)); + } + if (testStorage->m_outputReader) { + const int disabled = testStorage->m_outputReader->disabledTests(); + if (disabled > 0) + emit hadDisabledTests(disabled); + if (testStorage->m_outputReader->hasSummary()) + emit reportSummary(testStorage->m_outputReader->id(), testStorage->m_outputReader->summary()); + + testStorage->m_outputReader->resetCommandlineColor(); + } + }; + const Group group { + finishAllAndDone, + Storage(storage), + onGroupSetup(onSetup), + ProcessTask(onProcessSetup, onProcessDone, onProcessDone) + }; + tasks.append(group); + } + + m_taskTree.reset(new TaskTree(tasks)); + connect(m_taskTree.get(), &TaskTree::done, this, &TestRunner::onFinished); + connect(m_taskTree.get(), &TaskTree::errorOccurred, this, &TestRunner::onFinished); + + auto progress = new TaskProgress(m_taskTree.get()); + progress->setDisplayName(tr("Running Tests")); + progress->setAutoStopOnCancel(false); + progress->setHalfLifeTimePerTask(10000); // 10 seconds + connect(progress, &TaskProgress::canceled, this, [this, progress] { + // progress was a child of task tree which is going to be deleted directly. Unwind properly. + progress->setParent(nullptr); + progress->deleteLater(); + cancelCurrent(UserCanceled); + }); + + if (TestSettings::instance()->popupOnStart()) AutotestPlugin::popupResultsPane(); - scheduleNext(); + + m_taskTree->start(); } static void processOutput(TestOutputReader *outputreader, const QString &msg, OutputFormat format) @@ -626,13 +569,8 @@ void TestRunner::debugTests() } } - // We need a fake QFuture for the results. TODO: replace with QtConcurrent::run - QFutureInterface *futureInterface - = new QFutureInterface(QFutureInterfaceBase::Running); - m_futureWatcher.setFuture(futureInterface->future()); - if (useOutputProcessor) { - TestOutputReader *outputreader = config->createOutputReader(*futureInterface, nullptr); + TestOutputReader *outputreader = config->createOutputReader(nullptr); connect(outputreader, &TestOutputReader::newResult, this, &TestRunner::testResultReady); outputreader->setId(inferior.command.executable().toString()); connect(outputreader, &TestOutputReader::newOutputLineAvailable, @@ -641,9 +579,7 @@ void TestRunner::debugTests() this, [outputreader](const QString &msg, OutputFormat format) { processOutput(outputreader, msg, format); }); - - connect(runControl, &RunControl::stopped, - outputreader, &QObject::deleteLater); + connect(runControl, &RunControl::stopped, outputreader, &QObject::deleteLater); } m_stopDebugConnect = connect(this, &TestRunner::requestStopTestRun, @@ -652,13 +588,13 @@ void TestRunner::debugTests() connect(runControl, &RunControl::stopped, this, &TestRunner::onFinished); m_finishDebugConnect = connect(runControl, &RunControl::finished, this, &TestRunner::onFinished); ProjectExplorerPlugin::startRunControl(runControl); - if (useOutputProcessor && AutotestPlugin::settings()->popupOnStart) + if (useOutputProcessor && TestSettings::instance()->popupOnStart()) AutotestPlugin::popupResultsPane(); } static bool executablesEmpty() { - Target *target = SessionManager::startupTarget(); + Target *target = ProjectManager::startupTarget(); const QList configs = target->runConfigurations(); QTC_ASSERT(!configs.isEmpty(), return false); if (auto execAspect = configs.first()->aspect()) @@ -671,7 +607,7 @@ void TestRunner::runOrDebugTests() if (!m_skipTargetsCheck) { if (executablesEmpty()) { m_skipTargetsCheck = true; - Target * target = SessionManager::startupTarget(); + Target *target = ProjectManager::startupTarget(); QTimer::singleShot(5000, this, [this, target = QPointer(target)] { if (target) { disconnect(target, &Target::buildSystemUpdated, @@ -706,8 +642,7 @@ void TestRunner::buildProject(Project *project) BuildManager *buildManager = BuildManager::instance(); m_buildConnect = connect(this, &TestRunner::requestStopTestRun, buildManager, &BuildManager::cancel); - connect(buildManager, &BuildManager::buildQueueFinished, - this, &TestRunner::buildFinished); + connect(buildManager, &BuildManager::buildQueueFinished, this, &TestRunner::buildFinished); BuildManager::buildProjectWithDependencies(project); if (!BuildManager::isBuilding()) buildFinished(false); @@ -717,37 +652,33 @@ void TestRunner::buildFinished(bool success) { disconnect(m_buildConnect); BuildManager *buildManager = BuildManager::instance(); - disconnect(buildManager, &BuildManager::buildQueueFinished, - this, &TestRunner::buildFinished); + disconnect(buildManager, &BuildManager::buildQueueFinished, this, &TestRunner::buildFinished); if (success) { - if (!m_canceled) - runOrDebugTests(); - else if (m_executingTests) - onFinished(); - } else { - reportResult(ResultType::MessageFatal, Tr::tr("Build failed. Canceling test run.")); - onFinished(); + runOrDebugTests(); + return; } + reportResult(ResultType::MessageFatal, Tr::tr("Build failed. Canceling test run.")); + onFinished(); } static RunAfterBuildMode runAfterBuild() { - Project *project = SessionManager::startupProject(); + Project *project = ProjectManager::startupProject(); if (!project) return RunAfterBuildMode::None; if (!project->namedSettings(Constants::SK_USE_GLOBAL).isValid()) - return AutotestPlugin::settings()->runAfterBuild; + return TestSettings::instance()->runAfterBuildMode(); TestProjectSettings *projectSettings = AutotestPlugin::projectSettings(project); - return projectSettings->useGlobalSettings() ? AutotestPlugin::settings()->runAfterBuild + return projectSettings->useGlobalSettings() ? TestSettings::instance()->runAfterBuildMode() : projectSettings->runAfterBuild(); } void TestRunner::onBuildQueueFinished(bool success) { - if (m_executingTests || !m_selectedTests.isEmpty()) // paranoia! + if (isTestRunning() || !m_selectedTests.isEmpty()) // paranoia! return; if (!success || m_runMode != TestRunMode::None) @@ -768,17 +699,15 @@ void TestRunner::onBuildQueueFinished(bool success) void TestRunner::onFinished() { - m_cancelTimer.stop(); - // if we've been canceled and we still have test configurations queued just throw them away - qDeleteAll(m_selectedTests); - m_selectedTests.clear(); - + if (m_taskTree) + m_taskTree.release()->deleteLater(); disconnect(m_stopDebugConnect); disconnect(m_finishDebugConnect); disconnect(m_targetConnect); - m_fakeFutureInterface = nullptr; + qDeleteAll(m_selectedTests); + m_selectedTests.clear(); + m_cancelTimer.stop(); m_runMode = TestRunMode::None; - m_executingTests = false; emit testRunFinished(); } @@ -855,7 +784,7 @@ void RunConfigurationSelectionDialog::populate() { m_rcCombo->addItem({}, QStringList{{}, {}, {}}); // empty default - if (auto project = SessionManager::startupProject()) { + if (auto project = ProjectManager::startupProject()) { if (auto target = project->activeTarget()) { for (RunConfiguration *rc : target->runConfigurations()) { auto runnable = rc->runnable(); diff --git a/src/plugins/autotest/testrunner.h b/src/plugins/autotest/testrunner.h index c96af56075f..25c65f89851 100644 --- a/src/plugins/autotest/testrunner.h +++ b/src/plugins/autotest/testrunner.h @@ -4,12 +4,11 @@ #pragma once #include "autotest_global.h" -#include "testresult.h" + +#include "autotestconstants.h" #include -#include #include -#include #include QT_BEGIN_NAMESPACE @@ -20,13 +19,14 @@ class QLabel; QT_END_NAMESPACE namespace ProjectExplorer { class Project; } -namespace Utils { class QtcProcess; } +namespace Tasking { class TaskTree; } namespace Autotest { -enum class TestRunMode; class ITestConfiguration; -class TestOutputReader; +class ITestTreeItem; +class TestResult; +enum class ResultType; namespace Internal { @@ -44,7 +44,7 @@ public: void runTests(TestRunMode mode, const QList &selectedTests); void runTest(TestRunMode mode, const ITestTreeItem *item); - bool isTestRunning() const { return m_executingTests; } + bool isTestRunning() const { return m_buildConnect || m_stopDebugConnect || m_taskTree.get(); } signals: void testRunStarted(); @@ -61,12 +61,7 @@ private: void onFinished(); int precheckTestConfigurations(); - bool currentConfigValid(); - void setUpProcessEnv(); - void scheduleNext(); void cancelCurrent(CancelReason reason); - void onProcessDone(); - void resetInternalPointers(); void runTestsHelper(); void debugTests(); @@ -75,14 +70,9 @@ private: bool postponeTestRunWithEmptyExecutable(ProjectExplorer::Project *project); void onBuildSystemUpdated(); - QFutureWatcher m_futureWatcher; - QFutureInterface *m_fakeFutureInterface = nullptr; + std::unique_ptr m_taskTree; + QList m_selectedTests; - bool m_executingTests = false; - bool m_canceled = false; - ITestConfiguration *m_currentConfig = nullptr; - Utils::QtcProcess *m_currentProcess = nullptr; - TestOutputReader *m_currentOutputReader = nullptr; TestRunMode m_runMode = TestRunMode::None; // temporarily used if building before running is necessary diff --git a/src/plugins/autotest/testsettings.cpp b/src/plugins/autotest/testsettings.cpp index 6de76d815fc..b4507a9d9ab 100644 --- a/src/plugins/autotest/testsettings.cpp +++ b/src/plugins/autotest/testsettings.cpp @@ -4,53 +4,110 @@ #include "testsettings.h" #include "autotestconstants.h" +#include "autotesttr.h" #include "testframeworkmanager.h" -#include - #include -namespace Autotest { -namespace Internal { +namespace Autotest::Internal { -static const char timeoutKey[] = "Timeout"; -static const char omitInternalKey[] = "OmitInternal"; -static const char omitRunConfigWarnKey[] = "OmitRCWarnings"; -static const char limitResultOutputKey[] = "LimitResultOutput"; -static const char limitResultDescriptionKey[] = "LimitResultDescription"; -static const char resultDescriptionMaxSizeKey[] = "ResultDescriptionMaxSize"; -static const char autoScrollKey[] = "AutoScrollResults"; -static const char processArgsKey[] = "ProcessArgs"; -static const char displayApplicationKey[] = "DisplayApp"; -static const char popupOnStartKey[] = "PopupOnStart"; -static const char popupOnFinishKey[] = "PopupOnFinish"; -static const char popupOnFailKey[] = "PopupOnFail"; -static const char runAfterBuildKey[] = "RunAfterBuild"; static const char groupSuffix[] = ".group"; constexpr int defaultTimeout = 60000; -TestSettings::TestSettings() - : timeout(defaultTimeout) +static TestSettings *s_instance; + +TestSettings *TestSettings::instance() { + return s_instance; +} + +TestSettings::TestSettings() +{ + s_instance = this; + + setSettingsGroup(Constants::SETTINGSGROUP); + + timeout.setSettingsKey("Timeout"); + timeout.setDefaultValue(defaultTimeout); + timeout.setRange(5000, 36'000'000); // 36 Mio ms = 36'000 s = 10 h + timeout.setSuffix(Tr::tr(" s")); // we show seconds, but store milliseconds + timeout.setDisplayScaleFactor(1000); + timeout.setToolTip(Tr::tr("Timeout used when executing test cases. This will apply " + "for each test case on its own, not the whole project.")); + + omitInternalMsg.setSettingsKey("OmitInternal"); + omitInternalMsg.setDefaultValue(true); + omitInternalMsg.setLabelText(Tr::tr("Omit internal messages")); + omitInternalMsg.setToolTip(Tr::tr("Hides internal messages by default. " + "You can still enable them by using the test results filter.")); + + omitRunConfigWarn.setSettingsKey("OmitRCWarnings"); + omitRunConfigWarn.setLabelText(Tr::tr("Omit run configuration warnings")); + omitRunConfigWarn.setToolTip(Tr::tr("Hides warnings related to a deduced run configuration.")); + + limitResultOutput.setSettingsKey("LimitResultOutput"); + limitResultOutput.setDefaultValue(true); + limitResultOutput.setLabelText(Tr::tr("Limit result output")); + limitResultOutput.setToolTip(Tr::tr("Limits result output to 100000 characters.")); + + limitResultDescription.setSettingsKey("LimitResultDescription"); + limitResultDescription.setLabelText(Tr::tr("Limit result description:")); + limitResultDescription.setToolTip( + Tr::tr("Limit number of lines shown in test result tooltip and description.")); + + resultDescriptionMaxSize.setSettingsKey("ResultDescriptionMaxSize"); + resultDescriptionMaxSize.setDefaultValue(10); + resultDescriptionMaxSize.setRange(1, 100000); + resultDescriptionMaxSize.setEnabler(&limitResultDescription); + + autoScroll.setSettingsKey("AutoScrollResults"); + autoScroll.setDefaultValue(true); + autoScroll.setLabelText(Tr::tr("Automatically scroll results")); + autoScroll.setToolTip(Tr::tr("Automatically scrolls down when new items are added " + "and scrollbar is at bottom.")); + + processArgs.setSettingsKey("ProcessArgs"); + processArgs.setLabelText(Tr::tr("Process arguments")); + processArgs.setToolTip( + Tr::tr("Allow passing arguments specified on the respective run configuration.\n" + "Warning: this is an experimental feature and might lead to failing to " + "execute the test executable.")); + + displayApplication.setSettingsKey("DisplayApp"); + displayApplication.setLabelText(Tr::tr("Group results by application")); + + popupOnStart.setSettingsKey("PopupOnStart"); + popupOnStart.setLabelText(Tr::tr("Open results when tests start")); + popupOnStart.setToolTip( + Tr::tr("Displays test results automatically when tests are started.")); + + popupOnFinish.setSettingsKey("PopupOnFinish"); + popupOnFinish.setDefaultValue(true); + popupOnFinish.setLabelText(Tr::tr("Open results when tests finish")); + popupOnFinish.setToolTip( + Tr::tr("Displays test results automatically when tests are finished.")); + + popupOnFail.setSettingsKey("PopupOnFail"); + popupOnFail.setLabelText(Tr::tr("Only for unsuccessful test runs")); + popupOnFail.setEnabler(&popupOnFinish); + popupOnFail.setToolTip(Tr::tr("Displays test results only if the test run contains " + "failed, fatal or unexpectedly passed tests.")); + + runAfterBuild.setSettingsKey("RunAfterBuild"); + runAfterBuild.setDisplayStyle(Utils::SelectionAspect::DisplayStyle::ComboBox); + runAfterBuild.setToolTip(Tr::tr("Runs chosen tests automatically if a build succeeded.")); + runAfterBuild.addOption(Tr::tr("None")); + runAfterBuild.addOption(Tr::tr("All")); + runAfterBuild.addOption(Tr::tr("Selected")); } void TestSettings::toSettings(QSettings *s) const { + AspectContainer::writeSettings(s); + s->beginGroup(Constants::SETTINGSGROUP); - s->setValue(timeoutKey, timeout); - s->setValue(omitInternalKey, omitInternalMssg); - s->setValue(omitRunConfigWarnKey, omitRunConfigWarn); - s->setValue(limitResultOutputKey, limitResultOutput); - s->setValue(limitResultDescriptionKey, limitResultDescription); - s->setValue(resultDescriptionMaxSizeKey, resultDescriptionMaxSize); - s->setValue(autoScrollKey, autoScroll); - s->setValue(processArgsKey, processArgs); - s->setValue(displayApplicationKey, displayApplication); - s->setValue(popupOnStartKey, popupOnStart); - s->setValue(popupOnFinishKey, popupOnFinish); - s->setValue(popupOnFailKey, popupOnFail); - s->setValue(runAfterBuildKey, int(runAfterBuild)); + // store frameworks and their current active and grouping state for (auto it = frameworks.cbegin(); it != frameworks.cend(); ++it) { const Utils::Id &id = it.key(); @@ -65,21 +122,10 @@ void TestSettings::toSettings(QSettings *s) const void TestSettings::fromSettings(QSettings *s) { + AspectContainer::readSettings(s); + s->beginGroup(Constants::SETTINGSGROUP); - timeout = s->value(timeoutKey, defaultTimeout).toInt(); - omitInternalMssg = s->value(omitInternalKey, true).toBool(); - omitRunConfigWarn = s->value(omitRunConfigWarnKey, false).toBool(); - limitResultOutput = s->value(limitResultOutputKey, true).toBool(); - limitResultDescription = s->value(limitResultDescriptionKey, false).toBool(); - resultDescriptionMaxSize = s->value(resultDescriptionMaxSizeKey, 10).toInt(); - autoScroll = s->value(autoScrollKey, true).toBool(); - processArgs = s->value(processArgsKey, false).toBool(); - displayApplication = s->value(displayApplicationKey, false).toBool(); - popupOnStart = s->value(popupOnStartKey, true).toBool(); - popupOnFinish = s->value(popupOnFinishKey, true).toBool(); - popupOnFail = s->value(popupOnFailKey, false).toBool(); - runAfterBuild = RunAfterBuildMode(s->value(runAfterBuildKey, - int(RunAfterBuildMode::None)).toInt()); + // try to get settings for registered frameworks const TestFrameworks ®istered = TestFrameworkManager::registeredFrameworks(); frameworks.clear(); @@ -102,5 +148,9 @@ void TestSettings::fromSettings(QSettings *s) s->endGroup(); } -} // namespace Internal -} // namespace Autotest +RunAfterBuildMode TestSettings::runAfterBuildMode() const +{ + return static_cast(runAfterBuild.value()); +} + +} // namespace Autotest::Internal diff --git a/src/plugins/autotest/testsettings.h b/src/plugins/autotest/testsettings.h index ecf04fabaac..1e33269bd4f 100644 --- a/src/plugins/autotest/testsettings.h +++ b/src/plugins/autotest/testsettings.h @@ -3,18 +3,9 @@ #pragma once -#include +#include -namespace Utils { -class Id; -} - -QT_BEGIN_NAMESPACE -class QSettings; -QT_END_NAMESPACE - -namespace Autotest { -namespace Internal { +namespace Autotest::Internal { enum class RunAfterBuildMode { @@ -23,29 +14,39 @@ enum class RunAfterBuildMode Selected }; -struct TestSettings +class NonAspectSettings { - TestSettings(); - void toSettings(QSettings *s) const; - void fromSettings(QSettings *s); - - int timeout; - bool omitInternalMssg = true; - bool omitRunConfigWarn = false; - bool limitResultOutput = true; - bool limitResultDescription = false; - int resultDescriptionMaxSize = 10; - bool autoScroll = true; - bool processArgs = false; - bool displayApplication = false; - bool popupOnStart = true; - bool popupOnFinish = true; - bool popupOnFail = false; - RunAfterBuildMode runAfterBuild = RunAfterBuildMode::None; +public: QHash frameworks; QHash frameworksGrouping; QHash tools; }; -} // namespace Internal -} // namespace Autotest +class TestSettings : public Utils::AspectContainer, public NonAspectSettings +{ +public: + TestSettings(); + + static TestSettings *instance(); + + void toSettings(QSettings *s) const; + void fromSettings(QSettings *s); + + Utils::IntegerAspect timeout{this}; + Utils::BoolAspect omitInternalMsg{this}; + Utils::BoolAspect omitRunConfigWarn{this}; + Utils::BoolAspect limitResultOutput{this}; + Utils::BoolAspect limitResultDescription{this}; + Utils::IntegerAspect resultDescriptionMaxSize{this}; + Utils::BoolAspect autoScroll{this}; + Utils::BoolAspect processArgs{this}; + Utils::BoolAspect displayApplication{this}; + Utils::BoolAspect popupOnStart{this}; + Utils::BoolAspect popupOnFinish{this}; + Utils::BoolAspect popupOnFail{this}; + Utils::SelectionAspect runAfterBuild{this}; + + RunAfterBuildMode runAfterBuildMode() const; +}; + +} // Autotest::Internal diff --git a/src/plugins/autotest/testsettingspage.cpp b/src/plugins/autotest/testsettingspage.cpp index 1cd6e2d1ae4..6c9850bc2ac 100644 --- a/src/plugins/autotest/testsettingspage.cpp +++ b/src/plugins/autotest/testsettingspage.cpp @@ -25,116 +25,33 @@ #include #include #include -#include #include -#include using namespace Utils; -namespace Autotest { -namespace Internal { +namespace Autotest::Internal { -class TestSettingsWidget : public QWidget +class TestSettingsWidget : public Core::IOptionsPageWidget { public: - explicit TestSettingsWidget(QWidget *parent = nullptr); - - void setSettings(const TestSettings &settings); - TestSettings settings() const; + TestSettingsWidget(); private: void populateFrameworksListWidget(const QHash &frameworks, const QHash &testTools); - void testSettings(TestSettings &settings) const; - void testToolsSettings(TestSettings &settings) const; + void testSettings(NonAspectSettings &settings) const; + void testToolsSettings(NonAspectSettings &settings) const; void onFrameworkItemChanged(); - QCheckBox *m_omitInternalMsgCB; - QCheckBox *m_omitRunConfigWarnCB; - QCheckBox *m_limitResultOutputCB; - QCheckBox *m_limitResultDescriptionCb; - QSpinBox *m_limitResultDescriptionSpinBox; - QCheckBox *m_openResultsOnStartCB; - QCheckBox *m_openResultsOnFinishCB; - QCheckBox *m_openResultsOnFailCB; - QCheckBox *m_autoScrollCB; - QCheckBox *m_displayAppCB; - QCheckBox *m_processArgsCB; - QComboBox *m_runAfterBuildCB; - QSpinBox *m_timeoutSpin; QTreeWidget *m_frameworkTreeWidget; InfoLabel *m_frameworksWarn; }; -TestSettingsWidget::TestSettingsWidget(QWidget *parent) - : QWidget(parent) +TestSettingsWidget::TestSettingsWidget() { - resize(586, 469); - - m_omitInternalMsgCB = new QCheckBox(Tr::tr("Omit internal messages")); - m_omitInternalMsgCB->setChecked(true); - m_omitInternalMsgCB->setToolTip(Tr::tr("Hides internal messages by default. " - "You can still enable them by using the test results filter.")); - - m_omitRunConfigWarnCB = new QCheckBox(Tr::tr("Omit run configuration warnings")); - m_omitRunConfigWarnCB->setToolTip(Tr::tr("Hides warnings related to a deduced run configuration.")); - - m_limitResultOutputCB = new QCheckBox(Tr::tr("Limit result output")); - m_limitResultOutputCB->setChecked(true); - m_limitResultOutputCB->setToolTip(Tr::tr("Limits result output to 100000 characters.")); - - m_limitResultDescriptionCb = new QCheckBox(Tr::tr("Limit result description:")); - m_limitResultDescriptionCb->setToolTip( - Tr::tr("Limit number of lines shown in test result tooltip and description.")); - - m_limitResultDescriptionSpinBox = new QSpinBox; - m_limitResultDescriptionSpinBox->setEnabled(false); - m_limitResultDescriptionSpinBox->setMinimum(1); - m_limitResultDescriptionSpinBox->setMaximum(1000000); - m_limitResultDescriptionSpinBox->setValue(10); - - m_openResultsOnStartCB = new QCheckBox(Tr::tr("Open results when tests start")); - m_openResultsOnStartCB->setToolTip( - Tr::tr("Displays test results automatically when tests are started.")); - - m_openResultsOnFinishCB = new QCheckBox(Tr::tr("Open results when tests finish")); - m_openResultsOnFinishCB->setChecked(true); - m_openResultsOnFinishCB->setToolTip( - Tr::tr("Displays test results automatically when tests are finished.")); - - m_openResultsOnFailCB = new QCheckBox(Tr::tr("Only for unsuccessful test runs")); - m_openResultsOnFailCB->setToolTip( - Tr::tr("Displays test results only if the test run contains failed, fatal or unexpectedly passed tests.")); - - m_autoScrollCB = new QCheckBox(Tr::tr("Automatically scroll results")); - m_autoScrollCB->setChecked(true); - m_autoScrollCB->setToolTip(Tr::tr("Automatically scrolls down when new items are added and scrollbar is at bottom.")); - - m_displayAppCB = new QCheckBox(Tr::tr("Group results by application")); - - m_processArgsCB = new QCheckBox(Tr::tr("Process arguments")); - m_processArgsCB->setToolTip( - Tr::tr("Allow passing arguments specified on the respective run configuration.\n" - "Warning: this is an experimental feature and might lead to failing to execute the test executable.")); - - m_runAfterBuildCB = new QComboBox; - m_runAfterBuildCB->setToolTip(Tr::tr("Runs chosen tests automatically if a build succeeded.")); - m_runAfterBuildCB->addItem(Tr::tr("None")); - m_runAfterBuildCB->addItem(Tr::tr("All")); - m_runAfterBuildCB->addItem(Tr::tr("Selected")); - auto timeoutLabel = new QLabel(Tr::tr("Timeout:")); timeoutLabel->setToolTip(Tr::tr("Timeout used when executing each test case.")); - m_timeoutSpin = new QSpinBox; - m_timeoutSpin->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - m_timeoutSpin->setRange(5, 36000); - m_timeoutSpin->setValue(60); - m_timeoutSpin->setSuffix(Tr::tr(" s")); - m_timeoutSpin->setToolTip( - Tr::tr("Timeout used when executing test cases. This will apply " - "for each test case on its own, not the whole project.")); - m_frameworkTreeWidget = new QTreeWidget; m_frameworkTreeWidget->setRootIsDecorated(false); m_frameworkTreeWidget->setHeaderHidden(false); @@ -162,21 +79,22 @@ TestSettingsWidget::TestSettingsWidget(QWidget *parent) onClicked([] { AutotestPlugin::clearChoiceCache(); }, this) }; + TestSettings &s = *TestSettings::instance(); Group generalGroup { title(Tr::tr("General")), Column { - m_omitInternalMsgCB, - m_omitRunConfigWarnCB, - m_limitResultOutputCB, - Row { m_limitResultDescriptionCb, m_limitResultDescriptionSpinBox, st }, - m_openResultsOnStartCB, - m_openResultsOnFinishCB, - Row { Space(20), m_openResultsOnFailCB }, - m_autoScrollCB, - m_displayAppCB, - m_processArgsCB, - Row { Tr::tr("Automatically run"), m_runAfterBuildCB, st }, - Row { timeoutLabel, m_timeoutSpin, st }, + s.omitInternalMsg, + s.omitRunConfigWarn, + s.limitResultOutput, + Row { s.limitResultDescription, s.resultDescriptionMaxSize, st }, + s.popupOnStart, + s.popupOnFinish, + Row { Space(20), s.popupOnFail }, + s.autoScroll, + s.displayApplication, + s.processArgs, + Row { Tr::tr("Automatically run"), s.runAfterBuild, st }, + Row { timeoutLabel, s.timeout, st }, Row { resetChoicesButton, st } } }; @@ -199,50 +117,38 @@ TestSettingsWidget::TestSettingsWidget(QWidget *parent) connect(m_frameworkTreeWidget, &QTreeWidget::itemChanged, this, &TestSettingsWidget::onFrameworkItemChanged); - connect(m_openResultsOnFinishCB, &QCheckBox::toggled, - m_openResultsOnFailCB, &QCheckBox::setEnabled); - connect(m_limitResultDescriptionCb, &QCheckBox::toggled, - m_limitResultDescriptionSpinBox, &QSpinBox::setEnabled); -} -void TestSettingsWidget::setSettings(const TestSettings &settings) -{ - m_timeoutSpin->setValue(settings.timeout / 1000); // we store milliseconds - m_omitInternalMsgCB->setChecked(settings.omitInternalMssg); - m_omitRunConfigWarnCB->setChecked(settings.omitRunConfigWarn); - m_limitResultOutputCB->setChecked(settings.limitResultOutput); - m_limitResultDescriptionCb->setChecked(settings.limitResultDescription); - m_limitResultDescriptionSpinBox->setEnabled(settings.limitResultDescription); - m_limitResultDescriptionSpinBox->setValue(settings.resultDescriptionMaxSize); - m_autoScrollCB->setChecked(settings.autoScroll); - m_processArgsCB->setChecked(settings.processArgs); - m_displayAppCB->setChecked(settings.displayApplication); - m_openResultsOnStartCB->setChecked(settings.popupOnStart); - m_openResultsOnFinishCB->setChecked(settings.popupOnFinish); - m_openResultsOnFailCB->setChecked(settings.popupOnFail); - m_runAfterBuildCB->setCurrentIndex(int(settings.runAfterBuild)); - populateFrameworksListWidget(settings.frameworks, settings.tools); -} + populateFrameworksListWidget(s.frameworks, s.tools); -TestSettings TestSettingsWidget::settings() const -{ - TestSettings result; - result.timeout = m_timeoutSpin->value() * 1000; // we display seconds - result.omitInternalMssg = m_omitInternalMsgCB->isChecked(); - result.omitRunConfigWarn = m_omitRunConfigWarnCB->isChecked(); - result.limitResultOutput = m_limitResultOutputCB->isChecked(); - result.limitResultDescription = m_limitResultDescriptionCb->isChecked(); - result.resultDescriptionMaxSize = m_limitResultDescriptionSpinBox->value(); - result.autoScroll = m_autoScrollCB->isChecked(); - result.processArgs = m_processArgsCB->isChecked(); - result.displayApplication = m_displayAppCB->isChecked(); - result.popupOnStart = m_openResultsOnStartCB->isChecked(); - result.popupOnFinish = m_openResultsOnFinishCB->isChecked(); - result.popupOnFail = m_openResultsOnFailCB->isChecked(); - result.runAfterBuild = RunAfterBuildMode(m_runAfterBuildCB->currentIndex()); - testSettings(result); - testToolsSettings(result); - return result; + setOnApply([this] { + TestSettings &s = *TestSettings::instance(); + + NonAspectSettings tmp; + testSettings(tmp); + testToolsSettings(tmp); + + const QList changedIds = Utils::filtered(tmp.frameworksGrouping.keys(), + [&tmp, &s](Utils::Id id) { + return tmp.frameworksGrouping[id] != s.frameworksGrouping[id]; + }); + + testSettings(s); + testToolsSettings(s); + s.toSettings(Core::ICore::settings()); + + for (ITestFramework *framework : TestFrameworkManager::registeredFrameworks()) { + framework->setActive(s.frameworks.value(framework->id(), false)); + framework->setGrouping(s.frameworksGrouping.value(framework->id(), false)); + } + + for (ITestTool *testTool : TestFrameworkManager::registeredTestTools()) + testTool->setActive(s.tools.value(testTool->id(), false)); + + TestTreeModel::instance()->synchronizeTestFrameworks(); + TestTreeModel::instance()->synchronizeTestTools(); + if (!changedIds.isEmpty()) + TestTreeModel::instance()->rebuild(changedIds); + }); } enum TestBaseInfo @@ -283,7 +189,7 @@ void TestSettingsWidget::populateFrameworksListWidget(const QHash &fra } } -void TestSettingsWidget::testSettings(TestSettings &settings) const +void TestSettingsWidget::testSettings(NonAspectSettings &settings) const { const QAbstractItemModel *model = m_frameworkTreeWidget->model(); QTC_ASSERT(model, return); @@ -298,7 +204,7 @@ void TestSettingsWidget::testSettings(TestSettings &settings) const } } -void TestSettingsWidget::testToolsSettings(TestSettings &settings) const +void TestSettingsWidget::testToolsSettings(NonAspectSettings &settings) const { const QAbstractItemModel *model = m_frameworkTreeWidget->model(); QTC_ASSERT(model, return); @@ -343,50 +249,16 @@ void TestSettingsWidget::onFrameworkItemChanged() || (mixed == (ITestBase::Framework | ITestBase::Tool))); } -TestSettingsPage::TestSettingsPage(TestSettings *settings) - : m_settings(settings) +// TestSettingsPage + +TestSettingsPage::TestSettingsPage() { setId(Constants::AUTOTEST_SETTINGS_ID); setDisplayName(Tr::tr("General")); setCategory(Constants::AUTOTEST_SETTINGS_CATEGORY); setDisplayCategory(Tr::tr("Testing")); setCategoryIconPath(":/autotest/images/settingscategory_autotest.png"); + setWidgetCreator([] { return new TestSettingsWidget; }); } -QWidget *TestSettingsPage::widget() -{ - if (!m_widget) { - m_widget = new TestSettingsWidget; - m_widget->setSettings(*m_settings); - } - return m_widget; -} - -void TestSettingsPage::apply() -{ - if (!m_widget) // page was not shown at all - return; - const TestSettings newSettings = m_widget->settings(); - const QList changedIds = Utils::filtered(newSettings.frameworksGrouping.keys(), - [newSettings, this](const Id &id) { - return newSettings.frameworksGrouping[id] != m_settings->frameworksGrouping[id]; - }); - *m_settings = newSettings; - m_settings->toSettings(Core::ICore::settings()); - - for (ITestFramework *framework : TestFrameworkManager::registeredFrameworks()) { - framework->setActive(m_settings->frameworks.value(framework->id(), false)); - framework->setGrouping(m_settings->frameworksGrouping.value(framework->id(), false)); - } - - for (ITestTool *testTool : TestFrameworkManager::registeredTestTools()) - testTool->setActive(m_settings->tools.value(testTool->id(), false)); - - TestTreeModel::instance()->synchronizeTestFrameworks(); - TestTreeModel::instance()->synchronizeTestTools(); - if (!changedIds.isEmpty()) - TestTreeModel::instance()->rebuild(changedIds); -} - -} // namespace Internal -} // namespace Autotest +} // Autotest::Internal diff --git a/src/plugins/autotest/testsettingspage.h b/src/plugins/autotest/testsettingspage.h index 064a9504c97..03eb214097d 100644 --- a/src/plugins/autotest/testsettingspage.h +++ b/src/plugins/autotest/testsettingspage.h @@ -5,28 +5,12 @@ #include -#include - -namespace Autotest { -namespace Internal { - -struct TestSettings; -class TestSettingsWidget; +namespace Autotest::Internal { class TestSettingsPage : public Core::IOptionsPage { - Q_OBJECT public: - explicit TestSettingsPage(TestSettings *settings); - - QWidget *widget() override; - void apply() override; - void finish() override { } - -private: - TestSettings *m_settings; - QPointer m_widget; + TestSettingsPage(); }; -} // namespace Internal -} // namespace Autotest +} // Autotest::Internal diff --git a/src/plugins/autotest/testtreeitem.cpp b/src/plugins/autotest/testtreeitem.cpp index 884544e3b24..f18adb812d4 100644 --- a/src/plugins/autotest/testtreeitem.cpp +++ b/src/plugins/autotest/testtreeitem.cpp @@ -224,11 +224,11 @@ void TestTreeItem::markForRemovalRecursively(bool mark) childItem(row)->markForRemovalRecursively(mark); } -void TestTreeItem::markForRemovalRecursively(const FilePath &filepath) +void TestTreeItem::markForRemovalRecursively(const QSet &filePaths) { - bool mark = filePath() == filepath; - forFirstLevelChildItems([&mark, &filepath](TestTreeItem *child) { - child->markForRemovalRecursively(filepath); + bool mark = filePaths.contains(filePath()); + forFirstLevelChildItems([&mark, &filePaths](TestTreeItem *child) { + child->markForRemovalRecursively(filePaths); mark &= child->markedForRemoval(); }); markForRemoval(mark); diff --git a/src/plugins/autotest/testtreeitem.h b/src/plugins/autotest/testtreeitem.h index e51f48b211c..3737d2a3607 100644 --- a/src/plugins/autotest/testtreeitem.h +++ b/src/plugins/autotest/testtreeitem.h @@ -115,7 +115,7 @@ public: void setProFile(const Utils::FilePath &proFile) { m_proFile = proFile; } void markForRemoval(bool mark); void markForRemovalRecursively(bool mark); - virtual void markForRemovalRecursively(const Utils::FilePath &filepath); + virtual void markForRemovalRecursively(const QSet &filePaths); virtual bool removeOnSweepIfEmpty() const { return type() == GroupNode; } bool markedForRemoval() const { return m_status == MarkedForRemoval; } bool newlyAdded() const { return m_status == NewlyAdded; } diff --git a/src/plugins/autotest/testtreemodel.cpp b/src/plugins/autotest/testtreemodel.cpp index 8842734f0c5..5e7fcb85a86 100644 --- a/src/plugins/autotest/testtreemodel.cpp +++ b/src/plugins/autotest/testtreemodel.cpp @@ -10,12 +10,16 @@ #include "testprojectsettings.h" #include + #include #include -#include +#include #include + #include + #include + #include #include #include @@ -38,7 +42,7 @@ TestTreeModel::TestTreeModel(TestCodeParser *parser) : connect(m_parser, &TestCodeParser::aboutToPerformFullParse, this, &TestTreeModel::removeAllTestItems, Qt::QueuedConnection); connect(m_parser, &TestCodeParser::testParseResultReady, - this, &TestTreeModel::onParseResultReady, Qt::QueuedConnection); + this, &TestTreeModel::onParseResultReady); connect(m_parser, &TestCodeParser::parsingFinished, this, &TestTreeModel::sweep, Qt::QueuedConnection); connect(m_parser, &TestCodeParser::parsingFailed, @@ -70,8 +74,8 @@ void TestTreeModel::setupParsingConnections() m_parser->setDirty(); m_parser->setState(TestCodeParser::Idle); - SessionManager *sm = SessionManager::instance(); - connect(sm, &SessionManager::startupProjectChanged, this, [this, sm](Project *project) { + ProjectManager *sm = ProjectManager::instance(); + connect(sm, &ProjectManager::startupProjectChanged, this, [this, sm](Project *project) { synchronizeTestFrameworks(); // we might have project settings m_parser->onStartupProjectChanged(project); removeAllTestToolItems(); @@ -96,8 +100,8 @@ void TestTreeModel::setupParsingConnections() m_parser, &TestCodeParser::onCppDocumentUpdated, Qt::QueuedConnection); connect(cppMM, &CppEditor::CppModelManager::aboutToRemoveFiles, this, [this](const QStringList &files) { - const FilePaths filesToRemove = FileUtils::toFilePathList(files); - removeFiles(filesToRemove); + markForRemoval(transform(files, &FilePath::fromString)); + sweep(); }, Qt::QueuedConnection); connect(cppMM, &CppEditor::CppModelManager::projectPartsUpdated, m_parser, &TestCodeParser::onProjectPartsUpdated); @@ -105,11 +109,11 @@ void TestTreeModel::setupParsingConnections() QmlJS::ModelManagerInterface *qmlJsMM = QmlJS::ModelManagerInterface::instance(); connect(qmlJsMM, &QmlJS::ModelManagerInterface::documentUpdated, m_parser, &TestCodeParser::onQmlDocumentUpdated, Qt::QueuedConnection); - connect(qmlJsMM, - &QmlJS::ModelManagerInterface::aboutToRemoveFiles, - this, - &TestTreeModel::removeFiles, - Qt::QueuedConnection); + connect(qmlJsMM, &QmlJS::ModelManagerInterface::aboutToRemoveFiles, + this, [this](const FilePaths &filePaths) { + markForRemoval(Utils::toSet(filePaths)); + sweep(); + }, Qt::QueuedConnection); connectionsInitialized = true; } @@ -226,7 +230,7 @@ static QList testItemsByName(TestTreeItem *root, const QString void TestTreeModel::onTargetChanged(Target *target) { if (target && target->buildSystem()) { - const Target *topLevelTarget = SessionManager::startupProject()->targets().first(); + const Target *topLevelTarget = ProjectManager::startupProject()->targets().first(); connect(topLevelTarget->buildSystem(), &BuildSystem::testInformationUpdated, this, &TestTreeModel::onBuildSystemTestsUpdated, Qt::UniqueConnection); disconnect(target->project(), &Project::activeTargetChanged, @@ -236,7 +240,7 @@ void TestTreeModel::onTargetChanged(Target *target) void TestTreeModel::onBuildSystemTestsUpdated() { - const BuildSystem *bs = SessionManager::startupBuildSystem(); + const BuildSystem *bs = ProjectManager::startupBuildSystem(); if (!bs || !bs->project()) return; @@ -333,7 +337,7 @@ void TestTreeModel::synchronizeTestFrameworks() void TestTreeModel::synchronizeTestTools() { - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); TestTools tools; if (!project || AutotestPlugin::projectSettings(project)->useGlobalSettings()) { tools = Utils::filtered(TestFrameworkManager::registeredTestTools(), @@ -459,13 +463,6 @@ void TestTreeModel::clearFailedMarks() m_failedStateCache.clear(); } -void TestTreeModel::removeFiles(const FilePaths &files) -{ - for (const FilePath &file : files) - markForRemoval(file); - sweep(); -} - void TestTreeModel::markAllFrameworkItemsForRemoval() { for (TestTreeItem *frameworkRoot : frameworkRootNodes()) { @@ -475,15 +472,12 @@ void TestTreeModel::markAllFrameworkItemsForRemoval() } } -void TestTreeModel::markForRemoval(const FilePath &filePath) +void TestTreeModel::markForRemoval(const QSet &filePaths) { - if (filePath.isEmpty()) - return; - for (TestTreeItem *frameworkRoot : frameworkRootNodes()) { for (int childRow = frameworkRoot->childCount() - 1; childRow >= 0; --childRow) { TestTreeItem *child = frameworkRoot->childItem(childRow); - child->markForRemovalRecursively(filePath); + child->markForRemovalRecursively(filePaths); } } } diff --git a/src/plugins/autotest/testtreemodel.h b/src/plugins/autotest/testtreemodel.h index b8104e8b433..c48c957d999 100644 --- a/src/plugins/autotest/testtreemodel.h +++ b/src/plugins/autotest/testtreemodel.h @@ -66,7 +66,7 @@ public: #endif void markAllFrameworkItemsForRemoval(); - void markForRemoval(const Utils::FilePath &filePath); + void markForRemoval(const QSet &filePaths); void sweep(); signals: @@ -83,7 +83,6 @@ private: void handleParseResult(const TestParseResult *result, TestTreeItem *rootNode); void removeAllTestItems(); void removeAllTestToolItems(); - void removeFiles(const Utils::FilePaths &files); bool sweepChildren(TestTreeItem *item); void insertItemInParent(TestTreeItem *item, TestTreeItem *root, bool groupingEnabled); void revalidateCheckState(ITestTreeItem *item); diff --git a/src/plugins/autotoolsprojectmanager/autogenstep.cpp b/src/plugins/autotoolsprojectmanager/autogenstep.cpp index c0a383205d0..090389716de 100644 --- a/src/plugins/autotoolsprojectmanager/autogenstep.cpp +++ b/src/plugins/autotoolsprojectmanager/autogenstep.cpp @@ -42,23 +42,23 @@ private: void doRun() final; bool m_runAutogen = false; + StringAspect m_arguments{this}; }; AutogenStep::AutogenStep(BuildStepList *bsl, Id id) : AbstractProcessStep(bsl, id) { - auto arguments = addAspect(); - arguments->setSettingsKey("AutotoolsProjectManager.AutogenStep.AdditionalArguments"); - arguments->setLabelText(Tr::tr("Arguments:")); - arguments->setDisplayStyle(StringAspect::LineEditDisplay); - arguments->setHistoryCompleter("AutotoolsPM.History.AutogenStepArgs"); + m_arguments.setSettingsKey("AutotoolsProjectManager.AutogenStep.AdditionalArguments"); + m_arguments.setLabelText(Tr::tr("Arguments:")); + m_arguments.setDisplayStyle(StringAspect::LineEditDisplay); + m_arguments.setHistoryCompleter("AutotoolsPM.History.AutogenStepArgs"); - connect(arguments, &BaseAspect::changed, this, [this] { m_runAutogen = true; }); + connect(&m_arguments, &BaseAspect::changed, this, [this] { m_runAutogen = true; }); setWorkingDirectoryProvider([this] { return project()->projectDirectory(); }); - setCommandLineProvider([this, arguments] { + setCommandLineProvider([this] { return CommandLine(project()->projectDirectory() / "autogen.sh", - arguments->value(), + m_arguments(), CommandLine::Raw); }); diff --git a/src/plugins/autotoolsprojectmanager/autotoolsbuildsystem.cpp b/src/plugins/autotoolsprojectmanager/autotoolsbuildsystem.cpp index 0375ce9ac61..113212be5fb 100644 --- a/src/plugins/autotoolsprojectmanager/autotoolsbuildsystem.cpp +++ b/src/plugins/autotoolsprojectmanager/autotoolsbuildsystem.cpp @@ -14,6 +14,7 @@ #include using namespace ProjectExplorer; +using namespace Utils; namespace AutotoolsProjectManager::Internal { @@ -140,7 +141,7 @@ void AutotoolsBuildSystem::updateCppCodeModel() if (cxxflags.isEmpty()) cxxflags = cflags; - const QString includeFileBaseDir = projectDirectory().toString(); + const FilePath includeFileBaseDir = projectDirectory(); rpp.setFlagsForC({kitInfo.cToolChain, cflags, includeFileBaseDir}); rpp.setFlagsForCxx({kitInfo.cxxToolChain, cxxflags, includeFileBaseDir}); diff --git a/src/plugins/autotoolsprojectmanager/makefileparser.cpp b/src/plugins/autotoolsprojectmanager/makefileparser.cpp index 09fbd666479..e1cac6b1866 100644 --- a/src/plugins/autotoolsprojectmanager/makefileparser.cpp +++ b/src/plugins/autotoolsprojectmanager/makefileparser.cpp @@ -5,14 +5,16 @@ #include "autotoolsprojectmanagertr.h" +#include #include -#include #include #include #include #include +using namespace Utils; + namespace AutotoolsProjectManager::Internal { MakefileParser::MakefileParser(const QString &makefile) : m_makefile(makefile) @@ -423,7 +425,7 @@ QStringList MakefileParser::parseTermsAfterAssign(const QString &line) if (assignPos <= 0 || assignPos >= line.size()) return QStringList(); - const QStringList parts = Utils::ProcessArgs::splitArgs(line.mid(assignPos)); + const QStringList parts = ProcessArgs::splitArgs(line.mid(assignPos), HostOsInfo::hostOs()); QStringList result; for (int i = 0; i < parts.count(); ++i) { const QString cur = parts.at(i); diff --git a/src/plugins/axivion/Axivion.json.in b/src/plugins/axivion/Axivion.json.in new file mode 100644 index 00000000000..fac0520565f --- /dev/null +++ b/src/plugins/axivion/Axivion.json.in @@ -0,0 +1,21 @@ +{ + \"Name\" : \"Axivion\", + \"Version\" : \"$$QTCREATOR_VERSION\", + \"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\", + \"Revision\" : \"$$QTC_PLUGIN_REVISION\", + \"Experimental\" : true, + \"Vendor\" : \"The Qt Company Ltd\", + \"Copyright\" : \"(C) $$QTCREATOR_COPYRIGHT_YEAR The Qt Company Ltd\", + \"License\" : [ \"Commercial Usage\", + \"\", + \"Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt 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.\", + \"\", + \"GNU General Public License Usage\", + \"\", + \"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html.\" + ], + \"Category\" : \"Code Analyzer\", + \"Description\" : \"Integration of the axivion dashboard.\", + \"Url\" : \"https://www.qt.io\", + $$dependencyList +} diff --git a/src/plugins/axivion/CMakeLists.txt b/src/plugins/axivion/CMakeLists.txt new file mode 100644 index 00000000000..b346cc276dc --- /dev/null +++ b/src/plugins/axivion/CMakeLists.txt @@ -0,0 +1,15 @@ +add_qtc_plugin(Axivion + PLUGIN_DEPENDS + Core ProjectExplorer TextEditor + DEPENDS Qt::Network Qt::Widgets ExtensionSystem Utils + SOURCES + axivion.qrc + axivionoutputpane.cpp axivionoutputpane.h + axivionplugin.cpp axivionplugin.h + axivionprojectsettings.h axivionprojectsettings.cpp + axivionquery.h axivionquery.cpp + axivionresultparser.h axivionresultparser.cpp + axivionsettings.cpp axivionsettings.h + axivionsettingspage.cpp axivionsettingspage.h + axiviontr.h +) diff --git a/src/plugins/axivion/axivion.qbs b/src/plugins/axivion/axivion.qbs new file mode 100644 index 00000000000..bfe8efe6cf5 --- /dev/null +++ b/src/plugins/axivion/axivion.qbs @@ -0,0 +1,32 @@ +import qbs + +QtcPlugin { + name: "Axivion" + + Depends { name: "Core" } + Depends { name: "ProjectExplorer" } + Depends { name: "TextEditor" } + Depends { name: "ExtensionSystem" } + Depends { name: "Utils" } + Depends { name: "Qt.widgets" } + Depends { name: "Qt.network" } + + files: [ + "axivion.qrc", + "axivionoutputpane.cpp", + "axivionoutputpane.h", + "axivionplugin.cpp", + "axivionplugin.h", + "axivionprojectsettings.h", + "axivionprojectsettings.cpp", + "axivionquery.h", + "axivionquery.cpp", + "axivionresultparser.h", + "axivionresultparser.cpp", + "axivionsettings.cpp", + "axivionsettings.h", + "axivionsettingspage.cpp", + "axivionsettingspage.h", + "axiviontr.h", + ] +} diff --git a/src/plugins/axivion/axivion.qrc b/src/plugins/axivion/axivion.qrc new file mode 100644 index 00000000000..13d0b616c91 --- /dev/null +++ b/src/plugins/axivion/axivion.qrc @@ -0,0 +1,6 @@ + + + images/axivion.png + images/axivion@2x.png + + diff --git a/src/plugins/axivion/axivionoutputpane.cpp b/src/plugins/axivion/axivionoutputpane.cpp new file mode 100644 index 00000000000..ba226f63b5b --- /dev/null +++ b/src/plugins/axivion/axivionoutputpane.cpp @@ -0,0 +1,209 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "axivionoutputpane.h" + +#include "axivionplugin.h" +#include "axivionresultparser.h" +#include "axiviontr.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace Axivion::Internal { + +class DashboardWidget : public QScrollArea +{ +public: + explicit DashboardWidget(QWidget *parent = nullptr); + void updateUi(); + bool hasProject() const { return !m_project->text().isEmpty(); } +private: + QLabel *m_project = nullptr; + QLabel *m_loc = nullptr; + QFormLayout *m_formLayout = nullptr; +}; + +DashboardWidget::DashboardWidget(QWidget *parent) + : QScrollArea(parent) +{ + QWidget *widget = new QWidget(this); + QVBoxLayout *layout = new QVBoxLayout(widget); + QFormLayout *projectLayout = new QFormLayout; + m_project = new QLabel(this); + projectLayout->addRow(Tr::tr("Project:"), m_project); + m_loc = new QLabel(this); + projectLayout->addRow(Tr::tr("Lines of Code:"), m_loc); + layout->addLayout(projectLayout); + m_formLayout = new QFormLayout; + layout->addLayout(m_formLayout); + setWidget(widget); + setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + setWidgetResizable(true); +} + +void DashboardWidget::updateUi() +{ + const ProjectInfo &info = AxivionPlugin::projectInfo(); + m_project->setText(info.name); + m_loc->setText({}); + while (m_formLayout->rowCount()) + m_formLayout->removeRow(0); + + if (info.versions.isEmpty()) + return; + + const ResultVersion &last = info.versions.last(); + m_loc->setText(QString::number(last.linesOfCode)); + + const QString tmpl("%1 %2 +%3 / -%4"); + auto apply = [&tmpl](int t, int a, int r){ + QChar tr = (a == r ? '=' : (a < r ? '^' : 'v')); + return tmpl.arg(t, 10, 10, QLatin1Char(' ')).arg(tr).arg(a, 5, 10, QLatin1Char(' ')) + .arg(r, 5, 10, QLatin1Char(' ')); + }; + const QList &issueKinds = info.issueKinds; + auto toolTip = [issueKinds](const QString &prefix){ + for (const IssueKind &kind : issueKinds) { + if (kind.prefix == prefix) + return kind.nicePlural; + } + return QString(); + }; + int allTotal = 0, allAdded = 0, allRemoved = 0; + for (auto issueCount : std::as_const(last.issueCounts)) { + allTotal += issueCount.total; + allAdded += issueCount.added; + allRemoved += issueCount.removed; + const QString txt = apply(issueCount.total, issueCount.added, issueCount.removed); + const QString currentToolTip = toolTip(issueCount.issueKind); + QLabel *label = new QLabel(issueCount.issueKind, this); + label->setToolTip(currentToolTip); + QLabel *values = new QLabel(txt, this); + values->setToolTip(currentToolTip); + m_formLayout->addRow(label, values); + } + + QLabel *label = new QLabel(apply(allTotal, allAdded, allRemoved), this); + m_formLayout->addRow(Tr::tr("Total:"), label); +} + +AxivionOutputPane::AxivionOutputPane(QObject *parent) + : Core::IOutputPane(parent) +{ + m_outputWidget = new QStackedWidget; + DashboardWidget *dashboardWidget = new DashboardWidget(m_outputWidget); + m_outputWidget->addWidget(dashboardWidget); + QTextBrowser *browser = new QTextBrowser(m_outputWidget); + m_outputWidget->addWidget(browser); +} + +AxivionOutputPane::~AxivionOutputPane() +{ + if (!m_outputWidget->parent()) + delete m_outputWidget; +} + +QWidget *AxivionOutputPane::outputWidget(QWidget *parent) +{ + if (m_outputWidget) + m_outputWidget->setParent(parent); + else + QTC_CHECK(false); + return m_outputWidget; +} + +QList AxivionOutputPane::toolBarWidgets() const +{ + QList buttons; + auto showDashboard = new QToolButton(m_outputWidget); + showDashboard->setIcon(Utils::Icons::ONLINE_TOOLBAR.icon()); + showDashboard->setToolTip(Tr::tr("Show dashboard")); + connect(showDashboard, &QToolButton::clicked, this, [this]{ + QTC_ASSERT(m_outputWidget, return); + m_outputWidget->setCurrentIndex(0); + }); + buttons.append(showDashboard); + return buttons; +} + +QString AxivionOutputPane::displayName() const +{ + return Tr::tr("Axivion"); +} + +int AxivionOutputPane::priorityInStatusBar() const +{ + return -1; +} + +void AxivionOutputPane::clearContents() +{ +} + +void AxivionOutputPane::setFocus() +{ +} + +bool AxivionOutputPane::hasFocus() const +{ + return false; +} + +bool AxivionOutputPane::canFocus() const +{ + return true; +} + +bool AxivionOutputPane::canNavigate() const +{ + return true; +} + +bool AxivionOutputPane::canNext() const +{ + return false; +} + +bool AxivionOutputPane::canPrevious() const +{ + return false; +} + +void AxivionOutputPane::goToNext() +{ +} + +void AxivionOutputPane::goToPrev() +{ +} + +void AxivionOutputPane::updateDashboard() +{ + if (auto dashboard = static_cast(m_outputWidget->widget(0))) { + dashboard->updateUi(); + m_outputWidget->setCurrentIndex(0); + if (dashboard->hasProject()) + flash(); + } +} + +void AxivionOutputPane::updateAndShowRule(const QString &ruleHtml) +{ + if (auto browser = static_cast(m_outputWidget->widget(1))) { + browser->setText(ruleHtml); + if (!ruleHtml.isEmpty()) { + m_outputWidget->setCurrentIndex(1); + popup(Core::IOutputPane::NoModeSwitch); + } + } +} + +} // Axivion::Internal diff --git a/src/plugins/axivion/axivionoutputpane.h b/src/plugins/axivion/axivionoutputpane.h new file mode 100644 index 00000000000..d11accc1401 --- /dev/null +++ b/src/plugins/axivion/axivionoutputpane.h @@ -0,0 +1,42 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +QT_BEGIN_NAMESPACE +class QStackedWidget; +QT_END_NAMESPACE + +namespace Axivion::Internal { + +class AxivionOutputPane : public Core::IOutputPane +{ + Q_OBJECT +public: + explicit AxivionOutputPane(QObject *parent = nullptr); + ~AxivionOutputPane(); + + // IOutputPane interface + QWidget *outputWidget(QWidget *parent) override; + QList toolBarWidgets() const override; + QString displayName() const override; + int priorityInStatusBar() const override; + void clearContents() override; + void setFocus() override; + bool hasFocus() const override; + bool canFocus() const override; + bool canNavigate() const override; + bool canNext() const override; + bool canPrevious() const override; + void goToNext() override; + void goToPrev() override; + + void updateDashboard(); + void updateAndShowRule(const QString &ruleHtml); +private: + QStackedWidget *m_outputWidget = nullptr; +}; + +} // Axivion::Internal diff --git a/src/plugins/axivion/axivionplugin.cpp b/src/plugins/axivion/axivionplugin.cpp new file mode 100644 index 00000000000..7648448132a --- /dev/null +++ b/src/plugins/axivion/axivionplugin.cpp @@ -0,0 +1,348 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "axivionplugin.h" + +#include "axivionoutputpane.h" +#include "axivionprojectsettings.h" +#include "axivionquery.h" +#include "axivionresultparser.h" +#include "axivionsettings.h" +#include "axivionsettingspage.h" +#include "axiviontr.h" + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +constexpr char AxivionTextMarkId[] = "AxivionTextMark"; + +namespace Axivion::Internal { + +class AxivionPluginPrivate : public QObject +{ +public: + AxivionProjectSettings *projectSettings(ProjectExplorer::Project *project); + void onStartupProjectChanged(); + void fetchProjectInfo(const QString &projectName); + void handleProjectInfo(const ProjectInfo &info); + void handleOpenedDocs(ProjectExplorer::Project *project); + void onDocumentOpened(Core::IDocument *doc); + void onDocumentClosed(Core::IDocument * doc); + void clearAllMarks(); + void handleIssuesForFile(const IssuesList &issues); + void fetchRuleInfo(const QString &id); + + AxivionSettings m_axivionSettings; + AxivionSettingsPage m_axivionSettingsPage{&m_axivionSettings}; + AxivionOutputPane m_axivionOutputPane; + QHash m_axivionProjectSettings; + ProjectInfo m_currentProjectInfo; + bool m_runningQuery = false; +}; + +static AxivionPlugin *s_instance = nullptr; +static AxivionPluginPrivate *dd = nullptr; + +class AxivionTextMark : public TextEditor::TextMark +{ +public: + AxivionTextMark(const Utils::FilePath &filePath, const ShortIssue &issue); + +private: + QString m_id; +}; + +AxivionTextMark::AxivionTextMark(const Utils::FilePath &filePath, const ShortIssue &issue) + : TextEditor::TextMark(filePath, issue.lineNumber, {Tr::tr("Axivion"), AxivionTextMarkId}) + , m_id(issue.id) +{ + const QString markText = issue.entity.isEmpty() ? issue.message + : issue.entity + ": " + issue.message; + setToolTip(issue.errorNumber + " " + markText); + setPriority(TextEditor::TextMark::NormalPriority); + setLineAnnotation(markText); + setActionsProvider([this]{ + auto action = new QAction; + action->setIcon(Utils::Icons::INFO.icon()); + action->setToolTip(Tr::tr("Show rule details")); + QObject::connect(action, &QAction::triggered, + dd, [this]{ dd->fetchRuleInfo(m_id); }); + return QList{action}; + }); +} + +AxivionPlugin::AxivionPlugin() +{ + s_instance = this; +} + +AxivionPlugin::~AxivionPlugin() +{ + if (dd && !dd->m_axivionProjectSettings.isEmpty()) { + qDeleteAll(dd->m_axivionProjectSettings); + dd->m_axivionProjectSettings.clear(); + } + delete dd; + dd = nullptr; +} + +AxivionPlugin *AxivionPlugin::instance() +{ + return s_instance; +} + +bool AxivionPlugin::initialize(const QStringList &arguments, QString *errorMessage) +{ + Q_UNUSED(arguments) + Q_UNUSED(errorMessage) + + dd = new AxivionPluginPrivate; + dd->m_axivionSettings.fromSettings(Core::ICore::settings()); + + auto panelFactory = new ProjectExplorer::ProjectPanelFactory; + panelFactory->setPriority(250); + panelFactory->setDisplayName(Tr::tr("Axivion")); + panelFactory->setCreateWidgetFunction([](ProjectExplorer::Project *project){ + return new AxivionProjectSettingsWidget(project); + }); + ProjectExplorer::ProjectPanelFactory::registerFactory(panelFactory); + connect(ProjectExplorer::ProjectManager::instance(), + &ProjectExplorer::ProjectManager::startupProjectChanged, + dd, &AxivionPluginPrivate::onStartupProjectChanged); + connect(Core::EditorManager::instance(), &Core::EditorManager::documentOpened, + dd, &AxivionPluginPrivate::onDocumentOpened); + connect(Core::EditorManager::instance(), &Core::EditorManager::documentClosed, + dd, &AxivionPluginPrivate::onDocumentClosed); + return true; +} + +AxivionSettings *AxivionPlugin::settings() +{ + QTC_ASSERT(dd, return nullptr); + return &dd->m_axivionSettings; +} + +AxivionProjectSettings *AxivionPlugin::projectSettings(ProjectExplorer::Project *project) +{ + QTC_ASSERT(project, return nullptr); + QTC_ASSERT(dd, return nullptr); + + return dd->projectSettings(project); +} + +bool AxivionPlugin::handleCertificateIssue() +{ + QTC_ASSERT(dd, return false); + + const QString serverHost = QUrl(dd->m_axivionSettings.server.dashboard).host(); + if (QMessageBox::question(Core::ICore::dialogParent(), Tr::tr("Certificate Error"), + Tr::tr("Server certificate for %1 cannot be authenticated.\n" + "Do you want to disable SSL verification for this server?\n" + "Note: This can expose you to man-in-the-middle attack.") + .arg(serverHost)) + != QMessageBox::Yes) { + return false; + } + dd->m_axivionSettings.server.validateCert = false; + emit s_instance->settingsChanged(); + return true; +} + +void AxivionPlugin::fetchProjectInfo(const QString &projectName) +{ + QTC_ASSERT(dd, return); + dd->fetchProjectInfo(projectName); +} + +ProjectInfo AxivionPlugin::projectInfo() +{ + QTC_ASSERT(dd, return {}); + return dd->m_currentProjectInfo; +} + +AxivionProjectSettings *AxivionPluginPrivate::projectSettings(ProjectExplorer::Project *project) +{ + auto &settings = m_axivionProjectSettings[project]; + if (!settings) + settings = new AxivionProjectSettings(project); + return settings; +} + +void AxivionPluginPrivate::onStartupProjectChanged() +{ + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); + if (!project) { + clearAllMarks(); + m_currentProjectInfo = ProjectInfo(); + m_axivionOutputPane.updateDashboard(); + return; + } + + const AxivionProjectSettings *projSettings = projectSettings(project); + fetchProjectInfo(projSettings->dashboardProjectName()); +} + +void AxivionPluginPrivate::fetchProjectInfo(const QString &projectName) +{ + if (m_runningQuery) { // re-schedule + QTimer::singleShot(3000, [this, projectName]{ fetchProjectInfo(projectName); }); + return; + } + clearAllMarks(); + if (projectName.isEmpty()) { + m_currentProjectInfo = ProjectInfo(); + m_axivionOutputPane.updateDashboard(); + return; + } + m_runningQuery = true; + + AxivionQuery query(AxivionQuery::ProjectInfo, {projectName}); + AxivionQueryRunner *runner = new AxivionQueryRunner(query, this); + connect(runner, &AxivionQueryRunner::resultRetrieved, this, [this](const QByteArray &result){ + handleProjectInfo(ResultParser::parseProjectInfo(result)); + }); + connect(runner, &AxivionQueryRunner::finished, [runner]{ runner->deleteLater(); }); + runner->start(); +} + +void AxivionPluginPrivate::fetchRuleInfo(const QString &id) +{ + if (m_runningQuery) { + QTimer::singleShot(3000, [this, id]{ fetchRuleInfo(id); }); + return; + } + + const QStringList args = id.split(':'); + QTC_ASSERT(args.size() == 2, return); + m_runningQuery = true; + AxivionQuery query(AxivionQuery::RuleInfo, args); + AxivionQueryRunner *runner = new AxivionQueryRunner(query, this); + connect(runner, &AxivionQueryRunner::resultRetrieved, this, [this](const QByteArray &result){ + m_runningQuery = false; + m_axivionOutputPane.updateAndShowRule(ResultParser::parseRuleInfo(result)); + }); + connect(runner, &AxivionQueryRunner::finished, [runner]{ runner->deleteLater(); }); + runner->start(); +} + +void AxivionPluginPrivate::handleOpenedDocs(ProjectExplorer::Project *project) +{ + if (project && ProjectExplorer::ProjectManager::startupProject() != project) + return; + const QList openDocuments = Core::DocumentModel::openedDocuments(); + for (Core::IDocument *doc : openDocuments) + onDocumentOpened(doc); + if (project) + disconnect(ProjectExplorer::ProjectManager::instance(), + &ProjectExplorer::ProjectManager::projectFinishedParsing, + this, &AxivionPluginPrivate::handleOpenedDocs); +} + +void AxivionPluginPrivate::clearAllMarks() +{ + const QList openDocuments = Core::DocumentModel::openedDocuments(); + for (Core::IDocument *doc : openDocuments) + onDocumentClosed(doc); +} + +void AxivionPluginPrivate::handleProjectInfo(const ProjectInfo &info) +{ + m_runningQuery = false; + if (!info.error.isEmpty()) { + Core::MessageManager::writeFlashing("Axivion: " + info.error); + return; + } + + m_currentProjectInfo = info; + m_axivionOutputPane.updateDashboard(); + + if (m_currentProjectInfo.name.isEmpty()) + return; + + // handle already opened documents + if (auto buildSystem = ProjectExplorer::ProjectManager::startupBuildSystem(); + !buildSystem || !buildSystem->isParsing()) { + handleOpenedDocs(nullptr); + } else { + connect(ProjectExplorer::ProjectManager::instance(), + &ProjectExplorer::ProjectManager::projectFinishedParsing, + this, &AxivionPluginPrivate::handleOpenedDocs); + } +} + +void AxivionPluginPrivate::onDocumentOpened(Core::IDocument *doc) +{ + if (m_currentProjectInfo.name.isEmpty()) // we do not have a project info (yet) + return; + + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); + if (!doc || !project->isKnownFile(doc->filePath())) + return; + + Utils::FilePath relative = doc->filePath().relativeChildPath(project->projectDirectory()); + // for now only style violations + AxivionQuery query(AxivionQuery::IssuesForFileList, {m_currentProjectInfo.name, "SV", + relative.path() } ); + AxivionQueryRunner *runner = new AxivionQueryRunner(query, this); + connect(runner, &AxivionQueryRunner::resultRetrieved, this, [this](const QByteArray &result){ + handleIssuesForFile(ResultParser::parseIssuesList(result)); + }); + connect(runner, &AxivionQueryRunner::finished, [runner]{ runner->deleteLater(); }); + runner->start(); +} + +void AxivionPluginPrivate::onDocumentClosed(Core::IDocument *doc) +{ + const auto document = qobject_cast(doc); + if (!document) + return; + + const TextEditor::TextMarks marks = document->marks(); + for (auto m : marks) { + if (m->category().id == AxivionTextMarkId) + delete m; + } +} + +void AxivionPluginPrivate::handleIssuesForFile(const IssuesList &issues) +{ + if (issues.issues.isEmpty()) + return; + + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); + if (!project) + return; + + const Utils::FilePath filePath = project->projectDirectory() + .pathAppended(issues.issues.first().filePath); + + const Utils::Id axivionId(AxivionTextMarkId); + for (const ShortIssue &issue : std::as_const(issues.issues)) { + // FIXME the line location can be wrong (even the whole issue could be wrong) + // depending on whether this line has been changed since the last axivion run and the + // current state of the file - some magic has to happen here + new AxivionTextMark(filePath, issue); + } +} + +} // Axivion::Internal diff --git a/src/plugins/axivion/axivionplugin.h b/src/plugins/axivion/axivionplugin.h new file mode 100644 index 00000000000..992971b6bd6 --- /dev/null +++ b/src/plugins/axivion/axivionplugin.h @@ -0,0 +1,41 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace ProjectExplorer { class Project; } + +namespace Axivion::Internal { + +class AxivionSettings; +class AxivionProjectSettings; +class ProjectInfo; + +class AxivionPlugin final : public ExtensionSystem::IPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Axivion.json") + +public: + AxivionPlugin(); + ~AxivionPlugin() final; + + static AxivionPlugin *instance(); + static AxivionSettings *settings(); + static AxivionProjectSettings *projectSettings(ProjectExplorer::Project *project); + + static bool handleCertificateIssue(); + static void fetchProjectInfo(const QString &projectName); + static ProjectInfo projectInfo(); +signals: + void settingsChanged(); + +private: + bool initialize(const QStringList &arguments, QString *errorMessage) final; + void extensionsInitialized() final {} +}; + +} // Axivion::Internal + diff --git a/src/plugins/axivion/axivionprojectsettings.cpp b/src/plugins/axivion/axivionprojectsettings.cpp new file mode 100644 index 00000000000..32689ae950d --- /dev/null +++ b/src/plugins/axivion/axivionprojectsettings.cpp @@ -0,0 +1,184 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "axivionprojectsettings.h" + +#include "axivionplugin.h" +#include "axivionquery.h" +#include "axivionresultparser.h" +#include "axivionsettings.h" +#include "axiviontr.h" + +#include +#include +#include + +#include +#include +#include + +namespace Axivion::Internal { + +const char PSK_PROJECTNAME[] = "Axivion.ProjectName"; + +AxivionProjectSettings::AxivionProjectSettings(ProjectExplorer::Project *project) + : m_project{project} +{ + load(); + connect(project, &ProjectExplorer::Project::settingsLoaded, + this, &AxivionProjectSettings::load); + connect(project, &ProjectExplorer::Project::aboutToSaveSettings, + this, &AxivionProjectSettings::save); +} + +void AxivionProjectSettings::load() +{ + m_dashboardProjectName = m_project->namedSettings(PSK_PROJECTNAME).toString(); +} + +void AxivionProjectSettings::save() +{ + m_project->setNamedSettings(PSK_PROJECTNAME, m_dashboardProjectName); +} + +AxivionProjectSettingsWidget::AxivionProjectSettingsWidget(ProjectExplorer::Project *project, + QWidget *parent) + : ProjectExplorer::ProjectSettingsWidget{parent} + , m_projectSettings(AxivionPlugin::projectSettings(project)) + , m_globalSettings(AxivionPlugin::settings()) +{ + setUseGlobalSettingsCheckBoxVisible(false); + setUseGlobalSettingsLabelVisible(true); + setGlobalSettingsId("Axivion.Settings.General"); // FIXME move id to constants + // setup ui + auto verticalLayout = new QVBoxLayout(this); + verticalLayout->setContentsMargins(0, 0, 0, 0); + + m_linkedProject = new QLabel(this); + verticalLayout->addWidget(m_linkedProject); + + m_dashboardProjects = new QTreeWidget(this); + m_dashboardProjects->setHeaderHidden(true); + m_dashboardProjects->setRootIsDecorated(false); + verticalLayout->addWidget(new QLabel(Tr::tr("Dashboard projects:"))); + verticalLayout->addWidget(m_dashboardProjects); + + m_infoLabel = new Utils::InfoLabel(this); + m_infoLabel->setVisible(false); + verticalLayout->addWidget(m_infoLabel); + + auto horizontalLayout = new QHBoxLayout; + horizontalLayout->setContentsMargins(0, 0, 0, 0); + m_fetchProjects = new QPushButton(Tr::tr("Fetch Projects")); + horizontalLayout->addWidget(m_fetchProjects); + m_link = new QPushButton(Tr::tr("Link Project")); + m_link->setEnabled(false); + horizontalLayout->addWidget(m_link); + m_unlink = new QPushButton(Tr::tr("Unlink Project")); + m_unlink->setEnabled(false); + horizontalLayout->addWidget(m_unlink); + verticalLayout->addLayout(horizontalLayout); + + connect(m_dashboardProjects, &QTreeWidget::itemSelectionChanged, + this, &AxivionProjectSettingsWidget::updateEnabledStates); + connect(m_fetchProjects, &QPushButton::clicked, + this, &AxivionProjectSettingsWidget::fetchProjects); + connect(m_link, &QPushButton::clicked, + this, &AxivionProjectSettingsWidget::linkProject); + connect(m_unlink, &QPushButton::clicked, + this, &AxivionProjectSettingsWidget::unlinkProject); + connect(AxivionPlugin::instance(), &AxivionPlugin::settingsChanged, + this, &AxivionProjectSettingsWidget::onSettingsChanged); + + updateUi(); +} + +void AxivionProjectSettingsWidget::fetchProjects() +{ + m_dashboardProjects->clear(); + m_fetchProjects->setEnabled(false); + m_infoLabel->setVisible(false); + // TODO perform query and populate m_dashboardProjects + const AxivionQuery query(AxivionQuery::DashboardInfo); + AxivionQueryRunner *runner = new AxivionQueryRunner(query, this); + connect(runner, &AxivionQueryRunner::resultRetrieved, + this, [this](const QByteArray &result){ + onDashboardInfoReceived(ResultParser::parseDashboardInfo(result)); + }); + connect(runner, &AxivionQueryRunner::finished, this, [runner]{ runner->deleteLater(); }); + runner->start(); +} + +void AxivionProjectSettingsWidget::onDashboardInfoReceived(const DashboardInfo &info) +{ + if (!info.error.isEmpty()) { + m_infoLabel->setText(info.error); + m_infoLabel->setType(Utils::InfoLabel::Error); + m_infoLabel->setVisible(true); + updateEnabledStates(); + return; + } + + for (const Project &project : info.projects) + new QTreeWidgetItem(m_dashboardProjects, {project.name}); + updateEnabledStates(); +} + +void AxivionProjectSettingsWidget::onSettingsChanged() +{ + m_dashboardProjects->clear(); + m_infoLabel->setVisible(false); + updateUi(); +} + +void AxivionProjectSettingsWidget::linkProject() +{ + const QList selected = m_dashboardProjects->selectedItems(); + QTC_ASSERT(selected.size() == 1, return); + + const QString projectName = selected.first()->text(0); + m_projectSettings->setDashboardProjectName(projectName); + updateUi(); + AxivionPlugin::fetchProjectInfo(projectName); +} + +void AxivionProjectSettingsWidget::unlinkProject() +{ + QTC_ASSERT(!m_projectSettings->dashboardProjectName().isEmpty(), return); + + m_projectSettings->setDashboardProjectName({}); + updateUi(); + AxivionPlugin::fetchProjectInfo({}); +} + +void AxivionProjectSettingsWidget::updateUi() +{ + const QString projectName = m_projectSettings->dashboardProjectName(); + if (projectName.isEmpty()) + m_linkedProject->setText(Tr::tr("This project is not linked to a dashboard project.")); + else + m_linkedProject->setText(Tr::tr("This project is linked to \"%1\".").arg(projectName)); + updateEnabledStates(); +} + +void AxivionProjectSettingsWidget::updateEnabledStates() +{ + const bool hasDashboardSettings = m_globalSettings->curl().isExecutableFile() + && !m_globalSettings->server.dashboard.isEmpty() + && !m_globalSettings->server.token.isEmpty(); + const bool linked = !m_projectSettings->dashboardProjectName().isEmpty(); + const bool linkable = m_dashboardProjects->topLevelItemCount() + && !m_dashboardProjects->selectedItems().isEmpty(); + + m_fetchProjects->setEnabled(hasDashboardSettings); + m_link->setEnabled(!linked && linkable); + m_unlink->setEnabled(linked); + + if (!hasDashboardSettings) { + m_infoLabel->setText(Tr::tr("Incomplete or misconfigured settings.")); + m_infoLabel->setType(Utils::InfoLabel::NotOk); + m_infoLabel->setVisible(true); + } +} + +} // Axivion::Internal diff --git a/src/plugins/axivion/axivionprojectsettings.h b/src/plugins/axivion/axivionprojectsettings.h new file mode 100644 index 00000000000..d5bc1fd2e62 --- /dev/null +++ b/src/plugins/axivion/axivionprojectsettings.h @@ -0,0 +1,67 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "axivionsettings.h" + +#include + +#include + +QT_BEGIN_NAMESPACE +class QLabel; +class QPushButton; +class QTreeWidget; +QT_END_NAMESPACE + +namespace ProjectExplorer { class Project; } + +namespace Utils { class InfoLabel; } + +namespace Axivion::Internal { + +class DashboardInfo; + +class AxivionProjectSettings : public QObject +{ +public: + explicit AxivionProjectSettings(ProjectExplorer::Project *project); + + void setDashboardProjectName(const QString &name) { m_dashboardProjectName = name; } + QString dashboardProjectName() const { return m_dashboardProjectName; } + +private: + void load(); + void save(); + + ProjectExplorer::Project *m_project = nullptr; + QString m_dashboardProjectName; +}; + +class AxivionProjectSettingsWidget : public ProjectExplorer::ProjectSettingsWidget +{ +public: + explicit AxivionProjectSettingsWidget(ProjectExplorer::Project *project, + QWidget *parent = nullptr); + +private: + void fetchProjects(); + void onDashboardInfoReceived(const DashboardInfo &info); + void onSettingsChanged(); + void linkProject(); + void unlinkProject(); + void updateUi(); + void updateEnabledStates(); + + AxivionProjectSettings *m_projectSettings = nullptr; + AxivionSettings *m_globalSettings; + QLabel *m_linkedProject = nullptr; + QTreeWidget *m_dashboardProjects = nullptr; + QPushButton *m_fetchProjects = nullptr; + QPushButton *m_link = nullptr; + QPushButton *m_unlink = nullptr; + Utils::InfoLabel *m_infoLabel = nullptr; +}; + +} // Axivion::Internal diff --git a/src/plugins/axivion/axivionquery.cpp b/src/plugins/axivion/axivionquery.cpp new file mode 100644 index 00000000000..fda0eccf128 --- /dev/null +++ b/src/plugins/axivion/axivionquery.cpp @@ -0,0 +1,97 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "axivionquery.h" + +#include "axivionplugin.h" +#include "axivionsettings.h" + +#include +#include + +#include + +using namespace Utils; + +namespace Axivion::Internal { + +AxivionQuery::AxivionQuery(QueryType type, const QStringList ¶meters) + : m_type(type) + , m_parameters(parameters) +{ +} + +QString AxivionQuery::toString() const +{ + QString query = "/api"; // common for all except RuleInfo + switch (m_type) { + case NoQuery: + return {}; + case DashboardInfo: + return query; + case ProjectInfo: + QTC_ASSERT(m_parameters.size() == 1, return {}); + query += "/projects/" + QUrl::toPercentEncoding(m_parameters.first()); + return query; + case IssuesForFileList: + QTC_ASSERT(m_parameters.size() == 3, return {}); + // FIXME shall we validate the kind? (some kinds do not support path filter) + query += "/projects/" + QUrl::toPercentEncoding(m_parameters.first()) + + "/issues?kind=" + m_parameters.at(1) + "&filter_path=" + + QUrl::toPercentEncoding(m_parameters.at(2)) + "&format=csv"; + return query; + case RuleInfo: + QTC_ASSERT(m_parameters.size() == 2, return {}); + query = "/projects/" + QUrl::toPercentEncoding(m_parameters.first()) + + "/issues/" + m_parameters.at(1) + "/rule"; + return query; + } + + return {}; +} + +AxivionQueryRunner::AxivionQueryRunner(const AxivionQuery &query, QObject *parent) + : QObject(parent) +{ + const AxivionSettings *settings = AxivionPlugin::settings(); + const AxivionServer server = settings->server; + + QStringList args = server.curlArguments(); + args << "-i"; + args << "--header" << "Authorization: AxToken " + server.token; + + QString url = server.dashboard; + while (url.endsWith('/')) url.chop(1); + url += query.toString(); + args << url; + + m_process.setCommand({settings->curl(), args}); + connect(&m_process, &Process::done, this, [this]{ + if (m_process.result() != ProcessResult::FinishedWithSuccess) { + const int exitCode = m_process.exitCode(); + if (m_process.exitStatus() == QProcess::NormalExit + && (exitCode == 35 || exitCode == 60) + && AxivionPlugin::handleCertificateIssue()) { + // prepend -k for re-requesting same query + CommandLine cmdline = m_process.commandLine(); + cmdline.prependArgs({"-k"}); + m_process.close(); + m_process.setCommand(cmdline); + start(); + return; + } + emit resultRetrieved(m_process.readAllRawStandardError()); + } else { + emit resultRetrieved(m_process.readAllRawStandardOutput()); + } + emit finished(); + }); +} + +void AxivionQueryRunner::start() +{ + QTC_ASSERT(!m_process.isRunning(), return); + m_process.start(); +} + +} // Axivion::Internal diff --git a/src/plugins/axivion/axivionquery.h b/src/plugins/axivion/axivionquery.h new file mode 100644 index 00000000000..11af5bbd87b --- /dev/null +++ b/src/plugins/axivion/axivionquery.h @@ -0,0 +1,40 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +#include + +namespace Axivion::Internal { + +class AxivionQuery +{ +public: + enum QueryType {NoQuery, DashboardInfo, ProjectInfo, IssuesForFileList, RuleInfo}; + explicit AxivionQuery(QueryType type, const QStringList ¶meters = {}); + + QString toString() const; + +private: + QueryType m_type = NoQuery; + QStringList m_parameters; +}; + +class AxivionQueryRunner : public QObject +{ + Q_OBJECT +public: + explicit AxivionQueryRunner(const AxivionQuery &query, QObject *parent = nullptr); + void start(); + +signals: + void finished(); + void resultRetrieved(const QByteArray &json); + +private: + Utils::Process m_process; +}; + +} // Axivion::Internal diff --git a/src/plugins/axivion/axivionresultparser.cpp b/src/plugins/axivion/axivionresultparser.cpp new file mode 100644 index 00000000000..0d8bb27b7db --- /dev/null +++ b/src/plugins/axivion/axivionresultparser.cpp @@ -0,0 +1,347 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "axivionresultparser.h" + +#include + +#include +#include +#include +#include + +#include + +namespace Axivion::Internal { + +static std::pair splitHeaderAndBody(const QByteArray &input) +{ + QByteArray header; + QByteArray json; + int emptyLine = input.indexOf("\r\n\r\n"); // we always get \r\n as line separator + if (emptyLine != -1) { + header = input.left(emptyLine); + json = input.mid(emptyLine + 4); + } else { + json = input; + } + return {header, json}; +} + +static int httpStatus(const QByteArray &header) +{ + int firstHeaderEnd = header.indexOf("\r\n"); + if (firstHeaderEnd == -1) + return 600; // unexpected header + const QString firstLine = QString::fromUtf8(header.first(firstHeaderEnd)); + static const QRegularExpression regex(R"(^HTTP/\d\.\d (\d{3}) .*$)"); + const QRegularExpressionMatch match = regex.match(firstLine); + return match.hasMatch() ? match.captured(1).toInt() : 601; +} + +static BaseResult prehandleHeader(const QByteArray &header, const QByteArray &body) +{ + BaseResult result; + if (header.isEmpty()) { + result.error = QString::fromUtf8(body); // we likely had a curl problem + return result; + } + int status = httpStatus(header); + if ((status > 399) || (status > 299 && body.isEmpty())) { // FIXME handle some explicitly? + const QString statusStr = QString::number(status); + if (body.isEmpty() || body.startsWith('<')) // likely an html response or redirect + result.error = QLatin1String("(%1)").arg(statusStr); + else + result.error = QLatin1String("%1 (%2)").arg(QString::fromUtf8(body)).arg(statusStr); + } + return result; +} + +static std::pair prehandleHeaderAndBody(const QByteArray &header, + const QByteArray &body) +{ + BaseResult result = prehandleHeader(header, body); + if (!result.error.isEmpty()) + return {result, {}}; + + QJsonParseError error; + const QJsonDocument doc = QJsonDocument::fromJson(body, &error); + if (error.error != QJsonParseError::NoError) { + result.error = error.errorString(); + return {result, doc}; + } + + if (!doc.isObject()) { + result.error = "Not an object."; + return {result, {}}; + } + + return {result, doc}; +} + +static User::UserType userTypeForString(const QString &type) +{ + if (type == "DASHBOARD_USER") + return User::Dashboard; + if (type == "VIRTUAL_USER") + return User::Virtual; + return User::Unknown; +} + +static User userFromJson(const QJsonObject &object) +{ + User result; + if (object.isEmpty()) { + result.error = "Not a user object."; + return result; + } + result.name = object.value("name").toString(); + result.displayName = object.value("displayName").toString(); + result.type = userTypeForString(object.value("type").toString()); + return result; +} + +static QList usersFromJson(const QJsonArray &array) +{ + QList result; + for (const QJsonValue &value : array) { + User user = userFromJson(value.toObject()); + if (!user.error.isEmpty()) // add this error to result.error? + continue; + result.append(user); + } + return result; +} + +static IssueCount issueCountFromJson(const QJsonObject &object) +{ + IssueCount result; + if (object.isEmpty()) { + result.error = "Not an issue count object."; + return result; + } + result.added = object.value("Added").toInt(); + result.removed = object.value("Removed").toInt(); + result.total = object.value("Total").toInt(); + return result; +} + +static QList issueCountsFromJson(const QJsonObject &object) +{ + QList result; + + const QStringList keys = object.keys(); + for (const QString &k : keys) { + IssueCount issue = issueCountFromJson(object.value(k).toObject()); + if (!issue.error.isEmpty()) // add this error to result.error? + continue; + issue.issueKind = k; + result.append(issue); + } + return result; +} + +static ResultVersion versionFromJson(const QJsonObject &object) +{ + ResultVersion result; + if (object.isEmpty()) { + result.error = "Not a version object."; + return result; + } + const QJsonValue issuesValue = object.value("issueCounts"); + if (!issuesValue.isObject()) { + result.error = "Not an object (issueCounts)."; + return result; + } + result.issueCounts = issueCountsFromJson(issuesValue.toObject()); + result.timeStamp = object.value("date").toString(); + result.name = object.value("name").toString(); + result.linesOfCode = object.value("linesOfCode").toInt(); + return result; +} + +static QList versionsFromJson(const QJsonArray &array) +{ + QList result; + for (const QJsonValue &value : array) { + ResultVersion version = versionFromJson(value.toObject()); + if (!version.error.isEmpty()) // add this error to result.error? + continue; + result.append(version); + } + return result; +} + +static IssueKind issueKindFromJson(const QJsonObject &object) +{ + IssueKind result; + if (object.isEmpty()) { + result.error = "Not an issue kind object."; + return result; + } + result.prefix = object.value("prefix").toString(); + result.niceSingular = object.value("niceSingularName").toString(); + result.nicePlural = object.value("nicePluralName").toString(); + return result; +} + +static QList issueKindsFromJson(const QJsonArray &array) +{ + QList result; + for (const QJsonValue &value : array) { + IssueKind kind = issueKindFromJson(value.toObject()); + if (!kind.error.isEmpty()) // add this error to result.error? + continue; + result.append(kind); + } + return result; +} + +namespace ResultParser { + +DashboardInfo parseDashboardInfo(const QByteArray &input) +{ + DashboardInfo result; + + auto [header, body] = splitHeaderAndBody(input); + auto [error, doc] = prehandleHeaderAndBody(header, body); + if (!error.error.isEmpty()) { + result.error = error.error; + return result; + } + const QJsonObject object = doc.object(); + result.mainUrl = object.value("mainUrl").toString(); + + if (!object.contains("projects")) { + result.error = "Missing projects information."; + return result; + } + const QJsonValue projects = object.value("projects"); + if (!projects.isArray()) { + result.error = "Projects information not an array."; + return result; + } + const QJsonArray array = projects.toArray(); + for (const QJsonValue &val : array) { + if (!val.isObject()) + continue; + const QJsonObject projectObject = val.toObject(); + Project project; + project.name = projectObject.value("name").toString(); + project.url = projectObject.value("url").toString(); + if (project.name.isEmpty() || project.url.isEmpty()) + continue; + result.projects.append(project); + } + return result; +} + +ProjectInfo parseProjectInfo(const QByteArray &input) +{ + ProjectInfo result; + + auto [header, body] = splitHeaderAndBody(input); + auto [error, doc] = prehandleHeaderAndBody(header, body); + if (!error.error.isEmpty()) { + result.error = error.error; + return result; + } + + const QJsonObject object = doc.object(); + result.name = object.value("name").toString(); + + const QJsonValue usersValue = object.value("users"); + if (!usersValue.isArray()) { + result.error = "Malformed json response (users)."; + return result; + } + result.users = usersFromJson(usersValue.toArray()); + + const QJsonValue versionsValue = object.value("versions"); + if (!versionsValue.isArray()) { + result.error = "Malformed json response (versions)."; + return result; + } + result.versions = versionsFromJson(versionsValue.toArray()); + + const QJsonValue issueKindsValue = object.value("issueKinds"); + if (!issueKindsValue.isArray()) { + result.error = "Malformed json response (issueKinds)."; + return result; + } + result.issueKinds = issueKindsFromJson(issueKindsValue.toArray()); + return result; +} + +static QRegularExpression issueCsvLineRegex(const QByteArray &firstCsvLine) +{ + QString pattern = "^"; + for (const QByteArray &part : firstCsvLine.split(',')) { + const QString cleaned = QString::fromUtf8(part).remove(' ').chopped(1).mid(1); + pattern.append(QString("\"(?<" + cleaned + ">.*)\",")); + } + pattern.chop(1); // remove last comma + pattern.append('$'); + const QRegularExpression regex(pattern); + QTC_ASSERT(regex.isValid(), return {}); + return regex; +} + +static void parseCsvIssue(const QByteArray &csv, QList *issues) +{ + QTC_ASSERT(issues, return); + + bool first = true; + std::optional regex; + for (auto &line : csv.split('\n')) { + if (first) { + regex.emplace(issueCsvLineRegex(line)); + first = false; + if (regex.value().pattern().isEmpty()) + return; + continue; + } + if (line.isEmpty()) + continue; + const QRegularExpressionMatch match = regex->match(QString::fromUtf8(line)); + QTC_ASSERT(match.hasMatch(), continue); + // FIXME: some of these are not present for all issue kinds! Limited to SV for now + ShortIssue issue; + issue.id = match.captured("Id"); + issue.state = match.captured("State"); + issue.errorNumber = match.captured("ErrorNumber"); + issue.message = match.captured("Message"); + issue.entity = match.captured("Entity"); + issue.filePath = match.captured("Path"); + issue.severity = match.captured("Severity"); + issue.lineNumber = match.captured("Line").toInt(); + issues->append(issue); + } +} + +IssuesList parseIssuesList(const QByteArray &input) +{ + IssuesList result; + + auto [header, body] = splitHeaderAndBody(input); + BaseResult headerResult = prehandleHeader(header, body); + if (!headerResult.error.isEmpty()) { + result.error = headerResult.error; + return result; + } + parseCsvIssue(body, &result.issues); + return result; +} + +QString parseRuleInfo(const QByteArray &input) // html result! +{ + auto [header, body] = splitHeaderAndBody(input); + BaseResult headerResult = prehandleHeader(header, body); + if (!headerResult.error.isEmpty()) + return QString(); + return QString::fromLocal8Bit(body); +} + +} // ResultParser + +} // Axivion::Internal diff --git a/src/plugins/axivion/axivionresultparser.h b/src/plugins/axivion/axivionresultparser.h new file mode 100644 index 00000000000..84c5a6b8ca8 --- /dev/null +++ b/src/plugins/axivion/axivionresultparser.h @@ -0,0 +1,101 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace Axivion::Internal { + +class BaseResult +{ +public: + QString error; +}; + +class Project : public BaseResult +{ +public: + QString name; + QString url; +}; + +class DashboardInfo : public BaseResult +{ +public: + QString mainUrl; + QList projects; +}; + +class User : public BaseResult +{ +public: + QString name; + QString displayName; + enum UserType { Dashboard, Virtual, Unknown } type; +}; + +class IssueKind : public BaseResult +{ +public: + QString prefix; + QString niceSingular; + QString nicePlural; +}; + +class IssueCount : public BaseResult +{ +public: + QString issueKind; + int total = 0; + int added = 0; + int removed = 0; +}; + +class ResultVersion : public BaseResult +{ +public: + QString name; + QString timeStamp; + QList issueCounts; + int linesOfCode = 0; +}; + +class ProjectInfo : public BaseResult +{ +public: + QString name; + QList users; + QList versions; + QList issueKinds; +}; + +class ShortIssue : public BaseResult +{ +public: + QString id; + QString state; + QString errorNumber; + QString message; + QString entity; + QString filePath; + QString severity; + int lineNumber = 0; +}; + +class IssuesList : public BaseResult +{ +public: + QList issues; +}; + +namespace ResultParser { + +DashboardInfo parseDashboardInfo(const QByteArray &input); +ProjectInfo parseProjectInfo(const QByteArray &input); +IssuesList parseIssuesList(const QByteArray &input); +QString parseRuleInfo(const QByteArray &input); + +} // ResultParser + +} // Axivion::Internal diff --git a/src/plugins/axivion/axivionsettings.cpp b/src/plugins/axivion/axivionsettings.cpp new file mode 100644 index 00000000000..4a183631633 --- /dev/null +++ b/src/plugins/axivion/axivionsettings.cpp @@ -0,0 +1,131 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "axivionsettings.h" + +#include "axiviontr.h" + +#include +#include + +#include +#include +#include +#include + +namespace Axivion::Internal { + +AxivionServer::AxivionServer(const Utils::Id &id, const QString &dashboard, + const QString &description, const QString &token) + : id(id) + , dashboard(dashboard) + , description(description) + , token(token) +{ +} + +bool AxivionServer::operator==(const AxivionServer &other) const +{ + return id == other.id && dashboard == other.dashboard + && description == other.description && token == other.token; +} + +bool AxivionServer::operator!=(const AxivionServer &other) const +{ + return !(*this == other); +} + +QJsonObject AxivionServer::toJson() const +{ + QJsonObject result; + result.insert("id", id.toString()); + result.insert("dashboard", dashboard); + result.insert("description", description); + result.insert("token", token); + return result; +} + +AxivionServer AxivionServer::fromJson(const QJsonObject &json) +{ + const AxivionServer invalidServer; + const QJsonValue id = json.value("id"); + if (id == QJsonValue::Undefined) + return invalidServer; + const QJsonValue dashboard = json.value("dashboard"); + if (dashboard == QJsonValue::Undefined) + return invalidServer; + const QJsonValue description = json.value("description"); + if (description == QJsonValue::Undefined) + return invalidServer; + const QJsonValue token = json.value("token"); + if (token == QJsonValue::Undefined) + return invalidServer; + return { Utils::Id::fromString(id.toString()), dashboard.toString(), + description.toString(), token.toString() }; +} + +QStringList AxivionServer::curlArguments() const +{ + QStringList args { "-sS" }; // silent, but show error + if (dashboard.startsWith("https://") && !validateCert) + args << "-k"; + return args; +} + +AxivionSettings::AxivionSettings() +{ + setSettingsGroup("Axivion"); + + curl.setSettingsKey("Curl"); + curl.setLabelText(Tr::tr("curl:")); + curl.setExpectedKind(Utils::PathChooser::ExistingCommand); +} + +static Utils::FilePath tokensFilePath(const QSettings *s) +{ + return Utils::FilePath::fromString(s->fileName()).parentDir() + .pathAppended("qtcreator/axivion.json"); +} + +static void writeTokenFile(const Utils::FilePath &filePath, const AxivionServer &server) +{ + QJsonDocument doc; + doc.setObject(server.toJson()); + // FIXME error handling? + filePath.writeFileContents(doc.toJson()); + filePath.setPermissions(QFile::ReadUser | QFile::WriteUser); +} + +static AxivionServer readTokenFile(const Utils::FilePath &filePath) +{ + if (!filePath.exists()) + return {}; + Utils::expected_str contents = filePath.fileContents(); + if (!contents) + return {}; + const QJsonDocument doc = QJsonDocument::fromJson(*contents); + if (!doc.isObject()) + return {}; + return AxivionServer::fromJson(doc.object()); +} + +void AxivionSettings::toSettings(QSettings *s) const +{ + writeTokenFile(tokensFilePath(s), server); + Utils::AspectContainer::writeSettings(s); +} + +void AxivionSettings::fromSettings(QSettings *s) +{ + Utils::AspectContainer::readSettings(s); + server = readTokenFile(tokensFilePath(s)); + + if (curl().isEmpty() || !curl().exists()) { + const QString curlPath = QStandardPaths::findExecutable( + Utils::HostOsInfo::withExecutableSuffix("curl")); + if (!curlPath.isEmpty()) + curl.setFilePath(Utils::FilePath::fromString(curlPath)); + } +} + +} // Axivion::Internal diff --git a/src/plugins/axivion/axivionsettings.h b/src/plugins/axivion/axivionsettings.h new file mode 100644 index 00000000000..0df1746387f --- /dev/null +++ b/src/plugins/axivion/axivionsettings.h @@ -0,0 +1,51 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include +#include + +#include + +QT_BEGIN_NAMESPACE +class QJsonObject; +class QSettings; +QT_END_NAMESPACE + +namespace Axivion::Internal { + +class AxivionServer +{ +public: + AxivionServer() = default; + AxivionServer(const Utils::Id &id, const QString &dashboardUrl, + const QString &description, const QString &token); + + bool operator==(const AxivionServer &other) const; + bool operator!=(const AxivionServer &other) const; + + QJsonObject toJson() const; + static AxivionServer fromJson(const QJsonObject &json); + QStringList curlArguments() const; + + Utils::Id id; + QString dashboard; + QString description; + QString token; + + bool validateCert = true; +}; + +class AxivionSettings : public Utils::AspectContainer +{ +public: + AxivionSettings(); + void toSettings(QSettings *s) const; + void fromSettings(QSettings *s); + + AxivionServer server; // shall we have more than one? + Utils::FilePathAspect curl{this}; +}; + +} // Axivion::Internal diff --git a/src/plugins/axivion/axivionsettingspage.cpp b/src/plugins/axivion/axivionsettingspage.cpp new file mode 100644 index 00000000000..e837d71598d --- /dev/null +++ b/src/plugins/axivion/axivionsettingspage.cpp @@ -0,0 +1,215 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "axivionsettingspage.h" + +#include "axivionplugin.h" +#include "axivionsettings.h" +#include "axiviontr.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace Utils; + +namespace Axivion::Internal { + +// may allow some invalid, but does some minimal check for legality +static bool hostValid(const QString &host) +{ + static const QRegularExpression ip(R"(^(\d+).(\d+).(\d+).(\d+)$)"); + static const QRegularExpression dn(R"(^([a-zA-Z0-9][a-zA-Z0-9-]+\.)+[a-zA-Z0-9][a-zA-Z0-9-]+$)"); + const QRegularExpressionMatch match = ip.match(host); + if (match.hasMatch()) { + for (int i = 1; i < 5; ++i) { + int val = match.captured(i).toInt(); + if (val < 0 || val > 255) + return false; + } + return true; + } + return (host == "localhost") || dn.match(host).hasMatch(); +} + +static bool isUrlValid(const QString &in) +{ + const QUrl url(in); + return hostValid(url.host()) && (url.scheme() == "https" || url.scheme() == "http"); +} + +class DashboardSettingsWidget : public QWidget +{ +public: + enum Mode { Display, Edit }; + explicit DashboardSettingsWidget(Mode m, QWidget *parent, QPushButton *ok = nullptr); + + AxivionServer dashboardServer() const; + void setDashboardServer(const AxivionServer &server); + + bool isValid() const; + +private: + Mode m_mode = Display; + Id m_id; + StringAspect m_dashboardUrl; + StringAspect m_description; + StringAspect m_token; + BoolAspect m_valid; +}; + +DashboardSettingsWidget::DashboardSettingsWidget(Mode mode, QWidget *parent, QPushButton *ok) + : QWidget(parent) + , m_mode(mode) +{ + auto labelStyle = mode == Display ? StringAspect::LabelDisplay : StringAspect::LineEditDisplay; + m_dashboardUrl.setLabelText(Tr::tr("Dashboard URL:")); + m_dashboardUrl.setDisplayStyle(labelStyle); + m_dashboardUrl.setValidationFunction([](FancyLineEdit *edit, QString *){ + return isUrlValid(edit->text()); + }); + m_description.setLabelText(Tr::tr("Description:")); + m_description.setDisplayStyle(labelStyle); + m_description.setPlaceHolderText(Tr::tr("Non-empty description")); + + m_token.setLabelText(Tr::tr("Access token:")); + m_token.setDisplayStyle(labelStyle); + m_token.setPlaceHolderText(Tr::tr("IDE Access Token")); + m_token.setVisible(mode == Edit); + + using namespace Layouting; + + Form { + m_dashboardUrl, br, + m_description, br, + m_token, br, + mode == Edit ? normalMargin : noMargin + }.attachTo(this); + + if (mode == Edit) { + QTC_ASSERT(ok, return); + auto checkValidity = [this, ok] { + m_valid.setValue(isValid()); + ok->setEnabled(m_valid()); + }; + connect(&m_dashboardUrl, &BaseAspect::changed, this, checkValidity); + connect(&m_description, &BaseAspect::changed, this, checkValidity); + connect(&m_token, &BaseAspect::changed, this, checkValidity); + } +} + +AxivionServer DashboardSettingsWidget::dashboardServer() const +{ + AxivionServer result; + if (m_id.isValid()) + result.id = m_id; + else + result.id = m_mode == Edit ? Utils::Id::fromName(QUuid::createUuid().toByteArray()) : m_id; + result.dashboard = m_dashboardUrl(); + result.description = m_description(); + result.token = m_token(); + return result; +} + +void DashboardSettingsWidget::setDashboardServer(const AxivionServer &server) +{ + m_id = server.id; + m_dashboardUrl.setValue(server.dashboard); + m_description.setValue(server.description); + m_token.setValue(server.token); +} + +bool DashboardSettingsWidget::isValid() const +{ + return !m_token().isEmpty() && !m_description().isEmpty() && isUrlValid(m_dashboardUrl()); +} + +class AxivionSettingsWidget : public Core::IOptionsPageWidget +{ +public: + explicit AxivionSettingsWidget(AxivionSettings *settings); + + void apply() override; +private: + void showEditServerDialog(); + + AxivionSettings *m_settings; + + DashboardSettingsWidget *m_dashboardDisplay = nullptr; + QPushButton *m_edit = nullptr; +}; + +AxivionSettingsWidget::AxivionSettingsWidget(AxivionSettings *settings) + : m_settings(settings) +{ + using namespace Layouting; + + m_dashboardDisplay = new DashboardSettingsWidget(DashboardSettingsWidget::Display, this); + m_dashboardDisplay->setDashboardServer(m_settings->server); + m_edit = new QPushButton(Tr::tr("Edit..."), this); + Row { + Form { + m_dashboardDisplay, br, + m_settings->curl, br, + }, Column { m_edit, st } + }.attachTo(this); + + connect(m_edit, &QPushButton::clicked, this, &AxivionSettingsWidget::showEditServerDialog); +} + +void AxivionSettingsWidget::apply() +{ + m_settings->server = m_dashboardDisplay->dashboardServer(); + m_settings->toSettings(Core::ICore::settings()); + emit AxivionPlugin::instance()->settingsChanged(); +} + +void AxivionSettingsWidget::showEditServerDialog() +{ + const AxivionServer old = m_dashboardDisplay->dashboardServer(); + QDialog d; + d.setWindowTitle(Tr::tr("Edit Dashboard Configuration")); + QVBoxLayout *layout = new QVBoxLayout; + auto buttons = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok, this); + auto ok = buttons->button(QDialogButtonBox::Ok); + auto dashboardWidget = new DashboardSettingsWidget(DashboardSettingsWidget::Edit, this, ok); + dashboardWidget->setDashboardServer(old); + layout->addWidget(dashboardWidget); + ok->setEnabled(m_dashboardDisplay->isValid()); + connect(buttons->button(QDialogButtonBox::Cancel), &QPushButton::clicked, &d, &QDialog::reject); + connect(ok, &QPushButton::clicked, &d, &QDialog::accept); + layout->addWidget(buttons); + d.setLayout(layout); + d.resize(500, 200); + + if (d.exec() != QDialog::Accepted) + return; + if (dashboardWidget->isValid()) { + const AxivionServer server = dashboardWidget->dashboardServer(); + if (server != old) + m_dashboardDisplay->setDashboardServer(server); + } +} + +AxivionSettingsPage::AxivionSettingsPage(AxivionSettings *settings) + : m_settings(settings) +{ + setId("Axivion.Settings.General"); + setDisplayName(Tr::tr("General")); + setCategory("XY.Axivion"); + setDisplayCategory(Tr::tr("Axivion")); + setCategoryIconPath(":/axivion/images/axivion.png"); + setWidgetCreator([this] { return new AxivionSettingsWidget(m_settings); }); +} + +} // Axivion::Internal diff --git a/src/plugins/axivion/axivionsettingspage.h b/src/plugins/axivion/axivionsettingspage.h new file mode 100644 index 00000000000..744818300d9 --- /dev/null +++ b/src/plugins/axivion/axivionsettingspage.h @@ -0,0 +1,21 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace Axivion::Internal { + +class AxivionSettings; + +class AxivionSettingsPage : public Core::IOptionsPage +{ +public: + explicit AxivionSettingsPage(AxivionSettings *settings); + +private: + AxivionSettings *m_settings; +}; + +} // Axivion::Internal diff --git a/src/libs/utils/asynctask.cpp b/src/plugins/axivion/axiviontr.h similarity index 52% rename from src/libs/utils/asynctask.cpp rename to src/plugins/axivion/axiviontr.h index 3576ad804c4..1f3475cb456 100644 --- a/src/libs/utils/asynctask.cpp +++ b/src/plugins/axivion/axiviontr.h @@ -1,8 +1,15 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "asynctask.h" +#pragma once -namespace Utils { +#include -} // namespace Utils +namespace Axivion { + +struct Tr +{ + Q_DECLARE_TR_FUNCTIONS(Axivion) +}; + +} // Axivion diff --git a/src/plugins/axivion/images/axivion.png b/src/plugins/axivion/images/axivion.png new file mode 100644 index 00000000000..647f5972ce2 Binary files /dev/null and b/src/plugins/axivion/images/axivion.png differ diff --git a/src/plugins/axivion/images/axivion@2x.png b/src/plugins/axivion/images/axivion@2x.png new file mode 100644 index 00000000000..15bac95f52c Binary files /dev/null and b/src/plugins/axivion/images/axivion@2x.png differ diff --git a/src/plugins/baremetal/baremetaldebugsupport.cpp b/src/plugins/baremetal/baremetaldebugsupport.cpp index db0c376a5ad..4475be0b964 100644 --- a/src/plugins/baremetal/baremetaldebugsupport.cpp +++ b/src/plugins/baremetal/baremetaldebugsupport.cpp @@ -11,6 +11,7 @@ #include "idebugserverprovider.h" #include +#include #include #include @@ -23,8 +24,8 @@ #include #include +#include #include -#include using namespace Debugger; using namespace ProjectExplorer; diff --git a/src/plugins/baremetal/baremetaldebugsupport.h b/src/plugins/baremetal/baremetaldebugsupport.h index f0addb4729b..ca16c1ab7ec 100644 --- a/src/plugins/baremetal/baremetaldebugsupport.h +++ b/src/plugins/baremetal/baremetaldebugsupport.h @@ -3,12 +3,11 @@ #pragma once -#include +#include namespace BareMetal::Internal { -class BareMetalDebugSupportFactory final - : public ProjectExplorer::RunWorkerFactory +class BareMetalDebugSupportFactory final : public ProjectExplorer::RunWorkerFactory { public: BareMetalDebugSupportFactory(); diff --git a/src/plugins/baremetal/baremetaldevice.cpp b/src/plugins/baremetal/baremetaldevice.cpp index 4c8d7ea8c66..d7754ebc9a0 100644 --- a/src/plugins/baremetal/baremetaldevice.cpp +++ b/src/plugins/baremetal/baremetaldevice.cpp @@ -24,7 +24,6 @@ const char debugServerProviderIdKeyC[] = "IDebugServerProviderId"; BareMetalDevice::BareMetalDevice() { setDisplayType(Tr::tr("Bare Metal")); - setDefaultDisplayName(defaultDisplayName()); setOsType(Utils::OsTypeOther); } diff --git a/src/plugins/baremetal/debugserverproviderssettingspage.cpp b/src/plugins/baremetal/debugserverproviderssettingspage.cpp index 097d08033a2..58af3578a96 100644 --- a/src/plugins/baremetal/debugserverproviderssettingspage.cpp +++ b/src/plugins/baremetal/debugserverproviderssettingspage.cpp @@ -257,8 +257,6 @@ class DebugServerProvidersSettingsWidget final : public Core::IOptionsPageWidget public: DebugServerProvidersSettingsWidget(); - void apply() final { m_model.apply(); } - void providerSelectionChanged(); void removeProvider(); void updateState(); @@ -363,6 +361,8 @@ DebugServerProvidersSettingsWidget::DebugServerProvidersSettingsWidget() this, &DebugServerProvidersSettingsWidget::removeProvider); updateState(); + + setOnApply([this] { m_model.apply(); }); } void DebugServerProvidersSettingsWidget::providerSelectionChanged() diff --git a/src/plugins/baremetal/debugservers/gdb/gdbserverprovider.cpp b/src/plugins/baremetal/debugservers/gdb/gdbserverprovider.cpp index 336b606d798..222d22ade98 100644 --- a/src/plugins/baremetal/debugservers/gdb/gdbserverprovider.cpp +++ b/src/plugins/baremetal/debugservers/gdb/gdbserverprovider.cpp @@ -8,6 +8,8 @@ #include #include +#include + #include #include diff --git a/src/plugins/baremetal/debugservers/gdb/gdbserverprovider.h b/src/plugins/baremetal/debugservers/gdb/gdbserverprovider.h index eac2ca6e68a..1c23d499957 100644 --- a/src/plugins/baremetal/debugservers/gdb/gdbserverprovider.h +++ b/src/plugins/baremetal/debugservers/gdb/gdbserverprovider.h @@ -40,7 +40,7 @@ public: bool aboutToRun(Debugger::DebuggerRunTool *runTool, QString &errorMessage) const final; ProjectExplorer::RunWorker *targetRunner( - ProjectExplorer::RunControl *runControl) const final; + ProjectExplorer::RunControl *runControl) const override; bool isValid() const override; virtual QSet supportedStartupModes() const = 0; diff --git a/src/plugins/baremetal/debugservers/gdb/genericgdbserverprovider.h b/src/plugins/baremetal/debugservers/gdb/genericgdbserverprovider.h index 2754a3d91a2..98ca17451c1 100644 --- a/src/plugins/baremetal/debugservers/gdb/genericgdbserverprovider.h +++ b/src/plugins/baremetal/debugservers/gdb/genericgdbserverprovider.h @@ -19,6 +19,11 @@ class GenericGdbServerProvider final : public GdbServerProvider private: GenericGdbServerProvider(); QSet supportedStartupModes() const final; + ProjectExplorer::RunWorker *targetRunner(ProjectExplorer::RunControl *runControl) const final { + Q_UNUSED(runControl) + // Generic Runner assumes GDB Server already running + return nullptr; + } friend class GenericGdbServerProviderConfigWidget; friend class GenericGdbServerProviderFactory; diff --git a/src/plugins/baremetal/debugservers/gdb/jlinkgdbserverprovider.cpp b/src/plugins/baremetal/debugservers/gdb/jlinkgdbserverprovider.cpp index d631923788e..c1ee31b7109 100644 --- a/src/plugins/baremetal/debugservers/gdb/jlinkgdbserverprovider.cpp +++ b/src/plugins/baremetal/debugservers/gdb/jlinkgdbserverprovider.cpp @@ -9,8 +9,8 @@ #include #include +#include #include -#include #include #include diff --git a/src/plugins/baremetal/debugservers/gdb/openocdgdbserverprovider.cpp b/src/plugins/baremetal/debugservers/gdb/openocdgdbserverprovider.cpp index 334f31132cb..ca7f25e56f3 100644 --- a/src/plugins/baremetal/debugservers/gdb/openocdgdbserverprovider.cpp +++ b/src/plugins/baremetal/debugservers/gdb/openocdgdbserverprovider.cpp @@ -9,8 +9,8 @@ #include #include +#include #include -#include #include #include @@ -64,7 +64,7 @@ QString OpenOcdGdbServerProvider::channelString() const // otherwise running will be stuck. CommandLine cmd = command(); QStringList args = {"|", cmd.executable().toString()}; - for (const QString &a : ProcessArgs::splitArgs(cmd.arguments())) { + for (const QString &a : ProcessArgs::splitArgs(cmd.arguments(), HostOsInfo::hostOs())) { if (a.startsWith('\"') && a.endsWith('\"')) args << a; else diff --git a/src/plugins/baremetal/debugservers/uvsc/uvproject.cpp b/src/plugins/baremetal/debugservers/uvsc/uvproject.cpp index 773ec410666..faa32fea086 100644 --- a/src/plugins/baremetal/debugservers/uvsc/uvproject.cpp +++ b/src/plugins/baremetal/debugservers/uvsc/uvproject.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include #include #include diff --git a/src/plugins/baremetal/debugservers/uvsc/uvscserverprovider.cpp b/src/plugins/baremetal/debugservers/uvsc/uvscserverprovider.cpp index b95c333600d..a266ed7bfdc 100644 --- a/src/plugins/baremetal/debugservers/uvsc/uvscserverprovider.cpp +++ b/src/plugins/baremetal/debugservers/uvsc/uvscserverprovider.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -357,12 +358,12 @@ UvscServerProviderRunner::UvscServerProviderRunner(ProjectExplorer::RunControl * m_process.setCommand(runnable.command); - connect(&m_process, &QtcProcess::started, this, [this] { + connect(&m_process, &Process::started, this, [this] { ProcessHandle pid(m_process.processId()); this->runControl()->setApplicationProcessHandle(pid); reportStarted(); }); - connect(&m_process, &QtcProcess::done, this, [this] { + connect(&m_process, &Process::done, this, [this] { appendMessage(m_process.exitMessage(), NormalMessageFormat); reportStopped(); }); diff --git a/src/plugins/baremetal/debugservers/uvsc/uvscserverprovider.h b/src/plugins/baremetal/debugservers/uvsc/uvscserverprovider.h index d7b3bcafbad..4c14ee7b7c1 100644 --- a/src/plugins/baremetal/debugservers/uvsc/uvscserverprovider.h +++ b/src/plugins/baremetal/debugservers/uvsc/uvscserverprovider.h @@ -10,7 +10,7 @@ #include // for RunWorker -#include +#include namespace Utils { class PathChooser; } @@ -127,7 +127,7 @@ private: void start() final; void stop() final; - Utils::QtcProcess m_process; + Utils::Process m_process; }; } // namespace Internal diff --git a/src/plugins/baremetal/iarewtoolchain.cpp b/src/plugins/baremetal/iarewtoolchain.cpp index 80b5491ddff..c6653fefbb4 100644 --- a/src/plugins/baremetal/iarewtoolchain.cpp +++ b/src/plugins/baremetal/iarewtoolchain.cpp @@ -15,8 +15,8 @@ #include #include #include +#include #include -#include #include #include @@ -78,7 +78,7 @@ static Macros dumpPredefinedMacros(const FilePath &compiler, const QStringList & const QString outpath = fakeIn.fileName() + ".tmp"; - QtcProcess cpp; + Process cpp; cpp.setEnvironment(env); cpp.setTimeoutS(10); @@ -130,7 +130,7 @@ static HeaderPaths dumpHeaderPaths(const FilePath &compiler, const Id languageId cmd.addArg("--preinclude"); cmd.addArg("."); - QtcProcess cpp; + Process cpp; cpp.setEnvironment(env); cpp.setTimeoutS(10); cpp.setCommand(cmd); diff --git a/src/plugins/baremetal/keiltoolchain.cpp b/src/plugins/baremetal/keiltoolchain.cpp index 4c023ce3060..3bb2423ab23 100644 --- a/src/plugins/baremetal/keiltoolchain.cpp +++ b/src/plugins/baremetal/keiltoolchain.cpp @@ -15,8 +15,8 @@ #include #include #include +#include #include -#include #include #include @@ -113,7 +113,7 @@ static Macros dumpMcsPredefinedMacros(const FilePath &compiler, const Environmen fakeIn.close(); - QtcProcess cpp; + Process cpp; cpp.setEnvironment(env); cpp.setTimeoutS(10); cpp.setCommand({compiler, {fakeIn.fileName()}}); @@ -190,7 +190,7 @@ static Macros dumpC166PredefinedMacros(const FilePath &compiler, const Environme fakeIn.close(); - QtcProcess cpp; + Process cpp; cpp.setEnvironment(env); cpp.setTimeoutS(10); @@ -255,7 +255,7 @@ static Macros dumpC166PredefinedMacros(const FilePath &compiler, const Environme static Macros dumpArmPredefinedMacros(const FilePath &compiler, const QStringList &extraArgs, const Environment &env) { - QtcProcess cpp; + Process cpp; cpp.setEnvironment(env); cpp.setTimeoutS(10); diff --git a/src/plugins/baremetal/sdcctoolchain.cpp b/src/plugins/baremetal/sdcctoolchain.cpp index a16372eff97..a35a468d202 100644 --- a/src/plugins/baremetal/sdcctoolchain.cpp +++ b/src/plugins/baremetal/sdcctoolchain.cpp @@ -15,8 +15,8 @@ #include #include #include +#include #include -#include #include #include @@ -65,7 +65,7 @@ static Macros dumpPredefinedMacros(const FilePath &compiler, const Environment & return {}; fakeIn.close(); - QtcProcess cpp; + Process cpp; cpp.setEnvironment(env); cpp.setTimeoutS(10); cpp.setCommand({compiler, {compilerTargetFlag(abi), "-dM", "-E", fakeIn.fileName()}}); @@ -86,7 +86,7 @@ static HeaderPaths dumpHeaderPaths(const FilePath &compiler, const Environment & if (!compiler.exists()) return {}; - QtcProcess cpp; + Process cpp; cpp.setEnvironment(env); cpp.setTimeoutS(10); cpp.setCommand({compiler, {compilerTargetFlag(abi), "--print-search-dirs"}}); diff --git a/src/plugins/bazaar/bazaarclient.cpp b/src/plugins/bazaar/bazaarclient.cpp index 0b07bcd9ec0..303b7e21c28 100644 --- a/src/plugins/bazaar/bazaarclient.cpp +++ b/src/plugins/bazaar/bazaarclient.cpp @@ -29,13 +29,13 @@ namespace Bazaar::Internal { class BazaarDiffConfig : public VcsBaseEditorConfig { public: - BazaarDiffConfig(BazaarSettings &settings, QToolBar *toolBar) : - VcsBaseEditorConfig(toolBar) + explicit BazaarDiffConfig(QToolBar *toolBar) + : VcsBaseEditorConfig(toolBar) { mapSetting(addToggleButton("-w", Tr::tr("Ignore Whitespace")), - &settings.diffIgnoreWhiteSpace); + &settings().diffIgnoreWhiteSpace); mapSetting(addToggleButton("-B", Tr::tr("Ignore Blank Lines")), - &settings.diffIgnoreBlankLines); + &settings().diffIgnoreBlankLines); } QStringList arguments() const override @@ -54,18 +54,18 @@ public: class BazaarLogConfig : public VcsBaseEditorConfig { public: - BazaarLogConfig(BazaarSettings &settings, QToolBar *toolBar) : - VcsBaseEditorConfig(toolBar) + BazaarLogConfig(QToolBar *toolBar) + : VcsBaseEditorConfig(toolBar) { mapSetting(addToggleButton("--verbose", Tr::tr("Verbose"), Tr::tr("Show files changed in each revision.")), - &settings.logVerbose); + &settings().logVerbose); mapSetting(addToggleButton("--forward", Tr::tr("Forward"), Tr::tr("Show from oldest to newest.")), - &settings.logForward); + &settings().logForward); mapSetting(addToggleButton("--include-merges", Tr::tr("Include Merges"), Tr::tr("Show merged revisions.")), - &settings.logIncludeMerges); + &settings().logIncludeMerges); const QList logChoices = { {Tr::tr("Detailed"), "long"}, @@ -74,18 +74,14 @@ public: {Tr::tr("GNU Change Log"), "gnu-changelog"} }; mapSetting(addChoices(Tr::tr("Format"), { "--log-format=%1" }, logChoices), - &settings.logFormat); + &settings().logFormat); } }; -BazaarClient::BazaarClient(BazaarSettings *settings) : VcsBaseClient(settings) +BazaarClient::BazaarClient() : VcsBaseClient(&Internal::settings()) { - setDiffConfigCreator([settings](QToolBar *toolBar) { - return new BazaarDiffConfig(*settings, toolBar); - }); - setLogConfigCreator([settings](QToolBar *toolBar) { - return new BazaarLogConfig(*settings, toolBar); - }); + setDiffConfigCreator([](QToolBar *toolBar) { return new BazaarDiffConfig(toolBar); }); + setLogConfigCreator([](QToolBar *toolBar) { return new BazaarLogConfig(toolBar); }); } BranchInfo BazaarClient::synchronousBranchQuery(const FilePath &repositoryRoot) const diff --git a/src/plugins/bazaar/bazaarclient.h b/src/plugins/bazaar/bazaarclient.h index b84484b6875..5b6ce1e6d48 100644 --- a/src/plugins/bazaar/bazaarclient.h +++ b/src/plugins/bazaar/bazaarclient.h @@ -9,12 +9,10 @@ namespace Bazaar::Internal { -class BazaarSettings; - class BazaarClient : public VcsBase::VcsBaseClient { public: - explicit BazaarClient(BazaarSettings *settings); + BazaarClient(); BranchInfo synchronousBranchQuery(const Utils::FilePath &repositoryRoot) const; bool synchronousUncommit(const Utils::FilePath &workingDir, diff --git a/src/plugins/bazaar/bazaarcommitwidget.cpp b/src/plugins/bazaar/bazaarcommitwidget.cpp index 79d0b56b5a2..379fca66610 100644 --- a/src/plugins/bazaar/bazaarcommitwidget.cpp +++ b/src/plugins/bazaar/bazaarcommitwidget.cpp @@ -49,7 +49,7 @@ public: emailLineEdit = new QLineEdit; fixedBugsLineEdit = new QLineEdit; - using namespace Utils::Layouting; + using namespace Layouting; Column { Group { title(Tr::tr("General Information")), @@ -65,8 +65,9 @@ public: Tr::tr("Email:"), emailLineEdit, br, Tr::tr("Fixed bugs:"), fixedBugsLineEdit } - } - }.attachTo(this, WithoutMargins); + }, + noMargin + }.attachTo(this); } QLineEdit *branchLineEdit; diff --git a/src/plugins/bazaar/bazaarplugin.cpp b/src/plugins/bazaar/bazaarplugin.cpp index a0f980606d0..542c2ef7066 100644 --- a/src/plugins/bazaar/bazaarplugin.cpp +++ b/src/plugins/bazaar/bazaarplugin.cpp @@ -215,9 +215,8 @@ public: void createRepositoryActions(const Core::Context &context); // Variables - BazaarSettings m_settings; - BazaarClient m_client{&m_settings}; - BazaarSettingsPage m_settingPage{&m_settings}; + BazaarSettings m_setting; + BazaarClient m_client; VcsSubmitEditorFactory m_submitEditorFactory { submitEditorParameters, @@ -286,7 +285,7 @@ public: dryRunBtn->setToolTip(Tr::tr("Test the outcome of removing the last committed revision, without actually removing anything.")); buttonBox->addButton(dryRunBtn, QDialogButtonBox::ApplyRole); - using namespace Utils::Layouting; + using namespace Layouting; Column { Form { keepTagsCheckBox, br, @@ -372,7 +371,7 @@ BazaarPluginPrivate::BazaarPluginPrivate() toolsMenu->addMenu(m_bazaarContainer); m_menuAction = m_bazaarContainer->menu()->menuAction(); - connect(&m_settings, &AspectContainer::applied, this, &IVersionControl::configurationChanged); + connect(&settings(), &AspectContainer::applied, this, &IVersionControl::configurationChanged); } void BazaarPluginPrivate::createFileActions(const Context &context) @@ -387,7 +386,8 @@ void BazaarPluginPrivate::createFileActions(const Context &context) m_diffFile = new ParameterAction(Tr::tr("Diff Current File"), Tr::tr("Diff \"%1\""), ParameterAction::EnabledWithParameter, this); command = ActionManager::registerAction(m_diffFile, DIFF, context); command->setAttribute(Command::CA_UpdateText); - command->setDefaultKeySequence(QKeySequence(useMacShortcuts ? Tr::tr("Meta+Z,Meta+D") : Tr::tr("ALT+Z,Alt+D"))); + command->setDefaultKeySequence(QKeySequence(useMacShortcuts ? Tr::tr("Meta+Z,Meta+D") + : Tr::tr("Alt+Z,Alt+D"))); connect(m_diffFile, &QAction::triggered, this, &BazaarPluginPrivate::diffCurrentFile); m_bazaarContainer->addAction(command); m_commandLocator->appendCommand(command); @@ -395,7 +395,8 @@ void BazaarPluginPrivate::createFileActions(const Context &context) m_logFile = new ParameterAction(Tr::tr("Log Current File"), Tr::tr("Log \"%1\""), ParameterAction::EnabledWithParameter, this); command = ActionManager::registerAction(m_logFile, LOG, context); command->setAttribute(Command::CA_UpdateText); - command->setDefaultKeySequence(QKeySequence(useMacShortcuts ? Tr::tr("Meta+Z,Meta+L") : Tr::tr("ALT+Z,Alt+L"))); + command->setDefaultKeySequence(QKeySequence(useMacShortcuts ? Tr::tr("Meta+Z,Meta+L") + : Tr::tr("Alt+Z,Alt+L"))); connect(m_logFile, &QAction::triggered, this, &BazaarPluginPrivate::logCurrentFile); m_bazaarContainer->addAction(command); m_commandLocator->appendCommand(command); @@ -403,7 +404,8 @@ void BazaarPluginPrivate::createFileActions(const Context &context) m_statusFile = new ParameterAction(Tr::tr("Status Current File"), Tr::tr("Status \"%1\""), ParameterAction::EnabledWithParameter, this); command = ActionManager::registerAction(m_statusFile, STATUS, context); command->setAttribute(Command::CA_UpdateText); - command->setDefaultKeySequence(QKeySequence(useMacShortcuts ? Tr::tr("Meta+Z,Meta+S") : Tr::tr("ALT+Z,Alt+S"))); + command->setDefaultKeySequence(QKeySequence(useMacShortcuts ? Tr::tr("Meta+Z,Meta+S") + : Tr::tr("Alt+Z,Alt+S"))); connect(m_statusFile, &QAction::triggered, this, &BazaarPluginPrivate::statusCurrentFile); m_bazaarContainer->addAction(command); m_commandLocator->appendCommand(command); @@ -523,7 +525,7 @@ void BazaarPluginPrivate::logRepository() const VcsBasePluginState state = currentState(); QTC_ASSERT(state.hasTopLevel(), return); QStringList extraOptions; - extraOptions += "--limit=" + QString::number(m_settings.logCount.value()); + extraOptions += "--limit=" + QString::number(settings().logCount()); m_client.log(state.topLevel(), QStringList(), extraOptions); } @@ -571,7 +573,8 @@ void BazaarPluginPrivate::createRepositoryActions(const Context &context) action = new QAction(Tr::tr("Commit..."), this); m_repositoryActionList.append(action); command = ActionManager::registerAction(action, COMMIT, context); - command->setDefaultKeySequence(QKeySequence(useMacShortcuts ? Tr::tr("Meta+Z,Meta+C") : Tr::tr("ALT+Z,Alt+C"))); + command->setDefaultKeySequence(QKeySequence(useMacShortcuts ? Tr::tr("Meta+Z,Meta+C") + : Tr::tr("Alt+Z,Alt+C"))); connect(action, &QAction::triggered, this, &BazaarPluginPrivate::commit); m_bazaarContainer->addAction(command); m_commandLocator->appendCommand(command); @@ -705,8 +708,8 @@ void BazaarPluginPrivate::showCommitWidget(const QListsetFields(m_submitRepository, branch, - m_settings.userName.value(), - m_settings.userEmail.value(), status); + settings().userName(), + settings().userEmail(), status); } void BazaarPluginPrivate::diffFromEditorSelected(const QStringList &files) @@ -872,7 +875,7 @@ bool BazaarPluginPrivate::managesFile(const FilePath &workingDirectory, const QS bool BazaarPluginPrivate::isConfigured() const { - const FilePath binary = m_settings.binaryPath.filePath(); + const FilePath binary = settings().binaryPath(); return !binary.isEmpty() && binary.isExecutableFile(); } diff --git a/src/plugins/bazaar/bazaarsettings.cpp b/src/plugins/bazaar/bazaarsettings.cpp index 7149f93251c..dc6ef77dd48 100644 --- a/src/plugins/bazaar/bazaarsettings.cpp +++ b/src/plugins/bazaar/bazaarsettings.cpp @@ -16,95 +16,79 @@ using namespace Utils; namespace Bazaar::Internal { +static BazaarSettings *theSettings; + +BazaarSettings &settings() +{ + return *theSettings; +} + BazaarSettings::BazaarSettings() { - setSettingsGroup(Constants::BAZAAR); - setAutoApply(false); + theSettings = this; + + setSettingsGroup(Constants::BAZAAR); + setId(VcsBase::Constants::VCS_ID_BAZAAR); + setDisplayName(Tr::tr("Bazaar")); + setCategory(VcsBase::Constants::VCS_SETTINGS_CATEGORY); - registerAspect(&binaryPath); - binaryPath.setDisplayStyle(StringAspect::PathChooserDisplay); binaryPath.setExpectedKind(PathChooser::ExistingCommand); binaryPath.setDefaultValue(Constants::BAZAARDEFAULT); binaryPath.setDisplayName(Tr::tr("Bazaar Command")); binaryPath.setHistoryCompleter("Bazaar.Command.History"); binaryPath.setLabelText(Tr::tr("Command:")); - registerAspect(&diffIgnoreWhiteSpace); diffIgnoreWhiteSpace.setSettingsKey("diffIgnoreWhiteSpace"); - registerAspect(&diffIgnoreBlankLines); diffIgnoreBlankLines.setSettingsKey("diffIgnoreBlankLines"); - registerAspect(&logVerbose); logVerbose.setSettingsKey("logVerbose"); - registerAspect(&logFormat); logForward.setSettingsKey("logForward"); - registerAspect(&logIncludeMerges); logIncludeMerges.setSettingsKey("logIncludeMerges"); - registerAspect(&logFormat); logFormat.setDisplayStyle(StringAspect::LineEditDisplay); logFormat.setSettingsKey("logFormat"); logFormat.setDefaultValue("long"); - registerAspect(&userName); userName.setDisplayStyle(StringAspect::LineEditDisplay); userName.setLabelText(Tr::tr("Default username:")); userName.setToolTip(Tr::tr("Username to use by default on commit.")); - registerAspect(&userEmail); userEmail.setDisplayStyle(StringAspect::LineEditDisplay); userEmail.setLabelText(Tr::tr("Default email:")); userEmail.setToolTip(Tr::tr("Email to use by default on commit.")); - registerAspect(&logCount); logCount.setLabelText(Tr::tr("Log count:")); logCount.setToolTip(Tr::tr("The number of recent commit logs to show. Choose 0 to see all entries.")); - registerAspect(&logCount); timeout.setLabelText(Tr::tr("Timeout:")); timeout.setSuffix(Tr::tr("s")); -} -// BazaarSettingsPage - -BazaarSettingsPage::BazaarSettingsPage(BazaarSettings *settings) -{ - setId(VcsBase::Constants::VCS_ID_BAZAAR); - setDisplayName(Tr::tr("Bazaar")); - setCategory(VcsBase::Constants::VCS_SETTINGS_CATEGORY); - setSettings(settings); - - setLayouter([settings](QWidget *widget) { - BazaarSettings &s = *settings; + setLayouter([this] { using namespace Layouting; - Column { + return Column { Group { title(Tr::tr("Configuration")), - Row { s.binaryPath } + Row { binaryPath } }, Group { title(Tr::tr("User")), Form { - s.userName, - s.userEmail + userName, br, + userEmail } }, Group { title(Tr::tr("Miscellaneous")), - Row { - s.logCount, - s.timeout, - st - } + Row { logCount, timeout, st } }, st - }.attachTo(widget); + }; }); } diff --git a/src/plugins/bazaar/bazaarsettings.h b/src/plugins/bazaar/bazaarsettings.h index a10828b42df..0e6cc0d8520 100644 --- a/src/plugins/bazaar/bazaarsettings.h +++ b/src/plugins/bazaar/bazaarsettings.h @@ -3,8 +3,6 @@ #pragma once -#include - #include namespace Bazaar::Internal { @@ -14,18 +12,14 @@ class BazaarSettings final : public VcsBase::VcsBaseSettings public: BazaarSettings(); - Utils::BoolAspect diffIgnoreWhiteSpace; - Utils::BoolAspect diffIgnoreBlankLines; - Utils::BoolAspect logVerbose; - Utils::BoolAspect logForward; - Utils::BoolAspect logIncludeMerges; - Utils::StringAspect logFormat; + Utils::BoolAspect diffIgnoreWhiteSpace{this}; + Utils::BoolAspect diffIgnoreBlankLines{this}; + Utils::BoolAspect logVerbose{this}; + Utils::BoolAspect logForward{this}; + Utils::BoolAspect logIncludeMerges{this}; + Utils::StringAspect logFormat{this}; }; -class BazaarSettingsPage final : public Core::IOptionsPage -{ -public: - explicit BazaarSettingsPage(BazaarSettings *settings); -}; +BazaarSettings &settings(); } // Bazaar::Internal diff --git a/src/plugins/bazaar/pullorpushdialog.cpp b/src/plugins/bazaar/pullorpushdialog.cpp index 10a4ce0452a..4c2cd99e7c5 100644 --- a/src/plugins/bazaar/pullorpushdialog.cpp +++ b/src/plugins/bazaar/pullorpushdialog.cpp @@ -72,7 +72,7 @@ PullOrPushDialog::PullOrPushDialog(Mode mode, QWidget *parent) m_localCheckBox->setVisible(false); } - using namespace Utils::Layouting; + using namespace Layouting; Column { Group { title(Tr::tr("Branch Location")), diff --git a/src/plugins/beautifier/CMakeLists.txt b/src/plugins/beautifier/CMakeLists.txt index d721f1ef201..4e921342b7a 100644 --- a/src/plugins/beautifier/CMakeLists.txt +++ b/src/plugins/beautifier/CMakeLists.txt @@ -5,7 +5,6 @@ add_qtc_plugin(Beautifier abstractsettings.cpp abstractsettings.h artisticstyle/artisticstyle.cpp artisticstyle/artisticstyle.h artisticstyle/artisticstyleconstants.h - artisticstyle/artisticstyleoptionspage.cpp artisticstyle/artisticstyleoptionspage.h artisticstyle/artisticstylesettings.cpp artisticstyle/artisticstylesettings.h beautifier.qrc beautifierabstracttool.h @@ -14,15 +13,12 @@ add_qtc_plugin(Beautifier beautifiertr.h clangformat/clangformat.cpp clangformat/clangformat.h clangformat/clangformatconstants.h - clangformat/clangformatoptionspage.cpp clangformat/clangformatoptionspage.h clangformat/clangformatsettings.cpp clangformat/clangformatsettings.h configurationdialog.cpp configurationdialog.h configurationeditor.cpp configurationeditor.h configurationpanel.cpp configurationpanel.h - generaloptionspage.cpp generaloptionspage.h generalsettings.cpp generalsettings.h uncrustify/uncrustify.cpp uncrustify/uncrustify.h uncrustify/uncrustifyconstants.h - uncrustify/uncrustifyoptionspage.cpp uncrustify/uncrustifyoptionspage.h uncrustify/uncrustifysettings.cpp uncrustify/uncrustifysettings.h ) diff --git a/src/plugins/beautifier/abstractsettings.cpp b/src/plugins/beautifier/abstractsettings.cpp index 0b96e1579ea..1b4ff3aa10e 100644 --- a/src/plugins/beautifier/abstractsettings.cpp +++ b/src/plugins/beautifier/abstractsettings.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include @@ -26,15 +26,12 @@ using namespace Utils; namespace Beautifier::Internal { -const char COMMAND[] = "command"; -const char SUPPORTED_MIME[] = "supportedMime"; - class VersionUpdater { public: VersionUpdater() { - QObject::connect(&m_process, &QtcProcess::done, &m_process, [this] { + QObject::connect(&m_process, &Process::done, &m_process, [this] { if (m_process.result() != ProcessResult::FinishedWithSuccess) return; @@ -77,7 +74,7 @@ private: } QRegularExpression m_versionRegExp; - mutable QtcProcess m_process; + mutable Process m_process; QVersionNumber m_versionNumber; }; @@ -86,9 +83,38 @@ AbstractSettings::AbstractSettings(const QString &name, const QString &ending) , m_styleDir(Core::ICore::userResourcePath(Beautifier::Constants::SETTINGS_DIRNAME) .pathAppended(name) .toString()) - , m_name(name) , m_versionUpdater(new VersionUpdater) { + setSettingsGroups(Utils::Constants::BEAUTIFIER_SETTINGS_GROUP, name); + + command.setSettingsKey("command"); + command.setExpectedKind(Utils::PathChooser::ExistingCommand); + command.setCommandVersionArguments({"--version"}); + command.setPromptDialogTitle(BeautifierPlugin::msgCommandPromptDialogTitle("Clang Format")); + + supportedMimeTypes.setDisplayStyle(StringAspect::LineEditDisplay); + supportedMimeTypes.setSettingsKey("supportedMime"); + supportedMimeTypes.setLabelText(Tr::tr("Restrict to MIME types:")); + supportedMimeTypes.setDefaultValue("text/x-c++src; text/x-c++hdr; text/x-csrc;text/x-chdr; " + "text/x-objcsrc; text/x-objc++src"); + + supportedMimeTypes.setValueAcceptor([](const QString &, const QString &value) -> std::optional { + const QStringList stringTypes = value.split(';'); + QStringList types; + for (const QString &type : stringTypes) { + const MimeType mime = mimeTypeForName(type.trimmed()); + if (!mime.isValid()) + continue; + const QString canonicalName = mime.name(); + if (!types.contains(canonicalName)) + types << canonicalName; + } + return types.join("; "); + }); + + connect(&command, &BaseAspect::changed, this, [this] { + m_versionUpdater->update(command()); + }); } AbstractSettings::~AbstractSettings() = default; @@ -157,20 +183,6 @@ QString AbstractSettings::styleFileName(const QString &key) const return m_styleDir.absoluteFilePath(key + m_ending); } -FilePath AbstractSettings::command() const -{ - return m_command; -} - -void AbstractSettings::setCommand(const FilePath &cmd) -{ - if (cmd == m_command) - return; - - m_command = cmd; - m_versionUpdater->update(command()); -} - QVersionNumber AbstractSettings::version() const { return m_versionUpdater->version(); @@ -181,30 +193,6 @@ void AbstractSettings::setVersionRegExp(const QRegularExpression &versionRegExp) m_versionUpdater->setVersionRegExp(versionRegExp); } -QString AbstractSettings::supportedMimeTypesAsString() const -{ - return m_supportedMimeTypes.join("; "); -} - -void AbstractSettings::setSupportedMimeTypes(const QString &mimes) -{ - const QStringList stringTypes = mimes.split(';'); - QStringList types; - for (const QString &type : stringTypes) { - const MimeType mime = mimeTypeForName(type.trimmed()); - if (!mime.isValid()) - continue; - const QString canonicalName = mime.name(); - if (!types.contains(canonicalName)) - types << canonicalName; - } - - if (m_supportedMimeTypes != types) { - m_supportedMimeTypes = types; - emit supportedMimeTypesChanged(); - } -} - bool AbstractSettings::isApplicable(const Core::IDocument *document) const { if (!document) @@ -240,17 +228,8 @@ void AbstractSettings::save() { // Save settings, except styles QSettings *s = Core::ICore::settings(); - s->beginGroup(Utils::Constants::BEAUTIFIER_SETTINGS_GROUP); - s->beginGroup(m_name); - QMap::const_iterator iSettings = m_settings.constBegin(); - while (iSettings != m_settings.constEnd()) { - s->setValue(iSettings.key(), iSettings.value()); - ++iSettings; - } - s->setValue(COMMAND, m_command.toSettings()); - s->setValue(SUPPORTED_MIME, supportedMimeTypesAsString()); - s->endGroup(); - s->endGroup(); + + AspectContainer::writeSettings(s); // Save styles if (m_stylesToRemove.isEmpty() && m_styles.isEmpty()) @@ -306,27 +285,9 @@ void AbstractSettings::createDocumentationFile() const void AbstractSettings::read() { - // Set default values - setSupportedMimeTypes("text/x-c++src;text/x-c++hdr;text/x-csrc;text/x-chdr;text/x-objcsrc;" - "text/x-objc++src"); - // Read settings, except styles QSettings *s = Core::ICore::settings(); - s->beginGroup(Utils::Constants::BEAUTIFIER_SETTINGS_GROUP); - s->beginGroup(m_name); - const QStringList keys = s->allKeys(); - for (const QString &key : keys) { - if (key == COMMAND) - setCommand(FilePath::fromSettings(s->value(key))); - else if (key == SUPPORTED_MIME) - setSupportedMimeTypes(s->value(key).toString()); - else if (m_settings.contains(key)) - m_settings[key] = s->value(key); - else - s->remove(key); - } - s->endGroup(); - s->endGroup(); + AspectContainer::readSettings(s); m_styles.clear(); m_changedStyles.clear(); @@ -336,18 +297,19 @@ void AbstractSettings::read() void AbstractSettings::readDocumentation() { - const QString filename = documentationFilePath(); + const FilePath filename = documentationFilePath; if (filename.isEmpty()) { BeautifierPlugin::showError(Tr::tr("No documentation file specified.")); return; } - QFile file(filename); + QFile file(filename.toFSPathString()); if (!file.exists()) createDocumentationFile(); if (!file.open(QIODevice::ReadOnly)) { - BeautifierPlugin::showError(Tr::tr("Cannot open documentation file \"%1\".").arg(filename)); + BeautifierPlugin::showError(Tr::tr("Cannot open documentation file \"%1\".") + .arg(filename.toUserOutput())); return; } @@ -356,7 +318,7 @@ void AbstractSettings::readDocumentation() return; if (xml.name() != QLatin1String(Constants::DOCUMENTATION_XMLROOT)) { BeautifierPlugin::showError(Tr::tr("The file \"%1\" is not a valid documentation file.") - .arg(filename)); + .arg(filename.toUserOutput())); return; } @@ -387,7 +349,7 @@ void AbstractSettings::readDocumentation() if (xml.hasError()) { BeautifierPlugin::showError(Tr::tr("Cannot read documentation file \"%1\": %2.") - .arg(filename).arg(xml.errorString())); + .arg(filename.toUserOutput()).arg(xml.errorString())); } } diff --git a/src/plugins/beautifier/abstractsettings.h b/src/plugins/beautifier/abstractsettings.h index d386313e1b5..8e9ceea931d 100644 --- a/src/plugins/beautifier/abstractsettings.h +++ b/src/plugins/beautifier/abstractsettings.h @@ -3,17 +3,17 @@ #pragma once +#include + +#include #include -#include #include #include #include -#include #include #include #include -#include #include @@ -28,10 +28,8 @@ namespace Beautifier::Internal { class VersionUpdater; -class AbstractSettings : public QObject +class AbstractSettings : public Utils::AspectContainer { - Q_OBJECT - public: explicit AbstractSettings(const QString &name, const QString &ending); ~AbstractSettings() override; @@ -39,7 +37,6 @@ public: void read(); void save(); - virtual QString documentationFilePath() const = 0; virtual void createDocumentationFile() const; virtual QStringList completerWords(); @@ -52,25 +49,22 @@ public: void replaceStyle(const QString &oldKey, const QString &newKey, const QString &value); virtual QString styleFileName(const QString &key) const; - Utils::FilePath command() const; - void setCommand(const Utils::FilePath &cmd); + Utils::FilePathAspect command{this}; + Utils::StringAspect supportedMimeTypes{this}; + + Utils::FilePath documentationFilePath; + QVersionNumber version() const; - QString supportedMimeTypesAsString() const; - void setSupportedMimeTypes(const QString &mimes); bool isApplicable(const Core::IDocument *document) const; QStringList options(); QString documentation(const QString &option) const; -signals: - void supportedMimeTypesChanged(); - protected: void setVersionRegExp(const QRegularExpression &versionRegExp); QMap m_styles; - QMap m_settings; QString m_ending; QDir m_styleDir; @@ -78,11 +72,9 @@ protected: virtual void readStyles(); private: - QString m_name; std::unique_ptr m_versionUpdater; QStringList m_stylesToRemove; QSet m_changedStyles; - Utils::FilePath m_command; QHash m_options; QStringList m_docu; QStringList m_supportedMimeTypes; diff --git a/src/plugins/beautifier/artisticstyle/artisticstyle.cpp b/src/plugins/beautifier/artisticstyle/artisticstyle.cpp index 610f636d743..1f063829944 100644 --- a/src/plugins/beautifier/artisticstyle/artisticstyle.cpp +++ b/src/plugins/beautifier/artisticstyle/artisticstyle.cpp @@ -48,7 +48,7 @@ ArtisticStyle::ArtisticStyle() Core::ActionManager::actionContainer(Constants::MENU_ID)->addMenu(menu); - connect(&m_settings, &ArtisticStyleSettings::supportedMimeTypesChanged, + connect(&m_settings.supportedMimeTypes, &Utils::BaseAspect::changed, this, [this] { updateActions(Core::EditorManager::currentEditor()); }); } diff --git a/src/plugins/beautifier/artisticstyle/artisticstyle.h b/src/plugins/beautifier/artisticstyle/artisticstyle.h index ef394d068aa..3f3f15946af 100644 --- a/src/plugins/beautifier/artisticstyle/artisticstyle.h +++ b/src/plugins/beautifier/artisticstyle/artisticstyle.h @@ -5,7 +5,6 @@ #include "../beautifierabstracttool.h" -#include "artisticstyleoptionspage.h" #include "artisticstylesettings.h" namespace Beautifier::Internal { diff --git a/src/plugins/beautifier/artisticstyle/artisticstyleoptionspage.cpp b/src/plugins/beautifier/artisticstyle/artisticstyleoptionspage.cpp deleted file mode 100644 index 0c0735be0ed..00000000000 --- a/src/plugins/beautifier/artisticstyle/artisticstyleoptionspage.cpp +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (C) 2016 Lorenz Haas -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "artisticstyleoptionspage.h" - -#include "artisticstyleconstants.h" -#include "artisticstylesettings.h" - -#include "../beautifierconstants.h" -#include "../beautifierplugin.h" -#include "../beautifiertr.h" -#include "../configurationpanel.h" - -#include -#include - -#include -#include -#include -#include -#include - -namespace Beautifier::Internal { - -class ArtisticStyleOptionsPageWidget : public Core::IOptionsPageWidget -{ -public: - explicit ArtisticStyleOptionsPageWidget(ArtisticStyleSettings *settings); - - void apply() final; - -private: - ArtisticStyleSettings *m_settings; - - Utils::PathChooser *m_command; - QLineEdit *m_mime; - QCheckBox *m_useOtherFiles; - QCheckBox *m_useSpecificConfigFile; - Utils::PathChooser *m_specificConfigFile; - QCheckBox *m_useHomeFile; - QCheckBox *m_useCustomStyle; - Beautifier::Internal::ConfigurationPanel *m_configurations; -}; - -ArtisticStyleOptionsPageWidget::ArtisticStyleOptionsPageWidget(ArtisticStyleSettings *settings) - : m_settings(settings) -{ - resize(817, 631); - - m_command = new Utils::PathChooser; - - m_mime = new QLineEdit(m_settings->supportedMimeTypesAsString()); - - auto options = new QGroupBox(Tr::tr("Options")); - - m_useOtherFiles = new QCheckBox(Tr::tr("Use file *.astylerc defined in project files")); - m_useOtherFiles->setChecked(m_settings->useOtherFiles()); - - m_useSpecificConfigFile = new QCheckBox(Tr::tr("Use specific config file:")); - m_useSpecificConfigFile->setChecked(m_settings->useSpecificConfigFile()); - - m_specificConfigFile = new Utils::PathChooser; - m_specificConfigFile->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - m_specificConfigFile->setExpectedKind(Utils::PathChooser::File); - m_specificConfigFile->setPromptDialogFilter(Tr::tr("AStyle (*.astylerc)")); - m_specificConfigFile->setFilePath(m_settings->specificConfigFile()); - - m_useHomeFile = new QCheckBox( - Tr::tr("Use file .astylerc or astylerc in HOME"). - replace("HOME", QDir::toNativeSeparators(QDir::home().absolutePath()))); - m_useHomeFile->setChecked(m_settings->useHomeFile()); - - m_useCustomStyle = new QCheckBox(Tr::tr("Use customized style:")); - m_useCustomStyle->setChecked(m_settings->useCustomStyle()); - - m_configurations = new ConfigurationPanel(options); - m_configurations->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - m_configurations->setSettings(m_settings); - m_configurations->setCurrentConfiguration(m_settings->customStyle()); - - m_command->setExpectedKind(Utils::PathChooser::ExistingCommand); - m_command->setCommandVersionArguments({"--version"}); - m_command->setPromptDialogTitle(BeautifierPlugin::msgCommandPromptDialogTitle( - Tr::tr(Constants::ARTISTICSTYLE_DISPLAY_NAME))); - m_command->setFilePath(m_settings->command()); - - using namespace Utils::Layouting; - - Column { - m_useOtherFiles, - Row { m_useSpecificConfigFile, m_specificConfigFile }, - m_useHomeFile, - Row { m_useCustomStyle, m_configurations } - }.attachTo(options); - - Column { - Group { - title(Tr::tr("Configuration")), - Form { - Tr::tr("Artistic Style command:"), m_command, br, - Tr::tr("Restrict to MIME types:"), m_mime - } - }, - options, - st - }.attachTo(this); - - connect(m_command, &Utils::PathChooser::validChanged, options, &QWidget::setEnabled); -} - -void ArtisticStyleOptionsPageWidget::apply() -{ - m_settings->setCommand(m_command->filePath()); - m_settings->setSupportedMimeTypes(m_mime->text()); - m_settings->setUseOtherFiles(m_useOtherFiles->isChecked()); - m_settings->setUseSpecificConfigFile(m_useSpecificConfigFile->isChecked()); - m_settings->setSpecificConfigFile(m_specificConfigFile->filePath()); - m_settings->setUseHomeFile(m_useHomeFile->isChecked()); - m_settings->setUseCustomStyle(m_useCustomStyle->isChecked()); - m_settings->setCustomStyle(m_configurations->currentConfiguration()); - m_settings->save(); - - // update since not all MIME types are accepted (invalids or duplicates) - m_mime->setText(m_settings->supportedMimeTypesAsString()); -} - -ArtisticStyleOptionsPage::ArtisticStyleOptionsPage(ArtisticStyleSettings *settings) -{ - setId("ArtisticStyle"); - setDisplayName(Tr::tr("Artistic Style")); - setCategory(Constants::OPTION_CATEGORY); - setWidgetCreator([settings] { return new ArtisticStyleOptionsPageWidget(settings); }); -} - -} // Beautifier::Internal diff --git a/src/plugins/beautifier/artisticstyle/artisticstyleoptionspage.h b/src/plugins/beautifier/artisticstyle/artisticstyleoptionspage.h deleted file mode 100644 index 161e20f7067..00000000000 --- a/src/plugins/beautifier/artisticstyle/artisticstyleoptionspage.h +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (C) 2016 Lorenz Haas -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -namespace Beautifier::Internal { - -class ArtisticStyleSettings; - -class ArtisticStyleOptionsPage final : public Core::IOptionsPage -{ -public: - explicit ArtisticStyleOptionsPage(ArtisticStyleSettings *settings); -}; - -} // Beautifier::Internal diff --git a/src/plugins/beautifier/artisticstyle/artisticstylesettings.cpp b/src/plugins/beautifier/artisticstyle/artisticstylesettings.cpp index 1cff800ed1f..415d4ab20d6 100644 --- a/src/plugins/beautifier/artisticstyle/artisticstylesettings.cpp +++ b/src/plugins/beautifier/artisticstyle/artisticstylesettings.cpp @@ -3,16 +3,24 @@ #include "artisticstylesettings.h" +#include "artisticstyleconstants.h" #include "../beautifierconstants.h" +#include "../beautifierplugin.h" +#include "../beautifiertr.h" +#include "../configurationpanel.h" #include +#include +#include +#include #include -#include +#include #include #include #include +#include #include #include @@ -20,106 +28,56 @@ using namespace Utils; namespace Beautifier::Internal { -const char USE_OTHER_FILES[] = "useOtherFiles"; -const char USE_SPECIFIC_CONFIG_FILE[] = "useSpecificConfigFile"; -const char SPECIFIC_CONFIG_FILE[] = "specificConfigFile"; -const char USE_HOME_FILE[] = "useHomeFile"; -const char USE_CUSTOM_STYLE[] = "useCustomStyle"; -const char CUSTOM_STYLE[] = "customStyle"; const char SETTINGS_NAME[] = "artisticstyle"; -ArtisticStyleSettings::ArtisticStyleSettings() : - AbstractSettings(SETTINGS_NAME, ".astyle") +ArtisticStyleSettings::ArtisticStyleSettings() + : AbstractSettings(SETTINGS_NAME, ".astyle") { setVersionRegExp(QRegularExpression("([2-9]{1})\\.([0-9]{1,2})(\\.[1-9]{1})?$")); - setCommand("astyle"); - m_settings.insert(USE_OTHER_FILES, QVariant(true)); - m_settings.insert(USE_SPECIFIC_CONFIG_FILE, QVariant(false)); - m_settings.insert(SPECIFIC_CONFIG_FILE, QVariant()); - m_settings.insert(USE_HOME_FILE, QVariant(false)); - m_settings.insert(USE_CUSTOM_STYLE, QVariant(false)); - m_settings.insert(CUSTOM_STYLE, QVariant()); + command.setLabelText(Tr::tr("Artistic Style command:")); + command.setDefaultValue("astyle"); + command.setPromptDialogTitle(BeautifierPlugin::msgCommandPromptDialogTitle( + Tr::tr(Constants::ARTISTICSTYLE_DISPLAY_NAME))); + + useOtherFiles.setSettingsKey("useOtherFiles"); + useOtherFiles.setLabelText(Tr::tr("Use file *.astylerc defined in project files")); + useOtherFiles.setDefaultValue(true); + + useSpecificConfigFile.setSettingsKey("useSpecificConfigFile"); + useSpecificConfigFile.setLabelText(Tr::tr("Use specific config file:")); + + specificConfigFile.setSettingsKey("specificConfigFile"); + specificConfigFile.setExpectedKind(PathChooser::File); + specificConfigFile.setPromptDialogFilter(Tr::tr("AStyle (*.astylerc)")); + + useHomeFile.setSettingsKey("useHomeFile"); + useHomeFile.setLabelText(Tr::tr("Use file .astylerc or astylerc in HOME"). + replace("HOME", QDir::toNativeSeparators(QDir::home().absolutePath()))); + + useCustomStyle.setSettingsKey("useCustomStyle"); + useCustomStyle.setLabelText(Tr::tr("Use customized style:")); + + customStyle.setSettingsKey("customStyle"); + + documentationFilePath = + Core::ICore::userResourcePath(Beautifier::Constants::SETTINGS_DIRNAME) + .pathAppended(Beautifier::Constants::DOCUMENTATION_DIRNAME) + .pathAppended(SETTINGS_NAME) + .stringAppended(".xml"); + read(); } -bool ArtisticStyleSettings::useOtherFiles() const -{ - return m_settings.value(USE_OTHER_FILES).toBool(); -} - -void ArtisticStyleSettings::setUseOtherFiles(bool useOtherFiles) -{ - m_settings.insert(USE_OTHER_FILES, QVariant(useOtherFiles)); -} - -bool ArtisticStyleSettings::useSpecificConfigFile() const -{ - return m_settings.value(USE_SPECIFIC_CONFIG_FILE).toBool(); -} - -void ArtisticStyleSettings::setUseSpecificConfigFile(bool useSpecificConfigFile) -{ - m_settings.insert(USE_SPECIFIC_CONFIG_FILE, QVariant(useSpecificConfigFile)); -} - -FilePath ArtisticStyleSettings::specificConfigFile() const -{ - return FilePath::fromString(m_settings.value(SPECIFIC_CONFIG_FILE).toString()); -} - -void ArtisticStyleSettings::setSpecificConfigFile(const FilePath &specificConfigFile) -{ - m_settings.insert(SPECIFIC_CONFIG_FILE, QVariant(specificConfigFile.toString())); -} - -bool ArtisticStyleSettings::useHomeFile() const -{ - return m_settings.value(USE_HOME_FILE).toBool(); -} - -void ArtisticStyleSettings::setUseHomeFile(bool useHomeFile) -{ - m_settings.insert(USE_HOME_FILE, QVariant(useHomeFile)); -} - -bool ArtisticStyleSettings::useCustomStyle() const -{ - return m_settings.value(USE_CUSTOM_STYLE).toBool(); -} - -void ArtisticStyleSettings::setUseCustomStyle(bool useCustomStyle) -{ - m_settings.insert(USE_CUSTOM_STYLE, QVariant(useCustomStyle)); -} - -QString ArtisticStyleSettings::customStyle() const -{ - return m_settings.value(CUSTOM_STYLE).toString(); -} - -void ArtisticStyleSettings::setCustomStyle(const QString &customStyle) -{ - m_settings.insert(CUSTOM_STYLE, QVariant(customStyle)); -} - -QString ArtisticStyleSettings::documentationFilePath() const -{ - return (Core::ICore::userResourcePath(Beautifier::Constants::SETTINGS_DIRNAME) - / Beautifier::Constants::DOCUMENTATION_DIRNAME / SETTINGS_NAME) - .stringAppended(".xml") - .toString(); -} - void ArtisticStyleSettings::createDocumentationFile() const { - QtcProcess process; + Process process; process.setTimeoutS(2); process.setCommand({command(), {"-h"}}); process.runBlocking(); if (process.result() != ProcessResult::FinishedWithSuccess) return; - QFile file(documentationFilePath()); + QFile file(documentationFilePath.toFSPathString()); const QFileInfo fi(file); if (!fi.exists()) fi.dir().mkpath(fi.absolutePath()); @@ -183,4 +141,61 @@ void ArtisticStyleSettings::createDocumentationFile() const } } +class ArtisticStyleOptionsPageWidget : public Core::IOptionsPageWidget +{ +public: + explicit ArtisticStyleOptionsPageWidget(ArtisticStyleSettings *settings) + { + QGroupBox *options = nullptr; + + auto configurations = new ConfigurationPanel(this); + configurations->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + configurations->setSettings(settings); + configurations->setCurrentConfiguration(settings->customStyle()); + + using namespace Layouting; + + ArtisticStyleSettings &s = *settings; + + Column { + Group { + title(Tr::tr("Configuration")), + Form { + s.command, br, + s.supportedMimeTypes + } + }, + Group { + title(Tr::tr("Options")), + bindTo(&options), + Column { + s.useOtherFiles, + Row { s.useSpecificConfigFile, s.specificConfigFile }, + s.useHomeFile, + Row { s.useCustomStyle, configurations }, + } + }, + st + }.attachTo(this); + + setOnApply([&s, configurations] { + s.customStyle.setValue(configurations->currentConfiguration()); + s.save(); + }); + + s.read(); + + connect(s.command.pathChooser(), &PathChooser::validChanged, options, &QWidget::setEnabled); + options->setEnabled(s.command.pathChooser()->isValid()); + } +}; + +ArtisticStyleOptionsPage::ArtisticStyleOptionsPage(ArtisticStyleSettings *settings) +{ + setId("ArtisticStyle"); + setDisplayName(Tr::tr("Artistic Style")); + setCategory(Constants::OPTION_CATEGORY); + setWidgetCreator([settings] { return new ArtisticStyleOptionsPageWidget(settings); }); +} + } // Beautifier::Internal diff --git a/src/plugins/beautifier/artisticstyle/artisticstylesettings.h b/src/plugins/beautifier/artisticstyle/artisticstylesettings.h index de5402a8ee5..25932cf8db8 100644 --- a/src/plugins/beautifier/artisticstyle/artisticstylesettings.h +++ b/src/plugins/beautifier/artisticstyle/artisticstylesettings.h @@ -5,8 +5,6 @@ #include "../abstractsettings.h" -#include - namespace Beautifier::Internal { class ArtisticStyleSettings : public AbstractSettings @@ -14,26 +12,20 @@ class ArtisticStyleSettings : public AbstractSettings public: ArtisticStyleSettings(); - bool useOtherFiles() const; - void setUseOtherFiles(bool useOtherFiles); + Utils::BoolAspect useOtherFiles{this}; + Utils::BoolAspect useSpecificConfigFile{this}; + Utils::FilePathAspect specificConfigFile{this}; + Utils::BoolAspect useHomeFile{this}; + Utils::BoolAspect useCustomStyle{this}; + Utils::StringAspect customStyle{this}; - bool useSpecificConfigFile() const; - void setUseSpecificConfigFile(bool useSpecificConfigFile); - - Utils::FilePath specificConfigFile() const; - void setSpecificConfigFile(const Utils::FilePath &specificConfigFile); - - bool useHomeFile() const; - void setUseHomeFile(bool useHomeFile); - - bool useCustomStyle() const; - void setUseCustomStyle(bool useCustomStyle); - - QString customStyle() const; - void setCustomStyle(const QString &customStyle); - - QString documentationFilePath() const override; void createDocumentationFile() const override; }; +class ArtisticStyleOptionsPage final : public Core::IOptionsPage +{ +public: + explicit ArtisticStyleOptionsPage(ArtisticStyleSettings *settings); +}; + } // Beautifier::Internal diff --git a/src/plugins/beautifier/beautifier.qbs b/src/plugins/beautifier/beautifier.qbs index d1160ae5f59..24fecd671f1 100644 --- a/src/plugins/beautifier/beautifier.qbs +++ b/src/plugins/beautifier/beautifier.qbs @@ -25,8 +25,6 @@ QtcPlugin { "configurationeditor.h", "configurationpanel.cpp", "configurationpanel.h", - "generaloptionspage.cpp", - "generaloptionspage.h", "generalsettings.cpp", "generalsettings.h", ] @@ -38,8 +36,6 @@ QtcPlugin { "artisticstyle.cpp", "artisticstyle.h", "artisticstyleconstants.h", - "artisticstyleoptionspage.cpp", - "artisticstyleoptionspage.h", "artisticstylesettings.cpp", "artisticstylesettings.h" ] @@ -52,8 +48,6 @@ QtcPlugin { "clangformat.cpp", "clangformat.h", "clangformatconstants.h", - "clangformatoptionspage.cpp", - "clangformatoptionspage.h", "clangformatsettings.cpp", "clangformatsettings.h" ] @@ -66,8 +60,6 @@ QtcPlugin { "uncrustify.cpp", "uncrustify.h", "uncrustifyconstants.h", - "uncrustifyoptionspage.cpp", - "uncrustifyoptionspage.h", "uncrustifysettings.cpp", "uncrustifysettings.h" ] diff --git a/src/plugins/beautifier/beautifierplugin.cpp b/src/plugins/beautifier/beautifierplugin.cpp index 76db40daeab..b849976a05b 100644 --- a/src/plugins/beautifier/beautifierplugin.cpp +++ b/src/plugins/beautifier/beautifierplugin.cpp @@ -5,7 +5,6 @@ #include "beautifierconstants.h" #include "beautifiertr.h" -#include "generaloptionspage.h" #include "generalsettings.h" #include "artisticstyle/artisticstyle.h" @@ -31,8 +30,8 @@ #include #include #include +#include #include -#include #include #include @@ -80,12 +79,6 @@ public: &uncrustifyBeautifier, &clangFormatBeautifier }; - - GeneralOptionsPage optionsPage {{ - artisticStyleBeautifier.id(), - uncrustifyBeautifier.id(), - clangFormatBeautifier.id() - }}; }; static BeautifierPluginPrivate *dd = nullptr; @@ -112,6 +105,9 @@ ExtensionSystem::IPlugin::ShutdownFlag BeautifierPlugin::aboutToShutdown() BeautifierPluginPrivate::BeautifierPluginPrivate() { + for (BeautifierAbstractTool *tool : m_tools) + generalSettings.autoFormatTools.addOption(tool->id()); + updateActions(); const Core::EditorManager *editorManager = Core::EditorManager::instance(); @@ -129,14 +125,14 @@ void BeautifierPluginPrivate::updateActions(Core::IEditor *editor) void BeautifierPluginPrivate::autoFormatOnSave(Core::IDocument *document) { - if (!generalSettings.autoFormatOnSave()) + if (!generalSettings.autoFormatOnSave.value()) return; - if (!isAutoFormatApplicable(document, generalSettings.autoFormatMime())) + if (!isAutoFormatApplicable(document, generalSettings.allowedMimeTypes())) return; // Check if file is contained in the current project (if wished) - if (generalSettings.autoFormatOnlyCurrentProject()) { + if (generalSettings.autoFormatOnlyCurrentProject.value()) { const ProjectExplorer::Project *pro = ProjectExplorer::ProjectTree::currentProject(); if (!pro || pro->files([document](const ProjectExplorer::Node *n) { @@ -149,7 +145,7 @@ void BeautifierPluginPrivate::autoFormatOnSave(Core::IDocument *document) } // Find tool to use by id and format file! - const QString id = generalSettings.autoFormatTool(); + const QString id = generalSettings.autoFormatTools.stringValue(); auto tool = std::find_if(std::begin(m_tools), std::end(m_tools), [&id](const BeautifierAbstractTool *t){return t->id() == id;}); if (tool != std::end(m_tools)) { diff --git a/src/plugins/beautifier/clangformat/clangformat.cpp b/src/plugins/beautifier/clangformat/clangformat.cpp index c1833a77f11..92c8c7f454d 100644 --- a/src/plugins/beautifier/clangformat/clangformat.cpp +++ b/src/plugins/beautifier/clangformat/clangformat.cpp @@ -64,7 +64,7 @@ ClangFormat::ClangFormat() Core::ActionManager::actionContainer(Constants::MENU_ID)->addMenu(menu); - connect(&m_settings, &ClangFormatSettings::supportedMimeTypesChanged, + connect(&m_settings.supportedMimeTypes, &Utils::BaseAspect::changed, this, [this] { updateActions(Core::EditorManager::currentEditor()); }); } @@ -191,10 +191,10 @@ Command ClangFormat::command() const command.setProcessing(Command::PipeProcessing); if (m_settings.usePredefinedStyle()) { - const QString predefinedStyle = m_settings.predefinedStyle(); + const QString predefinedStyle = m_settings.predefinedStyle.stringValue(); command.addOption("-style=" + predefinedStyle); if (predefinedStyle == "File") { - const QString fallbackStyle = m_settings.fallbackStyle(); + const QString fallbackStyle = m_settings.fallbackStyle.stringValue(); if (fallbackStyle != "Default") command.addOption("-fallback-style=" + fallbackStyle); } diff --git a/src/plugins/beautifier/clangformat/clangformat.h b/src/plugins/beautifier/clangformat/clangformat.h index 8563a782efc..65fd23a43f1 100644 --- a/src/plugins/beautifier/clangformat/clangformat.h +++ b/src/plugins/beautifier/clangformat/clangformat.h @@ -5,7 +5,6 @@ #include "../beautifierabstracttool.h" -#include "clangformatoptionspage.h" #include "clangformatsettings.h" namespace Beautifier::Internal { diff --git a/src/plugins/beautifier/clangformat/clangformatoptionspage.cpp b/src/plugins/beautifier/clangformat/clangformatoptionspage.cpp deleted file mode 100644 index 9423aa54a82..00000000000 --- a/src/plugins/beautifier/clangformat/clangformatoptionspage.cpp +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (C) 2016 Lorenz Haas -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "clangformatoptionspage.h" - -#include "clangformatsettings.h" - -#include "../beautifierconstants.h" -#include "../beautifierplugin.h" -#include "../beautifiertr.h" -#include "../configurationpanel.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace Beautifier::Internal { - -class ClangFormatOptionsPageWidget : public Core::IOptionsPageWidget -{ -public: - explicit ClangFormatOptionsPageWidget(ClangFormatSettings *settings); - - void apply() final; - -private: - ClangFormatSettings *m_settings; - ConfigurationPanel *m_configurations; - QRadioButton *m_usePredefinedStyle; - QComboBox *m_predefinedStyle; - QComboBox *m_fallbackStyle; - Utils::PathChooser *m_command; - QLineEdit *m_mime; -}; - -ClangFormatOptionsPageWidget::ClangFormatOptionsPageWidget(ClangFormatSettings *settings) - : m_settings(settings) -{ - auto options = new QGroupBox(Tr::tr("Options")); - options->setEnabled(false); - - auto styleButtonGroup = new QButtonGroup(this); - - auto useCustomizedStyle = new QRadioButton(Tr::tr("Use customized style:")); - styleButtonGroup->addButton(useCustomizedStyle); - - m_configurations = new ConfigurationPanel; - m_configurations->setSettings(m_settings); - m_configurations->setCurrentConfiguration(m_settings->customStyle()); - - m_usePredefinedStyle = new QRadioButton(Tr::tr("Use predefined style:")); - - m_usePredefinedStyle->setChecked(true); - styleButtonGroup->addButton(m_usePredefinedStyle); - - m_predefinedStyle = new QComboBox; - m_predefinedStyle->addItems(m_settings->predefinedStyles()); - const int predefinedStyleIndex = m_predefinedStyle->findText(m_settings->predefinedStyle()); - if (predefinedStyleIndex != -1) - m_predefinedStyle->setCurrentIndex(predefinedStyleIndex); - - m_fallbackStyle = new QComboBox; - m_fallbackStyle->addItems(m_settings->fallbackStyles()); - m_fallbackStyle->setEnabled(false); - const int fallbackStyleIndex = m_fallbackStyle->findText(m_settings->fallbackStyle()); - if (fallbackStyleIndex != -1) - m_fallbackStyle->setCurrentIndex(fallbackStyleIndex); - - m_mime = new QLineEdit(m_settings->supportedMimeTypesAsString()); - - m_command = new Utils::PathChooser; - m_command->setExpectedKind(Utils::PathChooser::ExistingCommand); - m_command->setCommandVersionArguments({"--version"}); - m_command->setPromptDialogTitle( - BeautifierPlugin::msgCommandPromptDialogTitle("Clang Format")); - - if (m_settings->usePredefinedStyle()) - m_usePredefinedStyle->setChecked(true); - else - useCustomizedStyle->setChecked(true); - - using namespace Utils::Layouting; - - Form { - m_usePredefinedStyle, m_predefinedStyle, br, - empty, Row { Tr::tr("Fallback style:"), m_fallbackStyle }, br, - useCustomizedStyle, m_configurations, br, - }.attachTo(options); - - Column { - Group { - title(Tr::tr("Configuration")), - Form { - Tr::tr("Clang Format command:"), m_command, br, - Tr::tr("Restrict to MIME types:"), m_mime - } - }, - options, - st - }.attachTo(this); - - connect(m_command, &Utils::PathChooser::validChanged, options, &QWidget::setEnabled); - connect(m_predefinedStyle, &QComboBox::currentTextChanged, this, [this](const QString &item) { - m_fallbackStyle->setEnabled(item == "File"); - }); - connect(m_usePredefinedStyle, &QRadioButton::toggled, this, [this](bool checked) { - m_fallbackStyle->setEnabled(checked && m_predefinedStyle->currentText() == "File"); - m_predefinedStyle->setEnabled(checked); - }); - - // might trigger PathChooser::validChanged, so so after the connect above - m_command->setFilePath(m_settings->command()); -} - -void ClangFormatOptionsPageWidget::apply() -{ - m_settings->setCommand(m_command->filePath()); - m_settings->setSupportedMimeTypes(m_mime->text()); - m_settings->setUsePredefinedStyle(m_usePredefinedStyle->isChecked()); - m_settings->setPredefinedStyle(m_predefinedStyle->currentText()); - m_settings->setFallbackStyle(m_fallbackStyle->currentText()); - m_settings->setCustomStyle(m_configurations->currentConfiguration()); - m_settings->save(); - - // update since not all MIME types are accepted (invalids or duplicates) - m_mime->setText(m_settings->supportedMimeTypesAsString()); -} - -ClangFormatOptionsPage::ClangFormatOptionsPage(ClangFormatSettings *settings) -{ - setId("ClangFormat"); - setDisplayName(Tr::tr("Clang Format")); - setCategory(Constants::OPTION_CATEGORY); - setWidgetCreator([settings] { return new ClangFormatOptionsPageWidget(settings); }); -} - -} // Beautifier::Internal diff --git a/src/plugins/beautifier/clangformat/clangformatoptionspage.h b/src/plugins/beautifier/clangformat/clangformatoptionspage.h deleted file mode 100644 index e9a845af267..00000000000 --- a/src/plugins/beautifier/clangformat/clangformatoptionspage.h +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (C) 2016 Lorenz Haas -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -namespace Beautifier::Internal { - -class ClangFormatSettings; - -class ClangFormatOptionsPage final : public Core::IOptionsPage -{ -public: - explicit ClangFormatOptionsPage(ClangFormatSettings *settings); -}; - -} // Beautifier::Internal diff --git a/src/plugins/beautifier/clangformat/clangformatsettings.cpp b/src/plugins/beautifier/clangformat/clangformatsettings.cpp index 16a2225cd97..6a31667edb7 100644 --- a/src/plugins/beautifier/clangformat/clangformatsettings.cpp +++ b/src/plugins/beautifier/clangformat/clangformatsettings.cpp @@ -4,43 +4,75 @@ #include "clangformatsettings.h" #include "../beautifierconstants.h" +#include "../beautifierplugin.h" #include "../beautifiertr.h" - -#include -#include +#include "../configurationpanel.h" #include +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace Utils; + namespace Beautifier::Internal { -const char USE_PREDEFINED_STYLE[] = "usePredefinedStyle"; -const char PREDEFINED_STYLE[] = "predefinedStyle"; -const char FALLBACK_STYLE[] = "fallbackStyle"; -const char CUSTOM_STYLE[] = "customStyle"; const char SETTINGS_NAME[] = "clangformat"; -ClangFormatSettings::ClangFormatSettings() : - AbstractSettings(SETTINGS_NAME, ".clang-format") +ClangFormatSettings::ClangFormatSettings() + : AbstractSettings(SETTINGS_NAME, ".clang-format") { - setCommand("clang-format"); - m_settings.insert(USE_PREDEFINED_STYLE, QVariant(true)); - m_settings.insert(PREDEFINED_STYLE, "LLVM"); - m_settings.insert(FALLBACK_STYLE, "Default"); - m_settings.insert(CUSTOM_STYLE, QVariant()); - read(); -} + command.setDefaultValue("clang-format"); + command.setPromptDialogTitle(BeautifierPlugin::msgCommandPromptDialogTitle("Clang Format")); + command.setLabelText(Tr::tr("Clang Format command:")); -QString ClangFormatSettings::documentationFilePath() const -{ - return (Core::ICore::userResourcePath() / Beautifier::Constants::SETTINGS_DIRNAME - / Beautifier::Constants::DOCUMENTATION_DIRNAME / SETTINGS_NAME) - .stringAppended(".xml") - .toString(); + usePredefinedStyle.setSettingsKey("usePredefinedStyle"); + usePredefinedStyle.setLabelText(Tr::tr("Use predefined style:")); + usePredefinedStyle.setDefaultValue(true); + + predefinedStyle.setSettingsKey("predefinedStyle"); + predefinedStyle.setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox); + predefinedStyle.addOption("LLVM"); + predefinedStyle.addOption("Google"); + predefinedStyle.addOption("Chromium"); + predefinedStyle.addOption("Mozilla"); + predefinedStyle.addOption("WebKit"); + predefinedStyle.addOption("File"); + predefinedStyle.setDefaultValue("LLVM"); + + fallbackStyle.setSettingsKey("fallbackStyle"); + fallbackStyle.setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox); + fallbackStyle.addOption("Default"); + fallbackStyle.addOption("None"); + fallbackStyle.addOption("LLVM"); + fallbackStyle.addOption("Google"); + fallbackStyle.addOption("Chromium"); + fallbackStyle.addOption("Mozilla"); + fallbackStyle.addOption("WebKit"); + fallbackStyle.setDefaultValue("Default"); + + predefinedStyle.setSettingsKey("predefinedStyle"); + predefinedStyle.setDefaultValue("LLVM"); + + customStyle.setSettingsKey("customStyle"); + + documentationFilePath = Core::ICore::userResourcePath(Constants::SETTINGS_DIRNAME) + .pathAppended(Constants::DOCUMENTATION_DIRNAME) + .pathAppended(SETTINGS_NAME).stringAppended(".xml"); + + read(); } void ClangFormatSettings::createDocumentationFile() const { - QFile file(documentationFilePath()); + QFile file(documentationFilePath.toFSPathString()); const QFileInfo fi(file); if (!fi.exists()) fi.dir().mkpath(fi.absolutePath()); @@ -144,60 +176,6 @@ QStringList ClangFormatSettings::completerWords() }; } -bool ClangFormatSettings::usePredefinedStyle() const -{ - return m_settings.value(USE_PREDEFINED_STYLE).toBool(); -} - -void ClangFormatSettings::setUsePredefinedStyle(bool usePredefinedStyle) -{ - m_settings.insert(USE_PREDEFINED_STYLE, QVariant(usePredefinedStyle)); -} - -QString ClangFormatSettings::predefinedStyle() const -{ - return m_settings.value(PREDEFINED_STYLE).toString(); -} - -void ClangFormatSettings::setPredefinedStyle(const QString &predefinedStyle) -{ - const QStringList test = predefinedStyles(); - if (test.contains(predefinedStyle)) - m_settings.insert(PREDEFINED_STYLE, QVariant(predefinedStyle)); -} - -QString ClangFormatSettings::fallbackStyle() const -{ - return m_settings.value(FALLBACK_STYLE).toString(); -} - -void ClangFormatSettings::setFallbackStyle(const QString &fallbackStyle) -{ - const QStringList test = fallbackStyles(); - if (test.contains(fallbackStyle)) - m_settings.insert(FALLBACK_STYLE, QVariant(fallbackStyle)); -} - -QString ClangFormatSettings::customStyle() const -{ - return m_settings.value(CUSTOM_STYLE).toString(); -} - -void ClangFormatSettings::setCustomStyle(const QString &customStyle) -{ - m_settings.insert(CUSTOM_STYLE, QVariant(customStyle)); -} - -QStringList ClangFormatSettings::predefinedStyles() const -{ - return {"LLVM", "Google", "Chromium", "Mozilla", "WebKit", "File"}; -} - -QStringList ClangFormatSettings::fallbackStyles() const -{ - return {"Default", "None", "LLVM", "Google", "Chromium", "Mozilla", "WebKit"}; -} - QString ClangFormatSettings::styleFileName(const QString &key) const { return m_styleDir.absolutePath() + '/' + key + '/' + m_ending; @@ -213,4 +191,86 @@ void ClangFormatSettings::readStyles() } } +class ClangFormatOptionsPageWidget : public Core::IOptionsPageWidget +{ +public: + explicit ClangFormatOptionsPageWidget(ClangFormatSettings *settings) + { + ClangFormatSettings &s = *settings; + QGroupBox *options = nullptr; + + auto predefinedStyleButton = new QRadioButton; + s.usePredefinedStyle.adoptButton(predefinedStyleButton); + + auto customizedStyleButton = new QRadioButton(Tr::tr("Use customized style:")); + + auto styleButtonGroup = new QButtonGroup; + styleButtonGroup->addButton(predefinedStyleButton); + styleButtonGroup->addButton(customizedStyleButton); + + auto configurations = new ConfigurationPanel(this); + configurations->setSettings(&s); + configurations->setCurrentConfiguration(s.customStyle()); + + using namespace Layouting; + + auto fallbackBlob = Row { noMargin, Tr::tr("Fallback style:"), s.fallbackStyle }.emerge(); + + auto predefinedBlob = Column { noMargin, s.predefinedStyle, fallbackBlob }.emerge(); + + Column { + Group { + title(Tr::tr("Configuration")), + Form { + s.command, br, + s.supportedMimeTypes + } + }, + Group { + title(Tr::tr("Options")), + bindTo(&options), + Form { + s.usePredefinedStyle, predefinedBlob, br, + customizedStyleButton, configurations, + }, + }, + st + }.attachTo(this); + + if (s.usePredefinedStyle.value()) + predefinedStyleButton->click(); + else + customizedStyleButton->click(); + + const auto updateEnabled = [&s, styleButtonGroup, predefinedBlob, fallbackBlob, + configurations, predefinedStyleButton] { + const bool predefSelected = styleButtonGroup->checkedButton() == predefinedStyleButton; + predefinedBlob->setEnabled(predefSelected); + fallbackBlob->setEnabled(predefSelected && s.predefinedStyle.volatileValue().toInt() == 5); // File + configurations->setEnabled(!predefSelected); + }; + updateEnabled(); + connect(styleButtonGroup, &QButtonGroup::buttonClicked, this, updateEnabled); + connect(&s.predefinedStyle, &SelectionAspect::volatileValueChanged, this, updateEnabled); + + setOnApply([settings, configurations] { + settings->customStyle.setValue(configurations->currentConfiguration()); + settings->save(); + }); + + s.read(); + + connect(s.command.pathChooser(), &PathChooser::validChanged, options, &QWidget::setEnabled); + options->setEnabled(s.command.pathChooser()->isValid()); + } +}; + +ClangFormatOptionsPage::ClangFormatOptionsPage(ClangFormatSettings *settings) +{ + setId("ClangFormat"); + setDisplayName(Tr::tr("Clang Format")); + setCategory(Constants::OPTION_CATEGORY); + setWidgetCreator([settings] { return new ClangFormatOptionsPageWidget(settings); }); +} + } // Beautifier::Internal diff --git a/src/plugins/beautifier/clangformat/clangformatsettings.h b/src/plugins/beautifier/clangformat/clangformatsettings.h index 310eeaf8926..64f2b4b9bbd 100644 --- a/src/plugins/beautifier/clangformat/clangformatsettings.h +++ b/src/plugins/beautifier/clangformat/clangformatsettings.h @@ -12,24 +12,14 @@ class ClangFormatSettings : public AbstractSettings public: explicit ClangFormatSettings(); - QString documentationFilePath() const override; void createDocumentationFile() const override; + QStringList completerWords() override; - bool usePredefinedStyle() const; - void setUsePredefinedStyle(bool usePredefinedStyle); - - QString predefinedStyle() const; - void setPredefinedStyle(const QString &predefinedStyle); - - QString fallbackStyle() const; - void setFallbackStyle(const QString &fallbackStyle); - - QString customStyle() const; - void setCustomStyle(const QString &customStyle); - - QStringList predefinedStyles() const; - QStringList fallbackStyles() const; + Utils::BoolAspect usePredefinedStyle{this}; + Utils::SelectionAspect predefinedStyle{this}; + Utils::SelectionAspect fallbackStyle{this}; + Utils::StringAspect customStyle{this}; QString styleFileName(const QString &key) const override; @@ -37,4 +27,10 @@ private: void readStyles() override; }; +class ClangFormatOptionsPage final : public Core::IOptionsPage +{ +public: + explicit ClangFormatOptionsPage(ClangFormatSettings *settings); +}; + } // Beautifier::Internal diff --git a/src/plugins/beautifier/configurationdialog.cpp b/src/plugins/beautifier/configurationdialog.cpp index 8b2c61e7204..09e8c595f30 100644 --- a/src/plugins/beautifier/configurationdialog.cpp +++ b/src/plugins/beautifier/configurationdialog.cpp @@ -40,7 +40,7 @@ ConfigurationDialog::ConfigurationDialog(QWidget *parent) m_buttonBox->setOrientation(Qt::Horizontal); m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); - using namespace Utils::Layouting; + using namespace Layouting; Column { Group { diff --git a/src/plugins/beautifier/configurationpanel.cpp b/src/plugins/beautifier/configurationpanel.cpp index 6aa0f5b2d9f..fa0fca71393 100644 --- a/src/plugins/beautifier/configurationpanel.cpp +++ b/src/plugins/beautifier/configurationpanel.cpp @@ -17,8 +17,6 @@ namespace Beautifier::Internal { ConfigurationPanel::ConfigurationPanel(QWidget *parent) : QWidget(parent) { - resize(595, 23); - m_configurations = new QComboBox; m_configurations->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); @@ -26,14 +24,15 @@ ConfigurationPanel::ConfigurationPanel(QWidget *parent) m_remove = new QPushButton(Tr::tr("Remove")); auto add = new QPushButton(Tr::tr("Add")); - using namespace Utils::Layouting; + using namespace Layouting; Row { m_configurations, m_edit, m_remove, - add - }.attachTo(this, WithoutMargins); + add, + noMargin, + }.attachTo(this); connect(add, &QPushButton::clicked, this, &ConfigurationPanel::add); connect(m_edit, &QPushButton::clicked, this, &ConfigurationPanel::edit); diff --git a/src/plugins/beautifier/generaloptionspage.cpp b/src/plugins/beautifier/generaloptionspage.cpp deleted file mode 100644 index 859f323e244..00000000000 --- a/src/plugins/beautifier/generaloptionspage.cpp +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (C) 2016 Lorenz Haas -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "generaloptionspage.h" - -#include "beautifierconstants.h" -#include "beautifiertr.h" -#include "generalsettings.h" - -#include - -#include -#include -#include -#include -#include - -namespace Beautifier::Internal { - -class GeneralOptionsPageWidget : public Core::IOptionsPageWidget -{ -public: - explicit GeneralOptionsPageWidget(const QStringList &toolIds); - -private: - void apply() final; - - QCheckBox *m_autoFormat; - QComboBox *m_autoFormatTool; - QLineEdit *m_autoFormatMime; - QCheckBox *m_autoFormatOnlyCurrentProject; -}; - -GeneralOptionsPageWidget::GeneralOptionsPageWidget(const QStringList &toolIds) -{ - resize(817, 631); - - auto settings = GeneralSettings::instance(); - - m_autoFormat = new QCheckBox(Tr::tr("Enable auto format on file save")); - m_autoFormat->setChecked(settings->autoFormatOnSave()); - - auto toolLabel = new QLabel(Tr::tr("Tool:")); - toolLabel->setEnabled(false); - - auto mimeLabel = new QLabel(Tr::tr("Restrict to MIME types:")); - mimeLabel->setEnabled(false); - - m_autoFormatMime = new QLineEdit(settings->autoFormatMimeAsString()); - m_autoFormatMime->setEnabled(false); - - m_autoFormatOnlyCurrentProject = - new QCheckBox(Tr::tr("Restrict to files contained in the current project")); - m_autoFormatOnlyCurrentProject->setEnabled(false); - m_autoFormatOnlyCurrentProject->setChecked(settings->autoFormatOnlyCurrentProject()); - - m_autoFormatTool = new QComboBox; - m_autoFormatTool->setEnabled(false); - m_autoFormatTool->addItems(toolIds); - const int index = m_autoFormatTool->findText(settings->autoFormatTool()); - m_autoFormatTool->setCurrentIndex(qMax(index, 0)); - - using namespace Utils::Layouting; - - Column { - Group { - title(Tr::tr("Automatic Formatting on File Save")), - Form { - Span(2, m_autoFormat), br, - toolLabel, m_autoFormatTool, br, - mimeLabel, m_autoFormatMime, br, - Span(2, m_autoFormatOnlyCurrentProject) - } - }, - st - }.attachTo(this); - - connect(m_autoFormat, &QCheckBox::toggled, m_autoFormatTool, &QComboBox::setEnabled); - connect(m_autoFormat, &QCheckBox::toggled, m_autoFormatMime, &QLineEdit::setEnabled); - connect(m_autoFormat, &QCheckBox::toggled, toolLabel, &QLabel::setEnabled); - connect(m_autoFormat, &QCheckBox::toggled, mimeLabel, &QLabel::setEnabled); - connect(m_autoFormat, &QCheckBox::toggled, m_autoFormatOnlyCurrentProject, &QCheckBox::setEnabled); -} - -void GeneralOptionsPageWidget::apply() -{ - auto settings = GeneralSettings::instance(); - settings->setAutoFormatOnSave(m_autoFormat->isChecked()); - settings->setAutoFormatTool(m_autoFormatTool->currentText()); - settings->setAutoFormatMime(m_autoFormatMime->text()); - settings->setAutoFormatOnlyCurrentProject(m_autoFormatOnlyCurrentProject->isChecked()); - settings->save(); -} - -GeneralOptionsPage::GeneralOptionsPage(const QStringList &toolIds) -{ - setId(Constants::OPTION_GENERAL_ID); - setDisplayName(Tr::tr("General")); - setCategory(Constants::OPTION_CATEGORY); - setDisplayCategory(Tr::tr("Beautifier")); - setWidgetCreator([toolIds] { return new GeneralOptionsPageWidget(toolIds); }); - setCategoryIconPath(":/beautifier/images/settingscategory_beautifier.png"); -} - -} // Beautifier::Internal diff --git a/src/plugins/beautifier/generaloptionspage.h b/src/plugins/beautifier/generaloptionspage.h deleted file mode 100644 index 885a27cb199..00000000000 --- a/src/plugins/beautifier/generaloptionspage.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (C) 2016 Lorenz Haas -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -namespace Beautifier::Internal { - -class GeneralOptionsPage final : public Core::IOptionsPage -{ -public: - explicit GeneralOptionsPage(const QStringList &toolIds); -}; - -} // Beautifier::Internal diff --git a/src/plugins/beautifier/generalsettings.cpp b/src/plugins/beautifier/generalsettings.cpp index 218f6963f37..2f7dc4c3a0c 100644 --- a/src/plugins/beautifier/generalsettings.cpp +++ b/src/plugins/beautifier/generalsettings.cpp @@ -3,114 +3,73 @@ #include "generalsettings.h" -#include +#include "beautifierconstants.h" +#include "beautifiertr.h" #include #include -#include +#include + +using namespace Utils; namespace Beautifier::Internal { -const char AUTO_FORMAT_TOOL[] = "autoFormatTool"; -const char AUTO_FORMAT_MIME[] = "autoFormatMime"; -const char AUTO_FORMAT_ONLY_CURRENT_PROJECT[] = "autoFormatOnlyCurrentProject"; - -static GeneralSettings *m_instance; - GeneralSettings::GeneralSettings() { - m_instance = this; - read(); + setId(Constants::OPTION_GENERAL_ID); + setDisplayName(Tr::tr("General")); + setCategory(Constants::OPTION_CATEGORY); + setDisplayCategory(Tr::tr("Beautifier")); + setCategoryIconPath(":/beautifier/images/settingscategory_beautifier.png"); + setSettingsGroups("Beautifier", "General"); + + autoFormatOnSave.setSettingsKey(Utils::Constants::BEAUTIFIER_AUTO_FORMAT_ON_SAVE); + autoFormatOnSave.setDefaultValue(false); + autoFormatOnSave.setLabelText(Tr::tr("Enable auto format on file save")); + + autoFormatOnlyCurrentProject.setSettingsKey("autoFormatOnlyCurrentProject"); + autoFormatOnlyCurrentProject.setDefaultValue(true); + autoFormatOnlyCurrentProject.setLabelText(Tr::tr("Restrict to files contained in the current project")); + + autoFormatTools.setSettingsKey("autoFormatTool"); + autoFormatTools.setLabelText(Tr::tr("Tool:")); + autoFormatTools.setDefaultValue(0); + autoFormatTools.setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox); + + autoFormatMime.setSettingsKey("autoFormatMime"); + autoFormatMime.setDefaultValue("text/x-c++src;text/x-c++hdr"); + autoFormatMime.setLabelText(Tr::tr("Restrict to MIME types:")); + autoFormatMime.setDisplayStyle(StringAspect::LineEditDisplay); + + setLayouter([this] { + using namespace Layouting; + return Column { + Group { + title(Tr::tr("Automatic Formatting on File Save")), + autoFormatOnSave.groupChecker(), + Form { + autoFormatTools, br, + autoFormatMime, br, + Span(2, autoFormatOnlyCurrentProject) + } + }, + st + }; + }); } -GeneralSettings *GeneralSettings::instance() +QList GeneralSettings::allowedMimeTypes() const { - return m_instance; -} + const QStringList stringTypes = autoFormatMime.value().split(';'); -void GeneralSettings::read() -{ - QSettings *s = Core::ICore::settings(); - s->beginGroup(Utils::Constants::BEAUTIFIER_SETTINGS_GROUP); - s->beginGroup(Utils::Constants::BEAUTIFIER_GENERAL_GROUP); - m_autoFormatOnSave = s->value(Utils::Constants::BEAUTIFIER_AUTO_FORMAT_ON_SAVE, false).toBool(); - m_autoFormatTool = s->value(AUTO_FORMAT_TOOL, QString()).toString(); - setAutoFormatMime(s->value(AUTO_FORMAT_MIME, "text/x-c++src;text/x-c++hdr").toString()); - m_autoFormatOnlyCurrentProject = s->value(AUTO_FORMAT_ONLY_CURRENT_PROJECT, true).toBool(); - s->endGroup(); - s->endGroup(); -} - -void GeneralSettings::save() -{ - QSettings *s = Core::ICore::settings(); - s->beginGroup(Utils::Constants::BEAUTIFIER_SETTINGS_GROUP); - s->beginGroup(Utils::Constants::BEAUTIFIER_GENERAL_GROUP); - s->setValue(Utils::Constants::BEAUTIFIER_AUTO_FORMAT_ON_SAVE, m_autoFormatOnSave); - s->setValue(AUTO_FORMAT_TOOL, m_autoFormatTool); - s->setValue(AUTO_FORMAT_MIME, autoFormatMimeAsString()); - s->setValue(AUTO_FORMAT_ONLY_CURRENT_PROJECT, m_autoFormatOnlyCurrentProject); - s->endGroup(); - s->endGroup(); -} - -bool GeneralSettings::autoFormatOnSave() const -{ - return m_autoFormatOnSave; -} - -void GeneralSettings::setAutoFormatOnSave(bool autoFormatOnSave) -{ - m_autoFormatOnSave = autoFormatOnSave; -} - -QString GeneralSettings::autoFormatTool() const -{ - return m_autoFormatTool; -} - -void GeneralSettings::setAutoFormatTool(const QString &autoFormatTool) -{ - m_autoFormatTool = autoFormatTool; -} - -QList GeneralSettings::autoFormatMime() const -{ - return m_autoFormatMime; -} - -QString GeneralSettings::autoFormatMimeAsString() const -{ - return Utils::transform(m_autoFormatMime, &Utils::MimeType::name).join("; "); -} - -void GeneralSettings::setAutoFormatMime(const QList &autoFormatMime) -{ - m_autoFormatMime = autoFormatMime; -} - -void GeneralSettings::setAutoFormatMime(const QString &mimeList) -{ - const QStringList stringTypes = mimeList.split(';'); - QList types; - types.reserve(stringTypes.count()); + QList types; for (QString t : stringTypes) { t = t.trimmed(); - const Utils::MimeType mime = Utils::mimeTypeForName(t); + const MimeType mime = Utils::mimeTypeForName(t); if (mime.isValid()) types << mime; } - setAutoFormatMime(types); -} - -bool GeneralSettings::autoFormatOnlyCurrentProject() const -{ - return m_autoFormatOnlyCurrentProject; -} - -void GeneralSettings::setAutoFormatOnlyCurrentProject(bool autoFormatOnlyCurrentProject) -{ - m_autoFormatOnlyCurrentProject = autoFormatOnlyCurrentProject; + return types; } } // Beautifier::Internal diff --git a/src/plugins/beautifier/generalsettings.h b/src/plugins/beautifier/generalsettings.h index b381dacb26a..0be68b98e35 100644 --- a/src/plugins/beautifier/generalsettings.h +++ b/src/plugins/beautifier/generalsettings.h @@ -5,38 +5,21 @@ #include -#include +#include namespace Beautifier::Internal { -class GeneralSettings +class GeneralSettings : public Core::PagedSettings { public: - explicit GeneralSettings(); - static GeneralSettings *instance(); + GeneralSettings(); - void read(); - void save(); + QList allowedMimeTypes() const; - bool autoFormatOnSave() const; - void setAutoFormatOnSave(bool autoFormatOnSave); - - QString autoFormatTool() const; - void setAutoFormatTool(const QString &autoFormatTool); - - QList autoFormatMime() const; - QString autoFormatMimeAsString() const; - void setAutoFormatMime(const QList &autoFormatMime); - void setAutoFormatMime(const QString &mimeList); - - bool autoFormatOnlyCurrentProject() const; - void setAutoFormatOnlyCurrentProject(bool autoFormatOnlyCurrentProject); - -private: - bool m_autoFormatOnSave = false; - bool m_autoFormatOnlyCurrentProject = true; - QString m_autoFormatTool; - QList m_autoFormatMime; + Utils::BoolAspect autoFormatOnSave{this}; + Utils::BoolAspect autoFormatOnlyCurrentProject{this}; + Utils::SelectionAspect autoFormatTools{this}; + Utils::StringAspect autoFormatMime{this}; }; } // Beautifier::Internal diff --git a/src/plugins/beautifier/uncrustify/uncrustify.cpp b/src/plugins/beautifier/uncrustify/uncrustify.cpp index 2e6f6d67d59..1439dc315f2 100644 --- a/src/plugins/beautifier/uncrustify/uncrustify.cpp +++ b/src/plugins/beautifier/uncrustify/uncrustify.cpp @@ -54,7 +54,7 @@ Uncrustify::Uncrustify() Core::ActionManager::actionContainer(Constants::MENU_ID)->addMenu(menu); - connect(&m_settings, &UncrustifySettings::supportedMimeTypesChanged, + connect(&m_settings.supportedMimeTypes, &Utils::BaseAspect::changed, this, [this] { updateActions(Core::EditorManager::currentEditor()); }); } diff --git a/src/plugins/beautifier/uncrustify/uncrustify.h b/src/plugins/beautifier/uncrustify/uncrustify.h index 1261576274c..685a29c25a1 100644 --- a/src/plugins/beautifier/uncrustify/uncrustify.h +++ b/src/plugins/beautifier/uncrustify/uncrustify.h @@ -5,7 +5,6 @@ #include "../beautifierabstracttool.h" -#include "uncrustifyoptionspage.h" #include "uncrustifysettings.h" namespace Beautifier::Internal { diff --git a/src/plugins/beautifier/uncrustify/uncrustifyoptionspage.cpp b/src/plugins/beautifier/uncrustify/uncrustifyoptionspage.cpp deleted file mode 100644 index 2102dd311f8..00000000000 --- a/src/plugins/beautifier/uncrustify/uncrustifyoptionspage.cpp +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (C) 2016 Lorenz Haas -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "uncrustifyoptionspage.h" - -#include "uncrustifyconstants.h" -#include "uncrustifysettings.h" - -#include "../beautifierconstants.h" -#include "../beautifierplugin.h" -#include "../beautifiertr.h" -#include "../configurationpanel.h" - -#include -#include - -#include -#include -#include -#include - -namespace Beautifier::Internal { - -class UncrustifyOptionsPageWidget : public Core::IOptionsPageWidget -{ -public: - explicit UncrustifyOptionsPageWidget(UncrustifySettings *settings); - - void apply() final; - -private: - UncrustifySettings *m_settings; - - Utils::PathChooser *m_command; - QLineEdit *m_mime; - QCheckBox *m_useOtherFiles; - QCheckBox *m_useSpecificFile; - Utils::PathChooser *m_uncrusifyFilePath; - QCheckBox *m_useHomeFile; - QCheckBox *m_useCustomStyle; - ConfigurationPanel *m_configurations; - QCheckBox *m_formatEntireFileFallback; -}; - -UncrustifyOptionsPageWidget::UncrustifyOptionsPageWidget(UncrustifySettings *settings) - : m_settings(settings) -{ - resize(817, 631); - - m_command = new Utils::PathChooser; - - m_mime = new QLineEdit(m_settings->supportedMimeTypesAsString()); - - m_useOtherFiles = new QCheckBox(Tr::tr("Use file uncrustify.cfg defined in project files")); - m_useOtherFiles->setChecked(m_settings->useOtherFiles()); - - m_useSpecificFile = new QCheckBox(Tr::tr("Use file specific uncrustify.cfg")); - m_useSpecificFile->setChecked(m_settings->useSpecificConfigFile()); - - m_uncrusifyFilePath = new Utils::PathChooser; - m_uncrusifyFilePath->setExpectedKind(Utils::PathChooser::File); - m_uncrusifyFilePath->setPromptDialogFilter(Tr::tr("Uncrustify file (*.cfg)")); - m_uncrusifyFilePath->setFilePath(m_settings->specificConfigFile()); - - m_useHomeFile = new QCheckBox(Tr::tr("Use file uncrustify.cfg in HOME") - .replace( "HOME", QDir::toNativeSeparators(QDir::home().absolutePath()))); - m_useHomeFile->setChecked(m_settings->useHomeFile()); - - m_useCustomStyle = new QCheckBox(Tr::tr("Use customized style:")); - m_useCustomStyle->setChecked(m_settings->useCustomStyle()); - - m_configurations = new ConfigurationPanel; - m_configurations->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - m_configurations->setSettings(m_settings); - m_configurations->setCurrentConfiguration(m_settings->customStyle()); - - m_formatEntireFileFallback = new QCheckBox(Tr::tr("Format entire file if no text was selected")); - m_formatEntireFileFallback->setToolTip(Tr::tr("For action Format Selected Text")); - m_formatEntireFileFallback->setChecked(m_settings->formatEntireFileFallback()); - - m_command->setExpectedKind(Utils::PathChooser::ExistingCommand); - m_command->setCommandVersionArguments({"--version"}); - m_command->setPromptDialogTitle(BeautifierPlugin::msgCommandPromptDialogTitle( - Tr::tr(Constants::UNCRUSTIFY_DISPLAY_NAME))); - m_command->setFilePath(m_settings->command()); - - auto options = new QGroupBox(Tr::tr("Options")); - - using namespace Utils::Layouting; - - Column { - m_useOtherFiles, - Row { m_useSpecificFile, m_uncrusifyFilePath }, - m_useHomeFile, - Row { m_useCustomStyle, m_configurations }, - m_formatEntireFileFallback - }.attachTo(options); - - Column { - Group { - title(Tr::tr("Configuration")), - Form { - Tr::tr("Uncrustify command:"), m_command, br, - Tr::tr("Restrict to MIME types:"), m_mime - } - }, - options, - st - }.attachTo(this); - - connect(m_command, &Utils::PathChooser::validChanged, options, &QWidget::setEnabled); -} - -void UncrustifyOptionsPageWidget::apply() -{ - m_settings->setCommand(m_command->filePath()); - m_settings->setSupportedMimeTypes(m_mime->text()); - m_settings->setUseOtherFiles(m_useOtherFiles->isChecked()); - m_settings->setUseHomeFile(m_useHomeFile->isChecked()); - m_settings->setUseSpecificConfigFile(m_useSpecificFile->isChecked()); - m_settings->setSpecificConfigFile(m_uncrusifyFilePath->filePath()); - m_settings->setUseCustomStyle(m_useCustomStyle->isChecked()); - m_settings->setCustomStyle(m_configurations->currentConfiguration()); - m_settings->setFormatEntireFileFallback(m_formatEntireFileFallback->isChecked()); - m_settings->save(); - - // update since not all MIME types are accepted (invalids or duplicates) - m_mime->setText(m_settings->supportedMimeTypesAsString()); -} - -UncrustifyOptionsPage::UncrustifyOptionsPage(UncrustifySettings *settings) -{ - setId("Uncrustify"); - setDisplayName(Tr::tr("Uncrustify")); - setCategory(Constants::OPTION_CATEGORY); - setWidgetCreator([settings] { return new UncrustifyOptionsPageWidget(settings); }); -} - -} // Beautifier::Internal diff --git a/src/plugins/beautifier/uncrustify/uncrustifyoptionspage.h b/src/plugins/beautifier/uncrustify/uncrustifyoptionspage.h deleted file mode 100644 index a8512d0da62..00000000000 --- a/src/plugins/beautifier/uncrustify/uncrustifyoptionspage.h +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (C) 2016 Lorenz Haas -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -namespace Beautifier::Internal { - -class UncrustifySettings; - -class UncrustifyOptionsPage final : public Core::IOptionsPage -{ -public: - explicit UncrustifyOptionsPage(UncrustifySettings *settings); -}; - -} // Beautifier::Internal diff --git a/src/plugins/beautifier/uncrustify/uncrustifysettings.cpp b/src/plugins/beautifier/uncrustify/uncrustifysettings.cpp index e827a8f7915..1aa9e8502c6 100644 --- a/src/plugins/beautifier/uncrustify/uncrustifysettings.cpp +++ b/src/plugins/beautifier/uncrustify/uncrustifysettings.cpp @@ -3,14 +3,25 @@ #include "uncrustifysettings.h" +#include "uncrustifyconstants.h" #include "../beautifierconstants.h" +#include "../beautifierplugin.h" +#include "../beautifiertr.h" +#include "../configurationpanel.h" #include -#include +#include +#include +#include + +#include #include #include #include +#include +#include +#include #include #include @@ -18,120 +29,60 @@ using namespace Utils; namespace Beautifier::Internal { -const char USE_OTHER_FILES[] = "useOtherFiles"; -const char USE_HOME_FILE[] = "useHomeFile"; -const char USE_SPECIFIC_CONFIG_FILE_PATH[] = "useSpecificConfigFile"; -const char SPECIFIC_CONFIG_FILE_PATH[] = "specificConfigFile"; -const char USE_CUSTOM_STYLE[] = "useCustomStyle"; -const char CUSTOM_STYLE[] = "customStyle"; -const char FORMAT_ENTIRE_FILE_FALLBACK[] = "formatEntireFileFallback"; -const char SETTINGS_NAME[] = "uncrustify"; +const char SETTINGS_NAME[] = "uncrustify"; -UncrustifySettings::UncrustifySettings() : - AbstractSettings(SETTINGS_NAME, ".cfg") +UncrustifySettings::UncrustifySettings() + : AbstractSettings(SETTINGS_NAME, ".cfg") { setVersionRegExp(QRegularExpression("([0-9]{1})\\.([0-9]{2})")); - setCommand("uncrustify"); - m_settings.insert(USE_OTHER_FILES, QVariant(true)); - m_settings.insert(USE_HOME_FILE, QVariant(false)); - m_settings.insert(USE_CUSTOM_STYLE, QVariant(false)); - m_settings.insert(USE_SPECIFIC_CONFIG_FILE_PATH, QVariant(false)); - m_settings.insert(CUSTOM_STYLE, QVariant()); - m_settings.insert(FORMAT_ENTIRE_FILE_FALLBACK, QVariant(true)); - m_settings.insert(SPECIFIC_CONFIG_FILE_PATH, QVariant()); + + command.setDefaultValue("uncrustify"); + command.setLabelText(Tr::tr("Uncrustify command:")); + command.setPromptDialogTitle(BeautifierPlugin::msgCommandPromptDialogTitle( + Tr::tr(Constants::UNCRUSTIFY_DISPLAY_NAME))); + + useOtherFiles.setSettingsKey("useOtherFiles"); + useOtherFiles.setDefaultValue(true); + useOtherFiles.setLabelText(Tr::tr("Use file uncrustify.cfg defined in project files")); + + useHomeFile.setSettingsKey("useHomeFile"); + useHomeFile.setLabelText(Tr::tr("Use file uncrustify.cfg in HOME") + .replace( "HOME", QDir::toNativeSeparators(QDir::home().absolutePath()))); + + useCustomStyle.setSettingsKey("useCustomStyle"); + useCustomStyle.setLabelText(Tr::tr("Use customized style:")); + + useSpecificConfigFile.setSettingsKey("useSpecificConfigFile"); + useSpecificConfigFile.setLabelText(Tr::tr("Use file specific uncrustify.cfg")); + + customStyle.setSettingsKey("customStyle"); + + formatEntireFileFallback.setSettingsKey("formatEntireFileFallback"); + formatEntireFileFallback.setDefaultValue(true); + formatEntireFileFallback.setLabelText(Tr::tr("Format entire file if no text was selected")); + formatEntireFileFallback.setToolTip(Tr::tr("For action Format Selected Text")); + + specificConfigFile.setSettingsKey("specificConfigFile"); + specificConfigFile.setExpectedKind(Utils::PathChooser::File); + specificConfigFile.setPromptDialogFilter(Tr::tr("Uncrustify file (*.cfg)")); + + documentationFilePath = Core::ICore::userResourcePath(Constants::SETTINGS_DIRNAME) + .pathAppended(Constants::DOCUMENTATION_DIRNAME) + .pathAppended(SETTINGS_NAME).stringAppended(".xml"); + read(); } -UncrustifySettings::~UncrustifySettings() = default; - -bool UncrustifySettings::useOtherFiles() const -{ - return m_settings.value(USE_OTHER_FILES).toBool(); -} - -void UncrustifySettings::setUseOtherFiles(bool useOtherFiles) -{ - m_settings.insert(USE_OTHER_FILES, QVariant(useOtherFiles)); -} - -bool UncrustifySettings::useHomeFile() const -{ - return m_settings.value(USE_HOME_FILE).toBool(); -} - -void UncrustifySettings::setUseHomeFile(bool useHomeFile) -{ - m_settings.insert(USE_HOME_FILE, QVariant(useHomeFile)); -} - -FilePath UncrustifySettings::specificConfigFile() const -{ - return FilePath::fromString(m_settings.value(SPECIFIC_CONFIG_FILE_PATH).toString()); -} - -void UncrustifySettings::setSpecificConfigFile(const FilePath &filePath) -{ - m_settings.insert(SPECIFIC_CONFIG_FILE_PATH, QVariant(filePath.toString())); -} - -bool UncrustifySettings::useSpecificConfigFile() const -{ - return m_settings.value(USE_SPECIFIC_CONFIG_FILE_PATH).toBool(); -} - -void UncrustifySettings::setUseSpecificConfigFile(bool useConfigFile) -{ - m_settings.insert(USE_SPECIFIC_CONFIG_FILE_PATH, QVariant(useConfigFile)); -} - -bool UncrustifySettings::useCustomStyle() const -{ - return m_settings.value(USE_CUSTOM_STYLE).toBool(); -} - -void UncrustifySettings::setUseCustomStyle(bool useCustomStyle) -{ - m_settings.insert(USE_CUSTOM_STYLE, QVariant(useCustomStyle)); -} - -QString UncrustifySettings::customStyle() const -{ - return m_settings.value(CUSTOM_STYLE).toString(); -} - -void UncrustifySettings::setCustomStyle(const QString &customStyle) -{ - m_settings.insert(CUSTOM_STYLE, QVariant(customStyle)); -} - -bool UncrustifySettings::formatEntireFileFallback() const -{ - return m_settings.value(FORMAT_ENTIRE_FILE_FALLBACK).toBool(); -} - -void UncrustifySettings::setFormatEntireFileFallback(bool formatEntireFileFallback) -{ - m_settings.insert(FORMAT_ENTIRE_FILE_FALLBACK, QVariant(formatEntireFileFallback)); -} - -QString UncrustifySettings::documentationFilePath() const -{ - return (Core::ICore::userResourcePath() / Beautifier::Constants::SETTINGS_DIRNAME - / Beautifier::Constants::DOCUMENTATION_DIRNAME / SETTINGS_NAME) - .stringAppended(".xml") - .toString(); -} - void UncrustifySettings::createDocumentationFile() const { - QtcProcess process; + Process process; process.setTimeoutS(2); process.setCommand({command(), {"--show-config"}}); process.runBlocking(); if (process.result() != ProcessResult::FinishedWithSuccess) return; - QFile file(documentationFilePath()); + QFile file(documentationFilePath.toFSPathString()); const QFileInfo fi(file); if (!fi.exists()) fi.dir().mkpath(fi.absolutePath()); @@ -185,4 +136,62 @@ void UncrustifySettings::createDocumentationFile() const } } +class UncrustifyOptionsPageWidget : public Core::IOptionsPageWidget +{ +public: + explicit UncrustifyOptionsPageWidget(UncrustifySettings *settings) + { + UncrustifySettings &s = *settings; + + auto configurations = new ConfigurationPanel(this); + configurations->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + configurations->setSettings(settings); + configurations->setCurrentConfiguration(settings->customStyle()); + + QGroupBox *options = nullptr; + + using namespace Layouting; + + Column { + Group { + title(Tr::tr("Configuration")), + Form { + s.command, br, + s.supportedMimeTypes, + } + }, + Group { + title(Tr::tr("Options")), + bindTo(&options), + Column { + s.useOtherFiles, + Row { s.useSpecificConfigFile, s.specificConfigFile }, + s.useHomeFile, + Row { s.useCustomStyle, configurations }, + s.formatEntireFileFallback + }, + }, + st + }.attachTo(this); + + s.read(); + + connect(s.command.pathChooser(), &PathChooser::validChanged, options, &QWidget::setEnabled); + options->setEnabled(s.command.pathChooser()->isValid()); + + setOnApply([&s, configurations] { + s.customStyle.setValue(configurations->currentConfiguration()); + s.save(); + }); + } +}; + +UncrustifyOptionsPage::UncrustifyOptionsPage(UncrustifySettings *settings) +{ + setId("Uncrustify"); + setDisplayName(Tr::tr("Uncrustify")); + setCategory(Constants::OPTION_CATEGORY); + setWidgetCreator([settings] { return new UncrustifyOptionsPageWidget(settings); }); +} + } // Beautifier::Internal diff --git a/src/plugins/beautifier/uncrustify/uncrustifysettings.h b/src/plugins/beautifier/uncrustify/uncrustifysettings.h index d57bfe6d39e..3911cfa9e94 100644 --- a/src/plugins/beautifier/uncrustify/uncrustifysettings.h +++ b/src/plugins/beautifier/uncrustify/uncrustifysettings.h @@ -5,41 +5,30 @@ #include "../abstractsettings.h" -namespace Beautifier { -namespace Internal { +namespace Beautifier::Internal { class UncrustifySettings : public AbstractSettings { - Q_OBJECT - public: UncrustifySettings(); - ~UncrustifySettings() override; - bool useOtherFiles() const; - void setUseOtherFiles(bool useOtherFiles); - - bool useHomeFile() const; - void setUseHomeFile(bool useHomeFile); - - bool useCustomStyle() const; - void setUseCustomStyle(bool useCustomStyle); - - QString customStyle() const; - void setCustomStyle(const QString &customStyle); - - bool formatEntireFileFallback() const; - void setFormatEntireFileFallback(bool formatEntireFileFallback); - - QString documentationFilePath() const override; void createDocumentationFile() const override; - Utils::FilePath specificConfigFile() const; - void setSpecificConfigFile(const Utils::FilePath &filePath); + Utils::BoolAspect useOtherFiles{this}; + Utils::BoolAspect useHomeFile{this}; + Utils::BoolAspect useCustomStyle{this}; - bool useSpecificConfigFile() const; - void setUseSpecificConfigFile(bool useConfigFile); + Utils::StringAspect customStyle{this}; + Utils::BoolAspect formatEntireFileFallback{this}; + + Utils::FilePathAspect specificConfigFile{this}; + Utils::BoolAspect useSpecificConfigFile{this}; }; -} // namespace Internal -} // namespace Beautifier +class UncrustifyOptionsPage final : public Core::IOptionsPage +{ +public: + explicit UncrustifyOptionsPage(UncrustifySettings *settings); +}; + +} // Beautifier::Internal diff --git a/src/plugins/bookmarks/bookmarkfilter.cpp b/src/plugins/bookmarks/bookmarkfilter.cpp index 7cc9bf2a759..1cedbade481 100644 --- a/src/plugins/bookmarks/bookmarkfilter.cpp +++ b/src/plugins/bookmarks/bookmarkfilter.cpp @@ -19,101 +19,97 @@ BookmarkFilter::BookmarkFilter(BookmarkManager *manager) { setId("Bookmarks"); setDisplayName(Tr::tr("Bookmarks")); - setDescription(Tr::tr("Matches all bookmarks. Filter by file name, by the text on the line of the " - "bookmark, or by the bookmark's note text.")); + setDescription(Tr::tr("Locates bookmarks. Filter by file name, by the text on the line of the " + "bookmark, or by the bookmark's note text.")); setPriority(Medium); setDefaultShortcutString("b"); } -void BookmarkFilter::prepareSearch(const QString &entry) +LocatorMatcherTasks BookmarkFilter::matchers() { - m_results = {}; - if (m_manager->rowCount() == 0) - return; + using namespace Tasking; - auto match = [this](const QString &name, BookmarkManager::Roles role) { + TreeStorage storage; + + const auto onSetup = [=] { storage->reportOutput(match(storage->input())); }; + return {{Sync(onSetup), storage}}; +} + +LocatorFilterEntries BookmarkFilter::match(const QString &input) const +{ + if (m_manager->rowCount() == 0) + return {}; + const auto match = [this](const QString &name, BookmarkManager::Roles role) { return m_manager->match(m_manager->index(0, 0), role, name, -1, Qt::MatchContains | Qt::MatchWrap); }; - int colonIndex = entry.lastIndexOf(':'); + const int colonIndex = input.lastIndexOf(':'); QModelIndexList fileNameLineNumberMatches; if (colonIndex >= 0) { // Filter by "fileName:lineNumber" pattern - const QString fileName = entry.left(colonIndex); - const QString lineNumber = entry.mid(colonIndex + 1); + const QString fileName = input.left(colonIndex); + const QString lineNumber = input.mid(colonIndex + 1); fileNameLineNumberMatches = match(fileName, BookmarkManager::Filename); - fileNameLineNumberMatches = - Utils::filtered(fileNameLineNumberMatches, [lineNumber](const QModelIndex &index) { - return index.data(BookmarkManager::LineNumber).toString().contains(lineNumber); - }); + fileNameLineNumberMatches = Utils::filtered( + fileNameLineNumberMatches, [lineNumber](const QModelIndex &index) { + return index.data(BookmarkManager::LineNumber).toString().contains(lineNumber); + }); } - const QModelIndexList matches = filteredUnique(fileNameLineNumberMatches - + match(entry, BookmarkManager::Filename) - + match(entry, BookmarkManager::LineNumber) - + match(entry, BookmarkManager::Note) - + match(entry, BookmarkManager::LineText)); - + + match(input, BookmarkManager::Filename) + + match(input, BookmarkManager::LineNumber) + + match(input, BookmarkManager::Note) + + match(input, BookmarkManager::LineText)); + LocatorFilterEntries entries; for (const QModelIndex &idx : matches) { const Bookmark *bookmark = m_manager->bookmarkForIndex(idx); const QString filename = bookmark->filePath().fileName(); - LocatorFilterEntry filterEntry(this, - QString("%1:%2").arg(filename).arg(bookmark->lineNumber()), - QVariant::fromValue(idx)); + LocatorFilterEntry entry; + entry.displayName = QString("%1:%2").arg(filename).arg(bookmark->lineNumber()); + // TODO: according to QModelIndex docs, we shouldn't store model indexes: + // Model indexes should be used immediately and then discarded. + // You should not rely on indexes to remain valid after calling model functions + // that change the structure of the model or delete items. + entry.acceptor = [manager = m_manager, idx] { + if (Bookmark *bookmark = manager->bookmarkForIndex(idx)) + manager->gotoBookmark(bookmark); + return AcceptResult(); + }; if (!bookmark->note().isEmpty()) - filterEntry.extraInfo = bookmark->note(); + entry.extraInfo = bookmark->note(); else if (!bookmark->lineText().isEmpty()) - filterEntry.extraInfo = bookmark->lineText(); + entry.extraInfo = bookmark->lineText(); else - filterEntry.extraInfo = bookmark->filePath().toString(); - int highlightIndex = filterEntry.displayName.indexOf(entry, 0, Qt::CaseInsensitive); + entry.extraInfo = bookmark->filePath().toString(); + int highlightIndex = entry.displayName.indexOf(input, 0, Qt::CaseInsensitive); if (highlightIndex >= 0) { - filterEntry.highlightInfo = {highlightIndex, int(entry.length())}; + entry.highlightInfo = {highlightIndex, int(input.length())}; } else { - highlightIndex = filterEntry.extraInfo.indexOf(entry, 0, Qt::CaseInsensitive); + highlightIndex = entry.extraInfo.indexOf(input, 0, Qt::CaseInsensitive); if (highlightIndex >= 0) { - filterEntry.highlightInfo = {highlightIndex, int(entry.length()), - LocatorFilterEntry::HighlightInfo::ExtraInfo}; + entry.highlightInfo = {highlightIndex, int(input.length()), + LocatorFilterEntry::HighlightInfo::ExtraInfo}; } else if (colonIndex >= 0) { - const QString fileName = entry.left(colonIndex); - const QString lineNumber = entry.mid(colonIndex + 1); - highlightIndex = filterEntry.displayName.indexOf(fileName, 0, Qt::CaseInsensitive); + const QString fileName = input.left(colonIndex); + const QString lineNumber = input.mid(colonIndex + 1); + highlightIndex = entry.displayName.indexOf(fileName, 0, + Qt::CaseInsensitive); if (highlightIndex >= 0) { - filterEntry.highlightInfo = {highlightIndex, int(fileName.length())}; - highlightIndex = filterEntry.displayName.indexOf( + entry.highlightInfo = {highlightIndex, int(fileName.length())}; + highlightIndex = entry.displayName.indexOf( lineNumber, highlightIndex, Qt::CaseInsensitive); if (highlightIndex >= 0) { - filterEntry.highlightInfo.startsDisplay += highlightIndex; - filterEntry.highlightInfo.lengthsDisplay += lineNumber.length(); + entry.highlightInfo.startsDisplay += highlightIndex; + entry.highlightInfo.lengthsDisplay += lineNumber.length(); } } } } - - filterEntry.displayIcon = bookmark->icon(); - m_results.append(filterEntry); - } -} - -QList BookmarkFilter::matchesFor(QFutureInterface &future, - const QString &entry) -{ - Q_UNUSED(future) - Q_UNUSED(entry) - return m_results; -} - -void BookmarkFilter::accept(const LocatorFilterEntry &selection, QString *newText, - int *selectionStart, int *selectionLength) const -{ - Q_UNUSED(newText) - Q_UNUSED(selectionStart) - Q_UNUSED(selectionLength) - if (const Bookmark *bookmark = m_manager->bookmarkForIndex( - selection.internalData.toModelIndex())) { - m_manager->gotoBookmark(bookmark); + entry.displayIcon = bookmark->icon(); + entries.append(entry); } + return entries; } } // Bookmarks::Internal diff --git a/src/plugins/bookmarks/bookmarkfilter.h b/src/plugins/bookmarks/bookmarkfilter.h index 0c69e2b6d45..f85eff9b7d7 100644 --- a/src/plugins/bookmarks/bookmarkfilter.h +++ b/src/plugins/bookmarks/bookmarkfilter.h @@ -12,16 +12,13 @@ class BookmarkManager; class BookmarkFilter : public Core::ILocatorFilter { public: - explicit BookmarkFilter(BookmarkManager *manager); - void prepareSearch(const QString &entry) override; - QList matchesFor(QFutureInterface &future, - const QString &entry) override; - void accept(const Core::LocatorFilterEntry &selection, QString *newText, - int *selectionStart, int *selectionLength) const override; + BookmarkFilter(BookmarkManager *manager); private: + Core::LocatorMatcherTasks matchers() final; + Core::LocatorFilterEntries match(const QString &input) const; + BookmarkManager *m_manager = nullptr; // not owned - QList m_results; }; } // Bookmarks::Internal diff --git a/src/plugins/bookmarks/bookmarkmanager.cpp b/src/plugins/bookmarks/bookmarkmanager.cpp index e7c5a4e388e..83a2650b750 100644 --- a/src/plugins/bookmarks/bookmarkmanager.cpp +++ b/src/plugins/bookmarks/bookmarkmanager.cpp @@ -7,13 +7,13 @@ #include "bookmarks_global.h" #include "bookmarkstr.h" +#include +#include #include #include #include -#include -#include -#include -#include +#include + #include #include #include @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -39,7 +38,6 @@ Q_DECLARE_METATYPE(Bookmarks::Internal::Bookmark*) -using namespace ProjectExplorer; using namespace Core; using namespace Utils; @@ -256,11 +254,12 @@ void BookmarkView::keyPressEvent(QKeyEvent *event) void BookmarkView::removeAll() { - if (CheckableMessageBox::doNotAskAgainQuestion(this, - Tr::tr("Remove All Bookmarks"), - Tr::tr("Are you sure you want to remove all bookmarks from all files in the current session?"), - ICore::settings(), - QLatin1String("RemoveAllBookmarks")) != QDialogButtonBox::Yes) + if (CheckableMessageBox::question(this, + Tr::tr("Remove All Bookmarks"), + Tr::tr("Are you sure you want to remove all bookmarks from " + "all files in the current session?"), + QString("RemoveAllBookmarks")) + != QMessageBox::Yes) return; // The performance of this function could be greatly improved. diff --git a/src/plugins/bookmarks/bookmarksplugin.cpp b/src/plugins/bookmarks/bookmarksplugin.cpp index 2f25571b538..7473876ec63 100644 --- a/src/plugins/bookmarks/bookmarksplugin.cpp +++ b/src/plugins/bookmarks/bookmarksplugin.cpp @@ -128,14 +128,16 @@ BookmarksPluginPrivate::BookmarksPluginPrivate() mbm->addAction(cmd); connect(&m_toggleAction, &QAction::triggered, this, [this] { - BaseTextEditor *editor = BaseTextEditor::currentTextEditor(); - if (editor && !editor->document()->isTemporary()) + IEditor *editor = EditorManager::currentEditor(); + auto widget = TextEditorWidget::fromEditor(editor); + if (widget && editor && !editor->document()->isTemporary()) m_bookmarkManager.toggleBookmark(editor->document()->filePath(), editor->currentLine()); }); connect(&m_editAction, &QAction::triggered, this, [this] { - BaseTextEditor *editor = BaseTextEditor::currentTextEditor(); - if (editor && !editor->document()->isTemporary()) { + IEditor *editor = EditorManager::currentEditor(); + auto widget = TextEditorWidget::fromEditor(editor); + if (widget && editor && !editor->document()->isTemporary()) { const FilePath filePath = editor->document()->filePath(); const int line = editor->currentLine(); if (!m_bookmarkManager.hasBookmarkInPosition(filePath, line)) diff --git a/src/plugins/boot2qt/device-detection/qdbwatcher.cpp b/src/plugins/boot2qt/device-detection/qdbwatcher.cpp index 3375e10e026..869b2d84fcb 100644 --- a/src/plugins/boot2qt/device-detection/qdbwatcher.cpp +++ b/src/plugins/boot2qt/device-detection/qdbwatcher.cpp @@ -8,7 +8,7 @@ #include "../qdbutils.h" #include -#include +#include #include #include @@ -118,7 +118,7 @@ void QdbWatcher::forkHostServer() showMessage(message, true); return; } - if (Utils::QtcProcess::startDetached({qdbFilePath, {"server"}})) + if (Utils::Process::startDetached({qdbFilePath, {"server"}})) showMessage(Tr::tr("QDB host server started."), false); else showMessage(Tr::tr("Could not start QDB host server in %1").arg(qdbFilePath.toString()), true); diff --git a/src/plugins/boot2qt/qdbdeployconfigurationfactory.cpp b/src/plugins/boot2qt/qdbdeployconfigurationfactory.cpp index f1156857948..3abb700df1d 100644 --- a/src/plugins/boot2qt/qdbdeployconfigurationfactory.cpp +++ b/src/plugins/boot2qt/qdbdeployconfigurationfactory.cpp @@ -7,7 +7,7 @@ #include "qdbtr.h" #include -#include "projectexplorer/devicesupport/idevice.h" +#include #include #include #include diff --git a/src/plugins/boot2qt/qdbdevice.cpp b/src/plugins/boot2qt/qdbdevice.cpp index 90568f42641..43783279d69 100644 --- a/src/plugins/boot2qt/qdbdevice.cpp +++ b/src/plugins/boot2qt/qdbdevice.cpp @@ -15,8 +15,8 @@ #include #include +#include #include -#include #include #include @@ -30,11 +30,10 @@ using namespace Utils; namespace Qdb::Internal { -class QdbProcessImpl : public LinuxProcessInterface +class QdbProcessImpl : public SshProcessInterface { public: - QdbProcessImpl(const LinuxDevice *linuxDevice) - : LinuxProcessInterface(linuxDevice) {} + QdbProcessImpl(const IDevice::ConstPtr &device) : SshProcessInterface(device) {} ~QdbProcessImpl() { killIfRunning(); } private: @@ -51,7 +50,7 @@ class DeviceApplicationObserver : public QObject public: DeviceApplicationObserver(const IDevice::ConstPtr &device, const CommandLine &command) { - connect(&m_appRunner, &QtcProcess::done, this, &DeviceApplicationObserver::handleDone); + connect(&m_appRunner, &Process::done, this, &DeviceApplicationObserver::handleDone); QTC_ASSERT(device, return); m_deviceName = device->displayName(); @@ -95,7 +94,7 @@ private: deleteLater(); } - QtcProcess m_appRunner; + Process m_appRunner; QString m_deviceName; }; @@ -105,6 +104,7 @@ private: QdbDevice::QdbDevice() { setDisplayType(Tr::tr("Boot2Qt Device")); + setType(Constants::QdbLinuxOsType); addDeviceAction({Tr::tr("Reboot Device"), [](const IDevice::Ptr &device, QWidget *) { (void) new DeviceApplicationObserver(device, {device->filePath("reboot"), {}}); @@ -124,7 +124,7 @@ ProjectExplorer::IDeviceWidget *QdbDevice::createWidget() ProcessInterface *QdbDevice::createProcessInterface() const { - return new QdbProcessImpl(this); + return new QdbProcessImpl(sharedFromThis()); } void QdbDevice::setSerialNumber(const QString &serial) @@ -248,6 +248,7 @@ QdbLinuxDeviceFactory::QdbLinuxDeviceFactory() { setDisplayName(Tr::tr("Boot2Qt Device")); setCombinedIcon(":/qdb/images/qdbdevicesmall.png", ":/qdb/images/qdbdevice.png"); + setQuickCreationAllowed(true); setConstructionFunction(&QdbDevice::create); setCreator([] { QdbDeviceWizard wizard(Core::ICore::dialogParent()); diff --git a/src/plugins/boot2qt/qdbdevicedebugsupport.cpp b/src/plugins/boot2qt/qdbdevicedebugsupport.cpp index 97529dfa9fb..d6e198538f9 100644 --- a/src/plugins/boot2qt/qdbdevicedebugsupport.cpp +++ b/src/plugins/boot2qt/qdbdevicedebugsupport.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include #include using namespace Debugger; @@ -34,13 +34,13 @@ public: { setId("QdbDebuggeeRunner"); - connect(&m_launcher, &QtcProcess::started, this, &RunWorker::reportStarted); - connect(&m_launcher, &QtcProcess::done, this, &RunWorker::reportStopped); + connect(&m_launcher, &Process::started, this, &RunWorker::reportStarted); + connect(&m_launcher, &Process::done, this, &RunWorker::reportStopped); - connect(&m_launcher, &QtcProcess::readyReadStandardOutput, this, [this] { + connect(&m_launcher, &Process::readyReadStandardOutput, this, [this] { appendMessage(m_launcher.readAllStandardOutput(), StdOutFormat); }); - connect(&m_launcher, &QtcProcess::readyReadStandardError, this, [this] { + connect(&m_launcher, &Process::readyReadStandardError, this, [this] { appendMessage(m_launcher.readAllStandardError(), StdErrFormat); }); @@ -112,7 +112,7 @@ private: bool m_useGdbServer; bool m_useQmlServer; QmlDebug::QmlDebugServicesPreset m_qmlServices; - QtcProcess m_launcher; + Process m_launcher; }; // QdbDeviceRunSupport diff --git a/src/plugins/boot2qt/qdbmakedefaultappstep.cpp b/src/plugins/boot2qt/qdbmakedefaultappstep.cpp index 70c1c40acba..ae5806deb95 100644 --- a/src/plugins/boot2qt/qdbmakedefaultappstep.cpp +++ b/src/plugins/boot2qt/qdbmakedefaultappstep.cpp @@ -14,27 +14,35 @@ #include #include -#include +#include using namespace ProjectExplorer; +using namespace Tasking; using namespace Utils; -using namespace Utils::Tasking; namespace Qdb::Internal { -// QdbMakeDefaultAppService - -class QdbMakeDefaultAppService : public RemoteLinux::AbstractRemoteLinuxDeployService +class QdbMakeDefaultAppStep final : public RemoteLinux::AbstractRemoteLinuxDeployStep { public: - void setMakeDefault(bool makeDefault) { m_makeDefault = makeDefault; } + QdbMakeDefaultAppStep(BuildStepList *bsl, Id id) + : AbstractRemoteLinuxDeployStep(bsl, id) + { + auto selection = addAspect(); + selection->setSettingsKey("QdbMakeDefaultDeployStep.MakeDefault"); + selection->addOption(Tr::tr("Set this application to start by default")); + selection->addOption(Tr::tr("Reset default application")); + + setInternalInitializer([this, selection] { + m_makeDefault = selection->value() == 0; + return isDeploymentPossible(); + }); + } private: - bool isDeploymentNecessary() const final { return true; } - Group deployRecipe() final { - const auto setupHandler = [this](QtcProcess &process) { + const auto setupHandler = [this](Process &process) { QString remoteExe; if (RunConfiguration *rc = target()->activeRunConfiguration()) { if (auto exeAspect = rc->aspect()) @@ -46,50 +54,26 @@ private: else cmd.addArg("--remove-default"); process.setCommand(cmd); - QtcProcess *proc = &process; - connect(proc, &QtcProcess::readyReadStandardError, this, [this, proc] { - emit stdErrData(proc->readAllStandardError()); + Process *proc = &process; + connect(proc, &Process::readyReadStandardError, this, [this, proc] { + handleStdErrData(proc->readAllStandardError()); }); }; - const auto doneHandler = [this](const QtcProcess &) { + const auto doneHandler = [this](const Process &) { if (m_makeDefault) - emit progressMessage(Tr::tr("Application set as the default one.")); + addProgressMessage(Tr::tr("Application set as the default one.")); else - emit progressMessage(Tr::tr("Reset the default application.")); + addProgressMessage(Tr::tr("Reset the default application.")); }; - const auto errorHandler = [this](const QtcProcess &process) { - emit errorMessage(Tr::tr("Remote process failed: %1").arg(process.errorString())); + const auto errorHandler = [this](const Process &process) { + addErrorMessage(Tr::tr("Remote process failed: %1").arg(process.errorString())); }; - return Group { Process(setupHandler, doneHandler, errorHandler) }; + return Group { ProcessTask(setupHandler, doneHandler, errorHandler) }; } bool m_makeDefault = true; }; -// QdbMakeDefaultAppStep - -class QdbMakeDefaultAppStep final : public RemoteLinux::AbstractRemoteLinuxDeployStep -{ -public: - QdbMakeDefaultAppStep(BuildStepList *bsl, Id id) - : AbstractRemoteLinuxDeployStep(bsl, id) - { - auto service = new QdbMakeDefaultAppService; - setDeployService(service); - - auto selection = addAspect(); - selection->setSettingsKey("QdbMakeDefaultDeployStep.MakeDefault"); - selection->addOption(Tr::tr("Set this application to start by default")); - selection->addOption(Tr::tr("Reset default application")); - - setInternalInitializer([service, selection] { - service->setMakeDefault(selection->value() == 0); - return service->isDeploymentPossible(); - }); - } -}; - - // QdbMakeDefaultAppStepFactory QdbMakeDefaultAppStepFactory::QdbMakeDefaultAppStepFactory() diff --git a/src/plugins/boot2qt/qdbplugin.cpp b/src/plugins/boot2qt/qdbplugin.cpp index a73646b6c82..8c806cb158f 100644 --- a/src/plugins/boot2qt/qdbplugin.cpp +++ b/src/plugins/boot2qt/qdbplugin.cpp @@ -27,22 +27,18 @@ #include -#include -#include -#include #include -#include #include -#include +#include +#include #include using namespace ProjectExplorer; using namespace Utils; -namespace Qdb { -namespace Internal { +namespace Qdb::Internal { static FilePath flashWizardFilePath() { @@ -53,9 +49,9 @@ static void startFlashingWizard() { const FilePath filePath = flashWizardFilePath(); if (HostOsInfo::isWindowsHost()) { - if (QtcProcess::startDetached({"explorer.exe", {filePath.toUserOutput()}})) + if (Process::startDetached({"explorer.exe", {filePath.toUserOutput()}})) return; - } else if (QtcProcess::startDetached({filePath, {}})) { + } else if (Process::startDetached({filePath, {}})) { return; } const QString message = Tr::tr("Flash wizard \"%1\" failed to start."); @@ -100,14 +96,12 @@ void registerFlashAction(QObject *parentForAction) toolsContainer->addAction(flashCommand, flashActionId); } -template -class QdbDeployStepFactory : public ProjectExplorer::BuildStepFactory +class QdbDeployStepFactory : public BuildStepFactory { public: - explicit QdbDeployStepFactory(Id id) + explicit QdbDeployStepFactory(Id existingStepId) { - registerStep(id); - setDisplayName(Step::displayName()); + cloneStepCreator(existingStepId); setSupportedConfiguration(Constants::QdbDeployConfigurationId); setSupportedStepList(ProjectExplorer::Constants::BUILDSTEPS_DEPLOY); } @@ -125,12 +119,9 @@ public: QdbStopApplicationStepFactory m_stopApplicationStepFactory; QdbMakeDefaultAppStepFactory m_makeDefaultAppStepFactory; - QdbDeployStepFactory - m_directUploadStepFactory{RemoteLinux::Constants::DirectUploadStepId}; - QdbDeployStepFactory - m_rsyncDeployStepFactory{RemoteLinux::Constants::RsyncDeployStepId}; - QdbDeployStepFactory - m_makeInstallStepFactory{RemoteLinux::Constants::MakeInstallStepId}; + QdbDeployStepFactory m_directUploadStepFactory{RemoteLinux::Constants::DirectUploadStepId}; + QdbDeployStepFactory m_rsyncDeployStepFactory{RemoteLinux::Constants::RsyncDeployStepId}; + QdbDeployStepFactory m_makeInstallStepFactory{RemoteLinux::Constants::MakeInstallStepId}; const QList supportedRunConfigs { m_runConfigFactory.runConfigurationId(), @@ -180,5 +171,4 @@ void QdbPluginPrivate::setupDeviceDetection() m_deviceDetector.start(); } -} // Internal -} // Qdb +} // Qdb::Internal diff --git a/src/plugins/boot2qt/qdbstopapplicationstep.cpp b/src/plugins/boot2qt/qdbstopapplicationstep.cpp index 60d891beafb..5fef60268f9 100644 --- a/src/plugins/boot2qt/qdbstopapplicationstep.cpp +++ b/src/plugins/boot2qt/qdbstopapplicationstep.cpp @@ -13,60 +13,14 @@ #include -#include +#include using namespace ProjectExplorer; +using namespace Tasking; using namespace Utils; -using namespace Utils::Tasking; namespace Qdb::Internal { -// QdbStopApplicationService - -class QdbStopApplicationService : public RemoteLinux::AbstractRemoteLinuxDeployService -{ -private: - bool isDeploymentNecessary() const final { return true; } - Group deployRecipe() final; -}; - -Group QdbStopApplicationService::deployRecipe() -{ - const auto setupHandler = [this](QtcProcess &process) { - const auto device = DeviceKitAspect::device(target()->kit()); - if (!device) { - emit errorMessage(Tr::tr("No device to stop the application on.")); - return TaskAction::StopWithError; - } - QTC_CHECK(device); - process.setCommand({device->filePath(Constants::AppcontrollerFilepath), {"--stop"}}); - process.setWorkingDirectory("/usr/bin"); - QtcProcess *proc = &process; - connect(proc, &QtcProcess::readyReadStandardOutput, this, [this, proc] { - emit stdOutData(proc->readAllStandardOutput()); - }); - return TaskAction::Continue; - }; - const auto doneHandler = [this](const QtcProcess &) { - emit progressMessage(Tr::tr("Stopped the running application.")); - }; - const auto errorHandler = [this](const QtcProcess &process) { - const QString errorOutput = process.cleanedStdErr(); - const QString failureMessage = Tr::tr("Could not check and possibly stop running application."); - if (process.exitStatus() == QProcess::CrashExit) { - emit errorMessage(failureMessage); - } else if (process.result() != ProcessResult::FinishedWithSuccess) { - emit stdErrData(process.errorString()); - } else if (errorOutput.contains("Could not connect: Connection refused")) { - emit progressMessage(Tr::tr("Checked that there is no running application.")); - } else if (!errorOutput.isEmpty()) { - emit stdErrData(errorOutput); - emit errorMessage(failureMessage); - } - }; - return Group { Process(setupHandler, doneHandler, errorHandler) }; -} - // QdbStopApplicationStep class QdbStopApplicationStep final : public RemoteLinux::AbstractRemoteLinuxDeployStep @@ -75,15 +29,50 @@ public: QdbStopApplicationStep(BuildStepList *bsl, Id id) : AbstractRemoteLinuxDeployStep(bsl, id) { - auto service = new QdbStopApplicationService; - setDeployService(service); - setWidgetExpandedByDefault(false); - setInternalInitializer([service] { return service->isDeploymentPossible(); }); + setInternalInitializer([this] { return isDeploymentPossible(); }); } + + Group deployRecipe() final; }; +Group QdbStopApplicationStep::deployRecipe() +{ + const auto setupHandler = [this](Process &process) { + const auto device = DeviceKitAspect::device(target()->kit()); + if (!device) { + addErrorMessage(Tr::tr("No device to stop the application on.")); + return TaskAction::StopWithError; + } + QTC_CHECK(device); + process.setCommand({device->filePath(Constants::AppcontrollerFilepath), {"--stop"}}); + process.setWorkingDirectory("/usr/bin"); + Process *proc = &process; + connect(proc, &Process::readyReadStandardOutput, this, [this, proc] { + handleStdOutData(proc->readAllStandardOutput()); + }); + return TaskAction::Continue; + }; + const auto doneHandler = [this](const Process &) { + addProgressMessage(Tr::tr("Stopped the running application.")); + }; + const auto errorHandler = [this](const Process &process) { + const QString errorOutput = process.cleanedStdErr(); + const QString failureMessage = Tr::tr("Could not check and possibly stop running application."); + if (process.exitStatus() == QProcess::CrashExit) { + addErrorMessage(failureMessage); + } else if (process.result() != ProcessResult::FinishedWithSuccess) { + handleStdErrData(process.errorString()); + } else if (errorOutput.contains("Could not connect: Connection refused")) { + addProgressMessage(Tr::tr("Checked that there is no running application.")); + } else if (!errorOutput.isEmpty()) { + handleStdErrData(errorOutput); + addErrorMessage(failureMessage); + } + }; + return Group { ProcessTask(setupHandler, doneHandler, errorHandler) }; +} // QdbStopApplicationStepFactory diff --git a/src/plugins/clangcodemodel/CMakeLists.txt b/src/plugins/clangcodemodel/CMakeLists.txt index ace39071713..243a330efad 100644 --- a/src/plugins/clangcodemodel/CMakeLists.txt +++ b/src/plugins/clangcodemodel/CMakeLists.txt @@ -51,7 +51,6 @@ extend_qtc_plugin(ClangCodeModel CONDITION WITH_TESTS SOURCES test/activationsequenceprocessortest.cpp test/activationsequenceprocessortest.h - test/clangbatchfileprocessor.cpp test/clangbatchfileprocessor.h test/clangdtests.cpp test/clangdtests.h test/clangfixittest.cpp test/clangfixittest.h test/data/clangtestdata.qrc diff --git a/src/plugins/clangcodemodel/clangcodemodel.qbs b/src/plugins/clangcodemodel/clangcodemodel.qbs index 45a6abc347d..903a81147a9 100644 --- a/src/plugins/clangcodemodel/clangcodemodel.qbs +++ b/src/plugins/clangcodemodel/clangcodemodel.qbs @@ -94,15 +94,11 @@ QtcPlugin { } } - Group { - name: "Tests" - condition: qtc.testsEnabled + QtcTestFiles { prefix: "test/" files: [ "activationsequenceprocessortest.cpp", "activationsequenceprocessortest.h", - "clangbatchfileprocessor.cpp", - "clangbatchfileprocessor.h", "clangdtests.cpp", "clangdtests.h", "clangfixittest.cpp", diff --git a/src/plugins/clangcodemodel/clangcodemodelplugin.cpp b/src/plugins/clangcodemodel/clangcodemodelplugin.cpp index 2a3772afb26..9b5ec425231 100644 --- a/src/plugins/clangcodemodel/clangcodemodelplugin.cpp +++ b/src/plugins/clangcodemodel/clangcodemodelplugin.cpp @@ -10,7 +10,6 @@ #ifdef WITH_TESTS # include "test/activationsequenceprocessortest.h" -# include "test/clangbatchfileprocessor.h" # include "test/clangdtests.h" # include "test/clangfixittest.h" #endif @@ -28,15 +27,16 @@ #include #include #include -#include +#include +#include #include #include #include +#include #include #include -#include #include using namespace Core; @@ -49,7 +49,7 @@ void ClangCodeModelPlugin::generateCompilationDB() { using namespace CppEditor; - Target *target = SessionManager::startupTarget(); + Target *target = ProjectManager::startupTarget(); if (!target) return; @@ -61,7 +61,7 @@ void ClangCodeModelPlugin::generateCompilationDB() baseDir = TemporaryDirectory::masterDirectoryFilePath(); QFuture task - = Utils::runAsync(&Internal::generateCompilationDB, ProjectInfoList{projectInfo}, + = Utils::asyncRun(&Internal::generateCompilationDB, ProjectInfoList{projectInfo}, baseDir, CompilationDbPurpose::Project, warningsConfigForProject(target->project()), globalClangOptions(), @@ -78,15 +78,8 @@ ClangCodeModelPlugin::~ClangCodeModelPlugin() void ClangCodeModelPlugin::initialize() { TaskHub::addCategory(Constants::TASK_CATEGORY_DIAGNOSTICS, Tr::tr("Clang Code Model")); - - connect(ProjectExplorerPlugin::instance(), - &ProjectExplorerPlugin::finishedInitialization, - this, - &ClangCodeModelPlugin::maybeHandleBatchFileAndExit); - CppEditor::CppModelManager::instance()->activateClangCodeModel( - std::make_unique()); - + std::make_unique()); createCompilationDBAction(); #ifdef WITH_TESTS @@ -109,7 +102,7 @@ void ClangCodeModelPlugin::createCompilationDBAction() Tr::tr("Generate Compilation Database"), Tr::tr("Generate Compilation Database for \"%1\""), ParameterAction::AlwaysEnabled, this); - Project *startupProject = SessionManager::startupProject(); + Project *startupProject = ProjectManager::startupProject(); if (startupProject) m_generateCompilationDBAction->setParameter(startupProject->displayName()); Command *command = ActionManager::registerAction(m_generateCompilationDBAction, @@ -136,7 +129,7 @@ void ClangCodeModelPlugin::createCompilationDBAction() "Generator is already running."); return; } - Project * const project = SessionManager::startupProject(); + Project * const project = ProjectManager::startupProject(); if (!project) { MessageManager::writeDisrupting("Cannot generate compilation database: " "No active project."); @@ -154,21 +147,21 @@ void ClangCodeModelPlugin::createCompilationDBAction() }); connect(CppEditor::CppModelManager::instance(), &CppEditor::CppModelManager::projectPartsUpdated, this, [this](Project *project) { - if (project != SessionManager::startupProject()) + if (project != ProjectManager::startupProject()) return; m_generateCompilationDBAction->setParameter(project->displayName()); }); - connect(SessionManager::instance(), &SessionManager::startupProjectChanged, + connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, this, [this](Project *project) { m_generateCompilationDBAction->setParameter(project ? project->displayName() : ""); }); - connect(SessionManager::instance(), &SessionManager::projectDisplayNameChanged, + connect(ProjectManager::instance(), &ProjectManager::projectDisplayNameChanged, this, [this](Project *project) { - if (project != SessionManager::startupProject()) + if (project != ProjectManager::startupProject()) return; m_generateCompilationDBAction->setParameter(project->displayName()); }); - connect(SessionManager::instance(), &SessionManager::projectAdded, + connect(ProjectManager::instance(), &ProjectManager::projectAdded, this, [this](Project *project) { project->registerGenerator(Constants::GENERATE_COMPILATION_DB, m_generateCompilationDBAction->text(), @@ -176,16 +169,4 @@ void ClangCodeModelPlugin::createCompilationDBAction() }); } -// For e.g. creation of profile-guided optimization builds. -void ClangCodeModelPlugin::maybeHandleBatchFileAndExit() const -{ -#ifdef WITH_TESTS - const QString batchFilePath = qtcEnvironmentVariable("QTC_CLANG_BATCH"); - if (!batchFilePath.isEmpty() && QTC_GUARD(QFileInfo::exists(batchFilePath))) { - const bool runSucceeded = runClangBatchFile(batchFilePath); - QCoreApplication::exit(!runSucceeded); - } -#endif -} - -} // ClangCodeModel::Internal +} // namespace ClangCodeModel::Internal diff --git a/src/plugins/clangcodemodel/clangcodemodelplugin.h b/src/plugins/clangcodemodel/clangcodemodelplugin.h index 664cf113aa0..d0456257f2d 100644 --- a/src/plugins/clangcodemodel/clangcodemodelplugin.h +++ b/src/plugins/clangcodemodel/clangcodemodelplugin.h @@ -23,8 +23,6 @@ public: void initialize() override; private: - void maybeHandleBatchFileAndExit() const; - void generateCompilationDB(); void createCompilationDBAction(); diff --git a/src/plugins/clangcodemodel/clangdast.cpp b/src/plugins/clangcodemodel/clangdast.cpp index 6b97146dd56..01f12d1177e 100644 --- a/src/plugins/clangcodemodel/clangdast.cpp +++ b/src/plugins/clangcodemodel/clangdast.cpp @@ -172,6 +172,20 @@ bool ClangdAstNode::hasChildWithRole(const QString &role) const [&role](const ClangdAstNode &c) { return c.role() == role; }); } +bool ClangdAstNode::hasChild(const std::function &predicate, + bool recursive) const +{ + std::function fullPredicate = predicate; + if (recursive) { + fullPredicate = [&predicate](const ClangdAstNode &n) { + if (predicate(n)) + return true; + return n.hasChild(predicate, true); + }; + } + return Utils::contains(children().value_or(QList()), fullPredicate); +} + QString ClangdAstNode::operatorString() const { if (kind() == "BinaryOperator") diff --git a/src/plugins/clangcodemodel/clangdast.h b/src/plugins/clangcodemodel/clangdast.h index 7eb24e9f21f..cdb87c7be07 100644 --- a/src/plugins/clangcodemodel/clangdast.h +++ b/src/plugins/clangcodemodel/clangdast.h @@ -77,6 +77,7 @@ public: bool childContainsRange(int index, const LanguageServerProtocol::Range &range) const; bool hasChildWithRole(const QString &role) const; + bool hasChild(const std::function &predicate, bool recursive) const; QString operatorString() const; enum class FileStatus { Ours, Foreign, Mixed, Unknown }; diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index 53a5b967aa0..0082ff6cef9 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -9,12 +9,10 @@ #include "clangdcompletion.h" #include "clangdfindreferences.h" #include "clangdfollowsymbol.h" -#include "clangdlocatorfilters.h" #include "clangdmemoryusagewidget.h" #include "clangdquickfixes.h" #include "clangdsemantichighlighting.h" #include "clangdswitchdecldef.h" -#include "clangmodelmanagersupport.h" #include "clangtextmark.h" #include "clangutils.h" #include "tasktimers.h" @@ -38,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -48,7 +47,7 @@ #include #include #include -#include +#include #include #include #include @@ -57,10 +56,11 @@ #include #include #include +#include #include #include #include -#include +#include #include #include @@ -128,6 +128,27 @@ public: : Request("textDocument/symbolInfo", params) {} }; +class ClangdOutlineItem : public LanguageClientOutlineItem +{ + using LanguageClientOutlineItem::LanguageClientOutlineItem; +private: + QVariant data(int column, int role) const override + { + switch (role) { + case Qt::DisplayRole: + return ClangdClient::displayNameFromDocumentSymbol( + static_cast(type()), name(), detail()); + case Qt::ForegroundRole: + if ((detail().endsWith("class") || detail().endsWith("struct")) + && range().end() == selectionRange().end()) { + return creatorTheme()->color(Theme::TextColorDisabled); + } + break; + } + return LanguageClientOutlineItem::data(column, role); + } +}; + void setupClangdConfigFile() { const Utils::FilePath targetConfigFile = CppEditor::ClangdSettings::clangdUserConfigFilePath(); @@ -192,7 +213,7 @@ static BaseClientInterface *clientInterface(Project *project, const Utils::FileP if (settings.clangdVersion() >= QVersionNumber(16)) cmd.addArg("--rename-file-limit=0"); if (!jsonDbDir.isEmpty()) - cmd.addArg("--compile-commands-dir=" + jsonDbDir.onDevice(clangdExePath).path()); + cmd.addArg("--compile-commands-dir=" + clangdExePath.withNewMappedPath(jsonDbDir).path()); if (clangdLogServer().isDebugEnabled()) cmd.addArgs({"--log=verbose", "--pretty"}); cmd.addArg("--use-dirty-headers"); @@ -427,7 +448,6 @@ ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir, c }); setCurrentProject(project); setDocumentChangeUpdateThreshold(d->settings.documentUpdateThreshold); - setSymbolStringifier(displayNameFromDocumentSymbol); setSemanticTokensHandler([this](TextDocument *doc, const QList &tokens, int version, bool force) { d->handleSemanticTokens(doc, tokens, version, force); @@ -450,12 +470,7 @@ ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir, c } }); - connect(this, &Client::initialized, this, [this] { - auto currentDocumentFilter = static_cast( - CppEditor::CppModelManager::instance()->currentDocumentFilter()); - currentDocumentFilter->updateCurrentClient(); - d->openedExtraFiles.clear(); - }); + connect(this, &Client::initialized, this, [this] { d->openedExtraFiles.clear(); }); start(); } @@ -698,6 +713,12 @@ DiagnosticManager *ClangdClient::createDiagnosticManager() return diagnosticManager; } +LanguageClientOutlineItem *ClangdClient::createOutlineItem( + const LanguageServerProtocol::DocumentSymbol &symbol) +{ + return new ClangdOutlineItem(this, symbol); +} + bool ClangdClient::referencesShadowFile(const TextEditor::TextDocument *doc, const Utils::FilePath &candidate) { @@ -710,7 +731,7 @@ bool ClangdClient::fileBelongsToProject(const Utils::FilePath &filePath) const { if (CppEditor::ClangdSettings::instance().granularity() == CppEditor::ClangdSettings::Granularity::Session) { - return SessionManager::projectForFile(filePath); + return ProjectManager::projectForFile(filePath); } return Client::fileBelongsToProject(filePath); } @@ -1477,7 +1498,7 @@ void ClangdClient::Private::handleSemanticTokens(TextDocument *doc, clangdVersion = q->versionNumber(), this] { try { - return Utils::runAsync(doSemanticHighlighting, filePath, tokens, text, ast, doc, + return Utils::asyncRun(doSemanticHighlighting, filePath, tokens, text, ast, doc, rev, clangdVersion, highlightingTimer); } catch (const std::exception &e) { qWarning() << "caught" << e.what() << "in main highlighting thread"; diff --git a/src/plugins/clangcodemodel/clangdclient.h b/src/plugins/clangcodemodel/clangdclient.h index 67416d73795..27ec66a3017 100644 --- a/src/plugins/clangcodemodel/clangdclient.h +++ b/src/plugins/clangcodemodel/clangdclient.h @@ -3,12 +3,12 @@ #pragma once -#include #include #include #include #include #include +#include #include @@ -119,7 +119,7 @@ public: signals: void indexingFinished(); - void foundReferences(const QList &items); + void foundReferences(const Utils::SearchResultItems &items); void findUsagesDone(); void helpItemGathered(const Core::HelpItem &helpItem); void highlightingResultsReady(const TextEditor::HighlightingResults &results, @@ -137,6 +137,8 @@ private: const CustomInspectorTabs createCustomInspectorTabs() override; TextEditor::RefactoringChangesData *createRefactoringChangesBackend() const override; LanguageClient::DiagnosticManager *createDiagnosticManager() override; + LanguageClient::LanguageClientOutlineItem *createOutlineItem( + const LanguageServerProtocol::DocumentSymbol &symbol) override; bool referencesShadowFile(const TextEditor::TextDocument *doc, const Utils::FilePath &candidate) override; bool fileBelongsToProject(const Utils::FilePath &filePath) const override; diff --git a/src/plugins/clangcodemodel/clangdfindreferences.cpp b/src/plugins/clangcodemodel/clangdfindreferences.cpp index 8a2d3faaeff..b44fd41a828 100644 --- a/src/plugins/clangcodemodel/clangdfindreferences.cpp +++ b/src/plugins/clangcodemodel/clangdfindreferences.cpp @@ -21,9 +21,10 @@ #include #include +#include #include #include -#include +#include #include @@ -71,7 +72,7 @@ public: const Position linkAsPosition; const QPointer search; const LinkHandler callback; - QList declDefItems; + SearchResultItems declDefItems; bool openedExtraFileForLink = false; bool declHasUsedTag = false; bool recursiveCallDetected = false; @@ -88,7 +89,7 @@ public: const SearchResult *search, const ReplacementData &replacementData, const QString &newSymbolName, - const QList &checkedItems, + const SearchResultItems &checkedItems, bool preserveCase); void handleFindUsagesResult(const QList &locations); void finishSearch(); @@ -142,7 +143,7 @@ ClangdFindReferences::ClangdFindReferences(ClangdClient *client, TextDocument *d d->search->setAdditionalReplaceWidget(renameFilesCheckBox); const auto renameHandler = [search = d->search](const QString &newSymbolName, - const QList &checkedItems, + const SearchResultItems &checkedItems, bool preserveCase) { const auto replacementData = search->userData().value(); Private::handleRenameRequest(search, replacementData, newSymbolName, checkedItems, @@ -150,7 +151,7 @@ ClangdFindReferences::ClangdFindReferences(ClangdClient *client, TextDocument *d }; connect(d->search, &SearchResult::replaceButtonClicked, renameHandler); } - connect(d->search, &SearchResult::activated, [](const SearchResultItem& item) { + connect(d->search, &SearchResult::activated, [](const SearchResultItem &item) { EditorManager::openEditorAtSearchResult(item); }); if (d->search->isInteractive()) @@ -242,7 +243,7 @@ void ClangdFindReferences::Private::handleRenameRequest( const SearchResult *search, const ReplacementData &replacementData, const QString &newSymbolName, - const QList &checkedItems, + const SearchResultItems &checkedItems, bool preserveCase) { const Utils::FilePaths filePaths = BaseFileFind::replaceAll(newSymbolName, checkedItems, @@ -390,7 +391,7 @@ static Usage::Tags getUsageType(const ClangdAstPath &path, const QString &search void ClangdFindReferences::Private::addSearchResultsForFile(const FilePath &file, const ReferencesFileData &fileData) { - QList items; + SearchResultItems items; qCDebug(clangdLog) << file << "has valid AST:" << fileData.ast.isValid(); const auto expectedDeclTypes = [this]() -> QStringList { if (checkUnusedData) @@ -451,7 +452,7 @@ void ClangdFindReferences::Private::addSearchResultsForFile(const FilePath &file item.setContainingFunctionName(getContainingFunction(astPath, range).detail()); if (search->supportsReplace()) { - const bool fileInSession = SessionManager::projectForFile(file); + const bool fileInSession = ProjectManager::projectForFile(file); item.setSelectForReplacement(fileInSession); if (fileInSession && file.baseName().compare(replacementData->oldSymbolName, Qt::CaseInsensitive) == 0) { @@ -594,6 +595,19 @@ static Usage::Tags getUsageType(const ClangdAstPath &path, const QString &search tags |= Usage::Tag::Operator; } } + if (pathIt->kind() == "CXXMethod") { + const ClangdAstNode &classNode = *std::next(pathIt); + if (classNode.hasChild([&](const ClangdAstNode &n) { + if (n.kind() != "StaticAssert") + return false; + return n.hasChild([&](const ClangdAstNode &n) { + return n.arcanaContains("Q_PROPERTY"); }, true) + && n.hasChild([&](const ClangdAstNode &n) { + return n.arcanaContains(" " + searchTerm); }, true); + }, false)) { + tags |= Usage::Tag::MocInvokable; + } + } return tags; } if (pathIt->kind() == "MemberInitializer") diff --git a/src/plugins/clangcodemodel/clangdfindreferences.h b/src/plugins/clangcodemodel/clangdfindreferences.h index d904db31f41..e110b355434 100644 --- a/src/plugins/clangcodemodel/clangdfindreferences.h +++ b/src/plugins/clangcodemodel/clangdfindreferences.h @@ -3,9 +3,9 @@ #pragma once -#include #include #include +#include #include @@ -35,7 +35,7 @@ public: ~ClangdFindReferences(); signals: - void foundReferences(const QList &items); + void foundReferences(const Utils::SearchResultItems &items); void done(); private: diff --git a/src/plugins/clangcodemodel/clangdlocatorfilters.cpp b/src/plugins/clangcodemodel/clangdlocatorfilters.cpp index 725d961591d..f783a98499a 100644 --- a/src/plugins/clangcodemodel/clangdlocatorfilters.cpp +++ b/src/plugins/clangcodemodel/clangdlocatorfilters.cpp @@ -9,290 +9,205 @@ #include #include #include -#include -#include -#include + +#include + +#include #include -#include -#include -#include -#include +#include +#include +#include + +using namespace Core; using namespace LanguageClient; using namespace LanguageServerProtocol; +using namespace ProjectExplorer; +using namespace TextEditor; using namespace Utils; -namespace ClangCodeModel { -namespace Internal { +namespace ClangCodeModel::Internal { const int MaxResultCount = 10000; -class CppLocatorFilter : public CppEditor::CppLocatorFilter -{ -public: - CppLocatorFilter() - : CppEditor::CppLocatorFilter(CppEditor::CppModelManager::instance()->locatorData()) - { - setId({}); - setDisplayName({}); - setDefaultShortcutString({}); - setEnabled(false); - setHidden(true); - } -}; - -class LspWorkspaceFilter : public WorkspaceLocatorFilter -{ -public: - LspWorkspaceFilter() - { - setId({}); - setDisplayName({}); - setDefaultShortcutString({}); - setEnabled(false); - setHidden(true); - setMaxResultCount(MaxResultCount); - } -}; - - -class CppClassesFilter : public CppEditor::CppClassesFilter -{ -public: - CppClassesFilter() - : CppEditor::CppClassesFilter(CppEditor::CppModelManager::instance()->locatorData()) - { - setId({}); - setDisplayName({}); - setDefaultShortcutString({}); - setEnabled(false); - setHidden(true); - } -}; - -class LspClassesFilter : public WorkspaceClassLocatorFilter -{ -public: - LspClassesFilter() { - setId({}); - setDisplayName({}); - setDefaultShortcutString({}); - setEnabled(false); - setHidden(true); - setMaxResultCount(MaxResultCount); - } -}; - -class CppFunctionsFilter : public CppEditor::CppFunctionsFilter -{ -public: - CppFunctionsFilter() - : CppEditor::CppFunctionsFilter(CppEditor::CppModelManager::instance()->locatorData()) - { - setId({}); - setDisplayName({}); - setDefaultShortcutString({}); - setEnabled(false); - setHidden(true); - } -}; - -class LspFunctionsFilter : public WorkspaceMethodLocatorFilter -{ -public: - LspFunctionsFilter() - { - setId({}); - setDisplayName({}); - setDefaultShortcutString({}); - setEnabled(false); - setHidden(true); - setMaxResultCount(MaxResultCount); - } -}; - - -ClangGlobalSymbolFilter::ClangGlobalSymbolFilter() - : ClangGlobalSymbolFilter(new CppLocatorFilter, new LspWorkspaceFilter) -{ -} - -ClangGlobalSymbolFilter::ClangGlobalSymbolFilter(ILocatorFilter *cppFilter, - WorkspaceLocatorFilter *lspFilter) - : m_cppFilter(cppFilter), m_lspFilter(lspFilter) +ClangdAllSymbolsFilter::ClangdAllSymbolsFilter() { setId(CppEditor::Constants::LOCATOR_FILTER_ID); setDisplayName(::CppEditor::Tr::tr(CppEditor::Constants::LOCATOR_FILTER_DISPLAY_NAME)); + setDescription(::CppEditor::Tr::tr(CppEditor::Constants::LOCATOR_FILTER_DESCRIPTION)); setDefaultShortcutString(":"); - setDefaultIncludedByDefault(false); } -ClangGlobalSymbolFilter::~ClangGlobalSymbolFilter() +LocatorMatcherTasks ClangdAllSymbolsFilter::matchers() { - delete m_cppFilter; - delete m_lspFilter; + return CppEditor::cppMatchers(MatcherType::AllSymbols) + + LanguageClient::languageClientMatchers(MatcherType::AllSymbols, + ClangModelManagerSupport::clientsForOpenProjects(), MaxResultCount); } -void ClangGlobalSymbolFilter::prepareSearch(const QString &entry) -{ - m_cppFilter->prepareSearch(entry); - QList clients; - for (ProjectExplorer::Project * const project : ProjectExplorer::SessionManager::projects()) { - if (Client * const client = ClangModelManagerSupport::clientForProject(project)) - clients << client; - } - if (!clients.isEmpty()) - m_lspFilter->prepareSearch(entry, clients); -} - -QList ClangGlobalSymbolFilter::matchesFor( - QFutureInterface &future, const QString &entry) -{ - QList matches = m_cppFilter->matchesFor(future, entry); - const QList lspMatches = m_lspFilter->matchesFor(future, entry); - if (!lspMatches.isEmpty()) { - std::set> locations; - for (const auto &entry : std::as_const(matches)) { - const CppEditor::IndexItem::Ptr item - = qvariant_cast(entry.internalData); - locations.insert(std::make_tuple(item->filePath(), item->line(), item->column())); - } - for (const auto &entry : lspMatches) { - if (!entry.internalData.canConvert()) - continue; - const auto link = qvariant_cast(entry.internalData); - if (locations.find(std::make_tuple(link.targetFilePath, link.targetLine, - link.targetColumn)) == locations.cend()) { - matches << entry; // TODO: Insert sorted? - } - } - } - - return matches; -} - -void ClangGlobalSymbolFilter::accept(const Core::LocatorFilterEntry &selection, QString *newText, - int *selectionStart, int *selectionLength) const -{ - if (qvariant_cast(selection.internalData)) - m_cppFilter->accept(selection, newText, selectionStart, selectionLength); - else - m_lspFilter->accept(selection, newText, selectionStart, selectionLength); -} - - -ClangClassesFilter::ClangClassesFilter() - : ClangGlobalSymbolFilter(new CppClassesFilter, new LspClassesFilter) +ClangdClassesFilter::ClangdClassesFilter() { setId(CppEditor::Constants::CLASSES_FILTER_ID); setDisplayName(::CppEditor::Tr::tr(CppEditor::Constants::CLASSES_FILTER_DISPLAY_NAME)); + setDescription(::CppEditor::Tr::tr(CppEditor::Constants::CLASSES_FILTER_DESCRIPTION)); setDefaultShortcutString("c"); - setDefaultIncludedByDefault(false); } -ClangFunctionsFilter::ClangFunctionsFilter() - : ClangGlobalSymbolFilter(new CppFunctionsFilter, new LspFunctionsFilter) +LocatorMatcherTasks ClangdClassesFilter::matchers() +{ + return CppEditor::cppMatchers(MatcherType::Classes) + + LanguageClient::languageClientMatchers(MatcherType::Classes, + ClangModelManagerSupport::clientsForOpenProjects(), MaxResultCount); +} + +ClangdFunctionsFilter::ClangdFunctionsFilter() { setId(CppEditor::Constants::FUNCTIONS_FILTER_ID); setDisplayName(::CppEditor::Tr::tr(CppEditor::Constants::FUNCTIONS_FILTER_DISPLAY_NAME)); + setDescription(::CppEditor::Tr::tr(CppEditor::Constants::FUNCTIONS_FILTER_DESCRIPTION)); setDefaultShortcutString("m"); - setDefaultIncludedByDefault(false); } -class LspCurrentDocumentFilter : public DocumentLocatorFilter +LocatorMatcherTasks ClangdFunctionsFilter::matchers() { -public: - LspCurrentDocumentFilter() - { - setId({}); - setDisplayName({}); - setDefaultShortcutString({}); - setEnabled(false); - setHidden(true); - forceUse(); - } + return CppEditor::cppMatchers(MatcherType::Functions) + + LanguageClient::languageClientMatchers(MatcherType::Functions, + ClangModelManagerSupport::clientsForOpenProjects(), MaxResultCount); +} -private: - Core::LocatorFilterEntry generateLocatorEntry(const DocumentSymbol &info, - const Core::LocatorFilterEntry &parent) override +ClangdCurrentDocumentFilter::ClangdCurrentDocumentFilter() +{ + setId(CppEditor::Constants::CURRENT_DOCUMENT_FILTER_ID); + setDisplayName(::CppEditor::Tr::tr(CppEditor::Constants::CURRENT_DOCUMENT_FILTER_DISPLAY_NAME)); + setDescription(::CppEditor::Tr::tr(CppEditor::Constants::CURRENT_DOCUMENT_FILTER_DESCRIPTION)); + setDefaultShortcutString("."); + setPriority(High); + setEnabled(false); + connect(EditorManager::instance(), &EditorManager::currentEditorChanged, + this, [this](const IEditor *editor) { setEnabled(editor); }); +} + +static void filterCurrentResults(QPromise &promise, const LocatorStorage &storage, + const CurrentDocumentSymbolsData ¤tSymbolsData, + const QString &contents) +{ + Q_UNUSED(promise) + struct Entry { - Core::LocatorFilterEntry entry; - entry.filter = this; + LocatorFilterEntry entry; + DocumentSymbol symbol; + }; + QList docEntries; + + const auto docSymbolModifier = [&docEntries](LocatorFilterEntry &entry, + const DocumentSymbol &info, + const LocatorFilterEntry &parent) { entry.displayName = ClangdClient::displayNameFromDocumentSymbol( - static_cast(info.kind()), info.name(), - info.detail().value_or(QString())); - const Position &pos = info.range().start(); - entry.internalData = QVariant::fromValue(LineColumn(pos.line(), pos.character())); + static_cast(info.kind()), info.name(), + info.detail().value_or(QString())); entry.extraInfo = parent.extraInfo; if (!entry.extraInfo.isEmpty()) entry.extraInfo.append("::"); entry.extraInfo.append(parent.displayName); // TODO: Can we extend clangd to send visibility information? - entry.displayIcon = LanguageClient::symbolIcon(info.kind()); - + docEntries.append({entry, info}); return entry; + }; + // TODO: Pass promise into currentSymbols + const LocatorFilterEntries allMatches = LanguageClient::currentDocumentSymbols(storage.input(), + currentSymbolsData, docSymbolModifier); + if (docEntries.isEmpty()) { + storage.reportOutput(allMatches); + return; // SymbolInformation case } -}; -class ClangdCurrentDocumentFilter::Private -{ -public: - ~Private() { delete cppFilter; } + QTC_CHECK(docEntries.size() == allMatches.size()); + QHash> possibleDuplicates; + for (const Entry &e : std::as_const(docEntries)) + possibleDuplicates[e.entry.displayName + e.entry.extraInfo] << e; + const QTextDocument doc(contents); + for (auto it = possibleDuplicates.cbegin(); it != possibleDuplicates.cend(); ++it) { + const QList &duplicates = it.value(); + if (duplicates.size() == 1) + continue; + QList declarations; + QList definitions; + for (const Entry &candidate : duplicates) { + const DocumentSymbol symbol = candidate.symbol; + const SymbolKind kind = static_cast(symbol.kind()); + if (kind != SymbolKind::Class && kind != SymbolKind::Function) + break; + const Range range = symbol.range(); + const Range selectionRange = symbol.selectionRange(); + if (kind == SymbolKind::Class) { + if (range.end() == selectionRange.end()) + declarations << candidate; + else + definitions << candidate; + continue; + } + const int startPos = selectionRange.end().toPositionInDocument(&doc); + const int endPos = range.end().toPositionInDocument(&doc); + const QString functionBody = contents.mid(startPos, endPos - startPos); - Core::ILocatorFilter * const cppFilter - = CppEditor::CppModelManager::createAuxiliaryCurrentDocumentFilter(); - LspCurrentDocumentFilter lspFilter; - Core::ILocatorFilter *activeFilter = nullptr; -}; - - -ClangdCurrentDocumentFilter::ClangdCurrentDocumentFilter() : d(new Private) -{ - setId(CppEditor::Constants::CURRENT_DOCUMENT_FILTER_ID); - setDisplayName(::CppEditor::Tr::tr(CppEditor::Constants::CURRENT_DOCUMENT_FILTER_DISPLAY_NAME)); - setDefaultShortcutString("."); - setPriority(High); - setDefaultIncludedByDefault(false); - setEnabled(false); - connect(Core::EditorManager::instance(), &Core::EditorManager::currentEditorChanged, - this, [this](const Core::IEditor *editor) { setEnabled(editor); }); -} - -ClangdCurrentDocumentFilter::~ClangdCurrentDocumentFilter() { delete d; } - -void ClangdCurrentDocumentFilter::updateCurrentClient() -{ - d->lspFilter.updateCurrentClient(); -} - -void ClangdCurrentDocumentFilter::prepareSearch(const QString &entry) -{ - const auto doc = TextEditor::TextDocument::currentTextDocument(); - QTC_ASSERT(doc, return); - if (const ClangdClient * const client = ClangModelManagerSupport::clientForFile - (doc->filePath()); client && client->reachable()) { - d->activeFilter = &d->lspFilter; - } else { - d->activeFilter = d->cppFilter; + // Hacky, but I don't see anything better. + if (functionBody.contains('{') && functionBody.contains('}')) + definitions << candidate; + else + declarations << candidate; + } + if (definitions.size() == 1 + && declarations.size() + definitions.size() == duplicates.size()) { + for (const Entry &decl : std::as_const(declarations)) { + Utils::erase(docEntries, [&decl](const Entry &e) { + return e.symbol == decl.symbol; + }); + } + } } - d->activeFilter->prepareSearch(entry); + storage.reportOutput(Utils::transform(docEntries, + [](const Entry &entry) { return entry.entry; })); } -QList ClangdCurrentDocumentFilter::matchesFor( - QFutureInterface &future, const QString &entry) +LocatorMatcherTask currentDocumentMatcher() { - QTC_ASSERT(d->activeFilter, return {}); - return d->activeFilter->matchesFor(future, entry); + using namespace Tasking; + + TreeStorage storage; + TreeStorage resultStorage; + + const auto onQuerySetup = [=](CurrentDocumentSymbolsRequest &request) { + Q_UNUSED(request) + }; + const auto onQueryDone = [resultStorage](const CurrentDocumentSymbolsRequest &request) { + *resultStorage = request.currentDocumentSymbolsData(); + }; + + const auto onFilterSetup = [=](Async &async) { + async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer()); + async.setConcurrentCallData(filterCurrentResults, *storage, *resultStorage, + TextDocument::currentTextDocument()->plainText()); + }; + + const Group root { + Storage(resultStorage), + CurrentDocumentSymbolsRequestTask(onQuerySetup, onQueryDone), + AsyncTask(onFilterSetup) + }; + return {root, storage}; } -void ClangdCurrentDocumentFilter::accept(const Core::LocatorFilterEntry &selection, QString *newText, - int *selectionStart, int *selectionLength) const +LocatorMatcherTasks ClangdCurrentDocumentFilter::matchers() { - QTC_ASSERT(d->activeFilter, return); - d->activeFilter->accept(selection, newText, selectionStart, selectionLength); + const auto doc = TextDocument::currentTextDocument(); + QTC_ASSERT(doc, return {}); + if (const ClangdClient *client = ClangModelManagerSupport::clientForFile(doc->filePath()); + client && client->reachable()) { + return {currentDocumentMatcher()}; + } + return CppEditor::cppMatchers(MatcherType::CurrentDocumentSymbols); } -} // namespace Internal -} // namespace ClangCodeModel +} // namespace ClangCodeModel::Internal diff --git a/src/plugins/clangcodemodel/clangdlocatorfilters.h b/src/plugins/clangcodemodel/clangdlocatorfilters.h index f7deacc7605..1aba669417e 100644 --- a/src/plugins/clangcodemodel/clangdlocatorfilters.h +++ b/src/plugins/clangcodemodel/clangdlocatorfilters.h @@ -5,60 +5,42 @@ #include -namespace LanguageClient { class WorkspaceLocatorFilter; } +namespace ClangCodeModel::Internal { -namespace ClangCodeModel { -namespace Internal { - -class ClangGlobalSymbolFilter : public Core::ILocatorFilter +class ClangdAllSymbolsFilter : public Core::ILocatorFilter { public: - ClangGlobalSymbolFilter(); - ClangGlobalSymbolFilter(Core::ILocatorFilter *cppFilter, - LanguageClient::WorkspaceLocatorFilter *lspFilter); - ~ClangGlobalSymbolFilter() override; + ClangdAllSymbolsFilter(); private: - void prepareSearch(const QString &entry) override; - QList matchesFor(QFutureInterface &future, - const QString &entry) override; - void accept(const Core::LocatorFilterEntry &selection, QString *newText, - int *selectionStart, int *selectionLength) const override; - - Core::ILocatorFilter * const m_cppFilter; - LanguageClient::WorkspaceLocatorFilter * const m_lspFilter; + Core::LocatorMatcherTasks matchers() final; }; -class ClangClassesFilter : public ClangGlobalSymbolFilter +class ClangdClassesFilter : public Core::ILocatorFilter { public: - ClangClassesFilter(); + ClangdClassesFilter(); + +private: + Core::LocatorMatcherTasks matchers() final; }; -class ClangFunctionsFilter : public ClangGlobalSymbolFilter +class ClangdFunctionsFilter : public Core::ILocatorFilter { public: - ClangFunctionsFilter(); + ClangdFunctionsFilter(); + +private: + Core::LocatorMatcherTasks matchers() final; }; class ClangdCurrentDocumentFilter : public Core::ILocatorFilter { public: ClangdCurrentDocumentFilter(); - ~ClangdCurrentDocumentFilter() override; - - void updateCurrentClient(); private: - void prepareSearch(const QString &entry) override; - QList matchesFor(QFutureInterface &future, - const QString &entry) override; - void accept(const Core::LocatorFilterEntry &selection, QString *newText, - int *selectionStart, int *selectionLength) const override; - - class Private; - Private * const d; + Core::LocatorMatcherTasks matchers() final; }; -} // namespace Internal -} // namespace ClangCodeModel +} // namespace ClangCodeModel::Internal diff --git a/src/plugins/clangcodemodel/clangdqpropertyhighlighter.cpp b/src/plugins/clangcodemodel/clangdqpropertyhighlighter.cpp index e6e32b0e9ee..4a8db524ce8 100644 --- a/src/plugins/clangcodemodel/clangdqpropertyhighlighter.cpp +++ b/src/plugins/clangcodemodel/clangdqpropertyhighlighter.cpp @@ -394,10 +394,10 @@ void QPropertyHighlighter::Private::addResult(TextStyle textStyle, int symbolOff const Symbol &s = parser.symbol_lookup(symbolOffset); int line, column; Utils::Text::convertPosition(document, position + s.from, &line, &column); - if (line > 0 && column > 0) { + if (line > 0 && column >= 0) { TextStyles styles; styles.mainStyle = textStyle; - results << HighlightingResult(line, column, s.len, styles); + results << HighlightingResult(line, column + 1, s.len, styles); } } diff --git a/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp b/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp index cff8253de62..e1b82ab3770 100644 --- a/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp +++ b/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp @@ -112,7 +112,7 @@ static QList cleanupDisabledCode(HighlightingResults &results, const class ExtraHighlightingResultsCollector { public: - ExtraHighlightingResultsCollector(QFutureInterface &future, + ExtraHighlightingResultsCollector(QPromise &promise, HighlightingResults &results, const Utils::FilePath &filePath, const ClangdAstNode &ast, const QTextDocument *doc, const QString &docContent, @@ -131,7 +131,7 @@ private: void collectFromNode(const ClangdAstNode &node); void visitNode(const ClangdAstNode&node); - QFutureInterface &m_future; + QPromise &m_promise; HighlightingResults &m_results; const Utils::FilePath m_filePath; const ClangdAstNode &m_ast; @@ -142,7 +142,7 @@ private: }; void doSemanticHighlighting( - QFutureInterface &future, + QPromise &promise, const Utils::FilePath &filePath, const QList &tokens, const QString &docContents, @@ -153,10 +153,8 @@ void doSemanticHighlighting( const TaskTimer &taskTimer) { ThreadedSubtaskTimer t("highlighting", taskTimer); - if (future.isCanceled()) { - future.reportFinished(); + if (promise.isCanceled()) return; - } const QTextDocument doc(docContents); const auto tokenRange = [&doc](const ExpandedSemanticToken &token) { @@ -399,13 +397,13 @@ void doSemanticHighlighting( }; auto results = QtConcurrent::blockingMapped(tokens, safeToResult); const QList ifdefedOutBlocks = cleanupDisabledCode(results, &doc, docContents); - ExtraHighlightingResultsCollector(future, results, filePath, ast, &doc, docContents, + ExtraHighlightingResultsCollector(promise, results, filePath, ast, &doc, docContents, clangdVersion).collect(); Utils::erase(results, [](const HighlightingResult &res) { // QTCREATORBUG-28639 return res.textStyles.mainStyle == C_TEXT && res.textStyles.mixinStyles.empty(); }); - if (!future.isCanceled()) { + if (!promise.isCanceled()) { qCInfo(clangdLogHighlight) << "reporting" << results.size() << "highlighting results"; QMetaObject::invokeMethod(textDocument, [textDocument, ifdefedOutBlocks, docRevision] { if (textDocument && textDocument->document()->revision() == docRevision) @@ -423,16 +421,20 @@ void doSemanticHighlighting( if (ClangdClient * const client = ClangModelManagerSupport::clientForFile(filePath)) client->setVirtualRanges(filePath, virtualRanges, docRevision); }, Qt::QueuedConnection); - future.reportResults(QVector(results.cbegin(), results.cend())); +#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) + promise.addResults(results); +#else + for (const HighlightingResult &r : results) + promise.addResult(r); +#endif } - future.reportFinished(); } ExtraHighlightingResultsCollector::ExtraHighlightingResultsCollector( - QFutureInterface &future, HighlightingResults &results, + QPromise &promise, HighlightingResults &results, const Utils::FilePath &filePath, const ClangdAstNode &ast, const QTextDocument *doc, const QString &docContent, const QVersionNumber &clangdVersion) - : m_future(future), m_results(results), m_filePath(filePath), m_ast(ast), m_doc(doc), + : m_promise(promise), m_results(results), m_filePath(filePath), m_ast(ast), m_doc(doc), m_docContent(docContent), m_clangdVersion(clangdVersion.majorVersion()) { } @@ -573,10 +575,12 @@ void ExtraHighlightingResultsCollector::insertAngleBracketInfo(int searchStart1, result.useTextSyles = true; result.textStyles.mainStyle = C_PUNCTUATION; Utils::Text::convertPosition(m_doc, absOpeningAngleBracketPos, &result.line, &result.column); + ++result.column; result.length = 1; result.kind = CppEditor::SemanticHighlighter::AngleBracketOpen; insertResult(result); Utils::Text::convertPosition(m_doc, absClosingAngleBracketPos, &result.line, &result.column); + ++result.column; result.kind = CppEditor::SemanticHighlighter::AngleBracketClose; insertResult(result); } @@ -648,10 +652,12 @@ void ExtraHighlightingResultsCollector::collectFromNode(const ClangdAstNode &nod result.textStyles.mainStyle = C_PUNCTUATION; result.textStyles.mixinStyles.push_back(C_OPERATOR); Utils::Text::convertPosition(m_doc, absQuestionMarkPos, &result.line, &result.column); + ++result.column; result.length = 1; result.kind = CppEditor::SemanticHighlighter::TernaryIf; insertResult(result); Utils::Text::convertPosition(m_doc, absColonPos, &result.line, &result.column); + ++result.column; result.kind = CppEditor::SemanticHighlighter::TernaryElse; insertResult(result); return; @@ -839,10 +845,12 @@ void ExtraHighlightingResultsCollector::collectFromNode(const ClangdAstNode &nod Utils::Text::convertPosition(m_doc, nodeStartPos + openingBracketOffset, &result.line, &result.column); + ++result.column; insertResult(result); Utils::Text::convertPosition(m_doc, nodeStartPos + closingBracketOffset, &result.line, &result.column); + ++result.column; insertResult(result); } return; @@ -887,6 +895,7 @@ void ExtraHighlightingResultsCollector::collectFromNode(const ClangdAstNode &nod const int opStringOffsetInDoc = nodeStartPos + opStringOffset + detail.length() - opStringLen; Utils::Text::convertPosition(m_doc, opStringOffsetInDoc, &result.line, &result.column); + ++result.column; result.length = opStringLen; if (isArray || isCall) result.length = 1; @@ -908,15 +917,17 @@ void ExtraHighlightingResultsCollector::collectFromNode(const ClangdAstNode &nod return; Utils::Text::convertPosition(m_doc, nodeStartPos + openingParenOffset, &result.line, &result.column); + ++result.column; insertResult(result); Utils::Text::convertPosition(m_doc, nodeStartPos + closingParenOffset, &result.line, &result.column); + ++result.column; insertResult(result); } void ExtraHighlightingResultsCollector::visitNode(const ClangdAstNode &node) { - if (m_future.isCanceled()) + if (m_promise.isCanceled()) return; const ClangdAstNode::FileStatus prevFileStatus = m_currentFileStatus; m_currentFileStatus = node.fileStatus(m_filePath); diff --git a/src/plugins/clangcodemodel/clangdsemantichighlighting.h b/src/plugins/clangcodemodel/clangdsemantichighlighting.h index 10bc4b8d091..a7f667d459f 100644 --- a/src/plugins/clangcodemodel/clangdsemantichighlighting.h +++ b/src/plugins/clangcodemodel/clangdsemantichighlighting.h @@ -3,11 +3,15 @@ #pragma once -#include #include #include #include +QT_BEGIN_NAMESPACE +template +class QPromise; +QT_END_NAMESPACE + namespace LanguageClient { class ExpandedSemanticToken; } namespace TextEditor { class HighlightingResult; @@ -21,7 +25,7 @@ class TaskTimer; Q_DECLARE_LOGGING_CATEGORY(clangdLogHighlight); void doSemanticHighlighting( - QFutureInterface &future, + QPromise &promise, const Utils::FilePath &filePath, const QList &tokens, const QString &docContents, diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp index 248e97349fa..42e77c11a34 100644 --- a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp +++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -27,22 +28,23 @@ #include #include +#include #include #include #include #include +#include #include #include -#include #include #include #include +#include #include #include -#include #include #include @@ -51,22 +53,24 @@ #include #include +using namespace Core; using namespace CppEditor; using namespace LanguageClient; +using namespace ProjectExplorer; using namespace Utils; namespace ClangCodeModel::Internal { -static CppEditor::CppModelManager *cppModelManager() +static CppModelManager *cppModelManager() { - return CppEditor::CppModelManager::instance(); + return CppModelManager::instance(); } -static ProjectExplorer::Project *fallbackProject() +static Project *fallbackProject() { - if (ProjectExplorer::Project * const p = ProjectExplorer::ProjectTree::currentProject()) + if (Project * const p = ProjectTree::currentProject()) return p; - return ProjectExplorer::SessionManager::startupProject(); + return ProjectManager::startupProject(); } static bool sessionModeEnabled() @@ -76,18 +80,17 @@ static bool sessionModeEnabled() static const QList allCppDocuments() { - const auto isCppDocument = Utils::equal(&Core::IDocument::id, - Utils::Id(CppEditor::Constants::CPPEDITOR_ID)); - const QList documents - = Utils::filtered(Core::DocumentModel::openedDocuments(), isCppDocument); + const auto isCppDocument = Utils::equal(&IDocument::id, Id(CppEditor::Constants::CPPEDITOR_ID)); + const QList documents = Utils::filtered(DocumentModel::openedDocuments(), + isCppDocument); return Utils::qobject_container_cast(documents); } -static const QList projectsForClient(const Client *client) +static const QList projectsForClient(const Client *client) { - QList projects; + QList projects; if (sessionModeEnabled()) { - for (ProjectExplorer::Project * const p : ProjectExplorer::SessionManager::projects()) { + for (Project * const p : ProjectManager::projects()) { if (ClangdProjectSettings(p).settings().useClangd) projects << p; } @@ -99,7 +102,7 @@ static const QList projectsForClient(const Client *c static bool fileIsProjectBuildArtifact(const Client *client, const FilePath &filePath) { - for (const ProjectExplorer::Project * const p : projectsForClient(client)) { + for (const Project * const p : projectsForClient(client)) { if (const auto t = p->activeTarget()) { if (const auto bc = t->activeBuildConfiguration()) { if (filePath.isChildOf(bc->buildDirectory())) @@ -144,15 +147,15 @@ static void checkSystemForClangdSuitability() "You can enable/disable and fine-tune clangd
here.")); label->setWordWrap(true); QObject::connect(label, &QLabel::linkActivated, [] { - Core::ICore::showOptionsDialog(CppEditor::Constants::CPP_CLANGD_SETTINGS_ID); + ICore::showOptionsDialog(CppEditor::Constants::CPP_CLANGD_SETTINGS_ID); }); return label; }); info.addCustomButton(Tr::tr("Enable Anyway"), [clangdWarningSetting] { ClangdSettings::setUseClangdAndSave(true); - Core::ICore::infoBar()->removeInfo(clangdWarningSetting); + ICore::infoBar()->removeInfo(clangdWarningSetting); }); - Core::ICore::infoBar()->addInfo(info); + ICore::infoBar()->addInfo(info); } static void updateParserConfig(ClangdClient *client) @@ -170,8 +173,8 @@ static void updateParserConfig(ClangdClient *client) static bool projectIsParsing(const ClangdClient *client) { - for (const ProjectExplorer::Project * const p : projectsForClient(client)) { - const ProjectExplorer::BuildSystem * const bs = p && p->activeTarget() + for (const Project * const p : projectsForClient(client)) { + const BuildSystem * const bs = p && p->activeTarget() ? p->activeTarget()->buildSystem() : nullptr; if (bs && (bs->isParsing() || bs->isWaitingForParse())) return true; @@ -179,7 +182,6 @@ static bool projectIsParsing(const ClangdClient *client) return false; } - ClangModelManagerSupport::ClangModelManagerSupport() : m_clientRestartTimer(new QTimer(this)) { @@ -200,24 +202,37 @@ ClangModelManagerSupport::ClangModelManagerSupport() setupClangdConfigFile(); checkSystemForClangdSuitability(); cppModelManager()->setCurrentDocumentFilter(std::make_unique()); - cppModelManager()->setLocatorFilter(std::make_unique()); - cppModelManager()->setClassesFilter(std::make_unique()); - cppModelManager()->setFunctionsFilter(std::make_unique()); + cppModelManager()->setLocatorFilter(std::make_unique()); + cppModelManager()->setClassesFilter(std::make_unique()); + cppModelManager()->setFunctionsFilter(std::make_unique()); + // Setup matchers + LocatorMatcher::addMatcherCreator(MatcherType::AllSymbols, [] { + return LanguageClient::languageClientMatchers( + MatcherType::AllSymbols, clientsForOpenProjects(), 10000); + }); + LocatorMatcher::addMatcherCreator(MatcherType::Classes, [] { + return LanguageClient::languageClientMatchers( + MatcherType::Classes, clientsForOpenProjects(), 10000); + }); + LocatorMatcher::addMatcherCreator(MatcherType::Functions, [] { + return LanguageClient::languageClientMatchers( + MatcherType::Functions, clientsForOpenProjects(), 10000); + }); - Core::EditorManager *editorManager = Core::EditorManager::instance(); - connect(editorManager, &Core::EditorManager::editorOpened, + EditorManager *editorManager = EditorManager::instance(); + connect(editorManager, &EditorManager::editorOpened, this, &ClangModelManagerSupport::onEditorOpened); - connect(editorManager, &Core::EditorManager::currentEditorChanged, + connect(editorManager, &EditorManager::currentEditorChanged, this, &ClangModelManagerSupport::onCurrentEditorChanged); - CppEditor::CppModelManager *modelManager = cppModelManager(); - connect(modelManager, &CppEditor::CppModelManager::abstractEditorSupportContentsUpdated, + CppModelManager *modelManager = cppModelManager(); + connect(modelManager, &CppModelManager::abstractEditorSupportContentsUpdated, this, &ClangModelManagerSupport::onAbstractEditorSupportContentsUpdated); - connect(modelManager, &CppEditor::CppModelManager::abstractEditorSupportRemoved, + connect(modelManager, &CppModelManager::abstractEditorSupportRemoved, this, &ClangModelManagerSupport::onAbstractEditorSupportRemoved); - connect(modelManager, &CppEditor::CppModelManager::projectPartsUpdated, + connect(modelManager, &CppModelManager::projectPartsUpdated, this, &ClangModelManagerSupport::onProjectPartsUpdated); - connect(modelManager, &CppEditor::CppModelManager::projectPartsRemoved, + connect(modelManager, &CppModelManager::projectPartsRemoved, this, &ClangModelManagerSupport::onProjectPartsRemoved); connect(modelManager, &CppModelManager::fallbackProjectPartUpdated, this, [this] { if (sessionModeEnabled()) @@ -228,26 +243,23 @@ ClangModelManagerSupport::ClangModelManagerSupport() } }); - auto *sessionManager = ProjectExplorer::SessionManager::instance(); - connect(sessionManager, &ProjectExplorer::SessionManager::projectRemoved, - this, [this] { + auto projectManager = ProjectManager::instance(); + connect(projectManager, &ProjectManager::projectRemoved, this, [this] { if (!sessionModeEnabled()) claimNonProjectSources(clientForProject(fallbackProject())); }); - connect(sessionManager, &ProjectExplorer::SessionManager::sessionLoaded, - this, [this] { + connect(SessionManager::instance(), &SessionManager::sessionLoaded, this, [this] { if (sessionModeEnabled()) onClangdSettingsChanged(); }); - CppEditor::ClangdSettings::setDefaultClangdPath(Core::ICore::clangdExecutable(CLANG_BINDIR)); - connect(&CppEditor::ClangdSettings::instance(), &CppEditor::ClangdSettings::changed, + ClangdSettings::setDefaultClangdPath(ICore::clangdExecutable(CLANG_BINDIR)); + connect(&ClangdSettings::instance(), &ClangdSettings::changed, this, &ClangModelManagerSupport::onClangdSettingsChanged); - if (CppEditor::ClangdSettings::instance().useClangd()) + if (ClangdSettings::instance().useClangd()) new ClangdClient(nullptr, {}); - m_generatorSynchronizer.setCancelOnWait(true); new ClangdQuickFixFactory(); // memory managed by CppEditor::g_cppQuickFixFactories } @@ -256,9 +268,9 @@ ClangModelManagerSupport::~ClangModelManagerSupport() m_generatorSynchronizer.waitForFinished(); } -void ClangModelManagerSupport::followSymbol(const CppEditor::CursorInEditor &data, - const LinkHandler &processLinkCallback, bool resolveTarget, - bool inNextSplit) +void ClangModelManagerSupport::followSymbol(const CursorInEditor &data, + const LinkHandler &processLinkCallback, + bool resolveTarget, bool inNextSplit) { if (ClangdClient * const client = clientForFile(data.filePath()); client && client->isFullyIndexed()) { @@ -271,7 +283,7 @@ void ClangModelManagerSupport::followSymbol(const CppEditor::CursorInEditor &dat CppModelManager::Backend::Builtin); } -void ClangModelManagerSupport::followSymbolToType(const CppEditor::CursorInEditor &data, +void ClangModelManagerSupport::followSymbolToType(const CursorInEditor &data, const LinkHandler &processLinkCallback, bool inNextSplit) { @@ -284,8 +296,8 @@ void ClangModelManagerSupport::followSymbolToType(const CppEditor::CursorInEdito CppModelManager::Backend::Builtin); } -void ClangModelManagerSupport::switchDeclDef(const CppEditor::CursorInEditor &data, - const LinkHandler &processLinkCallback) +void ClangModelManagerSupport::switchDeclDef(const CursorInEditor &data, + const LinkHandler &processLinkCallback) { if (ClangdClient * const client = clientForFile(data.filePath()); client && client->isFullyIndexed()) { @@ -297,9 +309,9 @@ void ClangModelManagerSupport::switchDeclDef(const CppEditor::CursorInEditor &da CppModelManager::switchDeclDef(data, processLinkCallback, CppModelManager::Backend::Builtin); } -void ClangModelManagerSupport::startLocalRenaming(const CppEditor::CursorInEditor &data, - const CppEditor::ProjectPart *projectPart, - RenameCallback &&renameSymbolsCallback) +void ClangModelManagerSupport::startLocalRenaming(const CursorInEditor &data, + const ProjectPart *projectPart, + RenameCallback &&renameSymbolsCallback) { if (ClangdClient * const client = clientForFile(data.filePath()); client && client->reachable()) { @@ -312,7 +324,7 @@ void ClangModelManagerSupport::startLocalRenaming(const CppEditor::CursorInEdito std::move(renameSymbolsCallback), CppModelManager::Backend::Builtin); } -void ClangModelManagerSupport::globalRename(const CppEditor::CursorInEditor &cursor, +void ClangModelManagerSupport::globalRename(const CursorInEditor &cursor, const QString &replacement, const std::function &callback) { @@ -326,7 +338,7 @@ void ClangModelManagerSupport::globalRename(const CppEditor::CursorInEditor &cur CppModelManager::globalRename(cursor, replacement, callback, CppModelManager::Backend::Builtin); } -void ClangModelManagerSupport::findUsages(const CppEditor::CursorInEditor &cursor) const +void ClangModelManagerSupport::findUsages(const CursorInEditor &cursor) const { if (ClangdClient * const client = clientForFile(cursor.filePath()); client && client->isFullyIndexed()) { @@ -340,17 +352,30 @@ void ClangModelManagerSupport::findUsages(const CppEditor::CursorInEditor &curso void ClangModelManagerSupport::switchHeaderSource(const FilePath &filePath, bool inNextSplit) { - if (ClangdClient * const client = clientForFile(filePath)) - client->switchHeaderSource(filePath, inNextSplit); - else - CppModelManager::switchHeaderSource(inNextSplit, CppModelManager::Backend::Builtin); + if (ClangdClient * const client = clientForFile(filePath)) { + switch (ClangdProjectSettings(client->project()).settings().headerSourceSwitchMode) { + case ClangdSettings::HeaderSourceSwitchMode::BuiltinOnly: + CppModelManager::switchHeaderSource(inNextSplit, CppModelManager::Backend::Builtin); + return; + case ClangdSettings::HeaderSourceSwitchMode::ClangdOnly: + client->switchHeaderSource(filePath, inNextSplit); + return; + case ClangdSettings::HeaderSourceSwitchMode::Both: + const FilePath otherFile = correspondingHeaderOrSource(filePath); + if (!otherFile.isEmpty()) + openEditor(otherFile, inNextSplit); + else + client->switchHeaderSource(filePath, inNextSplit); + return; + } + } + CppModelManager::switchHeaderSource(inNextSplit, CppModelManager::Backend::Builtin); } -void ClangModelManagerSupport::checkUnused(const Link &link, Core::SearchResult *search, +void ClangModelManagerSupport::checkUnused(const Link &link, SearchResult *search, const LinkHandler &callback) { - if (const ProjectExplorer::Project * const project - = ProjectExplorer::SessionManager::projectForFile(link.targetFilePath)) { + if (const Project * const project = ProjectManager::projectForFile(link.targetFilePath)) { if (ClangdClient * const client = clientWithProject(project); client && client->isFullyIndexed()) { client->checkUnused(link, search, callback); @@ -367,7 +392,7 @@ bool ClangModelManagerSupport::usesClangd(const TextEditor::TextDocument *docume return clientForFile(document->filePath()); } -CppEditor::BaseEditorDocumentProcessor *ClangModelManagerSupport::createEditorDocumentProcessor( +BaseEditorDocumentProcessor *ClangModelManagerSupport::createEditorDocumentProcessor( TextEditor::TextDocument *baseTextDocument) { const auto processor = new ClangEditorDocumentProcessor(baseTextDocument); @@ -381,10 +406,10 @@ CppEditor::BaseEditorDocumentProcessor *ClangModelManagerSupport::createEditorDo return processor; } -void ClangModelManagerSupport::onCurrentEditorChanged(Core::IEditor *editor) +void ClangModelManagerSupport::onCurrentEditorChanged(IEditor *editor) { // Update task hub issues for current CppEditorDocument - ProjectExplorer::TaskHub::clearTasks(Constants::TASK_CATEGORY_DIAGNOSTICS); + TaskHub::clearTasks(Constants::TASK_CATEGORY_DIAGNOSTICS); if (!editor || !editor->document() || !cppModelManager()->isCppEditor(editor)) return; @@ -407,28 +432,25 @@ void ClangModelManagerSupport::connectToWidgetsMarkContextMenuRequested(QWidget } } -static FilePath getJsonDbDir(const ProjectExplorer::Project *project) +static FilePath getJsonDbDir(const Project *project) { static const QString dirName(".qtc_clangd"); if (!project) { const QString sessionDirName = FileUtils::fileSystemFriendlyName( - ProjectExplorer::SessionManager::activeSession()); - return Core::ICore::userResourcePath() / dirName / sessionDirName; // TODO: Make configurable? + SessionManager::activeSession()); + return ICore::userResourcePath() / dirName / sessionDirName; // TODO: Make configurable? } - if (const ProjectExplorer::Target * const target = project->activeTarget()) { - if (const ProjectExplorer::BuildConfiguration * const bc - = target->activeBuildConfiguration()) { + if (const Target * const target = project->activeTarget()) { + if (const BuildConfiguration * const bc = target->activeBuildConfiguration()) return bc->buildDirectory() / dirName; - } } return {}; } -static bool isProjectDataUpToDate( - ProjectExplorer::Project *project, ProjectInfoList projectInfo, - const FilePath &jsonDbDir) +static bool isProjectDataUpToDate(Project *project, ProjectInfoList projectInfo, + const FilePath &jsonDbDir) { - if (project && !ProjectExplorer::SessionManager::hasProject(project)) + if (project && !ProjectManager::hasProject(project)) return false; const ClangdSettings settings(ClangdProjectSettings(project).settings()); if (!settings.useClangd()) @@ -457,7 +479,7 @@ static bool isProjectDataUpToDate( return true; } -void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *project) +void ClangModelManagerSupport::updateLanguageClient(Project *project) { const ClangdSettings settings(ClangdProjectSettings(project).settings()); if (!settings.useClangd()) @@ -483,12 +505,12 @@ void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *pr return; const GenerateCompilationDbResult result = generatorWatcher->result(); if (!result.error.isEmpty()) { - Core::MessageManager::writeDisrupting( + MessageManager::writeDisrupting( Tr::tr("Cannot use clangd: Failed to generate compilation database:\n%1") .arg(result.error)); return; } - Utils::Id previousId; + Id previousId; if (Client * const oldClient = clientForProject(project)) { previousId = oldClient->id(); LanguageClientManager::shutdownClient(oldClient); @@ -524,7 +546,7 @@ void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *pr hasDocuments = true; continue; } - const Project * const docProject = SessionManager::projectForFile(doc->filePath()); + const Project * const docProject = ProjectManager::projectForFile(doc->filePath()); if (currentClient && currentClient->project() && currentClient->project() != project && currentClient->project() == docProject) { @@ -564,8 +586,8 @@ void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *pr ProjectNode *rootNode = nullptr; if (project) rootNode = project->rootProjectNode(); - else if (SessionManager::startupProject()) - rootNode = SessionManager::startupProject()->rootProjectNode(); + else if (ProjectManager::startupProject()) + rootNode = ProjectManager::startupProject()->rootProjectNode(); if (!rootNode) return; const Node * const cxxNode = rootNode->findNode([](Node *n) { @@ -583,7 +605,7 @@ void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *pr }); const FilePath includeDir = settings.clangdIncludePath(); - auto future = Utils::runAsync(&Internal::generateCompilationDB, projectInfo, + auto future = Utils::asyncRun(&Internal::generateCompilationDB, projectInfo, jsonDbDir, CompilationDbPurpose::CodeModel, warningsConfigForProject(project), globalClangOptions(), includeDir); @@ -591,18 +613,28 @@ void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *pr m_generatorSynchronizer.addFuture(future); } -ClangdClient *ClangModelManagerSupport::clientForProject(const ProjectExplorer::Project *project) +QList ClangModelManagerSupport::clientsForOpenProjects() +{ + QSet clients; + const QList projects = ProjectManager::projects(); + for (Project *project : projects) { + if (Client *client = ClangModelManagerSupport::clientForProject(project)) + clients << client; + } + return clients.values(); +} + +ClangdClient *ClangModelManagerSupport::clientForProject(const Project *project) { if (sessionModeEnabled()) project = nullptr; return clientWithProject(project); } -ClangdClient *ClangModelManagerSupport::clientWithProject(const ProjectExplorer::Project *project) +ClangdClient *ClangModelManagerSupport::clientWithProject(const Project *project) { const QList clients = Utils::filtered( - LanguageClientManager::clientsForProject(project), - [](const LanguageClient::Client *c) { + LanguageClientManager::clientsForProject(project), [](const Client *c) { return qobject_cast(c) && c->state() != Client::ShutdownRequested && c->state() != Client::Shutdown; @@ -640,7 +672,7 @@ void ClangModelManagerSupport::claimNonProjectSources(ClangdClient *client) } if (!ClangdSettings::instance().sizeIsOkay(doc->filePath())) continue; - if (ProjectExplorer::SessionManager::projectForFile(doc->filePath())) + if (ProjectManager::projectForFile(doc->filePath())) continue; if (client->project() && !ProjectFile::isHeader(doc->filePath())) continue; @@ -656,7 +688,7 @@ void ClangModelManagerSupport::claimNonProjectSources(ClangdClient *client) // workflow, e.g. a git branch switch will hit at least one open file. void ClangModelManagerSupport::watchForExternalChanges() { - connect(Core::DocumentManager::instance(), &Core::DocumentManager::filesChangedExternally, + connect(DocumentManager::instance(), &DocumentManager::filesChangedExternally, this, [this](const QSet &files) { if (!LanguageClientManager::hasClients()) return; @@ -664,8 +696,7 @@ void ClangModelManagerSupport::watchForExternalChanges() const ProjectFile::Kind kind = ProjectFile::classify(file.toString()); if (!ProjectFile::isSource(kind) && !ProjectFile::isHeader(kind)) continue; - ProjectExplorer::Project * const project - = ProjectExplorer::SessionManager::projectForFile(file); + Project * const project = ProjectManager::projectForFile(file); if (!project) continue; @@ -684,14 +715,13 @@ void ClangModelManagerSupport::watchForExternalChanges() // restart clangd for reliable re-parsing and re-indexing. void ClangModelManagerSupport::watchForInternalChanges() { - connect(Core::DocumentManager::instance(), &Core::DocumentManager::filesChangedInternally, + connect(DocumentManager::instance(), &DocumentManager::filesChangedInternally, this, [this](const FilePaths &filePaths) { for (const FilePath &fp : filePaths) { const ProjectFile::Kind kind = ProjectFile::classify(fp.toString()); if (!ProjectFile::isSource(kind) && !ProjectFile::isHeader(kind)) continue; - ProjectExplorer::Project * const project - = ProjectExplorer::SessionManager::projectForFile(fp); + Project * const project = ProjectManager::projectForFile(fp); if (!project) continue; if (ClangdClient * const client = clientForProject(project); @@ -717,18 +747,17 @@ void ClangModelManagerSupport::scheduleClientRestart(ClangdClient *client) m_clientRestartTimer->start(); } -void ClangModelManagerSupport::onEditorOpened(Core::IEditor *editor) +void ClangModelManagerSupport::onEditorOpened(IEditor *editor) { QTC_ASSERT(editor, return); - Core::IDocument *document = editor->document(); + IDocument *document = editor->document(); QTC_ASSERT(document, return); auto textDocument = qobject_cast(document); if (textDocument && cppModelManager()->isCppEditor(editor)) { connectToWidgetsMarkContextMenuRequested(editor->widget()); - ProjectExplorer::Project * project - = ProjectExplorer::SessionManager::projectForFile(document->filePath()); + Project * project = ProjectManager::projectForFile(document->filePath()); const ClangdSettings settings(ClangdProjectSettings(project).settings()); if (!settings.sizeIsOkay(textDocument->filePath())) return; @@ -823,17 +852,17 @@ static ClangEditorDocumentProcessors clangProcessors() return result; } -void ClangModelManagerSupport::onProjectPartsUpdated(ProjectExplorer::Project *project) +void ClangModelManagerSupport::onProjectPartsUpdated(Project *project) { QTC_ASSERT(project, return); updateLanguageClient(project); QStringList projectPartIds; - const CppEditor::ProjectInfo::ConstPtr projectInfo = cppModelManager()->projectInfo(project); + const ProjectInfo::ConstPtr projectInfo = cppModelManager()->projectInfo(project); QTC_ASSERT(projectInfo, return); - for (const CppEditor::ProjectPart::ConstPtr &projectPart : projectInfo->projectParts()) + for (const ProjectPart::ConstPtr &projectPart : projectInfo->projectParts()) projectPartIds.append(projectPart->id()); onProjectPartsRemoved(projectPartIds); } @@ -848,9 +877,8 @@ void ClangModelManagerSupport::onClangdSettingsChanged() { const bool sessionMode = sessionModeEnabled(); - for (ProjectExplorer::Project * const project : ProjectExplorer::SessionManager::projects()) { - const CppEditor::ClangdSettings settings( - CppEditor::ClangdProjectSettings(project).settings()); + for (Project * const project : ProjectManager::projects()) { + const ClangdSettings settings(ClangdProjectSettings(project).settings()); ClangdClient * const client = clientWithProject(project); if (sessionMode) { if (client && client->project()) diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.h b/src/plugins/clangcodemodel/clangmodelmanagersupport.h index a901b4407e2..140239550d8 100644 --- a/src/plugins/clangcodemodel/clangmodelmanagersupport.h +++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.h @@ -24,6 +24,7 @@ QT_END_NAMESPACE namespace Core { class IEditor; } namespace CppEditor { class RefactoringEngineInterface; } +namespace LanguageClient { class Client; } namespace TextEditor { class TextEditorWidget; } namespace ClangCodeModel { @@ -45,6 +46,7 @@ public: TextEditor::TextDocument *baseTextDocument) override; bool usesClangd(const TextEditor::TextDocument *document) const override; + static QList clientsForOpenProjects(); static ClangdClient *clientForProject(const ProjectExplorer::Project *project); static ClangdClient *clientForFile(const Utils::FilePath &file); diff --git a/src/plugins/clangcodemodel/clangtextmark.cpp b/src/plugins/clangcodemodel/clangtextmark.cpp index a38aeb62873..570e6a2e956 100644 --- a/src/plugins/clangcodemodel/clangtextmark.cpp +++ b/src/plugins/clangcodemodel/clangtextmark.cpp @@ -98,15 +98,12 @@ ClangDiagnosticConfig diagnosticConfig() return warningsConfigForProject(project); } -bool isDiagnosticConfigChangable(Project *project, const ClangDiagnostic &diagnostic) +static bool isDiagnosticConfigChangable(Project *project) { if (!project) return false; - const ClangDiagnosticConfig config = diagnosticConfig(); - if (config.clangTidyMode() == ClangDiagnosticConfig::TidyMode::UseConfigFile - && diagnosticType(diagnostic) == DiagnosticType::Tidy) { + if (diagnosticConfig().useBuildSystemWarnings()) return false; - } return true; } @@ -308,7 +305,7 @@ ClangdTextMark::ClangdTextMark(const FilePath &filePath, // Remove diagnostic warning action Project *project = projectForCurrentEditor(); - if (project && isDiagnosticConfigChangable(project, diag)) { + if (project && isDiagnosticConfigChangable(project)) { action = new QAction(); action->setIcon(Icons::BROKEN.icon()); action->setToolTip(Tr::tr("Disable Diagnostic in Current Project")); diff --git a/src/plugins/clangcodemodel/test/clangbatchfileprocessor.cpp b/src/plugins/clangcodemodel/test/clangbatchfileprocessor.cpp deleted file mode 100644 index 1f0219df997..00000000000 --- a/src/plugins/clangcodemodel/test/clangbatchfileprocessor.cpp +++ /dev/null @@ -1,729 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "clangbatchfileprocessor.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -using namespace ProjectExplorer; - -namespace ClangCodeModel { -namespace Internal { - -static Q_LOGGING_CATEGORY(debug, "qtc.clangcodemodel.batch", QtWarningMsg); - -static int timeOutFromEnvironmentVariable() -{ - bool isConversionOk = false; - const int intervalAsInt = Utils::qtcEnvironmentVariableIntValue("QTC_CLANG_BATCH_TIMEOUT", - &isConversionOk); - if (!isConversionOk) { - qCDebug(debug, "Environment variable QTC_CLANG_BATCH_TIMEOUT is not set, assuming 30000."); - return 30000; - } - - return intervalAsInt; -} - -int timeOutInMs() -{ - static int timeOut = timeOutFromEnvironmentVariable(); - return timeOut; -} - -namespace { - -class BatchFileLineTokenizer -{ -public: - BatchFileLineTokenizer(const QString &line); - - QString nextToken(); - -private: - const QChar *advanceToTokenBegin(); - const QChar *advanceToTokenEnd(); - - bool atEnd() const; - bool atWhiteSpace() const; - bool atQuotationMark() const; - -private: - bool m_isWithinQuotation = false; - QString m_line; - const QChar *m_currentChar; -}; - -BatchFileLineTokenizer::BatchFileLineTokenizer(const QString &line) - : m_line(line) - , m_currentChar(m_line.unicode()) -{ -} - -QString BatchFileLineTokenizer::nextToken() -{ - if (const QChar *tokenBegin = advanceToTokenBegin()) { - if (const QChar *tokenEnd = advanceToTokenEnd()) { - const int length = tokenEnd - tokenBegin; - return QString(tokenBegin, length); - } - } - - return QString(); -} - -const QChar *BatchFileLineTokenizer::advanceToTokenBegin() -{ - m_isWithinQuotation = false; - - forever { - if (atEnd()) - return nullptr; - - if (atQuotationMark()) { - m_isWithinQuotation = true; - ++m_currentChar; - return m_currentChar; - } - - if (!atWhiteSpace()) - return m_currentChar; - - ++m_currentChar; - } -} - -const QChar *BatchFileLineTokenizer::advanceToTokenEnd() -{ - forever { - if (m_isWithinQuotation) { - if (atEnd()) { - qWarning("ClangBatchFileProcessor: error: unfinished quotation."); - return nullptr; - } - - if (atQuotationMark()) - return m_currentChar++; - - } else if (atWhiteSpace() || atEnd()) { - return m_currentChar; - } - - ++m_currentChar; - } -} - -bool BatchFileLineTokenizer::atEnd() const -{ - return *m_currentChar == QLatin1Char('\0'); -} - -bool BatchFileLineTokenizer::atWhiteSpace() const -{ - return *m_currentChar == ' ' - || *m_currentChar == '\t' - || *m_currentChar == '\n'; -} - -bool BatchFileLineTokenizer::atQuotationMark() const -{ - return *m_currentChar == '"'; -} - -struct CommandContext { - QString filePath; - int lineNumber = -1; -}; - -class Command -{ -public: - using Ptr = QSharedPointer; - -public: - Command(const CommandContext &context) : m_commandContext(context) {} - virtual ~Command() = default; - - const CommandContext &context() const { return m_commandContext; } - virtual bool run() { return true; } - -private: - const CommandContext m_commandContext; -}; - -class OpenProjectCommand : public Command -{ -public: - OpenProjectCommand(const CommandContext &context, - const QString &projectFilePath); - - bool run() override; - - static Command::Ptr parse(BatchFileLineTokenizer &arguments, - const CommandContext &context); - -private: - QString m_projectFilePath; -}; - -OpenProjectCommand::OpenProjectCommand(const CommandContext &context, - const QString &projectFilePath) - : Command(context) - , m_projectFilePath(projectFilePath) -{ -} - -bool OpenProjectCommand::run() -{ - qCDebug(debug) << "line" << context().lineNumber << "OpenProjectCommand" << m_projectFilePath; - - const ProjectExplorerPlugin::OpenProjectResult openProjectSucceeded - = ProjectExplorerPlugin::openProject(Utils::FilePath::fromString(m_projectFilePath)); - QTC_ASSERT(openProjectSucceeded, return false); - - Project *project = openProjectSucceeded.project(); - project->configureAsExampleProject(nullptr); - - return CppEditor::Tests::TestCase::waitUntilProjectIsFullyOpened(project, timeOutInMs()); -} - -Command::Ptr OpenProjectCommand::parse(BatchFileLineTokenizer &arguments, - const CommandContext &context) -{ - const QString projectFilePath = arguments.nextToken(); - if (projectFilePath.isEmpty()) { - qWarning("%s:%d: error: No project file path given.", - qPrintable(context.filePath), - context.lineNumber); - return Command::Ptr(); - } - - const QString absoluteProjectFilePath = QFileInfo(projectFilePath).absoluteFilePath(); - - return Command::Ptr(new OpenProjectCommand(context, absoluteProjectFilePath)); -} - -class OpenDocumentCommand : public Command -{ -public: - OpenDocumentCommand(const CommandContext &context, - const QString &documentFilePath); - - bool run() override; - - static Command::Ptr parse(BatchFileLineTokenizer &arguments, const CommandContext &context); - -private: - Utils::FilePath m_documentFilePath; -}; - -OpenDocumentCommand::OpenDocumentCommand(const CommandContext &context, - const QString &documentFilePath) - : Command(context) - , m_documentFilePath(Utils::FilePath::fromString(documentFilePath)) -{ -} - -class WaitForUpdatedCodeWarnings : public QObject -{ -public: - WaitForUpdatedCodeWarnings(ClangEditorDocumentProcessor *processor); - - bool wait(int timeOutInMs) const; - -private: - void onCodeWarningsUpdated() { m_gotResults = true; } - -private: - - bool m_gotResults = false; -}; - -WaitForUpdatedCodeWarnings::WaitForUpdatedCodeWarnings(ClangEditorDocumentProcessor *processor) -{ - connect(processor, - &ClangEditorDocumentProcessor::codeWarningsUpdated, - this, &WaitForUpdatedCodeWarnings::onCodeWarningsUpdated); -} - -bool WaitForUpdatedCodeWarnings::wait(int timeOutInMs) const -{ - QElapsedTimer time; - time.start(); - - forever { - if (time.elapsed() > timeOutInMs) { - qWarning("WaitForUpdatedCodeWarnings: timeout of %d ms reached.", timeOutInMs); - return false; - } - - if (m_gotResults) - return true; - - QCoreApplication::processEvents(); - QThread::msleep(20); - } -} - -bool OpenDocumentCommand::run() -{ - qCDebug(debug) << "line" << context().lineNumber << "OpenDocumentCommand" << m_documentFilePath; - - const bool openEditorSucceeded = Core::EditorManager::openEditor(m_documentFilePath); - QTC_ASSERT(openEditorSucceeded, return false); - - auto *processor = ClangEditorDocumentProcessor::get(m_documentFilePath); - QTC_ASSERT(processor, return false); - - WaitForUpdatedCodeWarnings waiter(processor); - return waiter.wait(timeOutInMs()); -} - -Command::Ptr OpenDocumentCommand::parse(BatchFileLineTokenizer &arguments, - const CommandContext &context) -{ - const QString documentFilePath = arguments.nextToken(); - if (documentFilePath.isEmpty()) { - qWarning("%s:%d: error: No document file path given.", - qPrintable(context.filePath), - context.lineNumber); - return Command::Ptr(); - } - - const QString absoluteDocumentFilePath = QFileInfo(documentFilePath).absoluteFilePath(); - - return Command::Ptr(new OpenDocumentCommand(context, absoluteDocumentFilePath)); -} - -class CloseAllDocuments : public Command -{ -public: - CloseAllDocuments(const CommandContext &context); - - bool run() override; - - static Command::Ptr parse(BatchFileLineTokenizer &arguments, const CommandContext &context); -}; - -CloseAllDocuments::CloseAllDocuments(const CommandContext &context) - : Command(context) -{ -} - -bool CloseAllDocuments::run() -{ - qCDebug(debug) << "line" << context().lineNumber << "CloseAllDocuments"; - - return Core::EditorManager::closeAllEditors(/*askAboutModifiedEditors=*/ false); -} - -Command::Ptr CloseAllDocuments::parse(BatchFileLineTokenizer &arguments, - const CommandContext &context) -{ - const QString argument = arguments.nextToken(); - if (!argument.isEmpty()) { - qWarning("%s:%d: error: Unexpected argument.", - qPrintable(context.filePath), - context.lineNumber); - return Command::Ptr(); - } - - return Command::Ptr(new CloseAllDocuments(context)); -} - -class InsertTextCommand : public Command -{ -public: - // line and column are 1-based - InsertTextCommand(const CommandContext &context, const QString &text); - - bool run() override; - - static Command::Ptr parse(BatchFileLineTokenizer &arguments, - const CommandContext &context); - -private: - const QString m_textToInsert; -}; - -InsertTextCommand::InsertTextCommand(const CommandContext &context, const QString &text) - : Command(context) - , m_textToInsert(text) -{ -} - -TextEditor::BaseTextEditor *currentTextEditor() -{ - return qobject_cast(Core::EditorManager::currentEditor()); -} - -bool InsertTextCommand::run() -{ - qCDebug(debug) << "line" << context().lineNumber << "InsertTextCommand" << m_textToInsert; - - TextEditor::BaseTextEditor *editor = currentTextEditor(); - QTC_ASSERT(editor, return false); - const Utils::FilePath documentFilePath = editor->document()->filePath(); - auto processor = ClangEditorDocumentProcessor::get(documentFilePath); - QTC_ASSERT(processor, return false); - - editor->insert(m_textToInsert); - - WaitForUpdatedCodeWarnings waiter(processor); - return waiter.wait(timeOutInMs()); -} - -Command::Ptr InsertTextCommand::parse(BatchFileLineTokenizer &arguments, - const CommandContext &context) -{ - const QString textToInsert = arguments.nextToken(); - if (textToInsert.isEmpty()) { - qWarning("%s:%d: error: No text to insert given.", - qPrintable(context.filePath), - context.lineNumber); - return Command::Ptr(); - } - - return Command::Ptr(new InsertTextCommand(context, textToInsert)); -} - -class SetCursorCommand : public Command -{ -public: - // line and column are 1-based - SetCursorCommand(const CommandContext &context, int line, int column); - - bool run() override; - - static Command::Ptr parse(BatchFileLineTokenizer &arguments, - const CommandContext &context); - -private: - int m_line; - int m_column; -}; - -SetCursorCommand::SetCursorCommand(const CommandContext &context, int line, int column) - : Command(context) - , m_line(line) - , m_column(column) -{ -} - -bool SetCursorCommand::run() -{ - qCDebug(debug) << "line" << context().lineNumber << "SetCursorCommand" << m_line << m_column; - - TextEditor::BaseTextEditor *editor = currentTextEditor(); - QTC_ASSERT(editor, return false); - - editor->gotoLine(m_line, m_column - 1); - - return true; -} - -Command::Ptr SetCursorCommand::parse(BatchFileLineTokenizer &arguments, - const CommandContext &context) -{ - // Process line - const QString line = arguments.nextToken(); - if (line.isEmpty()) { - qWarning("%s:%d: error: No line number given.", - qPrintable(context.filePath), - context.lineNumber); - return Command::Ptr(); - } - bool converted = false; - const int lineNumber = line.toInt(&converted); - if (!converted) { - qWarning("%s:%d: error: Invalid line number.", - qPrintable(context.filePath), - context.lineNumber); - return Command::Ptr(); - } - - // Process column - const QString column = arguments.nextToken(); - if (column.isEmpty()) { - qWarning("%s:%d: error: No column number given.", - qPrintable(context.filePath), - context.lineNumber); - return Command::Ptr(); - } - converted = false; - const int columnNumber = column.toInt(&converted); - if (!converted) { - qWarning("%s:%d: error: Invalid column number.", - qPrintable(context.filePath), - context.lineNumber); - return Command::Ptr(); - } - - return Command::Ptr(new SetCursorCommand(context, lineNumber, columnNumber)); -} - -class ProcessEventsCommand : public Command -{ -public: - ProcessEventsCommand(const CommandContext &context, int durationInMs); - - bool run() override; - - static Command::Ptr parse(BatchFileLineTokenizer &arguments, - const CommandContext &context); - -private: - int m_durationInMs; -}; - -ProcessEventsCommand::ProcessEventsCommand(const CommandContext &context, - int durationInMs) - : Command(context) - , m_durationInMs(durationInMs) -{ -} - -bool ProcessEventsCommand::run() -{ - qCDebug(debug) << "line" << context().lineNumber << "ProcessEventsCommand" << m_durationInMs; - - QElapsedTimer time; - time.start(); - - forever { - if (time.elapsed() > m_durationInMs) - return true; - - QCoreApplication::processEvents(); - QThread::msleep(20); - } -} - -Command::Ptr ProcessEventsCommand::parse(BatchFileLineTokenizer &arguments, - const CommandContext &context) -{ - const QString durationInMsText = arguments.nextToken(); - if (durationInMsText.isEmpty()) { - qWarning("%s:%d: error: No duration given.", - qPrintable(context.filePath), - context.lineNumber); - return Command::Ptr(); - } - - bool converted = false; - const int durationInMs = durationInMsText.toInt(&converted); - if (!converted) { - qWarning("%s:%d: error: Invalid duration given.", - qPrintable(context.filePath), - context.lineNumber); - return Command::Ptr(); - } - - return Command::Ptr(new ProcessEventsCommand(context, durationInMs)); -} - -class BatchFileReader -{ -public: - BatchFileReader(const QString &filePath); - - bool isFilePathValid() const; - - QString read() const; - -private: - const QString m_batchFilePath; -}; - -BatchFileReader::BatchFileReader(const QString &filePath) - : m_batchFilePath(filePath) -{ -} - -bool BatchFileReader::isFilePathValid() const -{ - QFileInfo fileInfo(m_batchFilePath); - - return !m_batchFilePath.isEmpty() - && fileInfo.isFile() - && fileInfo.isReadable(); -} - -QString BatchFileReader::read() const -{ - QFile file(m_batchFilePath); - QTC_CHECK(file.open(QFile::ReadOnly | QFile::Text)); - - return QString::fromLocal8Bit(file.readAll()); -} - -class BatchFileParser -{ -public: - BatchFileParser(const QString &filePath, - const QString &commands); - - bool parse(); - QVector commands() const; - -private: - bool advanceLine(); - QString currentLine() const; - bool parseLine(const QString &line); - -private: - using ParseFunction = Command::Ptr (*)(BatchFileLineTokenizer &, const CommandContext &); - using CommandToParseFunction = QHash; - CommandToParseFunction m_commandParsers; - - int m_currentLineIndex = -1; - CommandContext m_context; - QStringList m_lines; - QVector m_commands; -}; - -BatchFileParser::BatchFileParser(const QString &filePath, - const QString &commands) - : m_lines(commands.split('\n')) -{ - m_context.filePath = filePath; - - m_commandParsers.insert("openProject", &OpenProjectCommand::parse); - m_commandParsers.insert("openDocument", &OpenDocumentCommand::parse); - m_commandParsers.insert("closeAllDocuments", &CloseAllDocuments::parse); - m_commandParsers.insert("setCursor", &SetCursorCommand::parse); - m_commandParsers.insert("insertText", &InsertTextCommand::parse); - m_commandParsers.insert("processEvents", &ProcessEventsCommand::parse); -} - -bool BatchFileParser::parse() -{ - while (advanceLine()) { - const QString line = currentLine().trimmed(); - if (line.isEmpty() || line.startsWith('#')) - continue; - - if (!parseLine(line)) - return false; - } - - return true; -} - -QVector BatchFileParser::commands() const -{ - return m_commands; -} - -bool BatchFileParser::advanceLine() -{ - ++m_currentLineIndex; - m_context.lineNumber = m_currentLineIndex + 1; - return m_currentLineIndex < m_lines.size(); -} - -QString BatchFileParser::currentLine() const -{ - return m_lines[m_currentLineIndex]; -} - -bool BatchFileParser::parseLine(const QString &line) -{ - BatchFileLineTokenizer tokenizer(line); - QString command = tokenizer.nextToken(); - QTC_CHECK(!command.isEmpty()); - - if (const ParseFunction parseFunction = m_commandParsers.value(command)) { - if (Command::Ptr cmd = parseFunction(tokenizer, m_context)) { - m_commands.append(cmd); - return true; - } - - return false; - } - - qWarning("%s:%d: error: Unknown command \"%s\".", - qPrintable(m_context.filePath), - m_context.lineNumber, - qPrintable(command)); - - return false; -} - -} // anonymous namespace - -static QString applySubstitutions(const QString &filePath, const QString &text) -{ - const QString dirPath = QFileInfo(filePath).absolutePath(); - - QString result = text; - result.replace("${PWD}", dirPath); - - return result; -} - -bool runClangBatchFile(const QString &filePath) -{ - qWarning("ClangBatchFileProcessor: Running \"%s\".", qPrintable(filePath)); - - BatchFileReader reader(filePath); - QTC_ASSERT(reader.isFilePathValid(), return false); - const QString fileContent = reader.read(); - const QString fileContentWithSubstitutionsApplied = applySubstitutions(filePath, fileContent); - - BatchFileParser parser(filePath, fileContentWithSubstitutionsApplied); - QTC_ASSERT(parser.parse(), return false); - const QVector commands = parser.commands(); - - Utils::ExecuteOnDestruction closeAllEditors([] { - qWarning("ClangBatchFileProcessor: Finished, closing all documents."); - QTC_CHECK(Core::EditorManager::closeAllEditors(/*askAboutModifiedEditors=*/ false)); - }); - - for (const Command::Ptr &command : commands) { - const bool runSucceeded = command->run(); - QCoreApplication::processEvents(); // Update GUI - - if (!runSucceeded) { - const CommandContext context = command->context(); - qWarning("%s:%d: Failed to run.", - qPrintable(context.filePath), - context.lineNumber); - return false; - } - } - - return true; -} - -} // namespace Internal -} // namespace ClangCodeModel diff --git a/src/plugins/clangcodemodel/test/clangbatchfileprocessor.h b/src/plugins/clangcodemodel/test/clangbatchfileprocessor.h deleted file mode 100644 index 942269b3648..00000000000 --- a/src/plugins/clangcodemodel/test/clangbatchfileprocessor.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -namespace ClangCodeModel { -namespace Internal { - -int timeOutInMs(); - -bool runClangBatchFile(const QString &filePath); - -} // namespace Internal -} // namespace ClangCodeModel diff --git a/src/plugins/clangcodemodel/test/clangdtests.cpp b/src/plugins/clangcodemodel/test/clangdtests.cpp index c60a45f1df2..a90126c00e8 100644 --- a/src/plugins/clangcodemodel/test/clangdtests.cpp +++ b/src/plugins/clangcodemodel/test/clangdtests.cpp @@ -3,7 +3,6 @@ #include "clangdtests.h" -#include "clangbatchfileprocessor.h" #include "../clangdclient.h" #include "../clangmodelmanagersupport.h" @@ -44,6 +43,7 @@ using namespace CppEditor::Tests; using namespace LanguageClient; using namespace ProjectExplorer; using namespace TextEditor; +using namespace Utils; namespace ClangCodeModel { namespace Internal { @@ -69,6 +69,27 @@ static QString qrcPath(const QString &relativeFilePath) return ":/unittests/ClangCodeModel/" + relativeFilePath; } +static Q_LOGGING_CATEGORY(debug, "qtc.clangcodemodel.batch", QtWarningMsg); + +static int timeOutFromEnvironmentVariable() +{ + bool isConversionOk = false; + const int intervalAsInt = Utils::qtcEnvironmentVariableIntValue("QTC_CLANG_BATCH_TIMEOUT", + &isConversionOk); + if (!isConversionOk) { + qCDebug(debug, "Environment variable QTC_CLANG_BATCH_TIMEOUT is not set, assuming 30000."); + return 30000; + } + + return intervalAsInt; +} + +int timeOutInMs() +{ + static int timeOut = timeOutFromEnvironmentVariable(); + return timeOut; +} + ClangdTest::~ClangdTest() { EditorManager::closeAllEditors(false); @@ -77,7 +98,7 @@ ClangdTest::~ClangdTest() delete m_projectDir; } -Utils::FilePath ClangdTest::filePath(const QString &fileName) const +FilePath ClangdTest::filePath(const QString &fileName) const { return m_projectDir->absolutePath(fileName); } @@ -119,7 +140,7 @@ void ClangdTest::initTestCase() { const QString clangdFromEnv = Utils::qtcEnvironmentVariable("QTC_CLANGD"); if (!clangdFromEnv.isEmpty()) - CppEditor::ClangdSettings::setClangdFilePath(Utils::FilePath::fromString(clangdFromEnv)); + CppEditor::ClangdSettings::setClangdFilePath(FilePath::fromString(clangdFromEnv)); const auto clangd = CppEditor::ClangdSettings::instance().clangdFilePath(); if (clangd.isEmpty() || !clangd.exists()) QSKIP("clangd binary not found"); @@ -170,7 +191,7 @@ void ClangdTestFindReferences::initTestCase() ClangdTest::initTestCase(); CppEditor::codeModelSettings()->setCategorizeFindReferences(true); connect(client(), &ClangdClient::foundReferences, this, - [this](const QList &results) { + [this](const SearchResultItems &results) { if (results.isEmpty()) return; if (results.first().path().first().endsWith("defs.h")) @@ -184,8 +205,7 @@ void ClangdTestFindReferences::test_data() { QTest::addColumn("fileName"); QTest::addColumn("pos"); - using ItemList = QList; - QTest::addColumn("expectedResults"); + QTest::addColumn("expectedResults"); static const auto makeItem = [](int line, int column, Usage::Tags tags) { SearchResultItem item; @@ -194,7 +214,7 @@ void ClangdTestFindReferences::test_data() return item; }; - QTest::newRow("struct member") << "defs.h" << 55 << ItemList{ + QTest::newRow("struct member") << "defs.h" << 55 << SearchResultItems{ makeItem(2, 17, Usage::Tag::Read), makeItem(3, 15, Usage::Tag::Declaration), makeItem(6, 17, Usage::Tag::WritableRef), makeItem(8, 11, Usage::Tag::WritableRef), makeItem(9, 13, Usage::Tag::WritableRef), makeItem(10, 12, Usage::Tag::WritableRef), @@ -211,23 +231,23 @@ void ClangdTestFindReferences::test_data() makeItem(56, 7, Usage::Tag::Write), makeItem(56, 25, Usage::Tags()), makeItem(58, 13, Usage::Tag::Read), makeItem(58, 25, Usage::Tag::Read), makeItem(59, 7, Usage::Tag::Write), makeItem(59, 24, Usage::Tag::Read)}; - QTest::newRow("constructor member initialization") << "defs.h" << 68 << ItemList{ + QTest::newRow("constructor member initialization") << "defs.h" << 68 << SearchResultItems{ makeItem(2, 10, Usage::Tag::Write), makeItem(4, 8, Usage::Tag::Declaration)}; - QTest::newRow("direct member initialization") << "defs.h" << 101 << ItemList{ + QTest::newRow("direct member initialization") << "defs.h" << 101 << SearchResultItems{ makeItem(5, 21, Initialization), makeItem(45, 16, Usage::Tag::Read)}; - ItemList pureVirtualRefs{makeItem(17, 17, Usage::Tag::Declaration), + SearchResultItems pureVirtualRefs{makeItem(17, 17, Usage::Tag::Declaration), makeItem(21, 9, {Usage::Tag::Declaration, Usage::Tag::Override})}; QTest::newRow("pure virtual declaration") << "defs.h" << 420 << pureVirtualRefs; - QTest::newRow("pointer variable") << "main.cpp" << 52 << ItemList{ + QTest::newRow("pointer variable") << "main.cpp" << 52 << SearchResultItems{ makeItem(6, 10, Initialization), makeItem(8, 4, Usage::Tag::Write), makeItem(10, 4, Usage::Tag::Write), makeItem(24, 5, Usage::Tag::Write), makeItem(25, 11, Usage::Tag::WritableRef), makeItem(26, 11, Usage::Tag::Read), makeItem(27, 10, Usage::Tag::WritableRef), makeItem(28, 10, Usage::Tag::Read), makeItem(29, 11, Usage::Tag::Read), makeItem(30, 15, Usage::Tag::WritableRef), makeItem(31, 22, Usage::Tag::Read)}; - QTest::newRow("struct variable") << "main.cpp" << 39 << ItemList{ + QTest::newRow("struct variable") << "main.cpp" << 39 << SearchResultItems{ makeItem(5, 7, Usage::Tag::Declaration), makeItem(6, 15, Usage::Tag::WritableRef), makeItem(8, 9, Usage::Tag::WritableRef), makeItem(9, 11, Usage::Tag::WritableRef), makeItem(11, 4, Usage::Tag::Write), makeItem(11, 11, Usage::Tag::WritableRef), @@ -248,7 +268,7 @@ void ClangdTestFindReferences::test_data() // Some of these are conceptually questionable, as S is a type and thus we cannot "read from" // or "write to" it. But it probably matches the intuitive user expectation. - QTest::newRow("struct type") << "defs.h" << 7 << ItemList{ + QTest::newRow("struct type") << "defs.h" << 7 << SearchResultItems{ makeItem(1, 7, Usage::Tag::Declaration), makeItem(2, 4, (Usage::Tags{Usage::Tag::Declaration, Usage::Tag::ConstructorDestructor})), makeItem(20, 19, Usage::Tags()), makeItem(10, 9, Usage::Tag::WritableRef), @@ -260,24 +280,24 @@ void ClangdTestFindReferences::test_data() makeItem(58, 10, Usage::Tag::Read), makeItem(58, 22, Usage::Tag::Read), makeItem(59, 4, Usage::Tag::Write), makeItem(59, 21, Usage::Tag::Read)}; - QTest::newRow("struct type 2") << "defs.h" << 450 << ItemList{ + QTest::newRow("struct type 2") << "defs.h" << 450 << SearchResultItems{ makeItem(20, 7, Usage::Tag::Declaration), makeItem(5, 4, Usage::Tags()), makeItem(13, 21, Usage::Tags()), makeItem(32, 8, Usage::Tags())}; - QTest::newRow("constructor") << "defs.h" << 627 << ItemList{ + QTest::newRow("constructor") << "defs.h" << 627 << SearchResultItems{ makeItem(31, 4, (Usage::Tags{Usage::Tag::Declaration, Usage::Tag::ConstructorDestructor})), makeItem(36, 7, Usage::Tag::ConstructorDestructor)}; - QTest::newRow("subclass") << "defs.h" << 450 << ItemList{ + QTest::newRow("subclass") << "defs.h" << 450 << SearchResultItems{ makeItem(20, 7, Usage::Tag::Declaration), makeItem(5, 4, Usage::Tags()), makeItem(13, 21, Usage::Tags()), makeItem(32, 8, Usage::Tags())}; - QTest::newRow("array variable") << "main.cpp" << 1134 << ItemList{ + QTest::newRow("array variable") << "main.cpp" << 1134 << SearchResultItems{ makeItem(57, 8, Usage::Tag::Declaration), makeItem(58, 4, Usage::Tag::Write), makeItem(59, 15, Usage::Tag::Read)}; - QTest::newRow("free function") << "defs.h" << 510 << ItemList{ + QTest::newRow("free function") << "defs.h" << 510 << SearchResultItems{ makeItem(24, 5, Usage::Tag::Declaration), makeItem(19, 4, Usage::Tags()), makeItem(25, 4, Usage::Tags()), makeItem(60, 26, Usage::Tag::Read)}; - QTest::newRow("member function") << "defs.h" << 192 << ItemList{ + QTest::newRow("member function") << "defs.h" << 192 << SearchResultItems{ makeItem(9, 12, Usage::Tag::Declaration), makeItem(40, 8, Usage::Tags())}; } @@ -288,7 +308,7 @@ void ClangdTestFindReferences::test() { QFETCH(QString, fileName); QFETCH(int, pos); - QFETCH(QList, expectedResults); + QFETCH(SearchResultItems, expectedResults); TextEditor::TextDocument * const doc = document(fileName); QVERIFY(doc); @@ -388,13 +408,13 @@ void ClangdTestFollowSymbol::test() timer.setSingleShot(true); QEventLoop loop; QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); - Utils::Link actualLink; - const auto handler = [&actualLink, &loop](const Utils::Link &l) { + Link actualLink; + const auto handler = [&actualLink, &loop](const Link &l) { actualLink = l; loop.quit(); }; QTextCursor cursor(doc->document()); - const int pos = Utils::Text::positionInText(doc->document(), sourceLine, sourceColumn); + const int pos = Text::positionInText(doc->document(), sourceLine, sourceColumn); cursor.setPosition(pos); client()->followSymbol(doc, cursor, nullptr, handler, true, goToType ? FollowTo::SymbolType : FollowTo::SymbolDef, false); @@ -500,15 +520,14 @@ void ClangdTestLocalReferences::test() QEventLoop loop; QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); QList actualRanges; - const auto handler = [&actualRanges, &loop](const QString &symbol, - const Utils::Links &links, int) { - for (const Utils::Link &link : links) + const auto handler = [&actualRanges, &loop](const QString &symbol, const Links &links, int) { + for (const Link &link : links) actualRanges << Range(link.targetLine, link.targetColumn, symbol.length()); loop.quit(); }; QTextCursor cursor(doc->document()); - const int pos = Utils::Text::positionInText(doc->document(), sourceLine, sourceColumn); + const int pos = Text::positionInText(doc->document(), sourceLine, sourceColumn); cursor.setPosition(pos); client()->findLocalUsages(doc, cursor, std::move(handler)); timer.start(10000); @@ -639,7 +658,7 @@ void ClangdTestTooltips::test() connect(client(), &ClangdClient::helpItemGathered, &loop, handler); QTextCursor cursor(doc->document()); - const int pos = Utils::Text::positionInText(doc->document(), line, column); + const int pos = Text::positionInText(doc->document(), line, column); cursor.setPosition(pos); editor->editorWidget()->processTooltipRequest(cursor); @@ -1296,11 +1315,11 @@ void ClangdTestHighlighting::test() const TextEditor::TextDocument * const doc = document("highlighting.cpp"); QVERIFY(doc); - const int startPos = Utils::Text::positionInText(doc->document(), firstLine, startColumn); - const int endPos = Utils::Text::positionInText(doc->document(), lastLine, endColumn); + const int startPos = Text::positionInText(doc->document(), firstLine, startColumn); + const int endPos = Text::positionInText(doc->document(), lastLine, endColumn); const auto lessThan = [=](const TextEditor::HighlightingResult &r, int) { - return Utils::Text::positionInText(doc->document(), r.line, r.column) < startPos; + return Text::positionInText(doc->document(), r.line, r.column) < startPos; }; const auto findResults = [=] { TextEditor::HighlightingResults results; @@ -1308,7 +1327,7 @@ void ClangdTestHighlighting::test() if (it == m_results.cend()) return results; while (it != m_results.cend()) { - const int resultEndPos = Utils::Text::positionInText(doc->document(), it->line, + const int resultEndPos = Text::positionInText(doc->document(), it->line, it->column) + it->length; if (resultEndPos > endPos) break; @@ -1419,7 +1438,7 @@ public: { const int pos = currentPosition(); QPair lineAndColumn; - Utils::Text::convertPosition(m_doc, pos, &lineAndColumn.first, &lineAndColumn.second); + Text::convertPosition(m_doc, pos, &lineAndColumn.first, &lineAndColumn.second); return lineAndColumn; } @@ -1520,7 +1539,7 @@ void ClangdTestCompletion::testCompleteGlobals() Manipulator manipulator; item->apply(manipulator, cursorPos); QCOMPARE(manipulator.getLine(7), " globalFunction() /* COMPLETE HERE */"); - QCOMPARE(manipulator.cursorPos(), qMakePair(7, 20)); + QCOMPARE(manipulator.cursorPos(), qMakePair(7, 19)); QCOMPARE(manipulator.skipPos(), -1); } @@ -1540,7 +1559,7 @@ void ClangdTestCompletion::testCompleteMembers() Manipulator manipulator; item->apply(manipulator, cursorPos); QCOMPARE(manipulator.getLine(7), " s.member /* COMPLETE HERE */"); - QCOMPARE(manipulator.cursorPos(), qMakePair(7, 13)); + QCOMPARE(manipulator.cursorPos(), qMakePair(7, 12)); QCOMPARE(manipulator.skipPos(), -1); } @@ -1558,7 +1577,7 @@ void ClangdTestCompletion::testCompleteMembersFromInside() Manipulator manipulator; item->apply(manipulator, cursorPos); QCOMPARE(manipulator.getLine(4), " privateFunc() /* COMPLETE HERE */"); - QCOMPARE(manipulator.cursorPos(), qMakePair(4, 22)); + QCOMPARE(manipulator.cursorPos(), qMakePair(4, 21)); QCOMPARE(manipulator.skipPos(), -1); } @@ -1576,7 +1595,7 @@ void ClangdTestCompletion::testCompleteMembersFromOutside() Manipulator manipulator; item->apply(manipulator, cursorPos); QCOMPARE(manipulator.getLine(13), " c.publicFunc() /* COMPLETE HERE */"); - QCOMPARE(manipulator.cursorPos(), qMakePair(13, 19)); + QCOMPARE(manipulator.cursorPos(), qMakePair(13, 18)); QCOMPARE(manipulator.skipPos(), -1); } @@ -1594,7 +1613,7 @@ void ClangdTestCompletion::testCompleteMembersFromFriend() Manipulator manipulator; item->apply(manipulator, cursorPos); QCOMPARE(manipulator.getLine(14), " C().privateFunc() /* COMPLETE HERE */"); - QCOMPARE(manipulator.cursorPos(), qMakePair(14, 22)); + QCOMPARE(manipulator.cursorPos(), qMakePair(14, 21)); QCOMPARE(manipulator.skipPos(), -1); } @@ -1611,7 +1630,7 @@ void ClangdTestCompletion::testFunctionAddress() Manipulator manipulator; item->apply(manipulator, cursorPos); QCOMPARE(manipulator.getLine(7), " const auto p = &S::memberFunc /* COMPLETE HERE */;"); - QCOMPARE(manipulator.cursorPos(), qMakePair(7, 34)); + QCOMPARE(manipulator.cursorPos(), qMakePair(7, 33)); QCOMPARE(manipulator.skipPos(), -1); } @@ -1677,7 +1696,7 @@ void ClangdTestCompletion::testCompleteClassAndConstructor() Manipulator manipulator; item->apply(manipulator, cursorPos); QCOMPARE(manipulator.getLine(7), " Foo( /* COMPLETE HERE */"); - QCOMPARE(manipulator.cursorPos(), qMakePair(7, 9)); + QCOMPARE(manipulator.cursorPos(), qMakePair(7, 8)); QCOMPARE(manipulator.skipPos(), -1); } @@ -1704,7 +1723,7 @@ void ClangdTestCompletion::testCompleteWithDotToArrowCorrection() Manipulator manipulator; item->apply(manipulator, cursorPos); QCOMPARE(manipulator.getLine(4), " bar->member /* COMPLETE HERE */"); - QCOMPARE(manipulator.cursorPos(), qMakePair(4, 16)); + QCOMPARE(manipulator.cursorPos(), qMakePair(4, 15)); QCOMPARE(manipulator.skipPos(), -1); } @@ -1735,7 +1754,7 @@ void ClangdTestCompletion::testCompleteCodeInGeneratedUiFile() Manipulator manipulator; item->apply(manipulator, cursorPos); QCOMPARE(manipulator.getLine(34), " ui->setupUi( /* COMPLETE HERE */"); - QCOMPARE(manipulator.cursorPos(), qMakePair(34, 17)); + QCOMPARE(manipulator.cursorPos(), qMakePair(34, 16)); QCOMPARE(manipulator.skipPos(), -1); } @@ -1844,7 +1863,7 @@ void ClangdTestCompletion::startCollectingHighlightingInfo() { m_documentsWithHighlighting.clear(); connect(client(), &ClangdClient::highlightingResultsReady, this, - [this](const HighlightingResults &, const Utils::FilePath &file) { + [this](const HighlightingResults &, const FilePath &file) { m_documentsWithHighlighting.insert(file); }); } @@ -1861,9 +1880,9 @@ void ClangdTestCompletion::getProposal(const QString &fileName, if (cursorPos) *cursorPos = pos; int line, column; - Utils::Text::convertPosition(doc->document(), pos, &line, &column); + Text::convertPosition(doc->document(), pos, &line, &column); const auto editor = qobject_cast( - EditorManager::openEditorAt({doc->filePath(), line, column - 1})); + EditorManager::openEditorAt({doc->filePath(), line, column})); QVERIFY(editor); QCOMPARE(EditorManager::currentEditor(), editor); QCOMPARE(editor->textDocument(), doc); diff --git a/src/plugins/clangcodemodel/test/clangdtests.h b/src/plugins/clangcodemodel/test/clangdtests.h index 2a8f33579c2..90f4eca45af 100644 --- a/src/plugins/clangcodemodel/test/clangdtests.h +++ b/src/plugins/clangcodemodel/test/clangdtests.h @@ -4,11 +4,11 @@ #pragma once #include -#include #include #include #include #include +#include #include #include @@ -74,7 +74,7 @@ private slots: void test(); private: - QList m_actualResults; + Utils::SearchResultItems m_actualResults; }; class ClangdTestFollowSymbol : public ClangdTest diff --git a/src/plugins/clangformat/CMakeLists.txt b/src/plugins/clangformat/CMakeLists.txt index 79767a2ffed..7c49e9ad47e 100644 --- a/src/plugins/clangformat/CMakeLists.txt +++ b/src/plugins/clangformat/CMakeLists.txt @@ -39,4 +39,5 @@ extend_qtc_plugin(ClangFormat tests/clangformat-test.h DEFINES TESTDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/tests/data" + EXPLICIT_MOC tests/clangformat-test.h ) diff --git a/src/plugins/clangformat/clangformat.qbs b/src/plugins/clangformat/clangformat.qbs index bd1b9677632..057035cd1a6 100644 --- a/src/plugins/clangformat/clangformat.qbs +++ b/src/plugins/clangformat/clangformat.qbs @@ -54,10 +54,8 @@ QtcPlugin { "clangformatutils.cpp", ] - Group { - name: "Tests" + QtcTestFiles { prefix: "tests/" - condition: qtc.testsEnabled cpp.defines: outer.concat('TESTDATA_DIR="' + sourceDirectory + "/tests/data" + '"') files: [ "clangformat-test.cpp", diff --git a/src/plugins/clangformat/clangformatbaseindenter.cpp b/src/plugins/clangformat/clangformatbaseindenter.cpp index affabfec671..5138e03510f 100644 --- a/src/plugins/clangformat/clangformatbaseindenter.cpp +++ b/src/plugins/clangformat/clangformatbaseindenter.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include #include #include @@ -56,9 +56,6 @@ void adjustFormatStyleForLineBreak(clang::format::FormatStyle &style, return; style.ColumnLimit = 0; -#ifdef KEEP_LINE_BREAKS_FOR_NON_EMPTY_LINES_BACKPORTED - style.KeepLineBreaksForNonEmptyLines = true; -#endif } llvm::StringRef clearExtraNewline(llvm::StringRef text) @@ -349,6 +346,17 @@ bool isInsideDummyTextInLine(const QString &originalLine, const QString &modifie || !modifiedLine.startsWith(originalLine)); } +static Utils::Text::Position utf16LineColumn(const QByteArray &utf8Buffer, int utf8Offset) +{ + Utils::Text::Position position; + position.line = static_cast(std::count(utf8Buffer.begin(), + utf8Buffer.begin() + utf8Offset, '\n')) + 1; + const int startOfLineOffset = utf8Offset ? (utf8Buffer.lastIndexOf('\n', utf8Offset - 1) + 1) + : 0; + position.column = QString::fromUtf8(utf8Buffer.mid(startOfLineOffset, + utf8Offset - startOfLineOffset)).length(); + return position; +} Utils::Text::Replacements utf16Replacements(const QTextDocument *doc, const QByteArray &utf8Buffer, const clang::tooling::Replacements &replacements) @@ -357,9 +365,8 @@ Utils::Text::Replacements utf16Replacements(const QTextDocument *doc, convertedReplacements.reserve(replacements.size()); for (const clang::tooling::Replacement &replacement : replacements) { - Utils::LineColumn lineColUtf16 = Utils::Text::utf16LineColumn(utf8Buffer, - static_cast( - replacement.getOffset())); + Utils::Text::Position lineColUtf16 = utf16LineColumn( + utf8Buffer, static_cast(replacement.getOffset())); if (!lineColUtf16.isValid()) continue; @@ -367,14 +374,13 @@ Utils::Text::Replacements utf16Replacements(const QTextDocument *doc, const QString bufferLineText = Utils::Text::utf16LineTextInUtf8Buffer(utf8Buffer, static_cast(replacement.getOffset())); - if (isInsideDummyTextInLine(lineText, bufferLineText, lineColUtf16.column)) + if (isInsideDummyTextInLine(lineText, bufferLineText, lineColUtf16.column + 1)) continue; - lineColUtf16.column = std::min(lineColUtf16.column, int(lineText.length()) + 1); - + lineColUtf16.column = std::min(lineColUtf16.column, int(lineText.length())); const int utf16Offset = Utils::Text::positionInText(doc, lineColUtf16.line, - lineColUtf16.column); + lineColUtf16.column + 1); const int utf16Length = QString::fromUtf8( utf8Buffer.mid(static_cast(replacement.getOffset()), static_cast(replacement.getLength()))) @@ -744,7 +750,7 @@ void ClangFormatBaseIndenter::autoIndent(const QTextCursor &cursor, clang::format::FormatStyle overrideStyle(const Utils::FilePath &fileName) { const ProjectExplorer::Project *projectForFile - = ProjectExplorer::SessionManager::projectForFile(fileName); + = ProjectExplorer::ProjectManager::projectForFile(fileName); const TextEditor::ICodeStylePreferences *preferences = projectForFile diff --git a/src/plugins/clangformat/clangformatchecks.cpp b/src/plugins/clangformat/clangformatchecks.cpp index 1ec64e7a379..6027e600d70 100644 --- a/src/plugins/clangformat/clangformatchecks.cpp +++ b/src/plugins/clangformat/clangformatchecks.cpp @@ -15,8 +15,6 @@ #include #include -using namespace Utils; - using namespace ClangFormat; ClangFormatChecks::ClangFormatChecks(QWidget *parent) diff --git a/src/plugins/clangformat/clangformatconfigwidget.cpp b/src/plugins/clangformat/clangformatconfigwidget.cpp index 12e02b4fa74..e2f9272e146 100644 --- a/src/plugins/clangformat/clangformatconfigwidget.cpp +++ b/src/plugins/clangformat/clangformatconfigwidget.cpp @@ -82,15 +82,14 @@ ClangFormatConfigWidget::ClangFormatConfigWidget(TextEditor::ICodeStylePreferenc d->project = project; d->config = std::make_unique(filePathToCurrentSettings(codeStyle->currentPreferences())); - resize(489, 305); d->fallbackConfig = new QLabel(Tr::tr("Clang-Format Style")); d->checksScrollArea = new QScrollArea(); d->checksWidget = new ClangFormatChecks(); d->checksScrollArea->setWidget(d->checksWidget); d->checksScrollArea->setWidgetResizable(true); - d->checksWidget->setEnabled(!codeStyle->isReadOnly() - && !codeStyle->isTemporarilyReadOnly()); + d->checksWidget->setEnabled(!codeStyle->isReadOnly() && !codeStyle->isTemporarilyReadOnly() + && !codeStyle->isAdditionalTabDisabled()); FilePath fileName; if (d->project) @@ -141,8 +140,8 @@ void ClangFormatConfigWidget::slotCodeStyleChanged( d->config->setIsReadOnly(codeStyle->isReadOnly()); d->style = d->config->style(); - d->checksWidget->setEnabled(!codeStyle->isReadOnly() - && !codeStyle->isTemporarilyReadOnly()); + d->checksWidget->setEnabled(!codeStyle->isReadOnly() && !codeStyle->isTemporarilyReadOnly() + && !codeStyle->isAdditionalTabDisabled()); fillTable(); updatePreview(); diff --git a/src/plugins/clangformat/clangformatfile.cpp b/src/plugins/clangformat/clangformatfile.cpp index 7023645cc80..f0c48099131 100644 --- a/src/plugins/clangformat/clangformatfile.cpp +++ b/src/plugins/clangformat/clangformatfile.cpp @@ -169,6 +169,12 @@ CppEditor::CppCodeStyleSettings ClangFormatFile::toCppCodeStyleSettings( settings.bindStarToRightSpecifier = style.PointerAlignment == FormatStyle::PAS_Right; } + settings.extraPaddingForConditionsIfConfusingAlign = style.BreakBeforeBinaryOperators + == FormatStyle::BOS_All; + settings.alignAssignments = style.BreakBeforeBinaryOperators == FormatStyle::BOS_All + || style.BreakBeforeBinaryOperators + == FormatStyle::BOS_NonAssignment; + return settings; } @@ -188,6 +194,9 @@ void ClangFormatFile::fromCppCodeStyleSettings(const CppEditor::CppCodeStyleSett if (settings.indentClassBraces || settings.indentEnumBraces || settings.indentBlockBraces || settings.indentFunctionBraces) m_style.BreakBeforeBraces = FormatStyle::BS_Whitesmiths; + else + m_style.BreakBeforeBraces = FormatStyle::BS_Custom; + m_style.IndentCaseLabels = settings.indentSwitchLabels; #if LLVM_VERSION_MAJOR >= 11 @@ -196,11 +205,12 @@ void ClangFormatFile::fromCppCodeStyleSettings(const CppEditor::CppCodeStyleSett || settings.indentControlFlowRelativeToSwitchLabels; #endif - if (settings.alignAssignments) - m_style.BreakBeforeBinaryOperators = FormatStyle::BOS_NonAssignment; - if (settings.extraPaddingForConditionsIfConfusingAlign) m_style.BreakBeforeBinaryOperators = FormatStyle::BOS_All; + else if (settings.alignAssignments) + m_style.BreakBeforeBinaryOperators = FormatStyle::BOS_NonAssignment; + else + m_style.BreakBeforeBinaryOperators = FormatStyle::BOS_None; m_style.DerivePointerAlignment = settings.bindStarToIdentifier || settings.bindStarToTypeName || settings.bindStarToLeftSpecifier diff --git a/src/plugins/clangformat/clangformatfile.h b/src/plugins/clangformat/clangformatfile.h index 6e2267befc9..dcd0e0d6c10 100644 --- a/src/plugins/clangformat/clangformatfile.h +++ b/src/plugins/clangformat/clangformatfile.h @@ -3,7 +3,8 @@ #pragma once -#include "utils/filepath.h" +#include + #include namespace CppEditor { class CppCodeStyleSettings; } diff --git a/src/plugins/clangformat/clangformatglobalconfigwidget.cpp b/src/plugins/clangformat/clangformatglobalconfigwidget.cpp index a0344e14eda..af8a0846b15 100644 --- a/src/plugins/clangformat/clangformatglobalconfigwidget.cpp +++ b/src/plugins/clangformat/clangformatglobalconfigwidget.cpp @@ -31,34 +31,37 @@ ClangFormatGlobalConfigWidget::ClangFormatGlobalConfigWidget( , m_project(project) , m_codeStyle(codeStyle) { - resize(489, 305); - m_projectHasClangFormat = new QLabel(this); m_formattingModeLabel = new QLabel(Tr::tr("Formatting mode:")); m_indentingOrFormatting = new QComboBox(this); m_formatWhileTyping = new QCheckBox(Tr::tr("Format while typing")); m_formatOnSave = new QCheckBox(Tr::tr("Format edited code on file save")); - m_overrideDefault = new QCheckBox(Tr::tr("Override Clang Format configuration file")); + m_overrideDefault = new QCheckBox(Tr::tr("Override .clang-format file")); m_useGlobalSettings = new QCheckBox(Tr::tr("Use global settings")); m_useGlobalSettings->hide(); + m_overrideDefaultFile = ClangFormatSettings::instance().overrideDefaultFile(); using namespace Layouting; + QWidget *globalSettingsGroupBoxWidget = nullptr; + Group globalSettingsGroupBox { + bindTo(&globalSettingsGroupBoxWidget), title(Tr::tr("ClangFormat settings:")), - Column { - m_useGlobalSettings, - Row { m_formattingModeLabel, m_indentingOrFormatting, st }, - m_formatWhileTyping, - m_formatOnSave, - m_projectHasClangFormat, - m_overrideDefault + Column { + m_useGlobalSettings, + Row { m_formattingModeLabel, m_indentingOrFormatting, st }, + m_formatWhileTyping, + m_formatOnSave, + m_projectHasClangFormat, + m_overrideDefault } }; Column { - globalSettingsGroupBox - }.attachTo(this, Layouting::WithoutMargins); + globalSettingsGroupBox, + noMargin + }.attachTo(this); initCheckBoxes(); initIndentationOrFormattingCombobox(); @@ -73,7 +76,8 @@ ClangFormatGlobalConfigWidget::ClangFormatGlobalConfigWidget( m_useGlobalSettings->show(); return; } - globalSettingsGroupBox.widget->show(); + + globalSettingsGroupBoxWidget->show(); } ClangFormatGlobalConfigWidget::~ClangFormatGlobalConfigWidget() = default; @@ -153,33 +157,52 @@ void ClangFormatGlobalConfigWidget::initOverrideCheckBox() "can be overridden by the settings below.")); } - auto setEnableOverrideCheckBox = [this](int index) { + auto setTemporarilyReadOnly = [this]() { + if (m_ignoreChanges.isLocked()) + return; + Utils::GuardLocker locker(m_ignoreChanges); + m_codeStyle->currentPreferences()->setTemporarilyReadOnly(!m_overrideDefault->isChecked()); + m_codeStyle->currentPreferences()->setIsAdditionalTabDisabled(!m_overrideDefault->isEnabled()); + ClangFormatSettings::instance().write(); + emit m_codeStyle->currentPreferencesChanged(m_codeStyle->currentPreferences()); + }; + + auto setEnableOverrideCheckBox = [this, setTemporarilyReadOnly](int index) { bool isDisable = index == static_cast(ClangFormatSettings::Mode::Disable); m_overrideDefault->setDisabled(isDisable); + setTemporarilyReadOnly(); }; setEnableOverrideCheckBox(m_indentingOrFormatting->currentIndex()); connect(m_indentingOrFormatting, &QComboBox::currentIndexChanged, this, setEnableOverrideCheckBox); - m_overrideDefault->setToolTip( - Tr::tr("Override Clang Format configuration file with the chosen configuration.")); + m_overrideDefault->setToolTip(Tr::tr( + "When this option is enabled, ClangFormat will use a\n" + "user-specified configuration from the widget below,\n" + "instead of the project .clang-format file. You can\n" + "customize the formatting options for your code by\n" + "adjusting the settings in the widget. Note that any\n" + "changes made there will only affect the current\n" + "configuration, and will not modify the project\n" + ".clang-format file.")); m_overrideDefault->setChecked(getProjectOverriddenSettings(m_project)); - m_codeStyle->currentPreferences()->setTemporarilyReadOnly(!m_overrideDefault->isChecked()); + setTemporarilyReadOnly(); - connect(m_overrideDefault, &QCheckBox::toggled, this, [this](bool checked) { - if (m_project) + connect(m_overrideDefault, &QCheckBox::toggled, this, [this, setTemporarilyReadOnly](bool checked) { + if (m_project) { m_project->setNamedSettings(Constants::OVERRIDE_FILE_ID, checked); - else { - m_codeStyle->currentPreferences()->setTemporarilyReadOnly(!checked); - emit m_codeStyle->currentPreferencesChanged(m_codeStyle->currentPreferences()); + } else { + ClangFormatSettings::instance().setOverrideDefaultFile(checked); + setTemporarilyReadOnly(); } }); - connect(m_codeStyle, &TextEditor::ICodeStylePreferences::currentPreferencesChanged, this, [this] { - m_codeStyle->currentPreferences()->setTemporarilyReadOnly(!m_overrideDefault->isChecked()); - }); + connect(m_codeStyle, + &TextEditor::ICodeStylePreferences::currentPreferencesChanged, + this, + setTemporarilyReadOnly); } @@ -192,12 +215,14 @@ void ClangFormatGlobalConfigWidget::apply() settings.setMode( static_cast(m_indentingOrFormatting->currentIndex())); settings.setOverrideDefaultFile(m_overrideDefault->isChecked()); + m_overrideDefaultFile = m_overrideDefault->isChecked(); } settings.write(); } void ClangFormatGlobalConfigWidget::finish() { + ClangFormatSettings::instance().setOverrideDefaultFile(m_overrideDefaultFile); m_codeStyle->currentPreferences()->setTemporarilyReadOnly( !ClangFormatSettings::instance().overrideDefaultFile()); } diff --git a/src/plugins/clangformat/clangformatglobalconfigwidget.h b/src/plugins/clangformat/clangformatglobalconfigwidget.h index 063c82852a3..7b2d6fe7c9c 100644 --- a/src/plugins/clangformat/clangformatglobalconfigwidget.h +++ b/src/plugins/clangformat/clangformatglobalconfigwidget.h @@ -5,6 +5,8 @@ #include +#include + #include QT_BEGIN_NAMESPACE @@ -40,6 +42,8 @@ private: ProjectExplorer::Project *m_project; TextEditor::ICodeStylePreferences *m_codeStyle; + Utils::Guard m_ignoreChanges; + bool m_overrideDefaultFile; QLabel *m_projectHasClangFormat; QLabel *m_formattingModeLabel; diff --git a/src/plugins/clangformat/clangformatindenter.cpp b/src/plugins/clangformat/clangformatindenter.cpp index 94e0fafd8e0..67d5171b010 100644 --- a/src/plugins/clangformat/clangformatindenter.cpp +++ b/src/plugins/clangformat/clangformatindenter.cpp @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include diff --git a/src/plugins/clangformat/clangformatutils.cpp b/src/plugins/clangformat/clangformatutils.cpp index 120e27fde7b..9304599d999 100644 --- a/src/plugins/clangformat/clangformatutils.cpp +++ b/src/plugins/clangformat/clangformatutils.cpp @@ -15,7 +15,7 @@ #include #include -#include +#include #include @@ -210,13 +210,12 @@ bool getProjectOverriddenSettings(const ProjectExplorer::Project *project) bool getCurrentOverriddenSettings(const Utils::FilePath &filePath) { - const ProjectExplorer::Project *project = ProjectExplorer::SessionManager::projectForFile( + const ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::projectForFile( filePath); - return getProjectUseGlobalSettings(project) ? !TextEditor::TextEditorSettings::codeStyle("Cpp") - ->currentPreferences() - ->isTemporarilyReadOnly() - : getProjectOverriddenSettings(project); + return getProjectUseGlobalSettings(project) + ? ClangFormatSettings::instance().overrideDefaultFile() + : getProjectOverriddenSettings(project); } ClangFormatSettings::Mode getProjectIndentationOrFormattingSettings( @@ -233,7 +232,7 @@ ClangFormatSettings::Mode getProjectIndentationOrFormattingSettings( ClangFormatSettings::Mode getCurrentIndentationOrFormattingSettings(const Utils::FilePath &filePath) { - const ProjectExplorer::Project *project = ProjectExplorer::SessionManager::projectForFile( + const ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::projectForFile( filePath); return getProjectUseGlobalSettings(project) @@ -264,7 +263,7 @@ Utils::FilePath configForFile(const Utils::FilePath &fileName) return findConfig(fileName); const ProjectExplorer::Project *projectForFile - = ProjectExplorer::SessionManager::projectForFile(fileName); + = ProjectExplorer::ProjectManager::projectForFile(fileName); const TextEditor::ICodeStylePreferences *preferences = projectForFile diff --git a/src/plugins/clangtools/clangtool.cpp b/src/plugins/clangtools/clangtool.cpp index 109f23875c4..00da4bb880e 100644 --- a/src/plugins/clangtools/clangtool.cpp +++ b/src/plugins/clangtools/clangtool.cpp @@ -34,7 +34,7 @@ #include #include #include -#include +#include #include #include @@ -342,7 +342,7 @@ static FileInfos sortedFileInfos(const QVector static RunSettings runSettings() { - if (Project *project = SessionManager::startupProject()) { + if (Project *project = ProjectManager::startupProject()) { const auto projectSettings = ClangToolsProjectSettings::getSettings(project); if (!projectSettings->useGlobalSettings()) return projectSettings->runSettings(); @@ -599,19 +599,18 @@ static bool continueDespiteReleaseBuild(const QString &toolName) "

%2

" "") .arg(problem, question); - return CheckableMessageBox::doNotAskAgainQuestion(ICore::dialogParent(), - title, - message, - ICore::settings(), - "ClangToolsCorrectModeWarning") - == QDialogButtonBox::Yes; + return CheckableMessageBox::question(ICore::dialogParent(), + title, + message, + QString("ClangToolsCorrectModeWarning")) + == QMessageBox::Yes; } void ClangTool::startTool(ClangTool::FileSelection fileSelection, const RunSettings &runSettings, const CppEditor::ClangDiagnosticConfig &diagnosticConfig) { - Project *project = SessionManager::startupProject(); + Project *project = ProjectManager::startupProject(); QTC_ASSERT(project, return); QTC_ASSERT(project->activeTarget(), return); @@ -858,19 +857,19 @@ static CheckResult canAnalyze() { const ClangDiagnosticConfig config = diagnosticConfig(runSettings().diagnosticConfigId()); - if (config.isEnabled(ClangToolType::Tidy) + if (toolEnabled(ClangToolType::Tidy, config, runSettings()) && !toolExecutable(ClangToolType::Tidy).isExecutableFile()) { return {CheckResult::InvalidTidyExecutable, Tr::tr("Set a valid Clang-Tidy executable.")}; } - if (config.isEnabled(ClangToolType::Clazy) + if (toolEnabled(ClangToolType::Clazy, config, runSettings()) && !toolExecutable(ClangToolType::Clazy).isExecutableFile()) { return {CheckResult::InvalidClazyExecutable, Tr::tr("Set a valid Clazy-Standalone executable.")}; } - if (Project *project = SessionManager::startupProject()) { + if (Project *project = ProjectManager::startupProject()) { if (!canAnalyzeProject(project)) { return {CheckResult::ProjectNotSuitable, Tr::tr("Project \"%1\" is not a C/C++ project.") diff --git a/src/plugins/clangtools/clangtoolruncontrol.cpp b/src/plugins/clangtools/clangtoolruncontrol.cpp index e583a6269de..11353b04ae4 100644 --- a/src/plugins/clangtools/clangtoolruncontrol.cpp +++ b/src/plugins/clangtools/clangtoolruncontrol.cpp @@ -21,14 +21,14 @@ #include #include -#include +#include #include -#include #include using namespace CppEditor; using namespace ProjectExplorer; +using namespace Tasking; using namespace Utils; static Q_LOGGING_CATEGORY(LOG, "qtc.clangtools.runcontrol", QtWarningMsg) @@ -183,10 +183,13 @@ void ClangToolRunWorker::start() m_filesAnalyzed.clear(); m_filesNotAnalyzed.clear(); - using namespace Tasking; - QList tasks{ParallelLimit(qMax(1, m_runSettings.parallelJobs()))}; + QList tasks{parallelLimit(qMax(1, m_runSettings.parallelJobs()))}; for (const AnalyzeUnit &unit : std::as_const(unitsToProcess)) { - const AnalyzeInputData input{tool, m_diagnosticConfig, m_temporaryDir.path(), + if (!m_diagnosticConfig.isEnabled(tool) + && !m_runSettings.hasConfigFileForSourceFile(unit.file)) { + continue; + } + const AnalyzeInputData input{tool, m_runSettings, m_diagnosticConfig, m_temporaryDir.path(), m_environment, unit}; const auto setupHandler = [this, unit, tool] { const QString filePath = unit.file.toUserOutput(); diff --git a/src/plugins/clangtools/clangtoolruncontrol.h b/src/plugins/clangtools/clangtoolruncontrol.h index 19ad9189af4..e6204da4ff2 100644 --- a/src/plugins/clangtools/clangtoolruncontrol.h +++ b/src/plugins/clangtools/clangtoolruncontrol.h @@ -15,7 +15,7 @@ #include #include -namespace Utils { class TaskTree; } +namespace Tasking { class TaskTree; } namespace ClangTools { namespace Internal { @@ -67,7 +67,7 @@ private: QString m_targetTriple; Utils::Id m_toolChainType; - std::unique_ptr m_taskTree; + std::unique_ptr m_taskTree; QSet m_projectFiles; QSet m_filesAnalyzed; QSet m_filesNotAnalyzed; diff --git a/src/plugins/clangtools/clangtoolrunner.cpp b/src/plugins/clangtools/clangtoolrunner.cpp index f5c955c50e7..3e2c7a6c111 100644 --- a/src/plugins/clangtools/clangtoolrunner.cpp +++ b/src/plugins/clangtools/clangtoolrunner.cpp @@ -11,8 +11,8 @@ #include #include +#include #include -#include #include #include @@ -52,21 +52,22 @@ static bool isClMode(const QStringList &options) return options.contains("--driver-mode=cl"); } -static QStringList checksArguments(ClangToolType tool, - const ClangDiagnosticConfig &diagnosticConfig) +static QStringList checksArguments(const AnalyzeInputData &input) { - if (tool == ClangToolType::Tidy) { - const ClangDiagnosticConfig::TidyMode tidyMode = diagnosticConfig.clangTidyMode(); - // The argument "-config={}" stops stating/evaluating the .clang-tidy file. - if (tidyMode == ClangDiagnosticConfig::TidyMode::UseDefaultChecks) + if (input.tool == ClangToolType::Tidy) { + if (input.runSettings.hasConfigFileForSourceFile(input.unit.file)) + return {"--warnings-as-errors=-*", "-checks=-clang-diagnostic-*"}; + switch (input.config.clangTidyMode()) { + case ClangDiagnosticConfig::TidyMode::UseDefaultChecks: + // The argument "-config={}" stops stating/evaluating the .clang-tidy file. return {"-config={}", "-checks=-clang-diagnostic-*"}; - if (tidyMode == ClangDiagnosticConfig::TidyMode::UseCustomChecks) - return {"-config=" + diagnosticConfig.clangTidyChecksAsJson()}; - return {"--warnings-as-errors=-*", "-checks=-clang-diagnostic-*"}; + case ClangDiagnosticConfig::TidyMode::UseCustomChecks: + return {"-config=" + input.config.clangTidyChecksAsJson()}; + } } - const QString clazyChecks = diagnosticConfig.checks(ClangToolType::Clazy); + const QString clazyChecks = input.config.checks(ClangToolType::Clazy); if (!clazyChecks.isEmpty()) - return {"-checks=" + diagnosticConfig.checks(ClangToolType::Clazy)}; + return {"-checks=" + input.config.checks(ClangToolType::Clazy)}; return {}; } @@ -111,17 +112,16 @@ TaskItem clangToolTask(const AnalyzeInputData &input, }; const TreeStorage storage; - const auto mainToolArguments = [=](const ClangToolStorage *data) - { + const auto mainToolArguments = [=](const ClangToolStorage &data) { QStringList result; - result << "-export-fixes=" + data->outputFilePath.nativePath(); - if (!input.overlayFilePath.isEmpty() && isVFSOverlaySupported(data->executable)) + result << "-export-fixes=" + data.outputFilePath.nativePath(); + if (!input.overlayFilePath.isEmpty() && isVFSOverlaySupported(data.executable)) result << "--vfsoverlay=" + input.overlayFilePath; result << input.unit.file.nativePath(); return result; }; - const auto onGroupSetup = [=] { + const auto onSetup = [=] { if (setupHandler && !setupHandler()) return TaskAction::StopWithError; @@ -141,54 +141,53 @@ TaskItem clangToolTask(const AnalyzeInputData &input, return TaskAction::Continue; }; - const auto onProcessSetup = [=](QtcProcess &process) { + const auto onProcessSetup = [=](Process &process) { process.setEnvironment(input.environment); process.setUseCtrlCStub(true); process.setLowPriority(); process.setWorkingDirectory(input.outputDirPath); // Current clang-cl puts log file into working dir. - const ClangToolStorage *data = storage.activeStorage(); + const ClangToolStorage &data = *storage; - const QStringList args = checksArguments(input.tool, input.config) + const QStringList args = checksArguments(input) + mainToolArguments(data) + QStringList{"--"} + clangArguments(input.config, input.unit.arguments); - const CommandLine commandLine = {data->executable, args}; + const CommandLine commandLine = {data.executable, args}; qCDebug(LOG).noquote() << "Starting" << commandLine.toUserOutput(); process.setCommand(commandLine); }; - const auto onProcessDone = [=](const QtcProcess &process) { + const auto onProcessDone = [=](const Process &process) { qCDebug(LOG).noquote() << "Output:\n" << process.cleanedStdOut(); if (!outputHandler) return; - const ClangToolStorage *data = storage.activeStorage(); - outputHandler({true, input.unit.file, data->outputFilePath, input.tool}); + outputHandler({true, input.unit.file, storage->outputFilePath, input.tool}); }; - const auto onProcessError = [=](const QtcProcess &process) { + const auto onProcessError = [=](const Process &process) { if (!outputHandler) return; const QString details = Tr::tr("Command line: %1\nProcess Error: %2\nOutput:\n%3") .arg(process.commandLine().toUserOutput()) .arg(process.error()) .arg(process.cleanedStdOut()); - const ClangToolStorage *data = storage.activeStorage(); + const ClangToolStorage &data = *storage; QString message; if (process.result() == ProcessResult::StartFailed) - message = Tr::tr("An error occurred with the %1 process.").arg(data->name); + message = Tr::tr("An error occurred with the %1 process.").arg(data.name); else if (process.result() == ProcessResult::FinishedWithError) - message = Tr::tr("%1 finished with exit code: %2.").arg(data->name).arg(process.exitCode()); + message = Tr::tr("%1 finished with exit code: %2.").arg(data.name).arg(process.exitCode()); else - message = Tr::tr("%1 crashed.").arg(data->name); - outputHandler({false, input.unit.file, data->outputFilePath, input.tool, message, details}); + message = Tr::tr("%1 crashed.").arg(data.name); + outputHandler({false, input.unit.file, data.outputFilePath, input.tool, message, details}); }; const Group group { Storage(storage), - OnGroupSetup(onGroupSetup), + onGroupSetup(onSetup), Group { - optional, - Process(onProcessSetup, onProcessDone, onProcessError) + finishAllAndDone, + ProcessTask(onProcessSetup, onProcessDone, onProcessError) } }; return group; diff --git a/src/plugins/clangtools/clangtoolrunner.h b/src/plugins/clangtools/clangtoolrunner.h index 104e3ab90b6..3a0f59f3417 100644 --- a/src/plugins/clangtools/clangtoolrunner.h +++ b/src/plugins/clangtools/clangtoolrunner.h @@ -4,12 +4,13 @@ #pragma once #include "clangfileinfo.h" +#include "clangtoolssettings.h" #include #include -namespace Utils::Tasking { class TaskItem; } +namespace Tasking { class TaskItem; } namespace ClangTools { namespace Internal { @@ -28,6 +29,7 @@ using AnalyzeUnits = QList; struct AnalyzeInputData { CppEditor::ClangToolType tool = CppEditor::ClangToolType::Tidy; + RunSettings runSettings; CppEditor::ClangDiagnosticConfig config; Utils::FilePath outputDirPath; Utils::Environment environment; @@ -48,9 +50,9 @@ struct AnalyzeOutputData using AnalyzeSetupHandler = std::function; using AnalyzeOutputHandler = std::function; -Utils::Tasking::TaskItem clangToolTask(const AnalyzeInputData &input, - const AnalyzeSetupHandler &setupHandler, - const AnalyzeOutputHandler &outputHandler); +Tasking::TaskItem clangToolTask(const AnalyzeInputData &input, + const AnalyzeSetupHandler &setupHandler, + const AnalyzeOutputHandler &outputHandler); } // namespace Internal } // namespace ClangTools diff --git a/src/plugins/clangtools/clangtools.qbs b/src/plugins/clangtools/clangtools.qbs index 9c86d4bb113..ad8543bf611 100644 --- a/src/plugins/clangtools/clangtools.qbs +++ b/src/plugins/clangtools/clangtools.qbs @@ -74,9 +74,7 @@ QtcPlugin { "virtualfilesystemoverlay.h", ] - Group { - name: "Unit tests" - condition: qtc.testsEnabled + QtcTestFiles { files: [ "clangtoolspreconfiguredsessiontests.cpp", "clangtoolspreconfiguredsessiontests.h", diff --git a/src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp b/src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp index 39329c79f14..e0b797a1e0a 100644 --- a/src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp +++ b/src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp @@ -10,14 +10,14 @@ #include "diagnosticmark.h" #include -#include +#include + #include #include #include #include -#include #include #include @@ -484,8 +484,8 @@ DiagnosticFilterModel::DiagnosticFilterModel(QObject *parent) { // So that when a user closes and re-opens a project and *then* clicks "Suppress", // we enter that information into the project settings. - connect(ProjectExplorer::SessionManager::instance(), - &ProjectExplorer::SessionManager::projectAdded, this, + connect(ProjectExplorer::ProjectManager::instance(), + &ProjectExplorer::ProjectManager::projectAdded, this, [this](ProjectExplorer::Project *project) { if (!m_project && project->projectDirectory() == m_lastProjectDirectory) setProject(project); diff --git a/src/plugins/clangtools/clangtoolsdiagnosticview.cpp b/src/plugins/clangtools/clangtoolsdiagnosticview.cpp index a516ef3066e..ed264b603d8 100644 --- a/src/plugins/clangtools/clangtoolsdiagnosticview.cpp +++ b/src/plugins/clangtools/clangtoolsdiagnosticview.cpp @@ -313,9 +313,8 @@ bool DiagnosticView::disableChecksEnabled() const if (!activeConfig.id().isValid()) return false; - // If all selected diagnostics come from clang-tidy and the active config is controlled - // by a .clang-tidy file, then we do not offer the action. - if (activeConfig.clangTidyMode() != ClangDiagnosticConfig::TidyMode::UseConfigFile) + // If all selected diagnostics come from clang-tidy and we prefer a .clang-tidy file, then we do not offer the action. + if (!settings->runSettings().preferConfigFile()) return true; return Utils::anyOf(indexes, [this](const QModelIndex &index) { return model()->data(index).toString().startsWith("clazy-"); diff --git a/src/plugins/clangtools/clangtoolsplugin.cpp b/src/plugins/clangtools/clangtoolsplugin.cpp index 6bd68d8710a..35086be7aaf 100644 --- a/src/plugins/clangtools/clangtoolsplugin.cpp +++ b/src/plugins/clangtools/clangtoolsplugin.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -171,7 +172,7 @@ void ClangToolsPlugin::registerAnalyzeActions() button->setPopupMode(QToolButton::InstantPopup); button->setIcon(icon); button->setToolTip(Tr::tr("Analyze File...")); - button->setProperty("noArrow", true); + button->setProperty(Utils::StyleHelper::C_NO_ARROW, true); widget->toolBar()->addWidget(button); const auto toolsMenu = new QMenu(widget); button->setMenu(toolsMenu); diff --git a/src/plugins/clangtools/clangtoolspreconfiguredsessiontests.cpp b/src/plugins/clangtools/clangtoolspreconfiguredsessiontests.cpp index 5041dc4dacc..4c858451fa3 100644 --- a/src/plugins/clangtools/clangtoolspreconfiguredsessiontests.cpp +++ b/src/plugins/clangtools/clangtoolspreconfiguredsessiontests.cpp @@ -5,16 +5,18 @@ #include "clangtool.h" #include "clangtoolsdiagnostic.h" -#include "clangtoolsutils.h" #include +#include + #include #include + #include #include #include #include -#include +#include #include #include @@ -29,6 +31,7 @@ #include +using namespace Core; using namespace CppEditor; using namespace ProjectExplorer; using namespace Utils; @@ -55,8 +58,8 @@ public: WaitForParsedProjects(const FilePaths &projects) : m_projectsToWaitFor(projects) { - connect(SessionManager::instance(), - &ProjectExplorer::SessionManager::projectFinishedParsing, + connect(ProjectManager::instance(), + &ProjectExplorer::ProjectManager::projectFinishedParsing, this, &WaitForParsedProjects::onProjectFinishedParsing); } @@ -87,7 +90,7 @@ void PreconfiguredSessionTests::initTestCase() QSKIP("Session must not be already active."); // Load session - const FilePaths projects = SessionManager::projectsForSessionName(preconfiguredSessionName); + const FilePaths projects = ProjectManager::projectsForSessionName(preconfiguredSessionName); WaitForParsedProjects waitForParsedProjects(projects); QVERIFY(SessionManager::loadSession(preconfiguredSessionName)); QVERIFY(waitForParsedProjects.wait()); @@ -102,7 +105,7 @@ void PreconfiguredSessionTests::testPreconfiguredSession() for (ClangTool * const tool : {ClangTidyTool::instance(), ClazyTool::instance()}) { tool->startTool(ClangTool::FileSelectionType::AllFiles); - QSignalSpy waitUntilAnalyzerFinished(tool, SIGNAL(finished(bool))); + QSignalSpy waitUntilAnalyzerFinished(tool, &ClangTool::finished); QVERIFY(waitUntilAnalyzerFinished.wait(30000)); const QList arguments = waitUntilAnalyzerFinished.takeFirst(); const bool analyzerFinishedSuccessfully = arguments.first().toBool(); @@ -175,7 +178,7 @@ void PreconfiguredSessionTests::testPreconfiguredSession_data() bool hasAddedTestData = false; - for (Project *project : validProjects(SessionManager::projects())) { + for (Project *project : validProjects(ProjectManager::projects())) { for (Target *target : validTargets(project)) { hasAddedTestData = true; QTest::newRow(dataTagName(project, target)) << project << target; @@ -189,17 +192,17 @@ void PreconfiguredSessionTests::testPreconfiguredSession_data() bool PreconfiguredSessionTests::switchToProjectAndTarget(Project *project, Target *target) { - Project * const activeProject = SessionManager::startupProject(); + Project * const activeProject = ProjectManager::startupProject(); if (project == activeProject && target == activeProject->activeTarget()) return true; // OK, desired project/target already active. if (project != activeProject) - SessionManager::setStartupProject(project); + ProjectManager::setStartupProject(project); if (target != project->activeTarget()) { - QSignalSpy spyFinishedParsing(ProjectExplorer::SessionManager::instance(), - &ProjectExplorer::SessionManager::projectFinishedParsing); - SessionManager::setActiveTarget(project, target, ProjectExplorer::SetActive::NoCascade); + QSignalSpy spyFinishedParsing(ProjectExplorer::ProjectManager::instance(), + &ProjectExplorer::ProjectManager::projectFinishedParsing); + project->setActiveTarget(target, ProjectExplorer::SetActive::NoCascade); QTC_ASSERT(spyFinishedParsing.wait(30000), return false); const QVariant projectArgument = spyFinishedParsing.takeFirst().constFirst(); diff --git a/src/plugins/clangtools/clangtoolsprojectsettings.cpp b/src/plugins/clangtools/clangtoolsprojectsettings.cpp index cde1731acbd..581a8ca57b6 100644 --- a/src/plugins/clangtools/clangtoolsprojectsettings.cpp +++ b/src/plugins/clangtools/clangtoolsprojectsettings.cpp @@ -4,7 +4,7 @@ #include "clangtoolsprojectsettings.h" #include "clangtoolsdiagnostic.h" -#include +#include #include #include diff --git a/src/plugins/clangtools/clangtoolsprojectsettingswidget.cpp b/src/plugins/clangtools/clangtoolsprojectsettingswidget.cpp index 5fe14872591..8e1ac057f93 100644 --- a/src/plugins/clangtools/clangtoolsprojectsettingswidget.cpp +++ b/src/plugins/clangtools/clangtoolsprojectsettingswidget.cpp @@ -68,7 +68,7 @@ ClangToolsProjectSettingsWidget::ClangToolsProjectSettingsWidget(ProjectExplorer m_removeSelectedButton = new QPushButton(Tr::tr("Remove Selected"), this); m_removeAllButton = new QPushButton(Tr::tr("Remove All")); - using namespace Utils::Layouting; + using namespace Layouting; Column { Row { m_restoreGlobal, st, gotoClangTidyModeLabel, gotoClazyModeLabel }, @@ -84,8 +84,9 @@ ClangToolsProjectSettingsWidget::ClangToolsProjectSettingsWidget(ProjectExplorer st } } - } - }.attachTo(this, WithoutMargins); + }, + noMargin + }.attachTo(this); setUseGlobalSettings(m_projectSettings->useGlobalSettings()); onGlobalCustomChanged(useGlobalSettings()); diff --git a/src/plugins/clangtools/clangtoolssettings.cpp b/src/plugins/clangtools/clangtoolssettings.cpp index eeb24d14579..286b076f95c 100644 --- a/src/plugins/clangtools/clangtoolssettings.cpp +++ b/src/plugins/clangtools/clangtoolssettings.cpp @@ -27,6 +27,7 @@ const char clangTidyExecutableKey[] = "ClangTidyExecutable"; const char clazyStandaloneExecutableKey[] = "ClazyStandaloneExecutable"; const char parallelJobsKey[] = "ParallelJobs"; +const char preferConfigFileKey[] = "PreferConfigFile"; const char buildBeforeAnalysisKey[] = "BuildBeforeAnalysis"; const char analyzeOpenFilesKey[] = "AnalyzeOpenFiles"; const char oldDiagnosticConfigIdKey[] = "diagnosticConfigId"; @@ -46,6 +47,7 @@ void RunSettings::fromMap(const QVariantMap &map, const QString &prefix) { m_diagnosticConfigId = Id::fromSetting(map.value(prefix + diagnosticConfigIdKey)); m_parallelJobs = map.value(prefix + parallelJobsKey).toInt(); + m_preferConfigFile = map.value(prefix + preferConfigFileKey).toBool(); m_buildBeforeAnalysis = map.value(prefix + buildBeforeAnalysisKey).toBool(); m_analyzeOpenFiles = map.value(prefix + analyzeOpenFilesKey).toBool(); } @@ -54,6 +56,7 @@ void RunSettings::toMap(QVariantMap &map, const QString &prefix) const { map.insert(prefix + diagnosticConfigIdKey, m_diagnosticConfigId.toSetting()); map.insert(prefix + parallelJobsKey, m_parallelJobs); + map.insert(prefix + preferConfigFileKey, m_preferConfigFile); map.insert(prefix + buildBeforeAnalysisKey, m_buildBeforeAnalysis); map.insert(prefix + analyzeOpenFilesKey, m_analyzeOpenFiles); } @@ -69,10 +72,23 @@ bool RunSettings::operator==(const RunSettings &other) const { return m_diagnosticConfigId == other.m_diagnosticConfigId && m_parallelJobs == other.m_parallelJobs + && m_preferConfigFile == other.m_preferConfigFile && m_buildBeforeAnalysis == other.m_buildBeforeAnalysis && m_analyzeOpenFiles == other.m_analyzeOpenFiles; } +bool RunSettings::hasConfigFileForSourceFile(const Utils::FilePath &sourceFile) const +{ + if (!preferConfigFile()) + return false; + for (FilePath parentDir = sourceFile.parentDir(); !parentDir.isEmpty(); + parentDir = parentDir.parentDir()) { + if (parentDir.resolvePath(QLatin1String(".clang-tidy")).isReadableFile()) + return true; + } + return false; +} + ClangToolsSettings::ClangToolsSettings() { readSettings(); @@ -139,6 +155,7 @@ void ClangToolsSettings::readSettings() QVariantMap defaults; defaults.insert(diagnosticConfigIdKey, defaultDiagnosticId().toSetting()); defaults.insert(parallelJobsKey, m_runSettings.parallelJobs()); + defaults.insert(preferConfigFileKey, m_runSettings.preferConfigFile()); defaults.insert(buildBeforeAnalysisKey, m_runSettings.buildBeforeAnalysis()); defaults.insert(analyzeOpenFilesKey, m_runSettings.analyzeOpenFiles()); map = defaults; diff --git a/src/plugins/clangtools/clangtoolssettings.h b/src/plugins/clangtools/clangtoolssettings.h index 3ecbfcc30ac..45a2cb032e4 100644 --- a/src/plugins/clangtools/clangtoolssettings.h +++ b/src/plugins/clangtools/clangtoolssettings.h @@ -31,6 +31,9 @@ public: Utils::Id diagnosticConfigId() const; void setDiagnosticConfigId(const Utils::Id &id) { m_diagnosticConfigId = id; } + bool preferConfigFile() const { return m_preferConfigFile; } + void setPreferConfigFile(bool yesno) { m_preferConfigFile = yesno; } + bool buildBeforeAnalysis() const { return m_buildBeforeAnalysis; } void setBuildBeforeAnalysis(bool yesno) { m_buildBeforeAnalysis = yesno; } @@ -42,9 +45,12 @@ public: bool operator==(const RunSettings &other) const; + bool hasConfigFileForSourceFile(const Utils::FilePath &sourceFile) const; + private: Utils::Id m_diagnosticConfigId; int m_parallelJobs = -1; + bool m_preferConfigFile = true; bool m_buildBeforeAnalysis = true; bool m_analyzeOpenFiles = true; }; diff --git a/src/plugins/clangtools/clangtoolsunittests.cpp b/src/plugins/clangtools/clangtoolsunittests.cpp index c8650c0b771..0672d430548 100644 --- a/src/plugins/clangtools/clangtoolsunittests.cpp +++ b/src/plugins/clangtools/clangtoolsunittests.cpp @@ -26,7 +26,6 @@ #include #include -#include #include #include #include diff --git a/src/plugins/clangtools/clangtoolsutils.cpp b/src/plugins/clangtools/clangtoolsutils.cpp index 01dcb614b5a..de3ac2b9cba 100644 --- a/src/plugins/clangtools/clangtoolsutils.cpp +++ b/src/plugins/clangtools/clangtoolsutils.cpp @@ -18,8 +18,8 @@ #include #include #include +#include #include -#include #include @@ -138,12 +138,10 @@ QString hintAboutBuildBeforeAnalysis() void showHintAboutBuildBeforeAnalysis() { - Utils::CheckableMessageBox::doNotShowAgainInformation( - Core::ICore::dialogParent(), - Tr::tr("Info About Build the Project Before Analysis"), - hintAboutBuildBeforeAnalysis(), - Core::ICore::settings(), - "ClangToolsDisablingBuildBeforeAnalysisHint"); + Utils::CheckableMessageBox::information(Core::ICore::dialogParent(), + Tr::tr("Info About Build the Project Before Analysis"), + hintAboutBuildBeforeAnalysis(), + QString("ClangToolsDisablingBuildBeforeAnalysisHint")); } FilePath fullPath(const FilePath &executable) @@ -211,7 +209,7 @@ bool isVFSOverlaySupported(const FilePath &executable) static QMap vfsCapabilities; auto it = vfsCapabilities.find(executable); if (it == vfsCapabilities.end()) { - QtcProcess p; + Process p; p.setCommand({executable, {"--help"}}); p.runBlocking(); it = vfsCapabilities.insert(executable, p.allOutput().contains("vfsoverlay")); @@ -284,7 +282,7 @@ static QStringList extraOptions(const QString &envVar) if (!qtcEnvironmentVariableIsSet(envVar)) return QStringList(); QString arguments = qtcEnvironmentVariable(envVar); - return Utils::ProcessArgs::splitArgs(arguments); + return ProcessArgs::splitArgs(arguments, HostOsInfo::hostOs()); } QStringList extraClangToolsPrependOptions() @@ -294,7 +292,7 @@ QStringList extraClangToolsPrependOptions() static const QStringList options = extraOptions(csaPrependOptions) + extraOptions(toolsPrependOptions); if (!options.isEmpty()) - qWarning() << "ClangTools options are prepended with " << options.toVector(); + qWarning() << "ClangTools options are prepended with " << options; return options; } @@ -305,7 +303,7 @@ QStringList extraClangToolsAppendOptions() static const QStringList options = extraOptions(csaAppendOptions) + extraOptions(toolsAppendOptions); if (!options.isEmpty()) - qWarning() << "ClangTools options are appended with " << options.toVector(); + qWarning() << "ClangTools options are appended with " << options; return options; } @@ -343,5 +341,13 @@ QString clazyDocUrl(const QString &check) return QString::fromLatin1(urlTemplate).arg(versionString, check); } +bool toolEnabled(CppEditor::ClangToolType type, const ClangDiagnosticConfig &config, + const RunSettings &runSettings) +{ + if (type == ClangToolType::Tidy && runSettings.preferConfigFile()) + return true; + return config.isEnabled(type); +} + } // namespace Internal } // namespace ClangTools diff --git a/src/plugins/clangtools/clangtoolsutils.h b/src/plugins/clangtools/clangtoolsutils.h index 6f4f2871a02..36bcc75ff29 100644 --- a/src/plugins/clangtools/clangtoolsutils.h +++ b/src/plugins/clangtools/clangtoolsutils.h @@ -17,6 +17,7 @@ namespace Utils { class FilePath; } namespace ClangTools { namespace Internal { +class RunSettings; QString clangTidyDocUrl(const QString &check); QString clazyDocUrl(const QString &check); @@ -47,6 +48,8 @@ Utils::FilePath toolShippedExecutable(CppEditor::ClangToolType tool); Utils::FilePath toolExecutable(CppEditor::ClangToolType tool); Utils::FilePath toolFallbackExecutable(CppEditor::ClangToolType tool); QString clangToolName(CppEditor::ClangToolType tool); +bool toolEnabled(CppEditor::ClangToolType type, const CppEditor::ClangDiagnosticConfig &config, + const RunSettings &runSettings); bool isVFSOverlaySupported(const Utils::FilePath &executable); diff --git a/src/plugins/clangtools/diagnosticconfigswidget.cpp b/src/plugins/clangtools/diagnosticconfigswidget.cpp index 1d160befa22..57480bf8cd9 100644 --- a/src/plugins/clangtools/diagnosticconfigswidget.cpp +++ b/src/plugins/clangtools/diagnosticconfigswidget.cpp @@ -14,8 +14,8 @@ #include #include +#include #include -#include #include #include @@ -56,7 +56,6 @@ QString removeClazyCheck(const QString &checks, const QString &check); class TidyChecksWidget : public QWidget { public: - QComboBox *tidyMode; QPushButton *plainTextEditButton; FancyLineEdit *filterLineEdit; QTreeView *checksPrefixesTree; @@ -65,10 +64,6 @@ public: TidyChecksWidget() { - tidyMode = new QComboBox; - tidyMode->addItem(Tr::tr("Select Checks")); - tidyMode->addItem(Tr::tr("Use .clang-tidy config file")); - plainTextEditButton = new QPushButton(Tr::tr("Edit Checks as String...")); filterLineEdit = new FancyLineEdit; @@ -95,16 +90,18 @@ public: using namespace Layouting; Column { - checksPrefixesTree - }.attachTo(checksPage, WithoutMargins); + checksPrefixesTree, + noMargin + }.attachTo(checksPage); Column { invalidExecutableLabel, st, - }.attachTo(invalidExecutablePage, WithoutMargins); + noMargin + }.attachTo(invalidExecutablePage); Column { - Row { tidyMode, plainTextEditButton, filterLineEdit }, + Row { plainTextEditButton, filterLineEdit }, stackedWidget, }.attachTo(this); } @@ -174,13 +171,15 @@ public: Column { label, - Row { groupBox, checksGroupBox } - }.attachTo(checksPage, WithoutMargins); + Row { groupBox, checksGroupBox }, + noMargin + }.attachTo(checksPage); Column { invalidExecutableLabel, - st - }.attachTo(invalidExecutablePage, WithoutMargins); + st, + noMargin + }.attachTo(invalidExecutablePage); Column { enableLowerLevelsCheckBox, @@ -1107,32 +1106,18 @@ void DiagnosticConfigsWidget::syncClangTidyWidgets(const ClangDiagnosticConfig & disconnectClangTidyItemChanged(); - const ClangDiagnosticConfig::TidyMode tidyMode = config.clangTidyMode(); - switch (tidyMode) { - case ClangDiagnosticConfig::TidyMode::UseConfigFile: - m_tidyChecks->tidyMode->setCurrentIndex(1); + if (m_tidyInfo.supportedChecks.isEmpty()) { m_tidyChecks->plainTextEditButton->setVisible(false); m_tidyChecks->filterLineEdit->setVisible(false); - m_tidyChecks->stackedWidget->setCurrentIndex(TidyPages::EmptyPage); - break; - case ClangDiagnosticConfig::TidyMode::UseCustomChecks: - case ClangDiagnosticConfig::TidyMode::UseDefaultChecks: - m_tidyChecks->tidyMode->setCurrentIndex(0); - if (m_tidyInfo.supportedChecks.isEmpty()) { - m_tidyChecks->plainTextEditButton->setVisible(false); - m_tidyChecks->filterLineEdit->setVisible(false); - m_tidyChecks->stackedWidget->setCurrentIndex(TidyPages::InvalidExecutablePage); - } else { - m_tidyChecks->plainTextEditButton->setVisible(true); - m_tidyChecks->filterLineEdit->setVisible(true); - m_tidyChecks->stackedWidget->setCurrentIndex(TidyPages::ChecksPage); - syncTidyChecksToTree(config); - } - break; + m_tidyChecks->stackedWidget->setCurrentIndex(TidyPages::InvalidExecutablePage); + } else { + m_tidyChecks->plainTextEditButton->setVisible(true); + m_tidyChecks->filterLineEdit->setVisible(true); + m_tidyChecks->stackedWidget->setCurrentIndex(TidyPages::ChecksPage); + syncTidyChecksToTree(config); } const bool enabled = !config.isReadOnly(); - m_tidyChecks->tidyMode->setEnabled(enabled); m_tidyChecks->plainTextEditButton->setText(enabled ? Tr::tr("Edit Checks as String...") : Tr::tr("View Checks as String...")); m_tidyTreeModel->setEnabled(enabled); @@ -1189,16 +1174,12 @@ void DiagnosticConfigsWidget::syncExtraWidgets(const ClangDiagnosticConfig &conf void DiagnosticConfigsWidget::connectClangTidyItemChanged() { - connect(m_tidyChecks->tidyMode, &QComboBox::currentIndexChanged, - this, &DiagnosticConfigsWidget::onClangTidyModeChanged); connect(m_tidyTreeModel.get(), &TidyChecksTreeModel::dataChanged, this, &DiagnosticConfigsWidget::onClangTidyTreeChanged); } void DiagnosticConfigsWidget::disconnectClangTidyItemChanged() { - disconnect(m_tidyChecks->tidyMode, &QComboBox::currentIndexChanged, - this, &DiagnosticConfigsWidget::onClangTidyModeChanged); disconnect(m_tidyTreeModel.get(), &TidyChecksTreeModel::dataChanged, this, &DiagnosticConfigsWidget::onClangTidyTreeChanged); } @@ -1215,18 +1196,6 @@ void DiagnosticConfigsWidget::disconnectClazyItemChanged() this, &DiagnosticConfigsWidget::onClazyTreeChanged); } -void DiagnosticConfigsWidget::onClangTidyModeChanged(int index) -{ - const ClangDiagnosticConfig::TidyMode tidyMode - = index == 0 ? ClangDiagnosticConfig::TidyMode::UseCustomChecks - : ClangDiagnosticConfig::TidyMode::UseConfigFile; - - ClangDiagnosticConfig config = currentConfig(); - config.setClangTidyMode(tidyMode); - updateConfig(config); - syncClangTidyWidgets(config); -} - void DiagnosticConfigsWidget::onClangTidyTreeChanged() { ClangDiagnosticConfig config = currentConfig(); @@ -1294,7 +1263,7 @@ void disableChecks(const QList &diagnostics) Utils::Id activeConfigId = settings->runSettings().diagnosticConfigId(); ClangToolsProjectSettings::ClangToolsProjectSettingsPtr projectSettings; - if (ProjectExplorer::Project *project = ProjectExplorer::SessionManager::projectForFile( + if (ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::projectForFile( diagnostics.first().location.filePath)) { projectSettings = ClangToolsProjectSettings::getSettings(project); if (!projectSettings->useGlobalSettings()) @@ -1330,7 +1299,7 @@ void disableChecks(const QList &diagnostics) } config.setChecks(ClangToolType::Clazy, removeClazyCheck(config.checks(ClangToolType::Clazy), diag.name)); - } else if (config.clangTidyMode() != ClangDiagnosticConfig::TidyMode::UseConfigFile) { + } else if (!settings->runSettings().preferConfigFile()){ if (config.clangTidyMode() == ClangDiagnosticConfig::TidyMode::UseDefaultChecks) { config.setClangTidyMode(ClangDiagnosticConfig::TidyMode::UseCustomChecks); const ClangTidyInfo tidyInfo(toolExecutable(ClangToolType::Tidy)); diff --git a/src/plugins/clangtools/diagnosticconfigswidget.h b/src/plugins/clangtools/diagnosticconfigswidget.h index 7a3b8927606..04af842f0e7 100644 --- a/src/plugins/clangtools/diagnosticconfigswidget.h +++ b/src/plugins/clangtools/diagnosticconfigswidget.h @@ -41,7 +41,6 @@ private: void syncClazyWidgets(const CppEditor::ClangDiagnosticConfig &config); void syncClazyChecksGroupBox(); - void onClangTidyModeChanged(int index); void onClangTidyTreeChanged(); void onClazyTreeChanged(); diff --git a/src/plugins/clangtools/documentclangtoolrunner.cpp b/src/plugins/clangtools/documentclangtoolrunner.cpp index 81a7e8f5444..3de2c6d55cf 100644 --- a/src/plugins/clangtools/documentclangtoolrunner.cpp +++ b/src/plugins/clangtools/documentclangtoolrunner.cpp @@ -18,14 +18,15 @@ #include #include -#include +#include #include +#include + #include #include #include -#include #include #include @@ -35,6 +36,7 @@ static Q_LOGGING_CATEGORY(LOG, "qtc.clangtools.cftr", QtWarningMsg) using namespace Core; using namespace CppEditor; using namespace ProjectExplorer; +using namespace Tasking; using namespace Utils; namespace ClangTools { @@ -97,8 +99,8 @@ void DocumentClangToolRunner::scheduleRun() static Project *findProject(const FilePath &file) { - Project *project = SessionManager::projectForFile(file); - return project ? project : SessionManager::startupProject(); + Project *project = ProjectManager::projectForFile(file); + return project ? project : ProjectManager::startupProject(); } static VirtualFileSystemOverlay &vfso() @@ -188,23 +190,26 @@ void DocumentClangToolRunner::run() vfso().update(); const ClangDiagnosticConfig config = diagnosticConfig(runSettings.diagnosticConfigId()); const Environment env = projectBuildEnvironment(project); - using namespace Tasking; QList tasks{parallel}; - const auto addClangTool = [this, &config, &env, &tasks](ClangToolType tool) { - if (!config.isEnabled(tool)) + const auto addClangTool = [this, &runSettings, &config, &env, &tasks](ClangToolType tool) { + if (!toolEnabled(tool, config, runSettings)) + return; + if (!config.isEnabled(tool) && !runSettings.hasConfigFileForSourceFile(m_fileInfo.file)) return; const FilePath executable = toolExecutable(tool); + if (executable.isEmpty() || !executable.isExecutableFile()) + return; const auto [includeDir, clangVersion] = getClangIncludeDirAndVersion(executable); - if (!executable.isExecutableFile() || includeDir.isEmpty() || clangVersion.isEmpty()) + if (includeDir.isEmpty() || clangVersion.isEmpty()) return; const AnalyzeUnit unit(m_fileInfo, includeDir, clangVersion); - const AnalyzeInputData input{tool, config, m_temporaryDir.path(), env, unit, + const AnalyzeInputData input{tool, runSettings, config, m_temporaryDir.path(), env, unit, vfso().overlayFilePath().toString()}; const auto setupHandler = [this, executable] { return !m_document->isModified() || isVFSOverlaySupported(executable); }; const auto outputHandler = [this](const AnalyzeOutputData &output) { onDone(output); }; - tasks.append(Group{optional, clangToolTask(input, setupHandler, outputHandler)}); + tasks.append(Group{finishAllAndDone, clangToolTask(input, setupHandler, outputHandler)}); }; addClangTool(ClangToolType::Tidy); addClangTool(ClangToolType::Clazy); diff --git a/src/plugins/clangtools/documentclangtoolrunner.h b/src/plugins/clangtools/documentclangtoolrunner.h index 94111abb37a..e80a9632dce 100644 --- a/src/plugins/clangtools/documentclangtoolrunner.h +++ b/src/plugins/clangtools/documentclangtoolrunner.h @@ -14,8 +14,8 @@ #include namespace Core { class IDocument; } +namespace Tasking { class TaskTree; } namespace TextEditor { class TextEditorWidget; } -namespace Utils { class TaskTree; } namespace ClangTools { namespace Internal { @@ -51,7 +51,7 @@ private: QList> m_editorsWithMarkers; SuppressedDiagnosticsList m_suppressed; Utils::FilePath m_lastProjectDirectory; - std::unique_ptr m_taskTree; + std::unique_ptr m_taskTree; }; } // namespace Internal diff --git a/src/plugins/clangtools/executableinfo.cpp b/src/plugins/clangtools/executableinfo.cpp index 1e280a68894..22898b5734a 100644 --- a/src/plugins/clangtools/executableinfo.cpp +++ b/src/plugins/clangtools/executableinfo.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include #include @@ -26,7 +26,7 @@ static QString runExecutable(const Utils::CommandLine &commandLine, QueryFailMod if (commandLine.executable().isEmpty() || !commandLine.executable().toFileInfo().isExecutable()) return {}; - QtcProcess cpp; + Process cpp; Environment env = Environment::systemEnvironment(); env.setupEnglishOutput(); cpp.setEnvironment(env); diff --git a/src/plugins/clangtools/filterdialog.cpp b/src/plugins/clangtools/filterdialog.cpp index 4c59b44b554..bcb81346d03 100644 --- a/src/plugins/clangtools/filterdialog.cpp +++ b/src/plugins/clangtools/filterdialog.cpp @@ -86,7 +86,7 @@ FilterDialog::FilterDialog(const Checks &checks, QWidget *parent) m_view->setSelectionBehavior(QAbstractItemView::SelectRows); m_view->setIndentation(0); - using namespace Utils::Layouting; + using namespace Layouting; Column { Tr::tr("Select the diagnostics to display."), diff --git a/src/plugins/clangtools/runsettingswidget.cpp b/src/plugins/clangtools/runsettingswidget.cpp index 0e7a34959fd..cd0dc6a1ee3 100644 --- a/src/plugins/clangtools/runsettingswidget.cpp +++ b/src/plugins/clangtools/runsettingswidget.cpp @@ -28,14 +28,10 @@ namespace ClangTools::Internal { RunSettingsWidget::RunSettingsWidget(QWidget *parent) : QWidget(parent) { - resize(383, 125); - m_diagnosticWidget = new ClangDiagnosticConfigsSelectionWidget; - + m_preferConfigFile = new QCheckBox(Tr::tr("Prefer .clang-tidy file, if present")); m_buildBeforeAnalysis = new QCheckBox(Tr::tr("Build the project before analysis")); - m_analyzeOpenFiles = new QCheckBox(Tr::tr("Analyze open files")); - m_parallelJobsSpinBox = new QSpinBox; m_parallelJobsSpinBox->setRange(1, 32); @@ -47,12 +43,14 @@ RunSettingsWidget::RunSettingsWidget(QWidget *parent) title(Tr::tr("Run Options")), Column { m_diagnosticWidget, + m_preferConfigFile, m_buildBeforeAnalysis, m_analyzeOpenFiles, Row { Tr::tr("Parallel jobs:"), m_parallelJobsSpinBox, st }, } - } - }.attachTo(this, WithoutMargins); + }, + noMargin + }.attachTo(this); } RunSettingsWidget::~RunSettingsWidget() = default; @@ -99,6 +97,9 @@ void RunSettingsWidget::fromSettings(const RunSettings &s) connect(m_diagnosticWidget, &ClangDiagnosticConfigsSelectionWidget::changed, this, &RunSettingsWidget::changed); + m_preferConfigFile->setChecked(s.preferConfigFile()); + connect(m_preferConfigFile, &QCheckBox::toggled, this, &RunSettingsWidget::changed); + disconnect(m_buildBeforeAnalysis, 0, 0, 0); m_buildBeforeAnalysis->setToolTip(hintAboutBuildBeforeAnalysis()); m_buildBeforeAnalysis->setCheckState(s.buildBeforeAnalysis() ? Qt::Checked : Qt::Unchecked); @@ -115,13 +116,13 @@ void RunSettingsWidget::fromSettings(const RunSettings &s) connect(m_parallelJobsSpinBox, &QSpinBox::valueChanged, this, &RunSettingsWidget::changed); m_analyzeOpenFiles->setChecked(s.analyzeOpenFiles()); connect(m_analyzeOpenFiles, &QCheckBox::toggled, this, &RunSettingsWidget::changed); - } RunSettings RunSettingsWidget::toSettings() const { RunSettings s; s.setDiagnosticConfigId(m_diagnosticWidget->currentConfigId()); + s.setPreferConfigFile(m_preferConfigFile->isChecked()); s.setBuildBeforeAnalysis(m_buildBeforeAnalysis->checkState() == Qt::CheckState::Checked); s.setParallelJobs(m_parallelJobsSpinBox->value()); s.setAnalyzeOpenFiles(m_analyzeOpenFiles->checkState() == Qt::CheckState::Checked); diff --git a/src/plugins/clangtools/runsettingswidget.h b/src/plugins/clangtools/runsettingswidget.h index 532bbd4ff8c..0d2e4b645a9 100644 --- a/src/plugins/clangtools/runsettingswidget.h +++ b/src/plugins/clangtools/runsettingswidget.h @@ -37,6 +37,7 @@ signals: private: CppEditor::ClangDiagnosticConfigsSelectionWidget *m_diagnosticWidget; + QCheckBox *m_preferConfigFile; QCheckBox *m_buildBeforeAnalysis; QCheckBox *m_analyzeOpenFiles; QSpinBox *m_parallelJobsSpinBox; diff --git a/src/plugins/clangtools/settingswidget.cpp b/src/plugins/clangtools/settingswidget.cpp index 76f915dd99c..80aad561b07 100644 --- a/src/plugins/clangtools/settingswidget.cpp +++ b/src/plugins/clangtools/settingswidget.cpp @@ -34,8 +34,6 @@ SettingsWidget::SettingsWidget() { m_instance = this; - resize(400, 300); - auto createPathChooser = [this](ClangToolType tool) { const QString placeHolderText = toolShippedExecutable(tool).toUserOutput(); @@ -53,6 +51,7 @@ SettingsWidget::SettingsWidget() pathChooser->setHistoryCompleter(tool == ClangToolType::Tidy ? QString("ClangTools.ClangTidyExecutable.History") : QString("ClangTools.ClazyStandaloneExecutable.History")); + pathChooser->setCommandVersionArguments({"--version"}); return pathChooser; }; m_clangTidyPathChooser = createPathChooser(ClangToolType::Tidy); diff --git a/src/plugins/clangtools/unit-tests/exported-diagnostics/CMakeLists.txt b/src/plugins/clangtools/unit-tests/exported-diagnostics/CMakeLists.txt index 30efc996911..98f9833492d 100644 --- a/src/plugins/clangtools/unit-tests/exported-diagnostics/CMakeLists.txt +++ b/src/plugins/clangtools/unit-tests/exported-diagnostics/CMakeLists.txt @@ -11,7 +11,7 @@ set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(Qt5 COMPONENTS Widgets REQUIRED) +find_package(Qt6 COMPONENTS Widgets REQUIRED) add_executable(clangtools main.cpp diff --git a/src/plugins/classview/classviewmanager.cpp b/src/plugins/classview/classviewmanager.cpp index a98cec70442..90c834a6216 100644 --- a/src/plugins/classview/classviewmanager.cpp +++ b/src/plugins/classview/classviewmanager.cpp @@ -8,8 +8,11 @@ #include #include + #include -#include + +#include + #include #include @@ -90,7 +93,7 @@ void ManagerPrivate::resetParser() cancelScheduledUpdate(); QHash> projectData; - for (const Project *project : SessionManager::projects()) { + for (const Project *project : ProjectManager::projects()) { projectData.insert(project->projectFilePath(), {project->displayName(), project->files(Project::SourceFiles)}); } @@ -201,8 +204,8 @@ void Manager::initialize() d->m_timer.setSingleShot(true); // connections to enable/disable navi widget factory - SessionManager *sessionManager = SessionManager::instance(); - connect(sessionManager, &SessionManager::projectAdded, + ProjectManager *sessionManager = ProjectManager::instance(); + connect(sessionManager, &ProjectManager::projectAdded, this, [this](Project *project) { const FilePath projectPath = project->projectFilePath(); const QString projectName = project->displayName(); @@ -211,7 +214,7 @@ void Manager::initialize() d->m_parser->addProject(projectPath, projectName, projectFiles); }, Qt::QueuedConnection); }); - connect(sessionManager, &SessionManager::projectRemoved, + connect(sessionManager, &ProjectManager::projectRemoved, this, [this](Project *project) { const FilePath projectPath = project->projectFilePath(); QMetaObject::invokeMethod(d->m_parser, [this, projectPath]() { @@ -375,7 +378,7 @@ void Manager::gotoLocations(const QList &list) int line; int column; textEditor->convertPosition(textEditor->position(), &line, &column); - const SymbolLocation current(filePath, line, column); + const SymbolLocation current(filePath, line, column + 1); if (auto it = locations.constFind(current), end = locations.constEnd(); it != end) { // we already are at the symbol, cycle to next location ++it; diff --git a/src/plugins/classview/classviewparsertreeitem.cpp b/src/plugins/classview/classviewparsertreeitem.cpp index 1d97d0c013b..787b63b3c84 100644 --- a/src/plugins/classview/classviewparsertreeitem.cpp +++ b/src/plugins/classview/classviewparsertreeitem.cpp @@ -7,9 +7,11 @@ #include #include + #include +#include #include -#include +#include #include #include @@ -261,7 +263,7 @@ bool ParserTreeItem::canFetchMore(QStandardItem *item) const */ void ParserTreeItem::fetchMore(QStandardItem *item) const { - using ProjectExplorer::SessionManager; + using ProjectExplorer::ProjectManager; if (!item) return; @@ -283,7 +285,7 @@ void ParserTreeItem::fetchMore(QStandardItem *item) const // icon const Utils::FilePath &filePath = ptr->projectFilePath(); if (!filePath.isEmpty()) { - ProjectExplorer::Project *project = SessionManager::projectForFile(filePath); + ProjectExplorer::Project *project = ProjectManager::projectForFile(filePath); if (project) add->setIcon(project->containerNode()->icon()); } diff --git a/src/plugins/clearcase/checkoutdialog.cpp b/src/plugins/clearcase/checkoutdialog.cpp index 8c3a5b82041..df8c43a4e1a 100644 --- a/src/plugins/clearcase/checkoutdialog.cpp +++ b/src/plugins/clearcase/checkoutdialog.cpp @@ -48,7 +48,7 @@ CheckOutDialog::CheckOutDialog(const QString &fileName, bool isUcm, bool showCom auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); - using namespace Utils::Layouting; + using namespace Layouting; Column { lblFileName, @@ -67,7 +67,7 @@ CheckOutDialog::CheckOutDialog(const QString &fileName, bool isUcm, bool showCom m_actSelector = new ActivitySelector(this); m_verticalLayout->insertWidget(0, m_actSelector); - m_verticalLayout->insertWidget(1, Utils::Layouting::createHr()); + m_verticalLayout->insertWidget(1, Layouting::createHr()); } if (!showComment) diff --git a/src/plugins/clearcase/clearcaseplugin.cpp b/src/plugins/clearcase/clearcaseplugin.cpp index de884452be6..943ffa194a4 100644 --- a/src/plugins/clearcase/clearcaseplugin.cpp +++ b/src/plugins/clearcase/clearcaseplugin.cpp @@ -27,16 +27,16 @@ #include #include -#include +#include #include +#include #include #include #include #include +#include #include -#include -#include #include #include @@ -57,7 +57,6 @@ #include #include #include -#include #include #include #include @@ -228,6 +227,10 @@ private: Q_INVOKABLE void updateStatusActions(); QString commitDisplayName() const final; + QString commitAbortTitle() const final; + QString commitAbortMessage() const final; + QString commitErrorMessage(const QString &error) const final; + void checkOutCurrentFile(); void addCurrentFile(); void undoCheckOutCurrent(); @@ -259,7 +262,7 @@ private: CommandResult runCleartool(const FilePath &workingDir, const QStringList &arguments, VcsBase::RunFlags flags = VcsBase::RunFlags::None, QTextCodec *codec = nullptr, int timeoutMultiplier = 1) const; - static void sync(QFutureInterface &future, QStringList files); + static void sync(QPromise &promise, QStringList files); void history(const FilePath &workingDir, const QStringList &file = {}, @@ -488,7 +491,7 @@ FileStatus::Status ClearCasePluginPrivate::getFileStatus(const QString &fileName /// "cleartool pwv" returns the values for "set view" and "working directory view", also for /// snapshot views. /// -/// \returns The ClearCase topLevel/VOB directory for this directory +/// Returns the ClearCase topLevel/VOB directory for this directory. QString ClearCasePluginPrivate::ccManagesDirectory(const FilePath &directory) const { const CommandResult result = runCleartoolProc(directory, {"pwv"}); @@ -595,7 +598,7 @@ ClearCasePluginPrivate::ClearCasePluginPrivate() m_settings.fromSettings(ICore::settings()); // update view name when changing active project - connect(SessionManager::instance(), &SessionManager::startupProjectChanged, + connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, this, &ClearCasePluginPrivate::projectChanged); const QString description = QLatin1String("ClearCase"); @@ -949,9 +952,27 @@ void ClearCasePluginPrivate::updateActions(VcsBasePluginPrivate::ActionState as) QString ClearCasePluginPrivate::commitDisplayName() const { + //: Name of the "commit" action of the VCS return Tr::tr("Check In"); } +QString ClearCasePluginPrivate::commitAbortTitle() const +{ + return Tr::tr("Close Check In Editor"); +} + +QString ClearCasePluginPrivate::commitAbortMessage() const +{ + return Tr::tr("Closing this editor will abort the check in."); +} + +QString ClearCasePluginPrivate::commitErrorMessage(const QString &error) const +{ + if (error.isEmpty()) + return Tr::tr("Cannot check in."); + return Tr::tr("Cannot check in: %1.").arg(error); +} + void ClearCasePluginPrivate::checkOutCurrentFile() { const VcsBasePluginState state = currentState(); @@ -1575,7 +1596,7 @@ CommandResult ClearCasePluginPrivate::runCleartoolProc(const FilePath &workingDi if (m_settings.ccBinaryPath.isEmpty()) return CommandResult(ProcessResult::StartFailed, Tr::tr("No ClearCase executable specified.")); - QtcProcess process; + Process process; Environment env = Environment::systemEnvironment(); VcsBase::setProcessEnvironment(&env); process.setEnvironment(env); @@ -1657,7 +1678,7 @@ bool ClearCasePluginPrivate::vcsOpen(const FilePath &workingDir, const QString & if (!m_settings.disableIndexer && (fi.isWritable() || vcsStatus(absPath).status == FileStatus::Unknown)) - runAsync(sync, QStringList(absPath)).waitForFinished(); + Utils::asyncRun(sync, QStringList(absPath)).waitForFinished(); if (vcsStatus(absPath).status == FileStatus::CheckedOut) { QMessageBox::information(ICore::dialogParent(), Tr::tr("ClearCase Checkout"), Tr::tr("File is already checked out.")); @@ -2124,12 +2145,13 @@ void ClearCasePluginPrivate::updateIndex() { QTC_ASSERT(currentState().hasTopLevel(), return); ProgressManager::cancelTasks(ClearCase::Constants::TASK_INDEX); - Project *project = SessionManager::startupProject(); + Project *project = ProjectManager::startupProject(); if (!project) return; m_checkInAllAction->setEnabled(false); m_statusMap->clear(); - QFuture result = runAsync(sync, transform(project->files(Project::SourceFiles), &FilePath::toString)); + QFuture result = Utils::asyncRun(sync, transform(project->files(Project::SourceFiles), + &FilePath::toString)); if (!m_settings.disableIndexer) ProgressManager::addTask(result, Tr::tr("Updating ClearCase Index"), ClearCase::Constants::TASK_INDEX); } @@ -2232,7 +2254,7 @@ void ClearCasePluginPrivate::diffGraphical(const QString &file1, const QString & args << file1; if (!pred) args << file2; - QtcProcess::startDetached({m_settings.ccBinaryPath, args}, m_topLevel); + Process::startDetached({m_settings.ccBinaryPath, args}, m_topLevel); } QString ClearCasePluginPrivate::runExtDiff(const FilePath &workingDir, const QStringList &arguments, @@ -2242,7 +2264,7 @@ QString ClearCasePluginPrivate::runExtDiff(const FilePath &workingDir, const QSt diff.addArgs(m_settings.diffArgs.split(' ', Qt::SkipEmptyParts)); diff.addArgs(arguments); - QtcProcess process; + Process process; process.setTimeoutS(timeOutS); process.setWorkingDirectory(workingDir); process.setCodec(outputCodec ? outputCodec : QTextCodec::codecForName("UTF-8")); @@ -2261,7 +2283,7 @@ void ClearCasePluginPrivate::syncSlot() FilePath topLevel = state.topLevel(); if (topLevel != state.currentProjectTopLevel()) return; - runAsync(sync, QStringList()); + Utils::asyncRun(sync, QStringList()); // TODO: make use of returned QFuture } void ClearCasePluginPrivate::closing() @@ -2271,12 +2293,12 @@ void ClearCasePluginPrivate::closing() disconnect(qApp, &QApplication::applicationStateChanged, nullptr, nullptr); } -void ClearCasePluginPrivate::sync(QFutureInterface &future, QStringList files) +void ClearCasePluginPrivate::sync(QPromise &promise, QStringList files) { ClearCasePluginPrivate *plugin = ClearCasePluginPrivate::instance(); ClearCaseSync ccSync(plugin->m_statusMap); connect(&ccSync, &ClearCaseSync::updateStreamAndView, plugin, &ClearCasePluginPrivate::updateStreamAndView); - ccSync.run(future, files); + ccSync.run(promise, files); } QString ClearCasePluginPrivate::displayName() const diff --git a/src/plugins/clearcase/clearcasesubmiteditorwidget.cpp b/src/plugins/clearcase/clearcasesubmiteditorwidget.cpp index 44c3fd1b44c..8708e566f3b 100644 --- a/src/plugins/clearcase/clearcasesubmiteditorwidget.cpp +++ b/src/plugins/clearcase/clearcasesubmiteditorwidget.cpp @@ -69,7 +69,7 @@ void ClearCaseSubmitEditorWidget::addActivitySelector(bool isUcm) m_actSelector = new ActivitySelector; m_verticalLayout->insertWidget(0, m_actSelector); - m_verticalLayout->insertWidget(1, Utils::Layouting::createHr()); + m_verticalLayout->insertWidget(1, Layouting::createHr()); } QString ClearCaseSubmitEditorWidget::commitName() const diff --git a/src/plugins/clearcase/clearcasesync.cpp b/src/plugins/clearcase/clearcasesync.cpp index 388ce1a8c18..8476d1f04ac 100644 --- a/src/plugins/clearcase/clearcasesync.cpp +++ b/src/plugins/clearcase/clearcasesync.cpp @@ -6,12 +6,13 @@ #include "clearcasesettings.h" #include -#include #include #include +#include #include -#include + +#include #ifdef WITH_TESTS #include @@ -22,13 +23,12 @@ using namespace Utils; namespace ClearCase::Internal { -static void runProcess(QFutureInterface &future, - const ClearCaseSettings &settings, +static void runProcess(QPromise &promise, const ClearCaseSettings &settings, const QStringList &args, std::function processLine) { const QString viewRoot = ClearCasePlugin::viewData().root; - QtcProcess process; + Process process; process.setWorkingDirectory(FilePath::fromString(viewRoot)); process.setCommand({settings.ccBinaryPath, args}); process.start(); @@ -37,7 +37,7 @@ static void runProcess(QFutureInterface &future, int processed = 0; QString buffer; - while (process.waitForReadyRead() && !future.isCanceled()) { + while (process.waitForReadyRead() && !promise.isCanceled()) { buffer += QString::fromLocal8Bit(process.readAllRawStandardOutput()); int index = buffer.indexOf('\n'); while (index != -1) { @@ -135,7 +135,7 @@ void ClearCaseSync::updateStatusForNotManagedFiles(const QStringList &files) } } -void ClearCaseSync::syncSnapshotView(QFutureInterface &future, QStringList &files, +void ClearCaseSync::syncSnapshotView(QPromise &promise, QStringList &files, const ClearCaseSettings &settings) { const QString view = ClearCasePlugin::viewData().name; @@ -167,18 +167,18 @@ void ClearCaseSync::syncSnapshotView(QFutureInterface &future, QStringList // adding 1 for initial sync in which total is not accurate, to prevent finishing // (we don't want it to become green) - future.setProgressRange(0, totalFileCount + 1); + promise.setProgressRange(0, totalFileCount + 1); int totalProcessed = 0; - runProcess(future, settings, args, [&](const QString &buffer, int processed) { + runProcess(promise, settings, args, [&](const QString &buffer, int processed) { processCleartoolLsLine(viewRootDir, buffer); - future.setProgressValue(qMin(totalFileCount, processed)); + promise.setProgressValue(qMin(totalFileCount, processed)); totalProcessed = processed; }); - if (!future.isCanceled()) { + if (!promise.isCanceled()) { updateStatusForNotManagedFiles(files); - future.setProgressValue(totalFileCount + 1); + promise.setProgressValue(totalFileCount + 1); if (!hot) updateTotalFilesCount(view, settings, totalProcessed); } @@ -193,21 +193,20 @@ void ClearCaseSync::processCleartoolLscheckoutLine(const QString &buffer) /// /// Update the file status for dynamic views. /// -void ClearCaseSync::syncDynamicView(QFutureInterface &future, - const ClearCaseSettings& settings) +void ClearCaseSync::syncDynamicView(QPromise &promise, const ClearCaseSettings& settings) { // Always invalidate status for all files invalidateStatusAllFiles(); const QStringList args({"lscheckout", "-avobs", "-me", "-cview", "-s"}); - runProcess(future, settings, args, [&](const QString &buffer, int processed) { + runProcess(promise, settings, args, [&](const QString &buffer, int processed) { processCleartoolLscheckoutLine(buffer); - future.setProgressValue(processed); + promise.setProgressValue(processed); }); } -void ClearCaseSync::run(QFutureInterface &future, QStringList &files) +void ClearCaseSync::run(QPromise &promise, QStringList &files) { ClearCaseSettings settings = ClearCasePlugin::settings(); if (settings.disableIndexer) @@ -225,9 +224,9 @@ void ClearCaseSync::run(QFutureInterface &future, QStringList &files) emit updateStreamAndView(); if (ClearCasePlugin::viewData().isDynamic) - syncDynamicView(future, settings); + syncDynamicView(promise, settings); else - syncSnapshotView(future, files, settings); + syncSnapshotView(promise, files, settings); } #ifdef WITH_TESTS diff --git a/src/plugins/clearcase/clearcasesync.h b/src/plugins/clearcase/clearcasesync.h index a008dabb600..4b69985b0c7 100644 --- a/src/plugins/clearcase/clearcasesync.h +++ b/src/plugins/clearcase/clearcasesync.h @@ -8,7 +8,7 @@ QT_BEGIN_NAMESPACE class QDir; template -class QFutureInterface; +class QPromise; QT_END_NAMESPACE namespace ClearCase::Internal { @@ -18,7 +18,7 @@ class ClearCaseSync : public QObject Q_OBJECT public: explicit ClearCaseSync(QSharedPointer statusMap); - void run(QFutureInterface &future, QStringList &files); + void run(QPromise &promise, QStringList &files); QStringList updateStatusHotFiles(const QString &viewRoot, int &total); void invalidateStatus(const QDir &viewRootDir, const QStringList &files); @@ -28,9 +28,8 @@ public: const int processed); void updateStatusForNotManagedFiles(const QStringList &files); - void syncDynamicView(QFutureInterface &future, - const ClearCaseSettings &settings); - void syncSnapshotView(QFutureInterface &future, QStringList &files, + void syncDynamicView(QPromise &promise, const ClearCaseSettings &settings); + void syncSnapshotView(QPromise &promise, QStringList &files, const ClearCaseSettings &settings); void processCleartoolLscheckoutLine(const QString &buffer); diff --git a/src/plugins/clearcase/settingspage.cpp b/src/plugins/clearcase/settingspage.cpp index bcbab9f16ae..1ff8e48264d 100644 --- a/src/plugins/clearcase/settingspage.cpp +++ b/src/plugins/clearcase/settingspage.cpp @@ -50,8 +50,6 @@ private: SettingsPageWidget::SettingsPageWidget() { - resize(512, 589); - commandPathChooser = new PathChooser; commandPathChooser->setPromptDialogTitle(Tr::tr("ClearCase Command")); commandPathChooser->setExpectedKind(PathChooser::ExistingCommand); @@ -109,8 +107,8 @@ SettingsPageWidget::SettingsPageWidget() using namespace Layouting; Form { - Tr::tr("Arg&uments:"), diffArgsEdit - }.attachTo(diffWidget, WithoutMargins); + Tr::tr("Arg&uments:"), diffArgsEdit, noMargin + }.attachTo(diffWidget); Column { Group { diff --git a/src/plugins/clearcase/versionselector.cpp b/src/plugins/clearcase/versionselector.cpp index 2c0d46a6a30..4d3dd16059f 100644 --- a/src/plugins/clearcase/versionselector.cpp +++ b/src/plugins/clearcase/versionselector.cpp @@ -51,7 +51,7 @@ VersionSelector::VersionSelector(const QString &fileName, const QString &message "the changes (not supported by the plugin)") + "

"); - using namespace Utils::Layouting; + using namespace Layouting; Column { headerLabel, diff --git a/src/plugins/cmakeprojectmanager/3rdparty/cmake/Copyright.txt b/src/plugins/cmakeprojectmanager/3rdparty/cmake/Copyright.txt new file mode 100644 index 00000000000..515e403a2d4 --- /dev/null +++ b/src/plugins/cmakeprojectmanager/3rdparty/cmake/Copyright.txt @@ -0,0 +1,136 @@ +CMake - Cross Platform Makefile Generator +Copyright 2000-2023 Kitware, Inc. and Contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +* Neither the name of Kitware, Inc. nor the names of Contributors + may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------------------------ + +The following individuals and institutions are among the Contributors: + +* Aaron C. Meadows +* Adriaan de Groot +* Aleksey Avdeev +* Alexander Neundorf +* Alexander Smorkalov +* Alexey Sokolov +* Alex Merry +* Alex Turbov +* Andreas Pakulat +* Andreas Schneider +* André Rigland Brodtkorb +* Axel Huebl, Helmholtz-Zentrum Dresden - Rossendorf +* Benjamin Eikel +* Bjoern Ricks +* Brad Hards +* Christopher Harvey +* Christoph Grüninger +* Clement Creusot +* Daniel Blezek +* Daniel Pfeifer +* Dawid Wróbel +* Enrico Scholz +* Eran Ifrah +* Esben Mose Hansen, Ange Optimization ApS +* Geoffrey Viola +* Google Inc +* Gregor Jasny +* Helio Chissini de Castro +* Ilya Lavrenov +* Insight Software Consortium +* Intel Corporation +* Jan Woetzel +* Jordan Williams +* Julien Schueller +* Kelly Thompson +* Konstantin Podsvirov +* Laurent Montel +* Mario Bensi +* Martin Gräßlin +* Mathieu Malaterre +* Matthaeus G. Chajdas +* Matthias Kretz +* Matthias Maennich +* Michael Hirsch, Ph.D. +* Michael Stürmer +* Miguel A. Figueroa-Villanueva +* Mike Durso +* Mike Jackson +* Mike McQuaid +* Nicolas Bock +* Nicolas Despres +* Nikita Krupen'ko +* NVIDIA Corporation +* OpenGamma Ltd. +* Patrick Stotko +* Per Øyvind Karlsen +* Peter Collingbourne +* Petr Gotthard +* Philip Lowman +* Philippe Proulx +* Raffi Enficiaud, Max Planck Society +* Raumfeld +* Roger Leigh +* Rolf Eike Beer +* Roman Donchenko +* Roman Kharitonov +* Ruslan Baratov +* Sebastian Holtermann +* Stephen Kelly +* Sylvain Joubert +* The Qt Company Ltd. +* Thomas Sondergaard +* Tobias Hunger +* Todd Gamblin +* Tristan Carel +* University of Dundee +* Vadim Zhukov +* Will Dicharry + +See version control history for details of individual contributions. + +The above copyright and license notice applies to distributions of +CMake in source and binary form. Third-party software packages supplied +with CMake under compatible licenses provide their own copyright notices +documented in corresponding subdirectories or source files. + +------------------------------------------------------------------------------ + +CMake was initially developed by Kitware with the following sponsorship: + + * National Library of Medicine at the National Institutes of Health + as part of the Insight Segmentation and Registration Toolkit (ITK). + + * US National Labs (Los Alamos, Livermore, Sandia) ASC Parallel + Visualization Initiative. + + * National Alliance for Medical Image Computing (NAMIC) is funded by the + National Institutes of Health through the NIH Roadmap for Medical Research, + Grant U54 EB005149. + + * Kitware, Inc. diff --git a/src/plugins/cmakeprojectmanager/3rdparty/cmake/README.md b/src/plugins/cmakeprojectmanager/3rdparty/cmake/README.md new file mode 100644 index 00000000000..92a4d869a66 --- /dev/null +++ b/src/plugins/cmakeprojectmanager/3rdparty/cmake/README.md @@ -0,0 +1,4 @@ +Files taken from the CMake repository https://gitlab.kitware.com/cmake/cmake.git + +624461526f4707a2406ebbd40245a605b6bd41fa (tag: v3.26.3) + diff --git a/src/plugins/cmakeprojectmanager/3rdparty/cmake/cmListFileCache.cxx b/src/plugins/cmakeprojectmanager/3rdparty/cmake/cmListFileCache.cxx new file mode 100644 index 00000000000..13fd40ca645 --- /dev/null +++ b/src/plugins/cmakeprojectmanager/3rdparty/cmake/cmListFileCache.cxx @@ -0,0 +1,235 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#define cmListFileCache_cxx +#include "cmListFileCache.h" + +#include +#include +#include + +#include "cmListFileLexer.h" + +struct cmListFileParser +{ + cmListFileParser(cmListFile* lf, std::string &error); + ~cmListFileParser(); + cmListFileParser(const cmListFileParser&) = delete; + cmListFileParser& operator=(const cmListFileParser&) = delete; + void IssueError(std::string const& text) const; + bool ParseFile(const char* filename); + bool ParseString(const std::string &str, const std::string &virtual_filename); + bool Parse(); + bool ParseFunction(const char* name, long line); + bool AddArgument(cmListFileLexer_Token* token, + cmListFileArgument::Delimiter delim); + cmListFile* ListFile; + cmListFileLexer* Lexer; + std::string FunctionName; + long FunctionLine; + long FunctionLineEnd; + std::vector FunctionArguments; + std::string &Error; + enum + { + SeparationOkay, + SeparationWarning, + SeparationError + } Separation; +}; + +cmListFileParser::cmListFileParser(cmListFile *lf, std::string &error) + : ListFile(lf) + , Lexer(cmListFileLexer_New()) + , Error(error) +{ +} + +cmListFileParser::~cmListFileParser() +{ + cmListFileLexer_Delete(this->Lexer); +} + +void cmListFileParser::IssueError(const std::string& text) const +{ + Error += text; + Error += "\n"; +} + +bool cmListFileParser::ParseString(const std::string &str, + const std::string &/*virtual_filename*/) +{ + if (!cmListFileLexer_SetString(this->Lexer, str.c_str(), (int)str.size())) { + this->IssueError("cmListFileCache: cannot allocate buffer."); + return false; + } + + return this->Parse(); +} + +bool cmListFileParser::Parse() +{ + // Use a simple recursive-descent parser to process the token + // stream. + bool haveNewline = true; + while (cmListFileLexer_Token* token = cmListFileLexer_Scan(this->Lexer)) { + if (token->type == cmListFileLexer_Token_Space) { + } else if (token->type == cmListFileLexer_Token_Newline) { + haveNewline = true; + } else if (token->type == cmListFileLexer_Token_CommentBracket) { + haveNewline = false; + } else if (token->type == cmListFileLexer_Token_Identifier) { + if (haveNewline) { + haveNewline = false; + if (this->ParseFunction(token->text, token->line)) { + this->ListFile->Functions.emplace_back( + std::move(this->FunctionName), this->FunctionLine, + this->FunctionLineEnd, std::move(this->FunctionArguments)); + } else { + return false; + } + } else { + std::ostringstream error; + error << "Parse error. Expected a newline, got " + << cmListFileLexer_GetTypeAsString(this->Lexer, token->type) + << " with text \"" << token->text << "\"."; + this->IssueError(error.str()); + return false; + } + } else { + std::ostringstream error; + error << "Parse error. Expected a command name, got " + << cmListFileLexer_GetTypeAsString(this->Lexer, token->type) + << " with text \"" << token->text << "\"."; + this->IssueError(error.str()); + return false; + } + } + + return true; +} + +bool cmListFile::ParseString(const std::string &str, const std::string& virtual_filename, std::string &error) +{ + bool parseError = false; + + { + cmListFileParser parser(this, error); + parseError = !parser.ParseString(str, virtual_filename); + } + + return !parseError; +} + +bool cmListFileParser::ParseFunction(const char* name, long line) +{ + // Ininitialize a new function call. + this->FunctionName = name; + this->FunctionLine = line; + + // Command name has already been parsed. Read the left paren. + cmListFileLexer_Token* token; + while ((token = cmListFileLexer_Scan(this->Lexer)) && + token->type == cmListFileLexer_Token_Space) { + } + if (!token) { + std::ostringstream error; + /* clang-format off */ + error << "Unexpected end of file.\n" + << "Parse error. Function missing opening \"(\"."; + /* clang-format on */ + this->IssueError(error.str()); + return false; + } + if (token->type != cmListFileLexer_Token_ParenLeft) { + std::ostringstream error; + error << "Parse error. Expected \"(\", got " + << cmListFileLexer_GetTypeAsString(this->Lexer, token->type) + << " with text \"" << token->text << "\"."; + this->IssueError(error.str()); + return false; + } + + // Arguments. + unsigned long parenDepth = 0; + this->Separation = SeparationOkay; + while ((token = cmListFileLexer_Scan(this->Lexer))) { + if (token->type == cmListFileLexer_Token_Space || + token->type == cmListFileLexer_Token_Newline) { + this->Separation = SeparationOkay; + continue; + } + if (token->type == cmListFileLexer_Token_ParenLeft) { + parenDepth++; + this->Separation = SeparationOkay; + if (!this->AddArgument(token, cmListFileArgument::Unquoted)) { + return false; + } + } else if (token->type == cmListFileLexer_Token_ParenRight) { + if (parenDepth == 0) { + this->FunctionLineEnd = token->line; + return true; + } + parenDepth--; + this->Separation = SeparationOkay; + if (!this->AddArgument(token, cmListFileArgument::Unquoted)) { + return false; + } + this->Separation = SeparationWarning; + } else if (token->type == cmListFileLexer_Token_Identifier || + token->type == cmListFileLexer_Token_ArgumentUnquoted) { + if (!this->AddArgument(token, cmListFileArgument::Unquoted)) { + return false; + } + this->Separation = SeparationWarning; + } else if (token->type == cmListFileLexer_Token_ArgumentQuoted) { + if (!this->AddArgument(token, cmListFileArgument::Quoted)) { + return false; + } + this->Separation = SeparationWarning; + } else if (token->type == cmListFileLexer_Token_ArgumentBracket) { + if (!this->AddArgument(token, cmListFileArgument::Bracket)) { + return false; + } + this->Separation = SeparationError; + } else if (token->type == cmListFileLexer_Token_CommentBracket) { + this->Separation = SeparationError; + } else { + // Error. + std::ostringstream error; + error << "Parse error. Function missing ending \")\". " + << "Instead found " + << cmListFileLexer_GetTypeAsString(this->Lexer, token->type) + << " with text \"" << token->text << "\"."; + this->IssueError(error.str()); + return false; + } + } + + std::ostringstream error; + error << "Parse error. Function missing ending \")\". " + << "End of file reached."; + IssueError(error.str()); + return false; +} + +bool cmListFileParser::AddArgument(cmListFileLexer_Token* token, + cmListFileArgument::Delimiter delim) +{ + this->FunctionArguments.emplace_back(token->text, delim, token->line, token->column); + if (this->Separation == SeparationOkay) { + return true; + } + bool isError = (this->Separation == SeparationError || + delim == cmListFileArgument::Bracket); + std::ostringstream m; + + m << "Syntax " << (isError ? "Error" : "Warning") << " in cmake code at " + << "column " << token->column << "\n" + << "Argument not separated from preceding token by whitespace."; + /* clang-format on */ + if (isError) { + IssueError(m.str()); + return false; + } + return true; +} diff --git a/src/plugins/cmakeprojectmanager/3rdparty/cmake/cmListFileCache.h b/src/plugins/cmakeprojectmanager/3rdparty/cmake/cmListFileCache.h new file mode 100644 index 00000000000..4b419e7a5a4 --- /dev/null +++ b/src/plugins/cmakeprojectmanager/3rdparty/cmake/cmListFileCache.h @@ -0,0 +1,114 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include +#include +#include +#include +#include +#include + +/** \class cmListFileCache + * \brief A class to cache list file contents. + * + * cmListFileCache is a class used to cache the contents of parsed + * cmake list files. + */ + +struct cmListFileArgument +{ + enum Delimiter + { + Unquoted, + Quoted, + Bracket + }; + cmListFileArgument() = default; + cmListFileArgument(std::string v, Delimiter d, long line, long column) + : Value(std::move(v)) + , Delim(d) + , Line(line) + , Column(column) + { + } + bool operator==(const cmListFileArgument& r) const + { + return (this->Value == r.Value) && (this->Delim == r.Delim); + } + bool operator!=(const cmListFileArgument& r) const { return !(*this == r); } + std::string Value; + Delimiter Delim = Unquoted; + long Line = 0; + long Column = 0; +}; + +class cmListFileFunction +{ +public: + cmListFileFunction(std::string name, long line, long lineEnd, + std::vector args) + : Impl{ std::make_shared(std::move(name), line, lineEnd, + std::move(args)) } + { + } + + std::string const& OriginalName() const noexcept + { + return this->Impl->OriginalName; + } + + std::string const& LowerCaseName() const noexcept + { + return this->Impl->LowerCaseName; + } + + long Line() const noexcept { return this->Impl->Line; } + long LineEnd() const noexcept { return this->Impl->LineEnd; } + + std::vector const& Arguments() const noexcept + { + return this->Impl->Arguments; + } + +private: + struct Implementation + { + Implementation(std::string name, long line, long lineEnd, + std::vector args) + : OriginalName{ std::move(name) } + , LowerCaseName{ tolower(this->OriginalName) } + , Line{ line } + , LineEnd{ lineEnd } + , Arguments{ std::move(args) } + { + } + + // taken from yaml-cpp + static bool IsLower(char ch) { return 'a' <= ch && ch <= 'z'; } + static bool IsUpper(char ch) { return 'A' <= ch && ch <= 'Z'; } + static char ToLower(char ch) { return IsUpper(ch) ? ch + 'a' - 'A' : ch; } + + std::string tolower(const std::string& str) + { + std::string s(str); + std::transform(s.begin(), s.end(), s.begin(), ToLower); + return s; + } + + std::string OriginalName; + std::string LowerCaseName; + long Line = 0; + long LineEnd = 0; + std::vector Arguments; + }; + + std::shared_ptr Impl; +}; + +struct cmListFile +{ + bool ParseString(const std::string &str, const std::string &virtual_filename, std::string &error); + + std::vector Functions; +}; diff --git a/src/plugins/cmakeprojectmanager/3rdparty/cmake/cmListFileLexer.cxx b/src/plugins/cmakeprojectmanager/3rdparty/cmake/cmListFileLexer.cxx new file mode 100644 index 00000000000..e093275a8d8 --- /dev/null +++ b/src/plugins/cmakeprojectmanager/3rdparty/cmake/cmListFileLexer.cxx @@ -0,0 +1,2831 @@ +#include "cmStandardLexer.h" + +#define FLEXINT_H 1 +#define YY_INT_ALIGNED short int + +/* A lexical scanner created by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 6 +#define YY_FLEX_SUBMINOR_VERSION 4 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +#ifdef yy_create_buffer +#define cmListFileLexer_yy_create_buffer_ALREADY_DEFINED +#else +#define yy_create_buffer cmListFileLexer_yy_create_buffer +#endif + +#ifdef yy_delete_buffer +#define cmListFileLexer_yy_delete_buffer_ALREADY_DEFINED +#else +#define yy_delete_buffer cmListFileLexer_yy_delete_buffer +#endif + +#ifdef yy_scan_buffer +#define cmListFileLexer_yy_scan_buffer_ALREADY_DEFINED +#else +#define yy_scan_buffer cmListFileLexer_yy_scan_buffer +#endif + +#ifdef yy_scan_string +#define cmListFileLexer_yy_scan_string_ALREADY_DEFINED +#else +#define yy_scan_string cmListFileLexer_yy_scan_string +#endif + +#ifdef yy_scan_bytes +#define cmListFileLexer_yy_scan_bytes_ALREADY_DEFINED +#else +#define yy_scan_bytes cmListFileLexer_yy_scan_bytes +#endif + +#ifdef yy_init_buffer +#define cmListFileLexer_yy_init_buffer_ALREADY_DEFINED +#else +#define yy_init_buffer cmListFileLexer_yy_init_buffer +#endif + +#ifdef yy_flush_buffer +#define cmListFileLexer_yy_flush_buffer_ALREADY_DEFINED +#else +#define yy_flush_buffer cmListFileLexer_yy_flush_buffer +#endif + +#ifdef yy_load_buffer_state +#define cmListFileLexer_yy_load_buffer_state_ALREADY_DEFINED +#else +#define yy_load_buffer_state cmListFileLexer_yy_load_buffer_state +#endif + +#ifdef yy_switch_to_buffer +#define cmListFileLexer_yy_switch_to_buffer_ALREADY_DEFINED +#else +#define yy_switch_to_buffer cmListFileLexer_yy_switch_to_buffer +#endif + +#ifdef yypush_buffer_state +#define cmListFileLexer_yypush_buffer_state_ALREADY_DEFINED +#else +#define yypush_buffer_state cmListFileLexer_yypush_buffer_state +#endif + +#ifdef yypop_buffer_state +#define cmListFileLexer_yypop_buffer_state_ALREADY_DEFINED +#else +#define yypop_buffer_state cmListFileLexer_yypop_buffer_state +#endif + +#ifdef yyensure_buffer_stack +#define cmListFileLexer_yyensure_buffer_stack_ALREADY_DEFINED +#else +#define yyensure_buffer_stack cmListFileLexer_yyensure_buffer_stack +#endif + +#ifdef yylex +#define cmListFileLexer_yylex_ALREADY_DEFINED +#else +#define yylex cmListFileLexer_yylex +#endif + +#ifdef yyrestart +#define cmListFileLexer_yyrestart_ALREADY_DEFINED +#else +#define yyrestart cmListFileLexer_yyrestart +#endif + +#ifdef yylex_init +#define cmListFileLexer_yylex_init_ALREADY_DEFINED +#else +#define yylex_init cmListFileLexer_yylex_init +#endif + +#ifdef yylex_init_extra +#define cmListFileLexer_yylex_init_extra_ALREADY_DEFINED +#else +#define yylex_init_extra cmListFileLexer_yylex_init_extra +#endif + +#ifdef yylex_destroy +#define cmListFileLexer_yylex_destroy_ALREADY_DEFINED +#else +#define yylex_destroy cmListFileLexer_yylex_destroy +#endif + +#ifdef yyget_debug +#define cmListFileLexer_yyget_debug_ALREADY_DEFINED +#else +#define yyget_debug cmListFileLexer_yyget_debug +#endif + +#ifdef yyset_debug +#define cmListFileLexer_yyset_debug_ALREADY_DEFINED +#else +#define yyset_debug cmListFileLexer_yyset_debug +#endif + +#ifdef yyget_extra +#define cmListFileLexer_yyget_extra_ALREADY_DEFINED +#else +#define yyget_extra cmListFileLexer_yyget_extra +#endif + +#ifdef yyset_extra +#define cmListFileLexer_yyset_extra_ALREADY_DEFINED +#else +#define yyset_extra cmListFileLexer_yyset_extra +#endif + +#ifdef yyget_in +#define cmListFileLexer_yyget_in_ALREADY_DEFINED +#else +#define yyget_in cmListFileLexer_yyget_in +#endif + +#ifdef yyset_in +#define cmListFileLexer_yyset_in_ALREADY_DEFINED +#else +#define yyset_in cmListFileLexer_yyset_in +#endif + +#ifdef yyget_out +#define cmListFileLexer_yyget_out_ALREADY_DEFINED +#else +#define yyget_out cmListFileLexer_yyget_out +#endif + +#ifdef yyset_out +#define cmListFileLexer_yyset_out_ALREADY_DEFINED +#else +#define yyset_out cmListFileLexer_yyset_out +#endif + +#ifdef yyget_leng +#define cmListFileLexer_yyget_leng_ALREADY_DEFINED +#else +#define yyget_leng cmListFileLexer_yyget_leng +#endif + +#ifdef yyget_text +#define cmListFileLexer_yyget_text_ALREADY_DEFINED +#else +#define yyget_text cmListFileLexer_yyget_text +#endif + +#ifdef yyget_lineno +#define cmListFileLexer_yyget_lineno_ALREADY_DEFINED +#else +#define yyget_lineno cmListFileLexer_yyget_lineno +#endif + +#ifdef yyset_lineno +#define cmListFileLexer_yyset_lineno_ALREADY_DEFINED +#else +#define yyset_lineno cmListFileLexer_yyset_lineno +#endif + +#ifdef yyget_column +#define cmListFileLexer_yyget_column_ALREADY_DEFINED +#else +#define yyget_column cmListFileLexer_yyget_column +#endif + +#ifdef yyset_column +#define cmListFileLexer_yyset_column_ALREADY_DEFINED +#else +#define yyset_column cmListFileLexer_yyset_column +#endif + +#ifdef yywrap +#define cmListFileLexer_yywrap_ALREADY_DEFINED +#else +#define yywrap cmListFileLexer_yywrap +#endif + +#ifdef yyalloc +#define cmListFileLexer_yyalloc_ALREADY_DEFINED +#else +#define yyalloc cmListFileLexer_yyalloc +#endif + +#ifdef yyrealloc +#define cmListFileLexer_yyrealloc_ALREADY_DEFINED +#else +#define yyrealloc cmListFileLexer_yyrealloc +#endif + +#ifdef yyfree +#define cmListFileLexer_yyfree_ALREADY_DEFINED +#else +#define yyfree cmListFileLexer_yyfree +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX (~(size_t)0) +#endif + +#endif /* ! C99 */ + +#endif /* ! FLEXINT_H */ + +/* begin standard C++ headers. */ + +/* TODO: this is always defined, so inline it */ +#define yyconst const + +#if defined(__GNUC__) && __GNUC__ >= 3 +#define yynoreturn __attribute__((__noreturn__)) +#else +#define yynoreturn +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an + * integer in range [0..255] for use as an array index. + */ +#define YY_SC_TO_UI(c) ((YY_CHAR) (c)) + +/* An opaque pointer. */ +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif + +/* For convenience, these vars (plus the bison vars far below) + are macros in the reentrant scanner. */ +#define yyin yyg->yyin_r +#define yyout yyg->yyout_r +#define yyextra yyg->yyextra_r +#define yyleng yyg->yyleng_r +#define yytext yyg->yytext_r +#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) +#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) +#define yy_flex_debug yyg->yy_flex_debug_r + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN yyg->yy_start = 1 + 2 * +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START ((yyg->yy_start - 1) / 2) +#define YYSTATE YY_START +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE yyrestart( yyin , yyscanner ) +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k. + * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. + * Ditto for the __ia64__ case accordingly. + */ +#define YY_BUF_SIZE 32768 +#else +#define YY_BUF_SIZE 16384 +#endif /* __ia64__ */ +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + /* Note: We specifically omit the test for yy_rule_can_match_eol because it requires + * access to the local variable yy_act. Since yyless() is a macro, it would break + * existing scanners that call yyless() from OUTSIDE yylex. + * One obvious solution it to make yy_act a global. I tried that, and saw + * a 5% performance hit in a non-yylineno scanner, because yy_act is + * normally declared as a register variable-- so it is not worth it. + */ + #define YY_LESS_LINENO(n) \ + do { \ + int yyl;\ + for ( yyl = n; yyl < yyleng; ++yyl )\ + if ( yytext[yyl] == '\n' )\ + --yylineno;\ + }while(0) + #define YY_LINENO_REWIND_TO(dst) \ + do {\ + const char *p;\ + for ( p = yy_cp-1; p >= (dst); --p)\ + if ( *p == '\n' )\ + --yylineno;\ + }while(0) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = yyg->yy_hold_char; \ + YY_RESTORE_YY_MORE_OFFSET \ + yyg->yy_c_buf_p = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) +#define unput(c) yyunput( c, yyg->yytext_ptr , yyscanner ) + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + int yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( yyg->yy_buffer_stack \ + ? yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] \ + : NULL) +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] + +void yyrestart ( FILE *input_file , yyscan_t yyscanner ); +void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size , yyscan_t yyscanner ); +void yy_delete_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yy_flush_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yypush_buffer_state ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +void yypop_buffer_state ( yyscan_t yyscanner ); + +static void yyensure_buffer_stack ( yyscan_t yyscanner ); +static void yy_load_buffer_state ( yyscan_t yyscanner ); +static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file , yyscan_t yyscanner ); +#define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER , yyscanner) + +YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_string ( const char *yy_str , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len , yyscan_t yyscanner ); + +void *yyalloc ( yy_size_t , yyscan_t yyscanner ); +void *yyrealloc ( void *, yy_size_t , yyscan_t yyscanner ); +void yyfree ( void * , yyscan_t yyscanner ); + +#define yy_new_buffer yy_create_buffer +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +/* Begin user sect3 */ + +#define cmListFileLexer_yywrap(yyscanner) (/*CONSTCOND*/1) +#define YY_SKIP_YYWRAP +typedef flex_uint8_t YY_CHAR; + +typedef int yy_state_type; + +#define yytext_ptr yytext_r + +static yy_state_type yy_get_previous_state ( yyscan_t yyscanner ); +static yy_state_type yy_try_NUL_trans ( yy_state_type current_state , yyscan_t yyscanner); +static int yy_get_next_buffer ( yyscan_t yyscanner ); +static void yynoreturn yy_fatal_error ( const char* msg , yyscan_t yyscanner ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + yyg->yytext_ptr = yy_bp; \ + yyleng = (int) (yy_cp - yy_bp); \ + yyg->yy_hold_char = *yy_cp; \ + *yy_cp = '\0'; \ + yyg->yy_c_buf_p = yy_cp; +#define YY_NUM_RULES 24 +#define YY_END_OF_BUFFER 25 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static const flex_int16_t yy_accept[79] = + { 0, + 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, + 25, 13, 22, 1, 16, 3, 13, 5, 6, 7, + 15, 23, 23, 17, 19, 20, 21, 24, 10, 11, + 8, 12, 9, 4, 13, 0, 13, 0, 22, 0, + 0, 7, 13, 0, 13, 0, 2, 0, 13, 17, + 0, 18, 10, 8, 4, 0, 14, 0, 0, 0, + 0, 14, 0, 0, 14, 0, 0, 0, 2, 14, + 0, 0, 0, 0, 0, 0, 0, 0 + } ; + +static const YY_CHAR yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 1, 5, 6, 7, 1, 1, 1, 8, + 9, 1, 1, 1, 1, 1, 1, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 1, 1, 1, + 11, 1, 1, 1, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 13, 14, 15, 1, 12, 1, 12, 12, 12, 12, + + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + } ; + +static const YY_CHAR yy_meta[17] = + { 0, + 1, 1, 2, 3, 4, 3, 1, 3, 5, 6, + 1, 6, 1, 1, 7, 2 + } ; + +static const flex_int16_t yy_base[97] = + { 0, + 0, 0, 14, 28, 42, 56, 70, 84, 18, 19, + 68, 100, 16, 298, 298, 54, 58, 298, 298, 13, + 115, 0, 298, 51, 298, 298, 21, 298, 0, 298, + 53, 298, 298, 0, 0, 126, 55, 0, 25, 25, + 53, 0, 0, 136, 53, 0, 57, 0, 0, 42, + 50, 298, 0, 43, 0, 146, 160, 45, 172, 43, + 26, 0, 42, 177, 0, 42, 188, 40, 298, 40, + 0, 38, 37, 34, 32, 31, 23, 298, 197, 204, + 211, 218, 225, 232, 239, 245, 252, 259, 262, 268, + 275, 278, 280, 286, 289, 291 + + } ; + +static const flex_int16_t yy_def[97] = + { 0, + 78, 1, 79, 79, 80, 80, 81, 81, 82, 82, + 78, 78, 78, 78, 78, 78, 12, 78, 78, 12, + 78, 83, 78, 84, 78, 78, 84, 78, 85, 78, + 78, 78, 78, 86, 12, 87, 12, 88, 78, 78, + 89, 20, 12, 90, 12, 21, 78, 91, 12, 84, + 84, 78, 85, 78, 86, 87, 78, 56, 87, 92, + 78, 57, 89, 90, 57, 64, 90, 93, 78, 57, + 94, 95, 92, 96, 93, 95, 96, 0, 78, 78, + 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, + 78, 78, 78, 78, 78, 78 + + } ; + +static const flex_int16_t yy_nxt[315] = + { 0, + 12, 13, 14, 13, 15, 16, 17, 18, 19, 12, + 12, 20, 21, 22, 12, 23, 25, 39, 26, 39, + 14, 14, 42, 52, 42, 50, 39, 27, 39, 28, + 25, 64, 26, 28, 28, 61, 61, 47, 47, 56, + 65, 27, 64, 28, 30, 57, 56, 60, 65, 74, + 62, 57, 72, 54, 50, 51, 31, 28, 30, 69, + 68, 62, 60, 54, 51, 41, 40, 78, 78, 78, + 31, 28, 30, 78, 78, 78, 78, 78, 78, 78, + 78, 78, 78, 78, 33, 28, 30, 78, 78, 78, + 78, 78, 78, 78, 78, 78, 78, 78, 33, 28, + + 35, 78, 78, 78, 36, 78, 37, 78, 78, 35, + 35, 35, 35, 38, 35, 43, 78, 78, 78, 44, + 78, 45, 78, 78, 43, 46, 43, 47, 48, 43, + 57, 78, 58, 78, 78, 78, 78, 78, 78, 59, + 65, 78, 66, 78, 78, 78, 78, 78, 78, 67, + 57, 78, 58, 78, 78, 78, 78, 78, 78, 59, + 57, 78, 78, 78, 36, 78, 70, 78, 78, 57, + 57, 57, 57, 71, 57, 56, 78, 56, 78, 56, + 56, 65, 78, 66, 78, 78, 78, 78, 78, 78, + 67, 64, 78, 64, 78, 64, 64, 24, 24, 24, + + 24, 24, 24, 24, 29, 29, 29, 29, 29, 29, + 29, 32, 32, 32, 32, 32, 32, 32, 34, 34, + 34, 34, 34, 34, 34, 49, 78, 49, 49, 49, + 49, 49, 50, 78, 50, 78, 50, 50, 50, 53, + 78, 53, 53, 53, 53, 55, 78, 55, 55, 55, + 55, 55, 56, 78, 78, 56, 78, 56, 56, 35, + 78, 35, 35, 35, 35, 35, 63, 63, 64, 78, + 78, 64, 78, 64, 64, 43, 78, 43, 43, 43, + 43, 43, 73, 73, 75, 75, 57, 78, 57, 57, + 57, 57, 57, 76, 76, 77, 77, 11, 78, 78, + + 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, + 78, 78, 78, 78 + } ; + +static const flex_int16_t yy_chk[315] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 3, 13, 3, 13, + 9, 10, 20, 27, 20, 27, 39, 3, 39, 3, + 4, 77, 4, 9, 10, 40, 61, 40, 61, 76, + 75, 4, 74, 4, 5, 73, 72, 70, 68, 66, + 63, 60, 58, 54, 51, 50, 5, 5, 6, 47, + 45, 41, 37, 31, 24, 17, 16, 11, 0, 0, + 6, 6, 7, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 7, 7, 8, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, + + 12, 0, 0, 0, 12, 0, 12, 0, 0, 12, + 12, 12, 12, 12, 12, 21, 0, 0, 0, 21, + 0, 21, 0, 0, 21, 21, 21, 21, 21, 21, + 36, 0, 36, 0, 0, 0, 0, 0, 0, 36, + 44, 0, 44, 0, 0, 0, 0, 0, 0, 44, + 56, 0, 56, 0, 0, 0, 0, 0, 0, 56, + 57, 0, 0, 0, 57, 0, 57, 0, 0, 57, + 57, 57, 57, 57, 57, 59, 0, 59, 0, 59, + 59, 64, 0, 64, 0, 0, 0, 0, 0, 0, + 64, 67, 0, 67, 0, 67, 67, 79, 79, 79, + + 79, 79, 79, 79, 80, 80, 80, 80, 80, 80, + 80, 81, 81, 81, 81, 81, 81, 81, 82, 82, + 82, 82, 82, 82, 82, 83, 0, 83, 83, 83, + 83, 83, 84, 0, 84, 0, 84, 84, 84, 85, + 0, 85, 85, 85, 85, 86, 0, 86, 86, 86, + 86, 86, 87, 0, 0, 87, 0, 87, 87, 88, + 0, 88, 88, 88, 88, 88, 89, 89, 90, 0, + 0, 90, 0, 90, 90, 91, 0, 91, 91, 91, + 91, 91, 92, 92, 93, 93, 94, 0, 94, 94, + 94, 94, 94, 95, 95, 96, 96, 78, 78, 78, + + 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, + 78, 78, 78, 78 + } ; + +/* Table of booleans, true if rule could match eol. */ +static const flex_int32_t yy_rule_can_match_eol[25] = + { 0, +1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, 0, }; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +/* + +This file must be translated to C and modified to build everywhere. + +Run flex >= 2.6 like this: + + flex --nounistd -DFLEXINT_H --noline -ocmListFileLexer.cxx cmListFileLexer.in.l + +Modify cmListFileLexer.c: + - remove trailing whitespace: sed -i 's/\s*$//' cmListFileLexer.c + - remove blank lines at end of file: sed -i '${/^$/d;}' cmListFileLexer.c + - #include "cmStandardLexer.h" at the top: sed -i '1i#include "cmStandardLexer.h"' cmListFileLexer.c + +*/ + +/* IWYU pragma: no_forward_declare yyguts_t */ + +/* Setup the proper cmListFileLexer_yylex declaration. */ +#define YY_EXTRA_TYPE cmListFileLexer* +#define YY_DECL int cmListFileLexer_yylex (yyscan_t yyscanner, cmListFileLexer* lexer) + +#include "cmListFileLexer.h" + +/*--------------------------------------------------------------------------*/ +struct cmListFileLexer_s +{ + cmListFileLexer_Token token; + int bracket; + int comment; + int line; + int column; + int size; + FILE* file; + size_t cr; + char* string_buffer; + char* string_position; + int string_left; + yyscan_t scanner; +}; + +static void cmListFileLexerSetToken(cmListFileLexer* lexer, const char* text, + int length); +static void cmListFileLexerAppend(cmListFileLexer* lexer, const char* text, + int length); +static int cmListFileLexerInput(cmListFileLexer* lexer, char* buffer, + size_t bufferSize); +static void cmListFileLexerInit(cmListFileLexer* lexer); +static void cmListFileLexerDestroy(cmListFileLexer* lexer); + +/* Replace the lexer input function. */ +#undef YY_INPUT +#define YY_INPUT(buf, result, max_size) \ + do { result = cmListFileLexerInput(cmListFileLexer_yyget_extra(yyscanner), buf, max_size); } while (0) + +/*--------------------------------------------------------------------------*/ + +#define INITIAL 0 +#define STRING 1 +#define BRACKET 2 +#define BRACKETEND 3 +#define COMMENT 4 + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +/* Holds the entire state of the reentrant scanner. */ +struct yyguts_t + { + + /* User-defined. Not touched by flex. */ + YY_EXTRA_TYPE yyextra_r; + + /* The rest are the same as the globals declared in the non-reentrant scanner. */ + FILE *yyin_r, *yyout_r; + size_t yy_buffer_stack_top; /**< index of top of stack. */ + size_t yy_buffer_stack_max; /**< capacity of stack. */ + YY_BUFFER_STATE * yy_buffer_stack; /**< Stack as an array. */ + char yy_hold_char; + int yy_n_chars; + int yyleng_r; + char *yy_c_buf_p; + int yy_init; + int yy_start; + int yy_did_buffer_switch_on_eof; + int yy_start_stack_ptr; + int yy_start_stack_depth; + int *yy_start_stack; + yy_state_type yy_last_accepting_state; + char* yy_last_accepting_cpos; + + int yylineno_r; + int yy_flex_debug_r; + + char *yytext_r; + int yy_more_flag; + int yy_more_len; + + }; /* end struct yyguts_t */ + +static int yy_init_globals ( yyscan_t yyscanner ); + +int yylex_init (yyscan_t* scanner); + +int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t* scanner); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int yylex_destroy ( yyscan_t yyscanner ); + +int yyget_debug ( yyscan_t yyscanner ); + +void yyset_debug ( int debug_flag , yyscan_t yyscanner ); + +YY_EXTRA_TYPE yyget_extra ( yyscan_t yyscanner ); + +void yyset_extra ( YY_EXTRA_TYPE user_defined , yyscan_t yyscanner ); + +FILE *yyget_in ( yyscan_t yyscanner ); + +void yyset_in ( FILE * _in_str , yyscan_t yyscanner ); + +FILE *yyget_out ( yyscan_t yyscanner ); + +void yyset_out ( FILE * _out_str , yyscan_t yyscanner ); + + int yyget_leng ( yyscan_t yyscanner ); + +char *yyget_text ( yyscan_t yyscanner ); + +int yyget_lineno ( yyscan_t yyscanner ); + +void yyset_lineno ( int _line_number , yyscan_t yyscanner ); + +int yyget_column ( yyscan_t yyscanner ); + +void yyset_column ( int _column_no , yyscan_t yyscanner ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap ( yyscan_t yyscanner ); +#else +extern int yywrap ( yyscan_t yyscanner ); +#endif +#endif + +#ifndef YY_NO_UNPUT + + static void yyunput ( int c, char *buf_ptr , yyscan_t yyscanner); + +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy ( char *, const char *, int , yyscan_t yyscanner); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen ( const char * , yyscan_t yyscanner); +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus +static int yyinput ( yyscan_t yyscanner ); +#else +static int input ( yyscan_t yyscanner ); +#endif + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k */ +#define YY_READ_BUF_SIZE 16384 +#else +#define YY_READ_BUF_SIZE 8192 +#endif /* __ia64__ */ +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + int n; \ + for ( n = 0; n < max_size && \ + (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yyin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg , yyscanner) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int yylex (yyscan_t yyscanner); + +#define YY_DECL int yylex (yyscan_t yyscanner) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK /*LINTED*/break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + yy_state_type yy_current_state; + char *yy_cp, *yy_bp; + int yy_act; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( !yyg->yy_init ) + { + yyg->yy_init = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! yyg->yy_start ) + yyg->yy_start = 1; /* first start state */ + + if ( ! yyin ) + yyin = stdin; + + if ( ! yyout ) + yyout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); + } + + yy_load_buffer_state( yyscanner ); + } + + { + + while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */ + { + yy_cp = yyg->yy_c_buf_p; + + /* Support of yytext. */ + *yy_cp = yyg->yy_hold_char; + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = yyg->yy_start; +yy_match: + do + { + YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 79 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + ++yy_cp; + } + while ( yy_base[yy_current_state] != 298 ); + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + if ( yy_act == 0 ) + { /* have to back up */ + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + yy_act = yy_accept[yy_current_state]; + } + + YY_DO_BEFORE_ACTION; + + if ( yy_act != YY_END_OF_BUFFER && yy_rule_can_match_eol[yy_act] ) + { + int yyl; + for ( yyl = 0; yyl < yyleng; ++yyl ) + if ( yytext[yyl] == '\n' ) + + do{ yylineno++; + yycolumn=0; + }while(0) +; + } + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = yyg->yy_hold_char; + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + goto yy_find_action; + +case 1: +/* rule 1 can match eol */ +YY_RULE_SETUP +{ + lexer->token.type = cmListFileLexer_Token_Newline; + cmListFileLexerSetToken(lexer, yytext, yyleng); + ++lexer->line; + lexer->column = 1; + BEGIN(INITIAL); + return 1; +} + YY_BREAK +case 2: +/* rule 2 can match eol */ +YY_RULE_SETUP +{ + const char* bracket = yytext; + lexer->comment = yytext[0] == '#'; + if (lexer->comment) { + lexer->token.type = cmListFileLexer_Token_CommentBracket; + bracket += 1; + } else { + lexer->token.type = cmListFileLexer_Token_ArgumentBracket; + } + cmListFileLexerSetToken(lexer, "", 0); + lexer->bracket = strchr(bracket+1, '[') - bracket; + if (yytext[yyleng-1] == '\n') { + ++lexer->line; + lexer->column = 1; + } else { + lexer->column += yyleng; + } + BEGIN(BRACKET); +} + YY_BREAK +case 3: +YY_RULE_SETUP +{ + lexer->column += yyleng; + BEGIN(COMMENT); +} + YY_BREAK +case 4: +YY_RULE_SETUP +{ + lexer->column += yyleng; +} + YY_BREAK +case 5: +YY_RULE_SETUP +{ + lexer->token.type = cmListFileLexer_Token_ParenLeft; + cmListFileLexerSetToken(lexer, yytext, yyleng); + lexer->column += yyleng; + return 1; +} + YY_BREAK +case 6: +YY_RULE_SETUP +{ + lexer->token.type = cmListFileLexer_Token_ParenRight; + cmListFileLexerSetToken(lexer, yytext, yyleng); + lexer->column += yyleng; + return 1; +} + YY_BREAK +case 7: +YY_RULE_SETUP +{ + lexer->token.type = cmListFileLexer_Token_Identifier; + cmListFileLexerSetToken(lexer, yytext, yyleng); + lexer->column += yyleng; + return 1; +} + YY_BREAK +case 8: +YY_RULE_SETUP +{ + /* Handle ]]====]=======]*/ + cmListFileLexerAppend(lexer, yytext, yyleng); + lexer->column += yyleng; + if (yyleng == lexer->bracket) { + BEGIN(BRACKETEND); + } +} + YY_BREAK +case 9: +YY_RULE_SETUP +{ + lexer->column += yyleng; + /* Erase the partial bracket from the token. */ + lexer->token.length -= lexer->bracket; + lexer->token.text[lexer->token.length] = 0; + BEGIN(INITIAL); + return 1; +} + YY_BREAK +case 10: +YY_RULE_SETUP +{ + cmListFileLexerAppend(lexer, yytext, yyleng); + lexer->column += yyleng; +} + YY_BREAK +case 11: +/* rule 11 can match eol */ +YY_RULE_SETUP +{ + cmListFileLexerAppend(lexer, yytext, yyleng); + ++lexer->line; + lexer->column = 1; + BEGIN(BRACKET); +} + YY_BREAK +case 12: +YY_RULE_SETUP +{ + cmListFileLexerAppend(lexer, yytext, yyleng); + lexer->column += yyleng; + BEGIN(BRACKET); +} + YY_BREAK +case YY_STATE_EOF(BRACKET): +case YY_STATE_EOF(BRACKETEND): +{ + lexer->token.type = cmListFileLexer_Token_BadBracket; + BEGIN(INITIAL); + return 1; +} + YY_BREAK +case 13: +YY_RULE_SETUP +{ + lexer->token.type = cmListFileLexer_Token_ArgumentUnquoted; + cmListFileLexerSetToken(lexer, yytext, yyleng); + lexer->column += yyleng; + return 1; +} + YY_BREAK +case 14: +YY_RULE_SETUP +{ + lexer->token.type = cmListFileLexer_Token_ArgumentUnquoted; + cmListFileLexerSetToken(lexer, yytext, yyleng); + lexer->column += yyleng; + return 1; +} + YY_BREAK +case 15: +YY_RULE_SETUP +{ + lexer->token.type = cmListFileLexer_Token_ArgumentUnquoted; + cmListFileLexerSetToken(lexer, yytext, yyleng); + lexer->column += yyleng; + return 1; +} + YY_BREAK +case 16: +YY_RULE_SETUP +{ + lexer->token.type = cmListFileLexer_Token_ArgumentQuoted; + cmListFileLexerSetToken(lexer, "", 0); + lexer->column += yyleng; + BEGIN(STRING); +} + YY_BREAK +case 17: +YY_RULE_SETUP +{ + cmListFileLexerAppend(lexer, yytext, yyleng); + lexer->column += yyleng; +} + YY_BREAK +case 18: +/* rule 18 can match eol */ +YY_RULE_SETUP +{ + /* Continuation: text is not part of string */ + ++lexer->line; + lexer->column = 1; +} + YY_BREAK +case 19: +/* rule 19 can match eol */ +YY_RULE_SETUP +{ + cmListFileLexerAppend(lexer, yytext, yyleng); + ++lexer->line; + lexer->column = 1; +} + YY_BREAK +case 20: +YY_RULE_SETUP +{ + lexer->column += yyleng; + BEGIN(INITIAL); + return 1; +} + YY_BREAK +case 21: +YY_RULE_SETUP +{ + cmListFileLexerAppend(lexer, yytext, yyleng); + lexer->column += yyleng; +} + YY_BREAK +case YY_STATE_EOF(STRING): +{ + lexer->token.type = cmListFileLexer_Token_BadString; + BEGIN(INITIAL); + return 1; +} + YY_BREAK +case 22: +YY_RULE_SETUP +{ + lexer->token.type = cmListFileLexer_Token_Space; + cmListFileLexerSetToken(lexer, yytext, yyleng); + lexer->column += yyleng; + return 1; +} + YY_BREAK +case 23: +YY_RULE_SETUP +{ + lexer->token.type = cmListFileLexer_Token_BadCharacter; + cmListFileLexerSetToken(lexer, yytext, yyleng); + lexer->column += yyleng; + return 1; +} + YY_BREAK +case YY_STATE_EOF(INITIAL): +case YY_STATE_EOF(COMMENT): +{ + lexer->token.type = cmListFileLexer_Token_None; + cmListFileLexerSetToken(lexer, 0, 0); + return 0; +} + YY_BREAK +case 24: +YY_RULE_SETUP +ECHO; + YY_BREAK + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - yyg->yytext_ptr) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = yyg->yy_hold_char; + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * yylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( yyg->yy_c_buf_p <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state , yyscanner); + + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++yyg->yy_c_buf_p; + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = yyg->yy_c_buf_p; + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_END_OF_FILE: + { + yyg->yy_did_buffer_switch_on_eof = 0; + + if ( yywrap( yyscanner ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + yyg->yy_c_buf_p = yyg->yytext_ptr + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = + yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + yyg->yy_c_buf_p = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars]; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ + } /* end of user's declarations */ +} /* end of yylex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + char *source = yyg->yytext_ptr; + int number_to_move, i; + int ret_val; + + if ( yyg->yy_c_buf_p > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( yyg->yy_c_buf_p - yyg->yytext_ptr - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr - 1); + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = 0; + + else + { + int num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE; + + int yy_c_buf_p_offset = + (int) (yyg->yy_c_buf_p - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + int new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + yyrealloc( (void *) b->yy_ch_buf, + (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = NULL; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + yyg->yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + yyg->yy_n_chars, num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + if ( yyg->yy_n_chars == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + yyrestart( yyin , yyscanner); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + if ((yyg->yy_n_chars + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { + /* Extend the array by 50%, plus the number we really need. */ + int new_size = yyg->yy_n_chars + number_to_move + (yyg->yy_n_chars >> 1); + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc( + (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size , yyscanner ); + if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); + /* "- 2" to take care of EOB's */ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2); + } + + yyg->yy_n_chars += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR; + + yyg->yytext_ptr = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (yyscan_t yyscanner) +{ + yy_state_type yy_current_state; + char *yy_cp; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_current_state = yyg->yy_start; + + for ( yy_cp = yyg->yytext_ptr + YY_MORE_ADJ; yy_cp < yyg->yy_c_buf_p; ++yy_cp ) + { + YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 16); + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 79 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state , yyscan_t yyscanner) +{ + int yy_is_jam; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */ + char *yy_cp = yyg->yy_c_buf_p; + + YY_CHAR yy_c = 16; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 79 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + yy_is_jam = (yy_current_state == 78); + + (void)yyg; + return yy_is_jam ? 0 : yy_current_state; +} + +#ifndef YY_NO_UNPUT + + static void yyunput (int c, char * yy_bp , yyscan_t yyscanner) +{ + char *yy_cp; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_cp = yyg->yy_c_buf_p; + + /* undo effects of setting up yytext */ + *yy_cp = yyg->yy_hold_char; + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + { /* need to shift things up to make room */ + /* +2 for EOB chars. */ + int number_to_move = yyg->yy_n_chars + 2; + char *dest = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size + 2]; + char *source = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]; + + while ( source > YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + *--dest = *--source; + + yy_cp += (int) (dest - source); + yy_bp += (int) (dest - source); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = + yyg->yy_n_chars = (int) YY_CURRENT_BUFFER_LVALUE->yy_buf_size; + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + YY_FATAL_ERROR( "flex scanner push-back overflow" ); + } + + *--yy_cp = (char) c; + + if ( c == '\n' ){ + --yylineno; + } + + yyg->yytext_ptr = yy_bp; + yyg->yy_hold_char = *yy_cp; + yyg->yy_c_buf_p = yy_cp; +} + +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (yyscan_t yyscanner) +#else + static int input (yyscan_t yyscanner) +#endif + +{ + int c; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + *yyg->yy_c_buf_p = yyg->yy_hold_char; + + if ( *yyg->yy_c_buf_p == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( yyg->yy_c_buf_p < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + /* This was really a NUL. */ + *yyg->yy_c_buf_p = '\0'; + + else + { /* need more input */ + int offset = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr); + ++yyg->yy_c_buf_p; + + switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + yyrestart( yyin , yyscanner); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( yywrap( yyscanner ) ) + return 0; + + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(yyscanner); +#else + return input(yyscanner); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = yyg->yytext_ptr + offset; + break; + } + } + } + + c = *(unsigned char *) yyg->yy_c_buf_p; /* cast for 8-bit char's */ + *yyg->yy_c_buf_p = '\0'; /* preserve yytext */ + yyg->yy_hold_char = *++yyg->yy_c_buf_p; + + if ( c == '\n' ) + + do{ yylineno++; + yycolumn=0; + }while(0) +; + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * @param yyscanner The scanner object. + * @note This function does not reset the start condition to @c INITIAL . + */ + void yyrestart (FILE * input_file , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! YY_CURRENT_BUFFER ){ + yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); + } + + yy_init_buffer( YY_CURRENT_BUFFER, input_file , yyscanner); + yy_load_buffer_state( yyscanner ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * @param yyscanner The scanner object. + */ + void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* TODO. We should be able to replace this entire function body + * with + * yypop_buffer_state(); + * yypush_buffer_state(new_buffer); + */ + yyensure_buffer_stack (yyscanner); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + yy_load_buffer_state( yyscanner ); + + /* We don't actually know whether we did this switch during + * EOF (yywrap()) processing, but the only time this flag + * is looked at is after yywrap() is called, so it's safe + * to go ahead and always set it. + */ + yyg->yy_did_buffer_switch_on_eof = 1; +} + +static void yy_load_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + yyg->yytext_ptr = yyg->yy_c_buf_p = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + yyg->yy_hold_char = *yyg->yy_c_buf_p; +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * @param yyscanner The scanner object. + * @return the allocated buffer state. + */ + YY_BUFFER_STATE yy_create_buffer (FILE * file, int size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + yy_init_buffer( b, file , yyscanner); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with yy_create_buffer() + * @param yyscanner The scanner object. + */ + void yy_delete_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + yyfree( (void *) b->yy_ch_buf , yyscanner ); + + yyfree( (void *) b , yyscanner ); +} + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a yyrestart() or at EOF. + */ + static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file , yyscan_t yyscanner) + +{ + int oerrno = errno; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_flush_buffer( b , yyscanner); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then yy_init_buffer was _probably_ + * called from yyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * @param yyscanner The scanner object. + */ + void yy_flush_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + yy_load_buffer_state( yyscanner ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * @param yyscanner The scanner object. + */ +void yypush_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (new_buffer == NULL) + return; + + yyensure_buffer_stack(yyscanner); + + /* This block is copied from yy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + yyg->yy_buffer_stack_top++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from yy_switch_to_buffer. */ + yy_load_buffer_state( yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * @param yyscanner The scanner object. + */ +void yypop_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (!YY_CURRENT_BUFFER) + return; + + yy_delete_buffer(YY_CURRENT_BUFFER , yyscanner); + YY_CURRENT_BUFFER_LVALUE = NULL; + if (yyg->yy_buffer_stack_top > 0) + --yyg->yy_buffer_stack_top; + + if (YY_CURRENT_BUFFER) { + yy_load_buffer_state( yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void yyensure_buffer_stack (yyscan_t yyscanner) +{ + yy_size_t num_to_alloc; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (!yyg->yy_buffer_stack) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */ + yyg->yy_buffer_stack = (struct yy_buffer_state**)yyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + yyg->yy_buffer_stack_max = num_to_alloc; + yyg->yy_buffer_stack_top = 0; + return; + } + + if (yyg->yy_buffer_stack_top >= (yyg->yy_buffer_stack_max) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + yy_size_t grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = yyg->yy_buffer_stack_max + grow_size; + yyg->yy_buffer_stack = (struct yy_buffer_state**)yyrealloc + (yyg->yy_buffer_stack, + num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + /* zero only the new slots.*/ + memset(yyg->yy_buffer_stack + yyg->yy_buffer_stack_max, 0, grow_size * sizeof(struct yy_buffer_state*)); + yyg->yy_buffer_stack_max = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return NULL; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); + + b->yy_buf_size = (int) (size - 2); /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = NULL; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + yy_switch_to_buffer( b , yyscanner ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to yylex() will + * scan from a @e copy of @a str. + * @param yystr a NUL-terminated string to scan + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * yy_scan_bytes() instead. + */ +YY_BUFFER_STATE yy_scan_string (const char * yystr , yyscan_t yyscanner) +{ + + return yy_scan_bytes( yystr, (int) strlen(yystr) , yyscanner); +} + +/** Setup the input buffer state to scan the given bytes. The next call to yylex() will + * scan from a @e copy of @a bytes. + * @param yybytes the byte buffer to scan + * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, int _yybytes_len , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = (yy_size_t) (_yybytes_len + 2); + buf = (char *) yyalloc( n , yyscanner ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = yy_scan_buffer( buf, n , yyscanner); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yynoreturn yy_fatal_error (const char* msg , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + yytext[yyleng] = yyg->yy_hold_char; \ + yyg->yy_c_buf_p = yytext + yyless_macro_arg; \ + yyg->yy_hold_char = *yyg->yy_c_buf_p; \ + *yyg->yy_c_buf_p = '\0'; \ + yyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the user-defined data for this scanner. + * @param yyscanner The scanner object. + */ +YY_EXTRA_TYPE yyget_extra (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyextra; +} + +/** Get the current line number. + * @param yyscanner The scanner object. + */ +int yyget_lineno (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yylineno; +} + +/** Get the current column number. + * @param yyscanner The scanner object. + */ +int yyget_column (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yycolumn; +} + +/** Get the input stream. + * @param yyscanner The scanner object. + */ +FILE *yyget_in (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyin; +} + +/** Get the output stream. + * @param yyscanner The scanner object. + */ +FILE *yyget_out (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyout; +} + +/** Get the length of the current token. + * @param yyscanner The scanner object. + */ +int yyget_leng (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyleng; +} + +/** Get the current token. + * @param yyscanner The scanner object. + */ + +char *yyget_text (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yytext; +} + +/** Set the user-defined data. This data is never touched by the scanner. + * @param user_defined The data to be associated with this scanner. + * @param yyscanner The scanner object. + */ +void yyset_extra (YY_EXTRA_TYPE user_defined , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyextra = user_defined ; +} + +/** Set the current line number. + * @param _line_number line number + * @param yyscanner The scanner object. + */ +void yyset_lineno (int _line_number , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* lineno is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + YY_FATAL_ERROR( "yyset_lineno called with no buffer" ); + + yylineno = _line_number; +} + +/** Set the current column. + * @param _column_no column number + * @param yyscanner The scanner object. + */ +void yyset_column (int _column_no , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* column is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + YY_FATAL_ERROR( "yyset_column called with no buffer" ); + + yycolumn = _column_no; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param _in_str A readable stream. + * @param yyscanner The scanner object. + * @see yy_switch_to_buffer + */ +void yyset_in (FILE * _in_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyin = _in_str ; +} + +void yyset_out (FILE * _out_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyout = _out_str ; +} + +int yyget_debug (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yy_flex_debug; +} + +void yyset_debug (int _bdebug , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yy_flex_debug = _bdebug ; +} + +/* Accessor methods for yylval and yylloc */ + +/* User-visible API */ + +/* yylex_init is special because it creates the scanner itself, so it is + * the ONLY reentrant function that doesn't take the scanner as the last argument. + * That's why we explicitly handle the declaration, instead of using our macros. + */ +int yylex_init(yyscan_t* ptr_yy_globals) +{ + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), NULL ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + return yy_init_globals ( *ptr_yy_globals ); +} + +/* yylex_init_extra has the same functionality as yylex_init, but follows the + * convention of taking the scanner as the last argument. Note however, that + * this is a *pointer* to a scanner, as it will be allocated by this call (and + * is the reason, too, why this function also must handle its own declaration). + * The user defined value in the first argument will be available to yyalloc in + * the yyextra field. + */ +int yylex_init_extra( YY_EXTRA_TYPE yy_user_defined, yyscan_t* ptr_yy_globals ) +{ + struct yyguts_t dummy_yyguts; + + yyset_extra (yy_user_defined, &dummy_yyguts); + + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), &dummy_yyguts ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in + yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + yyset_extra (yy_user_defined, *ptr_yy_globals); + + return yy_init_globals ( *ptr_yy_globals ); +} + +static int yy_init_globals (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from yylex_destroy(), so don't allocate here. + */ + + yyg->yy_buffer_stack = NULL; + yyg->yy_buffer_stack_top = 0; + yyg->yy_buffer_stack_max = 0; + yyg->yy_c_buf_p = NULL; + yyg->yy_init = 0; + yyg->yy_start = 0; + + yyg->yy_start_stack_ptr = 0; + yyg->yy_start_stack_depth = 0; + yyg->yy_start_stack = NULL; + +/* Defined in main.c */ +#ifdef YY_STDINIT + yyin = stdin; + yyout = stdout; +#else + yyin = NULL; + yyout = NULL; +#endif + + /* For future reference: Set errno on error, since we are called by + * yylex_init() + */ + return 0; +} + +/* yylex_destroy is for both reentrant and non-reentrant scanners. */ +int yylex_destroy (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + yy_delete_buffer( YY_CURRENT_BUFFER , yyscanner ); + YY_CURRENT_BUFFER_LVALUE = NULL; + yypop_buffer_state(yyscanner); + } + + /* Destroy the stack itself. */ + yyfree(yyg->yy_buffer_stack , yyscanner); + yyg->yy_buffer_stack = NULL; + + /* Destroy the start condition stack. */ + yyfree( yyg->yy_start_stack , yyscanner ); + yyg->yy_start_stack = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * yylex() is called, initialization will occur. */ + yy_init_globals( yyscanner); + + /* Destroy the main struct (reentrant only). */ + yyfree ( yyscanner , yyscanner ); + yyscanner = NULL; + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, const char * s2, int n , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + + int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (const char * s , yyscan_t yyscanner) +{ + int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *yyalloc (yy_size_t size , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + return malloc(size); +} + +void *yyrealloc (void * ptr, yy_size_t size , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return realloc(ptr, size); +} + +void yyfree (void * ptr , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + free( (char *) ptr ); /* see yyrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + +/*--------------------------------------------------------------------------*/ +static void cmListFileLexerSetToken(cmListFileLexer* lexer, const char* text, + int length) +{ + /* Set the token line and column number. */ + lexer->token.line = lexer->line; + lexer->token.column = lexer->column; + + /* Use the same buffer if possible. */ + if (lexer->token.text) { + if (text && length < lexer->size) { + strcpy(lexer->token.text, text); + lexer->token.length = length; + return; + } + free(lexer->token.text); + lexer->token.text = 0; + lexer->size = 0; + } + + /* Need to extend the buffer. */ + if (text) { + lexer->token.text = strdup(text); + lexer->token.length = length; + lexer->size = length + 1; + } else { + lexer->token.length = 0; + } +} + +/*--------------------------------------------------------------------------*/ +static void cmListFileLexerAppend(cmListFileLexer* lexer, const char* text, + int length) +{ + char* temp; + int newSize; + + /* If the appended text will fit in the buffer, do not reallocate. */ + newSize = lexer->token.length + length + 1; + if (lexer->token.text && newSize <= lexer->size) { + strcpy(lexer->token.text + lexer->token.length, text); + lexer->token.length += length; + return; + } + + /* We need to extend the buffer. */ + temp = (char *)malloc(newSize); + if (lexer->token.text) { + memcpy(temp, lexer->token.text, lexer->token.length); + free(lexer->token.text); + } + memcpy(temp + lexer->token.length, text, length); + temp[lexer->token.length + length] = 0; + lexer->token.text = temp; + lexer->token.length += length; + lexer->size = newSize; +} + +/*--------------------------------------------------------------------------*/ +static int cmListFileLexerInput(cmListFileLexer* lexer, char* buffer, + size_t bufferSize) +{ + if (lexer) { + if (lexer->file) { + /* Convert CRLF -> LF explicitly. The C FILE "t"ext mode + does not convert newlines on all platforms. Move any + trailing CR to the start of the buffer for the next read. */ + size_t cr = lexer->cr; + size_t n; + buffer[0] = '\r'; + n = fread(buffer + cr, 1, bufferSize - cr, lexer->file); + if (n) { + char* o = buffer; + const char* i = buffer; + const char* e; + n += cr; + cr = (buffer[n - 1] == '\r') ? 1 : 0; + e = buffer + n - cr; + while (i != e) { + if (i[0] == '\r' && i[1] == '\n') { + ++i; + } + *o++ = *i++; + } + n = o - buffer; + } else { + n = cr; + cr = 0; + } + lexer->cr = cr; + return n; + } else if (lexer->string_left) { + int length = lexer->string_left; + if ((int)bufferSize < length) { + length = (int)bufferSize; + } + memcpy(buffer, lexer->string_position, length); + lexer->string_position += length; + lexer->string_left -= length; + return length; + } + } + return 0; +} + +/*--------------------------------------------------------------------------*/ +static void cmListFileLexerInit(cmListFileLexer* lexer) +{ + if (lexer->file || lexer->string_buffer) { + cmListFileLexer_yylex_init(&lexer->scanner); + cmListFileLexer_yyset_extra(lexer, lexer->scanner); + } +} + +/*--------------------------------------------------------------------------*/ +static void cmListFileLexerDestroy(cmListFileLexer* lexer) +{ + cmListFileLexerSetToken(lexer, 0, 0); + if (lexer->file || lexer->string_buffer) { + cmListFileLexer_yylex_destroy(lexer->scanner); + if (lexer->file) { + fclose(lexer->file); + lexer->file = 0; + } + if (lexer->string_buffer) { + lexer->string_buffer = 0; + lexer->string_left = 0; + lexer->string_position = 0; + } + } +} + +/*--------------------------------------------------------------------------*/ +cmListFileLexer* cmListFileLexer_New(void) +{ + cmListFileLexer* lexer = (cmListFileLexer*)malloc(sizeof(cmListFileLexer)); + if (!lexer) { + return 0; + } + memset(lexer, 0, sizeof(*lexer)); + lexer->line = 1; + lexer->column = 1; + return lexer; +} + +/*--------------------------------------------------------------------------*/ +void cmListFileLexer_Delete(cmListFileLexer* lexer) +{ + cmListFileLexer_SetFileName(lexer, 0, 0); + free(lexer); +} + +/*--------------------------------------------------------------------------*/ +static cmListFileLexer_BOM cmListFileLexer_ReadBOM(FILE* f) +{ + unsigned char b[2]; + if (fread(b, 1, 2, f) == 2) { + if (b[0] == 0xEF && b[1] == 0xBB) { + if (fread(b, 1, 1, f) == 1 && b[0] == 0xBF) { + return cmListFileLexer_BOM_UTF8; + } + } else if (b[0] == 0xFE && b[1] == 0xFF) { + /* UTF-16 BE */ + return cmListFileLexer_BOM_UTF16BE; + } else if (b[0] == 0 && b[1] == 0) { + if (fread(b, 1, 2, f) == 2 && b[0] == 0xFE && b[1] == 0xFF) { + return cmListFileLexer_BOM_UTF32BE; + } + } else if (b[0] == 0xFF && b[1] == 0xFE) { + fpos_t p; + fgetpos(f, &p); + if (fread(b, 1, 2, f) == 2 && b[0] == 0 && b[1] == 0) { + return cmListFileLexer_BOM_UTF32LE; + } + if (fsetpos(f, &p) != 0) { + return cmListFileLexer_BOM_Broken; + } + return cmListFileLexer_BOM_UTF16LE; + } + } + if (fseek(f, 0, SEEK_SET) != 0) { + return cmListFileLexer_BOM_Broken; + } + return cmListFileLexer_BOM_None; +} + +/*--------------------------------------------------------------------------*/ +int cmListFileLexer_SetFileName(cmListFileLexer* lexer, const char* name, + cmListFileLexer_BOM* bom) +{ + int result = 1; + cmListFileLexerDestroy(lexer); + if (name) { + lexer->file = fopen(name, "rb"); + if (lexer->file) { + if (bom) { + *bom = cmListFileLexer_ReadBOM(lexer->file); + } + } else { + result = 0; + } + } + cmListFileLexerInit(lexer); + return result; +} + +/*--------------------------------------------------------------------------*/ +int cmListFileLexer_SetString(cmListFileLexer* lexer, const char* text, int length) +{ + int result = 1; + cmListFileLexerDestroy(lexer); + if (text) { + lexer->string_buffer = (char *) text; + lexer->string_position = lexer->string_buffer; + lexer->string_left = length; + } + cmListFileLexerInit(lexer); + return result; +} + +/*--------------------------------------------------------------------------*/ +cmListFileLexer_Token* cmListFileLexer_Scan(cmListFileLexer* lexer) +{ + if (!lexer->file && !lexer->string_buffer) { + return 0; + } + if (cmListFileLexer_yylex(lexer->scanner, lexer)) { + return &lexer->token; + } else { + cmListFileLexer_SetFileName(lexer, 0, 0); + return 0; + } +} + +/*--------------------------------------------------------------------------*/ +long cmListFileLexer_GetCurrentLine(cmListFileLexer* lexer) +{ + return lexer->line; +} + +/*--------------------------------------------------------------------------*/ +long cmListFileLexer_GetCurrentColumn(cmListFileLexer* lexer) +{ + return lexer->column; +} + +/*--------------------------------------------------------------------------*/ +const char* cmListFileLexer_GetTypeAsString(cmListFileLexer* lexer, + cmListFileLexer_Type type) +{ + (void)lexer; + switch (type) { + case cmListFileLexer_Token_None: + return "nothing"; + case cmListFileLexer_Token_Space: + return "space"; + case cmListFileLexer_Token_Newline: + return "newline"; + case cmListFileLexer_Token_Identifier: + return "identifier"; + case cmListFileLexer_Token_ParenLeft: + return "left paren"; + case cmListFileLexer_Token_ParenRight: + return "right paren"; + case cmListFileLexer_Token_ArgumentUnquoted: + return "unquoted argument"; + case cmListFileLexer_Token_ArgumentQuoted: + return "quoted argument"; + case cmListFileLexer_Token_ArgumentBracket: + return "bracket argument"; + case cmListFileLexer_Token_CommentBracket: + return "bracket comment"; + case cmListFileLexer_Token_BadCharacter: + return "bad character"; + case cmListFileLexer_Token_BadBracket: + return "unterminated bracket"; + case cmListFileLexer_Token_BadString: + return "unterminated string"; + } + return "unknown token"; +} + diff --git a/src/plugins/cmakeprojectmanager/3rdparty/cmake/cmListFileLexer.h b/src/plugins/cmakeprojectmanager/3rdparty/cmake/cmListFileLexer.h new file mode 100644 index 00000000000..14ee2c1ba1e --- /dev/null +++ b/src/plugins/cmakeprojectmanager/3rdparty/cmake/cmListFileLexer.h @@ -0,0 +1,68 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/* NOLINTNEXTLINE(modernize-use-using) */ +typedef enum cmListFileLexer_Type_e +{ + cmListFileLexer_Token_None, + cmListFileLexer_Token_Space, + cmListFileLexer_Token_Newline, + cmListFileLexer_Token_Identifier, + cmListFileLexer_Token_ParenLeft, + cmListFileLexer_Token_ParenRight, + cmListFileLexer_Token_ArgumentUnquoted, + cmListFileLexer_Token_ArgumentQuoted, + cmListFileLexer_Token_ArgumentBracket, + cmListFileLexer_Token_CommentBracket, + cmListFileLexer_Token_BadCharacter, + cmListFileLexer_Token_BadBracket, + cmListFileLexer_Token_BadString +} cmListFileLexer_Type; + +/* NOLINTNEXTLINE(modernize-use-using) */ +typedef struct cmListFileLexer_Token_s cmListFileLexer_Token; +struct cmListFileLexer_Token_s +{ + cmListFileLexer_Type type; + char* text; + int length; + int line; + int column; +}; + +enum cmListFileLexer_BOM_e +{ + cmListFileLexer_BOM_None, + cmListFileLexer_BOM_Broken, + cmListFileLexer_BOM_UTF8, + cmListFileLexer_BOM_UTF16BE, + cmListFileLexer_BOM_UTF16LE, + cmListFileLexer_BOM_UTF32BE, + cmListFileLexer_BOM_UTF32LE +}; + +/* NOLINTNEXTLINE(modernize-use-using) */ +typedef enum cmListFileLexer_BOM_e cmListFileLexer_BOM; + +/* NOLINTNEXTLINE(modernize-use-using) */ +typedef struct cmListFileLexer_s cmListFileLexer; + +cmListFileLexer* cmListFileLexer_New(void); +int cmListFileLexer_SetFileName(cmListFileLexer*, const char*, + cmListFileLexer_BOM* bom); +int cmListFileLexer_SetString(cmListFileLexer*, const char*, int length); +cmListFileLexer_Token* cmListFileLexer_Scan(cmListFileLexer*); +long cmListFileLexer_GetCurrentLine(cmListFileLexer*); +long cmListFileLexer_GetCurrentColumn(cmListFileLexer*); +const char* cmListFileLexer_GetTypeAsString(cmListFileLexer*, + cmListFileLexer_Type); +void cmListFileLexer_Delete(cmListFileLexer*); + +#ifdef __cplusplus +} /* extern "C" */ +#endif diff --git a/src/plugins/cmakeprojectmanager/3rdparty/cmake/cmListFileLexer.in.l b/src/plugins/cmakeprojectmanager/3rdparty/cmake/cmListFileLexer.in.l new file mode 100644 index 00000000000..b7a5c46687a --- /dev/null +++ b/src/plugins/cmakeprojectmanager/3rdparty/cmake/cmListFileLexer.in.l @@ -0,0 +1,543 @@ +%{ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +/* + +This file must be translated to C and modified to build everywhere. + +Run flex >= 2.6 like this: + + flex --nounistd -DFLEXINT_H --noline -ocmListFileLexer.cxx cmListFileLexer.in.l + +Modify cmListFileLexer.c: + - remove trailing whitespace: sed -i 's/\s*$//' cmListFileLexer.c + - remove blank lines at end of file: sed -i '${/^$/d;}' cmListFileLexer.c + - #include "cmStandardLexer.h" at the top: sed -i '1i#include "cmStandardLexer.h"' cmListFileLexer.c + +*/ + +/* IWYU pragma: no_forward_declare yyguts_t */ + +/* Setup the proper cmListFileLexer_yylex declaration. */ +#define YY_EXTRA_TYPE cmListFileLexer* +#define YY_DECL int cmListFileLexer_yylex (yyscan_t yyscanner, cmListFileLexer* lexer) + +#include "cmListFileLexer.h" + +/*--------------------------------------------------------------------------*/ +struct cmListFileLexer_s +{ + cmListFileLexer_Token token; + int bracket; + int comment; + int line; + int column; + int size; + FILE* file; + size_t cr; + char* string_buffer; + char* string_position; + int string_left; + yyscan_t scanner; +}; + +static void cmListFileLexerSetToken(cmListFileLexer* lexer, const char* text, + int length); +static void cmListFileLexerAppend(cmListFileLexer* lexer, const char* text, + int length); +static int cmListFileLexerInput(cmListFileLexer* lexer, char* buffer, + size_t bufferSize); +static void cmListFileLexerInit(cmListFileLexer* lexer); +static void cmListFileLexerDestroy(cmListFileLexer* lexer); + +/* Replace the lexer input function. */ +#undef YY_INPUT +#define YY_INPUT(buf, result, max_size) \ + do { result = cmListFileLexerInput(cmListFileLexer_yyget_extra(yyscanner), buf, max_size); } while (0) + +/*--------------------------------------------------------------------------*/ +%} + +%option prefix="cmListFileLexer_yy" + +%option reentrant +%option yylineno +%option noyywrap +%pointer +%x STRING +%x BRACKET +%x BRACKETEND +%x COMMENT + +MAKEVAR \$\([A-Za-z0-9_]*\) +UNQUOTED ([^ \0\t\r\n\(\)#\\\"[=]|\\[^\0\n]) +LEGACY {MAKEVAR}|{UNQUOTED}|\"({MAKEVAR}|{UNQUOTED}|[ \t[=])*\" + +%% + +\n { + lexer->token.type = cmListFileLexer_Token_Newline; + cmListFileLexerSetToken(lexer, yytext, yyleng); + ++lexer->line; + lexer->column = 1; + BEGIN(INITIAL); + return 1; +} + +#?\[=*\[\n? { + const char* bracket = yytext; + lexer->comment = yytext[0] == '#'; + if (lexer->comment) { + lexer->token.type = cmListFileLexer_Token_CommentBracket; + bracket += 1; + } else { + lexer->token.type = cmListFileLexer_Token_ArgumentBracket; + } + cmListFileLexerSetToken(lexer, "", 0); + lexer->bracket = strchr(bracket+1, '[') - bracket; + if (yytext[yyleng-1] == '\n') { + ++lexer->line; + lexer->column = 1; + } else { + lexer->column += yyleng; + } + BEGIN(BRACKET); +} + +# { + lexer->column += yyleng; + BEGIN(COMMENT); +} + +[^\0\n]* { + lexer->column += yyleng; +} + +\( { + lexer->token.type = cmListFileLexer_Token_ParenLeft; + cmListFileLexerSetToken(lexer, yytext, yyleng); + lexer->column += yyleng; + return 1; +} + +\) { + lexer->token.type = cmListFileLexer_Token_ParenRight; + cmListFileLexerSetToken(lexer, yytext, yyleng); + lexer->column += yyleng; + return 1; +} + +[A-Za-z_][A-Za-z0-9_]* { + lexer->token.type = cmListFileLexer_Token_Identifier; + cmListFileLexerSetToken(lexer, yytext, yyleng); + lexer->column += yyleng; + return 1; +} + +\]=* { + /* Handle ]]====]=======]*/ + cmListFileLexerAppend(lexer, yytext, yyleng); + lexer->column += yyleng; + if (yyleng == lexer->bracket) { + BEGIN(BRACKETEND); + } +} + +\] { + lexer->column += yyleng; + /* Erase the partial bracket from the token. */ + lexer->token.length -= lexer->bracket; + lexer->token.text[lexer->token.length] = 0; + BEGIN(INITIAL); + return 1; +} + +([^]\0\n])+ { + cmListFileLexerAppend(lexer, yytext, yyleng); + lexer->column += yyleng; +} + +\n { + cmListFileLexerAppend(lexer, yytext, yyleng); + ++lexer->line; + lexer->column = 1; + BEGIN(BRACKET); +} + +[^\0\n] { + cmListFileLexerAppend(lexer, yytext, yyleng); + lexer->column += yyleng; + BEGIN(BRACKET); +} + +<> { + lexer->token.type = cmListFileLexer_Token_BadBracket; + BEGIN(INITIAL); + return 1; +} + +({UNQUOTED}|=|\[=*{UNQUOTED})({UNQUOTED}|[[=])* { + lexer->token.type = cmListFileLexer_Token_ArgumentUnquoted; + cmListFileLexerSetToken(lexer, yytext, yyleng); + lexer->column += yyleng; + return 1; +} + +({MAKEVAR}|{UNQUOTED}|=|\[=*{LEGACY})({LEGACY}|[[=])* { + lexer->token.type = cmListFileLexer_Token_ArgumentUnquoted; + cmListFileLexerSetToken(lexer, yytext, yyleng); + lexer->column += yyleng; + return 1; +} + +\[ { + lexer->token.type = cmListFileLexer_Token_ArgumentUnquoted; + cmListFileLexerSetToken(lexer, yytext, yyleng); + lexer->column += yyleng; + return 1; +} + +\" { + lexer->token.type = cmListFileLexer_Token_ArgumentQuoted; + cmListFileLexerSetToken(lexer, "", 0); + lexer->column += yyleng; + BEGIN(STRING); +} + +([^\\\0\n\"]|\\[^\0\n])+ { + cmListFileLexerAppend(lexer, yytext, yyleng); + lexer->column += yyleng; +} + +\\\n { + /* Continuation: text is not part of string */ + ++lexer->line; + lexer->column = 1; +} + +\n { + cmListFileLexerAppend(lexer, yytext, yyleng); + ++lexer->line; + lexer->column = 1; +} + +\" { + lexer->column += yyleng; + BEGIN(INITIAL); + return 1; +} + +[^\0\n] { + cmListFileLexerAppend(lexer, yytext, yyleng); + lexer->column += yyleng; +} + +<> { + lexer->token.type = cmListFileLexer_Token_BadString; + BEGIN(INITIAL); + return 1; +} + +[ \t\r]+ { + lexer->token.type = cmListFileLexer_Token_Space; + cmListFileLexerSetToken(lexer, yytext, yyleng); + lexer->column += yyleng; + return 1; +} + +. { + lexer->token.type = cmListFileLexer_Token_BadCharacter; + cmListFileLexerSetToken(lexer, yytext, yyleng); + lexer->column += yyleng; + return 1; +} + +<> { + lexer->token.type = cmListFileLexer_Token_None; + cmListFileLexerSetToken(lexer, 0, 0); + return 0; +} + +%% + +/*--------------------------------------------------------------------------*/ +static void cmListFileLexerSetToken(cmListFileLexer* lexer, const char* text, + int length) +{ + /* Set the token line and column number. */ + lexer->token.line = lexer->line; + lexer->token.column = lexer->column; + + /* Use the same buffer if possible. */ + if (lexer->token.text) { + if (text && length < lexer->size) { + strcpy(lexer->token.text, text); + lexer->token.length = length; + return; + } + free(lexer->token.text); + lexer->token.text = 0; + lexer->size = 0; + } + + /* Need to extend the buffer. */ + if (text) { + lexer->token.text = strdup(text); + lexer->token.length = length; + lexer->size = length + 1; + } else { + lexer->token.length = 0; + } +} + +/*--------------------------------------------------------------------------*/ +static void cmListFileLexerAppend(cmListFileLexer* lexer, const char* text, + int length) +{ + char* temp; + int newSize; + + /* If the appended text will fit in the buffer, do not reallocate. */ + newSize = lexer->token.length + length + 1; + if (lexer->token.text && newSize <= lexer->size) { + strcpy(lexer->token.text + lexer->token.length, text); + lexer->token.length += length; + return; + } + + /* We need to extend the buffer. */ + temp = (char *)malloc(newSize); + if (lexer->token.text) { + memcpy(temp, lexer->token.text, lexer->token.length); + free(lexer->token.text); + } + memcpy(temp + lexer->token.length, text, length); + temp[lexer->token.length + length] = 0; + lexer->token.text = temp; + lexer->token.length += length; + lexer->size = newSize; +} + +/*--------------------------------------------------------------------------*/ +static int cmListFileLexerInput(cmListFileLexer* lexer, char* buffer, + size_t bufferSize) +{ + if (lexer) { + if (lexer->file) { + /* Convert CRLF -> LF explicitly. The C FILE "t"ext mode + does not convert newlines on all platforms. Move any + trailing CR to the start of the buffer for the next read. */ + size_t cr = lexer->cr; + size_t n; + buffer[0] = '\r'; + n = fread(buffer + cr, 1, bufferSize - cr, lexer->file); + if (n) { + char* o = buffer; + const char* i = buffer; + const char* e; + n += cr; + cr = (buffer[n - 1] == '\r') ? 1 : 0; + e = buffer + n - cr; + while (i != e) { + if (i[0] == '\r' && i[1] == '\n') { + ++i; + } + *o++ = *i++; + } + n = o - buffer; + } else { + n = cr; + cr = 0; + } + lexer->cr = cr; + return n; + } else if (lexer->string_left) { + int length = lexer->string_left; + if ((int)bufferSize < length) { + length = (int)bufferSize; + } + memcpy(buffer, lexer->string_position, length); + lexer->string_position += length; + lexer->string_left -= length; + return length; + } + } + return 0; +} + +/*--------------------------------------------------------------------------*/ +static void cmListFileLexerInit(cmListFileLexer* lexer) +{ + if (lexer->file || lexer->string_buffer) { + cmListFileLexer_yylex_init(&lexer->scanner); + cmListFileLexer_yyset_extra(lexer, lexer->scanner); + } +} + +/*--------------------------------------------------------------------------*/ +static void cmListFileLexerDestroy(cmListFileLexer* lexer) +{ + cmListFileLexerSetToken(lexer, 0, 0); + if (lexer->file || lexer->string_buffer) { + cmListFileLexer_yylex_destroy(lexer->scanner); + if (lexer->file) { + fclose(lexer->file); + lexer->file = 0; + } + if (lexer->string_buffer) { + lexer->string_buffer = 0; + lexer->string_left = 0; + lexer->string_position = 0; + } + } +} + +/*--------------------------------------------------------------------------*/ +cmListFileLexer* cmListFileLexer_New(void) +{ + cmListFileLexer* lexer = (cmListFileLexer*)malloc(sizeof(cmListFileLexer)); + if (!lexer) { + return 0; + } + memset(lexer, 0, sizeof(*lexer)); + lexer->line = 1; + lexer->column = 1; + return lexer; +} + +/*--------------------------------------------------------------------------*/ +void cmListFileLexer_Delete(cmListFileLexer* lexer) +{ + cmListFileLexer_SetFileName(lexer, 0, 0); + free(lexer); +} + +/*--------------------------------------------------------------------------*/ +static cmListFileLexer_BOM cmListFileLexer_ReadBOM(FILE* f) +{ + unsigned char b[2]; + if (fread(b, 1, 2, f) == 2) { + if (b[0] == 0xEF && b[1] == 0xBB) { + if (fread(b, 1, 1, f) == 1 && b[0] == 0xBF) { + return cmListFileLexer_BOM_UTF8; + } + } else if (b[0] == 0xFE && b[1] == 0xFF) { + /* UTF-16 BE */ + return cmListFileLexer_BOM_UTF16BE; + } else if (b[0] == 0 && b[1] == 0) { + if (fread(b, 1, 2, f) == 2 && b[0] == 0xFE && b[1] == 0xFF) { + return cmListFileLexer_BOM_UTF32BE; + } + } else if (b[0] == 0xFF && b[1] == 0xFE) { + fpos_t p; + fgetpos(f, &p); + if (fread(b, 1, 2, f) == 2 && b[0] == 0 && b[1] == 0) { + return cmListFileLexer_BOM_UTF32LE; + } + if (fsetpos(f, &p) != 0) { + return cmListFileLexer_BOM_Broken; + } + return cmListFileLexer_BOM_UTF16LE; + } + } + if (fseek(f, 0, SEEK_SET) != 0) { + return cmListFileLexer_BOM_Broken; + } + return cmListFileLexer_BOM_None; +} + +/*--------------------------------------------------------------------------*/ +int cmListFileLexer_SetFileName(cmListFileLexer* lexer, const char* name, + cmListFileLexer_BOM* bom) +{ + int result = 1; + cmListFileLexerDestroy(lexer); + if (name) { + lexer->file = fopen(name, "rb"); + if (lexer->file) { + if (bom) { + *bom = cmListFileLexer_ReadBOM(lexer->file); + } + } else { + result = 0; + } + } + cmListFileLexerInit(lexer); + return result; +} + +/*--------------------------------------------------------------------------*/ +int cmListFileLexer_SetString(cmListFileLexer* lexer, const char* text, int length) +{ + int result = 1; + cmListFileLexerDestroy(lexer); + if (text) { + lexer->string_buffer = (char *) text; + lexer->string_position = lexer->string_buffer; + lexer->string_left = length; + } + cmListFileLexerInit(lexer); + return result; +} + +/*--------------------------------------------------------------------------*/ +cmListFileLexer_Token* cmListFileLexer_Scan(cmListFileLexer* lexer) +{ + if (!lexer->file && !lexer->string_buffer) { + return 0; + } + if (cmListFileLexer_yylex(lexer->scanner, lexer)) { + return &lexer->token; + } else { + cmListFileLexer_SetFileName(lexer, 0, 0); + return 0; + } +} + +/*--------------------------------------------------------------------------*/ +long cmListFileLexer_GetCurrentLine(cmListFileLexer* lexer) +{ + return lexer->line; +} + +/*--------------------------------------------------------------------------*/ +long cmListFileLexer_GetCurrentColumn(cmListFileLexer* lexer) +{ + return lexer->column; +} + +/*--------------------------------------------------------------------------*/ +const char* cmListFileLexer_GetTypeAsString(cmListFileLexer* lexer, + cmListFileLexer_Type type) +{ + (void)lexer; + switch (type) { + case cmListFileLexer_Token_None: + return "nothing"; + case cmListFileLexer_Token_Space: + return "space"; + case cmListFileLexer_Token_Newline: + return "newline"; + case cmListFileLexer_Token_Identifier: + return "identifier"; + case cmListFileLexer_Token_ParenLeft: + return "left paren"; + case cmListFileLexer_Token_ParenRight: + return "right paren"; + case cmListFileLexer_Token_ArgumentUnquoted: + return "unquoted argument"; + case cmListFileLexer_Token_ArgumentQuoted: + return "quoted argument"; + case cmListFileLexer_Token_ArgumentBracket: + return "bracket argument"; + case cmListFileLexer_Token_CommentBracket: + return "bracket comment"; + case cmListFileLexer_Token_BadCharacter: + return "bad character"; + case cmListFileLexer_Token_BadBracket: + return "unterminated bracket"; + case cmListFileLexer_Token_BadString: + return "unterminated string"; + } + return "unknown token"; +} diff --git a/src/plugins/cmakeprojectmanager/3rdparty/cmake/cmStandardLexer.h b/src/plugins/cmakeprojectmanager/3rdparty/cmake/cmStandardLexer.h new file mode 100644 index 00000000000..08088613428 --- /dev/null +++ b/src/plugins/cmakeprojectmanager/3rdparty/cmake/cmStandardLexer.h @@ -0,0 +1,88 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#if defined(__linux) +/* Needed for glibc < 2.12 */ +// NOLINTNEXTLINE(bugprone-reserved-identifier) +# define _XOPEN_SOURCE 600 +#endif +#if !defined(_POSIX_C_SOURCE) && !defined(_WIN32) && !defined(__sun) && \ + !defined(__OpenBSD__) +/* POSIX APIs are needed */ +// NOLINTNEXTLINE(bugprone-reserved-identifier) +# define _POSIX_C_SOURCE 200809L +#endif +#if defined(__sun) && defined(__GNUC__) && !defined(__cplusplus) +/* C sources: for fileno and strdup */ +// NOLINTNEXTLINE(bugprone-reserved-identifier) +# define _XOPEN_SOURCE 600 +#endif +#if defined(__FreeBSD__) || defined(__NetBSD__) +/* For isascii */ +// NOLINTNEXTLINE(bugprone-reserved-identifier) +# define _XOPEN_SOURCE 700 +#endif + +/* Disable some warnings. */ +#if defined(_MSC_VER) +# pragma warning(disable : 4018) +# pragma warning(disable : 4127) +# pragma warning(disable : 4131) +# pragma warning(disable : 4244) +# pragma warning(disable : 4251) +# pragma warning(disable : 4267) +# pragma warning(disable : 4305) +# pragma warning(disable : 4309) +# pragma warning(disable : 4706) +# pragma warning(disable : 4786) +# pragma warning(disable : 4996) +#endif + +#if defined(__GNUC__) && !defined(__INTEL_COMPILER) +# if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 402 +# pragma GCC diagnostic ignored "-Wconversion" +# pragma GCC diagnostic ignored "-Wsign-compare" +# endif +# if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 403 +# pragma GCC diagnostic ignored "-Wsign-conversion" +# endif +#endif + +#if defined(__LCC__) +# pragma diag_suppress 1873 /* comparison between signed and unsigned */ +#endif + +#if defined(__NVCOMPILER) +# pragma diag_suppress 111 /* statement is unreachable */ +# pragma diag_suppress 550 /* variable set but never used */ +#endif + +/* Make sure isatty is available. */ +#if defined(_WIN32) && !defined(__CYGWIN__) +# include +# if defined(_MSC_VER) +# define isatty _isatty +# endif +#else +# include // IWYU pragma: export +#endif + +/* Make sure malloc and free are available on QNX. */ +#ifdef __QNX__ +# include +#endif + +/* Disable features we do not need. */ +#define YY_NEVER_INTERACTIVE 1 +#define YY_NO_INPUT 1 +#define YY_NO_UNPUT 1 +#define ECHO + +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; diff --git a/src/plugins/cmakeprojectmanager/CMakeLists.txt b/src/plugins/cmakeprojectmanager/CMakeLists.txt index 683a2a407a8..057b4139459 100644 --- a/src/plugins/cmakeprojectmanager/CMakeLists.txt +++ b/src/plugins/cmakeprojectmanager/CMakeLists.txt @@ -2,6 +2,7 @@ add_qtc_plugin(CMakeProjectManager PLUGIN_CLASS CMakeProjectPlugin DEPENDS QmlJS PLUGIN_DEPENDS Core CppEditor ProjectExplorer TextEditor QtSupport + INCLUDES 3dparty/cmake SOURCES builddirparameters.cpp builddirparameters.h cmake_global.h @@ -15,8 +16,6 @@ add_qtc_plugin(CMakeProjectManager cmakeeditor.cpp cmakeeditor.h cmakefilecompletionassist.cpp cmakefilecompletionassist.h cmakeformatter.cpp cmakeformatter.h - cmakeformatteroptionspage.cpp cmakeformatteroptionspage.h - cmakeformattersettings.cpp cmakeformattersettings.h cmakeindenter.cpp cmakeindenter.h cmakeinstallstep.cpp cmakeinstallstep.h cmakekitinformation.cpp cmakekitinformation.h @@ -44,4 +43,7 @@ add_qtc_plugin(CMakeProjectManager presetsparser.cpp presetsparser.h presetsmacros.cpp presetsmacros.h projecttreehelper.cpp projecttreehelper.h + 3rdparty/cmake/cmListFileCache.cxx + 3rdparty/cmake/cmListFileLexer.cxx + 3rdparty/cmake/cmListFileCache.h ) diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp index 494d7ee67d7..5ddc68c1c23 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp @@ -41,7 +41,7 @@ #include #include #include -#include +#include #include #include @@ -54,7 +54,6 @@ #include #include #include -#include #include #include #include @@ -68,6 +67,7 @@ #include #include #include +#include #include #include #include @@ -250,7 +250,7 @@ CMakeBuildSettingsWidget::CMakeBuildSettingsWidget(CMakeBuildSystem *bs) : m_configView->setUniformRowHeights(true); m_configView->setSortingEnabled(true); m_configView->sortByColumn(0, Qt::AscendingOrder); - (void) new HeaderViewStretcher(m_configView->header(), 0); + m_configView->header()->setSectionResizeMode(QHeaderView::Stretch); m_configView->setSelectionMode(QAbstractItemView::ExtendedSelection); m_configView->setSelectionBehavior(QAbstractItemView::SelectItems); m_configView->setAlternatingRowColors(true); @@ -335,8 +335,8 @@ CMakeBuildSettingsWidget::CMakeBuildSettingsWidget(CMakeBuildSystem *bs) : Column { Form { - buildDirAspect, - bc->aspect(), + buildDirAspect, br, + bc->aspect(), br, qmlDebugAspect }, m_warningMessageLabel, @@ -347,19 +347,21 @@ CMakeBuildSettingsWidget::CMakeBuildSettingsWidget(CMakeBuildSystem *bs) : Column { cmakeConfiguration, Row { - bc->aspect(), + bc->aspect(), br, bc->aspect() }, m_reconfigureButton, } }, configureEnvironmentAspectWidget - } - }.attachTo(details, WithoutMargins); + }, + noMargin + }.attachTo(details); Column { m_configureDetailsWidget, - }.attachTo(this, WithoutMargins); + noMargin + }.attachTo(this); updateAdvancedCheckBox(); setError(m_buildSystem->error()); @@ -588,23 +590,18 @@ void CMakeBuildSettingsWidget::batchEditConfiguration() void CMakeBuildSettingsWidget::reconfigureWithInitialParameters() { auto settings = CMakeSpecificSettings::instance(); - bool doNotAsk = !settings->askBeforeReConfigureInitialParams.value(); - if (!doNotAsk) { - QDialogButtonBox::StandardButton reply = CheckableMessageBox::question( - Core::ICore::dialogParent(), - Tr::tr("Re-configure with Initial Parameters"), - Tr::tr("Clear CMake configuration and configure with initial parameters?"), - Tr::tr("Do not ask again"), - &doNotAsk, - QDialogButtonBox::Yes | QDialogButtonBox::No, - QDialogButtonBox::Yes); + QMessageBox::StandardButton reply = CheckableMessageBox::question( + Core::ICore::dialogParent(), + Tr::tr("Re-configure with Initial Parameters"), + Tr::tr("Clear CMake configuration and configure with initial parameters?"), + settings->askBeforeReConfigureInitialParams.checkableDecider(), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::Yes); - settings->askBeforeReConfigureInitialParams.setValue(!doNotAsk); - settings->writeSettings(Core::ICore::settings()); + settings->writeSettings(Core::ICore::settings()); - if (reply != QDialogButtonBox::Yes) { - return; - } + if (reply != QMessageBox::Yes) { + return; } m_buildSystem->clearCMakeCache(); @@ -643,7 +640,7 @@ void CMakeBuildSettingsWidget::updateInitialCMakeArguments() // As the user would expect to have e.g. "--preset" from "Initial Configuration" // to "Current Configuration" as additional parameters m_buildSystem->setAdditionalCMakeArguments(ProcessArgs::splitArgs( - bc->aspect()->value())); + bc->aspect()->value(), HostOsInfo::hostOs())); } void CMakeBuildSettingsWidget::kitCMakeConfiguration() @@ -663,17 +660,19 @@ void CMakeBuildSettingsWidget::kitCMakeConfiguration() CMakeGeneratorKitAspect generatorAspect; CMakeConfigurationKitAspect configurationKitAspect; - auto layout = new QGridLayout(dialog); - + Layouting::Grid grid; KitAspectWidget *widget = kitAspect.createConfigWidget(m_buildSystem->kit()); widget->setParent(dialog); - widget->addToLayoutWithLabel(layout->parentWidget()); + widget->addToLayoutWithLabel(grid, dialog); widget = generatorAspect.createConfigWidget(m_buildSystem->kit()); widget->setParent(dialog); - widget->addToLayoutWithLabel(layout->parentWidget()); + widget->addToLayoutWithLabel(grid, dialog); widget = configurationKitAspect.createConfigWidget(m_buildSystem->kit()); widget->setParent(dialog); - widget->addToLayoutWithLabel(layout->parentWidget()); + widget->addToLayoutWithLabel(grid, dialog); + grid.attachTo(dialog); + + auto layout = qobject_cast(dialog->layout()); layout->setColumnStretch(1, 1); @@ -701,7 +700,7 @@ void CMakeBuildSettingsWidget::updateConfigureDetailsWidgetsSummary( const FilePath buildDirectory = bc ? bc->buildDirectory() : "."; cmd.addArgs({"-S", m_buildSystem->projectDirectory().path()}); - cmd.addArgs({"-B", buildDirectory.onDevice(cmd.executable()).path()}); + cmd.addArgs({"-B", buildDirectory.path()}); cmd.addArgs(configurationArguments); params.setCommandLine(cmd); @@ -826,9 +825,10 @@ void CMakeBuildSettingsWidget::updateFromKit() // Then the additional parameters const QStringList additionalKitCMake = ProcessArgs::splitArgs( - CMakeConfigurationKitAspect::additionalConfiguration(k)); + CMakeConfigurationKitAspect::additionalConfiguration(k), HostOsInfo::hostOs()); const QStringList additionalInitialCMake = ProcessArgs::splitArgs( - m_buildSystem->buildConfiguration()->aspect()->value()); + m_buildSystem->buildConfiguration()->aspect()->value(), + HostOsInfo::hostOs()); QStringList mergedArgumentList; std::set_union(additionalInitialCMake.begin(), @@ -1137,7 +1137,7 @@ static CommandLine defaultInitialCMakeCommand(const Kit *k, const QString buildT // Package manager auto setup if (Internal::CMakeSpecificSettings::instance()->packageManagerAutoSetup.value()) { cmd.addArg(QString("-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=" - "%{buildDir}/%1/auto-setup.cmake") + "%{BuildConfig:BuildDirectory:NativeFilePath}/%1/auto-setup.cmake") .arg(Constants::PACKAGE_MANAGER_DIR)); } @@ -1177,12 +1177,6 @@ static void addCMakeConfigurePresetToInitialArguments(QStringList &initialArgume initialArguments.removeIf( [presetArgument](const QString &item) { return item == presetArgument; }); - // Remove the -DQTC_KIT_DEFAULT_CONFIG_HASH argument - const QString presetHashArgument - = CMakeConfigurationKitAspect::kitDefaultConfigHashItem(k).toArgument(); - initialArguments.removeIf( - [presetHashArgument](const QString &item) { return item == presetHashArgument; }); - PresetsDetails::ConfigurePreset configurePreset = Utils::findOrDefault(project->presetsData().configurePresets, [presetName](const PresetsDetails::ConfigurePreset &preset) { @@ -1743,7 +1737,8 @@ void CMakeBuildSystem::setInitialCMakeArguments(const QStringList &args) QStringList CMakeBuildSystem::additionalCMakeArguments() const { - return ProcessArgs::splitArgs(buildConfiguration()->aspect()->value()); + return ProcessArgs::splitArgs(buildConfiguration()->aspect()->value(), + HostOsInfo::hostOs()); } void CMakeBuildSystem::setAdditionalCMakeArguments(const QStringList &args) @@ -1766,7 +1761,8 @@ void CMakeBuildSystem::filterConfigArgumentsFromAdditionalCMakeArguments() // which is already part of the CMake variables and should not be also // in the addtional CMake options const QStringList arguments = ProcessArgs::splitArgs( - buildConfiguration()->aspect()->value()); + buildConfiguration()->aspect()->value(), + HostOsInfo::hostOs()); QStringList unknownOptions; const CMakeConfig config = CMakeConfig::fromArguments(arguments, unknownOptions); @@ -2083,8 +2079,8 @@ void CMakeBuildConfiguration::addToEnvironment(Utils::Environment &env) const return; auto settings = CMakeSpecificSettings::instance(); - if (!settings->ninjaPath.filePath().isEmpty()) { - const Utils::FilePath ninja = settings->ninjaPath.filePath(); + if (!settings->ninjaPath().isEmpty()) { + const Utils::FilePath ninja = settings->ninjaPath(); env.appendOrSetPath(ninja.isFile() ? ninja.parentDir() : ninja); } } @@ -2162,7 +2158,7 @@ const QStringList InitialCMakeArgumentsAspect::allValues() const return ci.toArgument(nullptr); }); - initialCMakeArguments.append(ProcessArgs::splitArgs(value())); + initialCMakeArguments.append(ProcessArgs::splitArgs(value(), HostOsInfo::hostOs())); return initialCMakeArguments; } @@ -2281,6 +2277,7 @@ public: ConfigureEnvironmentAspect::ConfigureEnvironmentAspect(ProjectExplorer::Target *target) { setIsLocal(true); + setAllowPrintOnRun(false); setConfigWidgetCreator( [this, target] { return new ConfigureEnvironmentAspectWidget(this, target); }); addSupportedBaseEnvironment(Tr::tr("Clean Environment"), {}); diff --git a/src/plugins/cmakeprojectmanager/cmakebuildstep.cpp b/src/plugins/cmakeprojectmanager/cmakebuildstep.cpp index ccb21ccfb3e..2d2501ab03b 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildstep.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildstep.cpp @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -44,6 +45,8 @@ namespace CMakeProjectManager::Internal { const char BUILD_TARGETS_KEY[] = "CMakeProjectManager.MakeStep.BuildTargets"; const char CMAKE_ARGUMENTS_KEY[] = "CMakeProjectManager.MakeStep.CMakeArguments"; const char TOOL_ARGUMENTS_KEY[] = "CMakeProjectManager.MakeStep.AdditionalArguments"; +const char USE_STAGING_KEY[] = "CMakeProjectManager.MakeStep.UseStaging"; +const char STAGING_DIR_KEY[] = "CMakeProjectManager.MakeStep.StagingDir"; const char IOS_AUTOMATIC_PROVISIONG_UPDATES_ARGUMENTS_KEY[] = "CMakeProjectManager.MakeStep.iOSAutomaticProvisioningUpdates"; const char CLEAR_SYSTEM_ENVIRONMENT_KEY[] = "CMakeProjectManager.MakeStep.ClearSystemEnvironment"; @@ -156,7 +159,27 @@ Qt::ItemFlags CMakeTargetItem::flags(int) const // CMakeBuildStep -CMakeBuildStep::CMakeBuildStep(BuildStepList *bsl, Utils::Id id) : +static QString initialStagingDir() +{ + // Avoid actual file accesses. + auto rg = QRandomGenerator::global(); + const qulonglong rand = rg->generate64(); + char buf[sizeof(rand)]; + memcpy(&buf, &rand, sizeof(rand)); + const QByteArray ba = QByteArray(buf, sizeof(buf)).toHex(); + return QString::fromUtf8("/tmp/Qt-Creator-staging-" + ba); +} + +static bool buildAndRunOnSameDevice(Kit *kit) +{ + IDeviceConstPtr runDevice = DeviceKitAspect::device(kit); + IDeviceConstPtr buildDevice = BuildDeviceKitAspect::device(kit); + QTC_ASSERT(runDevice, return false); + QTC_ASSERT(buildDevice, return false); + return runDevice->id() == buildDevice->id(); +} + +CMakeBuildStep::CMakeBuildStep(BuildStepList *bsl, Id id) : CMakeAbstractProcessStep(bsl, id) { m_cmakeArguments = addAspect(); @@ -169,6 +192,16 @@ CMakeBuildStep::CMakeBuildStep(BuildStepList *bsl, Utils::Id id) : m_toolArguments->setLabelText(Tr::tr("Tool arguments:")); m_toolArguments->setDisplayStyle(StringAspect::LineEditDisplay); + m_useStaging = addAspect(); + m_useStaging->setSettingsKey(USE_STAGING_KEY); + m_useStaging->setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBox); + m_useStaging->setDefaultValue(!buildAndRunOnSameDevice(kit())); + + m_stagingDir = addAspect(); + m_stagingDir->setSettingsKey(STAGING_DIR_KEY); + m_stagingDir->setLabelText(Tr::tr("Staging directory:")); + m_stagingDir->setDefaultValue(initialStagingDir()); + Kit *kit = buildConfiguration()->kit(); if (CMakeBuildConfiguration::isIos(kit)) { m_useiOSAutomaticProvisioningUpdates = addAspect(); @@ -199,6 +232,9 @@ CMakeBuildStep::CMakeBuildStep(BuildStepList *bsl, Utils::Id id) : if (!env.expandedValueForKey("NINJA_STATUS").startsWith(ninjaProgressString)) env.set("NINJA_STATUS", ninjaProgressString + "%o/sec] "); env.modify(m_userEnvironmentChanges); + + if (m_useStaging) + env.set("DESTDIR", currentStagingDir()); }); connect(target(), &Target::parsingFinished, this, [this](bool success) { @@ -210,7 +246,6 @@ CMakeBuildStep::CMakeBuildStep(BuildStepList *bsl, Utils::Id id) : this, &CMakeBuildStep::updateBuildTargetsModel); } - QVariantMap CMakeBuildStep::toMap() const { QVariantMap map(CMakeAbstractProcessStep::toMap()); @@ -375,15 +410,13 @@ void CMakeBuildStep::setBuildTargets(const QStringList &buildTargets) CommandLine CMakeBuildStep::cmakeCommand() const { - CommandLine cmd; - if (CMakeTool *tool = CMakeKitAspect::cmakeTool(kit())) - cmd.setExecutable(tool->cmakeExecutable()); + CommandLine cmd{cmakeExecutable()}; FilePath buildDirectory = "."; if (buildConfiguration()) buildDirectory = buildConfiguration()->buildDirectory(); - cmd.addArgs({"--build", buildDirectory.onDevice(cmd.executable()).path()}); + cmd.addArgs({"--build", buildDirectory.path()}); cmd.addArg("--target"); cmd.addArgs(Utils::transform(m_buildTargets, [this](const QString &s) { @@ -406,6 +439,9 @@ CommandLine CMakeBuildStep::cmakeCommand() const if (!m_cmakeArguments->value().isEmpty()) cmd.addArgs(m_cmakeArguments->value(), CommandLine::Raw); + if (m_useStaging->value()) + cmd.addArg("install"); + bool toolArgumentsSpecified = false; if (!m_toolArguments->value().isEmpty()) { cmd.addArg("--"); @@ -466,6 +502,12 @@ QWidget *CMakeBuildStep::createConfigWidget() QString summaryText = param.summary(displayName()); + m_stagingDir->setEnabled(m_useStaging->value()); + if (m_useStaging->value()) { + summaryText.append(" " + Tr::tr("and stage at %2 for %3") + .arg(currentStagingDir(), currentInstallPrefix())); + } + if (!m_buildPreset.isEmpty()) { const CMakeProject *cp = static_cast(project()); @@ -519,28 +561,32 @@ QWidget *CMakeBuildStep::createConfigWidget() envWidget->setBaseEnvironmentText(baseEnvironmentText()); }); - builder.addRow(clearBox); - builder.addRow(envWidget); + builder.addRow({clearBox}); + builder.addRow({envWidget}); }; Layouting::Form builder; - builder.addRow(m_cmakeArguments); - builder.addRow(m_toolArguments); + builder.addRow({m_cmakeArguments}); + builder.addRow({m_toolArguments}); + builder.addRow({Tr::tr("Stage for installation:"), Layouting::Row{m_useStaging, m_stagingDir}}); if (m_useiOSAutomaticProvisioningUpdates) - builder.addRow(m_useiOSAutomaticProvisioningUpdates); + builder.addRow({m_useiOSAutomaticProvisioningUpdates}); builder.addRow({new QLabel(Tr::tr("Targets:")), frame}); if (!isCleanStep() && !m_buildPreset.isEmpty()) createAndAddEnvironmentWidgets(builder); - auto widget = builder.emerge(Layouting::WithoutMargins); + builder.addItem(Layouting::noMargin); + auto widget = builder.emerge(); updateDetails(); connect(m_cmakeArguments, &StringAspect::changed, this, updateDetails); connect(m_toolArguments, &StringAspect::changed, this, updateDetails); + connect(m_useStaging, &BoolAspect::changed, this, updateDetails); + connect(m_stagingDir, &StringAspect::changed, this, updateDetails); if (m_useiOSAutomaticProvisioningUpdates) connect(m_useiOSAutomaticProvisioningUpdates, &BoolAspect::changed, this, updateDetails); @@ -683,8 +729,62 @@ QString CMakeBuildStep::baseEnvironmentText() const return Tr::tr("System Environment"); } +QString CMakeBuildStep::currentInstallPrefix() const +{ + auto bs = qobject_cast(buildSystem()); + QTC_ASSERT(bs, return {}); + const CMakeConfig config = bs->configurationFromCMake(); + return QString::fromUtf8(config.valueOf("CMAKE_INSTALL_PREFIX")); +} + +QString CMakeBuildStep::currentStagingDir() const +{ + return m_stagingDir->filePath().path(); +} + +FilePath CMakeBuildStep::cmakeExecutable() const +{ + CMakeTool *tool = CMakeKitAspect::cmakeTool(kit()); + return tool ? tool->cmakeExecutable() : FilePath(); +} + +void CMakeBuildStep::updateDeploymentData() +{ + if (!m_useStaging->value()) + return; + + QString install = currentInstallPrefix(); + QString stagingDir = currentStagingDir(); + FilePath rootDir = cmakeExecutable().withNewPath(stagingDir); + Q_UNUSED(install); + + DeploymentData deploymentData; + deploymentData.setLocalInstallRoot(rootDir); + + const int startPos = rootDir.path().length(); + + const auto appFileNames = transform>(buildSystem()->applicationTargets(), + [](const BuildTargetInfo &appTarget) { return appTarget.targetFilePath.fileName(); }); + + auto handleFile = [&appFileNames, startPos, &deploymentData](const FilePath &filePath) { + const DeployableFile::Type type = appFileNames.contains(filePath.fileName()) + ? DeployableFile::TypeExecutable + : DeployableFile::TypeNormal; + const QString targetDir = filePath.parentDir().path().mid(startPos); + deploymentData.addFile(filePath, targetDir, type); + return IterationPolicy::Continue; + }; + + rootDir.iterateDirectory(handleFile, + {{}, QDir::Files | QDir::Hidden, QDirIterator::Subdirectories}); + + buildSystem()->setDeploymentData(deploymentData); +} + void CMakeBuildStep::finish(ProcessResult result) { + updateDeploymentData(); + emit progress(100, {}); AbstractProcessStep::finish(result); } diff --git a/src/plugins/cmakeprojectmanager/cmakebuildstep.h b/src/plugins/cmakeprojectmanager/cmakebuildstep.h index 96d53919e18..85ee46d953b 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildstep.h +++ b/src/plugins/cmakeprojectmanager/cmakebuildstep.h @@ -85,6 +85,10 @@ private: void doRun() override; QWidget *createConfigWidget() override; + Utils::FilePath cmakeExecutable() const; + QString currentInstallPrefix() const; + QString currentStagingDir() const; + QString defaultBuildTarget() const; bool isCleanStep() const; @@ -94,6 +98,7 @@ private: void handleBuildTargetsChanges(bool success); void recreateBuildTargetsModel(); void updateBuildTargetsModel(); + void updateDeploymentData(); QMetaObject::Connection m_runTrigger; @@ -102,6 +107,8 @@ private: Utils::StringAspect *m_cmakeArguments = nullptr; Utils::StringAspect *m_toolArguments = nullptr; Utils::BoolAspect *m_useiOSAutomaticProvisioningUpdates = nullptr; + Utils::BoolAspect *m_useStaging = nullptr; + Utils::FilePathAspect *m_stagingDir = nullptr; QString m_allTarget = "all"; QString m_installTarget = "install"; diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp index 726414dafc3..1bd19dffeb1 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp @@ -17,6 +17,8 @@ #include #include +#include +#include #include #include @@ -30,7 +32,11 @@ #include #include +#include +#include + #include +#include #include #include @@ -40,8 +46,8 @@ #include #include #include +#include #include -#include #include #include @@ -51,74 +57,11 @@ #include using namespace ProjectExplorer; +using namespace TextEditor; using namespace Utils; namespace CMakeProjectManager::Internal { -static void copySourcePathsToClipboard(const FilePaths &srcPaths, const ProjectNode *node) -{ - QClipboard *clip = QGuiApplication::clipboard(); - - QString data = Utils::transform(srcPaths, [projDir = node->filePath()](const FilePath &path) { - return path.relativePathFrom(projDir).cleanPath().toString(); - }).join(" "); - clip->setText(data); -} - -static void noAutoAdditionNotify(const FilePaths &filePaths, const ProjectNode *node) -{ - const FilePaths srcPaths = Utils::filtered(filePaths, [](const FilePath &file) { - const auto mimeType = Utils::mimeTypeForFile(file).name(); - return mimeType == CppEditor::Constants::C_SOURCE_MIMETYPE || - mimeType == CppEditor::Constants::C_HEADER_MIMETYPE || - mimeType == CppEditor::Constants::CPP_SOURCE_MIMETYPE || - mimeType == CppEditor::Constants::CPP_HEADER_MIMETYPE || - mimeType == ProjectExplorer::Constants::FORM_MIMETYPE || - mimeType == ProjectExplorer::Constants::RESOURCE_MIMETYPE || - mimeType == ProjectExplorer::Constants::SCXML_MIMETYPE; - }); - - if (!srcPaths.empty()) { - auto settings = CMakeSpecificSettings::instance(); - switch (settings->afterAddFileSetting.value()) { - case AskUser: { - bool checkValue{false}; - QDialogButtonBox::StandardButton reply = CheckableMessageBox::question( - Core::ICore::dialogParent(), - Tr::tr("Copy to Clipboard?"), - Tr::tr("Files are not automatically added to the " - "CMakeLists.txt file of the CMake project." - "\nCopy the path to the source files to the clipboard?"), - "Remember My Choice", - &checkValue, - QDialogButtonBox::Yes | QDialogButtonBox::No, - QDialogButtonBox::Yes); - if (checkValue) { - if (QDialogButtonBox::Yes == reply) - settings->afterAddFileSetting.setValue(CopyFilePath); - else if (QDialogButtonBox::No == reply) - settings->afterAddFileSetting.setValue(NeverCopyFilePath); - - settings->writeSettings(Core::ICore::settings()); - } - - if (QDialogButtonBox::Yes == reply) - copySourcePathsToClipboard(srcPaths, node); - - break; - } - - case CopyFilePath: { - copySourcePathsToClipboard(srcPaths, node); - break; - } - - case NeverCopyFilePath: - break; - } - } -} - static Q_LOGGING_CATEGORY(cmakeBuildSystemLog, "qtc.cmake.buildsystem", QtWarningMsg); // -------------------------------------------------------------------- @@ -258,29 +201,443 @@ void CMakeBuildSystem::triggerParsing() bool CMakeBuildSystem::supportsAction(Node *context, ProjectAction action, const Node *node) const { if (dynamic_cast(context)) - return action == ProjectAction::AddNewFile; - - if (dynamic_cast(context)) - return action == ProjectAction::AddNewFile; + return action == ProjectAction::AddNewFile || action == ProjectAction::AddExistingFile + || action == ProjectAction::AddExistingDirectory || action == ProjectAction::Rename + || action == ProjectAction::RemoveFile; return BuildSystem::supportsAction(context, action, node); } -bool CMakeBuildSystem::addFiles(Node *context, const FilePaths &filePaths, FilePaths *notAdded) +static QString newFilesForFunction(const std::string &cmakeFunction, + const FilePaths &filePaths, + const FilePath &projDir) { - if (auto n = dynamic_cast(context)) { - noAutoAdditionNotify(filePaths, n); - return true; // Return always true as autoadd is not supported! + auto relativeFilePaths = [projDir](const FilePaths &filePaths) { + return Utils::transform(filePaths, [projDir](const FilePath &path) { + return path.canonicalPath().relativePathFrom(projDir).cleanPath().toString(); + }); + }; + + if (cmakeFunction == "qt_add_qml_module" || cmakeFunction == "qt6_add_qml_module") { + FilePaths sourceFiles; + FilePaths resourceFiles; + FilePaths qmlFiles; + + for (const auto &file : filePaths) { + const auto mimeType = Utils::mimeTypeForFile(file); + if (mimeType.matchesName(CppEditor::Constants::CPP_SOURCE_MIMETYPE) + || mimeType.matchesName(CppEditor::Constants::CPP_HEADER_MIMETYPE) + || mimeType.matchesName(CppEditor::Constants::OBJECTIVE_C_SOURCE_MIMETYPE) + || mimeType.matchesName(CppEditor::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE)) { + sourceFiles << file; + } else if (mimeType.matchesName(QmlJSTools::Constants::QML_MIMETYPE) + || mimeType.matchesName(QmlJSTools::Constants::QMLUI_MIMETYPE) + || mimeType.matchesName(QmlJSTools::Constants::QMLPROJECT_MIMETYPE) + || mimeType.matchesName(QmlJSTools::Constants::JS_MIMETYPE) + || mimeType.matchesName(QmlJSTools::Constants::JSON_MIMETYPE)) { + qmlFiles << file; + } else { + resourceFiles << file; + } + } + + QStringList result; + if (!sourceFiles.isEmpty()) + result << QString("SOURCES %1").arg(relativeFilePaths(sourceFiles).join(" ")); + if (!resourceFiles.isEmpty()) + result << QString("RESOURCES %1").arg(relativeFilePaths(resourceFiles).join(" ")); + if (!qmlFiles.isEmpty()) + result << QString("QML_FILES %1").arg(relativeFilePaths(qmlFiles).join(" ")); + + return result.join("\n"); } + return relativeFilePaths(filePaths).join(" "); +} + +bool CMakeBuildSystem::addFiles(Node *context, const FilePaths &filePaths, FilePaths *notAdded) +{ if (auto n = dynamic_cast(context)) { - noAutoAdditionNotify(filePaths, n); - return true; // Return always true as autoadd is not supported! + const QString targetName = n->buildKey(); + auto target = Utils::findOrDefault(buildTargets(), + [targetName](const CMakeBuildTarget &target) { + return target.title == targetName; + }); + + if (target.backtrace.isEmpty()) { + *notAdded = filePaths; + return false; + } + const FilePath targetCMakeFile = target.backtrace.last().path; + const int targetDefinitionLine = target.backtrace.last().line; + + // Have a fresh look at the CMake file, not relying on a cached value + expected_str fileContent = targetCMakeFile.fileContents(); + cmListFile cmakeListFile; + std::string errorString; + if (fileContent) { + fileContent = fileContent->replace("\r\n", "\n"); + if (!cmakeListFile.ParseString(fileContent->toStdString(), + targetCMakeFile.fileName().toStdString(), + errorString)) { + *notAdded = filePaths; + return false; + } + } + + auto function = std::find_if(cmakeListFile.Functions.begin(), + cmakeListFile.Functions.end(), + [targetDefinitionLine](const auto &func) { + return func.Line() == targetDefinitionLine; + }); + + if (function == cmakeListFile.Functions.end()) { + *notAdded = filePaths; + return false; + } + + // Special case: when qt_add_executable and qt_add_qml_module use the same target name + // then qt_add_qml_module function should be used + const std::string target_name = targetName.toStdString(); + auto add_qml_module_func + = std::find_if(cmakeListFile.Functions.begin(), + cmakeListFile.Functions.end(), + [target_name](const auto &func) { + return (func.LowerCaseName() == "qt_add_qml_module" + || func.LowerCaseName() == "qt6_add_qml_module") + && func.Arguments().front().Value == target_name; + }); + if (add_qml_module_func != cmakeListFile.Functions.end()) + function = add_qml_module_func; + + const QString newSourceFiles = newFilesForFunction(function->LowerCaseName(), + filePaths, + n->filePath().canonicalPath()); + + static QSet knownFunctions{"add_executable", + "add_library", + "qt_add_executable", + "qt_add_library", + "qt6_add_executable", + "qt6_add_library", + "qt_add_qml_module", + "qt6_add_qml_module"}; + + int line = 0; + int column = 0; + int extraChars = 0; + QString snippet; + + auto afterFunctionLastArgument = + [&line, &column, &snippet, &extraChars, newSourceFiles](const auto &f) { + auto lastArgument = f->Arguments().back(); + + line = lastArgument.Line; + column = lastArgument.Column + static_cast(lastArgument.Value.size()) - 1; + snippet = QString("\n%1").arg(newSourceFiles); + + // Take into consideration the quotes + if (lastArgument.Delim == cmListFileArgument::Quoted) + extraChars = 2; + }; + + if (knownFunctions.contains(function->LowerCaseName())) { + afterFunctionLastArgument(function); + } else { + auto targetSourcesFunc = std::find_if(cmakeListFile.Functions.begin(), + cmakeListFile.Functions.end(), + [target_name](const auto &func) { + return func.LowerCaseName() + == "target_sources" + && func.Arguments().front().Value + == target_name; + }); + + if (targetSourcesFunc == cmakeListFile.Functions.end()) { + line = function->LineEnd() + 1; + column = 0; + snippet = QString("\ntarget_sources(%1\n PRIVATE\n %2\n)\n") + .arg(targetName) + .arg(newSourceFiles); + } else { + afterFunctionLastArgument(targetSourcesFunc); + } + } + + BaseTextEditor *editor = qobject_cast( + Core::EditorManager::openEditorAt({targetCMakeFile, line, column + extraChars}, + Constants::CMAKE_EDITOR_ID, + Core::EditorManager::DoNotMakeVisible)); + if (!editor) { + *notAdded = filePaths; + return false; + } + + editor->insert(snippet); + editor->editorWidget()->autoIndent(); + if (!Core::DocumentManager::saveDocument(editor->document())) + return false; + + return true; } return BuildSystem::addFiles(context, filePaths, notAdded); } +std::optional +CMakeBuildSystem::projectFileArgumentPosition(const QString &targetName, const QString &fileName) +{ + auto target = Utils::findOrDefault(buildTargets(), [targetName](const CMakeBuildTarget &target) { + return target.title == targetName; + }); + + if (target.backtrace.isEmpty()) + return std::nullopt; + + const FilePath targetCMakeFile = target.backtrace.last().path; + + // Have a fresh look at the CMake file, not relying on a cached value + expected_str fileContent = targetCMakeFile.fileContents(); + cmListFile cmakeListFile; + std::string errorString; + if (fileContent) { + fileContent = fileContent->replace("\r\n", "\n"); + if (!cmakeListFile.ParseString(fileContent->toStdString(), + targetCMakeFile.fileName().toStdString(), + errorString)) + return std::nullopt; + } + + const int targetDefinitionLine = target.backtrace.last().line; + + auto function = std::find_if(cmakeListFile.Functions.begin(), + cmakeListFile.Functions.end(), + [targetDefinitionLine](const auto &func) { + return func.Line() == targetDefinitionLine; + }); + + const std::string target_name = targetName.toStdString(); + auto targetSourcesFunc = std::find_if(cmakeListFile.Functions.begin(), + cmakeListFile.Functions.end(), + [target_name](const auto &func) { + return func.LowerCaseName() == "target_sources" + && func.Arguments().size() > 1 + && func.Arguments().front().Value + == target_name; + }); + auto addQmlModuleFunc = std::find_if(cmakeListFile.Functions.begin(), + cmakeListFile.Functions.end(), + [target_name](const auto &func) { + return (func.LowerCaseName() == "qt_add_qml_module" + || func.LowerCaseName() == "qt6_add_qml_module") + && func.Arguments().size() > 1 + && func.Arguments().front().Value + == target_name; + }); + + for (const auto &func : {function, targetSourcesFunc, addQmlModuleFunc}) { + if (func == cmakeListFile.Functions.end()) + continue; + auto filePathArgument = Utils::findOrDefault(func->Arguments(), + [file_name = fileName.toStdString()]( + const auto &arg) { + return arg.Value == file_name; + }); + + if (!filePathArgument.Value.empty()) { + return ProjectFileArgumentPosition{filePathArgument, targetCMakeFile, fileName}; + } else { + // Check if the filename is part of globbing variable result + const auto globFunctions = std::get<0>( + Utils::partition(cmakeListFile.Functions, [](const auto &f) { + return f.LowerCaseName() == "file" && f.Arguments().size() > 2 + && (f.Arguments().front().Value == "GLOB" + || f.Arguments().front().Value == "GLOB_RECURSE"); + })); + + const auto globVariables = Utils::transform(globFunctions, [](const auto &func) { + return std::string("${") + func.Arguments()[1].Value + "}"; + }); + + const auto haveGlobbing = Utils::anyOf(func->Arguments(), + [globVariables](const auto &arg) { + return globVariables.contains(arg.Value); + }); + + if (haveGlobbing) { + return ProjectFileArgumentPosition{filePathArgument, + targetCMakeFile, + fileName, + true}; + } + + // Check if the filename is part of a variable set by the user + const auto setFunctions = std::get<0>( + Utils::partition(cmakeListFile.Functions, [](const auto &f) { + return f.LowerCaseName() == "set" && f.Arguments().size() > 1; + })); + + for (const auto &arg : func->Arguments()) { + auto matchedFunctions = Utils::filtered(setFunctions, [arg](const auto &f) { + return arg.Value == std::string("${") + f.Arguments()[0].Value + "}"; + }); + + for (const auto &f : matchedFunctions) { + filePathArgument = Utils::findOrDefault(f.Arguments(), + [file_name = fileName.toStdString()]( + const auto &arg) { + return arg.Value == file_name; + }); + + if (!filePathArgument.Value.empty()) { + return ProjectFileArgumentPosition{filePathArgument, + targetCMakeFile, + fileName}; + } + } + } + } + } + + return std::nullopt; +} + +RemovedFilesFromProject CMakeBuildSystem::removeFiles(Node *context, + const FilePaths &filePaths, + FilePaths *notRemoved) +{ + FilePaths badFiles; + if (auto n = dynamic_cast(context)) { + const FilePath projDir = n->filePath().canonicalPath(); + const QString targetName = n->buildKey(); + + for (const auto &file : filePaths) { + const QString fileName + = file.canonicalPath().relativePathFrom(projDir).cleanPath().toString(); + + auto filePos = projectFileArgumentPosition(targetName, fileName); + if (filePos) { + if (!filePos.value().cmakeFile.exists()) { + badFiles << file; + continue; + } + + BaseTextEditor *editor = qobject_cast( + Core::EditorManager::openEditorAt({filePos.value().cmakeFile, + static_cast(filePos.value().argumentPosition.Line), + static_cast(filePos.value().argumentPosition.Column + - 1)}, + Constants::CMAKE_EDITOR_ID, + Core::EditorManager::DoNotMakeVisible)); + if (!editor) { + badFiles << file; + continue; + } + + // If quotes were used for the source file, remove the quotes too + int extraChars = 0; + if (filePos->argumentPosition.Delim == cmListFileArgument::Quoted) + extraChars = 2; + + if (!filePos.value().fromGlobbing) + editor->replace(filePos.value().relativeFileName.length() + extraChars, ""); + + editor->editorWidget()->autoIndent(); + if (!Core::DocumentManager::saveDocument(editor->document())) { + badFiles << file; + continue; + } + } else { + badFiles << file; + } + } + + if (notRemoved && !badFiles.isEmpty()) + *notRemoved = badFiles; + + return badFiles.isEmpty() ? RemovedFilesFromProject::Ok : RemovedFilesFromProject::Error; + } + + return RemovedFilesFromProject::Error; +} + +bool CMakeBuildSystem::canRenameFile(Node *context, + const FilePath &oldFilePath, + const FilePath &newFilePath) +{ + // "canRenameFile" will cause an actual rename after the function call. + // This will make the a sequence like + // canonicalPath().relativePathFrom(projDir).cleanPath().toString() + // to fail if the file doesn't exist on disk + // therefore cache the results for the subsequent "renameFile" call + // where oldFilePath has already been renamed as newFilePath. + + if (auto n = dynamic_cast(context)) { + const FilePath projDir = n->filePath().canonicalPath(); + const QString oldRelPathName + = oldFilePath.canonicalPath().relativePathFrom(projDir).cleanPath().toString(); + + const QString targetName = n->buildKey(); + + const QString key + = QStringList{projDir.path(), targetName, oldFilePath.path(), newFilePath.path()} + .join(";"); + + auto filePos = projectFileArgumentPosition(targetName, oldRelPathName); + if (!filePos) + return false; + + m_filesToBeRenamed.insert(key, filePos.value()); + return true; + } + return false; +} + +bool CMakeBuildSystem::renameFile(Node *context, + const FilePath &oldFilePath, + const FilePath &newFilePath) +{ + if (auto n = dynamic_cast(context)) { + const FilePath projDir = n->filePath().canonicalPath(); + const QString newRelPathName + = newFilePath.canonicalPath().relativePathFrom(projDir).cleanPath().toString(); + + const QString targetName = n->buildKey(); + const QString key + = QStringList{projDir.path(), targetName, oldFilePath.path(), newFilePath.path()}.join( + ";"); + + auto fileToRename = m_filesToBeRenamed.take(key); + if (!fileToRename.cmakeFile.exists()) + return false; + + BaseTextEditor *editor = qobject_cast( + Core::EditorManager::openEditorAt({fileToRename.cmakeFile, + static_cast(fileToRename.argumentPosition.Line), + static_cast(fileToRename.argumentPosition.Column + - 1)}, + Constants::CMAKE_EDITOR_ID, + Core::EditorManager::DoNotMakeVisible)); + if (!editor) + return false; + + // If quotes were used for the source file, skip the starting quote + if (fileToRename.argumentPosition.Delim == cmListFileArgument::Quoted) + editor->setCursorPosition(editor->position() + 1); + + if (!fileToRename.fromGlobbing) + editor->replace(fileToRename.relativeFileName.length(), newRelPathName); + + editor->editorWidget()->autoIndent(); + if (!Core::DocumentManager::saveDocument(editor->document())) + return false; + + return true; + } + + return false; +} + FilePaths CMakeBuildSystem::filesGeneratedFrom(const FilePath &sourceFile) const { FilePath project = projectDirectory(); @@ -522,13 +879,22 @@ void CMakeBuildSystem::checkAndReportError(QString &errorMessage) } } +static QSet projectFilesToWatch(const QSet &cmakeFiles) +{ + return Utils::transform(Utils::filtered(cmakeFiles, + [](const CMakeFileInfo &info) { + return !info.isGenerated; + }), + [](const CMakeFileInfo &info) { return info.path; }); +} + void CMakeBuildSystem::updateProjectData() { qCDebug(cmakeBuildSystemLog) << "Updating CMake project data"; QTC_ASSERT(m_treeScanner.isFinished() && !m_reader.isParsing(), return ); - buildConfiguration()->project()->setExtraProjectFiles(m_reader.projectFilesToWatch()); + buildConfiguration()->project()->setExtraProjectFiles(projectFilesToWatch(m_cmakeFiles)); CMakeConfig patchedConfig = configurationFromCMake(); { @@ -601,7 +967,7 @@ void CMakeBuildSystem::updateProjectData() for (RawProjectPart &rpp : rpps) { rpp.setQtVersion( kitInfo.projectPartQtVersion); // TODO: Check if project actually uses Qt. - const QString includeFileBaseDir = buildConfiguration()->buildDirectory().toString(); + const FilePath includeFileBaseDir = buildConfiguration()->buildDirectory(); QStringList cxxFlags = rpp.flagsForCxx.commandLineFlags; QStringList cFlags = rpp.flagsForC.commandLineFlags; addTargetFlagForIos(cxxFlags, cFlags, this, [this] { @@ -735,6 +1101,8 @@ void CMakeBuildSystem::handleParsingSucceeded(bool restoredFromBackup) return result; }); m_buildTargets += m_reader.takeBuildTargets(errorMessage); + m_cmakeFiles = m_reader.takeCMakeFileInfos(errorMessage); + checkAndReportError(errorMessage); } @@ -747,7 +1115,11 @@ void CMakeBuildSystem::handleParsingSucceeded(bool restoredFromBackup) m_ctestPath = tool->cmakeExecutable().withNewPath(m_reader.ctestPath()); setApplicationTargets(appTargets()); - setDeploymentData(deploymentData()); + + // Note: This is practically always wrong and resulting in an empty view. + // Setting the real data is triggered from a successful run of a + // MakeInstallStep. + setDeploymentData(deploymentDataFromFile()); QTC_ASSERT(m_waitingForParse, return ); m_waitingForParse = false; @@ -901,11 +1273,11 @@ void CMakeBuildSystem::runCTest() QTC_ASSERT(parameters.isValid(), return); ensureBuildDirectory(parameters); - m_ctestProcess.reset(new QtcProcess); + m_ctestProcess.reset(new Process); m_ctestProcess->setEnvironment(buildConfiguration()->environment()); m_ctestProcess->setWorkingDirectory(parameters.buildDirectory); m_ctestProcess->setCommand({m_ctestPath, { "-N", "--show-only=json-v1"}}); - connect(m_ctestProcess.get(), &QtcProcess::done, this, [this] { + connect(m_ctestProcess.get(), &Process::done, this, [this] { if (m_ctestProcess->result() == ProcessResult::FinishedWithSuccess) { const QJsonDocument json = QJsonDocument::fromJson(m_ctestProcess->readAllRawStandardOutput()); @@ -1064,7 +1436,7 @@ CommandLine CMakeBuildSystem::commandLineForTests(const QList &tests, return {m_ctestPath, args}; } -DeploymentData CMakeBuildSystem::deploymentData() const +DeploymentData CMakeBuildSystem::deploymentDataFromFile() const { DeploymentData result; @@ -1226,7 +1598,6 @@ void CMakeBuildSystem::updateInitialCMakeExpandableVars() "CMAKE_CXX_COMPILER", "QT_QMAKE_EXECUTABLE", "QT_HOST_PATH", - "CMAKE_PROJECT_INCLUDE_BEFORE", "CMAKE_TOOLCHAIN_FILE" }; for (const auto &var : singlePathList) { @@ -1299,7 +1670,7 @@ MakeInstallCommand CMakeBuildSystem::makeInstallCommand(const FilePath &installR buildDirectory = bc->buildDirectory(); cmd.command.addArg("--build"); - cmd.command.addArg(buildDirectory.onDevice(cmd.command.executable()).path()); + cmd.command.addArg(buildDirectory.path()); cmd.command.addArg("--target"); cmd.command.addArg(installTarget); @@ -1341,20 +1712,20 @@ void CMakeBuildSystem::runGenerator(Id id) const CMakeTool * const cmakeTool = CMakeKitAspect::cmakeTool(buildConfiguration()->target()->kit()); if (!cmakeTool) { - showError(Tr::tr("Kit does not have a cmake binary set")); + showError(Tr::tr("Kit does not have a cmake binary set.")); return; } const QString generator = id.toSetting().toString(); const FilePath outDir = buildConfiguration()->buildDirectory() / ("qtc_" + FileUtils::fileSystemFriendlyName(generator)); if (!outDir.ensureWritableDir()) { - showError(Tr::tr("Cannot create output directory \"%1\"").arg(outDir.toString())); + showError(Tr::tr("Cannot create output directory \"%1\".").arg(outDir.toString())); return; } CommandLine cmdLine(cmakeTool->cmakeExecutable(), {"-S", buildConfiguration()->target() ->project()->projectDirectory().toUserOutput(), "-G", generator}); if (!cmdLine.executable().isExecutableFile()) { - showError(Tr::tr("No valid cmake executable")); + showError(Tr::tr("No valid cmake executable.")); return; } const auto itemFilter = [](const CMakeConfigItem &item) { @@ -1380,19 +1751,19 @@ void CMakeBuildSystem::runGenerator(Id id) optionsAspect && !optionsAspect->value().isEmpty()) { cmdLine.addArgs(optionsAspect->value(), CommandLine::Raw); } - const auto proc = new QtcProcess(this); - connect(proc, &QtcProcess::done, proc, &QtcProcess::deleteLater); - connect(proc, &QtcProcess::readyReadStandardOutput, this, [proc] { + const auto proc = new Process(this); + connect(proc, &Process::done, proc, &Process::deleteLater); + connect(proc, &Process::readyReadStandardOutput, this, [proc] { Core::MessageManager::writeFlashing(QString::fromLocal8Bit(proc->readAllRawStandardOutput())); }); - connect(proc, &QtcProcess::readyReadStandardError, this, [proc] { + connect(proc, &Process::readyReadStandardError, this, [proc] { Core::MessageManager::writeDisrupting(QString::fromLocal8Bit(proc->readAllRawStandardError())); }); proc->setWorkingDirectory(outDir); proc->setEnvironment(buildConfiguration()->environment()); proc->setCommand(cmdLine); - Core::MessageManager::writeFlashing(Tr::tr("Running in %1: %2") - .arg(outDir.toUserOutput(), cmdLine.toUserOutput())); + Core::MessageManager::writeFlashing( + Tr::tr("Running in %1: %2.").arg(outDir.toUserOutput(), cmdLine.toUserOutput())); proc->start(); } diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.h b/src/plugins/cmakeprojectmanager/cmakebuildsystem.h index 802921f04b8..4770b4d84bc 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.h +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.h @@ -18,7 +18,7 @@ namespace ProjectExplorer { class ExtraCompiler; class FolderNode; } -namespace Utils { class QtcProcess; } +namespace Utils { class Process; } namespace CMakeProjectManager { @@ -48,6 +48,18 @@ public: bool addFiles(ProjectExplorer::Node *context, const Utils::FilePaths &filePaths, Utils::FilePaths *) final; + ProjectExplorer::RemovedFilesFromProject removeFiles(ProjectExplorer::Node *context, + const Utils::FilePaths &filePaths, + Utils::FilePaths *notRemoved + = nullptr) final; + + bool canRenameFile(ProjectExplorer::Node *context, + const Utils::FilePath &oldFilePath, + const Utils::FilePath &newFilePath) final; + bool renameFile(ProjectExplorer::Node *context, + const Utils::FilePath &oldFilePath, + const Utils::FilePath &newFilePath) final; + Utils::FilePaths filesGeneratedFrom(const Utils::FilePath &sourceFile) const final; QString name() const final { return QLatin1String("cmake"); } @@ -67,7 +79,7 @@ public: const QList appTargets() const; QStringList buildTargetTitles() const; const QList &buildTargets() const; - ProjectExplorer::DeploymentData deploymentData() const; + ProjectExplorer::DeploymentData deploymentDataFromFile() const; CMakeBuildConfiguration *cmakeBuildConfiguration() const; @@ -185,6 +197,16 @@ private: void runCTest(); + struct ProjectFileArgumentPosition + { + cmListFileArgument argumentPosition; + Utils::FilePath cmakeFile; + QString relativeFileName; + bool fromGlobbing = false; + }; + std::optional projectFileArgumentPosition( + const QString &targetName, const QString &fileName); + ProjectExplorer::TreeScanner m_treeScanner; std::shared_ptr m_allFiles; QHash m_mimeBinaryCache; @@ -199,6 +221,9 @@ private: CppEditor::CppProjectUpdater *m_cppCodeModelUpdater = nullptr; QList m_extraCompilers; QList m_buildTargets; + QSet m_cmakeFiles; + + QHash m_filesToBeRenamed; // Parsing state: BuildDirParameters m_parameters; @@ -208,7 +233,7 @@ private: // CTest integration Utils::FilePath m_ctestPath; - std::unique_ptr m_ctestProcess; + std::unique_ptr m_ctestProcess; QList m_testNames; CMakeConfig m_configurationFromCMake; diff --git a/src/plugins/cmakeprojectmanager/cmakeeditor.cpp b/src/plugins/cmakeprojectmanager/cmakeeditor.cpp index 9f507e14808..5193661e57b 100644 --- a/src/plugins/cmakeprojectmanager/cmakeeditor.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeeditor.cpp @@ -147,18 +147,17 @@ void CMakeEditorWidget::findLinkAt(const QTextCursor &cursor, int line = 0; int column = 0; convertPosition(cursor.position(), &line, &column); - const int positionInBlock = column - 1; const QString block = cursor.block().text(); // check if the current position is commented out const int hashPos = block.indexOf(QLatin1Char('#')); - if (hashPos >= 0 && hashPos < positionInBlock) + if (hashPos >= 0 && hashPos < column) return processLinkCallback(link); // find the beginning of a filename QString buffer; - int beginPos = positionInBlock - 1; + int beginPos = column; while (beginPos >= 0) { if (isValidFileNameChar(block, beginPos)) { buffer.prepend(block.at(beginPos)); @@ -169,7 +168,7 @@ void CMakeEditorWidget::findLinkAt(const QTextCursor &cursor, } // find the end of a filename - int endPos = positionInBlock; + int endPos = column; while (endPos < block.count()) { if (isValidFileNameChar(block, endPos)) { buffer.append(block.at(endPos)); @@ -199,8 +198,8 @@ void CMakeEditorWidget::findLinkAt(const QTextCursor &cursor, return processLinkCallback(link); } link.targetFilePath = Utils::FilePath::fromString(fileName); - link.linkTextStart = cursor.position() - positionInBlock + beginPos + 1; - link.linkTextEnd = cursor.position() - positionInBlock + endPos; + link.linkTextStart = cursor.position() - column + beginPos + 1; + link.linkTextEnd = cursor.position() - column + endPos; } processLinkCallback(link); } diff --git a/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp b/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp index 325a6012a53..01fbeaa2329 100644 --- a/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp +++ b/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp @@ -8,8 +8,9 @@ #include "cmaketool.h" #include -#include +#include #include + #include #include @@ -39,7 +40,7 @@ IAssistProposal *CMakeFileCompletionAssist::performAsync() Keywords kw; const Utils::FilePath &filePath = interface()->filePath(); if (!filePath.isEmpty() && filePath.toFileInfo().isFile()) { - Project *p = SessionManager::projectForFile(filePath); + Project *p = ProjectManager::projectForFile(filePath); if (p && p->activeTarget()) { CMakeTool *cmake = CMakeKitAspect::cmakeTool(p->activeTarget()->kit()); if (cmake && cmake->isValid()) diff --git a/src/plugins/cmakeprojectmanager/cmakeformatter.cpp b/src/plugins/cmakeprojectmanager/cmakeformatter.cpp index d9aa8b2c84a..dca6d47e203 100644 --- a/src/plugins/cmakeprojectmanager/cmakeformatter.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeformatter.cpp @@ -4,7 +4,6 @@ #include "cmakeformatter.h" -#include "cmakeformattersettings.h" #include "cmakeprojectconstants.h" #include "cmakeprojectmanagertr.h" @@ -12,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -20,54 +20,183 @@ #include #include +#include #include #include -#include -#include +#include +#include +#include +#include +#include + +using namespace Core; using namespace TextEditor; +using namespace Utils; -namespace CMakeProjectManager { -namespace Internal { +namespace CMakeProjectManager::Internal { -void CMakeFormatter::updateActions(Core::IEditor *editor) +class CMakeFormatterPrivate : public PagedSettings { - const bool enabled = editor && CMakeFormatterSettings::instance()->isApplicable(editor->document()); - m_formatFile->setEnabled(enabled); +public: + CMakeFormatterPrivate() + { + setSettingsGroups(Constants::CMAKEFORMATTER_SETTINGS_GROUP, + Constants::CMAKEFORMATTER_GENERAL_GROUP); + + setId(Constants::Settings::FORMATTER_ID); + setDisplayName(Tr::tr("Formatter")); + setDisplayCategory("CMake"); + setCategory(Constants::Settings::CATEGORY); + + command.setSettingsKey("autoFormatCommand"); + command.setDefaultValue("cmake-format"); + command.setExpectedKind(PathChooser::ExistingCommand); + + autoFormatOnSave.setSettingsKey("autoFormatOnSave"); + autoFormatOnSave.setLabelText(Tr::tr("Enable auto format on file save")); + + autoFormatOnlyCurrentProject.setSettingsKey("autoFormatOnlyCurrentProject"); + autoFormatOnlyCurrentProject.setDefaultValue(true); + autoFormatOnlyCurrentProject.setLabelText(Tr::tr("Restrict to files contained in the current project")); + + autoFormatMime.setSettingsKey("autoFormatMime"); + autoFormatMime.setDefaultValue("text/x-cmake"); + autoFormatMime.setLabelText(Tr::tr("Restrict to MIME types:")); + + setLayouter([this] { + using namespace Layouting; + return Column { + Row { Tr::tr("CMakeFormat command:"), command }, + Space(10), + Group { + title(Tr::tr("Automatic Formatting on File Save")), + autoFormatOnSave.groupChecker(), + Form { + autoFormatMime, br, + Span(2, autoFormatOnlyCurrentProject) + } + }, + st + }; + }); + + ActionContainer *menu = ActionManager::createMenu(Constants::CMAKEFORMATTER_MENU_ID); + menu->menu()->setTitle(Tr::tr("CMakeFormatter")); + menu->setOnAllDisabledBehavior(ActionContainer::Show); + ActionManager::actionContainer(Core::Constants::M_TOOLS)->addMenu(menu); + + Core::Command *cmd = ActionManager::registerAction(&formatFile, Constants::CMAKEFORMATTER_ACTION_ID); + connect(&formatFile, &QAction::triggered, this, [this] { + TextEditor::formatCurrentFile(formatCommand()); + }); + + ActionManager::actionContainer(Constants::CMAKEFORMATTER_MENU_ID)->addAction(cmd); + + auto updateActions = [this] { + auto editor = EditorManager::currentEditor(); + formatFile.setEnabled(editor && isApplicable(editor->document())); + }; + + connect(&autoFormatMime, &Utils::StringAspect::changed, + this, updateActions); + connect(EditorManager::instance(), &EditorManager::currentEditorChanged, + this, updateActions); + connect(EditorManager::instance(), &EditorManager::aboutToSave, + this, &CMakeFormatterPrivate::applyIfNecessary); + + readSettings(); + } + + bool isApplicable(const IDocument *document) const; + + void applyIfNecessary(IDocument *document) const; + + TextEditor::Command formatCommand() const + { + TextEditor::Command cmd; + cmd.setExecutable(command()); + cmd.setProcessing(TextEditor::Command::FileProcessing); + cmd.addOption("--in-place"); + cmd.addOption("%file"); + return cmd; + } + + FilePathAspect command{this}; + BoolAspect autoFormatOnSave{this}; + BoolAspect autoFormatOnlyCurrentProject{this}; + StringAspect autoFormatMime{this}; + + QAction formatFile{Tr::tr("Format &Current File")}; +}; + +bool CMakeFormatterPrivate::isApplicable(const IDocument *document) const +{ + if (!document) + return false; + + if (autoFormatMime.value().isEmpty()) + return true; + + const QStringList allowedMimeTypes = autoFormatMime.value().split(';'); + const MimeType documentMimeType = Utils::mimeTypeForName(document->mimeType()); + + return anyOf(allowedMimeTypes, [&documentMimeType](const QString &mime) { + return documentMimeType.inherits(mime); + }); } -void CMakeFormatter::formatFile() +void CMakeFormatterPrivate::applyIfNecessary(IDocument *document) const { - formatCurrentFile(command()); + if (!autoFormatOnSave.value()) + return; + + if (!document) + return; + + if (!isApplicable(document)) + return; + + // Check if file is contained in the current project (if wished) + if (autoFormatOnlyCurrentProject.value()) { + const ProjectExplorer::Project *pro = ProjectExplorer::ProjectTree::currentProject(); + if (!pro || pro->files([document](const ProjectExplorer::Node *n) { + return ProjectExplorer::Project::SourceFiles(n) + && n->filePath() == document->filePath(); + }).isEmpty()) { + return; + } + } + + TextEditor::Command command = formatCommand(); + if (!command.isValid()) + return; + + const QList editors = DocumentModel::editorsForDocument(document); + if (editors.isEmpty()) + return; + + IEditor *currentEditor = EditorManager::currentEditor(); + IEditor *editor = editors.contains(currentEditor) ? currentEditor : editors.first(); + if (auto widget = TextEditorWidget::fromEditor(editor)) + TextEditor::formatEditor(widget, command); } -Command CMakeFormatter::command() const +// CMakeFormatter + +CMakeFormatter::CMakeFormatter() + : d(new CMakeFormatterPrivate) +{} + +CMakeFormatter::~CMakeFormatter() { - Command command; - command.setExecutable(CMakeFormatterSettings::instance()->command()); - command.setProcessing(Command::FileProcessing); - command.addOption("--in-place"); - command.addOption("%file"); - return command; + delete d; } -bool CMakeFormatter::isApplicable(const Core::IDocument *document) const +void CMakeFormatter::applyIfNecessary(IDocument *document) const { - return CMakeFormatterSettings::instance()->isApplicable(document); + d->applyIfNecessary(document); } -void CMakeFormatter::initialize() -{ - m_formatFile = new QAction(Tr::tr("Format &Current File"), this); - Core::Command *cmd = Core::ActionManager::registerAction(m_formatFile, Constants::CMAKEFORMATTER_ACTION_ID); - connect(m_formatFile, &QAction::triggered, this, &CMakeFormatter::formatFile); - - Core::ActionManager::actionContainer(Constants::CMAKEFORMATTER_MENU_ID)->addAction(cmd); - - connect(CMakeFormatterSettings::instance(), &CMakeFormatterSettings::supportedMimeTypesChanged, - [this] { updateActions(Core::EditorManager::currentEditor()); }); -} - -} // namespace Internal -} // namespace CMakeProjectManager +} // CMakeProjectManager::Internal diff --git a/src/plugins/cmakeprojectmanager/cmakeformatter.h b/src/plugins/cmakeprojectmanager/cmakeformatter.h index f3fc3a28c53..9727c245a50 100644 --- a/src/plugins/cmakeprojectmanager/cmakeformatter.h +++ b/src/plugins/cmakeprojectmanager/cmakeformatter.h @@ -4,35 +4,20 @@ #pragma once -#include +namespace Core { class IDocument; } -#include "cmakeformatteroptionspage.h" +namespace CMakeProjectManager::Internal { -namespace Core { -class IDocument; -class IEditor; -} - -namespace CMakeProjectManager { -namespace Internal { - -class CMakeFormatter : public QObject +class CMakeFormatter { - Q_OBJECT - public: - void updateActions(Core::IEditor *editor); - TextEditor::Command command() const; - bool isApplicable(const Core::IDocument *document) const; + CMakeFormatter(); + ~CMakeFormatter(); - void initialize(); + void applyIfNecessary(Core::IDocument *document) const; private: - void formatFile(); - - QAction *m_formatFile = nullptr; - CMakeFormatterOptionsPage m_page; + class CMakeFormatterPrivate *d = nullptr; }; -} // namespace Internal -} // namespace CMakeProjectManager +} // CMakeProjectManager::Internal diff --git a/src/plugins/cmakeprojectmanager/cmakeformatteroptionspage.cpp b/src/plugins/cmakeprojectmanager/cmakeformatteroptionspage.cpp deleted file mode 100644 index a82e7be07af..00000000000 --- a/src/plugins/cmakeprojectmanager/cmakeformatteroptionspage.cpp +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (C) 2016 Lorenz Haas -// Copyright (C) 2022 Xavier BESSON -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "cmakeformatteroptionspage.h" - -#include "cmakeprojectconstants.h" -#include "cmakeprojectmanagertr.h" -#include "cmakeformattersettings.h" - -#include -#include - -#include -#include -#include -#include -#include - -namespace CMakeProjectManager::Internal { - -class CMakeFormatterOptionsPageWidget : public Core::IOptionsPageWidget -{ -public: - explicit CMakeFormatterOptionsPageWidget(); - -private: - void apply() final; - - Utils::PathChooser *m_command; - QCheckBox *m_autoFormat; - QLineEdit *m_autoFormatMime; - QCheckBox *m_autoFormatOnlyCurrentProject; -}; - -CMakeFormatterOptionsPageWidget::CMakeFormatterOptionsPageWidget() -{ - resize(817, 631); - - auto settings = CMakeFormatterSettings::instance(); - - m_autoFormat = new QCheckBox(Tr::tr("Enable auto format on file save")); - m_autoFormat->setChecked(settings->autoFormatOnSave()); - - auto mimeLabel = new QLabel(Tr::tr("Restrict to MIME types:")); - mimeLabel->setEnabled(false); - - m_autoFormatMime = new QLineEdit(settings->autoFormatMimeAsString()); - m_autoFormatMime->setEnabled(m_autoFormat->isChecked()); - - m_autoFormatOnlyCurrentProject = - new QCheckBox(Tr::tr("Restrict to files contained in the current project")); - m_autoFormatOnlyCurrentProject->setEnabled(m_autoFormat->isChecked()); - m_autoFormatOnlyCurrentProject->setChecked(settings->autoFormatOnlyCurrentProject()); - - m_command = new Utils::PathChooser; - m_command->setExpectedKind(Utils::PathChooser::ExistingCommand); - m_command->setCommandVersionArguments({"--version"}); - m_command->setPromptDialogTitle(Tr::tr("%1 Command").arg(Tr::tr("Formatter"))); - m_command->setFilePath(settings->command()); - - using namespace Utils::Layouting; - - Column { - Group { - title(Tr::tr("Automatic Formatting on File Save")), - Form { - Tr::tr("CMakeFormat command:"), m_command, br, - Span(2, m_autoFormat), br, - mimeLabel, m_autoFormatMime, br, - Span(2, m_autoFormatOnlyCurrentProject) - } - }, - st - }.attachTo(this); - - connect(m_autoFormat, &QCheckBox::toggled, m_autoFormatMime, &QLineEdit::setEnabled); - connect(m_autoFormat, &QCheckBox::toggled, mimeLabel, &QLabel::setEnabled); - connect(m_autoFormat, &QCheckBox::toggled, m_autoFormatOnlyCurrentProject, &QCheckBox::setEnabled); -} - -void CMakeFormatterOptionsPageWidget::apply() -{ - auto settings = CMakeFormatterSettings::instance(); - settings->setCommand(m_command->filePath().toString()); - settings->setAutoFormatOnSave(m_autoFormat->isChecked()); - settings->setAutoFormatMime(m_autoFormatMime->text()); - settings->setAutoFormatOnlyCurrentProject(m_autoFormatOnlyCurrentProject->isChecked()); - settings->save(); -} - -CMakeFormatterOptionsPage::CMakeFormatterOptionsPage() -{ - setId(Constants::Settings::FORMATTER_ID); - setDisplayName(Tr::tr("Formatter")); - setDisplayCategory("CMake"); - setCategory(Constants::Settings::CATEGORY); - setWidgetCreator([] { return new CMakeFormatterOptionsPageWidget; }); -} - -} // CMakeProjectManager::Internal diff --git a/src/plugins/cmakeprojectmanager/cmakeformatteroptionspage.h b/src/plugins/cmakeprojectmanager/cmakeformatteroptionspage.h deleted file mode 100644 index 08cfac3590c..00000000000 --- a/src/plugins/cmakeprojectmanager/cmakeformatteroptionspage.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (C) 2016 Lorenz Haas -// Copyright (C) 2022 Xavier BESSON -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -namespace CMakeProjectManager::Internal { - -class CMakeFormatterOptionsPage final : public Core::IOptionsPage -{ -public: - explicit CMakeFormatterOptionsPage(); -}; - -} // CMakeProjectManager::Internal diff --git a/src/plugins/cmakeprojectmanager/cmakeformattersettings.cpp b/src/plugins/cmakeprojectmanager/cmakeformattersettings.cpp deleted file mode 100644 index 4bb2218340d..00000000000 --- a/src/plugins/cmakeprojectmanager/cmakeformattersettings.cpp +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (C) 2016 Lorenz Haas -// Copyright (C) 2022 Xavier BESSON -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "cmakeformattersettings.h" -#include "cmakeprojectconstants.h" - -#include -#include -#include -#include -#include -#include - -namespace CMakeProjectManager { -namespace Internal { - -namespace { -const char AUTO_FORMAT_COMMAND[] = "autoFormatCommand"; -const char AUTO_FORMAT_MIME[] = "autoFormatMime"; -const char AUTO_FORMAT_ONLY_CURRENT_PROJECT[] = "autoFormatOnlyCurrentProject"; -const char AUTO_FORMAT_ON_SAVE[] = "autoFormatOnSave"; -} - -CMakeFormatterSettings::CMakeFormatterSettings(QObject* parent) - : QObject(parent) -{ - read(); -} - -CMakeFormatterSettings *CMakeFormatterSettings::instance() -{ - static CMakeFormatterSettings m_instance; - return &m_instance; -} - -void CMakeFormatterSettings::read() -{ - QSettings *s = Core::ICore::settings(); - s->beginGroup(Constants::CMAKEFORMATTER_SETTINGS_GROUP); - s->beginGroup(Constants::CMAKEFORMATTER_GENERAL_GROUP); - setCommand(s->value(AUTO_FORMAT_COMMAND, QString("cmake-format")).toString()); - m_autoFormatOnSave = s->value(AUTO_FORMAT_ON_SAVE, false).toBool(); - setAutoFormatMime(s->value(AUTO_FORMAT_MIME, QString("text/x-cmake")).toString()); - m_autoFormatOnlyCurrentProject = s->value(AUTO_FORMAT_ONLY_CURRENT_PROJECT, true).toBool(); - s->endGroup(); - s->endGroup(); -} - -void CMakeFormatterSettings::save() -{ - QSettings *s = Core::ICore::settings(); - s->beginGroup(Constants::CMAKEFORMATTER_SETTINGS_GROUP); - s->beginGroup(Constants::CMAKEFORMATTER_GENERAL_GROUP); - Utils::QtcSettings::setValueWithDefault(s, AUTO_FORMAT_COMMAND, m_command, QString("cmake-format")); - Utils::QtcSettings::setValueWithDefault(s, AUTO_FORMAT_ON_SAVE, m_autoFormatOnSave, false); - Utils::QtcSettings::setValueWithDefault(s, AUTO_FORMAT_MIME, autoFormatMimeAsString(), QString("text/x-cmake")); - Utils::QtcSettings::setValueWithDefault(s, AUTO_FORMAT_ONLY_CURRENT_PROJECT, m_autoFormatOnlyCurrentProject, true); - s->endGroup(); - s->endGroup(); -} - -Utils::FilePath CMakeFormatterSettings::command() const -{ - return Utils::FilePath::fromString(m_command); -} - -void CMakeFormatterSettings::setCommand(const QString &cmd) -{ - if (cmd == m_command) - return; - - m_command = cmd; -} - -bool CMakeFormatterSettings::autoFormatOnSave() const -{ - return m_autoFormatOnSave; -} - -void CMakeFormatterSettings::setAutoFormatOnSave(bool autoFormatOnSave) -{ - m_autoFormatOnSave = autoFormatOnSave; -} - -QStringList CMakeFormatterSettings::autoFormatMime() const -{ - return m_autoFormatMime; -} - -QString CMakeFormatterSettings::autoFormatMimeAsString() const -{ - return m_autoFormatMime.join("; "); -} - -void CMakeFormatterSettings::setAutoFormatMime(const QStringList &autoFormatMime) -{ - if (m_autoFormatMime == autoFormatMime) - return; - - m_autoFormatMime = autoFormatMime; - emit supportedMimeTypesChanged(); -} - -void CMakeFormatterSettings::setAutoFormatMime(const QString &mimeList) -{ - setAutoFormatMime(mimeList.split(';')); -} - -bool CMakeFormatterSettings::autoFormatOnlyCurrentProject() const -{ - return m_autoFormatOnlyCurrentProject; -} - -void CMakeFormatterSettings::setAutoFormatOnlyCurrentProject(bool autoFormatOnlyCurrentProject) -{ - m_autoFormatOnlyCurrentProject = autoFormatOnlyCurrentProject; -} - -bool CMakeFormatterSettings::isApplicable(const Core::IDocument *document) const -{ - if (!document) - return false; - - if (m_autoFormatMime.isEmpty()) - return true; - - const Utils::MimeType documentMimeType = Utils::mimeTypeForName(document->mimeType()); - return Utils::anyOf(m_autoFormatMime, [&documentMimeType](const QString &mime) { - return documentMimeType.inherits(mime); - }); -} - -} // namespace Internal -} // namespace CMakeProjectManager diff --git a/src/plugins/cmakeprojectmanager/cmakeformattersettings.h b/src/plugins/cmakeprojectmanager/cmakeformattersettings.h deleted file mode 100644 index ea8a3ba1628..00000000000 --- a/src/plugins/cmakeprojectmanager/cmakeformattersettings.h +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (C) 2016 Lorenz Haas -// Copyright (C) 2022 Xavier BESSON -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include -#include -#include - -namespace Core { class IDocument; } -namespace Utils { class FilePath; } - -namespace CMakeProjectManager { -namespace Internal { - -class VersionUpdater; - -class CMakeFormatterSettings : public QObject -{ - Q_OBJECT -public: - explicit CMakeFormatterSettings(QObject* parent = nullptr); - static CMakeFormatterSettings *instance(); - - void read(); - void save(); - - Utils::FilePath command() const; - void setCommand(const QString &cmd); - - bool autoFormatOnSave() const; - void setAutoFormatOnSave(bool autoFormatOnSave); - - QStringList autoFormatMime() const; - QString autoFormatMimeAsString() const; - void setAutoFormatMime(const QStringList &autoFormatMime); - void setAutoFormatMime(const QString &mimeList); - - bool autoFormatOnlyCurrentProject() const; - void setAutoFormatOnlyCurrentProject(bool autoFormatOnlyCurrentProject); - - bool isApplicable(const Core::IDocument *document) const; - -signals: - void supportedMimeTypesChanged(); - -private: - QString m_command; - - bool m_autoFormatOnSave = false; - bool m_autoFormatOnlyCurrentProject = true; - QString m_autoFormatTool; - QStringList m_autoFormatMime; -}; - -} // namespace Internal -} // namespace CMakeProjectManager diff --git a/src/plugins/cmakeprojectmanager/cmakeinstallstep.cpp b/src/plugins/cmakeprojectmanager/cmakeinstallstep.cpp index 6e6a49523f7..07d3c6fd478 100644 --- a/src/plugins/cmakeprojectmanager/cmakeinstallstep.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeinstallstep.cpp @@ -74,7 +74,7 @@ CommandLine CMakeInstallStep::cmakeCommand() const if (buildConfiguration()) buildDirectory = buildConfiguration()->buildDirectory(); - cmd.addArgs({"--install", buildDirectory.onDevice(cmd.executable()).path()}); + cmd.addArgs({"--install", buildDirectory.path()}); auto bs = qobject_cast(buildSystem()); if (bs && bs->isMultiConfigReader()) { @@ -93,6 +93,7 @@ void CMakeInstallStep::finish(ProcessResult result) emit progress(100, {}); AbstractProcessStep::finish(result); } + QWidget *CMakeInstallStep::createConfigWidget() { auto updateDetails = [this] { @@ -105,10 +106,8 @@ QWidget *CMakeInstallStep::createConfigWidget() setDisplayName(Tr::tr("Install", "ConfigWidget display name.")); - Layouting::Form builder; - builder.addRow(m_cmakeArguments); - - auto widget = builder.emerge(Layouting::WithoutMargins); + using namespace Layouting; + auto widget = Form { m_cmakeArguments, noMargin }.emerge(); updateDetails(); diff --git a/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp b/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp index 347c8e794c5..def549f5800 100644 --- a/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp +++ b/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp @@ -38,7 +38,6 @@ #include #include -#include #include #include #include @@ -49,7 +48,6 @@ using namespace ProjectExplorer; using namespace Utils; -using namespace Utils::Layouting; namespace CMakeProjectManager { @@ -105,7 +103,7 @@ private: // KitAspectWidget interface void makeReadOnly() override { m_comboBox->setEnabled(false); } - void addToLayout(LayoutBuilder &builder) override + void addToLayout(Layouting::LayoutItem &builder) override { addMutableAction(m_comboBox); builder.addItem(m_comboBox); @@ -350,11 +348,11 @@ private: // KitAspectWidget interface void makeReadOnly() override { m_changeButton->setEnabled(false); } - void addToLayout(LayoutBuilder &builder) override + void addToLayout(Layouting::LayoutItem &parent) override { addMutableAction(m_label); - builder.addItem(m_label); - builder.addItem(m_changeButton); + parent.addItem(m_label); + parent.addItem(m_changeButton); } void refresh() override @@ -680,7 +678,7 @@ QVariant CMakeGeneratorKitAspect::defaultValue(const Kit *k) const if (it != known.constEnd()) { const bool hasNinja = [k, tool] { auto settings = Internal::CMakeSpecificSettings::instance(); - if (settings->ninjaPath.filePath().isEmpty()) { + if (settings->ninjaPath().isEmpty()) { auto findNinja = [](const Environment &env) -> bool { return !env.searchInPath("ninja").isEmpty(); }; @@ -874,7 +872,6 @@ const char CMAKE_CXX_TOOLCHAIN_KEY[] = "CMAKE_CXX_COMPILER"; const char CMAKE_QMAKE_KEY[] = "QT_QMAKE_EXECUTABLE"; const char CMAKE_PREFIX_PATH_KEY[] = "CMAKE_PREFIX_PATH"; const char QTC_CMAKE_PRESET_KEY[] = "QTC_CMAKE_PRESET"; -const char QTC_KIT_DEFAULT_CONFIG_HASH[] = "QTC_KIT_DEFAULT_CONFIG_HASH"; class CMakeConfigurationKitAspectWidget final : public KitAspectWidget { @@ -892,11 +889,11 @@ public: private: // KitAspectWidget interface - void addToLayout(LayoutBuilder &builder) override + void addToLayout(Layouting::LayoutItem &parent) override { addMutableAction(m_summaryLabel); - builder.addItem(m_summaryLabel); - builder.addItem(m_manageButton); + parent.addItem(m_summaryLabel); + parent.addItem(m_manageButton); } void makeReadOnly() override @@ -1135,51 +1132,6 @@ CMakeConfigItem CMakeConfigurationKitAspect::cmakePresetConfigItem(const Project }); } -void CMakeConfigurationKitAspect::setKitDefaultConfigHash(ProjectExplorer::Kit *k) -{ - const CMakeConfig defaultConfigExpanded - = Utils::transform(defaultConfiguration(k).toList(), [k](const CMakeConfigItem &item) { - CMakeConfigItem expanded(item); - expanded.value = item.expandedValue(k).toUtf8(); - return expanded; - }); - const CMakeTool *const tool = CMakeKitAspect::cmakeTool(k); - const QByteArray kitHash = computeDefaultConfigHash(defaultConfigExpanded, - tool ? tool->cmakeExecutable() - : FilePath()); - - CMakeConfig config = configuration(k); - config.append(CMakeConfigItem(QTC_KIT_DEFAULT_CONFIG_HASH, CMakeConfigItem::INTERNAL, kitHash)); - - setConfiguration(k, config); -} - -CMakeConfigItem CMakeConfigurationKitAspect::kitDefaultConfigHashItem(const ProjectExplorer::Kit *k) -{ - const CMakeConfig config = configuration(k); - return Utils::findOrDefault(config, [](const CMakeConfigItem &item) { - return item.key == QTC_KIT_DEFAULT_CONFIG_HASH; - }); -} - -QByteArray CMakeConfigurationKitAspect::computeDefaultConfigHash(const CMakeConfig &config, - const FilePath &cmakeBinary) -{ - const CMakeConfig defaultConfig = defaultConfiguration(nullptr); - const QByteArray configValues = std::accumulate(defaultConfig.begin(), - defaultConfig.end(), - QByteArray(), - [config](QByteArray &sum, - const CMakeConfigItem &item) { - return sum += config.valueOf(item.key); - }); - return QCryptographicHash::hash(cmakeBinary.caseSensitivity() == Qt::CaseInsensitive - ? configValues.toLower() - : configValues, - QCryptographicHash::Md5) - .toHex(); -} - QVariant CMakeConfigurationKitAspect::defaultValue(const Kit *k) const { // FIXME: Convert preload scripts @@ -1209,16 +1161,15 @@ Tasks CMakeConfigurationKitAspect::validate(const Kit *k) const FilePath tcCxxPath; for (const CMakeConfigItem &i : config) { // Do not use expand(QByteArray) as we cannot be sure the input is latin1 - const FilePath expandedValue - = FilePath::fromString(k->macroExpander()->expand(QString::fromUtf8(i.value))); + const QString expandedValue = k->macroExpander()->expand(QString::fromUtf8(i.value)); if (i.key == CMAKE_QMAKE_KEY) - qmakePath = expandedValue.onDevice(cmake->cmakeExecutable()); + qmakePath = cmake->cmakeExecutable().withNewPath(expandedValue); else if (i.key == CMAKE_C_TOOLCHAIN_KEY) - tcCPath = expandedValue.onDevice(cmake->cmakeExecutable()); + tcCPath = cmake->cmakeExecutable().withNewPath(expandedValue); else if (i.key == CMAKE_CXX_TOOLCHAIN_KEY) - tcCxxPath = expandedValue.onDevice(cmake->cmakeExecutable()); + tcCxxPath = cmake->cmakeExecutable().withNewPath(expandedValue); else if (i.key == CMAKE_PREFIX_PATH_KEY) - qtInstallDirs = CMakeConfigItem::cmakeSplitValue(expandedValue.path()); + qtInstallDirs = CMakeConfigItem::cmakeSplitValue(expandedValue); } Tasks result; @@ -1259,7 +1210,7 @@ Tasks CMakeConfigurationKitAspect::validate(const Kit *k) const if (!tcC || !tcC->isValid()) { addWarning(Tr::tr("CMake configuration has a path to a C compiler set, " "even though the kit has no valid tool chain.")); - } else if (tcCPath != tcC->compilerCommand() && tcCPath != tcC->compilerCommand().onDevice(tcCPath)) { + } else if (tcCPath != tcC->compilerCommand() && tcCPath != tcCPath.withNewMappedPath(tcC->compilerCommand())) { addWarning(Tr::tr("CMake configuration has a path to a C compiler set " "that does not match the compiler path " "configured in the tool chain of the kit.")); @@ -1275,7 +1226,7 @@ Tasks CMakeConfigurationKitAspect::validate(const Kit *k) const if (!tcCxx || !tcCxx->isValid()) { addWarning(Tr::tr("CMake configuration has a path to a C++ compiler set, " "even though the kit has no valid tool chain.")); - } else if (tcCxxPath != tcCxx->compilerCommand() && tcCxxPath != tcCxx->compilerCommand().onDevice(tcCxxPath)) { + } else if (tcCxxPath != tcCxx->compilerCommand() && tcCxxPath != tcCxxPath.withNewMappedPath(tcCxx->compilerCommand())) { addWarning(Tr::tr("CMake configuration has a path to a C++ compiler set " "that does not match the compiler path " "configured in the tool chain of the kit.")); diff --git a/src/plugins/cmakeprojectmanager/cmakekitinformation.h b/src/plugins/cmakeprojectmanager/cmakekitinformation.h index bf056f99e23..3b85235d917 100644 --- a/src/plugins/cmakeprojectmanager/cmakekitinformation.h +++ b/src/plugins/cmakeprojectmanager/cmakekitinformation.h @@ -91,11 +91,6 @@ public: static void setCMakePreset(ProjectExplorer::Kit *k, const QString &presetName); static CMakeConfigItem cmakePresetConfigItem(const ProjectExplorer::Kit *k); - static void setKitDefaultConfigHash(ProjectExplorer::Kit *k); - static CMakeConfigItem kitDefaultConfigHashItem(const ProjectExplorer::Kit *k); - static QByteArray computeDefaultConfigHash(const CMakeConfig &config, - const Utils::FilePath &cmakeBinary); - // KitAspect interface ProjectExplorer::Tasks validate(const ProjectExplorer::Kit *k) const final; void setup(ProjectExplorer::Kit *k) final; diff --git a/src/plugins/cmakeprojectmanager/cmakelocatorfilter.cpp b/src/plugins/cmakeprojectmanager/cmakelocatorfilter.cpp index ab646d1fa69..8d63ee0fa91 100644 --- a/src/plugins/cmakeprojectmanager/cmakelocatorfilter.cpp +++ b/src/plugins/cmakeprojectmanager/cmakelocatorfilter.cpp @@ -8,168 +8,151 @@ #include "cmakeproject.h" #include "cmakeprojectmanagertr.h" -#include - #include #include -#include +#include #include #include +using namespace Core; using namespace ProjectExplorer; using namespace Utils; namespace CMakeProjectManager::Internal { -// -------------------------------------------------------------------- -// CMakeTargetLocatorFilter: -// -------------------------------------------------------------------- +using BuildAcceptor = std::function; -CMakeTargetLocatorFilter::CMakeTargetLocatorFilter() +static LocatorMatcherTasks cmakeMatchers(const BuildAcceptor &acceptor) { - connect(SessionManager::instance(), &SessionManager::projectAdded, - this, &CMakeTargetLocatorFilter::projectListUpdated); - connect(SessionManager::instance(), &SessionManager::projectRemoved, - this, &CMakeTargetLocatorFilter::projectListUpdated); + using namespace Tasking; - // Initialize the filter - projectListUpdated(); -} + TreeStorage storage; -void CMakeTargetLocatorFilter::prepareSearch(const QString &entry) -{ - m_result.clear(); - const QList projects = SessionManager::projects(); - for (Project *p : projects) { - auto cmakeProject = qobject_cast(p); - if (!cmakeProject || !cmakeProject->activeTarget()) - continue; - auto bs = qobject_cast(cmakeProject->activeTarget()->buildSystem()); - if (!bs) - continue; - - const QList buildTargets = bs->buildTargets(); - for (const CMakeBuildTarget &target : buildTargets) { - if (CMakeBuildSystem::filteredOutTarget(target)) + const auto onSetup = [storage, acceptor] { + const QString input = storage->input(); + const QList projects = ProjectManager::projects(); + LocatorFilterEntries entries; + for (Project *project : projects) { + const auto cmakeProject = qobject_cast(project); + if (!cmakeProject || !cmakeProject->activeTarget()) + continue; + const auto bs = qobject_cast( + cmakeProject->activeTarget()->buildSystem()); + if (!bs) continue; - const int index = target.title.indexOf(entry, 0, Qt::CaseInsensitive); - if (index >= 0) { - const FilePath path = target.backtrace.isEmpty() ? cmakeProject->projectFilePath() - : target.backtrace.last().path; - const int line = target.backtrace.isEmpty() ? -1 : target.backtrace.last().line; - QVariantMap extraData; - extraData.insert("project", cmakeProject->projectFilePath().toString()); - extraData.insert("line", line); - extraData.insert("file", path.toString()); - - Core::LocatorFilterEntry filterEntry(this, target.title, extraData); - filterEntry.extraInfo = path.shortNativePath(); - filterEntry.highlightInfo = {index, int(entry.length())}; - filterEntry.filePath = path; - - m_result.append(filterEntry); + const QList buildTargets = bs->buildTargets(); + for (const CMakeBuildTarget &target : buildTargets) { + if (CMakeBuildSystem::filteredOutTarget(target)) + continue; + const int index = target.title.indexOf(input, 0, Qt::CaseInsensitive); + if (index >= 0) { + const FilePath path = target.backtrace.isEmpty() + ? cmakeProject->projectFilePath() + : target.backtrace.last().path; + const int line = target.backtrace.isEmpty() ? 0 : target.backtrace.last().line; + const FilePath projectPath = cmakeProject->projectFilePath(); + const QString displayName = target.title; + LocatorFilterEntry entry; + entry.displayName = displayName; + if (acceptor) { + entry.acceptor = [projectPath, displayName, acceptor] { + acceptor(projectPath, displayName); + return AcceptResult(); + }; + } + entry.linkForEditor = {path, line}; + entry.extraInfo = path.shortNativePath(); + entry.highlightInfo = {index, int(input.length())}; + entry.filePath = cmakeProject->projectFilePath(); + entries.append(entry); + } } } - } + storage->reportOutput(entries); + }; + return {{Sync(onSetup), storage}}; } -QList CMakeTargetLocatorFilter::matchesFor(QFutureInterface &future, const QString &entry) +void setupFilter(ILocatorFilter *filter) { - Q_UNUSED(future) - Q_UNUSED(entry) - return m_result; -} - -void CMakeTargetLocatorFilter::projectListUpdated() -{ - // Enable the filter if there's at least one CMake project - setEnabled(Utils::contains(SessionManager::projects(), [](Project *p) { return qobject_cast(p); })); + const auto projectListUpdated = [filter] { + filter->setEnabled(Utils::contains(ProjectManager::projects(), + [](Project *p) { return qobject_cast(p); })); + }; + QObject::connect(ProjectManager::instance(), &ProjectManager::projectAdded, + filter, projectListUpdated); + QObject::connect(ProjectManager::instance(), &ProjectManager::projectRemoved, + filter, projectListUpdated); } // -------------------------------------------------------------------- // BuildCMakeTargetLocatorFilter: // -------------------------------------------------------------------- -BuildCMakeTargetLocatorFilter::BuildCMakeTargetLocatorFilter() +static void buildAcceptor(const FilePath &projectPath, const QString &displayName) { - setId("Build CMake target"); - setDisplayName(Tr::tr("Build CMake target")); - setDescription(Tr::tr("Builds a target of any open CMake project.")); - setDefaultShortcutString("cm"); - setPriority(High); -} - -void BuildCMakeTargetLocatorFilter::accept(const Core::LocatorFilterEntry &selection, - QString *newText, - int *selectionStart, - int *selectionLength) const -{ - Q_UNUSED(newText) - Q_UNUSED(selectionStart) - Q_UNUSED(selectionLength) - - const QVariantMap extraData = selection.internalData.toMap(); - const FilePath projectPath = FilePath::fromString(extraData.value("project").toString()); - // Get the project containing the target selected const auto cmakeProject = qobject_cast( - Utils::findOrDefault(SessionManager::projects(), [projectPath](Project *p) { + Utils::findOrDefault(ProjectManager::projects(), [projectPath](Project *p) { return p->projectFilePath() == projectPath; })); if (!cmakeProject || !cmakeProject->activeTarget() || !cmakeProject->activeTarget()->activeBuildConfiguration()) return; + if (BuildManager::isBuilding(cmakeProject)) + BuildManager::cancel(); + // Find the make step - BuildStepList *buildStepList = - cmakeProject->activeTarget()->activeBuildConfiguration()->buildSteps(); - auto buildStep = buildStepList->firstOfType(); + const BuildStepList *buildStepList = + cmakeProject->activeTarget()->activeBuildConfiguration()->buildSteps(); + const auto buildStep = buildStepList->firstOfType(); if (!buildStep) return; // Change the make step to build only the given target - QStringList oldTargets = buildStep->buildTargets(); - buildStep->setBuildTargets({selection.displayName}); + const QStringList oldTargets = buildStep->buildTargets(); + buildStep->setBuildTargets({displayName}); // Build BuildManager::buildProjectWithDependencies(cmakeProject); buildStep->setBuildTargets(oldTargets); } +CMakeBuildTargetFilter::CMakeBuildTargetFilter() +{ + setId("Build CMake target"); + setDisplayName(Tr::tr("Build CMake Target")); + setDescription(Tr::tr("Builds a target of any open CMake project.")); + setDefaultShortcutString("cm"); + setPriority(High); + setupFilter(this); +} + +Core::LocatorMatcherTasks CMakeBuildTargetFilter::matchers() +{ + return cmakeMatchers(&buildAcceptor); +} + // -------------------------------------------------------------------- // OpenCMakeTargetLocatorFilter: // -------------------------------------------------------------------- -OpenCMakeTargetLocatorFilter::OpenCMakeTargetLocatorFilter() +CMakeOpenTargetFilter::CMakeOpenTargetFilter() { setId("Open CMake target definition"); - setDisplayName(Tr::tr("Open CMake target")); - setDescription(Tr::tr("Jumps to the definition of a target of any open CMake project.")); + setDisplayName(Tr::tr("Open CMake Target")); + setDescription(Tr::tr("Locates the definition of a target of any open CMake project.")); setDefaultShortcutString("cmo"); setPriority(Medium); + setupFilter(this); } -void OpenCMakeTargetLocatorFilter::accept(const Core::LocatorFilterEntry &selection, - QString *newText, - int *selectionStart, - int *selectionLength) const +Core::LocatorMatcherTasks CMakeOpenTargetFilter::matchers() { - Q_UNUSED(newText) - Q_UNUSED(selectionStart) - Q_UNUSED(selectionLength) - - const QVariantMap extraData = selection.internalData.toMap(); - const int line = extraData.value("line").toInt(); - const auto file = FilePath::fromVariant(extraData.value("file")); - - if (line >= 0) - Core::EditorManager::openEditorAt({file, line}, - {}, - Core::EditorManager::AllowExternalEditor); - else - Core::EditorManager::openEditor(file, {}, Core::EditorManager::AllowExternalEditor); + return cmakeMatchers({}); } -} // CMakeProjectManager::Internal +} // namespace CMakeProjectManager::Internal diff --git a/src/plugins/cmakeprojectmanager/cmakelocatorfilter.h b/src/plugins/cmakeprojectmanager/cmakelocatorfilter.h index 0a74eeafeb3..73e0a9fe4ab 100644 --- a/src/plugins/cmakeprojectmanager/cmakelocatorfilter.h +++ b/src/plugins/cmakeprojectmanager/cmakelocatorfilter.h @@ -7,41 +7,22 @@ namespace CMakeProjectManager::Internal { -class CMakeTargetLocatorFilter : public Core::ILocatorFilter +class CMakeBuildTargetFilter : Core::ILocatorFilter { public: - CMakeTargetLocatorFilter(); - - void prepareSearch(const QString &entry) override; - QList matchesFor(QFutureInterface &future, - const QString &entry) final; + CMakeBuildTargetFilter(); private: - void projectListUpdated(); - - QList m_result; + Core::LocatorMatcherTasks matchers() final; }; -class BuildCMakeTargetLocatorFilter : CMakeTargetLocatorFilter +class CMakeOpenTargetFilter : Core::ILocatorFilter { public: - BuildCMakeTargetLocatorFilter(); + CMakeOpenTargetFilter(); - void accept(const Core::LocatorFilterEntry &selection, - QString *newText, - int *selectionStart, - int *selectionLength) const final; +private: + Core::LocatorMatcherTasks matchers() final; }; -class OpenCMakeTargetLocatorFilter : CMakeTargetLocatorFilter -{ -public: - OpenCMakeTargetLocatorFilter(); - - void accept(const Core::LocatorFilterEntry &selection, - QString *newText, - int *selectionStart, - int *selectionLength) const final; -}; - -} // CMakeProjectManager::Internal +} // namespace CMakeProjectManager::Internal diff --git a/src/plugins/cmakeprojectmanager/cmakeprocess.cpp b/src/plugins/cmakeprojectmanager/cmakeprocess.cpp index 7a14143a4b9..1455cbf88b9 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprocess.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeprocess.cpp @@ -14,8 +14,8 @@ #include #include +#include #include -#include #include using namespace Core; @@ -38,7 +38,7 @@ CMakeProcess::~CMakeProcess() m_parser.flush(); } -static const int failedToStartExitCode = 0xFF; // See QtcProcessPrivate::handleDone() impl +static const int failedToStartExitCode = 0xFF; // See ProcessPrivate::handleDone() impl void CMakeProcess::run(const BuildDirParameters ¶meters, const QStringList &arguments) { @@ -67,8 +67,8 @@ void CMakeProcess::run(const BuildDirParameters ¶meters, const QStringList & return; } - const FilePath sourceDirectory = parameters.sourceDirectory.onDevice(cmakeExecutable); - const FilePath buildDirectory = parameters.buildDirectory.onDevice(cmakeExecutable); + const FilePath sourceDirectory = cmakeExecutable.withNewMappedPath(parameters.sourceDirectory); + const FilePath buildDirectory = parameters.buildDirectory; if (!buildDirectory.exists()) { const QString msg = ::CMakeProjectManager::Tr::tr( @@ -106,7 +106,7 @@ void CMakeProcess::run(const BuildDirParameters ¶meters, const QStringList & // Always use the sourceDir: If we are triggered because the build directory is getting deleted // then we are racing against CMakeCache.txt also getting deleted. - m_process.reset(new QtcProcess); + m_process.reset(new Process); m_process->setWorkingDirectory(buildDirectory); m_process->setEnvironment(parameters.environment); @@ -120,7 +120,7 @@ void CMakeProcess::run(const BuildDirParameters ¶meters, const QStringList & BuildSystem::appendBuildSystemOutput(stripTrailingNewline(s)); }); - connect(m_process.get(), &QtcProcess::done, this, [this] { + connect(m_process.get(), &Process::done, this, [this] { handleProcessDone(m_process->resultData()); }); diff --git a/src/plugins/cmakeprojectmanager/cmakeprocess.h b/src/plugins/cmakeprojectmanager/cmakeprocess.h index d13b0efabe2..bb365d337cc 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprocess.h +++ b/src/plugins/cmakeprojectmanager/cmakeprocess.h @@ -13,7 +13,7 @@ namespace Utils { class ProcessResultData; -class QtcProcess; +class Process; } namespace CMakeProjectManager::Internal { @@ -37,7 +37,7 @@ signals: private: void handleProcessDone(const Utils::ProcessResultData &resultData); - std::unique_ptr m_process; + std::unique_ptr m_process; Utils::OutputFormatter m_parser; QElapsedTimer m_elapsed; }; diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.cpp b/src/plugins/cmakeprojectmanager/cmakeproject.cpp index ae6e26f9e32..78b6449a7a9 100644 --- a/src/plugins/cmakeprojectmanager/cmakeproject.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeproject.cpp @@ -7,16 +7,17 @@ #include "cmakeprojectconstants.h" #include "cmakeprojectimporter.h" #include "cmakeprojectmanagertr.h" -#include "cmaketool.h" #include #include +#include #include #include #include #include #include #include +#include using namespace ProjectExplorer; using namespace Utils; @@ -34,7 +35,11 @@ CMakeProject::CMakeProject(const FilePath &fileName) setProjectLanguages(Core::Context(ProjectExplorer::Constants::CXX_LANGUAGE_ID)); setDisplayName(projectDirectory().fileName()); setCanBuildProducts(); - setHasMakeInstallEquivalent(true); + + // This only influences whether 'Install into temporary host directory' + // will show up by default enabled in some remote deploy configurations. + // We rely on staging via the actual cmake build step. + setHasMakeInstallEquivalent(false); readPresets(); } @@ -62,7 +67,7 @@ Tasks CMakeProject::projectIssues(const Kit *k) const ProjectImporter *CMakeProject::projectImporter() const { if (!m_projectImporter) - m_projectImporter = new CMakeProjectImporter(projectFilePath(), m_presetsData); + m_projectImporter = new CMakeProjectImporter(projectFilePath(), this); return m_projectImporter; } @@ -88,6 +93,14 @@ Internal::PresetsData CMakeProject::combinePresets(Internal::PresetsData &cmakeP result.version = cmakePresetsData.version; result.cmakeMinimimRequired = cmakePresetsData.cmakeMinimimRequired; + result.include = cmakePresetsData.include; + if (result.include) { + if (cmakeUserPresetsData.include) + result.include->append(cmakeUserPresetsData.include.value()); + } else { + result.include = cmakeUserPresetsData.include; + } + auto combinePresetsInternal = [](auto &presetsHash, auto &presets, auto &userPresets, @@ -225,7 +238,7 @@ void CMakeProject::readPresets() if (includeStack.contains(includePath)) { TaskHub::addTask(BuildSystemTask( Task::TaskType::Warning, - Tr::tr("Attempt to include %1 which was already parsed.") + Tr::tr("Attempt to include \"%1\" which was already parsed.") .arg(includePath.path()), Utils::FilePath(), -1)); @@ -280,4 +293,28 @@ ProjectExplorer::DeploymentKnowledge CMakeProject::deploymentKnowledge() const : DeploymentKnowledge::Bad; } +void CMakeProject::configureAsExampleProject(ProjectExplorer::Kit *kit) +{ + QList infoList; + const QList kits(kit != nullptr ? QList({kit}) : KitManager::kits()); + for (Kit *k : kits) { + if (QtSupport::QtKitAspect::qtVersion(k) != nullptr) { + if (auto factory = BuildConfigurationFactory::find(k, projectFilePath())) + infoList << factory->allAvailableSetups(k, projectFilePath()); + } + } + setup(infoList); +} + +void CMakeProjectManager::CMakeProject::setOldPresetKits( + const QList &presetKits) const +{ + m_oldPresetKits = presetKits; +} + +QList CMakeProject::oldPresetKits() const +{ + return m_oldPresetKits; +} + } // namespace CMakeProjectManager diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.h b/src/plugins/cmakeprojectmanager/cmakeproject.h index 2885b9eb4b1..6540dd4964d 100644 --- a/src/plugins/cmakeprojectmanager/cmakeproject.h +++ b/src/plugins/cmakeprojectmanager/cmakeproject.h @@ -31,16 +31,22 @@ public: Internal::PresetsData presetsData() const; void readPresets(); + void setOldPresetKits(const QList &presetKits) const; + QList oldPresetKits() const; + protected: bool setupTarget(ProjectExplorer::Target *t) final; private: ProjectExplorer::DeploymentKnowledge deploymentKnowledge() const override; + void configureAsExampleProject(ProjectExplorer::Kit *kit) override; + Internal::PresetsData combinePresets(Internal::PresetsData &cmakePresetsData, Internal::PresetsData &cmakeUserPresetsData); void setupBuildPresets(Internal::PresetsData &presetsData); mutable Internal::CMakeProjectImporter *m_projectImporter = nullptr; + mutable QList m_oldPresetKits; ProjectExplorer::Tasks m_issues; Internal::PresetsData m_presetsData; diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectconstants.h b/src/plugins/cmakeprojectmanager/cmakeprojectconstants.h index cbaa6487266..183e9501360 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectconstants.h +++ b/src/plugins/cmakeprojectmanager/cmakeprojectconstants.h @@ -17,6 +17,7 @@ const char BUILD_FILE_CONTEXT_MENU[] = "CMakeProject.BuildFileContextMenu"; const char BUILD_FILE[] = "CMakeProject.BuildFile"; const char CMAKE_HOME_DIR[] = "CMakeProject.HomeDirectory"; const char QML_DEBUG_SETTING[] = "CMakeProject.EnableQmlDebugging"; +const char RELOAD_CMAKE_PRESETS[] = "CMakeProject.ReloadCMakePresets"; const char CMAKEFORMATTER_SETTINGS_GROUP[] = "CMakeFormatter"; const char CMAKEFORMATTER_GENERAL_GROUP[] = "General"; diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp b/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp index dc752f0a9f9..3a7a7e6c534 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp @@ -5,6 +5,7 @@ #include "cmakebuildconfiguration.h" #include "cmakekitinformation.h" +#include "cmakeproject.h" #include "cmakeprojectconstants.h" #include "cmakeprojectmanagertr.h" #include "cmaketoolmanager.h" @@ -15,13 +16,14 @@ #include #include #include +#include #include #include #include +#include #include -#include #include #include @@ -46,7 +48,6 @@ struct DirectoryData QString cmakePresetDisplayname; QString cmakePreset; - QByteArray cmakePresetDefaultConfigHash; // Kit Stuff FilePath cmakeBinary; @@ -93,9 +94,9 @@ static QString uniqueCMakeToolDisplayName(CMakeTool &tool) // CMakeProjectImporter -CMakeProjectImporter::CMakeProjectImporter(const FilePath &path, const PresetsData &presetsData) +CMakeProjectImporter::CMakeProjectImporter(const FilePath &path, const CMakeProject *project) : QtProjectImporter(path) - , m_presetsData(presetsData) + , m_project(project) , m_presetsTempDir("qtc-cmake-presets-XXXXXXXX") { useTemporaryKitAspect(CMakeKitAspect::id(), @@ -120,7 +121,7 @@ FilePaths CMakeProjectImporter::importCandidates() candidates << scanDirectory(shadowBuildDirectory.absolutePath(), QString()); } - for (const auto &configPreset : m_presetsData.configurePresets) { + for (const auto &configPreset : m_project->presetsData().configurePresets) { if (configPreset.hidden.value()) continue; @@ -153,6 +154,21 @@ FilePaths CMakeProjectImporter::importCandidates() return finalists; } +Target *CMakeProjectImporter::preferredTarget(const QList &possibleTargets) +{ + for (Kit *kit : m_project->oldPresetKits()) { + const bool haveKit = Utils::contains(possibleTargets, [kit](const auto &target) { + return target->kit() == kit; + }); + + if (!haveKit) + KitManager::deregisterKit(kit); + } + m_project->setOldPresetKits({}); + + return ProjectImporter::preferredTarget(possibleTargets); +} + static CMakeConfig configurationFromPresetProbe( const FilePath &importPath, const FilePath &sourceDirectory, @@ -164,7 +180,7 @@ static CMakeConfig configurationFromPresetProbe( "project(preset-probe)\n" "\n")); - QtcProcess cmake; + Process cmake; cmake.setTimeoutS(30); cmake.setDisableUnixTerminal(); @@ -247,81 +263,107 @@ static CMakeConfig configurationFromPresetProbe( return config; } -static FilePath qmakeFromCMakeCache(const CMakeConfig &config) +struct QMakeAndCMakePrefixPath +{ + FilePath qmakePath; + QString cmakePrefixPath; // can be a semicolon-separated list +}; + +static QMakeAndCMakePrefixPath qtInfoFromCMakeCache(const CMakeConfig &config, + const Environment &env) { // Qt4 way to define things (more convenient for us, so try this first;-) const FilePath qmake = config.filePathValueOf("QT_QMAKE_EXECUTABLE"); qCDebug(cmInputLog) << "QT_QMAKE_EXECUTABLE=" << qmake.toUserOutput(); - if (!qmake.isEmpty()) - return qmake; // Check Qt5 settings: oh, the horror! - const FilePath qtCMakeDir = [config] { - FilePath tmp = config.filePathValueOf("Qt5Core_DIR"); - if (tmp.isEmpty()) - tmp = config.filePathValueOf("Qt6Core_DIR"); + const FilePath qtCMakeDir = [config, env] { + FilePath tmp; + // Check the CMake "_DIR" variable + for (const auto &var : {"Qt6", "Qt6Core", "Qt5", "Qt5Core"}) { + tmp = config.filePathValueOf(QByteArray(var) + "_DIR"); + if (!tmp.isEmpty()) + break; + } return tmp; }(); qCDebug(cmInputLog) << "QtXCore_DIR=" << qtCMakeDir.toUserOutput(); const FilePath canQtCMakeDir = FilePath::fromString(qtCMakeDir.toFileInfo().canonicalFilePath()); qCInfo(cmInputLog) << "QtXCore_DIR (canonical)=" << canQtCMakeDir.toUserOutput(); - QString prefixPath; - if (!qtCMakeDir.isEmpty()) { - prefixPath = canQtCMakeDir.parentDir().parentDir().parentDir().toString(); // Up 3 levels... - } else { - prefixPath = config.stringValueOf("CMAKE_PREFIX_PATH"); - } + + const QString prefixPath = [qtCMakeDir, canQtCMakeDir, config, env] { + QString result; + if (!qtCMakeDir.isEmpty()) { + result = canQtCMakeDir.parentDir().parentDir().parentDir().path(); // Up 3 levels... + } else { + // Check the CMAKE_PREFIX_PATH and "_ROOT" CMake or environment variables + // This can be a single value or a semicolon-separated list + for (const auto &var : {"CMAKE_PREFIX_PATH", "Qt6_ROOT", "Qt5_ROOT"}) { + result = config.stringValueOf(var); + if (result.isEmpty()) + result = env.value(QString::fromUtf8(var)); + if (!result.isEmpty()) + break; + } + } + return result; + }(); qCDebug(cmInputLog) << "PrefixPath:" << prefixPath; + if (!qmake.isEmpty() && !prefixPath.isEmpty()) + return {qmake, prefixPath}; + FilePath toolchainFile = config.filePathValueOf(QByteArray("CMAKE_TOOLCHAIN_FILE")); if (prefixPath.isEmpty() && toolchainFile.isEmpty()) - return FilePath(); + return {qmake, QString()}; // Run a CMake project that would do qmake probing TemporaryDirectory qtcQMakeProbeDir("qtc-cmake-qmake-probe-XXXXXXXX"); - QFile cmakeListTxt(qtcQMakeProbeDir.filePath("CMakeLists.txt").toString()); - if (!cmakeListTxt.open(QIODevice::WriteOnly)) { - return FilePath(); - } - // FIXME replace by raw string when gcc 8+ is minimum - cmakeListTxt.write(QByteArray( -"cmake_minimum_required(VERSION 3.15)\n" -"\n" -"project(qmake-probe LANGUAGES NONE)\n" -"\n" -"# Bypass Qt6's usage of find_dependency, which would require compiler\n" -"# and source code probing, which slows things unnecessarily\n" -"file(WRITE \"${CMAKE_SOURCE_DIR}/CMakeFindDependencyMacro.cmake\"\n" -"[=[" -" macro(find_dependency dep)\n" -" endmacro()\n" -"]=])\n" -"set(CMAKE_MODULE_PATH \"${CMAKE_SOURCE_DIR}\")\n" -"\n" -"find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED)\n" -"find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core REQUIRED)\n" -"\n" -"if (CMAKE_CROSSCOMPILING)\n" -" find_program(qmake_binary\n" -" NAMES qmake qmake.bat\n" -" PATHS \"${Qt${QT_VERSION_MAJOR}_DIR}/../../../bin\"\n" -" NO_DEFAULT_PATH)\n" -" file(WRITE \"${CMAKE_SOURCE_DIR}/qmake-location.txt\" \"${qmake_binary}\")\n" -"else()\n" -" file(GENERATE\n" -" OUTPUT \"${CMAKE_SOURCE_DIR}/qmake-location.txt\"\n" -" CONTENT \"$\")\n" -"endif()\n" -)); - cmakeListTxt.close(); + FilePath cmakeListTxt(qtcQMakeProbeDir.filePath("CMakeLists.txt")); - QtcProcess cmake; + cmakeListTxt.writeFileContents(QByteArray(R"( + cmake_minimum_required(VERSION 3.15) + + project(qmake-probe LANGUAGES NONE) + + # Bypass Qt6's usage of find_dependency, which would require compiler + # and source code probing, which slows things unnecessarily + file(WRITE "${CMAKE_SOURCE_DIR}/CMakeFindDependencyMacro.cmake" + [=[ + macro(find_dependency dep) + endmacro() + ]=]) + set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}") + + find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED) + find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core REQUIRED) + + if (CMAKE_CROSSCOMPILING) + find_program(qmake_binary + NAMES qmake qmake.bat + PATHS "${Qt${QT_VERSION_MAJOR}_DIR}/../../../bin" + NO_DEFAULT_PATH) + file(WRITE "${CMAKE_SOURCE_DIR}/qmake-location.txt" "${qmake_binary}") + else() + file(GENERATE + OUTPUT "${CMAKE_SOURCE_DIR}/qmake-location.txt" + CONTENT "$") + endif() + + # Remove a Qt CMake hack that adds lib/cmake at the end of every path in CMAKE_PREFIX_PATH + list(REMOVE_DUPLICATES CMAKE_PREFIX_PATH) + list(TRANSFORM CMAKE_PREFIX_PATH REPLACE "/lib/cmake$" "") + file(WRITE "${CMAKE_SOURCE_DIR}/cmake-prefix-path.txt" "${CMAKE_PREFIX_PATH}") + )")); + + Process cmake; cmake.setTimeoutS(5); cmake.setDisableUnixTerminal(); - Environment env = Environment::systemEnvironment(); - env.setupEnglishOutput(); - cmake.setEnvironment(env); + + Environment cmakeEnv(env); + cmakeEnv.setupEnglishOutput(); + cmake.setEnvironment(cmakeEnv); cmake.setTimeOutMessageBoxEnabled(false); QString cmakeGenerator = config.stringValueOf(QByteArray("CMAKE_GENERATOR")); @@ -330,6 +372,7 @@ static FilePath qmakeFromCMakeCache(const CMakeConfig &config) FilePath cmakeExecutable = config.filePathValueOf(QByteArray("CMAKE_COMMAND")); FilePath cmakeMakeProgram = config.filePathValueOf(QByteArray("CMAKE_MAKE_PROGRAM")); FilePath hostPath = config.filePathValueOf(QByteArray("QT_HOST_PATH")); + const QString findRootPath = config.stringValueOf("CMAKE_FIND_ROOT_PATH"); QStringList args; args.push_back("-S"); @@ -350,13 +393,16 @@ static FilePath qmakeFromCMakeCache(const CMakeConfig &config) if (!cmakeMakeProgram.isEmpty()) { args.push_back(QStringLiteral("-DCMAKE_MAKE_PROGRAM=%1").arg(cmakeMakeProgram.toString())); } - if (toolchainFile.isEmpty()) { - args.push_back(QStringLiteral("-DCMAKE_PREFIX_PATH=%1").arg(prefixPath)); - } else { - if (!prefixPath.isEmpty()) - args.push_back(QStringLiteral("-DCMAKE_FIND_ROOT_PATH=%1").arg(prefixPath)); + + if (!toolchainFile.isEmpty()) { args.push_back(QStringLiteral("-DCMAKE_TOOLCHAIN_FILE=%1").arg(toolchainFile.toString())); } + if (!prefixPath.isEmpty()) { + args.push_back(QStringLiteral("-DCMAKE_PREFIX_PATH=%1").arg(prefixPath)); + } + if (!findRootPath.isEmpty()) { + args.push_back(QStringLiteral("-DCMAKE_FIND_ROOT_PATH=%1").arg(findRootPath)); + } if (!hostPath.isEmpty()) { args.push_back(QStringLiteral("-DQT_HOST_PATH=%1").arg(hostPath.toString())); } @@ -365,14 +411,17 @@ static FilePath qmakeFromCMakeCache(const CMakeConfig &config) cmake.setCommand({cmakeExecutable, args}); cmake.runBlocking(); - QFile qmakeLocationTxt(qtcQMakeProbeDir.filePath("qmake-location.txt").path()); - if (!qmakeLocationTxt.open(QIODevice::ReadOnly)) { - return FilePath(); - } - FilePath qmakeLocation = FilePath::fromUtf8(qmakeLocationTxt.readLine().constData()); + const FilePath qmakeLocationTxt = qtcQMakeProbeDir.filePath("qmake-location.txt"); + const FilePath qmakeLocation = FilePath::fromUtf8( + qmakeLocationTxt.fileContents().value_or(QByteArray())); qCDebug(cmInputLog) << "qmake location: " << qmakeLocation.toUserOutput(); - return qmakeLocation; + const FilePath prefixPathTxt = qtcQMakeProbeDir.filePath("cmake-prefix-path.txt"); + const QString resultedPrefixPath = QString::fromUtf8( + prefixPathTxt.fileContents().value_or(QByteArray())); + qCDebug(cmInputLog) << "PrefixPath [after qmake probe]: " << resultedPrefixPath; + + return {qmakeLocation, resultedPrefixPath}; } static QVector extractToolChainsFromCache(const CMakeConfig &config) @@ -590,7 +639,7 @@ QList CMakeProjectImporter::examineDirectory(const FilePath &importPath, const QString presetName = importPath.fileName(); PresetsDetails::ConfigurePreset configurePreset - = Utils::findOrDefault(m_presetsData.configurePresets, + = Utils::findOrDefault(m_project->presetsData().configurePresets, [presetName](const PresetsDetails::ConfigurePreset &preset) { return preset.name == presetName; }); @@ -687,19 +736,21 @@ QList CMakeProjectImporter::examineDirectory(const FilePath &importPath, configurePreset.generator.value().toUtf8()); } - const FilePath qmake = qmakeFromCMakeCache(config); + const auto [qmake, cmakePrefixPath] = qtInfoFromCMakeCache(config, env); if (!qmake.isEmpty()) data->qt = findOrCreateQtVersion(qmake); + if (!cmakePrefixPath.isEmpty() && config.valueOf("CMAKE_PREFIX_PATH").isEmpty()) + config << CMakeConfigItem("CMAKE_PREFIX_PATH", + CMakeConfigItem::PATH, + cmakePrefixPath.toUtf8()); + // ToolChains: data->toolChains = extractToolChainsFromCache(config); // Update QT_QMAKE_EXECUTABLE and CMAKE_C|XX_COMPILER config values updateConfigWithDirectoryData(config, data); - data->cmakePresetDefaultConfigHash - = CMakeConfigurationKitAspect::computeDefaultConfigHash(config, data->cmakeBinary); - QByteArrayList buildConfigurationTypes = {cache.valueOf("CMAKE_BUILD_TYPE")}; if (buildConfigurationTypes.front().isEmpty()) { buildConfigurationTypes.clear(); @@ -747,6 +798,8 @@ QList CMakeProjectImporter::examineDirectory(const FilePath &importPath, buildConfigurationTypes = buildConfigurationTypesString.split(';'); } + const Environment env = projectDirectory().deviceEnvironment(); + for (auto const &buildType: std::as_const(buildConfigurationTypes)) { auto data = std::make_unique(); @@ -778,7 +831,7 @@ QList CMakeProjectImporter::examineDirectory(const FilePath &importPath, data->sysroot = config.filePathValueOf("CMAKE_SYSROOT"); // Qt: - const FilePath qmake = qmakeFromCMakeCache(config); + const auto [qmake, cmakePrefixPath] = qtInfoFromCMakeCache(config, env); if (!qmake.isEmpty()) data->qt = findOrCreateQtVersion(qmake); @@ -826,36 +879,50 @@ bool CMakeProjectImporter::matchKit(void *directoryData, const Kit *k) const if (data->qt.qt && QtSupport::QtKitAspect::qtVersionId(k) != data->qt.qt->uniqueId()) return false; + const bool compilersMatch = [k, data] { + const QList allLanguages = ToolChainManager::allLanguages(); + for (const ToolChainDescription &tcd : data->toolChains) { + if (!Utils::contains(allLanguages, + [&tcd](const Id &language) { return language == tcd.language; })) + continue; + ToolChain *tc = ToolChainKitAspect::toolChain(k, tcd.language); + if ((!tc || !tc->matchesCompilerCommand(tcd.compilerPath))) { + return false; + } + } + return true; + }(); + const bool noCompilers = [k, data] { + const QList allLanguages = ToolChainManager::allLanguages(); + for (const ToolChainDescription &tcd : data->toolChains) { + if (!Utils::contains(allLanguages, + [&tcd](const Id &language) { return language == tcd.language; })) + continue; + ToolChain *tc = ToolChainKitAspect::toolChain(k, tcd.language); + if (tc && tc->matchesCompilerCommand(tcd.compilerPath)) { + return false; + } + } + return true; + }(); + bool haveCMakePreset = false; if (!data->cmakePreset.isEmpty()) { const auto presetConfigItem = CMakeConfigurationKitAspect::cmakePresetConfigItem(k); - const auto kitConfigHashItem = CMakeConfigurationKitAspect::kitDefaultConfigHashItem(k); const QString presetName = presetConfigItem.expandedValue(k); - const bool haveSameKitConfigHash = kitConfigHashItem.isNull() - ? true - : data->cmakePresetDefaultConfigHash - == kitConfigHashItem.value; - - if (data->cmakePreset != presetName || !haveSameKitConfigHash) + if (data->cmakePreset != presetName) return false; ensureBuildDirectory(*data, k); haveCMakePreset = true; } - const QList allLanguages = ToolChainManager::allLanguages(); - for (const ToolChainDescription &tcd : data->toolChains) { - if (!Utils::contains(allLanguages, [&tcd](const Id& language) {return language == tcd.language;})) - continue; - ToolChain *tc = ToolChainKitAspect::toolChain(k, tcd.language); - if ((!tc || !tc->matchesCompilerCommand(tcd.compilerPath)) && !haveCMakePreset) { - return false; - } - } + if (!compilersMatch && !(haveCMakePreset && noCompilers)) + return false; qCDebug(cmInputLog) << k->displayName() - << "matches directoryData for" << data->buildDirectory.toUserOutput(); + << "matches directoryData for" << data->buildDirectory.toUserOutput(); return true; } @@ -894,7 +961,6 @@ Kit *CMakeProjectImporter::createKit(void *directoryData) const QString("%1 (CMake preset)").arg(data->cmakePresetDisplayname)); CMakeConfigurationKitAspect::setCMakePreset(k, data->cmakePreset); - CMakeConfigurationKitAspect::setKitDefaultConfigHash(k); } if (!data->cmakePreset.isEmpty()) ensureBuildDirectory(*data, k); @@ -1022,8 +1088,9 @@ void CMakeProjectPlugin::testCMakeProjectImporterQt() config.append(CMakeConfigItem(key.toUtf8(), value.toUtf8())); } - FilePath realQmake = qmakeFromCMakeCache(config); - QCOMPARE(realQmake.toString(), expectedQmake); + auto [realQmake, cmakePrefixPath] = qtInfoFromCMakeCache(config, + Environment::systemEnvironment()); + QCOMPARE(realQmake.path(), expectedQmake); } void CMakeProjectPlugin::testCMakeProjectImporterToolChain_data() { diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectimporter.h b/src/plugins/cmakeprojectmanager/cmakeprojectimporter.h index 7c7716dc8b2..350b0b77d6c 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectimporter.h +++ b/src/plugins/cmakeprojectmanager/cmakeprojectimporter.h @@ -4,12 +4,14 @@ #pragma once #include "presetsparser.h" -#include "utils/temporarydirectory.h" #include +#include + namespace CMakeProjectManager { +class CMakeProject; class CMakeTool; namespace Internal { @@ -19,9 +21,11 @@ struct DirectoryData; class CMakeProjectImporter : public QtSupport::QtProjectImporter { public: - CMakeProjectImporter(const Utils::FilePath &path, const Internal::PresetsData &presetsData); + CMakeProjectImporter(const Utils::FilePath &path, + const CMakeProjectManager::CMakeProject *project); Utils::FilePaths importCandidates() final; + ProjectExplorer::Target *preferredTarget(const QList &possibleTargets) final; private: QList examineDirectory(const Utils::FilePath &importPath, @@ -43,7 +47,7 @@ private: void ensureBuildDirectory(DirectoryData &data, const ProjectExplorer::Kit *k) const; - Internal::PresetsData m_presetsData; + const CMakeProject *m_project; Utils::TemporaryDirectory m_presetsTempDir; }; diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.cpp b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.cpp index e05e155cf6f..c43c7978a61 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.cpp @@ -9,6 +9,7 @@ #include "cmakeprojectconstants.h" #include "cmakeprojectmanagertr.h" #include "cmakeprojectnodes.h" +#include "cmakespecificsettings.h" #include #include @@ -16,14 +17,19 @@ #include #include #include +#include + #include + #include #include #include +#include #include -#include #include +#include +#include #include #include @@ -40,6 +46,8 @@ CMakeManager::CMakeManager() , m_clearCMakeCacheAction(new QAction(QIcon(), Tr::tr("Clear CMake Configuration"), this)) , m_runCMakeActionContextMenu(new QAction(QIcon(), Tr::tr("Run CMake"), this)) , m_rescanProjectAction(new QAction(QIcon(), Tr::tr("Rescan Project"), this)) + , m_reloadCMakePresetsAction( + new QAction(Utils::Icons::RELOAD_TOOLBAR.icon(), Tr::tr("Reload CMake Presets"), this)) { Core::ActionContainer *mbuild = Core::ActionManager::actionContainer(ProjectExplorer::Constants::M_BUILDPROJECT); @@ -59,7 +67,7 @@ CMakeManager::CMakeManager() command->setAttribute(Core::Command::CA_Hide); mbuild->addAction(command, ProjectExplorer::Constants::G_BUILD_BUILD); connect(m_runCMakeAction, &QAction::triggered, this, [this] { - runCMake(SessionManager::startupBuildSystem()); + runCMake(ProjectManager::startupBuildSystem()); }); command = Core::ActionManager::registerAction(m_clearCMakeCacheAction, @@ -68,7 +76,7 @@ CMakeManager::CMakeManager() command->setAttribute(Core::Command::CA_Hide); mbuild->addAction(command, ProjectExplorer::Constants::G_BUILD_BUILD); connect(m_clearCMakeCacheAction, &QAction::triggered, this, [this] { - clearCMakeCache(SessionManager::startupBuildSystem()); + clearCMakeCache(ProjectManager::startupBuildSystem()); }); command = Core::ActionManager::registerAction(m_runCMakeActionContextMenu, @@ -99,6 +107,15 @@ CMakeManager::CMakeManager() rescanProject(ProjectTree::currentBuildSystem()); }); + command = Core::ActionManager::registerAction(m_reloadCMakePresetsAction, + Constants::RELOAD_CMAKE_PRESETS, + globalContext); + command->setAttribute(Core::Command::CA_Hide); + mbuild->addAction(command, ProjectExplorer::Constants::G_BUILD_BUILD); + connect(m_reloadCMakePresetsAction, &QAction::triggered, this, [this] { + reloadCMakePresets(); + }); + m_buildFileAction = new Utils::ParameterAction(Tr::tr("Build File"), Tr::tr("Build File \"%1\""), Utils::ParameterAction::AlwaysEnabled, @@ -111,7 +128,7 @@ CMakeManager::CMakeManager() mbuild->addAction(command, ProjectExplorer::Constants::G_BUILD_BUILD); connect(m_buildFileAction, &QAction::triggered, this, [this] { buildFile(); }); - connect(SessionManager::instance(), &SessionManager::startupProjectChanged, this, [this] { + connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, this, [this] { updateCmakeActions(ProjectTree::currentNode()); }); connect(BuildManager::instance(), &BuildManager::buildStateChanged, this, [this] { @@ -127,12 +144,22 @@ CMakeManager::CMakeManager() void CMakeManager::updateCmakeActions(Node *node) { - auto project = qobject_cast(SessionManager::startupProject()); + auto project = qobject_cast(ProjectManager::startupProject()); const bool visible = project && !BuildManager::isBuilding(project); m_runCMakeAction->setVisible(visible); m_runCMakeActionContextMenu->setEnabled(visible); m_clearCMakeCacheAction->setVisible(visible); m_rescanProjectAction->setVisible(visible); + + const bool reloadPresetsVisible = [project] { + if (!project) + return false; + const FilePath presetsPath = project->projectFilePath().parentDir().pathAppended( + "CMakePresets.json"); + return presetsPath.exists(); + }(); + m_reloadCMakePresetsAction->setVisible(reloadPresetsVisible); + enableBuildFileMenus(node); } @@ -202,6 +229,62 @@ void CMakeManager::enableBuildFileMenus(Node *node) } } +void CMakeManager::reloadCMakePresets() +{ + auto settings = CMakeSpecificSettings::instance(); + + QMessageBox::StandardButton clickedButton + = CheckableMessageBox::question(Core::ICore::dialogParent(), + Tr::tr("Reload CMake Presets"), + Tr::tr("Re-generates the CMake presets kits. The manual " + "CMake project modifications will be lost."), + settings->askBeforePresetsReload.checkableDecider(), + QMessageBox::Yes | QMessageBox::Cancel, + QMessageBox::Yes, + QMessageBox::Yes, + { + {QMessageBox::Yes, Tr::tr("Reload")}, + }); + + if (clickedButton == QMessageBox::Cancel) + return; + + CMakeProject *project = static_cast(ProjectTree::currentProject()); + if (!project) + return; + + const QSet oldPresets = Utils::transform(project->presetsData().configurePresets, + [](const auto &preset) { + return preset.name; + }); + project->readPresets(); + + QList oldKits; + for (const auto &target : project->targets()) { + const CMakeConfigItem presetItem = CMakeConfigurationKitAspect::cmakePresetConfigItem( + target->kit()); + + if (BuildManager::isBuilding(target)) + BuildManager::cancel(); + + // Only clear the CMake configuration for preset kits. Any manual kit configuration + // will get the chance to get imported afterwards in the Kit selection wizard + CMakeBuildSystem *bs = static_cast(target->buildSystem()); + if (!presetItem.isNull() && bs) + bs->clearCMakeCache(); + + if (!presetItem.isNull() && oldPresets.contains(QString::fromUtf8(presetItem.value))) + oldKits << target->kit(); + + project->removeTarget(target); + } + + project->setOldPresetKits(oldKits); + + Core::ModeManager::activateMode(ProjectExplorer::Constants::MODE_SESSION); + Core::ModeManager::setFocusToCurrentMode(); +} + void CMakeManager::buildFile(Node *node) { if (!node) { diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.h b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.h index 3a527092917..c47d724c9b6 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.h +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.h @@ -30,12 +30,14 @@ private: void buildFile(ProjectExplorer::Node *node = nullptr); void updateBuildFileAction(); void enableBuildFileMenus(ProjectExplorer::Node *node); + void reloadCMakePresets(); QAction *m_runCMakeAction; QAction *m_clearCMakeCacheAction; QAction *m_runCMakeActionContextMenu; QAction *m_rescanProjectAction; QAction *m_buildFileContextMenu; + QAction *m_reloadCMakePresetsAction; Utils::ParameterAction *m_buildFileAction; }; diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs index 89e449ae402..3597a948713 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs @@ -35,10 +35,6 @@ QtcPlugin { "cmakefilecompletionassist.h", "cmakeformatter.cpp", "cmakeformatter.h", - "cmakeformatteroptionspage.cpp", - "cmakeformatteroptionspage.h", - "cmakeformattersettings.cpp", - "cmakeformattersettings.h", "cmakeinstallstep.cpp", "cmakeinstallstep.h", "cmakekitinformation.h", @@ -93,4 +89,18 @@ QtcPlugin { "projecttreehelper.cpp", "projecttreehelper.h" ] + + Group { + name: "3rdparty" + cpp.includePaths: base.concat("3rdparty/cmake") + + prefix: "3rdparty/cmake/" + files: [ + "cmListFileCache.cxx", + "cmListFileCache.h", + "cmListFileLexer.cxx", + "cmListFileLexer.h", + "cmStandardLexer.h", + ] + } } diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectnodes.cpp b/src/plugins/cmakeprojectmanager/cmakeprojectnodes.cpp index 9c0d687ca6f..b26f9a70b5b 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectnodes.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeprojectnodes.cpp @@ -30,6 +30,15 @@ CMakeInputsNode::CMakeInputsNode(const FilePath &cmakeLists) : setListInProject(false); } +CMakePresetsNode::CMakePresetsNode(const FilePath &projectPath) : + ProjectExplorer::ProjectNode(projectPath) +{ + setPriority(Node::DefaultPriority - 9); + setDisplayName(Tr::tr("CMake Presets")); + setIcon(DirectoryIcon(ProjectExplorer::Constants::FILEOVERLAY_PRODUCT)); + setListInProject(false); +} + CMakeListsNode::CMakeListsNode(const FilePath &cmakeListPath) : ProjectExplorer::ProjectNode(cmakeListPath) { @@ -198,6 +207,10 @@ void CMakeTargetNode::setTargetInformation(const QList &artifacts, con m_tooltip += Tr::tr("Build artifacts:") + "
" + tmp.join("
"); m_artifact = artifacts.first(); } + if (type == "EXECUTABLE") + setProductType(ProductType::App); + else if (type == "SHARED_LIBRARY" || type == "STATIC_LIBRARY") + setProductType(ProductType::Lib); } } // CMakeProjectManager::Internal diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectnodes.h b/src/plugins/cmakeprojectmanager/cmakeprojectnodes.h index 5d248020abe..94cde6dfef1 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectnodes.h +++ b/src/plugins/cmakeprojectmanager/cmakeprojectnodes.h @@ -15,6 +15,12 @@ public: CMakeInputsNode(const Utils::FilePath &cmakeLists); }; +class CMakePresetsNode : public ProjectExplorer::ProjectNode +{ +public: + CMakePresetsNode(const Utils::FilePath &projectPath); +}; + class CMakeListsNode : public ProjectExplorer::ProjectNode { public: diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectplugin.cpp b/src/plugins/cmakeprojectmanager/cmakeprojectplugin.cpp index bb0270d16ef..39e3ec29269 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectplugin.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeprojectplugin.cpp @@ -8,7 +8,6 @@ #include "cmakebuildsystem.h" #include "cmakeeditor.h" #include "cmakeformatter.h" -#include "cmakeformattersettings.h" #include "cmakeinstallstep.h" #include "cmakekitinformation.h" #include "cmakelocatorfilter.h" @@ -23,7 +22,6 @@ #include #include -#include #include #include @@ -44,27 +42,9 @@ using namespace Utils; namespace CMakeProjectManager::Internal { -bool isAutoFormatApplicable(const Core::IDocument *document, const QStringList &allowedMimeTypes) -{ - if (!document) - return false; - - if (allowedMimeTypes.isEmpty()) - return true; - - const Utils::MimeType documentMimeType = Utils::mimeTypeForName(document->mimeType()); - return Utils::anyOf(allowedMimeTypes, [&documentMimeType](const QString &mime) { - return documentMimeType.inherits(mime); - }); -} - class CMakeProjectPluginPrivate : public QObject { public: - CMakeProjectPluginPrivate(); - void updateActions(Core::IEditor *editor = nullptr); - void autoFormatOnSave(Core::IDocument *document); - CMakeToolManager cmakeToolManager; // have that before the first CMakeKitAspect ParameterAction buildTargetContextAction{ @@ -74,15 +54,15 @@ public: }; CMakeSettingsPage settingsPage; - CMakeSpecificSettingsPage specificSettings; + CMakeSpecificSettings specificSettings; CMakeManager manager; CMakeBuildStepFactory buildStepFactory; CMakeBuildConfigurationFactory buildConfigFactory; CMakeEditorFactory editorFactor; CMakeInstallStepFactory installStepFactory; - BuildCMakeTargetLocatorFilter buildCMakeTargetLocatorFilter; - OpenCMakeTargetLocatorFilter openCMakeTargetLocationFilter; + CMakeBuildTargetFilter cMakeBuildTargetFilter; + CMakeOpenTargetFilter cMakeOpenTargetFilter; CMakeKitAspect cmakeKitAspect; CMakeGeneratorKitAspect cmakeGeneratorKitAspect; @@ -91,53 +71,6 @@ public: CMakeFormatter cmakeFormatter; }; -CMakeProjectPluginPrivate::CMakeProjectPluginPrivate() -{ - const Core::EditorManager *editorManager = Core::EditorManager::instance(); - QObject::connect(editorManager, &Core::EditorManager::currentEditorChanged, - this, &CMakeProjectPluginPrivate::updateActions); - QObject::connect(editorManager, &Core::EditorManager::aboutToSave, - this, &CMakeProjectPluginPrivate::autoFormatOnSave); -} - -void CMakeProjectPluginPrivate::updateActions(Core::IEditor *editor) -{ - cmakeFormatter.updateActions(editor); -} - -void CMakeProjectPluginPrivate::autoFormatOnSave(Core::IDocument *document) -{ - if (!CMakeFormatterSettings::instance()->autoFormatOnSave()) - return; - - if (!isAutoFormatApplicable(document, CMakeFormatterSettings::instance()->autoFormatMime())) - return; - - // Check if file is contained in the current project (if wished) - if (CMakeFormatterSettings::instance()->autoFormatOnlyCurrentProject()) { - const ProjectExplorer::Project *pro = ProjectExplorer::ProjectTree::currentProject(); - if (!pro || pro->files([document](const ProjectExplorer::Node *n) { - return ProjectExplorer::Project::SourceFiles(n) - && n->filePath() == document->filePath(); - }).isEmpty()) { - return; - } - } - - if (!cmakeFormatter.isApplicable(document)) - return; - const TextEditor::Command command = cmakeFormatter.command(); - if (!command.isValid()) - return; - const QList editors = Core::DocumentModel::editorsForDocument(document); - if (editors.isEmpty()) - return; - IEditor *currentEditor = EditorManager::currentEditor(); - IEditor *editor = editors.contains(currentEditor) ? currentEditor : editors.first(); - if (auto widget = TextEditor::TextEditorWidget::fromEditor(editor)) - TextEditor::formatEditor(widget, command); -} - CMakeProjectPlugin::~CMakeProjectPlugin() { delete d; @@ -146,7 +79,6 @@ CMakeProjectPlugin::~CMakeProjectPlugin() void CMakeProjectPlugin::initialize() { d = new CMakeProjectPluginPrivate; - CMakeSpecificSettings::instance()->readSettings(ICore::settings()); const Context projectContext{CMakeProjectManager::Constants::CMAKE_PROJECT_ID}; @@ -179,14 +111,6 @@ void CMakeProjectPlugin::initialize() bs->buildCMakeTarget(targetNode ? targetNode->displayName() : QString()); } }); - - Core::ActionContainer *menu = Core::ActionManager::createMenu(Constants::CMAKEFORMATTER_MENU_ID); - menu->menu()->setTitle(Tr::tr("CMakeFormatter")); - menu->setOnAllDisabledBehavior(Core::ActionContainer::Show); - Core::ActionManager::actionContainer(Core::Constants::M_TOOLS)->addMenu(menu); - - d->cmakeFormatter.initialize(); - d->updateActions(); } void CMakeProjectPlugin::extensionsInitialized() diff --git a/src/plugins/cmakeprojectmanager/cmakespecificsettings.cpp b/src/plugins/cmakeprojectmanager/cmakespecificsettings.cpp index b29351d1752..1fe948cc72a 100644 --- a/src/plugins/cmakeprojectmanager/cmakespecificsettings.cpp +++ b/src/plugins/cmakeprojectmanager/cmakespecificsettings.cpp @@ -15,38 +15,53 @@ using namespace Utils; namespace CMakeProjectManager::Internal { +static CMakeSpecificSettings *theSettings; + +CMakeSpecificSettings *CMakeSpecificSettings::instance() +{ + return theSettings; +} + CMakeSpecificSettings::CMakeSpecificSettings() { + theSettings = this; + + setId(Constants::Settings::GENERAL_ID); + setDisplayName(::CMakeProjectManager::Tr::tr("General")); + setDisplayCategory("CMake"); + setCategory(Constants::Settings::CATEGORY); + setCategoryIconPath(Constants::Icons::SETTINGS_CATEGORY); + + setLayouter([this] { + using namespace Layouting; + return Column { + autorunCMake, + packageManagerAutoSetup, + askBeforeReConfigureInitialParams, + askBeforePresetsReload, + showSourceSubFolders, + showAdvancedOptionsByDefault, + st + }; + }); + // TODO: fixup of QTCREATORBUG-26289 , remove in Qt Creator 7 or so Core::ICore::settings()->remove("CMakeSpecificSettings/NinjaPath"); setSettingsGroup("CMakeSpecificSettings"); setAutoApply(false); - registerAspect(&autorunCMake); autorunCMake.setSettingsKey("AutorunCMake"); autorunCMake.setDefaultValue(true); autorunCMake.setLabelText(::CMakeProjectManager::Tr::tr("Autorun CMake")); autorunCMake.setToolTip(::CMakeProjectManager::Tr::tr( "Automatically run CMake after changes to CMake project files.")); - registerAspect(&afterAddFileSetting); - afterAddFileSetting.setSettingsKey("ProjectPopupSetting"); - afterAddFileSetting.setDefaultValue(AfterAddFileAction::AskUser); - afterAddFileSetting.addOption(::CMakeProjectManager::Tr::tr("Ask about copying file paths")); - afterAddFileSetting.addOption(::CMakeProjectManager::Tr::tr("Do not copy file paths")); - afterAddFileSetting.addOption(::CMakeProjectManager::Tr::tr("Copy file paths")); - afterAddFileSetting.setToolTip(::CMakeProjectManager::Tr::tr("Determines whether file paths are copied " - "to the clipboard for pasting to the CMakeLists.txt file when you " - "add new files to CMake projects.")); - - registerAspect(&ninjaPath); ninjaPath.setSettingsKey("NinjaPath"); // never save this to the settings: ninjaPath.setToSettingsTransformation( [](const QVariant &) { return QVariant::fromValue(QString()); }); - registerAspect(&packageManagerAutoSetup); packageManagerAutoSetup.setSettingsKey("PackageManagerAutoSetup"); packageManagerAutoSetup.setDefaultValue(true); packageManagerAutoSetup.setLabelText(::CMakeProjectManager::Tr::tr("Package manager auto setup")); @@ -54,59 +69,26 @@ CMakeSpecificSettings::CMakeSpecificSettings() "pointing to a CMake script that will install dependencies from the conanfile.txt, " "conanfile.py, or vcpkg.json file from the project source directory.")); - registerAspect(&askBeforeReConfigureInitialParams); askBeforeReConfigureInitialParams.setSettingsKey("AskReConfigureInitialParams"); askBeforeReConfigureInitialParams.setDefaultValue(true); askBeforeReConfigureInitialParams.setLabelText(::CMakeProjectManager::Tr::tr("Ask before re-configuring with " "initial parameters")); - registerAspect(&showSourceSubFolders); + askBeforePresetsReload.setSettingsKey("AskBeforePresetsReload"); + askBeforePresetsReload.setDefaultValue(true); + askBeforePresetsReload.setLabelText(::CMakeProjectManager::Tr::tr("Ask before reloading CMake Presets")); + showSourceSubFolders.setSettingsKey("ShowSourceSubFolders"); showSourceSubFolders.setDefaultValue(true); showSourceSubFolders.setLabelText( ::CMakeProjectManager::Tr::tr("Show subfolders inside source group folders")); - registerAspect(&showAdvancedOptionsByDefault); showAdvancedOptionsByDefault.setSettingsKey("ShowAdvancedOptionsByDefault"); showAdvancedOptionsByDefault.setDefaultValue(false); showAdvancedOptionsByDefault.setLabelText( ::CMakeProjectManager::Tr::tr("Show advanced options by default")); -} -CMakeSpecificSettings *CMakeSpecificSettings::instance() -{ - static CMakeSpecificSettings theSettings; - return &theSettings; -} - -// CMakeSpecificSettingsPage - -CMakeSpecificSettingsPage::CMakeSpecificSettingsPage() -{ - CMakeSpecificSettings *settings = CMakeSpecificSettings::instance(); - setId(Constants::Settings::GENERAL_ID); - setDisplayName(::CMakeProjectManager::Tr::tr("General")); - setDisplayCategory("CMake"); - setCategory(Constants::Settings::CATEGORY); - setCategoryIconPath(Constants::Icons::SETTINGS_CATEGORY); - setSettings(settings); - - setLayouter([settings](QWidget *widget) { - CMakeSpecificSettings &s = *settings; - using namespace Layouting; - Column { - Group { - title(::CMakeProjectManager::Tr::tr("Adding Files")), - Column { s.afterAddFileSetting } - }, - s.autorunCMake, - s.packageManagerAutoSetup, - s.askBeforeReConfigureInitialParams, - s.showSourceSubFolders, - s.showAdvancedOptionsByDefault, - st - }.attachTo(widget); - }); + readSettings(); } } // CMakeProjectManager::Internal diff --git a/src/plugins/cmakeprojectmanager/cmakespecificsettings.h b/src/plugins/cmakeprojectmanager/cmakespecificsettings.h index d0b19bee426..a9c2758e190 100644 --- a/src/plugins/cmakeprojectmanager/cmakespecificsettings.h +++ b/src/plugins/cmakeprojectmanager/cmakespecificsettings.h @@ -5,36 +5,22 @@ #include -#include - namespace CMakeProjectManager::Internal { -enum AfterAddFileAction : int { - AskUser, - CopyFilePath, - NeverCopyFilePath -}; - -class CMakeSpecificSettings final : public Utils::AspectContainer +class CMakeSpecificSettings final : public Core::PagedSettings { public: CMakeSpecificSettings(); static CMakeSpecificSettings *instance(); - Utils::BoolAspect autorunCMake; - Utils::SelectionAspect afterAddFileSetting; - Utils::StringAspect ninjaPath; - Utils::BoolAspect packageManagerAutoSetup; - Utils::BoolAspect askBeforeReConfigureInitialParams; - Utils::BoolAspect showSourceSubFolders; - Utils::BoolAspect showAdvancedOptionsByDefault; -}; - -class CMakeSpecificSettingsPage final : public Core::IOptionsPage -{ -public: - CMakeSpecificSettingsPage(); + Utils::BoolAspect autorunCMake{this}; + Utils::FilePathAspect ninjaPath{this}; + Utils::BoolAspect packageManagerAutoSetup{this}; + Utils::BoolAspect askBeforeReConfigureInitialParams{this}; + Utils::BoolAspect askBeforePresetsReload{this}; + Utils::BoolAspect showSourceSubFolders{this}; + Utils::BoolAspect showAdvancedOptionsByDefault{this}; }; } // CMakeProjectManager::Internal diff --git a/src/plugins/cmakeprojectmanager/cmaketool.cpp b/src/plugins/cmakeprojectmanager/cmaketool.cpp index 184811884db..9239ea54177 100644 --- a/src/plugins/cmakeprojectmanager/cmaketool.cpp +++ b/src/plugins/cmakeprojectmanager/cmaketool.cpp @@ -10,8 +10,8 @@ #include #include +#include #include -#include #include #include @@ -162,7 +162,7 @@ bool CMakeTool::isValid() const return m_introspection->m_didRun && !m_introspection->m_fileApis.isEmpty(); } -void CMakeTool::runCMake(QtcProcess &cmake, const QStringList &args, int timeoutS) const +void CMakeTool::runCMake(Process &cmake, const QStringList &args, int timeoutS) const { const FilePath executable = cmakeExecutable(); cmake.setTimeoutS(timeoutS); @@ -248,7 +248,7 @@ TextEditor::Keywords CMakeTool::keywords() return {}; if (m_introspection->m_functions.isEmpty() && m_introspection->m_didRun) { - QtcProcess proc; + Process proc; runCMake(proc, {"--help-command-list"}, 5); if (proc.result() == ProcessResult::FinishedWithSuccess) m_introspection->m_functions = proc.cleanedStdOut().split('\n'); @@ -492,7 +492,7 @@ QStringList CMakeTool::parseVariableOutput(const QString &output) void CMakeTool::fetchFromCapabilities() const { - QtcProcess cmake; + Process cmake; runCMake(cmake, {"-E", "capabilities"}); if (cmake.result() == ProcessResult::FinishedWithSuccess) { diff --git a/src/plugins/cmakeprojectmanager/cmaketool.h b/src/plugins/cmakeprojectmanager/cmaketool.h index 42fa7b8f62d..31b9e016276 100644 --- a/src/plugins/cmakeprojectmanager/cmaketool.h +++ b/src/plugins/cmakeprojectmanager/cmaketool.h @@ -12,7 +12,7 @@ #include -namespace Utils { class QtcProcess; } +namespace Utils { class Process; } namespace CMakeProjectManager { @@ -61,6 +61,8 @@ public: Utils::Id id() const { return m_id; } QVariantMap toMap () const; + void setAutorun(bool autoRun) { m_isAutoRun = autoRun; } + void setFilePath(const Utils::FilePath &executable); Utils::FilePath filePath() const; Utils::FilePath cmakeExecutable() const; @@ -95,7 +97,7 @@ public: private: void readInformation() const; - void runCMake(Utils::QtcProcess &proc, const QStringList &args, int timeoutS = 1) const; + void runCMake(Utils::Process &proc, const QStringList &args, int timeoutS = 1) const; void parseFunctionDetailsOutput(const QString &output); QStringList parseVariableOutput(const QString &output); diff --git a/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp b/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp index 8bc396ced1a..7e4c777317d 100644 --- a/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp +++ b/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp @@ -141,6 +141,7 @@ void CMakeToolManager::restoreCMakeTools() emit m_instance->cmakeToolsLoaded(); // Store the default CMake tool "Autorun CMake" value globally + // TODO: Remove in Qt Creator 13 auto settings = Internal::CMakeSpecificSettings::instance(); if (settings->autorunCMake.value() == settings->autorunCMake.defaultValue()) { CMakeTool *cmake = defaultCMakeTool(); diff --git a/src/plugins/cmakeprojectmanager/cmaketoolsettingsaccessor.cpp b/src/plugins/cmakeprojectmanager/cmaketoolsettingsaccessor.cpp index 43f1915709f..05969b492e2 100644 --- a/src/plugins/cmakeprojectmanager/cmaketoolsettingsaccessor.cpp +++ b/src/plugins/cmakeprojectmanager/cmaketoolsettingsaccessor.cpp @@ -4,6 +4,7 @@ #include "cmaketoolsettingsaccessor.h" #include "cmakeprojectmanagertr.h" +#include "cmakespecificsettings.h" #include "cmaketool.h" #include @@ -44,36 +45,26 @@ const char CMAKE_TOOL_FILENAME[] = "cmaketools.xml"; static std::vector> autoDetectCMakeTools() { - Environment env = Environment::systemEnvironment(); - - FilePaths path = env.path(); - path = Utils::filteredUnique(path); + FilePaths extraDirs; if (HostOsInfo::isWindowsHost()) { for (const auto &envVar : QStringList{"ProgramFiles", "ProgramFiles(x86)", "ProgramW6432"}) { if (qtcEnvironmentVariableIsSet(envVar)) { const QString progFiles = qtcEnvironmentVariable(envVar); - path.append(FilePath::fromUserInput(progFiles + "/CMake")); - path.append(FilePath::fromUserInput(progFiles + "/CMake/bin")); + extraDirs.append(FilePath::fromUserInput(progFiles + "/CMake")); + extraDirs.append(FilePath::fromUserInput(progFiles + "/CMake/bin")); } } } if (HostOsInfo::isMacHost()) { - path.append("/Applications/CMake.app/Contents/bin"); - path.append("/usr/local/bin"); // homebrew intel - path.append("/opt/homebrew/bin"); // homebrew arm - path.append("/opt/local/bin"); // macports + extraDirs.append("/Applications/CMake.app/Contents/bin"); + extraDirs.append("/usr/local/bin"); // homebrew intel + extraDirs.append("/opt/homebrew/bin"); // homebrew arm + extraDirs.append("/opt/local/bin"); // macports } - FilePaths suspects; - for (const FilePath &base : std::as_const(path)) { - if (base.isEmpty()) - continue; - const FilePath suspect = base / "cmake"; - if (std::optional foundExe = suspect.refersToExecutableFile(FilePath::WithAnySuffix)) - suspects << *foundExe; - } + const FilePaths suspects = FilePath("cmake").searchAllInPath(extraDirs); std::vector> found; for (const FilePath &command : std::as_const(suspects)) { @@ -138,11 +129,10 @@ mergeTools(std::vector> &sdkTools, // CMakeToolSettingsAccessor: // -------------------------------------------------------------------- -CMakeToolSettingsAccessor::CMakeToolSettingsAccessor() : - UpgradingSettingsAccessor("QtCreatorCMakeTools", - Tr::tr("CMake"), - Core::Constants::IDE_DISPLAY_NAME) +CMakeToolSettingsAccessor::CMakeToolSettingsAccessor() { + setDocType("QtCreatorCMakeTools"); + setApplicationDisplayName(Core::Constants::IDE_DISPLAY_NAME); setBaseFilePath(Core::ICore::userResourcePath(CMAKE_TOOL_FILENAME)); addVersionUpgrader(std::make_unique()); @@ -185,9 +175,14 @@ void CMakeToolSettingsAccessor::saveCMakeTools(const QList &cmakeTo data.insert(QLatin1String(CMAKE_TOOL_DEFAULT_KEY), defaultId.toSetting()); int count = 0; - for (const CMakeTool *item : cmakeTools) { + for (CMakeTool *item : cmakeTools) { Utils::FilePath fi = item->cmakeExecutable(); + // Gobal Autorun value will be set for all tools + // TODO: Remove in Qt Creator 13 + const auto settings = CMakeSpecificSettings::instance(); + item->setAutorun(settings->autorunCMake.value()); + if (fi.needsDevice() || fi.isExecutableFile()) { // be graceful for device related stuff QVariantMap tmp = item->toMap(); if (tmp.isEmpty()) diff --git a/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp b/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp index 13a1510ebd6..1ef71721686 100644 --- a/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp +++ b/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp @@ -3,6 +3,7 @@ #include "fileapidataextractor.h" +#include "cmakeprojectconstants.h" #include "cmakeprojectmanagertr.h" #include "cmakeprojectplugin.h" #include "cmakespecificsettings.h" @@ -13,8 +14,8 @@ #include #include +#include #include -#include #include #include @@ -27,6 +28,8 @@ using namespace CMakeProjectManager::Internal::FileApiDetails; namespace CMakeProjectManager::Internal { +static Q_LOGGING_CATEGORY(cmakeLogger, "qtc.cmake.fileApiExtractor", QtWarningMsg); + // -------------------------------------------------------------------- // Helpers: // -------------------------------------------------------------------- @@ -53,7 +56,24 @@ CMakeFileResult extractCMakeFilesData(const std::vector &cmakefil const int oldCount = result.cmakeFiles.count(); CMakeFileInfo absolute(info); absolute.path = sfn; + + const auto mimeType = Utils::mimeTypeForFile(info.path); + if (mimeType.matchesName(Constants::CMAKE_MIMETYPE) + || mimeType.matchesName(Constants::CMAKE_PROJECT_MIMETYPE)) { + expected_str fileContent = sfn.fileContents(); + std::string errorString; + if (fileContent) { + fileContent = fileContent->replace("\r\n", "\n"); + if (!absolute.cmakeListFile.ParseString(fileContent->toStdString(), + sfn.fileName().toStdString(), + errorString)) + qCWarning(cmakeLogger) + << "Failed to parse:" << sfn.path() << QString::fromLatin1(errorString); + } + } + result.cmakeFiles.insert(absolute); + if (oldCount < result.cmakeFiles.count()) { if (info.isCMake && !info.isCMakeListsDotTxt) { // Skip files that cmake considers to be part of the installation -- but include @@ -245,7 +265,7 @@ QList generateBuildTargets(const PreprocessedData &input, continue; // CMake sometimes mixes several shell-escaped pieces into one fragment. Disentangle that again: - const QStringList parts = ProcessArgs::splitArgs(f.fragment); + const QStringList parts = ProcessArgs::splitArgs(f.fragment, HostOsInfo::hostOs()); for (QString part : parts) { // Library search paths that are added with target_link_directories are added as // -LIBPATH:... (Windows/MSVC), or @@ -264,7 +284,7 @@ QList generateBuildTargets(const PreprocessedData &input, continue; const FilePath buildDir = haveLibrariesRelativeToBuildDirectory ? buildDirectory : currentBuildDir; - FilePath tmp = buildDir.resolvePath(FilePath::fromUserInput(part).onDevice(buildDir)); + FilePath tmp = buildDir.resolvePath(part); if (f.role == "libraries") tmp = tmp.parentDir(); @@ -306,7 +326,7 @@ static QStringList splitFragments(const QStringList &fragments) { QStringList result; for (const QString &f : fragments) { - result += ProcessArgs::splitArgs(f); + result += ProcessArgs::splitArgs(f, HostOsInfo::hostOs()); } return result; } @@ -497,11 +517,8 @@ FolderNode *createSourceGroupNode(const QString &sourceGroupName, const QStringList parts = sourceGroupName.split("\\"); for (const QString &p : parts) { - FolderNode *existingNode = Utils::findOrDefault(currentNode->folderNodes(), - [&p](const FolderNode *fn) { - return fn->displayName() == p; - }); - + FolderNode *existingNode = currentNode->findChildFolderNode( + [&p](const FolderNode *fn) { return fn->displayName() == p; }); if (!existingNode) { auto node = createCMakeVFolder(sourceDirectory, Node::DefaultFolderPriority + 5, p); node->setListInProject(false); @@ -605,6 +622,28 @@ void addCompileGroups(ProjectNode *targetRoot, std::move(otherFileNodes)); } +static void addGeneratedFilesNode(ProjectNode *targetRoot, const FilePath &topLevelBuildDir, + const TargetDetails &td) +{ + if (td.artifacts.isEmpty()) + return; + FileType type = FileType::Unknown; + if (td.type == "EXECUTABLE") + type = FileType::App; + else if (td.type == "SHARED_LIBRARY" || td.type == "STATIC_LIBRARY") + type = FileType::Lib; + if (type == FileType::Unknown) + return; + std::vector> nodes; + const FilePath buildDir = topLevelBuildDir.resolvePath(td.buildDir); + for (const FilePath &artifact : td.artifacts) { + nodes.emplace_back(new FileNode(buildDir.resolvePath(artifact), type)); + type = FileType::Unknown; + nodes.back()->setIsGenerated(true); + } + addCMakeVFolder(targetRoot, buildDir, 10, Tr::tr(""), std::move(nodes)); +} + void addTargets(const QHash &cmakeListsNodes, const Configuration &config, const std::vector &targetDetails, @@ -635,6 +674,7 @@ void addTargets(const QHash &cm tNode->setBuildDirectory(directoryBuildDir(config, buildDir, t.directory)); addCompileGroups(tNode, sourceDir, dir, tNode->buildDirectory(), td); + addGeneratedFilesNode(tNode, buildDir, td); } } @@ -671,6 +711,9 @@ std::unique_ptr generateRootProjectNode( std::move(data.cmakeNodesBuild), std::move(data.cmakeNodesOther)); + + addCMakePresets(result.get(), sourceDirectory); + data.cmakeNodesSource.clear(); // Remove all the nullptr in the vector... data.cmakeNodesBuild.clear(); // Remove all the nullptr in the vector... data.cmakeNodesOther.clear(); // Remove all the nullptr in the vector... diff --git a/src/plugins/cmakeprojectmanager/fileapidataextractor.h b/src/plugins/cmakeprojectmanager/fileapidataextractor.h index 04f4ecea43f..e4ed3aa778c 100644 --- a/src/plugins/cmakeprojectmanager/fileapidataextractor.h +++ b/src/plugins/cmakeprojectmanager/fileapidataextractor.h @@ -5,6 +5,7 @@ #include "cmakebuildtarget.h" #include "cmakeprojectnodes.h" +#include "3rdparty/cmake/cmListFileCache.h" #include @@ -32,6 +33,7 @@ public: bool isCMakeListsDotTxt = false; bool isExternal = false; bool isGenerated = false; + cmListFile cmakeListFile; }; class FileApiQtcData diff --git a/src/plugins/cmakeprojectmanager/fileapiparser.cpp b/src/plugins/cmakeprojectmanager/fileapiparser.cpp index 0f25f27580d..e4dca5c9b26 100644 --- a/src/plugins/cmakeprojectmanager/fileapiparser.cpp +++ b/src/plugins/cmakeprojectmanager/fileapiparser.cpp @@ -16,6 +16,7 @@ #include #include #include +#include using namespace Utils; @@ -825,7 +826,7 @@ static QStringList uniqueTargetFiles(const Configuration &config) return files; } -FileApiData FileApiParser::parseData(QFutureInterface> &fi, +FileApiData FileApiParser::parseData(QPromise> &promise, const FilePath &replyFilePath, const QString &cmakeBuildType, QString &errorMessage) @@ -836,8 +837,8 @@ FileApiData FileApiParser::parseData(QFutureInterface #include -#include #include #include #include #include +QT_BEGIN_NAMESPACE +template +class QPromise; +QT_END_NAMESPACE + namespace CMakeProjectManager::Internal { namespace FileApiDetails { @@ -218,7 +222,7 @@ public: class FileApiParser { public: - static FileApiData parseData(QFutureInterface> &fi, + static FileApiData parseData(QPromise> &promise, const Utils::FilePath &replyFilePath, const QString &cmakeBuildType, QString &errorMessage); diff --git a/src/plugins/cmakeprojectmanager/fileapireader.cpp b/src/plugins/cmakeprojectmanager/fileapireader.cpp index c6ee73d6abb..fd507f7bf71 100644 --- a/src/plugins/cmakeprojectmanager/fileapireader.cpp +++ b/src/plugins/cmakeprojectmanager/fileapireader.cpp @@ -5,7 +5,6 @@ #include "cmakeprocess.h" #include "cmakeprojectmanagertr.h" -#include "cmakeprojectplugin.h" #include "cmakespecificsettings.h" #include "fileapidataextractor.h" #include "fileapiparser.h" @@ -15,8 +14,8 @@ #include #include +#include #include -#include #include @@ -166,20 +165,19 @@ bool FileApiReader::isParsing() const return m_isParsing; } -QSet FileApiReader::projectFilesToWatch() const -{ - return Utils::transform( - Utils::filtered(m_cmakeFiles, - [](const CMakeFileInfo &info) { return !info.isGenerated; }), - [](const CMakeFileInfo &info) { return info.path;}); -} - QList FileApiReader::takeBuildTargets(QString &errorMessage){ Q_UNUSED(errorMessage) return std::exchange(m_buildTargets, {}); } +QSet FileApiReader::takeCMakeFileInfos(QString &errorMessage) +{ + Q_UNUSED(errorMessage) + + return std::exchange(m_cmakeFiles, {}); +} + CMakeConfig FileApiReader::takeParsedConfiguration(QString &errorMessage) { if (m_lastCMakeExitCode != 0) @@ -235,11 +233,11 @@ void FileApiReader::endState(const FilePath &replyFilePath, bool restoredFromBac m_lastReplyTimestamp = replyFilePath.lastModified(); - m_future = runAsync(ProjectExplorerPlugin::sharedThreadPool(), + m_future = Utils::asyncRun(ProjectExplorerPlugin::sharedThreadPool(), [replyFilePath, sourceDirectory, buildDirectory, cmakeBuildType]( - QFutureInterface> &fi) { + QPromise> &promise) { auto result = std::make_shared(); - FileApiData data = FileApiParser::parseData(fi, + FileApiData data = FileApiParser::parseData(promise, replyFilePath, cmakeBuildType, result->errorMessage); @@ -248,7 +246,7 @@ void FileApiReader::endState(const FilePath &replyFilePath, bool restoredFromBac else qWarning() << result->errorMessage; - fi.reportResult(result); + promise.addResult(result); }); onResultReady(m_future.value(), this, diff --git a/src/plugins/cmakeprojectmanager/fileapireader.h b/src/plugins/cmakeprojectmanager/fileapireader.h index c854ce20400..115d22ea71a 100644 --- a/src/plugins/cmakeprojectmanager/fileapireader.h +++ b/src/plugins/cmakeprojectmanager/fileapireader.h @@ -44,8 +44,8 @@ public: bool isParsing() const; - QSet projectFilesToWatch() const; QList takeBuildTargets(QString &errorMessage); + QSet takeCMakeFileInfos(QString &errorMessage); CMakeConfig takeParsedConfiguration(QString &errorMessage); QString ctestPath() const; ProjectExplorer::RawProjectParts createRawProjectParts(QString &errorMessage); diff --git a/src/plugins/cmakeprojectmanager/presetsmacros.cpp b/src/plugins/cmakeprojectmanager/presetsmacros.cpp index af5524216fb..220d6d6575d 100644 --- a/src/plugins/cmakeprojectmanager/presetsmacros.cpp +++ b/src/plugins/cmakeprojectmanager/presetsmacros.cpp @@ -10,6 +10,8 @@ #include #include +using namespace Utils; + namespace CMakeProjectManager::Internal::CMakePresets::Macros { static QString getHostSystemName(Utils::OsType osType) @@ -61,6 +63,7 @@ static void expandAllButEnv(const PresetsDetails::BuildPreset &preset, value.replace("${sourceDirName}", sourceDirectory.fileName()); value.replace("${presetName}", preset.name); + value.replace("${hostSystemName}", getHostSystemName(sourceDirectory.osType())); value.replace("${pathListSep}", Utils::OsSpecificAspects::pathListSeparator(sourceDirectory.osType())); } @@ -107,32 +110,26 @@ static QString expandMacroEnv(const QString ¯oPrefix, return result; } -static Utils::Environment getEnvCombined(const std::optional &optPresetEnv, - const Utils::Environment &env) +static Environment getEnvCombined(const std::optional &optPresetEnv, + const Environment &env) { - Utils::Environment result = env; + Environment result = env; - if (!optPresetEnv) - return result; - - Utils::Environment presetEnv = optPresetEnv.value(); - for (auto it = presetEnv.constBegin(); it != presetEnv.constEnd(); ++it) { - result.set(it.key().name, it.value().first); + if (optPresetEnv) { + optPresetEnv->forEachEntry([&result](const QString &key, const QString &value, bool) { + result.set(key, value); + }); } return result; } template -void expand(const PresetType &preset, - Utils::Environment &env, - const Utils::FilePath &sourceDirectory) +void expand(const PresetType &preset, Environment &env, const FilePath &sourceDirectory) { - const Utils::Environment presetEnv = getEnvCombined(preset.environment, env); - for (auto it = presetEnv.constBegin(); it != presetEnv.constEnd(); ++it) { - const QString key = it.key().name; - QString value = it.value().first; - + const Environment presetEnv = getEnvCombined(preset.environment, env); + presetEnv.forEachEntry([&](const QString &key, const QString &value_, bool) { + QString value = value_; expandAllButEnv(preset, sourceDirectory, value); value = expandMacroEnv("env", value, [presetEnv](const QString ¯oName) { return presetEnv.value(macroName); @@ -141,7 +138,7 @@ void expand(const PresetType &preset, QString sep; bool append = true; if (key.compare("PATH", Qt::CaseInsensitive) == 0) { - sep = Utils::OsSpecificAspects::pathListSeparator(env.osType()); + sep = OsSpecificAspects::pathListSeparator(env.osType()); const int index = value.indexOf("$penv{PATH}", 0, Qt::CaseInsensitive); if (index != 0) append = false; @@ -159,20 +156,15 @@ void expand(const PresetType &preset, env.appendOrSet(key, value, sep); else env.prependOrSet(key, value, sep); - } + }); } template -void expand(const PresetType &preset, - Utils::EnvironmentItems &envItems, - const Utils::FilePath &sourceDirectory) +void expand(const PresetType &preset, EnvironmentItems &envItems, const FilePath &sourceDirectory) { - const Utils::Environment presetEnv = preset.environment ? preset.environment.value() - : Utils::Environment(); - for (auto it = presetEnv.constBegin(); it != presetEnv.constEnd(); ++it) { - const QString key = it.key().name; - QString value = it.value().first; - + const Environment presetEnv = preset.environment ? *preset.environment : Environment(); + presetEnv.forEachEntry([&](const QString &key, const QString &value_, bool) { + QString value = value_; expandAllButEnv(preset, sourceDirectory, value); value = expandMacroEnv("env", value, [presetEnv](const QString ¯oName) { if (presetEnv.hasKey(macroName)) @@ -180,12 +172,12 @@ void expand(const PresetType &preset, return QString("${%1}").arg(macroName); }); - auto operation = Utils::EnvironmentItem::Operation::SetEnabled; + auto operation = EnvironmentItem::Operation::SetEnabled; if (key.compare("PATH", Qt::CaseInsensitive) == 0) { - operation = Utils::EnvironmentItem::Operation::Append; + operation = EnvironmentItem::Operation::Append; const int index = value.indexOf("$penv{PATH}", 0, Qt::CaseInsensitive); if (index != 0) - operation = Utils::EnvironmentItem::Operation::Prepend; + operation = EnvironmentItem::Operation::Prepend; value.replace("$penv{PATH}", "", Qt::CaseInsensitive); } @@ -197,7 +189,7 @@ void expand(const PresetType &preset, expandAllButEnv(preset, sourceDirectory, value); envItems.emplace_back(Utils::EnvironmentItem(key, value, operation)); - } + }); } template diff --git a/src/plugins/cmakeprojectmanager/presetsparser.cpp b/src/plugins/cmakeprojectmanager/presetsparser.cpp index 5ebd17fd630..f3be87f7ea8 100644 --- a/src/plugins/cmakeprojectmanager/presetsparser.cpp +++ b/src/plugins/cmakeprojectmanager/presetsparser.cpp @@ -494,16 +494,6 @@ static QHash merge(const QHash &first, return result; } -static Utils::Environment merge(const Utils::Environment &first, const Utils::Environment &second) -{ - Utils::Environment result = first; - for (auto it = second.constBegin(); it != second.constEnd(); ++it) { - result.set(it.key().name, it.value().first); - } - - return result; -} - static CMakeConfig merge(const CMakeConfig &first, const CMakeConfig &second) { return Utils::setUnionMerge( @@ -561,7 +551,7 @@ void PresetsDetails::ConfigurePreset::inheritFrom(const ConfigurePreset &other) if (!environment && other.environment) environment = other.environment; else if (environment && other.environment) - environment = merge(other.environment.value(), environment.value()); + environment = environment.value().appliedToEnvironment(other.environment.value()); if (!warnings && other.warnings) warnings = other.warnings; @@ -587,7 +577,7 @@ void PresetsDetails::BuildPreset::inheritFrom(const BuildPreset &other) if (!environment && other.environment) environment = other.environment; else if (environment && other.environment) - environment = merge(other.environment.value(), environment.value()); + environment = environment.value().appliedToEnvironment(other.environment.value()); if (!configurePreset && other.configurePreset) configurePreset = other.configurePreset; diff --git a/src/plugins/cmakeprojectmanager/projecttreehelper.cpp b/src/plugins/cmakeprojectmanager/projecttreehelper.cpp index 3fb89d9fec6..7b9efb07b94 100644 --- a/src/plugins/cmakeprojectmanager/projecttreehelper.cpp +++ b/src/plugins/cmakeprojectmanager/projecttreehelper.cpp @@ -3,9 +3,11 @@ #include "projecttreehelper.h" +#include "cmakeproject.h" #include "cmakeprojectmanagertr.h" #include +#include #include #include @@ -42,8 +44,7 @@ void addCMakeVFolder(FolderNode *base, base->addNode(std::move(newFolder)); } folder->addNestedNodes(std::move(files)); - for (FolderNode *fn : folder->folderNodes()) - fn->compress(); + folder->forEachFolderNode([] (FolderNode *fn) { fn->compress(); }); } std::vector> &&removeKnownNodes( @@ -89,6 +90,34 @@ void addCMakeInputs(FolderNode *root, root->addNode(std::move(cmakeVFolder)); } +void addCMakePresets(FolderNode *root, const Utils::FilePath &sourceDir) +{ + QStringList presetFileNames; + presetFileNames << "CMakePresets.json"; + presetFileNames << "CMakeUserPresets.json"; + + const CMakeProject *cp = static_cast( + ProjectManager::projectForFile(sourceDir.pathAppended("CMakeLists.txt"))); + + if (cp && cp->presetsData().include) + presetFileNames.append(cp->presetsData().include.value()); + + std::vector> presets; + for (const auto &fileName : presetFileNames) { + Utils::FilePath file = sourceDir.pathAppended(fileName); + if (file.exists()) + presets.push_back(std::make_unique(file, Node::fileTypeForFileName(file))); + } + + if (presets.empty()) + return; + + std::unique_ptr cmakeVFolder = std::make_unique(root->filePath()); + addCMakeVFolder(cmakeVFolder.get(), sourceDir, 1000, QString(), std::move(presets)); + + root->addNode(std::move(cmakeVFolder)); +} + QHash addCMakeLists( CMakeProjectNode *root, std::vector> &&cmakeLists) { diff --git a/src/plugins/cmakeprojectmanager/projecttreehelper.h b/src/plugins/cmakeprojectmanager/projecttreehelper.h index 66cde4aea9b..34a8723759f 100644 --- a/src/plugins/cmakeprojectmanager/projecttreehelper.h +++ b/src/plugins/cmakeprojectmanager/projecttreehelper.h @@ -30,6 +30,8 @@ void addCMakeInputs(ProjectExplorer::FolderNode *root, std::vector> &&buildInputs, std::vector> &&rootInputs); +void addCMakePresets(ProjectExplorer::FolderNode *root, const Utils::FilePath &sourceDir); + QHash addCMakeLists( CMakeProjectNode *root, std::vector> &&cmakeLists); diff --git a/src/plugins/coco/cocoplugin.cpp b/src/plugins/coco/cocoplugin.cpp index abea861af1e..f192b70a62a 100644 --- a/src/plugins/coco/cocoplugin.cpp +++ b/src/plugins/coco/cocoplugin.cpp @@ -40,12 +40,13 @@ void CocoPluginPrivate::startCoco() auto layout = new QFormLayout(); const Environment env = Environment::systemEnvironment(); - FilePath squishCocoPath = FilePath::fromString(env.value("SQUISHCOCO")); - const FilePaths candidates = env.findAllInPath("coveragebrowser", {squishCocoPath}); + const FilePath squishCocoPath = FilePath::fromUserInput(env.value("SQUISHCOCO")); + const FilePath candidate = FilePath("coveragebrowser").searchInPath({squishCocoPath}, + FilePath::PrependToPath); PathChooser cocoChooser; - if (!candidates.isEmpty()) - cocoChooser.setFilePath(candidates.first()); + if (!candidate.isEmpty()) + cocoChooser.setFilePath(candidate); cocoChooser.setExpectedKind(PathChooser::Command); cocoChooser.setPromptDialogTitle(Tr::tr("Select a Squish Coco CoverageBrowser Executable")); diff --git a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseproject.cpp b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseproject.cpp index 051552d16ca..ad614e7e23a 100644 --- a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseproject.cpp +++ b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseproject.cpp @@ -148,8 +148,8 @@ void addDriverModeFlagIfNeeded(const ToolChain *toolchain, RawProjectPart makeRawProjectPart(const Utils::FilePath &projectFile, Kit *kit, ProjectExplorer::KitInfo &kitInfo, - const QString &workingDir, - const Utils::FilePath &fileName, + const FilePath &workingDir, + const FilePath &filePath, QStringList flags) { HeaderPaths headerPaths; @@ -157,7 +157,7 @@ RawProjectPart makeRawProjectPart(const Utils::FilePath &projectFile, CppEditor::ProjectFile::Kind fileKind = CppEditor::ProjectFile::Unclassified; const QStringList originalFlags = flags; - filteredFlags(fileName.fileName(), + filteredFlags(filePath, workingDir, flags, headerPaths, @@ -166,10 +166,12 @@ RawProjectPart makeRawProjectPart(const Utils::FilePath &projectFile, kitInfo.sysRootPath); RawProjectPart rpp; + rpp.setProjectFileLocation(projectFile.toString()); - rpp.setBuildSystemTarget(workingDir); - rpp.setDisplayName(fileName.fileName()); - rpp.setFiles({fileName.toString()}); + rpp.setBuildSystemTarget(workingDir.path()); + rpp.setDisplayName(filePath.fileName()); + rpp.setFiles({filePath.toFSPathString()}); + rpp.setHeaderPaths(headerPaths); rpp.setMacros(macros); @@ -221,13 +223,10 @@ FolderNode *addChildFolderNode(FolderNode *parent, const QString &childName) FolderNode *addOrGetChildFolderNode(FolderNode *parent, const QString &childName) { - for (FolderNode *folder : parent->folderNodes()) { - if (folder->filePath().fileName() == childName) { - return folder; - } - } - - return addChildFolderNode(parent, childName); + FolderNode *fn = parent->findChildFolderNode([&](FolderNode *folder) { + return folder->filePath().fileName() == childName; + }); + return fn ? fn : addChildFolderNode(parent, childName); } // Return the node for folderPath. diff --git a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseprojectmanager.qbs b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseprojectmanager.qbs index 9c12b05a3a7..ce7e4e35ac3 100644 --- a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseprojectmanager.qbs +++ b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseprojectmanager.qbs @@ -21,9 +21,7 @@ QtcPlugin { "compilationdbparser.h", ] - Group { - name: "Tests" - condition: qtc.testsEnabled + QtcTestFiles { files: [ "compilationdatabasetests.cpp", "compilationdatabasetests.h", diff --git a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseprojectmanagerplugin.cpp b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseprojectmanagerplugin.cpp index fc1da70ca75..13f03c0ed31 100644 --- a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseprojectmanagerplugin.cpp +++ b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseprojectmanagerplugin.cpp @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include @@ -72,7 +72,7 @@ void CompilationDatabaseProjectManagerPlugin::initialize() d->changeRootAction.setEnabled(currentProject); }; - connect(SessionManager::instance(), &SessionManager::startupProjectChanged, + connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, this, onProjectChanged); connect(ProjectTree::instance(), &ProjectTree::currentProjectChanged, diff --git a/src/plugins/compilationdatabaseprojectmanager/compilationdatabasetests.cpp b/src/plugins/compilationdatabaseprojectmanager/compilationdatabasetests.cpp index 6fff4947647..f8b25747716 100644 --- a/src/plugins/compilationdatabaseprojectmanager/compilationdatabasetests.cpp +++ b/src/plugins/compilationdatabaseprojectmanager/compilationdatabasetests.cpp @@ -92,7 +92,9 @@ public: QStringList getFilteredFlags() { - filteredFlags(fileName, workingDir, flags, headerPaths, macros, fileKind, sysRoot); + filteredFlags(FilePath::fromString(fileName), + FilePath::fromString(workingDir), + flags, headerPaths, macros, fileKind, sysRoot); return flags; } diff --git a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseutils.cpp b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseutils.cpp index df8e08a68f5..be0ab87e547 100644 --- a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseutils.cpp +++ b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseutils.cpp @@ -22,15 +22,6 @@ using namespace Utils; namespace CompilationDatabaseProjectManager { namespace Internal { -static QString updatedPathFlag(const QString &pathStr, const QString &workingDir) -{ - QString result = pathStr; - if (QDir(pathStr).isRelative()) - result = workingDir + "/" + pathStr; - - return result; -} - static CppEditor::ProjectFile::Kind fileKindFromString(QString flag) { using namespace CppEditor; @@ -86,13 +77,13 @@ QStringList filterFromFileName(const QStringList &flags, QString fileName) return result; } -void filteredFlags(const QString &fileName, - const QString &workingDir, +void filteredFlags(const FilePath &filePath, + const FilePath &workingDir, QStringList &flags, HeaderPaths &headerPaths, Macros ¯os, CppEditor::ProjectFile::Kind &fileKind, - Utils::FilePath &sysRoot) + FilePath &sysRoot) { if (flags.empty()) return; @@ -113,7 +104,7 @@ void filteredFlags(const QString &fileName, } if (includePathType) { - const QString pathStr = updatedPathFlag(flag, workingDir); + const QString pathStr = workingDir.resolvePath(flag).toString(); headerPaths.append({pathStr, includePathType.value()}); includePathType.reset(); continue; @@ -145,14 +136,14 @@ void filteredFlags(const QString &fileName, continue; } - const QStringList userIncludeFlags{"-I", "/I"}; - const QStringList systemIncludeFlags{"-isystem", "-imsvc", "/imsvc"}; + const QStringList userIncludeFlags{"-I", "-iquote", "/I"}; + const QStringList systemIncludeFlags{"-isystem", "-idirafter", "-imsvc", "/imsvc"}; const QStringList allIncludeFlags = QStringList(userIncludeFlags) << systemIncludeFlags; const QString includeOpt = Utils::findOrDefault(allIncludeFlags, [flag](const QString &opt) { return flag.startsWith(opt) && flag != opt; }); if (!includeOpt.isEmpty()) { - const QString pathStr = updatedPathFlag(flag.mid(includeOpt.length()), workingDir); + const QString pathStr = workingDir.resolvePath(flag.mid(includeOpt.length())).toString(); headerPaths.append({pathStr, userIncludeFlags.contains(includeOpt) ? HeaderPathType::User : HeaderPathType::System}); continue; @@ -182,14 +173,14 @@ void filteredFlags(const QString &fileName, if (flag.startsWith("--sysroot=")) { if (sysRoot.isEmpty()) - sysRoot = FilePath::fromUserInput(updatedPathFlag(flag.mid(10), workingDir)); + sysRoot = workingDir.resolvePath(flag.mid(10)); continue; } if ((flag.startsWith("-std=") || flag.startsWith("/std:")) && fileKind == CppEditor::ProjectFile::Unclassified) { const bool cpp = (flag.contains("c++") || flag.contains("gnu++")); - if (CppEditor::ProjectFile::isHeader(CppEditor::ProjectFile::classify(fileName))) + if (CppEditor::ProjectFile::isHeader(CppEditor::ProjectFile::classify(filePath.path()))) fileKind = cpp ? CppEditor::ProjectFile::CXXHeader : CppEditor::ProjectFile::CHeader; else fileKind = cpp ? CppEditor::ProjectFile::CXXSource : CppEditor::ProjectFile::CSource; @@ -203,7 +194,7 @@ void filteredFlags(const QString &fileName, } if (fileKind == CppEditor::ProjectFile::Unclassified) - fileKind = CppEditor::ProjectFile::classify(fileName); + fileKind = CppEditor::ProjectFile::classify(filePath.path()); flags = filtered; } diff --git a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseutils.h b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseutils.h index 79130799b48..ec024443d8f 100644 --- a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseutils.h +++ b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseutils.h @@ -4,10 +4,8 @@ #pragma once #include -#include #include -#include namespace ProjectExplorer { class HeaderPath; @@ -21,7 +19,7 @@ class DbEntry { public: QStringList flags; Utils::FilePath fileName; - QString workingDir; + Utils::FilePath workingDir; }; class DbContents { @@ -35,8 +33,8 @@ using MimeBinaryCache = QHash; QStringList filterFromFileName(const QStringList &flags, QString baseName); -void filteredFlags(const QString &fileName, - const QString &workingDir, +void filteredFlags(const Utils::FilePath &filePath, + const Utils::FilePath &workingDir, QStringList &flags, QVector &headerPaths, QVector ¯os, diff --git a/src/plugins/compilationdatabaseprojectmanager/compilationdbparser.cpp b/src/plugins/compilationdatabaseprojectmanager/compilationdbparser.cpp index 251e9685c45..ccbead4c4ca 100644 --- a/src/plugins/compilationdatabaseprojectmanager/compilationdbparser.cpp +++ b/src/plugins/compilationdatabaseprojectmanager/compilationdbparser.cpp @@ -8,8 +8,8 @@ #include #include +#include #include -#include #include #include @@ -95,7 +95,7 @@ void CompilationDbParser::start() } // Thread 2: Parse the project file. - const QFuture future = runAsync(&CompilationDbParser::parseProject, this); + const QFuture future = Utils::asyncRun(&CompilationDbParser::parseProject, this); Core::ProgressManager::addTask(future, Tr::tr("Parse \"%1\" project").arg(m_projectName), "CompilationDatabase.Parse"); @@ -182,7 +182,7 @@ std::vector CompilationDbParser::readJsonObjects() const const Utils::FilePath filePath = jsonObjectFilePath(object); const QStringList flags = filterFromFileName(jsonObjectFlags(object, flagsCache), filePath.fileName()); - result.push_back({flags, filePath, object["directory"].toString()}); + result.push_back({flags, filePath, FilePath::fromUserInput(object["directory"].toString())}); objectStart = m_projectFileContents.indexOf('{', objectEnd + 1); objectEnd = m_projectFileContents.indexOf('}', objectStart + 1); diff --git a/src/plugins/conan/CMakeLists.txt b/src/plugins/conan/CMakeLists.txt index c9ad746a84d..80da66de4d2 100644 --- a/src/plugins/conan/CMakeLists.txt +++ b/src/plugins/conan/CMakeLists.txt @@ -3,7 +3,7 @@ add_qtc_plugin(Conan SOURCES conanconstants.h conaninstallstep.cpp conaninstallstep.h - conanplugin.cpp conanplugin.h + conanplugin.cpp conansettings.cpp conansettings.h conantr.h ) diff --git a/src/plugins/conan/conan.qbs b/src/plugins/conan/conan.qbs index 486df35f49f..b5beeb7d359 100644 --- a/src/plugins/conan/conan.qbs +++ b/src/plugins/conan/conan.qbs @@ -13,7 +13,6 @@ QtcPlugin { "conanconstants.h", "conaninstallstep.h", "conaninstallstep.cpp", - "conanplugin.h", "conanplugin.cpp", "conansettings.h", "conansettings.cpp", diff --git a/src/plugins/conan/conaninstallstep.cpp b/src/plugins/conan/conaninstallstep.cpp index d0c2ef5bacd..2a634190365 100644 --- a/src/plugins/conan/conaninstallstep.cpp +++ b/src/plugins/conan/conaninstallstep.cpp @@ -4,7 +4,6 @@ #include "conaninstallstep.h" #include "conanconstants.h" -#include "conanplugin.h" #include "conansettings.h" #include "conantr.h" @@ -19,12 +18,40 @@ #include #include #include +#include using namespace ProjectExplorer; using namespace Utils; namespace Conan::Internal { +static FilePath conanFilePath(Project *project, const FilePath &defaultFilePath = {}) +{ + const FilePath projectDirectory = project->projectDirectory(); + // conanfile.py takes precedence over conanfile.txt when "conan install dir" is invoked + const FilePath conanPy = projectDirectory / "conanfile.py"; + if (conanPy.exists()) + return conanPy; + const FilePath conanTxt = projectDirectory / "conanfile.txt"; + if (conanTxt.exists()) + return conanTxt; + return defaultFilePath; +} + +static void connectTarget(Project *project, Target *target) +{ + if (!conanFilePath(project).isEmpty()) { + const QList buildConfigurations = target->buildConfigurations(); + for (BuildConfiguration *buildConfiguration : buildConfigurations) + buildConfiguration->buildSteps()->insertStep(0, Constants::INSTALL_STEP); + } + QObject::connect(target, &Target::addedBuildConfiguration, + target, [project] (BuildConfiguration *buildConfiguration) { + if (!conanFilePath(project).isEmpty()) + buildConfiguration->buildSteps()->insertStep(0, Constants::INSTALL_STEP); + }); +} + // ConanInstallStep class ConanInstallStep final : public AbstractProcessStep @@ -43,13 +70,12 @@ ConanInstallStep::ConanInstallStep(BuildStepList *bsl, Id id) setUseEnglishOutput(); setDisplayName(Tr::tr("Conan install")); - auto conanFile = addAspect(); + auto conanFile = addAspect(); conanFile->setSettingsKey("ConanPackageManager.InstallStep.ConanFile"); - conanFile->setFilePath(ConanPlugin::conanFilePath(project(), + conanFile->setFilePath(conanFilePath(project(), project()->projectDirectory() / "conanfile.txt")); conanFile->setLabelText(Tr::tr("Conan file:")); conanFile->setToolTip(Tr::tr("Enter location of conanfile.txt or conanfile.py.")); - conanFile->setDisplayStyle(StringAspect::PathChooserDisplay); conanFile->setExpectedKind(PathChooser::File); auto additionalArguments = addAspect(); @@ -68,7 +94,7 @@ ConanInstallStep::ConanInstallStep(BuildStepList *bsl, Id id) const QString buildType = bt == BuildConfiguration::Release ? QString("Release") : QString("Debug"); - CommandLine cmd(ConanPlugin::conanSettings()->conanFilePath.filePath()); + CommandLine cmd(settings().conanFilePath()); cmd.addArgs({"install", "-s", "build_type=" + buildType}); if (buildMissing->value()) cmd.addArg("--build=missing"); @@ -85,6 +111,12 @@ ConanInstallStep::ConanInstallStep(BuildStepList *bsl, Id id) setupProcessParameters(¶m); return param.summary(displayName()); }); + + connect(ProjectManager::instance(), &ProjectManager::projectAdded, this, [](Project * project) { + connect(project, &Project::addedTarget, project, [project] (Target *target) { + connectTarget(project, target); + }); + }); } bool ConanInstallStep::init() diff --git a/src/plugins/conan/conanplugin.cpp b/src/plugins/conan/conanplugin.cpp index a1a78acca33..13655b93b63 100644 --- a/src/plugins/conan/conanplugin.cpp +++ b/src/plugins/conan/conanplugin.cpp @@ -1,86 +1,26 @@ // Copyright (C) 2018 Jochen Seemann // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "conanplugin.h" - -#include "conanconstants.h" #include "conaninstallstep.h" #include "conansettings.h" -#include - -#include -#include -#include -#include -#include -#include -#include - -using namespace Core; -using namespace ProjectExplorer; -using namespace Utils; +#include namespace Conan::Internal { -class ConanPluginPrivate +class ConanPlugin final : public ExtensionSystem::IPlugin { + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Conan.json") + public: - ConanInstallStepFactory installStepFactory; + ConanPlugin() + { + addManaged(); + addManaged(); + } }; -ConanPlugin::~ConanPlugin() -{ - delete d; -} - -void ConanPlugin::initialize() -{ - d = new ConanPluginPrivate; - conanSettings()->readSettings(ICore::settings()); - - connect(SessionManager::instance(), &SessionManager::projectAdded, - this, &ConanPlugin::projectAdded); -} - -static void connectTarget(Project *project, Target *target) -{ - if (!ConanPlugin::conanFilePath(project).isEmpty()) { - const QList buildConfigurations = target->buildConfigurations(); - for (BuildConfiguration *buildConfiguration : buildConfigurations) - buildConfiguration->buildSteps()->appendStep(Constants::INSTALL_STEP); - } - QObject::connect(target, &Target::addedBuildConfiguration, - target, [project] (BuildConfiguration *buildConfiguration) { - if (!ConanPlugin::conanFilePath(project).isEmpty()) - buildConfiguration->buildSteps()->appendStep(Constants::INSTALL_STEP); - }); -} - -void ConanPlugin::projectAdded(Project *project) -{ - connect(project, &Project::addedTarget, project, [project] (Target *target) { - connectTarget(project, target); - }); -} - -ConanSettings *ConanPlugin::conanSettings() -{ - static ConanSettings theSettings; - return &theSettings; -} - -FilePath ConanPlugin::conanFilePath(Project *project, const FilePath &defaultFilePath) -{ - const FilePath projectDirectory = project->projectDirectory(); - // conanfile.py takes precedence over conanfile.txt when "conan install dir" is invoked - const FilePath conanPy = projectDirectory / "conanfile.py"; - if (conanPy.exists()) - return conanPy; - const FilePath conanTxt = projectDirectory / "conanfile.txt"; - if (conanTxt.exists()) - return conanTxt; - return defaultFilePath; -} - } // Conan::Internal + +#include "conanplugin.moc" diff --git a/src/plugins/conan/conanplugin.h b/src/plugins/conan/conanplugin.h deleted file mode 100644 index 59fbe976d2e..00000000000 --- a/src/plugins/conan/conanplugin.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (C) 2018 Jochen Seemann -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include -#include - -namespace ProjectExplorer { class Project; } - -namespace Conan::Internal { - -class ConanSettings; - -class ConanPlugin final : public ExtensionSystem::IPlugin -{ - Q_OBJECT - Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Conan.json") - -public: - static ConanSettings *conanSettings(); - static Utils::FilePath conanFilePath(ProjectExplorer::Project *project, - const Utils::FilePath &defaultFilePath = {}); - -private: - ~ConanPlugin() final; - void projectAdded(ProjectExplorer::Project *project); - - void initialize() final; - - class ConanPluginPrivate *d = nullptr; -}; - -} // Conan::Internal diff --git a/src/plugins/conan/conansettings.cpp b/src/plugins/conan/conansettings.cpp index 6a608b99f89..50373c764b6 100644 --- a/src/plugins/conan/conansettings.cpp +++ b/src/plugins/conan/conansettings.cpp @@ -3,22 +3,30 @@ #include "conansettings.h" +#include + #include using namespace Utils; namespace Conan::Internal { +static ConanSettings *theSettings; + +ConanSettings &settings() { return *theSettings; } + ConanSettings::ConanSettings() { + theSettings = this; + setSettingsGroup("ConanSettings"); setAutoApply(false); - registerAspect(&conanFilePath); conanFilePath.setSettingsKey("ConanFilePath"); - conanFilePath.setDisplayStyle(StringAspect::PathChooserDisplay); conanFilePath.setExpectedKind(PathChooser::ExistingCommand); conanFilePath.setDefaultValue(HostOsInfo::withExecutableSuffix("conan")); + + readSettings(Core::ICore::settings()); } } // Conan::Internal diff --git a/src/plugins/conan/conansettings.h b/src/plugins/conan/conansettings.h index 93e61908399..67485037288 100644 --- a/src/plugins/conan/conansettings.h +++ b/src/plugins/conan/conansettings.h @@ -12,7 +12,9 @@ class ConanSettings : public Utils::AspectContainer public: ConanSettings(); - Utils::StringAspect conanFilePath; + Utils::FilePathAspect conanFilePath{this}; }; +ConanSettings &settings(); + } // Conan::Internal diff --git a/src/plugins/copilot/CMakeLists.txt b/src/plugins/copilot/CMakeLists.txt new file mode 100644 index 00000000000..01129674fce --- /dev/null +++ b/src/plugins/copilot/CMakeLists.txt @@ -0,0 +1,19 @@ +add_qtc_plugin(Copilot + PLUGIN_DEPENDS Core LanguageClient + SOURCES + authwidget.cpp authwidget.h + copilot.qrc + copilotclient.cpp copilotclient.h + copilotconstants.h + copilothoverhandler.cpp copilothoverhandler.h + copilotoptionspage.cpp copilotoptionspage.h + copilotplugin.cpp copilotplugin.h + copilotprojectpanel.cpp copilotprojectpanel.h + copilotsettings.cpp copilotsettings.h + copilotsuggestion.cpp copilotsuggestion.h + requests/checkstatus.h + requests/getcompletions.h + requests/signinconfirm.h + requests/signininitiate.h + requests/signout.h +) diff --git a/src/plugins/copilot/Copilot.json.in b/src/plugins/copilot/Copilot.json.in new file mode 100644 index 00000000000..55ff6f6bf42 --- /dev/null +++ b/src/plugins/copilot/Copilot.json.in @@ -0,0 +1,19 @@ +{ + \"Name\" : \"Copilot\", + \"Version\" : \"$$QTCREATOR_VERSION\", + \"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\", + \"Experimental\" : true, + \"Vendor\" : \"The Qt Company Ltd\", + \"Copyright\" : \"(C) $$QTCREATOR_COPYRIGHT_YEAR The Qt Company Ltd\", + \"License\" : [ \"Commercial Usage\", + \"\", + \"Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt 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.\", + \"\", + \"GNU General Public License Usage\", + \"\", + \"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html.\" + ], + \"Description\" : \"Copilot support\", + \"Url\" : \"http://www.qt.io\", + $$dependencyList +} diff --git a/src/plugins/copilot/authwidget.cpp b/src/plugins/copilot/authwidget.cpp new file mode 100644 index 00000000000..f29e3d3a150 --- /dev/null +++ b/src/plugins/copilot/authwidget.cpp @@ -0,0 +1,159 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "authwidget.h" + +#include "copilotclient.h" +#include "copilottr.h" + +#include +#include + +#include + +#include +#include + +using namespace LanguageClient; +using namespace Copilot::Internal; + +namespace Copilot { + +AuthWidget::AuthWidget(QWidget *parent) + : QWidget(parent) +{ + using namespace Layouting; + + m_button = new QPushButton(Tr::tr("Sign in")); + m_button->setEnabled(false); + m_progressIndicator = new Utils::ProgressIndicator(Utils::ProgressIndicatorSize::Small); + m_progressIndicator->setVisible(false); + m_statusLabel = new QLabel(); + m_statusLabel->setVisible(false); + + // clang-format off + Column { + Row { + m_button, m_progressIndicator, st + }, + m_statusLabel + }.attachTo(this); + // clang-format on + + connect(m_button, &QPushButton::clicked, this, [this]() { + if (m_status == Status::SignedIn) + signOut(); + else if (m_status == Status::SignedOut) + signIn(); + }); +} + +AuthWidget::~AuthWidget() +{ + if (m_client) + LanguageClientManager::shutdownClient(m_client); +} + +void AuthWidget::setState(const QString &buttonText, bool working) +{ + m_button->setText(buttonText); + m_button->setVisible(true); + m_progressIndicator->setVisible(working); + m_statusLabel->setVisible(!m_statusLabel->text().isEmpty()); + m_button->setEnabled(!working); +} + +void AuthWidget::checkStatus() +{ + QTC_ASSERT(m_client && m_client->reachable(), return); + + setState("Checking status ...", true); + + m_client->requestCheckStatus(false, [this](const CheckStatusRequest::Response &response) { + if (response.error()) { + setState("failed: " + response.error()->message(), false); + return; + } + const CheckStatusResponse result = *response.result(); + + if (result.user().isEmpty()) { + setState("Sign in", false); + m_status = Status::SignedOut; + return; + } + + setState("Sign out " + result.user(), false); + m_status = Status::SignedIn; + }); +} + +void AuthWidget::updateClient(const Utils::FilePath &nodeJs, const Utils::FilePath &agent) +{ + LanguageClientManager::shutdownClient(m_client); + m_client = nullptr; + setState(Tr::tr("Sign in"), false); + m_button->setEnabled(false); + if (!nodeJs.isExecutableFile() || !agent.exists()) { + return; + } + + setState(Tr::tr("Sign in"), true); + + m_client = new CopilotClient(nodeJs, agent); + connect(m_client, &Client::initialized, this, &AuthWidget::checkStatus); +} + +void AuthWidget::signIn() +{ + qCritical() << "Not implemented"; + QTC_ASSERT(m_client && m_client->reachable(), return); + + setState("Signing in ...", true); + + m_client->requestSignInInitiate([this](const SignInInitiateRequest::Response &response) { + QTC_ASSERT(!response.error(), return); + + Utils::setClipboardAndSelection(response.result()->userCode()); + + QDesktopServices::openUrl(QUrl(response.result()->verificationUri())); + + m_statusLabel->setText(Tr::tr("A browser window will open, enter the code %1 when " + "asked.\nThe code has been copied to your clipboard.") + .arg(response.result()->userCode())); + m_statusLabel->setVisible(true); + + m_client + ->requestSignInConfirm(response.result()->userCode(), + [this](const SignInConfirmRequest::Response &response) { + m_statusLabel->setText(""); + + if (response.error()) { + QMessageBox::critical(this, + Tr::tr("Login failed"), + Tr::tr( + "The login request failed: ") + + response.error()->message()); + setState("Sign in", false); + return; + } + + setState("Sign Out " + response.result()->user(), false); + }); + }); +} + +void AuthWidget::signOut() +{ + QTC_ASSERT(m_client && m_client->reachable(), return); + + setState("Signing out ...", true); + + m_client->requestSignOut([this](const SignOutRequest::Response &response) { + QTC_ASSERT(!response.error(), return); + QTC_ASSERT(response.result()->status() == "NotSignedIn", return); + + checkStatus(); + }); +} + +} // namespace Copilot diff --git a/src/plugins/copilot/authwidget.h b/src/plugins/copilot/authwidget.h new file mode 100644 index 00000000000..acb18810fe4 --- /dev/null +++ b/src/plugins/copilot/authwidget.h @@ -0,0 +1,48 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "copilotclient.h" + +#include + +#include +#include +#include + +namespace LanguageClient { +class Client; +} + +namespace Copilot { + +class AuthWidget : public QWidget +{ + Q_OBJECT + + enum class Status { SignedIn, SignedOut, Unknown }; + +public: + explicit AuthWidget(QWidget *parent = nullptr); + ~AuthWidget() override; + + void updateClient(const Utils::FilePath &nodeJs, const Utils::FilePath &agent); + +private: + void setState(const QString &buttonText, bool working); + void checkStatus(); + + + void signIn(); + void signOut(); + +private: + Status m_status = Status::Unknown; + QPushButton *m_button = nullptr; + QLabel *m_statusLabel = nullptr; + Utils::ProgressIndicator *m_progressIndicator = nullptr; + Internal::CopilotClient *m_client = nullptr; +}; + +} // namespace Copilot diff --git a/src/plugins/copilot/copilot.qbs b/src/plugins/copilot/copilot.qbs new file mode 100644 index 00000000000..714c45543d4 --- /dev/null +++ b/src/plugins/copilot/copilot.qbs @@ -0,0 +1,37 @@ +import qbs 1.0 + +QtcPlugin { + name: "Copilot" + + Depends { name: "Core" } + Depends { name: "LanguageClient" } + Depends { name: "ProjectExplorer" } + Depends { name: "TextEditor" } + Depends { name: "Qt"; submodules: ["widgets", "xml", "network"] } + + files: [ + "authwidget.cpp", + "authwidget.h", + "copilot.qrc", + "copilotclient.cpp", + "copilotclient.h", + "copilotconstants.h", + "copilothoverhandler.cpp", + "copilothoverhandler.h", + "copilotoptionspage.cpp", + "copilotoptionspage.h", + "copilotplugin.cpp", + "copilotplugin.h", + "copilotprojectpanel.cpp", + "copilotprojectpanel.h", + "copilotsettings.cpp", + "copilotsettings.h", + "copilotsuggestion.cpp", + "copilotsuggestion.h", + "requests/checkstatus.h", + "requests/getcompletions.h", + "requests/signinconfirm.h", + "requests/signininitiate.h", + "requests/signout.h", + ] +} diff --git a/src/plugins/copilot/copilot.qrc b/src/plugins/copilot/copilot.qrc new file mode 100644 index 00000000000..65d481b92ac --- /dev/null +++ b/src/plugins/copilot/copilot.qrc @@ -0,0 +1,8 @@ + + + images/settingscategory_copilot.png + images/settingscategory_copilot@2x.png + images/copilot.png + images/copilot@2x.png + + diff --git a/src/plugins/copilot/copilotclient.cpp b/src/plugins/copilot/copilotclient.cpp new file mode 100644 index 00000000000..f935210b53d --- /dev/null +++ b/src/plugins/copilot/copilotclient.cpp @@ -0,0 +1,252 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "copilotclient.h" +#include "copilotconstants.h" +#include "copilotsettings.h" +#include "copilotsuggestion.h" + +#include +#include +#include + +#include +#include + +#include + +#include + +#include +#include + +#include + +#include +#include + +using namespace LanguageServerProtocol; +using namespace TextEditor; +using namespace Utils; +using namespace ProjectExplorer; +using namespace Core; + +namespace Copilot::Internal { + +static LanguageClient::BaseClientInterface *clientInterface(const FilePath &nodePath, + const FilePath &distPath) +{ + CommandLine cmd{nodePath, {distPath.toFSPathString()}}; + + const auto interface = new LanguageClient::StdIOClientInterface; + interface->setCommandLine(cmd); + return interface; +} + +CopilotClient::CopilotClient(const FilePath &nodePath, const FilePath &distPath) + : LanguageClient::Client(clientInterface(nodePath, distPath)) +{ + setName("Copilot"); + LanguageClient::LanguageFilter langFilter; + + langFilter.filePattern = {"*"}; + + setSupportedLanguage(langFilter); + start(); + + auto openDoc = [this](IDocument *document) { + if (auto *textDocument = qobject_cast(document)) + openDocument(textDocument); + }; + + connect(EditorManager::instance(), &EditorManager::documentOpened, this, openDoc); + connect(EditorManager::instance(), + &EditorManager::documentClosed, + this, + [this](IDocument *document) { + if (auto textDocument = qobject_cast(document)) + closeDocument(textDocument); + }); + + for (IDocument *doc : DocumentModel::openedDocuments()) + openDoc(doc); +} + +CopilotClient::~CopilotClient() +{ + for (IEditor *editor : DocumentModel::editorsForOpenedDocuments()) { + if (auto textEditor = qobject_cast(editor)) + textEditor->editorWidget()->removeHoverHandler(&m_hoverHandler); + } +} + +void CopilotClient::openDocument(TextDocument *document) +{ + auto project = ProjectManager::projectForFile(document->filePath()); + if (!isEnabled(project)) + return; + + Client::openDocument(document); + connect(document, + &TextDocument::contentsChangedWithPosition, + this, + [this, document](int position, int charsRemoved, int charsAdded) { + Q_UNUSED(charsRemoved) + if (!CopilotSettings::instance().autoComplete()) + return; + + auto project = ProjectManager::projectForFile(document->filePath()); + if (!isEnabled(project)) + return; + + auto textEditor = BaseTextEditor::currentTextEditor(); + if (!textEditor || textEditor->document() != document) + return; + TextEditorWidget *widget = textEditor->editorWidget(); + if (widget->multiTextCursor().hasMultipleCursors()) + return; + const int cursorPosition = widget->textCursor().position(); + if (cursorPosition < position || cursorPosition > position + charsAdded) + return; + scheduleRequest(widget); + }); +} + +void CopilotClient::scheduleRequest(TextEditorWidget *editor) +{ + cancelRunningRequest(editor); + + if (!m_scheduledRequests.contains(editor)) { + auto timer = new QTimer(this); + timer->setSingleShot(true); + connect(timer, &QTimer::timeout, this, [this, editor]() { + if (m_scheduledRequests[editor].cursorPosition == editor->textCursor().position()) + requestCompletions(editor); + }); + connect(editor, &TextEditorWidget::destroyed, this, [this, editor]() { + delete m_scheduledRequests.take(editor).timer; + cancelRunningRequest(editor); + }); + connect(editor, &TextEditorWidget::cursorPositionChanged, this, [this, editor] { + cancelRunningRequest(editor); + }); + m_scheduledRequests.insert(editor, {editor->textCursor().position(), timer}); + } else { + m_scheduledRequests[editor].cursorPosition = editor->textCursor().position(); + } + m_scheduledRequests[editor].timer->start(500); +} + +void CopilotClient::requestCompletions(TextEditorWidget *editor) +{ + auto project = ProjectManager::projectForFile(editor->textDocument()->filePath()); + + if (!isEnabled(project)) + return; + + Utils::MultiTextCursor cursor = editor->multiTextCursor(); + if (cursor.hasMultipleCursors() || cursor.hasSelection() || editor->suggestionVisible()) + return; + + const Utils::FilePath filePath = editor->textDocument()->filePath(); + GetCompletionRequest request{ + {TextDocumentIdentifier(hostPathToServerUri(filePath)), + documentVersion(filePath), + Position(cursor.mainCursor())}}; + request.setResponseCallback([this, editor = QPointer(editor)]( + const GetCompletionRequest::Response &response) { + QTC_ASSERT(editor, return); + handleCompletions(response, editor); + }); + m_runningRequests[editor] = request; + sendMessage(request); +} + +void CopilotClient::handleCompletions(const GetCompletionRequest::Response &response, + TextEditorWidget *editor) +{ + if (response.error()) + log(*response.error()); + + int requestPosition = -1; + if (const auto requestParams = m_runningRequests.take(editor).params()) + requestPosition = requestParams->position().toPositionInDocument(editor->document()); + + const Utils::MultiTextCursor cursors = editor->multiTextCursor(); + if (cursors.hasMultipleCursors()) + return; + + if (cursors.hasSelection() || cursors.mainCursor().position() != requestPosition) + return; + + if (const std::optional result = response.result()) { + QList completions = result->completions().toListOrEmpty(); + if (completions.isEmpty()) + return; + editor->insertSuggestion( + std::make_unique(completions, editor->document())); + editor->addHoverHandler(&m_hoverHandler); + } +} + +void CopilotClient::cancelRunningRequest(TextEditor::TextEditorWidget *editor) +{ + auto it = m_runningRequests.find(editor); + if (it == m_runningRequests.end()) + return; + cancelRequest(it->id()); + m_runningRequests.erase(it); +} + +void CopilotClient::requestCheckStatus( + bool localChecksOnly, std::function callback) +{ + CheckStatusRequest request{localChecksOnly}; + request.setResponseCallback(callback); + + sendMessage(request); +} + +void CopilotClient::requestSignOut( + std::function callback) +{ + SignOutRequest request; + request.setResponseCallback(callback); + + sendMessage(request); +} + +void CopilotClient::requestSignInInitiate( + std::function callback) +{ + SignInInitiateRequest request; + request.setResponseCallback(callback); + + sendMessage(request); +} + +void CopilotClient::requestSignInConfirm( + const QString &userCode, + std::function callback) +{ + SignInConfirmRequest request(userCode); + request.setResponseCallback(callback); + + sendMessage(request); +} + +bool CopilotClient::canOpenProject(Project *project) +{ + return isEnabled(project); +} + +bool CopilotClient::isEnabled(Project *project) +{ + if (!project) + return CopilotSettings::instance().enableCopilot(); + + CopilotProjectSettings settings(project); + return settings.isEnabled(); +} + +} // namespace Copilot::Internal diff --git a/src/plugins/copilot/copilotclient.h b/src/plugins/copilot/copilotclient.h new file mode 100644 index 00000000000..47208236b52 --- /dev/null +++ b/src/plugins/copilot/copilotclient.h @@ -0,0 +1,64 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "copilothoverhandler.h" +#include "requests/checkstatus.h" +#include "requests/getcompletions.h" +#include "requests/signinconfirm.h" +#include "requests/signininitiate.h" +#include "requests/signout.h" + +#include + +#include + +#include +#include + +namespace Copilot::Internal { + +class CopilotClient : public LanguageClient::Client +{ +public: + CopilotClient(const Utils::FilePath &nodePath, const Utils::FilePath &distPath); + ~CopilotClient() override; + + void openDocument(TextEditor::TextDocument *document) override; + + void scheduleRequest(TextEditor::TextEditorWidget *editor); + void requestCompletions(TextEditor::TextEditorWidget *editor); + void handleCompletions(const GetCompletionRequest::Response &response, + TextEditor::TextEditorWidget *editor); + void cancelRunningRequest(TextEditor::TextEditorWidget *editor); + + void requestCheckStatus( + bool localChecksOnly, + std::function callback); + + void requestSignOut(std::function callback); + + void requestSignInInitiate( + std::function callback); + + void requestSignInConfirm( + const QString &userCode, + std::function callback); + + bool canOpenProject(ProjectExplorer::Project *project) override; + + bool isEnabled(ProjectExplorer::Project *project); + +private: + QMap m_runningRequests; + struct ScheduleData + { + int cursorPosition = -1; + QTimer *timer = nullptr; + }; + QMap m_scheduledRequests; + CopilotHoverHandler m_hoverHandler; +}; + +} // namespace Copilot::Internal diff --git a/src/plugins/copilot/copilotconstants.h b/src/plugins/copilot/copilotconstants.h new file mode 100644 index 00000000000..6ace5f2f6f9 --- /dev/null +++ b/src/plugins/copilot/copilotconstants.h @@ -0,0 +1,20 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +namespace Copilot::Constants { +const char COPILOT_PROJECT_SETTINGS_ID[] = "Copilot.Project.Settings"; +const char ENABLE_COPILOT[] = "Copilot.EnableCopilot"; +const char COPILOT_USE_GLOBAL_SETTINGS[] = "Copilot.UseGlobalSettings"; + +const char COPILOT_TOGGLE[] = "Copilot.Toggle"; +const char COPILOT_ENABLE[] = "Copilot.Enable"; +const char COPILOT_DISABLE[] = "Copilot.Disable"; +const char COPILOT_REQUEST_SUGGESTION[] = "Copilot.RequestSuggestion"; + +const char COPILOT_GENERAL_OPTIONS_ID[] = "Copilot.General"; +const char COPILOT_GENERAL_OPTIONS_CATEGORY[] = "ZY.Copilot"; +const char COPILOT_GENERAL_OPTIONS_DISPLAY_CATEGORY[] = "Copilot"; + +} // namespace Copilot::Constants diff --git a/src/plugins/copilot/copilothoverhandler.cpp b/src/plugins/copilot/copilothoverhandler.cpp new file mode 100644 index 00000000000..135cbd8390f --- /dev/null +++ b/src/plugins/copilot/copilothoverhandler.cpp @@ -0,0 +1,159 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "copilothoverhandler.h" + +#include "copilotclient.h" +#include "copilotsuggestion.h" +#include "copilottr.h" + +#include +#include +#include + +#include +#include + +#include +#include +#include + +using namespace TextEditor; +using namespace LanguageServerProtocol; +using namespace Utils; + +namespace Copilot::Internal { + +class CopilotCompletionToolTip : public QToolBar +{ +public: + CopilotCompletionToolTip(QList completions, + int currentCompletion, + TextEditorWidget *editor) + : m_numberLabel(new QLabel) + , m_completions(completions) + , m_currentCompletion(std::max(0, std::min(currentCompletion, completions.size() - 1))) + , m_editor(editor) + { + auto prev = addAction(Utils::Icons::PREV_TOOLBAR.icon(), + Tr::tr("Select Previous Copilot Suggestion")); + prev->setEnabled(m_completions.size() > 1); + addWidget(m_numberLabel); + auto next = addAction(Utils::Icons::NEXT_TOOLBAR.icon(), + Tr::tr("Select Next Copilot Suggestion")); + next->setEnabled(m_completions.size() > 1); + + auto apply = addAction(Tr::tr("Apply (%1)").arg(QKeySequence(Qt::Key_Tab).toString())); + auto applyWord = addAction( + Tr::tr("Apply Word (%1)").arg(QKeySequence(QKeySequence::MoveToNextWord).toString())); + + connect(prev, &QAction::triggered, this, &CopilotCompletionToolTip::selectPrevious); + connect(next, &QAction::triggered, this, &CopilotCompletionToolTip::selectNext); + connect(apply, &QAction::triggered, this, &CopilotCompletionToolTip::apply); + connect(applyWord, &QAction::triggered, this, &CopilotCompletionToolTip::applyWord); + + updateLabels(); + } + +private: + void updateLabels() + { + m_numberLabel->setText(Tr::tr("%1 of %2") + .arg(m_currentCompletion + 1) + .arg(m_completions.count())); + } + + void selectPrevious() + { + --m_currentCompletion; + if (m_currentCompletion < 0) + m_currentCompletion = m_completions.size() - 1; + setCurrentCompletion(); + } + + void selectNext() + { + ++m_currentCompletion; + if (m_currentCompletion >= m_completions.size()) + m_currentCompletion = 0; + setCurrentCompletion(); + } + + void setCurrentCompletion() + { + updateLabels(); + if (TextSuggestion *suggestion = m_editor->currentSuggestion()) + suggestion->reset(); + m_editor->insertSuggestion(std::make_unique(m_completions, + m_editor->document(), + m_currentCompletion)); + } + + void apply() + { + if (TextSuggestion *suggestion = m_editor->currentSuggestion()) { + if (!suggestion->apply()) + return; + } + ToolTip::hide(); + } + + void applyWord() + { + if (TextSuggestion *suggestion = m_editor->currentSuggestion()) { + if (!suggestion->applyWord(m_editor)) + return; + } + ToolTip::hide(); + } + + QLabel *m_numberLabel; + QList m_completions; + int m_currentCompletion = 0; + TextEditorWidget *m_editor; +}; + +void CopilotHoverHandler::identifyMatch(TextEditorWidget *editorWidget, + int pos, + ReportPriority report) +{ + auto reportNone = qScopeGuard([&] { report(Priority_None); }); + if (!editorWidget->suggestionVisible()) + return; + + QTextCursor cursor(editorWidget->document()); + cursor.setPosition(pos); + m_block = cursor.block(); + auto *suggestion = dynamic_cast(TextDocumentLayout::suggestion(m_block)); + + if (!suggestion) + return; + + const QList completions = suggestion->completions(); + if (completions.isEmpty()) + return; + + reportNone.dismiss(); + report(Priority_Suggestion); +} + +void CopilotHoverHandler::operateTooltip(TextEditorWidget *editorWidget, const QPoint &point) +{ + Q_UNUSED(point) + auto *suggestion = dynamic_cast(TextDocumentLayout::suggestion(m_block)); + + if (!suggestion) + return; + + auto tooltipWidget = new CopilotCompletionToolTip(suggestion->completions(), + suggestion->currentCompletion(), + editorWidget); + + const QRect cursorRect = editorWidget->cursorRect(editorWidget->textCursor()); + QPoint pos = editorWidget->viewport()->mapToGlobal(cursorRect.topLeft()) + - Utils::ToolTip::offsetFromPosition(); + pos.ry() -= tooltipWidget->sizeHint().height(); + ToolTip::show(pos, tooltipWidget, editorWidget); +} + +} // namespace Copilot::Internal diff --git a/src/plugins/copilot/copilothoverhandler.h b/src/plugins/copilot/copilothoverhandler.h new file mode 100644 index 00000000000..1c48e75d5b7 --- /dev/null +++ b/src/plugins/copilot/copilothoverhandler.h @@ -0,0 +1,32 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "requests/getcompletions.h" + +#include + +#include + +#pragma once + +namespace TextEditor { class TextSuggestion; } +namespace Copilot::Internal { + +class CopilotClient; + +class CopilotHoverHandler final : public TextEditor::BaseHoverHandler +{ +public: + CopilotHoverHandler() = default; + +protected: + void identifyMatch(TextEditor::TextEditorWidget *editorWidget, + int pos, + ReportPriority report) final; + void operateTooltip(TextEditor::TextEditorWidget *editorWidget, const QPoint &point) final; + +private: + QTextBlock m_block; +}; + +} // namespace Copilot::Internal diff --git a/src/plugins/copilot/copiloticons.h b/src/plugins/copilot/copiloticons.h new file mode 100644 index 00000000000..b2b54077d3c --- /dev/null +++ b/src/plugins/copilot/copiloticons.h @@ -0,0 +1,12 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace Copilot { +static const Utils::Icon COPILOT_ICON({{":/copilot/images/copilot.png", + Utils::Theme::IconsBaseColor}}); + +} diff --git a/src/plugins/copilot/copilotoptionspage.cpp b/src/plugins/copilot/copilotoptionspage.cpp new file mode 100644 index 00000000000..001db87834d --- /dev/null +++ b/src/plugins/copilot/copilotoptionspage.cpp @@ -0,0 +1,101 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "copilotoptionspage.h" + +#include "authwidget.h" +#include "copilotconstants.h" +#include "copilotsettings.h" +#include "copilottr.h" + +#include + +#include +#include + +using namespace Utils; +using namespace LanguageClient; + +namespace Copilot { + +class CopilotOptionsPageWidget : public Core::IOptionsPageWidget +{ +public: + CopilotOptionsPageWidget() + { + using namespace Layouting; + + auto authWidget = new AuthWidget(); + + QLabel *helpLabel = new QLabel(); + helpLabel->setTextFormat(Qt::MarkdownText); + helpLabel->setWordWrap(true); + helpLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse + | Qt::LinksAccessibleByKeyboard); + helpLabel->setOpenExternalLinks(true); + + // clang-format off + helpLabel->setText(Tr::tr(R"( +The Copilot plugin requires node.js and the Copilot neovim plugin. +If you install the neovim plugin as described in the +[README.md](https://github.com/github/copilot.vim), +the plugin will find the agent.js file automatically. + +Otherwise you need to specify the path to the +[agent.js](https://github.com/github/copilot.vim/tree/release/copilot/dist) +file from the Copilot neovim plugin. + )", "Markdown text for the copilot instruction label")); + + Column { + authWidget, br, + CopilotSettings::instance().enableCopilot, br, + CopilotSettings::instance().nodeJsPath, br, + CopilotSettings::instance().distPath, br, + CopilotSettings::instance().autoComplete, br, + helpLabel, br, + st + }.attachTo(this); + // clang-format on + + auto updateAuthWidget = [authWidget]() { + authWidget->updateClient( + FilePath::fromUserInput( + CopilotSettings::instance().nodeJsPath.volatileValue().toString()), + FilePath::fromUserInput( + CopilotSettings::instance().distPath.volatileValue().toString())); + }; + + connect(CopilotSettings::instance().nodeJsPath.pathChooser(), + &PathChooser::textChanged, + authWidget, + updateAuthWidget); + connect(CopilotSettings::instance().distPath.pathChooser(), + &PathChooser::textChanged, + authWidget, + updateAuthWidget); + updateAuthWidget(); + + setOnApply([] { + CopilotSettings::instance().apply(); + CopilotSettings::instance().writeSettings(Core::ICore::settings()); + }); + } +}; + +CopilotOptionsPage::CopilotOptionsPage() +{ + setId(Constants::COPILOT_GENERAL_OPTIONS_ID); + setDisplayName("Copilot"); + setCategory(Constants::COPILOT_GENERAL_OPTIONS_CATEGORY); + setDisplayCategory(Constants::COPILOT_GENERAL_OPTIONS_DISPLAY_CATEGORY); + setCategoryIconPath(":/copilot/images/settingscategory_copilot.png"); + setWidgetCreator([] { return new CopilotOptionsPageWidget; }); +} + +CopilotOptionsPage &CopilotOptionsPage::instance() +{ + static CopilotOptionsPage settingsPage; + return settingsPage; +} + +} // namespace Copilot diff --git a/src/plugins/copilot/copilotoptionspage.h b/src/plugins/copilot/copilotoptionspage.h new file mode 100644 index 00000000000..103e975b634 --- /dev/null +++ b/src/plugins/copilot/copilotoptionspage.h @@ -0,0 +1,18 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace Copilot { + +class CopilotOptionsPage : public Core::IOptionsPage +{ +public: + CopilotOptionsPage(); + + static CopilotOptionsPage &instance(); +}; + +} // Copilot diff --git a/src/plugins/copilot/copilotplugin.cpp b/src/plugins/copilot/copilotplugin.cpp new file mode 100644 index 00000000000..526a6061cbe --- /dev/null +++ b/src/plugins/copilot/copilotplugin.cpp @@ -0,0 +1,139 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "copilotplugin.h" + +#include "copilotclient.h" +#include "copilotconstants.h" +#include "copiloticons.h" +#include "copilotoptionspage.h" +#include "copilotprojectpanel.h" +#include "copilotsettings.h" +#include "copilottr.h" + +#include +#include +#include +#include + +#include + +#include + +#include + +#include +#include + +using namespace Utils; +using namespace Core; +using namespace ProjectExplorer; + +namespace Copilot { +namespace Internal { + +void CopilotPlugin::initialize() +{ + CopilotSettings::instance().readSettings(ICore::settings()); + + restartClient(); + + connect(&CopilotSettings::instance(), + &CopilotSettings::applied, + this, + &CopilotPlugin::restartClient); + + QAction *requestAction = new QAction(this); + requestAction->setText(Tr::tr("Request Copilot Suggestion")); + requestAction->setToolTip( + Tr::tr("Request Copilot suggestion at the current editor's cursor position.")); + + connect(requestAction, &QAction::triggered, this, [this] { + if (auto editor = TextEditor::TextEditorWidget::currentTextEditorWidget()) { + if (m_client->reachable()) + m_client->requestCompletions(editor); + } + }); + + ActionManager::registerAction(requestAction, Constants::COPILOT_REQUEST_SUGGESTION); + + QAction *disableAction = new QAction(this); + disableAction->setText(Tr::tr("Disable Copilot")); + disableAction->setToolTip(Tr::tr("Disable Copilot.")); + connect(disableAction, &QAction::triggered, this, [] { + CopilotSettings::instance().enableCopilot.setValue(true); + CopilotSettings::instance().apply(); + }); + ActionManager::registerAction(disableAction, Constants::COPILOT_DISABLE); + + QAction *enableAction = new QAction(this); + enableAction->setText(Tr::tr("Enable Copilot")); + enableAction->setToolTip(Tr::tr("Enable Copilot.")); + connect(enableAction, &QAction::triggered, this, [] { + CopilotSettings::instance().enableCopilot.setValue(false); + CopilotSettings::instance().apply(); + }); + ActionManager::registerAction(enableAction, Constants::COPILOT_ENABLE); + + QAction *toggleAction = new QAction(this); + toggleAction->setText(Tr::tr("Toggle Copilot")); + toggleAction->setCheckable(true); + toggleAction->setChecked(CopilotSettings::instance().enableCopilot.value()); + toggleAction->setIcon(COPILOT_ICON.icon()); + connect(toggleAction, &QAction::toggled, this, [](bool checked) { + CopilotSettings::instance().enableCopilot.setValue(checked); + CopilotSettings::instance().apply(); + }); + + ActionManager::registerAction(toggleAction, Constants::COPILOT_TOGGLE); + + auto updateActions = [toggleAction, requestAction] { + const bool enabled = CopilotSettings::instance().enableCopilot.value(); + toggleAction->setToolTip(enabled ? Tr::tr("Disable Copilot.") : Tr::tr("Enable Copilot.")); + toggleAction->setChecked(enabled); + requestAction->setEnabled(enabled); + }; + + connect(&CopilotSettings::instance().enableCopilot, + &BoolAspect::valueChanged, + this, + updateActions); + + updateActions(); + + auto toggleButton = new QToolButton; + toggleButton->setDefaultAction(toggleAction); + StatusBarManager::addStatusBarWidget(toggleButton, StatusBarManager::RightCorner); + + auto panelFactory = new ProjectPanelFactory; + panelFactory->setPriority(1000); + panelFactory->setDisplayName(Tr::tr("Copilot")); + panelFactory->setCreateWidgetFunction(&Internal::createCopilotProjectPanel); + ProjectPanelFactory::registerFactory(panelFactory); +} + +void CopilotPlugin::extensionsInitialized() +{ + (void)CopilotOptionsPage::instance(); +} + +void CopilotPlugin::restartClient() +{ + LanguageClient::LanguageClientManager::shutdownClient(m_client); + + if (!CopilotSettings::instance().nodeJsPath().isExecutableFile()) + return; + m_client = new CopilotClient(CopilotSettings::instance().nodeJsPath(), + CopilotSettings::instance().distPath()); +} + +ExtensionSystem::IPlugin::ShutdownFlag CopilotPlugin::aboutToShutdown() +{ + if (!m_client) + return SynchronousShutdown; + connect(m_client, &QObject::destroyed, this, &IPlugin::asynchronousShutdownFinished); + return AsynchronousShutdown; +} + +} // namespace Internal +} // namespace Copilot diff --git a/src/plugins/copilot/copilotplugin.h b/src/plugins/copilot/copilotplugin.h new file mode 100644 index 00000000000..8c1a176a732 --- /dev/null +++ b/src/plugins/copilot/copilotplugin.h @@ -0,0 +1,31 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "copilotclient.h" + +#include + +#include + +namespace Copilot { +namespace Internal { + +class CopilotPlugin : public ExtensionSystem::IPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Copilot.json") + +public: + void initialize() override; + void extensionsInitialized() override; + void restartClient(); + ShutdownFlag aboutToShutdown() override; + +private: + QPointer m_client; +}; + +} // namespace Internal +} // namespace Copilot diff --git a/src/plugins/copilot/copilotprojectpanel.cpp b/src/plugins/copilot/copilotprojectpanel.cpp new file mode 100644 index 00000000000..f1eb5d119e8 --- /dev/null +++ b/src/plugins/copilot/copilotprojectpanel.cpp @@ -0,0 +1,59 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "copilotprojectpanel.h" +#include "copilotconstants.h" +#include "copilotsettings.h" + +#include +#include + +#include + +#include + +using namespace ProjectExplorer; + +namespace Copilot::Internal { + +class CopilotProjectSettingsWidget : public ProjectExplorer::ProjectSettingsWidget +{ +public: + CopilotProjectSettingsWidget() + { + setGlobalSettingsId(Constants::COPILOT_GENERAL_OPTIONS_ID); + setUseGlobalSettingsCheckBoxVisible(true); + } +}; + +ProjectSettingsWidget *createCopilotProjectPanel(Project *project) +{ + using namespace Layouting; + using namespace ProjectExplorer; + + auto widget = new CopilotProjectSettingsWidget; + auto settings = new CopilotProjectSettings(project, widget); + + QObject::connect(widget, + &ProjectSettingsWidget::useGlobalSettingsChanged, + settings, + &CopilotProjectSettings::setUseGlobalSettings); + + widget->setUseGlobalSettings(settings->useGlobalSettings()); + widget->setEnabled(!settings->useGlobalSettings()); + + QObject::connect(widget, + &ProjectSettingsWidget::useGlobalSettingsChanged, + widget, + [widget](bool useGlobal) { widget->setEnabled(!useGlobal); }); + + // clang-format off + Column { + settings->enableCopilot, + }.attachTo(widget); + // clang-format on + + return widget; +} + +} // namespace Copilot::Internal diff --git a/src/plugins/copilot/copilotprojectpanel.h b/src/plugins/copilot/copilotprojectpanel.h new file mode 100644 index 00000000000..2f4be6d6f9c --- /dev/null +++ b/src/plugins/copilot/copilotprojectpanel.h @@ -0,0 +1,15 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +namespace ProjectExplorer { +class ProjectSettingsWidget; +class Project; +} // namespace ProjectExplorer + +namespace Copilot::Internal { + +ProjectExplorer::ProjectSettingsWidget *createCopilotProjectPanel(ProjectExplorer::Project *project); + +} // namespace Copilot::Internal diff --git a/src/plugins/copilot/copilotsettings.cpp b/src/plugins/copilot/copilotsettings.cpp new file mode 100644 index 00000000000..b8567f2feb4 --- /dev/null +++ b/src/plugins/copilot/copilotsettings.cpp @@ -0,0 +1,118 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "copilotsettings.h" +#include "copilotconstants.h" +#include "copilottr.h" + +#include + +#include +#include + +using namespace Utils; + +namespace Copilot { + +static void initEnableAspect(BoolAspect &enableCopilot) +{ + enableCopilot.setSettingsKey(Constants::ENABLE_COPILOT); + enableCopilot.setDisplayName(Tr::tr("Enable Copilot")); + enableCopilot.setLabelText(Tr::tr("Enable Copilot")); + enableCopilot.setToolTip(Tr::tr("Enables the Copilot integration.")); + enableCopilot.setDefaultValue(true); +} + +CopilotSettings &CopilotSettings::instance() +{ + static CopilotSettings settings; + return settings; +} + +CopilotSettings::CopilotSettings() +{ + setAutoApply(false); + + const FilePath nodeFromPath = FilePath("node").searchInPath(); + + const FilePaths searchDirs + = {FilePath::fromUserInput("~/.vim/pack/github/start/copilot.vim/copilot/dist/agent.js"), + FilePath::fromUserInput( + "~/.config/nvim/pack/github/start/copilot.vim/copilot/dist/agent.js"), + FilePath::fromUserInput( + "~/vimfiles/pack/github/start/copilot.vim/copilot/dist/agent.js"), + FilePath::fromUserInput( + "~/AppData/Local/nvim/pack/github/start/copilot.vim/copilot/dist/agent.js")}; + + const FilePath distFromVim = findOrDefault(searchDirs, &FilePath::exists); + + nodeJsPath.setExpectedKind(PathChooser::ExistingCommand); + nodeJsPath.setDefaultFilePath(nodeFromPath); + nodeJsPath.setSettingsKey("Copilot.NodeJsPath"); + nodeJsPath.setLabelText(Tr::tr("Node.js path:")); + nodeJsPath.setHistoryCompleter("Copilot.NodePath.History"); + nodeJsPath.setDisplayName(Tr::tr("Node.js Path")); + nodeJsPath.setToolTip( + Tr::tr("Select path to node.js executable. See https://nodejs.org/en/download/" + "for installation instructions.")); + + distPath.setExpectedKind(PathChooser::File); + distPath.setDefaultFilePath(distFromVim); + distPath.setSettingsKey("Copilot.DistPath"); + distPath.setLabelText(Tr::tr("Path to agent.js:")); + distPath.setHistoryCompleter("Copilot.DistPath.History"); + distPath.setDisplayName(Tr::tr("Agent.js path")); + distPath.setToolTip(Tr::tr( + "Select path to agent.js in Copilot Neovim plugin. See " + "https://github.com/github/copilot.vim#getting-started for installation instructions.")); + + autoComplete.setDisplayName(Tr::tr("Auto Complete")); + autoComplete.setSettingsKey("Copilot.Autocomplete"); + autoComplete.setLabelText(Tr::tr("Request completions automatically")); + autoComplete.setDefaultValue(true); + autoComplete.setToolTip(Tr::tr("Automatically request suggestions for the current text cursor " + "position after changes to the document.")); + + initEnableAspect(enableCopilot); +} + +CopilotProjectSettings::CopilotProjectSettings(ProjectExplorer::Project *project, QObject *parent) + : AspectContainer(parent) +{ + setAutoApply(true); + + useGlobalSettings.setSettingsKey(Constants::COPILOT_USE_GLOBAL_SETTINGS); + useGlobalSettings.setDefaultValue(true); + + initEnableAspect(enableCopilot); + + QVariantMap map = project->namedSettings(Constants::COPILOT_PROJECT_SETTINGS_ID).toMap(); + fromMap(map); + + connect(&enableCopilot, &BoolAspect::valueChanged, this, [this, project] { save(project); }); + connect(&useGlobalSettings, &BoolAspect::valueChanged, this, [this, project] { save(project); }); +} + +void CopilotProjectSettings::setUseGlobalSettings(bool useGlobal) +{ + useGlobalSettings.setValue(useGlobal); +} + +bool CopilotProjectSettings::isEnabled() const +{ + if (useGlobalSettings()) + return CopilotSettings::instance().enableCopilot(); + return enableCopilot(); +} + +void CopilotProjectSettings::save(ProjectExplorer::Project *project) +{ + QVariantMap map; + toMap(map); + project->setNamedSettings(Constants::COPILOT_PROJECT_SETTINGS_ID, map); + + // This triggers a restart of the Copilot language server. + CopilotSettings::instance().apply(); +} + +} // namespace Copilot diff --git a/src/plugins/copilot/copilotsettings.h b/src/plugins/copilot/copilotsettings.h new file mode 100644 index 00000000000..cec44c43fed --- /dev/null +++ b/src/plugins/copilot/copilotsettings.h @@ -0,0 +1,41 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace ProjectExplorer { +class Project; +} + +namespace Copilot { + +class CopilotSettings : public Utils::AspectContainer +{ +public: + CopilotSettings(); + + static CopilotSettings &instance(); + + Utils::FilePathAspect nodeJsPath{this}; + Utils::FilePathAspect distPath{this}; + Utils::BoolAspect autoComplete{this}; + Utils::BoolAspect enableCopilot{this}; +}; + +class CopilotProjectSettings : public Utils::AspectContainer +{ +public: + CopilotProjectSettings(ProjectExplorer::Project *project, QObject *parent = nullptr); + + void save(ProjectExplorer::Project *project); + void setUseGlobalSettings(bool useGlobalSettings); + + bool isEnabled() const; + + Utils::BoolAspect enableCopilot{this}; + Utils::BoolAspect useGlobalSettings{this}; +}; + +} // namespace Copilot diff --git a/src/plugins/copilot/copilotsuggestion.cpp b/src/plugins/copilot/copilotsuggestion.cpp new file mode 100644 index 00000000000..28da286f1f7 --- /dev/null +++ b/src/plugins/copilot/copilotsuggestion.cpp @@ -0,0 +1,79 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "copilotsuggestion.h" + +#include + +#include + +using namespace Utils; +using namespace TextEditor; +using namespace LanguageServerProtocol; + +namespace Copilot::Internal { + +CopilotSuggestion::CopilotSuggestion(const QList &completions, + QTextDocument *origin, + int currentCompletion) + : m_completions(completions) + , m_currentCompletion(currentCompletion) +{ + const Completion completion = completions.value(currentCompletion); + const Position start = completion.range().start(); + const Position end = completion.range().end(); + QString text = start.toTextCursor(origin).block().text(); + int length = text.length() - start.character(); + if (start.line() == end.line()) + length = end.character() - start.character(); + text.replace(start.character(), length, completion.text()); + document()->setPlainText(text); + m_start = completion.position().toTextCursor(origin); + m_start.setKeepPositionOnInsert(true); + setCurrentPosition(m_start.position()); +} + +bool CopilotSuggestion::apply() +{ + reset(); + const Completion completion = m_completions.value(m_currentCompletion); + QTextCursor cursor = completion.range().toSelection(m_start.document()); + cursor.insertText(completion.text()); + return true; +} + +bool CopilotSuggestion::applyWord(TextEditorWidget *widget) +{ + const Completion completion = m_completions.value(m_currentCompletion); + const QTextCursor cursor = completion.range().toSelection(m_start.document()); + QTextCursor currentCursor = widget->textCursor(); + const QString text = completion.text(); + const int startPos = currentCursor.positionInBlock() - cursor.positionInBlock() + + (cursor.selectionEnd() - cursor.selectionStart()); + const int next = endOfNextWord(text, startPos); + + if (next == -1) + return apply(); + + // TODO: Allow adding more than one line + QString subText = text.mid(startPos, next - startPos); + subText = subText.left(subText.indexOf('\n')); + if (subText.isEmpty()) + return false; + + currentCursor.insertText(subText); + return false; +} + +void CopilotSuggestion::reset() +{ + m_start.removeSelectedText(); +} + +int CopilotSuggestion::position() +{ + return m_start.position(); +} + +} // namespace Copilot::Internal + diff --git a/src/plugins/copilot/copilotsuggestion.h b/src/plugins/copilot/copilotsuggestion.h new file mode 100644 index 00000000000..719016236a2 --- /dev/null +++ b/src/plugins/copilot/copilotsuggestion.h @@ -0,0 +1,32 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#pragma once + +#include "requests/getcompletions.h" + +#include +#include + +namespace Copilot::Internal { + +class CopilotSuggestion final : public TextEditor::TextSuggestion +{ +public: + CopilotSuggestion(const QList &completions, + QTextDocument *origin, + int currentCompletion = 0); + + bool apply() final; + bool applyWord(TextEditor::TextEditorWidget *widget) final; + void reset() final; + int position() final; + + const QList &completions() const { return m_completions; } + int currentCompletion() const { return m_currentCompletion; } + +private: + QList m_completions; + int m_currentCompletion = 0; + QTextCursor m_start; +}; +} // namespace Copilot::Internal diff --git a/src/plugins/copilot/copilottr.h b/src/plugins/copilot/copilottr.h new file mode 100644 index 00000000000..42fef818db0 --- /dev/null +++ b/src/plugins/copilot/copilottr.h @@ -0,0 +1,15 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace Copilot { + +struct Tr +{ + Q_DECLARE_TR_FUNCTIONS(QtC::Copilot) +}; + +} // namespace Copilot diff --git a/src/plugins/copilot/images/copilot.png b/src/plugins/copilot/images/copilot.png new file mode 100644 index 00000000000..00d63f65602 Binary files /dev/null and b/src/plugins/copilot/images/copilot.png differ diff --git a/src/plugins/copilot/images/copilot@2x.png b/src/plugins/copilot/images/copilot@2x.png new file mode 100644 index 00000000000..add84de1719 Binary files /dev/null and b/src/plugins/copilot/images/copilot@2x.png differ diff --git a/src/plugins/copilot/images/settingscategory_copilot.png b/src/plugins/copilot/images/settingscategory_copilot.png new file mode 100644 index 00000000000..9a47fb24135 Binary files /dev/null and b/src/plugins/copilot/images/settingscategory_copilot.png differ diff --git a/src/plugins/copilot/images/settingscategory_copilot@2x.png b/src/plugins/copilot/images/settingscategory_copilot@2x.png new file mode 100644 index 00000000000..0437465aff3 Binary files /dev/null and b/src/plugins/copilot/images/settingscategory_copilot@2x.png differ diff --git a/src/plugins/copilot/requests/checkstatus.h b/src/plugins/copilot/requests/checkstatus.h new file mode 100644 index 00000000000..20e77eb1457 --- /dev/null +++ b/src/plugins/copilot/requests/checkstatus.h @@ -0,0 +1,54 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include +#include + +namespace Copilot { + +class CheckStatusParams : public LanguageServerProtocol::JsonObject +{ + static constexpr char16_t optionsKey[] = u"options"; + static constexpr char16_t localChecksOnlyKey[] = u"options"; + +public: + using JsonObject::JsonObject; + + CheckStatusParams(bool localChecksOnly = false) { setLocalChecksOnly(localChecksOnly); } + + void setLocalChecksOnly(bool localChecksOnly) + { + QJsonObject options; + options.insert(localChecksOnlyKey, localChecksOnly); + setOptions(options); + } + + void setOptions(QJsonObject options) { insert(optionsKey, options); } +}; + +class CheckStatusResponse : public LanguageServerProtocol::JsonObject +{ + static constexpr char16_t userKey[] = u"user"; + static constexpr char16_t statusKey[] = u"status"; + +public: + using JsonObject::JsonObject; + + QString status() const { return typedValue(statusKey); } + QString user() const { return typedValue(userKey); } +}; + +class CheckStatusRequest + : public LanguageServerProtocol::Request +{ +public: + explicit CheckStatusRequest(const CheckStatusParams ¶ms) + : Request(methodName, params) + {} + using Request::Request; + constexpr static const char methodName[] = "checkStatus"; +}; + +} // namespace Copilot diff --git a/src/plugins/copilot/requests/getcompletions.h b/src/plugins/copilot/requests/getcompletions.h new file mode 100644 index 00000000000..d602fea97d3 --- /dev/null +++ b/src/plugins/copilot/requests/getcompletions.h @@ -0,0 +1,119 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include +#include +#include + +namespace Copilot { + +class Completion : public LanguageServerProtocol::JsonObject +{ + static constexpr char16_t displayTextKey[] = u"displayText"; + static constexpr char16_t uuidKey[] = u"uuid"; + +public: + using JsonObject::JsonObject; + + QString displayText() const { return typedValue(displayTextKey); } + LanguageServerProtocol::Position position() const + { + return typedValue(LanguageServerProtocol::positionKey); + } + LanguageServerProtocol::Range range() const + { + return typedValue(LanguageServerProtocol::rangeKey); + } + QString text() const { return typedValue(LanguageServerProtocol::textKey); } + QString uuid() const { return typedValue(uuidKey); } + + bool isValid() const override + { + return contains(LanguageServerProtocol::textKey) + && contains(LanguageServerProtocol::rangeKey) + && contains(LanguageServerProtocol::positionKey); + } +}; + +class GetCompletionParams : public LanguageServerProtocol::JsonObject +{ +public: + static constexpr char16_t docKey[] = u"doc"; + + GetCompletionParams(const LanguageServerProtocol::TextDocumentIdentifier &document, + int version, + const LanguageServerProtocol::Position &position) + { + setTextDocument(document); + setVersion(version); + setPosition(position); + } + using JsonObject::JsonObject; + + // The text document. + LanguageServerProtocol::TextDocumentIdentifier textDocument() const + { + return typedValue(docKey); + } + void setTextDocument(const LanguageServerProtocol::TextDocumentIdentifier &id) + { + insert(docKey, id); + } + + // The position inside the text document. + LanguageServerProtocol::Position position() const + { + return LanguageServerProtocol::fromJsonValue( + value(docKey).toObject().value(LanguageServerProtocol::positionKey)); + } + void setPosition(const LanguageServerProtocol::Position &position) + { + QJsonObject result = value(docKey).toObject(); + result[LanguageServerProtocol::positionKey] = (QJsonObject) position; + insert(docKey, result); + } + + // The version + int version() const { return typedValue(LanguageServerProtocol::versionKey); } + void setVersion(int version) + { + QJsonObject result = value(docKey).toObject(); + result[LanguageServerProtocol::versionKey] = version; + insert(docKey, result); + } + + bool isValid() const override + { + return contains(docKey) + && value(docKey).toObject().contains(LanguageServerProtocol::positionKey) + && value(docKey).toObject().contains(LanguageServerProtocol::versionKey); + } +}; + +class GetCompletionResponse : public LanguageServerProtocol::JsonObject +{ + static constexpr char16_t completionKey[] = u"completions"; + +public: + using JsonObject::JsonObject; + + LanguageServerProtocol::LanguageClientArray completions() const + { + return clientArray(completionKey); + } +}; + +class GetCompletionRequest + : public LanguageServerProtocol::Request +{ +public: + explicit GetCompletionRequest(const GetCompletionParams ¶ms = {}) + : Request(methodName, params) + {} + using Request::Request; + constexpr static const char methodName[] = "getCompletionsCycling"; +}; + +} // namespace Copilot diff --git a/src/plugins/copilot/requests/signinconfirm.h b/src/plugins/copilot/requests/signinconfirm.h new file mode 100644 index 00000000000..64f4ce7d53d --- /dev/null +++ b/src/plugins/copilot/requests/signinconfirm.h @@ -0,0 +1,36 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "checkstatus.h" + +#include +#include + +namespace Copilot { + +class SignInConfirmParams : public LanguageServerProtocol::JsonObject +{ + static constexpr char16_t userCodeKey[] = u"userCode"; + +public: + using JsonObject::JsonObject; + + SignInConfirmParams(const QString &userCode) { setUserCode(userCode); } + + void setUserCode(const QString &userCode) { insert(userCodeKey, userCode); } +}; + +class SignInConfirmRequest + : public LanguageServerProtocol::Request +{ +public: + explicit SignInConfirmRequest(const QString &userCode) + : Request(methodName, {userCode}) + {} + using Request::Request; + constexpr static const char methodName[] = "signInConfirm"; +}; + +} // namespace Copilot diff --git a/src/plugins/copilot/requests/signininitiate.h b/src/plugins/copilot/requests/signininitiate.h new file mode 100644 index 00000000000..005205e6e01 --- /dev/null +++ b/src/plugins/copilot/requests/signininitiate.h @@ -0,0 +1,43 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include +#include + +namespace Copilot { + +using SignInInitiateParams = LanguageServerProtocol::JsonObject; + +class SignInInitiateResponse : public LanguageServerProtocol::JsonObject +{ + static constexpr char16_t verificationUriKey[] = u"verificationUri"; + static constexpr char16_t userCodeKey[] = u"userCode"; + +public: + using JsonObject::JsonObject; + +public: + QString verificationUri() const { return typedValue(verificationUriKey); } + QString userCode() const { return typedValue(userCodeKey); } +}; + +class SignInInitiateRequest : public LanguageServerProtocol::Request +{ +public: + explicit SignInInitiateRequest() + : Request(methodName, {}) + {} + using Request::Request; + constexpr static const char methodName[] = "signInInitiate"; +}; + +} // namespace Copilot diff --git a/src/plugins/copilot/requests/signout.h b/src/plugins/copilot/requests/signout.h new file mode 100644 index 00000000000..944c10d414b --- /dev/null +++ b/src/plugins/copilot/requests/signout.h @@ -0,0 +1,26 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "checkstatus.h" + +#include +#include + +namespace Copilot { + +using SignOutParams = LanguageServerProtocol::JsonObject; + +class SignOutRequest + : public LanguageServerProtocol::Request +{ +public: + explicit SignOutRequest() + : Request(methodName, {}) + {} + using Request::Request; + constexpr static const char methodName[] = "signOut"; +}; + +} // namespace Copilot diff --git a/src/plugins/coreplugin/CMakeLists.txt b/src/plugins/coreplugin/CMakeLists.txt index 17d1af01e39..b262d0d7683 100644 --- a/src/plugins/coreplugin/CMakeLists.txt +++ b/src/plugins/coreplugin/CMakeLists.txt @@ -7,156 +7,302 @@ add_qtc_plugin(Core DEPENDS Qt::PrintSupport Qt::Qml Qt::Sql Qt::Gui Qt::GuiPrivate PUBLIC_DEPENDS Aggregation ExtensionSystem Utils app_version SOURCES - actionmanager/actioncontainer.cpp actionmanager/actioncontainer.h actionmanager/actioncontainer_p.h - actionmanager/actionmanager.cpp actionmanager/actionmanager.h actionmanager/actionmanager_p.h - actionmanager/command.cpp actionmanager/command.h actionmanager/command_p.h - actionmanager/commandbutton.cpp actionmanager/commandbutton.h - actionmanager/commandmappings.cpp actionmanager/commandmappings.h - actionmanager/commandsfile.cpp actionmanager/commandsfile.h - actionsfilter.cpp actionsfilter.h - basefilewizard.cpp basefilewizard.h - basefilewizardfactory.cpp basefilewizardfactory.h ${CMAKE_CURRENT_BINARY_DIR}/core_logo_cmake.qrc + actionmanager/actioncontainer.cpp + actionmanager/actioncontainer.h + actionmanager/actioncontainer_p.h + actionmanager/actionmanager.cpp + actionmanager/actionmanager.h + actionmanager/actionmanager_p.h + actionmanager/command.cpp + actionmanager/command.h + actionmanager/command_p.h + actionmanager/commandbutton.cpp + actionmanager/commandbutton.h + actionmanager/commandmappings.cpp + actionmanager/commandmappings.h + actionmanager/commandsfile.cpp + actionmanager/commandsfile.h + actionsfilter.cpp + actionsfilter.h + basefilewizard.cpp + basefilewizard.h + basefilewizardfactory.cpp + basefilewizardfactory.h core.qrc core_global.h coreconstants.h - coreicons.cpp coreicons.h - corejsextensions.cpp corejsextensions.h - coreplugin.cpp coreplugin.h + coreicons.cpp + coreicons.h + corejsextensions.cpp + corejsextensions.h + coreplugin.cpp + coreplugin.h coreplugintr.h - designmode.cpp designmode.h - dialogs/addtovcsdialog.cpp dialogs/addtovcsdialog.h - dialogs/codecselector.cpp dialogs/codecselector.h - dialogs/externaltoolconfig.cpp dialogs/externaltoolconfig.h - dialogs/filepropertiesdialog.cpp dialogs/filepropertiesdialog.h - dialogs/ioptionspage.cpp dialogs/ioptionspage.h - dialogs/newdialog.cpp dialogs/newdialog.h - dialogs/newdialogwidget.cpp dialogs/newdialogwidget.h - dialogs/openwithdialog.cpp dialogs/openwithdialog.h - dialogs/promptoverwritedialog.cpp dialogs/promptoverwritedialog.h - dialogs/readonlyfilesdialog.cpp dialogs/readonlyfilesdialog.h - dialogs/restartdialog.cpp dialogs/restartdialog.h - dialogs/saveitemsdialog.cpp dialogs/saveitemsdialog.h - dialogs/settingsdialog.cpp dialogs/settingsdialog.h - dialogs/shortcutsettings.cpp dialogs/shortcutsettings.h - diffservice.cpp diffservice.h - documentmanager.cpp documentmanager.h - editmode.cpp editmode.h - editormanager/documentmodel.cpp editormanager/documentmodel.h editormanager/documentmodel_p.h - editormanager/editorarea.cpp editormanager/editorarea.h - editormanager/editormanager.cpp editormanager/editormanager.h editormanager/editormanager_p.h - editormanager/editorview.cpp editormanager/editorview.h - editormanager/editorwindow.cpp editormanager/editorwindow.h - editormanager/ieditor.cpp editormanager/ieditor.h - editormanager/ieditorfactory.cpp editormanager/ieditorfactory.h editormanager/ieditorfactory_p.h - editormanager/iexternaleditor.cpp editormanager/iexternaleditor.h - editormanager/openeditorsview.cpp editormanager/openeditorsview.h - editormanager/openeditorswindow.cpp editormanager/openeditorswindow.h - editormanager/systemeditor.cpp editormanager/systemeditor.h - editortoolbar.cpp editortoolbar.h - externaltool.cpp externaltool.h - externaltoolmanager.cpp externaltoolmanager.h - fancyactionbar.cpp fancyactionbar.h + designmode.cpp + designmode.h + dialogs/addtovcsdialog.cpp + dialogs/addtovcsdialog.h + dialogs/codecselector.cpp + dialogs/codecselector.h + dialogs/externaltoolconfig.cpp + dialogs/externaltoolconfig.h + dialogs/filepropertiesdialog.cpp + dialogs/filepropertiesdialog.h + dialogs/ioptionspage.cpp + dialogs/ioptionspage.h + dialogs/newdialog.cpp + dialogs/newdialog.h + dialogs/newdialogwidget.cpp + dialogs/newdialogwidget.h + dialogs/openwithdialog.cpp + dialogs/openwithdialog.h + dialogs/promptoverwritedialog.cpp + dialogs/promptoverwritedialog.h + dialogs/readonlyfilesdialog.cpp + dialogs/readonlyfilesdialog.h + dialogs/restartdialog.cpp + dialogs/restartdialog.h + dialogs/saveitemsdialog.cpp + dialogs/saveitemsdialog.h + dialogs/settingsdialog.cpp + dialogs/settingsdialog.h + dialogs/shortcutsettings.cpp + dialogs/shortcutsettings.h + diffservice.cpp + diffservice.h + documentmanager.cpp + documentmanager.h + editmode.cpp + editmode.h + editormanager/documentmodel.cpp + editormanager/documentmodel.h + editormanager/documentmodel_p.h + editormanager/editorarea.cpp + editormanager/editorarea.h + editormanager/editormanager.cpp + editormanager/editormanager.h + editormanager/editormanager_p.h + editormanager/editorview.cpp + editormanager/editorview.h + editormanager/editorwindow.cpp + editormanager/editorwindow.h + editormanager/ieditor.cpp + editormanager/ieditor.h + editormanager/ieditorfactory.cpp + editormanager/ieditorfactory.h + editormanager/ieditorfactory_p.h + editormanager/iexternaleditor.cpp + editormanager/iexternaleditor.h + editormanager/openeditorsview.cpp + editormanager/openeditorsview.h + editormanager/openeditorswindow.cpp + editormanager/openeditorswindow.h + editormanager/systemeditor.cpp + editormanager/systemeditor.h + editortoolbar.cpp + editortoolbar.h + externaltool.cpp + externaltool.h + externaltoolmanager.cpp + externaltoolmanager.h + fancyactionbar.cpp + fancyactionbar.h fancyactionbar.qrc - fancytabwidget.cpp fancytabwidget.h - featureprovider.cpp featureprovider.h - fileutils.cpp fileutils.h - find/basetextfind.cpp find/basetextfind.h - find/currentdocumentfind.cpp find/currentdocumentfind.h + fancytabwidget.cpp + fancytabwidget.h + featureprovider.cpp + featureprovider.h + fileutils.cpp + fileutils.h + find/basetextfind.cpp + find/basetextfind.h + find/currentdocumentfind.cpp + find/currentdocumentfind.h find/find.qrc - find/findplugin.cpp find/findplugin.h - find/findtoolbar.cpp find/findtoolbar.h - find/findtoolwindow.cpp find/findtoolwindow.h - find/highlightscrollbarcontroller.cpp find/highlightscrollbarcontroller.h - find/ifindfilter.cpp find/ifindfilter.h - find/ifindsupport.cpp find/ifindsupport.h - find/itemviewfind.cpp find/itemviewfind.h - find/optionspopup.cpp find/optionspopup.h - find/searchresultcolor.h - find/searchresultitem.h - find/searchresulttreeitemdelegate.cpp find/searchresulttreeitemdelegate.h + find/findplugin.cpp + find/findplugin.h + find/findtoolbar.cpp + find/findtoolbar.h + find/findtoolwindow.cpp + find/findtoolwindow.h + find/highlightscrollbarcontroller.cpp + find/highlightscrollbarcontroller.h + find/ifindfilter.cpp + find/ifindfilter.h + find/ifindsupport.cpp + find/ifindsupport.h + find/itemviewfind.cpp + find/itemviewfind.h + find/optionspopup.cpp + find/optionspopup.h + find/searchresulttreeitemdelegate.cpp + find/searchresulttreeitemdelegate.h find/searchresulttreeitemroles.h - find/searchresulttreeitems.cpp find/searchresulttreeitems.h - find/searchresulttreemodel.cpp find/searchresulttreemodel.h - find/searchresulttreeview.cpp find/searchresulttreeview.h - find/searchresultwidget.cpp find/searchresultwidget.h - find/searchresultwindow.cpp find/searchresultwindow.h + find/searchresulttreeitems.cpp + find/searchresulttreeitems.h + find/searchresulttreemodel.cpp + find/searchresulttreemodel.h + find/searchresulttreeview.cpp + find/searchresulttreeview.h + find/searchresultwidget.cpp + find/searchresultwidget.h + find/searchresultwindow.cpp + find/searchresultwindow.h find/textfindconstants.h - findplaceholder.cpp findplaceholder.h + findplaceholder.cpp + findplaceholder.h foldernavigationwidget.cpp foldernavigationwidget.h - generalsettings.cpp generalsettings.h - generatedfile.cpp generatedfile.h - helpitem.cpp helpitem.h - helpmanager.cpp helpmanager.h helpmanager_implementation.h - icontext.cpp icontext.h - icore.cpp icore.h - idocument.cpp idocument.h - idocumentfactory.cpp idocumentfactory.h + generalsettings.cpp + generalsettings.h + generatedfile.cpp + generatedfile.h + helpitem.cpp + helpitem.h + helpmanager.cpp + helpmanager.h + helpmanager_implementation.h + icontext.cpp + icontext.h + icore.cpp + icore.h + idocument.cpp + idocument.h + idocumentfactory.cpp + idocumentfactory.h ifilewizardextension.h - imode.cpp imode.h - inavigationwidgetfactory.cpp inavigationwidgetfactory.h - ioutputpane.cpp ioutputpane.h - iversioncontrol.cpp iversioncontrol.h - iwelcomepage.cpp iwelcomepage.h - iwizardfactory.cpp iwizardfactory.h - jsexpander.cpp jsexpander.h - locator/basefilefilter.cpp locator/basefilefilter.h - locator/commandlocator.cpp locator/commandlocator.h - locator/directoryfilter.cpp locator/directoryfilter.h - locator/executefilter.cpp locator/executefilter.h - locator/externaltoolsfilter.cpp locator/externaltoolsfilter.h - locator/filesystemfilter.cpp locator/filesystemfilter.h - locator/ilocatorfilter.cpp locator/ilocatorfilter.h - locator/javascriptfilter.cpp locator/javascriptfilter.h - locator/locator.cpp locator/locator.h + imode.cpp + imode.h + inavigationwidgetfactory.cpp + inavigationwidgetfactory.h + ioutputpane.cpp + ioutputpane.h + iversioncontrol.cpp + iversioncontrol.h + iwelcomepage.cpp + iwelcomepage.h + iwizardfactory.cpp + iwizardfactory.h + jsexpander.cpp + jsexpander.h + locator/commandlocator.cpp + locator/commandlocator.h + locator/directoryfilter.cpp + locator/directoryfilter.h + locator/executefilter.cpp + locator/executefilter.h + locator/externaltoolsfilter.cpp + locator/externaltoolsfilter.h + locator/filesystemfilter.cpp + locator/filesystemfilter.h + locator/ilocatorfilter.cpp + locator/ilocatorfilter.h + locator/javascriptfilter.cpp + locator/javascriptfilter.h + locator/locator.cpp + locator/locator.h locator/locatorconstants.h - locator/locatorfiltersfilter.cpp locator/locatorfiltersfilter.h - locator/locatormanager.cpp locator/locatormanager.h - locator/locatorsearchutils.cpp locator/locatorsearchutils.h - locator/locatorsettingspage.cpp locator/locatorsettingspage.h - locator/locatorwidget.cpp locator/locatorwidget.h - locator/opendocumentsfilter.cpp locator/opendocumentsfilter.h - locator/spotlightlocatorfilter.h locator/spotlightlocatorfilter.cpp - locator/urllocatorfilter.cpp locator/urllocatorfilter.h - loggingviewer.cpp loggingviewer.h - loggingmanager.cpp loggingmanager.h - mainwindow.cpp mainwindow.h - manhattanstyle.cpp manhattanstyle.h - messagebox.cpp messagebox.h - messagemanager.cpp messagemanager.h - messageoutputwindow.cpp messageoutputwindow.h - mimetypemagicdialog.cpp mimetypemagicdialog.h - mimetypesettings.cpp mimetypesettings.h - minisplitter.cpp minisplitter.h - modemanager.cpp modemanager.h - navigationsubwidget.cpp navigationsubwidget.h - navigationwidget.cpp navigationwidget.h - opendocumentstreeview.cpp opendocumentstreeview.h - outputpane.cpp outputpane.h - outputpanemanager.cpp outputpanemanager.h - outputwindow.cpp outputwindow.h - patchtool.cpp patchtool.h - plugindialog.cpp plugindialog.h - plugininstallwizard.cpp plugininstallwizard.h - progressmanager/futureprogress.cpp progressmanager/futureprogress.h - progressmanager/processprogress.cpp progressmanager/processprogress.h - progressmanager/progressbar.cpp progressmanager/progressbar.h - progressmanager/progressmanager.cpp progressmanager/progressmanager.h progressmanager/progressmanager_p.h - progressmanager/progressview.cpp progressmanager/progressview.h - progressmanager/taskprogress.cpp progressmanager/taskprogress.h - rightpane.cpp rightpane.h - settingsdatabase.cpp settingsdatabase.h - sidebar.cpp sidebar.h - sidebarwidget.cpp sidebarwidget.h - statusbarmanager.cpp statusbarmanager.h - systemsettings.cpp systemsettings.h - textdocument.cpp textdocument.h - themechooser.cpp themechooser.h - vcsmanager.cpp vcsmanager.h - versiondialog.cpp versiondialog.h - welcomepagehelper.cpp welcomepagehelper.h - windowsupport.cpp windowsupport.h + locator/locatorfiltersfilter.cpp + locator/locatorfiltersfilter.h + locator/locatormanager.cpp + locator/locatormanager.h + locator/locatorsettingspage.cpp + locator/locatorsettingspage.h + locator/locatorwidget.cpp + locator/locatorwidget.h + locator/opendocumentsfilter.cpp + locator/opendocumentsfilter.h + locator/spotlightlocatorfilter.cpp + locator/spotlightlocatorfilter.h + locator/urllocatorfilter.cpp + locator/urllocatorfilter.h + loggingmanager.cpp + loggingmanager.h + loggingviewer.cpp + loggingviewer.h + mainwindow.cpp + mainwindow.h + manhattanstyle.cpp + manhattanstyle.h + messagebox.cpp + messagebox.h + messagemanager.cpp + messagemanager.h + messageoutputwindow.cpp + messageoutputwindow.h + mimetypemagicdialog.cpp + mimetypemagicdialog.h + mimetypesettings.cpp + mimetypesettings.h + minisplitter.cpp + minisplitter.h + modemanager.cpp + modemanager.h + navigationsubwidget.cpp + navigationsubwidget.h + navigationwidget.cpp + navigationwidget.h + opendocumentstreeview.cpp + opendocumentstreeview.h + outputpane.cpp + outputpane.h + outputpanemanager.cpp + outputpanemanager.h + outputwindow.cpp + outputwindow.h + patchtool.cpp + patchtool.h + plugindialog.cpp + plugindialog.h + plugininstallwizard.cpp + plugininstallwizard.h + progressmanager/futureprogress.cpp + progressmanager/futureprogress.h + progressmanager/processprogress.cpp + progressmanager/processprogress.h + progressmanager/progressbar.cpp + progressmanager/progressbar.h + progressmanager/progressmanager.cpp + progressmanager/progressmanager.h + progressmanager/progressmanager_p.h + progressmanager/progressview.cpp + progressmanager/progressview.h + progressmanager/taskprogress.cpp + progressmanager/taskprogress.h + rightpane.cpp + rightpane.h + session.cpp + session.h + session_p.h + sessiondialog.cpp + sessiondialog.h + sessionmodel.cpp + sessionmodel.h + sessionview.cpp + sessionview.h + settingsdatabase.cpp + settingsdatabase.h + sidebar.cpp + sidebar.h + sidebarwidget.cpp + sidebarwidget.h + statusbarmanager.cpp + statusbarmanager.h + systemsettings.cpp + systemsettings.h + textdocument.cpp + textdocument.h + themechooser.cpp + themechooser.h + vcsmanager.cpp + vcsmanager.h + versiondialog.cpp + versiondialog.h + welcomepagehelper.cpp + welcomepagehelper.h + windowsupport.cpp + windowsupport.h EXPLICIT_MOC dialogs/filepropertiesdialog.h ) @@ -169,8 +315,10 @@ extend_qtc_plugin(Core CONDITION WITH_TESTS SOURCES locator/locator_test.cpp - locator/locatorfiltertest.cpp locator/locatorfiltertest.h - testdatadir.cpp testdatadir.h + locator/locatorfiltertest.cpp + locator/locatorfiltertest.h + testdatadir.cpp + testdatadir.h ) extend_qtc_plugin(Core diff --git a/src/plugins/coreplugin/Core.json.in b/src/plugins/coreplugin/Core.json.in index b3f36025bfb..606085c91d8 100644 --- a/src/plugins/coreplugin/Core.json.in +++ b/src/plugins/coreplugin/Core.json.in @@ -30,6 +30,14 @@ { \"Name\" : \"-presentationMode\", \"Description\" : \"Enable presentation mode with pop-ups for key combos\" + }, + { + \"Name\" : \"-lastsession\", + \"Description\" : \"Restore the last session\" + }, + { + \"Name\" : \"\", + \"Description\" : \"Restore a saved session\" } ], $$dependencyList diff --git a/src/plugins/coreplugin/actionmanager/actionmanager.cpp b/src/plugins/coreplugin/actionmanager/actionmanager.cpp index 3d0a200283c..54e58dc217a 100644 --- a/src/plugins/coreplugin/actionmanager/actionmanager.cpp +++ b/src/plugins/coreplugin/actionmanager/actionmanager.cpp @@ -34,8 +34,6 @@ namespace Core::Internal { class PresentationModeHandler : public QObject { - Q_OBJECT - public: void connectCommand(Command *command); @@ -520,5 +518,3 @@ void ActionManagerPrivate::saveSettings() saveSettings(j.value()); } } - -#include "actionmanager.moc" diff --git a/src/plugins/coreplugin/actionmanager/command.cpp b/src/plugins/coreplugin/actionmanager/command.cpp index 592ba08faac..23b398791f2 100644 --- a/src/plugins/coreplugin/actionmanager/command.cpp +++ b/src/plugins/coreplugin/actionmanager/command.cpp @@ -289,6 +289,15 @@ QAction *Command::action() const return d->m_action; } +QAction *Command::actionForContext(const Utils::Id &contextId) const +{ + auto it = d->m_contextActionMap.find(contextId); + if (it == d->m_contextActionMap.end()) + return nullptr; + + return *it; +} + QString Command::stringWithAppendedShortcut(const QString &str) const { return Utils::ProxyAction::stringWithAppendedShortcut(str, keySequence()); @@ -338,8 +347,10 @@ void Internal::CommandPrivate::setCurrentContext(const Context &context) m_context = context; QAction *currentAction = nullptr; - for (int i = 0; i < m_context.size(); ++i) { - if (QAction *a = m_contextActionMap.value(m_context.at(i), nullptr)) { + for (const Id &id : std::as_const(m_context)) { + if (id == Constants::C_GLOBAL_CUTOFF) + break; + if (QAction *a = m_contextActionMap.value(id, nullptr)) { currentAction = a; break; } diff --git a/src/plugins/coreplugin/actionmanager/command.h b/src/plugins/coreplugin/actionmanager/command.h index b900a50f55a..73dd92a5eb8 100644 --- a/src/plugins/coreplugin/actionmanager/command.h +++ b/src/plugins/coreplugin/actionmanager/command.h @@ -57,6 +57,8 @@ public: Utils::Id id() const; QAction *action() const; + QAction *actionForContext(const Utils::Id &contextId) const; + Context context() const; void setAttribute(CommandAttribute attr); diff --git a/src/plugins/coreplugin/actionmanager/commandbutton.cpp b/src/plugins/coreplugin/actionmanager/commandbutton.cpp index ed724e42d02..a8dab391368 100644 --- a/src/plugins/coreplugin/actionmanager/commandbutton.cpp +++ b/src/plugins/coreplugin/actionmanager/commandbutton.cpp @@ -92,7 +92,8 @@ QString CommandAction::toolTipBase() const } /*! - Sets the base tool tip that is extended with the command's shortcut. + Sets the base tool tip that is extended with the command's shortcut to + \a toolTipBase. \sa toolTipBase() */ @@ -155,7 +156,8 @@ QString CommandButton::toolTipBase() const } /*! - Sets the base tool tip that is extended with the command's shortcut. + Sets the base tool tip that is extended with the command's shortcut to + \a toolTipBase. \sa toolTipBase() */ diff --git a/src/plugins/coreplugin/actionmanager/commandmappings.h b/src/plugins/coreplugin/actionmanager/commandmappings.h index 67c864a0bf9..a6725265500 100644 --- a/src/plugins/coreplugin/actionmanager/commandmappings.h +++ b/src/plugins/coreplugin/actionmanager/commandmappings.h @@ -12,8 +12,6 @@ class QTreeWidget; class QTreeWidgetItem; QT_END_NAMESPACE -namespace Utils { class FancyLineEdit; } - namespace Core { namespace Internal { class CommandMappingsPrivate; } diff --git a/src/plugins/coreplugin/actionsfilter.cpp b/src/plugins/coreplugin/actionsfilter.cpp index 927e7da6331..01772ac2520 100644 --- a/src/plugins/coreplugin/actionsfilter.cpp +++ b/src/plugins/coreplugin/actionsfilter.cpp @@ -10,9 +10,11 @@ #include "icore.h" #include "locator/locatormanager.h" +#include + #include +#include #include -#include #include #include @@ -21,8 +23,11 @@ #include #include #include +#include #include +using namespace Utils; + static const char lastTriggeredC[] = "LastTriggeredActions"; QT_BEGIN_NAMESPACE @@ -34,6 +39,13 @@ QT_END_NAMESPACE namespace Core::Internal { +static const QList menuBarActions() +{ + QMenuBar *menuBar = Core::ActionManager::actionContainer(Constants::MENU_BAR)->menuBar(); + QTC_ASSERT(menuBar, return {}); + return menuBar->actions(); +} + ActionsFilter::ActionsFilter() { setId("Actions from the menu"); @@ -50,29 +62,21 @@ ActionsFilter::ActionsFilter() }); } -static const QList menuBarActions() -{ - QMenuBar *menuBar = Core::ActionManager::actionContainer(Constants::MENU_BAR)->menuBar(); - QTC_ASSERT(menuBar, return {}); - return menuBar->actions(); -} - -QList ActionsFilter::matchesFor(QFutureInterface &future, - const QString &entry) +static void matches(QPromise &promise, const LocatorStorage &storage, + const LocatorFilterEntries &entries) { using Highlight = LocatorFilterEntry::HighlightInfo; - if (entry.simplified().isEmpty()) - return m_entries; - - const QRegularExpression regExp = createRegExp(entry, Qt::CaseInsensitive, true); - + using MatchLevel = ILocatorFilter::MatchLevel; + const QString input = storage.input(); + const QRegularExpression regExp = ILocatorFilter::createRegExp(input, Qt::CaseInsensitive, + true); using FilterResult = std::pair; const auto filter = [&](const LocatorFilterEntry &filterEntry) -> std::optional { - if (future.isCanceled()) + if (promise.isCanceled()) return {}; Highlight highlight; - const auto withHighlight = [&](LocatorFilterEntry result) { + const auto withHighlight = [&highlight](LocatorFilterEntry result) { result.highlightInfo = highlight; return result; }; @@ -89,17 +93,18 @@ QList ActionsFilter::matchesFor(QFutureInterface ActionsFilter::matchesFor(QFutureInterface> filtered; - const QList> filterResults = Utils::map(std::as_const(m_entries), filter) - .results(); + QMap filtered; + const QList> filterResults + = QtConcurrent::blockingMapped(entries, filter); + if (promise.isCanceled()) + return; for (const std::optional &filterResult : filterResults) { + if (promise.isCanceled()) + return; if (filterResult) filtered[filterResult->first] << filterResult->second; } - - QList result; - for (const QList &sublist : std::as_const(filtered)) - result << sublist; - return result; + storage.reportOutput(std::accumulate(std::begin(filtered), std::end(filtered), + LocatorFilterEntries())); } -void ActionsFilter::accept(const LocatorFilterEntry &selection, QString *newText, - int *selectionStart, int *selectionLength) const +LocatorMatcherTasks ActionsFilter::matchers() +{ + using namespace Tasking; + + TreeStorage storage; + + const auto onSetup = [this, storage](Async &async) { + m_entries.clear(); + m_indexes.clear(); + QList processedMenus; + collectEntriesForLastTriggered(); + for (QAction* action : menuBarActions()) + collectEntriesForAction(action, {}, processedMenus); + collectEntriesForCommands(); + if (storage->input().simplified().isEmpty()) { + storage->reportOutput(m_entries); + return TaskAction::StopWithDone; + } + async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer()); + async.setConcurrentCallData(matches, *storage, m_entries); + return TaskAction::Continue; + }; + + return {{AsyncTask(onSetup), storage}}; +} + +LocatorFilterEntry::Acceptor ActionsFilter::acceptor(const ActionFilterEntryData &data) const { static const int maxHistorySize = 30; - Q_UNUSED(newText) - Q_UNUSED(selectionStart) - Q_UNUSED(selectionLength) - auto data = selection.internalData.value(); - if (data.action) { + return [this, data] { + if (!data.action) + return AcceptResult(); m_lastTriggered.removeAll(data); m_lastTriggered.prepend(data); QMetaObject::invokeMethod(data.action, [action = data.action] { @@ -185,7 +214,8 @@ void ActionsFilter::accept(const LocatorFilterEntry &selection, QString *newText }, Qt::QueuedConnection); if (m_lastTriggered.size() > maxHistorySize) m_lastTriggered.resize(maxHistorySize); - } + return AcceptResult(); + }; } static QString actionText(QAction *action) @@ -214,8 +244,10 @@ void ActionsFilter::collectEntriesForAction(QAction *action, collectEntriesForAction(menuAction, menuPath, processedMenus); } } else if (!text.isEmpty()) { - const ActionFilterEntryData data{action, {}}; - LocatorFilterEntry filterEntry(this, text, QVariant::fromValue(data), action->icon()); + LocatorFilterEntry filterEntry; + filterEntry.displayName = text; + filterEntry.acceptor = acceptor({action, {}}); + filterEntry.displayIcon = action->icon(); filterEntry.extraInfo = path.join(" > "); updateEntry(action, filterEntry); } @@ -241,8 +273,10 @@ void ActionsFilter::collectEntriesForCommands() const QString identifier = command->id().toString(); const QStringList path = identifier.split(QLatin1Char('.')); - const ActionFilterEntryData data{action, command->id()}; - LocatorFilterEntry filterEntry(this, text, QVariant::fromValue(data), action->icon()); + LocatorFilterEntry filterEntry; + filterEntry.displayName = text; + filterEntry.acceptor = acceptor({action, command->id()}); + filterEntry.displayIcon = action->icon(); filterEntry.displayExtra = command->keySequence().toString(QKeySequence::NativeText); if (path.size() >= 2) filterEntry.extraInfo = path.mid(0, path.size() - 1).join(" > "); @@ -259,15 +293,17 @@ void ActionsFilter::collectEntriesForLastTriggered() } if (!data.action || !m_enabledActions.contains(data.action)) continue; - const QString text = actionText(data.action); - LocatorFilterEntry filterEntry(this, text, QVariant::fromValue(data), data.action->icon()); + LocatorFilterEntry filterEntry; + filterEntry.displayName = actionText(data.action); + filterEntry.acceptor = acceptor(data); + filterEntry.displayIcon = data.action->icon(); updateEntry(data.action, filterEntry); } } void ActionsFilter::updateEntry(const QPointer action, const LocatorFilterEntry &entry) { - auto index = m_indexes.value(action, -1); + const int index = m_indexes.value(action, -1); if (index < 0) { m_indexes[action] = m_entries.size(); m_entries << entry; @@ -311,18 +347,6 @@ void ActionsFilter::updateEnabledActionCache() } } -void Core::Internal::ActionsFilter::prepareSearch(const QString &entry) -{ - Q_UNUSED(entry) - m_entries.clear(); - m_indexes.clear(); - QList processedMenus; - collectEntriesForLastTriggered(); - for (QAction* action : menuBarActions()) - collectEntriesForAction(action, QStringList(), processedMenus); - collectEntriesForCommands(); -} - void ActionsFilter::saveState(QJsonObject &object) const { QJsonArray commands; @@ -336,9 +360,10 @@ void ActionsFilter::saveState(QJsonObject &object) const void ActionsFilter::restoreState(const QJsonObject &object) { m_lastTriggered.clear(); - for (const QJsonValue &command : object.value(lastTriggeredC).toArray()) { + const QJsonArray commands = object.value(lastTriggeredC).toArray(); + for (const QJsonValue &command : commands) { if (command.isString()) - m_lastTriggered.append({nullptr, Utils::Id::fromString(command.toString())}); + m_lastTriggered.append({nullptr, Id::fromString(command.toString())}); } } diff --git a/src/plugins/coreplugin/actionsfilter.h b/src/plugins/coreplugin/actionsfilter.h index 50a993ad013..692dea95d26 100644 --- a/src/plugins/coreplugin/actionsfilter.h +++ b/src/plugins/coreplugin/actionsfilter.h @@ -14,8 +14,7 @@ class QAction; class QMenu; QT_END_NAMESPACE -namespace Core { -namespace Internal { +namespace Core::Internal { class ActionFilterEntryData { @@ -30,19 +29,14 @@ public: class ActionsFilter : public ILocatorFilter { - Q_OBJECT public: ActionsFilter(); - QList matchesFor(QFutureInterface &future, - const QString &entry) override; - void accept(const LocatorFilterEntry &selection, QString *newText, - int *selectionStart, int *selectionLength) const override; - void prepareSearch(const QString &entry) override; - private: + LocatorMatcherTasks matchers() final; void saveState(QJsonObject &object) const override; void restoreState(const QJsonObject &object) override; + LocatorFilterEntry::Acceptor acceptor(const ActionFilterEntryData &data) const; void collectEntriesForAction(QAction *action, const QStringList &path, QList &processedMenus); @@ -51,11 +45,10 @@ private: void updateEntry(const QPointer action, const LocatorFilterEntry &entry); void updateEnabledActionCache(); - QList m_entries; + LocatorFilterEntries m_entries; QMap, int> m_indexes; QSet> m_enabledActions; mutable QList m_lastTriggered; }; -} // namespace Internal -} // namespace Core +} // namespace Core::Internal diff --git a/src/plugins/coreplugin/coreconstants.h b/src/plugins/coreplugin/coreconstants.h index 0b4831e51b6..513d02eb6ee 100644 --- a/src/plugins/coreplugin/coreconstants.h +++ b/src/plugins/coreplugin/coreconstants.h @@ -46,6 +46,11 @@ const char C_EDITORMANAGER[] = "Core.EditorManager"; const char C_NAVIGATION_PANE[] = "Core.NavigationPane"; const char C_PROBLEM_PANE[] = "Core.ProblemPane"; const char C_GENERAL_OUTPUT_PANE[] = "Core.GeneralOutputPane"; +// Special context that leads to all "more specific" contexts to be ignored. +// If you use Context(mycontextId, C_GLOBAL_CUTOFF) for a widget that has focus, +// mycontextId will be enabled but the contexts for all parent widgets, the manually added +// "additional" contexts, and the global context will be turned off. +const char C_GLOBAL_CUTOFF[] = "Global Cutoff"; // Default editor kind const char K_DEFAULT_TEXT_EDITOR_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("QtC::Core", "Plain Text Editor"); diff --git a/src/plugins/coreplugin/coreplugin.cpp b/src/plugins/coreplugin/coreplugin.cpp index 043bdba74d1..2d825dd3ae5 100644 --- a/src/plugins/coreplugin/coreplugin.cpp +++ b/src/plugins/coreplugin/coreplugin.cpp @@ -6,12 +6,12 @@ #include "designmode.h" #include "editmode.h" #include "foldernavigationwidget.h" -#include "helpmanager.h" #include "icore.h" #include "idocument.h" #include "iwizardfactory.h" #include "mainwindow.h" #include "modemanager.h" +#include "session.h" #include "themechooser.h" #include @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -71,7 +72,7 @@ void CorePlugin::setupSystemEnvironment() CorePlugin::CorePlugin() { qRegisterMetaType(); - qRegisterMetaType(); + qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); @@ -148,6 +149,7 @@ bool CorePlugin::initialize(const QStringList &arguments, QString *errorMessage) Theme::setInitialPalette(theme); // Initialize palette before setting it setCreatorTheme(theme); InfoBar::initialize(ICore::settings()); + CheckableMessageBox::initialize(ICore::settings()); new ActionManager(this); ActionManager::setPresentationModeEnabled(args.presentationMode); m_mainWindow = new MainWindow; @@ -158,8 +160,8 @@ bool CorePlugin::initialize(const QStringList &arguments, QString *errorMessage) m_mainWindow->init(); m_editMode = new EditMode; ModeManager::activateMode(m_editMode->id()); - m_folderNavigationWidgetFactory = new FolderNavigationWidgetFactory; + m_sessionManager.reset(new SessionManager); IWizardFactory::initialize(); @@ -471,8 +473,7 @@ QString CorePlugin::msgCrashpadInformation() ExtensionSystem::IPlugin::ShutdownFlag CorePlugin::aboutToShutdown() { Find::aboutToShutdown(); - ExtensionSystem::IPlugin::ShutdownFlag shutdownFlag = m_locator->aboutToShutdown( - [this] { emit asynchronousShutdownFinished(); }); + m_locator->aboutToShutdown(); m_mainWindow->aboutToShutdown(); - return shutdownFlag; + return SynchronousShutdown; } diff --git a/src/plugins/coreplugin/coreplugin.h b/src/plugins/coreplugin/coreplugin.h index 7153c5415b8..fa73f951c13 100644 --- a/src/plugins/coreplugin/coreplugin.h +++ b/src/plugins/coreplugin/coreplugin.h @@ -9,6 +9,8 @@ #include #include +#include + QT_BEGIN_NAMESPACE class QMenu; QT_END_NAMESPACE @@ -20,6 +22,7 @@ class PathChooser; namespace Core { class FolderNavigationWidgetFactory; +class SessionManager; namespace Internal { @@ -52,7 +55,7 @@ public: static QString msgCrashpadInformation(); public slots: - void fileOpenRequest(const QString&); + void fileOpenRequest(const QString &); #if defined(WITH_TESTS) private slots: @@ -74,6 +77,7 @@ private: MainWindow *m_mainWindow = nullptr; EditMode *m_editMode = nullptr; Locator *m_locator = nullptr; + std::unique_ptr m_sessionManager; FolderNavigationWidgetFactory *m_folderNavigationWidgetFactory = nullptr; Utils::Environment m_startupSystemEnvironment; Utils::EnvironmentItems m_environmentChanges; diff --git a/src/plugins/coreplugin/coreplugin.qbs b/src/plugins/coreplugin/coreplugin.qbs index 36edf7f5acf..096076877f3 100644 --- a/src/plugins/coreplugin/coreplugin.qbs +++ b/src/plugins/coreplugin/coreplugin.qbs @@ -149,6 +149,15 @@ Project { "plugininstallwizard.h", "rightpane.cpp", "rightpane.h", + "session.cpp", + "session.h", + "session_p.h", + "sessiondialog.cpp", + "sessiondialog.h", + "sessionmodel.cpp", + "sessionmodel.h", + "sessionview.cpp", + "sessionview.h", "settingsdatabase.cpp", "settingsdatabase.h", "sidebar.cpp", @@ -269,9 +278,7 @@ Project { ] } - Group { - name: "Tests" - condition: qtc.testsEnabled + QtcTestFiles { files: [ "testdatadir.cpp", "testdatadir.h", @@ -308,8 +315,6 @@ Project { "itemviewfind.h", "optionspopup.cpp", "optionspopup.h", - "searchresultcolor.h", - "searchresultitem.h", "searchresulttreeitemdelegate.cpp", "searchresulttreeitemdelegate.h", "searchresulttreeitemroles.h", @@ -331,8 +336,6 @@ Project { name: "Locator" prefix: "locator/" files: [ - "basefilefilter.cpp", - "basefilefilter.h", "commandlocator.cpp", "commandlocator.h", "directoryfilter.cpp", @@ -354,8 +357,6 @@ Project { "locatormanager.h", "locator.cpp", "locator.h", - "locatorsearchutils.cpp", - "locatorsearchutils.h", "locatorsettingspage.cpp", "locatorsettingspage.h", "locatorwidget.cpp", diff --git a/src/plugins/coreplugin/dialogs/addtovcsdialog.cpp b/src/plugins/coreplugin/dialogs/addtovcsdialog.cpp index 7750cfcc17d..09a4bf45921 100644 --- a/src/plugins/coreplugin/dialogs/addtovcsdialog.cpp +++ b/src/plugins/coreplugin/dialogs/addtovcsdialog.cpp @@ -22,7 +22,7 @@ AddToVcsDialog::AddToVcsDialog(QWidget *parent, const QString &vcsDisplayName) : QDialog(parent) { - using namespace Utils::Layouting; + using namespace Layouting; resize(363, 375); setMinimumSize({200, 200}); @@ -33,7 +33,7 @@ AddToVcsDialog::AddToVcsDialog(QWidget *parent, filesListWidget->setSelectionMode(QAbstractItemView::NoSelection); filesListWidget->setSelectionBehavior(QAbstractItemView::SelectRows); - QWidget *scrollAreaWidgetContents = Column{filesListWidget}.emerge(WithoutMargins); + QWidget *scrollAreaWidgetContents = Column{filesListWidget, noMargin}.emerge(); scrollAreaWidgetContents->setGeometry({0, 0, 341, 300}); auto scrollArea = new QScrollArea; diff --git a/src/plugins/coreplugin/dialogs/externaltoolconfig.cpp b/src/plugins/coreplugin/dialogs/externaltoolconfig.cpp index 270ed9f681d..59237aad79a 100644 --- a/src/plugins/coreplugin/dialogs/externaltoolconfig.cpp +++ b/src/plugins/coreplugin/dialogs/externaltoolconfig.cpp @@ -18,8 +18,8 @@ #include #include #include +#include #include -#include #include #include @@ -564,11 +564,11 @@ ExternalToolConfig::ExternalToolConfig() Tr::tr("Environment:"), m_environmentLabel, environmentButton, br, empty, m_modifiesDocumentCheckbox, br, inputLabel, m_inputText - }.attachTo(m_infoWidget, WithMargins); + }.attachTo(m_infoWidget); Column { - m_infoWidget - }.attachTo(scrollAreaWidgetContents, WithoutMargins); + m_infoWidget, noMargin + }.attachTo(scrollAreaWidgetContents); Row { Column { diff --git a/src/plugins/coreplugin/dialogs/ioptionspage.cpp b/src/plugins/coreplugin/dialogs/ioptionspage.cpp index 451859c5274..4def6095a11 100644 --- a/src/plugins/coreplugin/dialogs/ioptionspage.cpp +++ b/src/plugins/coreplugin/dialogs/ioptionspage.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -135,9 +136,6 @@ QWidget *IOptionsPage::widget() if (!m_widget) { if (m_widgetCreator) { m_widget = m_widgetCreator(); - } else if (m_layouter) { - m_widget = new QWidget; - m_layouter(m_widget); } else { QTC_CHECK(false); } @@ -156,9 +154,10 @@ QWidget *IOptionsPage::widget() void IOptionsPage::apply() { - if (auto widget = qobject_cast(m_widget)) { + if (auto widget = qobject_cast(m_widget)) widget->apply(); - } else if (m_settings) { + + if (m_settings) { if (m_settings->isDirty()) { m_settings->apply(); m_settings->writeSettings(ICore::settings()); @@ -179,7 +178,8 @@ void IOptionsPage::finish() { if (auto widget = qobject_cast(m_widget)) widget->finish(); - else if (m_settings) + + if (m_settings) m_settings->finish(); delete m_widget; @@ -199,9 +199,13 @@ void IOptionsPage::setSettings(AspectContainer *settings) m_settings = settings; } -void IOptionsPage::setLayouter(const std::function &layouter) +void IOptionsPage::setLayouter(const std::function &layouter) { - m_layouter = layouter; + m_widgetCreator = [layouter] { + auto widget = new IOptionsPageWidget; + layouter().attachTo(widget); + return widget; + }; } /*! @@ -237,11 +241,10 @@ void IOptionsPage::setLayouter(const std::function &layouter) static QList g_optionsPages; /*! - Constructs an options page with the given \a parent and registers it + Constructs an options page and registers it at the global options page pool if \a registerGlobally is \c true. */ -IOptionsPage::IOptionsPage(QObject *parent, bool registerGlobally) - : QObject(parent) +IOptionsPage::IOptionsPage(bool registerGlobally) { if (registerGlobally) g_optionsPages.append(this); @@ -278,8 +281,7 @@ bool IOptionsPage::matches(const QRegularExpression ®exp) const static QList g_optionsPagesProviders; -IOptionsPageProvider::IOptionsPageProvider(QObject *parent) - : QObject(parent) +IOptionsPageProvider::IOptionsPageProvider() { g_optionsPagesProviders.append(this); } @@ -299,4 +301,17 @@ QIcon IOptionsPageProvider::categoryIcon() const return m_categoryIcon.icon(); } +// PagedSettings + +PagedSettings::PagedSettings() +{ + setSettings(this); + setAutoApply(false); +} + +void PagedSettings::readSettings() +{ + return AspectContainer::readSettings(Core::ICore::settings()); +} + } // Core diff --git a/src/plugins/coreplugin/dialogs/ioptionspage.h b/src/plugins/coreplugin/dialogs/ioptionspage.h index 300ecda4a08..f4801b7c734 100644 --- a/src/plugins/coreplugin/dialogs/ioptionspage.h +++ b/src/plugins/coreplugin/dialogs/ioptionspage.h @@ -5,6 +5,7 @@ #include +#include #include #include @@ -15,6 +16,8 @@ #include +namespace Layouting { class LayoutItem; }; + namespace Utils { class AspectContainer; }; namespace Core { @@ -22,18 +25,28 @@ namespace Core { class CORE_EXPORT IOptionsPageWidget : public QWidget { Q_OBJECT + public: - virtual void apply() = 0; - virtual void finish() {} + void setOnApply(const std::function &func) { m_onApply = func; } + void setOnFinish(const std::function &func) { m_onFinish = func; } + +protected: + friend class IOptionsPage; + virtual void apply() { if (m_onApply) m_onApply(); } + virtual void finish() { if (m_onFinish) m_onFinish(); } + +private: + std::function m_onApply; + std::function m_onFinish; }; -class CORE_EXPORT IOptionsPage : public QObject +class CORE_EXPORT IOptionsPage { - Q_OBJECT + Q_DISABLE_COPY_MOVE(IOptionsPage) public: - IOptionsPage(QObject *parent = nullptr, bool registerGlobally = true); - ~IOptionsPage() override; + explicit IOptionsPage(bool registerGlobally = true); + virtual ~IOptionsPage(); static const QList allOptionsPages(); @@ -61,7 +74,7 @@ protected: void setCategoryIcon(const Utils::Icon &categoryIcon) { m_categoryIcon = categoryIcon; } void setCategoryIconPath(const Utils::FilePath &categoryIconPath); void setSettings(Utils::AspectContainer *settings); - void setLayouter(const std::function &layouter); + void setLayouter(const std::function &layouter); // Used in FontSettingsPage. FIXME? QPointer m_widget; // Used in conjunction with m_widgetCreator @@ -78,7 +91,6 @@ private: mutable QStringList m_keywords; Utils::AspectContainer *m_settings = nullptr; - std::function m_layouter; }; /* @@ -89,13 +101,13 @@ private: before the options pages get available.) */ -class CORE_EXPORT IOptionsPageProvider : public QObject +class CORE_EXPORT IOptionsPageProvider { - Q_OBJECT + Q_DISABLE_COPY_MOVE(IOptionsPageProvider); public: - IOptionsPageProvider(QObject *parent = nullptr); - ~IOptionsPageProvider() override; + IOptionsPageProvider(); + virtual ~IOptionsPageProvider(); static const QList allOptionsPagesProviders(); @@ -116,4 +128,13 @@ protected: Utils::Icon m_categoryIcon; }; +class CORE_EXPORT PagedSettings : public Utils::AspectContainer, public IOptionsPage +{ +public: + PagedSettings(); + + using AspectContainer::readSettings; // FIXME: Remove. + void readSettings(); // Intentionally hides AspectContainer::readSettings() +}; + } // namespace Core diff --git a/src/plugins/coreplugin/dialogs/openwithdialog.cpp b/src/plugins/coreplugin/dialogs/openwithdialog.cpp index ddec97928bf..ff8db72ddbf 100644 --- a/src/plugins/coreplugin/dialogs/openwithdialog.cpp +++ b/src/plugins/coreplugin/dialogs/openwithdialog.cpp @@ -25,7 +25,7 @@ OpenWithDialog::OpenWithDialog(const Utils::FilePath &filePath, QWidget *parent) buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); - using namespace Utils::Layouting; + using namespace Layouting; // clang-format off Column { Tr::tr("Open file \"%1\" with:").arg(filePath.fileName()), diff --git a/src/plugins/coreplugin/dialogs/readonlyfilesdialog.cpp b/src/plugins/coreplugin/dialogs/readonlyfilesdialog.cpp index 97d35aca82c..88431454fef 100644 --- a/src/plugins/coreplugin/dialogs/readonlyfilesdialog.cpp +++ b/src/plugins/coreplugin/dialogs/readonlyfilesdialog.cpp @@ -422,8 +422,12 @@ void ReadOnlyFilesDialogPrivate::initDialog(const FilePaths &filePaths) using namespace Layouting; - QWidget *setAllWidget = Row{Tr::tr("Select all, if possible: "), m_setAll, st}.emerge( - WithoutMargins); + QWidget *setAllWidget = Row { + Tr::tr("Select all, if possible: "), + m_setAll, + st, + noMargin + }.emerge(); // clang-format off Column { diff --git a/src/plugins/coreplugin/dialogs/saveitemsdialog.cpp b/src/plugins/coreplugin/dialogs/saveitemsdialog.cpp index 87953b3ab92..3b9034ddd71 100644 --- a/src/plugins/coreplugin/dialogs/saveitemsdialog.cpp +++ b/src/plugins/coreplugin/dialogs/saveitemsdialog.cpp @@ -59,7 +59,7 @@ SaveItemsDialog::SaveItemsDialog(QWidget *parent, const QList &item m_saveBeforeBuildCheckBox->setVisible(false); - using namespace Utils::Layouting; + using namespace Layouting; // clang-format off Column { m_msgLabel, diff --git a/src/plugins/coreplugin/dialogs/settingsdialog.cpp b/src/plugins/coreplugin/dialogs/settingsdialog.cpp index 017b8dc8846..f429994edd9 100644 --- a/src/plugins/coreplugin/dialogs/settingsdialog.cpp +++ b/src/plugins/coreplugin/dialogs/settingsdialog.cpp @@ -331,14 +331,26 @@ public: class SmartScrollArea : public QScrollArea { public: - explicit SmartScrollArea(QWidget *parent) - : QScrollArea(parent) + explicit SmartScrollArea(QWidget *parent, IOptionsPage *page) + : QScrollArea(parent), m_page(page) { setFrameStyle(QFrame::NoFrame | QFrame::Plain); viewport()->setAutoFillBackground(false); setWidgetResizable(true); } + private: + void showEvent(QShowEvent *event) final + { + if (!widget()) { + QWidget *inner = m_page->widget(); + setWidget(inner); + inner->setAutoFillBackground(false); + } + + QScrollArea::showEvent(event); + } + void resizeEvent(QResizeEvent *event) final { QWidget *inner = widget(); @@ -387,6 +399,8 @@ private: return 0; return list.first()->sizeHint().width(); } + + IOptionsPage *m_page = nullptr; }; // ----------- SettingsDialog @@ -604,14 +618,8 @@ void SettingsDialog::ensureCategoryWidget(Category *category) m_model.ensurePages(category); auto tabWidget = new QTabWidget; tabWidget->tabBar()->setObjectName("qc_settings_main_tabbar"); // easier lookup in Squish - for (IOptionsPage *page : std::as_const(category->pages)) { - QWidget *widget = page->widget(); - ICore::setupScreenShooter(page->displayName(), widget); - auto ssa = new SmartScrollArea(this); - ssa->setWidget(widget); - widget->setAutoFillBackground(false); - tabWidget->addTab(ssa, page->displayName()); - } + for (IOptionsPage *page : std::as_const(category->pages)) + tabWidget->addTab(new SmartScrollArea(this, page), page->displayName()); connect(tabWidget, &QTabWidget::currentChanged, this, &SettingsDialog::currentTabChanged); diff --git a/src/plugins/coreplugin/dialogs/shortcutsettings.cpp b/src/plugins/coreplugin/dialogs/shortcutsettings.cpp index 598cf92d993..c846fb9dae0 100644 --- a/src/plugins/coreplugin/dialogs/shortcutsettings.cpp +++ b/src/plugins/coreplugin/dialogs/shortcutsettings.cpp @@ -268,10 +268,8 @@ ShortcutSettingsWidget::ShortcutSettingsWidget() this, &ShortcutSettingsWidget::initialize); connect(this, &ShortcutSettingsWidget::currentCommandChanged, this, &ShortcutSettingsWidget::handleCurrentCommandChanged); - connect(this, - &ShortcutSettingsWidget::resetRequested, - this, - &ShortcutSettingsWidget::resetToDefault); + connect(this, &ShortcutSettingsWidget::resetRequested, + this, &ShortcutSettingsWidget::resetToDefault); m_shortcutBox = new QGroupBox(Tr::tr("Shortcut"), this); m_shortcutBox->setEnabled(false); @@ -287,37 +285,12 @@ ShortcutSettingsWidget::~ShortcutSettingsWidget() qDeleteAll(m_scitems); } -ShortcutSettings::ShortcutSettings() -{ - setId(Constants::SETTINGS_ID_SHORTCUTS); - setDisplayName(Tr::tr("Keyboard")); - setCategory(Constants::SETTINGS_CATEGORY_CORE); -} - -QWidget *ShortcutSettings::widget() -{ - if (!m_widget) - m_widget = new ShortcutSettingsWidget(); - return m_widget; -} - void ShortcutSettingsWidget::apply() { for (const ShortcutItem *item : std::as_const(m_scitems)) item->m_cmd->setKeySequences(item->m_keys); } -void ShortcutSettings::apply() -{ - QTC_ASSERT(m_widget, return); - m_widget->apply(); -} - -void ShortcutSettings::finish() -{ - delete m_widget; -} - ShortcutItem *shortcutItem(QTreeWidgetItem *treeItem) { if (!treeItem) @@ -706,5 +679,31 @@ void ShortcutInput::setConflictChecker(const ShortcutInput::ConflictChecker &fun m_conflictChecker = fun; } +// ShortcutSettingsPageWidget + +class ShortcutSettingsPageWidget : public IOptionsPageWidget +{ +public: + ShortcutSettingsPageWidget() + { + auto inner = new ShortcutSettingsWidget; + auto vbox = new QVBoxLayout(this); + vbox->addWidget(inner); + vbox->setContentsMargins(0, 0, 0, 0); + + setOnApply([inner] { inner->apply(); }); + } +}; + +// ShortcutSettings + +ShortcutSettings::ShortcutSettings() +{ + setId(Constants::SETTINGS_ID_SHORTCUTS); + setDisplayName(Tr::tr("Keyboard")); + setCategory(Constants::SETTINGS_CATEGORY_CORE); + setWidgetCreator([] { return new ShortcutSettingsPageWidget; }); +} + } // namespace Internal } // namespace Core diff --git a/src/plugins/coreplugin/dialogs/shortcutsettings.h b/src/plugins/coreplugin/dialogs/shortcutsettings.h index d7edfcad29a..49297b3d501 100644 --- a/src/plugins/coreplugin/dialogs/shortcutsettings.h +++ b/src/plugins/coreplugin/dialogs/shortcutsettings.h @@ -14,19 +14,17 @@ #include QT_BEGIN_NAMESPACE -class QGroupBox; class QLabel; QT_END_NAMESPACE +namespace Utils { class FancyLineEdit; } + namespace Core { class Command; namespace Internal { -class ActionManagerPrivate; -class ShortcutSettingsWidget; - struct ShortcutItem { Command *m_cmd; @@ -90,13 +88,6 @@ class ShortcutSettings final : public IOptionsPage { public: ShortcutSettings(); - - QWidget *widget() override; - void apply() override; - void finish() override; - -private: - QPointer m_widget; }; } // namespace Internal diff --git a/src/plugins/coreplugin/documentmanager.cpp b/src/plugins/coreplugin/documentmanager.cpp index 226564fa0ba..b7b7ccfbe06 100644 --- a/src/plugins/coreplugin/documentmanager.cpp +++ b/src/plugins/coreplugin/documentmanager.cpp @@ -856,7 +856,7 @@ FilePath DocumentManager::getSaveFileNameWithExtension(const QString &title, con FilePath DocumentManager::getSaveAsFileName(const IDocument *document) { QTC_ASSERT(document, return {}); - const QString filter = allDocumentFactoryFiltersString(); + QString filter = allDocumentFactoryFiltersString(); const FilePath filePath = document->filePath(); QString selectedFilter; FilePath fileDialogPath = filePath; @@ -876,6 +876,9 @@ FilePath DocumentManager::getSaveAsFileName(const IDocument *document) if (selectedFilter.isEmpty()) selectedFilter = Utils::mimeTypeForName(document->mimeType()).filterString(); + if (!filter.contains(selectedFilter)) + filter.prepend(selectedFilter + QLatin1String(";;")); + return getSaveFileName(Tr::tr("Save File As"), fileDialogPath, filter, @@ -1027,6 +1030,10 @@ void DocumentManager::showFilePropertiesDialog(const FilePath &filePath) and \a selectedFilter arguments are interpreted like in QFileDialog::getOpenFileNames(). \a pathIn specifies a path to open the dialog in if that is not overridden by the user's policy. + + The \a options argument holds various options about how to run the dialog. + See the QFileDialog::Option enum for more information about the flags you + can pass. */ FilePaths DocumentManager::getOpenFileNames(const QString &filters, diff --git a/src/plugins/coreplugin/editormanager/documentmodel.cpp b/src/plugins/coreplugin/editormanager/documentmodel.cpp index 04a877ec491..876b22b1281 100644 --- a/src/plugins/coreplugin/editormanager/documentmodel.cpp +++ b/src/plugins/coreplugin/editormanager/documentmodel.cpp @@ -11,6 +11,8 @@ #include #include +#include +#include #include #include #include @@ -121,63 +123,55 @@ DocumentModel::Entry *DocumentModelPrivate::addEntry(DocumentModel::Entry *entry bool DocumentModelPrivate::disambiguateDisplayNames(DocumentModel::Entry *entry) { const QString displayName = entry->plainDisplayName(); - int minIdx = -1, maxIdx = -1; - QList dups; + QList dups; + FilePaths paths; + int minIdx = m_entries.count(); + int maxIdx = 0; - for (int i = 0, total = m_entries.count(); i < total; ++i) { + for (int i = 0; i < m_entries.count(); ++i) { DocumentModel::Entry *e = m_entries.at(i); if (e == entry || e->plainDisplayName() == displayName) { - e->document->setUniqueDisplayName(QString()); - dups += DynamicEntry(e); - maxIdx = i; - if (minIdx < 0) + if (minIdx > i) minIdx = i; + if (maxIdx < i) + maxIdx = i; + dups += e; + if (!e->filePath().isEmpty()) + paths += e->filePath(); } } - const int dupsCount = dups.count(); - if (dupsCount == 0) + const auto triggerDataChanged = [this](int minIdx, int maxIdx) { + const QModelIndex idxMin = index(minIdx + 1 /**/, 0); + const QModelIndex idxMax = index(maxIdx + 1 /**/, 0); + if (idxMin.isValid() && idxMax.isValid()) + emit dataChanged(idxMin, idxMax); + }; + + if (dups.count() == 1) { + dups.at(0)->document->setUniqueDisplayName({}); + triggerDataChanged(minIdx, maxIdx); return false; - - if (dupsCount > 1) { - int serial = 0; - int count = 0; - // increase uniqueness unless no dups are left - forever { - bool seenDups = false; - for (int i = 0; i < dupsCount - 1; ++i) { - DynamicEntry &e = dups[i]; - const Utils::FilePath myFileName = e->document->filePath(); - if (e->document->isTemporary() || myFileName.isEmpty() || count > 10) { - // path-less entry, append number - e.setNumberedName(++serial); - continue; - } - for (int j = i + 1; j < dupsCount; ++j) { - DynamicEntry &e2 = dups[j]; - if (e->displayName().compare(e2->displayName(), Utils::HostOsInfo::fileNameCaseSensitivity()) == 0) { - const Utils::FilePath otherFileName = e2->document->filePath(); - if (otherFileName.isEmpty()) - continue; - seenDups = true; - e2.disambiguate(); - if (j > maxIdx) - maxIdx = j; - } - } - if (seenDups) { - e.disambiguate(); - ++count; - break; - } - } - if (!seenDups) - break; - } } - emit dataChanged(index(minIdx + 1, 0), index(maxIdx + 1, 0)); + const FilePath commonAncestor = FileUtils::commonPath(paths); + + int countWithoutFilePath = 0; + for (DocumentModel::Entry *e : std::as_const(dups)) { + const FilePath path = e->filePath(); + if (path.isEmpty()) { + e->document->setUniqueDisplayName(QStringLiteral("%1 (%2)") + .arg(e->document->displayName()) + .arg(++countWithoutFilePath)); + continue; + } + const QString uniqueDisplayName = path.relativeChildPath(commonAncestor).toString(); + if (uniqueDisplayName != "" && e->document->uniqueDisplayName() != uniqueDisplayName) { + e->document->setUniqueDisplayName(uniqueDisplayName); + } + } + triggerDataChanged(minIdx, maxIdx); return true; } @@ -483,30 +477,6 @@ void DocumentModelPrivate::removeAllSuspendedEntries(PinnedFileRemovalPolicy pin } } -DocumentModelPrivate::DynamicEntry::DynamicEntry(DocumentModel::Entry *e) : - entry(e), - pathComponents(0) -{ -} - -DocumentModel::Entry *DocumentModelPrivate::DynamicEntry::operator->() const -{ - return entry; -} - -void DocumentModelPrivate::DynamicEntry::disambiguate() -{ - const QString display = entry->filePath().fileNameWithPathComponents(++pathComponents); - entry->document->setUniqueDisplayName(display); -} - -void DocumentModelPrivate::DynamicEntry::setNumberedName(int number) -{ - entry->document->setUniqueDisplayName(QStringLiteral("%1 (%2)") - .arg(entry->document->displayName()) - .arg(number)); -} - } // Internal DocumentModel::Entry::Entry() : diff --git a/src/plugins/coreplugin/editormanager/documentmodel_p.h b/src/plugins/coreplugin/editormanager/documentmodel_p.h index 8e3efa98b0b..6b99d3fe438 100644 --- a/src/plugins/coreplugin/editormanager/documentmodel_p.h +++ b/src/plugins/coreplugin/editormanager/documentmodel_p.h @@ -61,18 +61,6 @@ public: void itemChanged(IDocument *document); - class DynamicEntry - { - public: - DocumentModel::Entry *entry; - int pathComponents; - - DynamicEntry(DocumentModel::Entry *e); - DocumentModel::Entry *operator->() const; - void disambiguate(); - void setNumberedName(int number); - }; - QList m_entries; QMap > m_editors; QHash m_entryByFixedPath; diff --git a/src/plugins/coreplugin/editormanager/editormanager.cpp b/src/plugins/coreplugin/editormanager/editormanager.cpp index 2953a833b70..e7147e94cea 100644 --- a/src/plugins/coreplugin/editormanager/editormanager.cpp +++ b/src/plugins/coreplugin/editormanager/editormanager.cpp @@ -24,10 +24,10 @@ #include "../editormanager/ieditorfactory_p.h" #include "../editormanager/iexternaleditor.h" #include "../fileutils.h" -#include "../find/searchresultitem.h" #include "../findplaceholder.h" #include "../icore.h" #include "../iversioncontrol.h" +#include "../locator/ilocatorfilter.h" #include "../modemanager.h" #include "../outputpane.h" #include "../outputpanemanager.h" @@ -51,6 +51,7 @@ #include #include #include +#include #include #include @@ -238,6 +239,10 @@ void EditorManagerPlaceHolder::showEvent(QShowEvent *) \value SwitchSplitIfAlreadyVisible Switches to another split if the document is already visible there. + \value DoNotRaise + Prevents raising the \QC window to the foreground. + \value AllowExternalEditor + Allows opening the file in an external editor. */ /*! @@ -751,17 +756,13 @@ bool EditorManagerPrivate::skipOpeningBigTextFile(const FilePath &filePath) .arg(filePath.fileName()) .arg(fileSizeInMB, 0, 'f', 2); - CheckableMessageBox messageBox(ICore::dialogParent()); - messageBox.setWindowTitle(title); - messageBox.setText(text); - messageBox.setStandardButtons(QDialogButtonBox::Yes|QDialogButtonBox::No); - messageBox.setDefaultButton(QDialogButtonBox::No); - messageBox.setIcon(QMessageBox::Question); - messageBox.setCheckBoxVisible(true); - messageBox.setCheckBoxText(CheckableMessageBox::msgDoNotAskAgain()); - messageBox.exec(); - setWarnBeforeOpeningBigFilesEnabled(!messageBox.isChecked()); - return messageBox.clickedStandardButton() != QDialogButtonBox::Yes; + bool askAgain = true; + CheckableDecider decider(&askAgain); + + QMessageBox::StandardButton clickedButton + = CheckableMessageBox::question(ICore::dialogParent(), title, text, decider); + setWarnBeforeOpeningBigFilesEnabled(askAgain); + return clickedButton != QMessageBox::Yes; } return false; @@ -3157,6 +3158,17 @@ IEditor *EditorManager::openEditorAt(const Link &link, newEditor); } +IEditor *EditorManager::openEditor(const LocatorFilterEntry &entry) +{ + const OpenEditorFlags defaultFlags = EditorManager::AllowExternalEditor; + if (entry.linkForEditor) + return EditorManager::openEditorAt(*entry.linkForEditor, {}, defaultFlags); + else if (!entry.filePath.isEmpty()) + return EditorManager::openEditor(entry.filePath, {}, defaultFlags); + return nullptr; +} + + /*! Opens the document at the position of the search result \a item using the editor type \a editorId and the specified \a flags. @@ -3180,7 +3192,7 @@ void EditorManager::openEditorAtSearchResult(const SearchResultItem &item, openEditor(FilePath::fromUserInput(item.lineText()), editorId, flags, newEditor); return; } - const Search::TextPosition position = item.mainRange().begin; + const Text::Position position = item.mainRange().begin; openEditorAt({FilePath::fromUserInput(path.first()), position.line, position.column}, editorId, flags, newEditor); } @@ -3238,7 +3250,11 @@ void EditorManager::addCloseEditorListener(const std::function /*! Asks the user for a list of files to open and returns the choice. - \sa DocumentManager::getOpenFileNames() + The \a options argument holds various options about how to run the dialog. + See the QFileDialog::Options enum for more information about the flags you + can pass. + + \sa DocumentManager::getOpenFileNames(), QFileDialog::Options */ FilePaths EditorManager::getOpenFilePaths(QFileDialog::Options options) { diff --git a/src/plugins/coreplugin/editormanager/editormanager.h b/src/plugins/coreplugin/editormanager/editormanager.h index c1c4c4644c3..09218f42c3e 100644 --- a/src/plugins/coreplugin/editormanager/editormanager.h +++ b/src/plugins/coreplugin/editormanager/editormanager.h @@ -9,8 +9,8 @@ #include "documentmodel.h" #include "ieditor.h" -#include "utils/link.h" -#include "utils/textfileformat.h" +#include +#include #include #include @@ -18,21 +18,18 @@ #include -QT_FORWARD_DECLARE_CLASS(QMenu) +QT_BEGIN_NAMESPACE +class QMenu; +QT_END_NAMESPACE -namespace Utils { -class MimeType; -} +namespace Utils { class SearchResultItem; } namespace Core { class IDocument; -class SearchResultItem; +class LocatorFilterEntry; -namespace Internal { -class EditorManagerPrivate; -class MainWindow; -} // namespace Internal +namespace Internal { class MainWindow; } class CORE_EXPORT EditorManagerPlaceHolder final : public QWidget { @@ -76,8 +73,9 @@ public: Utils::Id editorId = {}, OpenEditorFlags flags = NoFlags, bool *newEditor = nullptr); + static IEditor *openEditor(const LocatorFilterEntry &entry); - static void openEditorAtSearchResult(const SearchResultItem &item, + static void openEditorAtSearchResult(const Utils::SearchResultItem &item, Utils::Id editorId = {}, OpenEditorFlags flags = NoFlags, bool *newEditor = nullptr); diff --git a/src/plugins/coreplugin/editormanager/iexternaleditor.cpp b/src/plugins/coreplugin/editormanager/iexternaleditor.cpp index 44a2db84832..8176bf4d3ee 100644 --- a/src/plugins/coreplugin/editormanager/iexternaleditor.cpp +++ b/src/plugins/coreplugin/editormanager/iexternaleditor.cpp @@ -17,21 +17,6 @@ namespace Core { editor in the \uicontrol{Open With} dialog. */ -/*! - \fn QString Core::IExternalEditor::displayName() const - Returns a user-visible description of the editor type. -*/ - -/*! - \fn Utils::Id Core::IExternalEditor::id() const - Returns the ID of the factory or editor type. -*/ - -/*! - \fn QStringList Core::IExternalEditor::mimeTypes() const - Returns a list of MIME types that the editor supports -*/ - /*! \fn bool Core::IExternalEditor::startEditor(const Utils::FilePath &fileName, QString *errorMessage) diff --git a/src/plugins/coreplugin/editortoolbar.cpp b/src/plugins/coreplugin/editortoolbar.cpp index fadfe4cae67..ee4d912d85c 100644 --- a/src/plugins/coreplugin/editortoolbar.cpp +++ b/src/plugins/coreplugin/editortoolbar.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -109,7 +110,7 @@ EditorToolBar::EditorToolBar(QWidget *parent) : d->m_lockButton->setEnabled(false); - d->m_dragHandle->setProperty("noArrow", true); + d->m_dragHandle->setProperty(Utils::StyleHelper::C_NO_ARROW, true); d->m_dragHandle->setToolTip(Tr::tr("Drag to drag documents between splits")); d->m_dragHandle->installEventFilter(this); d->m_dragHandleMenu = new QMenu(d->m_dragHandle); @@ -118,9 +119,9 @@ EditorToolBar::EditorToolBar(QWidget *parent) : connect(d->m_goBackAction, &QAction::triggered, this, &EditorToolBar::goBackClicked); connect(d->m_goForwardAction, &QAction::triggered, this, &EditorToolBar::goForwardClicked); - d->m_editorList->setProperty("hideicon", true); - d->m_editorList->setProperty("notelideasterisk", true); - d->m_editorList->setProperty("elidemode", Qt::ElideMiddle); + d->m_editorList->setProperty(Utils::StyleHelper::C_HIDE_ICON, true); + d->m_editorList->setProperty(Utils::StyleHelper::C_NOT_ELIDE_ASTERISK, true); + d->m_editorList->setProperty(Utils::StyleHelper::C_ELIDE_MODE, Qt::ElideMiddle); d->m_editorList->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); d->m_editorList->setMinimumContentsLength(20); d->m_editorList->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); @@ -130,7 +131,7 @@ EditorToolBar::EditorToolBar(QWidget *parent) : d->m_closeEditorButton->setIcon(Utils::Icons::CLOSE_TOOLBAR.icon()); d->m_closeEditorButton->setEnabled(false); - d->m_closeEditorButton->setProperty("showborder", true); + d->m_closeEditorButton->setProperty(Utils::StyleHelper::C_SHOW_BORDER, true); d->m_toolBarPlaceholder->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); @@ -141,7 +142,7 @@ EditorToolBar::EditorToolBar(QWidget *parent) : d->m_splitButton->setIcon(Utils::Icons::SPLIT_HORIZONTAL_TOOLBAR.icon()); d->m_splitButton->setToolTip(Tr::tr("Split")); d->m_splitButton->setPopupMode(QToolButton::InstantPopup); - d->m_splitButton->setProperty("noArrow", true); + d->m_splitButton->setProperty(Utils::StyleHelper::C_NO_ARROW, true); auto splitMenu = new QMenu(d->m_splitButton); splitMenu->addAction(d->m_horizontalSplitAction); splitMenu->addAction(d->m_verticalSplitAction); diff --git a/src/plugins/coreplugin/externaltool.cpp b/src/plugins/coreplugin/externaltool.cpp index 0c0b1d89260..6cafa6ff887 100644 --- a/src/plugins/coreplugin/externaltool.cpp +++ b/src/plugins/coreplugin/externaltool.cpp @@ -16,8 +16,8 @@ #include #include #include +#include #include -#include #include #include @@ -623,11 +623,11 @@ void ExternalToolRunner::run() DocumentManager::expectFileChange(m_expectedFilePath); } } - m_process = new QtcProcess(this); - connect(m_process, &QtcProcess::done, this, &ExternalToolRunner::done); - connect(m_process, &QtcProcess::readyReadStandardOutput, + m_process = new Process(this); + connect(m_process, &Process::done, this, &ExternalToolRunner::done); + connect(m_process, &Process::readyReadStandardOutput, this, &ExternalToolRunner::readStandardOutput); - connect(m_process, &QtcProcess::readyReadStandardError, + connect(m_process, &Process::readyReadStandardError, this, &ExternalToolRunner::readStandardError); if (!m_resolvedWorkingDirectory.isEmpty()) m_process->setWorkingDirectory(m_resolvedWorkingDirectory); diff --git a/src/plugins/coreplugin/externaltool.h b/src/plugins/coreplugin/externaltool.h index 8a32d241653..d92d6099bc9 100644 --- a/src/plugins/coreplugin/externaltool.h +++ b/src/plugins/coreplugin/externaltool.h @@ -14,7 +14,7 @@ #include #include -namespace Utils { class QtcProcess; } +namespace Utils { class Process; } namespace Core { @@ -127,7 +127,7 @@ private: QString m_resolvedInput; Utils::FilePath m_resolvedWorkingDirectory; Utils::Environment m_resolvedEnvironment; - Utils::QtcProcess *m_process; + Utils::Process *m_process; QTextCodec *m_outputCodec; QTextCodec::ConverterState m_outputCodecState; QTextCodec::ConverterState m_errorCodecState; diff --git a/src/plugins/coreplugin/fancyactionbar.cpp b/src/plugins/coreplugin/fancyactionbar.cpp index 55904bf149b..a07dd718976 100644 --- a/src/plugins/coreplugin/fancyactionbar.cpp +++ b/src/plugins/coreplugin/fancyactionbar.cpp @@ -192,10 +192,7 @@ void FancyToolButton::paintEvent(QPaintEvent *event) const int textFlags = Qt::AlignVCenter | Qt::AlignHCenter; const QString projectName = defaultAction()->property("heading").toString(); - if (!projectName.isNull()) - centerRect.adjust(0, lineHeight + 4, 0, 0); - - centerRect.adjust(0, 0, 0, -lineHeight * 2 - 4); + centerRect.adjust(0, lineHeight + 4, 0, -lineHeight * 2 - 4); iconRect.moveCenter(centerRect.center()); StyleHelper::drawIconWithShadow(icon(), iconRect, &painter, iconMode); @@ -294,12 +291,12 @@ QSize FancyToolButton::sizeHint() const boldFont.setBold(true); const QFontMetrics fm(boldFont); const qreal lineHeight = fm.height(); - const QString projectName = defaultAction()->property("heading").toString(); - buttonSize += QSizeF(0, 10); - if (!projectName.isEmpty()) - buttonSize += QSizeF(0, lineHeight + 2); - - buttonSize += QSizeF(0, lineHeight * 2 + 2); + const int extraHeight = 10 // Spacing between top and projectName + + lineHeight // projectName height + + 2 // Spacing between projectName and icon + + lineHeight * 2 // configurationName height (2 lines) + + 2; // Spacing between configurationName and bottom + buttonSize.rheight() += extraHeight; } return buttonSize.toSize(); } diff --git a/src/plugins/coreplugin/featureprovider.cpp b/src/plugins/coreplugin/featureprovider.cpp index efe792bfb21..739bdb7067d 100644 --- a/src/plugins/coreplugin/featureprovider.cpp +++ b/src/plugins/coreplugin/featureprovider.cpp @@ -13,18 +13,22 @@ for wizards. The features provided by an object in the object pool implementing IFeatureProvider - will be respected by wizards implementing IWizard. + will be respected by wizards implementing IWizardFactory. This feature set, provided by all instances of IFeatureProvider in the - object pool, is checked against \c IWizard::requiredFeatures() + object pool, is checked against \c IWizardFactory::requiredFeatures() and only if all required features are available, the wizard is displayed when creating a new file or project. + If you created JSON-based wizards, the feature set is checked against the + \c featuresRequired setting that is described in + \l{https://doc.qt.io/qtcreator/creator-project-wizards.html} + {Adding New Custom Wizards} in the \QC Manual. + The QtSupport plugin creates an instance of IFeatureProvider and provides Qt specific features for the available versions of Qt. - \sa Core::IWizard - \sa QtSupport::QtVersionManager + \sa Core::IWizardFactory */ /*! diff --git a/src/plugins/coreplugin/fileutils.cpp b/src/plugins/coreplugin/fileutils.cpp index 6c783488971..3df3309c318 100644 --- a/src/plugins/coreplugin/fileutils.cpp +++ b/src/plugins/coreplugin/fileutils.cpp @@ -17,8 +17,9 @@ #include #include #include -#include +#include #include +#include #include #include @@ -56,7 +57,7 @@ void FileUtils::showInGraphicalShell(QWidget *parent, const FilePath &pathIn) const QFileInfo fileInfo = pathIn.toFileInfo(); // Mac, Windows support folder or file. if (HostOsInfo::isWindowsHost()) { - const FilePath explorer = Environment::systemEnvironment().searchInPath(QLatin1String("explorer.exe")); + const FilePath explorer = FilePath("explorer.exe").searchInPath(); if (explorer.isEmpty()) { QMessageBox::warning(parent, Tr::tr("Launching Windows Explorer Failed"), @@ -67,15 +68,16 @@ void FileUtils::showInGraphicalShell(QWidget *parent, const FilePath &pathIn) if (!pathIn.isDir()) param += QLatin1String("/select,"); param += QDir::toNativeSeparators(fileInfo.canonicalFilePath()); - QtcProcess::startDetached({explorer, param}); + Process::startDetached({explorer, param}); } else if (HostOsInfo::isMacHost()) { - QtcProcess::startDetached({"/usr/bin/open", {"-R", fileInfo.canonicalFilePath()}}); + Process::startDetached({"/usr/bin/open", {"-R", fileInfo.canonicalFilePath()}}); } else { // we cannot select a file here, because no file browser really supports it... const QString folder = fileInfo.isDir() ? fileInfo.absoluteFilePath() : fileInfo.filePath(); const QString app = UnixUtils::fileBrowser(ICore::settings()); QStringList browserArgs = ProcessArgs::splitArgs( - UnixUtils::substituteFileBrowserParameters(app, folder)); + UnixUtils::substituteFileBrowserParameters(app, folder), + HostOsInfo::hostOs()); QString error; if (browserArgs.isEmpty()) { error = Tr::tr("The command for file browser is not set."); @@ -104,8 +106,7 @@ void FileUtils::showInFileSystemView(const FilePath &path) void FileUtils::openTerminal(const FilePath &path, const Environment &env) { - QTC_ASSERT(DeviceFileHooks::instance().openTerminal, return); - DeviceFileHooks::instance().openTerminal(path, env); + Terminal::Hooks::instance().openTerminal({std::nullopt, path, env}); } QString FileUtils::msgFindInDirectory() @@ -169,8 +170,11 @@ bool FileUtils::renameFile(const FilePath &orgFilePath, const FilePath &newFileP if (orgFilePath == newFilePath) return false; - FilePath dir = orgFilePath.absolutePath(); + const FilePath dir = orgFilePath.absolutePath(); IVersionControl *vc = VcsManager::findVersionControlForDirectory(dir); + const FilePath newDir = newFilePath.absolutePath(); + if (newDir != dir && !newDir.ensureWritableDir()) + return false; bool result = false; if (vc && vc->supportsOperation(IVersionControl::MoveOperation)) diff --git a/src/plugins/coreplugin/find/findtoolbar.cpp b/src/plugins/coreplugin/find/findtoolbar.cpp index 11c51fe26ea..96a99f6eba7 100644 --- a/src/plugins/coreplugin/find/findtoolbar.cpp +++ b/src/plugins/coreplugin/find/findtoolbar.cpp @@ -133,7 +133,6 @@ FindToolBar::FindToolBar(CurrentDocumentFind *currentDocumentFind) auto verticalLayout_3 = new QVBoxLayout(); verticalLayout_3->setSpacing(0); verticalLayout_3->addWidget(m_advancedButton); - verticalLayout_3->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding)); auto gridLayout = new QGridLayout(); gridLayout->setHorizontalSpacing(3); @@ -147,8 +146,8 @@ FindToolBar::FindToolBar(CurrentDocumentFind *currentDocumentFind) m_findEdit, findButtonsWidget, br, - Column { m_replaceLabel, st }.setSpacing(0), - Column { m_replaceEdit, st }.setSpacing(0), + Column { spacing(0), m_replaceLabel, st }, + Column { spacing(0), m_replaceEdit, st }, gridLayout, }.attachTo(this); @@ -159,7 +158,7 @@ FindToolBar::FindToolBar(CurrentDocumentFind *currentDocumentFind) mainLayout->setColumnStretch(1, 10); setFocusProxy(m_findEdit); - setProperty("topBorder", true); + setProperty(StyleHelper::C_TOP_BORDER, true); setSingleRow(false); QWidget::setTabOrder(m_findEdit, m_replaceEdit); diff --git a/src/plugins/coreplugin/find/findtoolwindow.cpp b/src/plugins/coreplugin/find/findtoolwindow.cpp index 1ab13cdc7c1..0125671d34e 100644 --- a/src/plugins/coreplugin/find/findtoolwindow.cpp +++ b/src/plugins/coreplugin/find/findtoolwindow.cpp @@ -109,7 +109,8 @@ FindToolWindow::FindToolWindow(QWidget *parent) m_wholeWords, m_regExp, st, - }.attachTo(m_optionsWidget, WithoutMargins); + noMargin + }.attachTo(m_optionsWidget); Grid { label, m_filterList, br, diff --git a/src/plugins/coreplugin/find/itemviewfind.cpp b/src/plugins/coreplugin/find/itemviewfind.cpp index c1c6e84f936..8bba18ae692 100644 --- a/src/plugins/coreplugin/find/itemviewfind.cpp +++ b/src/plugins/coreplugin/find/itemviewfind.cpp @@ -203,11 +203,18 @@ IFindSupport::Result ItemViewFind::find(const QString &searchTxt, index, d->m_role).toString(); if (d->m_view->model()->flags(index) & Qt::ItemIsSelectable && (index.row() != currentRow || index.parent() != currentIndex.parent()) - && text.indexOf(searchExpr) != -1) + && text.indexOf(searchExpr) != -1) { resultIndex = index; + break; + } } index = followingIndex(index, backward, &stepWrapped); - } while (!resultIndex.isValid() && index.isValid() && index != currentIndex); + if (index == currentIndex) { // we're back where we started + if (d->m_view->model()->data(index, d->m_role).toString().indexOf(searchExpr) != -1) + resultIndex = index; + break; + } + } while (index.isValid()); if (resultIndex.isValid()) { d->m_view->setCurrentIndex(resultIndex); diff --git a/src/plugins/coreplugin/find/searchresultcolor.h b/src/plugins/coreplugin/find/searchresultcolor.h deleted file mode 100644 index a6bc46b4e18..00000000000 --- a/src/plugins/coreplugin/find/searchresultcolor.h +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "../core_global.h" - -#include -#include - -namespace Core { - -class CORE_EXPORT SearchResultColor -{ -public: - enum class Style { Default, Alt1, Alt2 }; - - SearchResultColor() = default; - SearchResultColor(const QColor &textBg, const QColor &textFg, - const QColor &highlightBg, const QColor &highlightFg, - const QColor &functionBg, const QColor &functionFg - ) - : textBackground(textBg), textForeground(textFg), - highlightBackground(highlightBg), highlightForeground(highlightFg), - containingFunctionBackground(functionBg),containingFunctionForeground(functionFg) - { - if (!highlightBackground.isValid()) - highlightBackground = textBackground; - if (!highlightForeground.isValid()) - highlightForeground = textForeground; - if (!containingFunctionBackground.isValid()) - containingFunctionBackground = textBackground; - if (!containingFunctionForeground.isValid()) - containingFunctionForeground = textForeground; - } - - friend auto qHash(SearchResultColor::Style style) - { - return QT_PREPEND_NAMESPACE(qHash(int(style))); - } - - QColor textBackground; - QColor textForeground; - QColor highlightBackground; - QColor highlightForeground; - QColor containingFunctionBackground; - QColor containingFunctionForeground; -}; - -using SearchResultColors = QHash; - -} // namespace Core diff --git a/src/plugins/coreplugin/find/searchresultitem.h b/src/plugins/coreplugin/find/searchresultitem.h deleted file mode 100644 index d33df65f0a0..00000000000 --- a/src/plugins/coreplugin/find/searchresultitem.h +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "searchresultcolor.h" - -#include -#include - -#include -#include -#include - -#include - -namespace Core { - -namespace Search { - -class TextPosition -{ -public: - TextPosition() = default; - TextPosition(int line, int column) : line(line), column(column) {} - - int line = -1; // (0 or -1 for no line number) - int column = -1; // 0-based starting position for a mark (-1 for no mark) - - bool operator<(const TextPosition &other) - { return line < other.line || (line == other.line && column < other.column); } -}; - -class TextRange -{ -public: - TextRange() = default; - TextRange(TextPosition begin, TextPosition end) : begin(begin), end(end) {} - - QString mid(const QString &text) const { return text.mid(begin.column, length(text)); } - - int length(const QString &text) const - { - if (begin.line == end.line) - return end.column - begin.column; - - const int lineCount = end.line - begin.line; - int index = text.indexOf(QChar::LineFeed); - int currentLine = 1; - while (index > 0 && currentLine < lineCount) { - ++index; - index = text.indexOf(QChar::LineFeed, index); - ++currentLine; - } - - if (index < 0) - return 0; - - return index - begin.column + end.column; - } - - TextPosition begin; - TextPosition end; - - bool operator<(const TextRange &other) - { return begin < other.begin; } -}; - -} // namespace Search - -class CORE_EXPORT SearchResultItem -{ -public: - QStringList path() const { return m_path; } - void setPath(const QStringList &path) { m_path = path; } - void setFilePath(const Utils::FilePath &filePath) - { - m_path = QStringList{filePath.toUserOutput()}; - } - - QString lineText() const { return m_lineText; } - void setLineText(const QString &text) { m_lineText = text; } - - QIcon icon() const { return m_icon; } - void setIcon(const QIcon &icon) { m_icon = icon; } - - QVariant userData() const { return m_userData; } - void setUserData(const QVariant &userData) { m_userData = userData; } - - Search::TextRange mainRange() const { return m_mainRange; } - void setMainRange(const Search::TextRange &mainRange) { m_mainRange = mainRange; } - void setMainRange(int line, int column, int length) - { - m_mainRange = {}; - m_mainRange.begin.line = line; - m_mainRange.begin.column = column; - m_mainRange.end.line = m_mainRange.begin.line; - m_mainRange.end.column = m_mainRange.begin.column + length; - } - - bool useTextEditorFont() const { return m_useTextEditorFont; } - void setUseTextEditorFont(bool useTextEditorFont) { m_useTextEditorFont = useTextEditorFont; } - - SearchResultColor::Style style() const { return m_style; } - void setStyle(SearchResultColor::Style style) { m_style = style; } - - bool selectForReplacement() const { return m_selectForReplacement; } - void setSelectForReplacement(bool select) { m_selectForReplacement = select; } - - std::optional containingFunctionName() const { return m_containingFunctionName; } - - void setContainingFunctionName(std::optional containingFunctionName) - { - m_containingFunctionName = std::move(containingFunctionName); - } - -private: - QStringList m_path; // hierarchy to the parent item of this item - QString m_lineText; // text to show for the item itself - QIcon m_icon; // icon to show in front of the item (by be null icon to hide) - QVariant m_userData; // user data for identification of the item - Search::TextRange m_mainRange; - bool m_useTextEditorFont = false; - bool m_selectForReplacement = true; - SearchResultColor::Style m_style = SearchResultColor::Style::Default; - std::optional m_containingFunctionName; -}; - -} // namespace Core - -Q_DECLARE_METATYPE(Core::SearchResultItem) -Q_DECLARE_METATYPE(Core::Search::TextPosition) diff --git a/src/plugins/coreplugin/find/searchresulttreeitems.cpp b/src/plugins/coreplugin/find/searchresulttreeitems.cpp index 79ccc2429d0..9aa8a765db9 100644 --- a/src/plugins/coreplugin/find/searchresulttreeitems.cpp +++ b/src/plugins/coreplugin/find/searchresulttreeitems.cpp @@ -3,10 +3,12 @@ #include "searchresulttreeitems.h" +#include + namespace Core { namespace Internal { -SearchResultTreeItem::SearchResultTreeItem(const SearchResultItem &item, +SearchResultTreeItem::SearchResultTreeItem(const Utils::SearchResultItem &item, SearchResultTreeItem *parent) : item(item), m_parent(parent), @@ -79,7 +81,8 @@ int SearchResultTreeItem::insertionIndex(const QString &text, SearchResultTreeIt return insertionPosition - m_children.begin(); } -int SearchResultTreeItem::insertionIndex(const SearchResultItem &item, SearchResultTreeItem **existingItem) const +int SearchResultTreeItem::insertionIndex(const Utils::SearchResultItem &item, + SearchResultTreeItem **existingItem) const { return insertionIndex(item.lineText(), existingItem); } @@ -89,13 +92,13 @@ void SearchResultTreeItem::insertChild(int index, SearchResultTreeItem *child) m_children.insert(index, child); } -void SearchResultTreeItem::insertChild(int index, const SearchResultItem &item) +void SearchResultTreeItem::insertChild(int index, const Utils::SearchResultItem &item) { auto child = new SearchResultTreeItem(item, this); insertChild(index, child); } -void SearchResultTreeItem::appendChild(const SearchResultItem &item) +void SearchResultTreeItem::appendChild(const Utils::SearchResultItem &item) { insertChild(m_children.count(), item); } diff --git a/src/plugins/coreplugin/find/searchresulttreeitems.h b/src/plugins/coreplugin/find/searchresulttreeitems.h index 4d4b3530393..dbbc27d8861 100644 --- a/src/plugins/coreplugin/find/searchresulttreeitems.h +++ b/src/plugins/coreplugin/find/searchresulttreeitems.h @@ -5,8 +5,7 @@ #include "searchresultwindow.h" -#include -#include +#include namespace Core { namespace Internal { @@ -14,7 +13,7 @@ namespace Internal { class SearchResultTreeItem { public: - explicit SearchResultTreeItem(const SearchResultItem &item = SearchResultItem(), + explicit SearchResultTreeItem(const Utils::SearchResultItem &item = {}, SearchResultTreeItem *parent = nullptr); virtual ~SearchResultTreeItem(); @@ -22,10 +21,10 @@ public: SearchResultTreeItem *parent() const; SearchResultTreeItem *childAt(int index) const; int insertionIndex(const QString &text, SearchResultTreeItem **existingItem) const; - int insertionIndex(const SearchResultItem &item, SearchResultTreeItem **existingItem) const; + int insertionIndex(const Utils::SearchResultItem &item, SearchResultTreeItem **existingItem) const; void insertChild(int index, SearchResultTreeItem *child); - void insertChild(int index, const SearchResultItem &item); - void appendChild(const SearchResultItem &item); + void insertChild(int index, const Utils::SearchResultItem &item); + void appendChild(const Utils::SearchResultItem &item); int childrenCount() const; int rowOfItem() const; void clearChildren(); @@ -36,7 +35,7 @@ public: bool isGenerated() const { return m_isGenerated; } void setGenerated(bool value) { m_isGenerated = value; } - SearchResultItem item; + Utils::SearchResultItem item; private: SearchResultTreeItem *m_parent; diff --git a/src/plugins/coreplugin/find/searchresulttreemodel.cpp b/src/plugins/coreplugin/find/searchresulttreemodel.cpp index cf00cf755a2..b6d620b80b5 100644 --- a/src/plugins/coreplugin/find/searchresulttreemodel.cpp +++ b/src/plugins/coreplugin/find/searchresulttreemodel.cpp @@ -6,12 +6,15 @@ #include "searchresulttreeitemroles.h" #include +#include #include #include #include #include +using namespace Utils; + namespace Core { namespace Internal { @@ -38,7 +41,7 @@ public: QModelIndex next(const QModelIndex &idx, bool includeGenerated = false, bool *wrapped = nullptr) const; QModelIndex prev(const QModelIndex &idx, bool includeGenerated = false, bool *wrapped = nullptr) const; - QList addResults(const QList &items, SearchResult::AddMode mode); + QList addResults(const SearchResultItems &items, SearchResult::AddMode mode); static SearchResultTreeItem *treeItemAtIndex(const QModelIndex &idx); @@ -51,7 +54,7 @@ public slots: private: QModelIndex index(SearchResultTreeItem *item) const; - void addResultsToCurrentParent(const QList &items, SearchResult::AddMode mode); + void addResultsToCurrentParent(const SearchResultItems &items, SearchResult::AddMode mode); QSet addPath(const QStringList &path); QVariant data(const SearchResultTreeItem *row, int role) const; bool setCheckState(const QModelIndex &idx, Qt::CheckState checkState, bool firstCall = true); @@ -319,9 +322,13 @@ QVariant SearchResultTreeModel::data(const SearchResultTreeItem *row, int role) case ItemDataRoles::ResultBeginColumnNumberRole: result = row->item.mainRange().begin.column; break; - case ItemDataRoles::SearchTermLengthRole: - result = row->item.mainRange().length(row->item.lineText()); + case ItemDataRoles::SearchTermLengthRole:{ + Text::Range range = row->item.mainRange(); + range.end.line -= range.begin.line - 1; + range.begin.line = 1; + result = range.length(row->item.lineText()); break; + } case ItemDataRoles::ContainingFunctionNameRole: result = row->item.containingFunctionName().value_or(QString{}); break; @@ -382,7 +389,8 @@ QSet SearchResultTreeModel::addPath(const QStringList &p return pathNodes; } -void SearchResultTreeModel::addResultsToCurrentParent(const QList &items, SearchResult::AddMode mode) +void SearchResultTreeModel::addResultsToCurrentParent(const SearchResultItems &items, + SearchResult::AddMode mode) { if (!m_currentParent) return; @@ -433,12 +441,12 @@ static bool lessThanByPath(const SearchResultItem &a, const SearchResultItem &b) * Adds the search result to the list of results, creating nodes for the path when * necessary. */ -QList SearchResultTreeModel::addResults(const QList &items, SearchResult::AddMode mode) +QList SearchResultTreeModel::addResults(const SearchResultItems &items, SearchResult::AddMode mode) { QSet pathNodes; - QList sortedItems = items; + SearchResultItems sortedItems = items; std::stable_sort(sortedItems.begin(), sortedItems.end(), lessThanByPath); - QList itemSet; + SearchResultItems itemSet; for (const SearchResultItem &item : sortedItems) { m_editorFontIsUsed |= item.useTextEditorFont(); if (!m_currentParent || (m_currentPath != item.path())) { @@ -577,7 +585,7 @@ void SearchResultFilterModel::setTextEditorFont(const QFont &font, const SearchR sourceModel()->setTextEditorFont(font, colors); } -QList SearchResultFilterModel::addResults(const QList &items, +QList SearchResultFilterModel::addResults(const SearchResultItems &items, SearchResult::AddMode mode) { QList sourceIndexes = sourceModel()->addResults(items, mode); diff --git a/src/plugins/coreplugin/find/searchresulttreemodel.h b/src/plugins/coreplugin/find/searchresulttreemodel.h index 6727fc56b4c..716bcccca32 100644 --- a/src/plugins/coreplugin/find/searchresulttreemodel.h +++ b/src/plugins/coreplugin/find/searchresulttreemodel.h @@ -4,7 +4,6 @@ #pragma once #include "searchresultwindow.h" -#include "searchresultcolor.h" #include #include @@ -25,8 +24,9 @@ public: void setFilter(SearchResultFilter *filter); void setShowReplaceUI(bool show); - void setTextEditorFont(const QFont &font, const SearchResultColors &colors); - QList addResults(const QList &items, SearchResult::AddMode mode); + void setTextEditorFont(const QFont &font, const Utils::SearchResultColors &colors); + QList addResults(const Utils::SearchResultItems &items, + SearchResult::AddMode mode); void clear(); QModelIndex next(const QModelIndex &idx, bool includeGenerated = false, bool *wrapped = nullptr) const; diff --git a/src/plugins/coreplugin/find/searchresulttreeview.cpp b/src/plugins/coreplugin/find/searchresulttreeview.cpp index e5ac5b4719c..d8026ccd847 100644 --- a/src/plugins/coreplugin/find/searchresulttreeview.cpp +++ b/src/plugins/coreplugin/find/searchresulttreeview.cpp @@ -7,11 +7,14 @@ #include "searchresulttreeitemdelegate.h" #include +#include #include #include #include +using namespace Utils; + namespace Core { namespace Internal { @@ -31,7 +34,7 @@ public: }; SearchResultTreeView::SearchResultTreeView(QWidget *parent) - : Utils::TreeView(parent) + : TreeView(parent) , m_model(new SearchResultFilterModel(this)) , m_autoExpandResults(false) { @@ -70,7 +73,7 @@ void SearchResultTreeView::clear() m_model->clear(); } -void SearchResultTreeView::addResults(const QList &items, SearchResult::AddMode mode) +void SearchResultTreeView::addResults(const SearchResultItems &items, SearchResult::AddMode mode) { const QList addedParents = m_model->addResults(items, mode); if (m_autoExpandResults && !addedParents.isEmpty()) { diff --git a/src/plugins/coreplugin/find/searchresulttreeview.h b/src/plugins/coreplugin/find/searchresulttreeview.h index 9ddbb06cd15..06996fefe09 100644 --- a/src/plugins/coreplugin/find/searchresulttreeview.h +++ b/src/plugins/coreplugin/find/searchresulttreeview.h @@ -6,9 +6,9 @@ #include "searchresultwindow.h" #include +#include namespace Core { -class SearchResultColor; namespace Internal { @@ -22,11 +22,11 @@ public: explicit SearchResultTreeView(QWidget *parent = nullptr); void setAutoExpandResults(bool expand); - void setTextEditorFont(const QFont &font, const SearchResultColors &colors); + void setTextEditorFont(const QFont &font, const Utils::SearchResultColors &colors); void setTabWidth(int tabWidth); SearchResultFilterModel *model() const; - void addResults(const QList &items, SearchResult::AddMode mode); + void addResults(const Utils::SearchResultItems &items, SearchResult::AddMode mode); void setFilter(SearchResultFilter *filter); bool hasFilter() const; void showFilterWidget(QWidget *parent); @@ -35,7 +35,7 @@ public: bool event(QEvent *e) override; signals: - void jumpToSearchResult(const SearchResultItem &item); + void jumpToSearchResult(const Utils::SearchResultItem &item); void filterInvalidated(); void filterChanged(); diff --git a/src/plugins/coreplugin/find/searchresultwidget.cpp b/src/plugins/coreplugin/find/searchresultwidget.cpp index 36344ed804d..ec2ab2c0f64 100644 --- a/src/plugins/coreplugin/find/searchresultwidget.cpp +++ b/src/plugins/coreplugin/find/searchresultwidget.cpp @@ -93,7 +93,7 @@ SearchResultWidget::SearchResultWidget(QWidget *parent) : topLayout->addWidget(m_topReplaceWidget); m_messageWidget = new QFrame; - pal.setColor(QPalette::WindowText, creatorTheme()->color(Theme::CanceledSearchTextColor)); + pal.setColor(QPalette::WindowText, creatorTheme()->color(Theme::TextColorError)); m_messageWidget->setPalette(pal); if (creatorTheme()->flag(Theme::DrawSearchResultWidgetFrame)) { m_messageWidget->setFrameStyle(QFrame::Panel | QFrame::Raised); @@ -226,7 +226,7 @@ void SearchResultWidget::setAdditionalReplaceWidget(QWidget *widget) m_additionalReplaceWidget = widget; } -void SearchResultWidget::addResults(const QList &items, SearchResult::AddMode mode) +void SearchResultWidget::addResults(const SearchResultItems &items, SearchResult::AddMode mode) { bool firstItems = (m_count == 0); m_count += items.size(); @@ -496,9 +496,9 @@ void SearchResultWidget::searchAgain() emit searchAgainRequested(); } -QList SearchResultWidget::checkedItems() const +SearchResultItems SearchResultWidget::checkedItems() const { - QList result; + SearchResultItems result; SearchResultFilterModel *model = m_searchResultTreeView->model(); const int fileCount = model->rowCount(); for (int i = 0; i < fileCount; ++i) { diff --git a/src/plugins/coreplugin/find/searchresultwidget.h b/src/plugins/coreplugin/find/searchresultwidget.h index f10bd5dd673..6722af579eb 100644 --- a/src/plugins/coreplugin/find/searchresultwidget.h +++ b/src/plugins/coreplugin/find/searchresultwidget.h @@ -6,6 +6,7 @@ #include "searchresultwindow.h" #include +#include #include @@ -33,9 +34,10 @@ public: QWidget *additionalReplaceWidget() const; void setAdditionalReplaceWidget(QWidget *widget); - void addResults(const QList &items, SearchResult::AddMode mode); + void addResults(const Utils::SearchResultItems &items, SearchResult::AddMode mode); int count() const; + bool isSearching() const { return m_searching; } void setSupportsReplace(bool replaceSupported, const QString &group); bool supportsReplace() const; @@ -51,7 +53,7 @@ public: void notifyVisibilityChanged(bool visible); - void setTextEditorFont(const QFont &font, const SearchResultColors &colors); + void setTextEditorFont(const QFont &font, const Utils::SearchResultColors &colors); void setTabWidth(int tabWidth); void setAutoExpandResults(bool expand); @@ -75,8 +77,9 @@ public slots: void sendRequestPopup(); signals: - void activated(const Core::SearchResultItem &item); - void replaceButtonClicked(const QString &replaceText, const QList &checkedItems, bool preserveCase); + void activated(const Utils::SearchResultItem &item); + void replaceButtonClicked(const QString &replaceText, + const Utils::SearchResultItems &checkedItems, bool preserveCase); void replaceTextChanged(const QString &replaceText); void searchAgainRequested(); void canceled(); @@ -90,7 +93,7 @@ signals: void navigateStateChanged(); private: - void handleJumpToSearchResult(const SearchResultItem &item); + void handleJumpToSearchResult(const Utils::SearchResultItem &item); void handleReplaceButton(); void doReplace(); void cancel(); @@ -100,7 +103,7 @@ private: void continueAfterSizeWarning(); void cancelAfterSizeWarning(); - QList checkedItems() const; + Utils::SearchResultItems checkedItems() const; void updateMatchesFoundLabel(); SearchResultTreeView *m_searchResultTreeView = nullptr; diff --git a/src/plugins/coreplugin/find/searchresultwindow.cpp b/src/plugins/coreplugin/find/searchresultwindow.cpp index 3b2ea6e8ae9..e2a83a71a0e 100644 --- a/src/plugins/coreplugin/find/searchresultwindow.cpp +++ b/src/plugins/coreplugin/find/searchresultwindow.cpp @@ -8,11 +8,11 @@ #include "../actionmanager/actionmanager.h" #include "../actionmanager/command.h" #include "../coreplugintr.h" -#include "../icontext.h" #include "../icore.h" #include #include +#include #include #include @@ -28,6 +28,9 @@ static const char SETTINGSKEYSECTIONNAME[] = "SearchResults"; static const char SETTINGSKEYEXPANDRESULTS[] = "ExpandResults"; + +// Note that this is a soft limit: If all searches are still running, none of them will be +// removed when a new one is started. static const int MAX_SEARCH_HISTORY = 12; namespace Core { @@ -38,24 +41,6 @@ namespace Core { \internal */ -/*! - \class Core::Search::TextPosition - \inmodule QtCreator - \internal -*/ - -/*! - \class Core::Search::TextRange - \inmodule QtCreator - \internal -*/ - -/*! - \class Core::SearchResultItem - \inmodule QtCreator - \internal -*/ - namespace Internal { class InternalScrollArea : public QScrollArea @@ -90,6 +75,7 @@ namespace Internal { void popupRequested(SearchResultWidget *widget, bool focus); void handleExpandCollapseToolButton(bool checked); void updateFilterButton(); + int indexOfSearchToEvict() const; QList toolBarWidgets(); SearchResultWindow *q; @@ -107,7 +93,7 @@ namespace Internal { QList m_searchResults; int m_currentIndex; QFont m_font; - SearchResultColors m_colors; + Utils::SearchResultColors m_colors; int m_tabWidth; }; @@ -257,14 +243,14 @@ using namespace Core::Internal; */ /*! - \fn void Core::SearchResult::activated(const Core::SearchResultItem &item) + \fn void Core::SearchResult::activated(const Utils::SearchResultItem &item) Indicates that the user activated the search result \a item by double-clicking it, for example. */ /*! \fn void Core::SearchResult::replaceButtonClicked(const QString &replaceText, - const QList &checkedItems, + const Utils::SearchResultItems &checkedItems, bool preserveCase) Indicates that the user initiated a text replace by selecting @@ -290,7 +276,7 @@ using namespace Core::Internal; */ /*! - \fn void Core::SearchResult::cancelled() + \fn void Core::SearchResult::canceled() This signal is emitted if the user cancels the search. */ @@ -473,11 +459,15 @@ SearchResult *SearchResultWindow::startNewSearch(const QString &label, // temporarily set the index to the last but one existing d->m_currentIndex = d->m_recentSearchesBox->count() - 2; } - d->m_searchResultWidgets.last()->notifyVisibilityChanged(false); - // widget first, because that might send interesting signals to SearchResult - delete d->m_searchResultWidgets.takeLast(); - delete d->m_searchResults.takeLast(); - d->m_recentSearchesBox->removeItem(d->m_recentSearchesBox->count() - 1); + if (const int toRemoveIndex = d->indexOfSearchToEvict(); toRemoveIndex != -1) { + SearchResultWidget * const widgetToRemove + = d->m_searchResultWidgets.takeAt(toRemoveIndex); + widgetToRemove->notifyVisibilityChanged(false); + // widget first, because that might send interesting signals to SearchResult + delete widgetToRemove; + delete d->m_searchResults.takeAt(toRemoveIndex); + d->m_recentSearchesBox->removeItem(toRemoveIndex + 1); + } } d->m_recentSearchesBox->insertItem(1, Tr::tr("%1 %2").arg(label, searchTerm)); } @@ -570,7 +560,8 @@ void SearchResultWindow::setFocus() /*! \internal */ -void SearchResultWindow::setTextEditorFont(const QFont &font, const SearchResultColors &colors) +void SearchResultWindow::setTextEditorFont(const QFont &font, + const Utils::SearchResultColors &colors) { d->m_font = font; d->m_colors = colors; @@ -616,13 +607,22 @@ void SearchResultWindowPrivate::updateFilterButton() && m_searchResultWidgets.at(visibleSearchIndex())->hasFilter()); } +int SearchResultWindowPrivate::indexOfSearchToEvict() const +{ + for (int i = m_searchResultWidgets.size() - 1; i >= 0; --i) { + if (!m_searchResultWidgets.at(i)->isSearching()) + return i; + } + return -1; +} + QList SearchResultWindowPrivate::toolBarWidgets() { if (!m_historyLabel) m_historyLabel = new QLabel(Tr::tr("History:")); if (!m_recentSearchesBox) { m_recentSearchesBox = new QComboBox; - m_recentSearchesBox->setProperty("drawleftborder", true); + m_recentSearchesBox->setProperty(Utils::StyleHelper::C_DRAW_LEFT_BORDER, true); m_recentSearchesBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); m_recentSearchesBox->addItem(Tr::tr("New Search")); connect(m_recentSearchesBox, &QComboBox::activated, @@ -821,7 +821,7 @@ void SearchResult::setAdditionalReplaceWidget(QWidget *widget) \sa addResults() */ -void SearchResult::addResult(const SearchResultItem &item) +void SearchResult::addResult(const Utils::SearchResultItem &item) { m_widget->addResults({item}, AddOrdered); } @@ -832,7 +832,7 @@ void SearchResult::addResult(const SearchResultItem &item) \sa addResult() */ -void SearchResult::addResults(const QList &items, AddMode mode) +void SearchResult::addResults(const Utils::SearchResultItems &items, AddMode mode) { m_widget->addResults(items, mode); emit countChanged(m_widget->count()); @@ -845,7 +845,8 @@ void SearchResult::setFilter(SearchResultFilter *filter) /*! Notifies the \uicontrol {Search Results} output pane that the current search - has been \a canceled, and the UI should reflect that. + has been \a canceled for the specified \a reason, and the UI should reflect + that. */ void SearchResult::finishSearch(bool canceled, const QString &reason) { diff --git a/src/plugins/coreplugin/find/searchresultwindow.h b/src/plugins/coreplugin/find/searchresultwindow.h index da1bcf67e41..ae611926818 100644 --- a/src/plugins/coreplugin/find/searchresultwindow.h +++ b/src/plugins/coreplugin/find/searchresultwindow.h @@ -3,11 +3,10 @@ #pragma once -#include "searchresultcolor.h" -#include "searchresultitem.h" - #include +#include + #include #include #include @@ -19,9 +18,10 @@ class QFont; QT_END_NAMESPACE namespace Core { + namespace Internal { - class SearchResultWindowPrivate; - class SearchResultWidget; +class SearchResultWindowPrivate; +class SearchResultWidget; } class SearchResultWindow; @@ -31,7 +31,7 @@ class CORE_EXPORT SearchResultFilter : public QObject public: virtual QWidget *createWidget() = 0; - virtual bool matches(const SearchResultItem &item) const = 0; + virtual bool matches(const Utils::SearchResultItem &item) const = 0; signals: void filterChanged(); @@ -59,8 +59,8 @@ public: bool isInteractive() const { return !m_finishedHandler; } public slots: - void addResult(const SearchResultItem &item); - void addResults(const QList &items, AddMode mode); + void addResult(const Utils::SearchResultItem &item); + void addResults(const Utils::SearchResultItems &items, AddMode mode); void setFilter(SearchResultFilter *filter); // Takes ownership void finishSearch(bool canceled, const QString &reason = {}); void setTextToReplace(const QString &textToReplace); @@ -70,8 +70,9 @@ public slots: void popup(); signals: - void activated(const Core::SearchResultItem &item); - void replaceButtonClicked(const QString &replaceText, const QList &checkedItems, bool preserveCase); + void activated(const Utils::SearchResultItem &item); + void replaceButtonClicked(const QString &replaceText, + const Utils::SearchResultItems &checkedItems, bool preserveCase); void replaceTextChanged(const QString &replaceText); void canceled(); void paused(bool paused); @@ -125,7 +126,7 @@ public: void goToPrev() override; bool canNavigate() const override; - void setTextEditorFont(const QFont &font, const SearchResultColors &colors); + void setTextEditorFont(const QFont &font, const Utils::SearchResultColors &colors); void setTabWidth(int width); void openNewSearchPanel(); diff --git a/src/plugins/coreplugin/foldernavigationwidget.cpp b/src/plugins/coreplugin/foldernavigationwidget.cpp index fc7b86ab177..7460f427b5c 100644 --- a/src/plugins/coreplugin/foldernavigationwidget.cpp +++ b/src/plugins/coreplugin/foldernavigationwidget.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -818,7 +819,7 @@ Core::NavigationView FolderNavigationWidgetFactory::createWidget() filter->setIcon(Utils::Icons::FILTER.icon()); filter->setToolTip(Tr::tr("Options")); filter->setPopupMode(QToolButton::InstantPopup); - filter->setProperty("noArrow", true); + filter->setProperty(StyleHelper::C_NO_ARROW, true); auto filterMenu = new QMenu(filter); filterMenu->addAction(fnw->m_filterHiddenFilesAction); filterMenu->addAction(fnw->m_showBreadCrumbsAction); diff --git a/src/plugins/coreplugin/generalsettings.cpp b/src/plugins/coreplugin/generalsettings.cpp index ae5139b95c8..19b66e64497 100644 --- a/src/plugins/coreplugin/generalsettings.cpp +++ b/src/plugins/coreplugin/generalsettings.cpp @@ -9,6 +9,8 @@ #include +#include + #include #include #include @@ -17,6 +19,7 @@ #include #include +#include #include #include #include @@ -38,6 +41,7 @@ namespace Internal { const char settingsKeyDPI[] = "Core/EnableHighDpiScaling"; const char settingsKeyShortcutsInContextMenu[] = "General/ShowShortcutsInContextMenu"; const char settingsKeyCodecForLocale[] = "General/OverrideCodecForLocale"; +const char settingsKeyToolbarStyle[] = "General/ToolbarStyle"; class GeneralSettingsWidget final : public IOptionsPageWidget { @@ -57,6 +61,7 @@ public: void fillCodecBox() const; static QByteArray codecForLocale(); static void setCodecForLocale(const QByteArray&); + void fillToolbarSyleBox() const; GeneralSettings *q; QComboBox *m_languageBox; @@ -65,6 +70,7 @@ public: QtColorButton *m_colorButton; ThemeChooser *m_themeChooser; QPushButton *m_resetWarningsButton; + QComboBox *m_toolbarStyleBox; }; GeneralSettingsWidget::GeneralSettingsWidget(GeneralSettings *q) @@ -75,6 +81,7 @@ GeneralSettingsWidget::GeneralSettingsWidget(GeneralSettings *q) , m_colorButton(new QtColorButton) , m_themeChooser(new ThemeChooser) , m_resetWarningsButton(new QPushButton) + , m_toolbarStyleBox(new QComboBox) { m_languageBox->setObjectName("languageBox"); m_languageBox->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); @@ -92,6 +99,8 @@ GeneralSettingsWidget::GeneralSettingsWidget(GeneralSettings *q) "Show Again\" (for example, missing highlighter).", nullptr)); + m_toolbarStyleBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); + auto resetColorButton = new QPushButton(Tr::tr("Reset")); resetColorButton->setToolTip(Tr::tr("Reset to default.", "Color")); @@ -114,12 +123,14 @@ GeneralSettingsWidget::GeneralSettingsWidget(GeneralSettings *q) } form.addRow({empty, m_showShortcutsInContextMenus}); - form.addRow(Row{m_resetWarningsButton, st}); + form.addRow({Row{m_resetWarningsButton, st}}); form.addRow({Tr::tr("Text codec for tools:"), m_codecBox, st}); + form.addRow({Tr::tr("Toolbar Style:"), m_toolbarStyleBox, st}); Column{Group{title(Tr::tr("User Interface")), form}}.attachTo(this); fillLanguageBox(); fillCodecBox(); + fillToolbarSyleBox(); m_colorButton->setColor(StyleHelper::requestedBaseColor()); m_resetWarningsButton->setEnabled(canResetWarnings()); @@ -188,6 +199,15 @@ void GeneralSettingsWidget::apply() // Apply the new base color if accepted StyleHelper::setBaseColor(m_colorButton->color()); m_themeChooser->apply(); + if (const auto newStyle = m_toolbarStyleBox->currentData().value(); + newStyle != StyleHelper::toolbarStyle()) { + ICore::settings()->setValueWithDefault(settingsKeyToolbarStyle, int(newStyle), + int(StyleHelper::defaultToolbarStyle)); + StyleHelper::setToolbarStyle(newStyle); + QStyle *applicationStyle = QApplication::style(); + for (QWidget *widget : QApplication::allWidgets()) + applicationStyle->polish(widget); + } } bool GeneralSettings::showShortcutsInContextMenu() @@ -206,14 +226,13 @@ void GeneralSettingsWidget::resetInterfaceColor() void GeneralSettingsWidget::resetWarnings() { InfoBar::clearGloballySuppressed(); - CheckableMessageBox::resetAllDoNotAskAgainQuestions(ICore::settings()); + CheckableMessageBox::resetAllDoNotAskAgainQuestions(); m_resetWarningsButton->setEnabled(false); } bool GeneralSettingsWidget::canResetWarnings() { - return InfoBar::anyGloballySuppressed() - || CheckableMessageBox::hasSuppressedQuestions(ICore::settings()); + return InfoBar::anyGloballySuppressed() || CheckableMessageBox::hasSuppressedQuestions(); } void GeneralSettingsWidget::resetLanguage() @@ -268,6 +287,24 @@ void GeneralSettingsWidget::setCodecForLocale(const QByteArray &codec) QTextCodec::setCodecForLocale(QTextCodec::codecForName(codec)); } +StyleHelper::ToolbarStyle toolbarStylefromSettings() +{ + if (!ExtensionSystem::PluginManager::instance()) // May happen in tests + return StyleHelper::defaultToolbarStyle; + + return StyleHelper::ToolbarStyle( + ICore::settings()->value(settingsKeyToolbarStyle, + StyleHelper::defaultToolbarStyle).toInt()); +} + +void GeneralSettingsWidget::fillToolbarSyleBox() const +{ + m_toolbarStyleBox->addItem(Tr::tr("Compact"), StyleHelper::ToolbarStyleCompact); + m_toolbarStyleBox->addItem(Tr::tr("Relaxed"), StyleHelper::ToolbarStyleRelaxed); + const int curId = m_toolbarStyleBox->findData(toolbarStylefromSettings()); + m_toolbarStyleBox->setCurrentIndex(curId); +} + void GeneralSettings::setShowShortcutsInContextMenu(bool show) { ICore::settings()->setValueWithDefault(settingsKeyShortcutsInContextMenu, @@ -276,6 +313,11 @@ void GeneralSettings::setShowShortcutsInContextMenu(bool show) QCoreApplication::setAttribute(Qt::AA_DontShowShortcutsInContextMenus, !show); } +void GeneralSettings::applyToolbarStyleFromSettings() +{ + StyleHelper::setToolbarStyle(toolbarStylefromSettings()); +} + GeneralSettings::GeneralSettings() { setId(Constants::SETTINGS_ID_INTERFACE); diff --git a/src/plugins/coreplugin/generalsettings.h b/src/plugins/coreplugin/generalsettings.h index 78d1dab2702..4587443eaca 100644 --- a/src/plugins/coreplugin/generalsettings.h +++ b/src/plugins/coreplugin/generalsettings.h @@ -16,6 +16,8 @@ public: static bool showShortcutsInContextMenu(); void setShowShortcutsInContextMenu(bool show); + static void applyToolbarStyleFromSettings(); + private: friend class GeneralSettingsWidget; bool m_defaultShowShortcutsInContextMenu; diff --git a/src/plugins/coreplugin/ioutputpane.h b/src/plugins/coreplugin/ioutputpane.h index 8222ca19bc6..f3ae7fa0b82 100644 --- a/src/plugins/coreplugin/ioutputpane.h +++ b/src/plugins/coreplugin/ioutputpane.h @@ -4,6 +4,7 @@ #pragma once #include "core_global.h" +#include "icontext.h" #include #include @@ -88,6 +89,7 @@ protected: void setFilteringEnabled(bool enable); QWidget *filterWidget() const { return m_filterOutputLineEdit; } void setupContext(const char *context, QWidget *widget); + void setupContext(const Context &context, QWidget *widget); void setZoomButtonsEnabled(bool enabled); private: diff --git a/src/plugins/coreplugin/iwizardfactory.cpp b/src/plugins/coreplugin/iwizardfactory.cpp index 35713531054..caa664d7e46 100644 --- a/src/plugins/coreplugin/iwizardfactory.cpp +++ b/src/plugins/coreplugin/iwizardfactory.cpp @@ -66,6 +66,17 @@ The wizard creates a new project. */ +/*! + \enum Core::IWizardFactory::WizardFlag + + Holds information about the created projects and files. + + \value PlatformIndependent + The wizard creates projects that run on all platforms. + \value ForceCapitalLetterForFileName + The wizard uses an initial capital letter for the names of new files. +*/ + /*! \fn Core::IWizardFactory::WizardKind Core::IWizardFactory::kind() const Returns what kind of objects are created by the wizard. @@ -237,6 +248,9 @@ FilePath IWizardFactory::runPath(const FilePath &defaultPath) const The \a path argument is a suggestion for the location where files should be created. The wizard should fill this in its path selection elements as a default path. + + When \a showWizard is \c false, the wizard instance is created and set up + but not actually shown. */ Wizard *IWizardFactory::runWizard(const FilePath &path, QWidget *parent, Id platform, const QVariantMap &variables, diff --git a/src/plugins/coreplugin/locator/basefilefilter.cpp b/src/plugins/coreplugin/locator/basefilefilter.cpp deleted file mode 100644 index 9ce27cc0528..00000000000 --- a/src/plugins/coreplugin/locator/basefilefilter.cpp +++ /dev/null @@ -1,276 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "basefilefilter.h" - -#include -#include -#include -#include -#include -#include - -#include -#include - -using namespace Utils; - -namespace Core { -namespace Internal { - -class Data -{ -public: - void clear() - { - iterator.clear(); - previousResultPaths.clear(); - previousEntry.clear(); - } - - QSharedPointer iterator; - FilePaths previousResultPaths; - bool forceNewSearchList; - QString previousEntry; -}; - -class BaseFileFilterPrivate -{ -public: - Data m_data; - Data m_current; -}; - -} // Internal - -/*! - \class Core::BaseFileFilter - \inheaderfile coreplugin/locator/basefilefilter.h - \inmodule QtCreator - - \brief The BaseFileFilter class is a base class for locator filter classes. -*/ - -/*! - \class Core::BaseFileFilter::Iterator - \inmodule QtCreator - \internal -*/ - -/*! - \class Core::BaseFileFilter::ListIterator - \inmodule QtCreator - \internal -*/ - -BaseFileFilter::Iterator::~Iterator() = default; - -/*! - \internal -*/ -BaseFileFilter::BaseFileFilter() - : d(new Internal::BaseFileFilterPrivate) -{ - d->m_data.forceNewSearchList = true; - setFileIterator(new ListIterator({})); -} - -/*! - \internal -*/ -BaseFileFilter::~BaseFileFilter() -{ - delete d; -} - -/*! - \reimp -*/ -void BaseFileFilter::prepareSearch(const QString &entry) -{ - Q_UNUSED(entry) - d->m_current = d->m_data; - d->m_data.forceNewSearchList = false; -} - -ILocatorFilter::MatchLevel BaseFileFilter::matchLevelFor(const QRegularExpressionMatch &match, - const QString &matchText) -{ - const int consecutivePos = match.capturedStart(1); - if (consecutivePos == 0) - return MatchLevel::Best; - if (consecutivePos > 0) { - const QChar prevChar = matchText.at(consecutivePos - 1); - if (prevChar == '_' || prevChar == '.') - return MatchLevel::Better; - } - if (match.capturedStart() == 0) - return MatchLevel::Good; - return MatchLevel::Normal; -} - -/*! - \reimp -*/ -QList BaseFileFilter::matchesFor(QFutureInterface &future, const QString &origEntry) -{ - QList entries[int(MatchLevel::Count)]; - // If search string contains spaces, treat them as wildcard '*' and search in full path - const QString entry = QDir::fromNativeSeparators(origEntry).replace(' ', '*'); - const Link link = Link::fromString(entry, true); - - const QRegularExpression regexp = createRegExp(link.targetFilePath.toString()); - if (!regexp.isValid()) { - d->m_current.clear(); // free memory - return {}; - } - auto containsPathSeparator = [](const QString &candidate) { - return candidate.contains('/') || candidate.contains('*'); - }; - - const bool hasPathSeparator = containsPathSeparator(link.targetFilePath.toString()); - const bool containsPreviousEntry = !d->m_current.previousEntry.isEmpty() - && link.targetFilePath.toString().contains(d->m_current.previousEntry); - const bool pathSeparatorAdded = !containsPathSeparator(d->m_current.previousEntry) - && hasPathSeparator; - const bool searchInPreviousResults = !d->m_current.forceNewSearchList && containsPreviousEntry - && !pathSeparatorAdded; - if (searchInPreviousResults) - d->m_current.iterator.reset(new ListIterator(d->m_current.previousResultPaths)); - - QTC_ASSERT(d->m_current.iterator.data(), return QList()); - d->m_current.previousResultPaths.clear(); - d->m_current.previousEntry = link.targetFilePath.toString(); - d->m_current.iterator->toFront(); - bool canceled = false; - while (d->m_current.iterator->hasNext()) { - if (future.isCanceled()) { - canceled = true; - break; - } - - d->m_current.iterator->next(); - FilePath path = d->m_current.iterator->filePath(); - QString matchText = hasPathSeparator ? path.toString() : path.fileName(); - QRegularExpressionMatch match = regexp.match(matchText); - - if (match.hasMatch()) { - LocatorFilterEntry filterEntry(this, path.fileName()); - filterEntry.filePath = path; - filterEntry.extraInfo = path.shortNativePath(); - filterEntry.linkForEditor = Link(path, link.targetLine, link.targetColumn); - const MatchLevel matchLevel = matchLevelFor(match, matchText); - if (hasPathSeparator) { - match = regexp.match(filterEntry.extraInfo); - filterEntry.highlightInfo = - highlightInfo(match, LocatorFilterEntry::HighlightInfo::ExtraInfo); - } else { - filterEntry.highlightInfo = highlightInfo(match); - } - - entries[int(matchLevel)].append(filterEntry); - d->m_current.previousResultPaths.append(path); - } - } - - if (canceled) { - // we keep the old list of previous search results if this search was canceled - // so a later search without forceNewSearchList will use that previous list instead of an - // incomplete list of a canceled search - d->m_current.clear(); // free memory - } else { - d->m_current.iterator.clear(); - QMetaObject::invokeMethod(this, &BaseFileFilter::updatePreviousResultData, - Qt::QueuedConnection); - } - - for (auto &entry : entries) { - if (entry.size() < 1000) - Utils::sort(entry, LocatorFilterEntry::compareLexigraphically); - } - - return std::accumulate(std::begin(entries), std::end(entries), QList()); -} - -/*! - \reimp -*/ -void BaseFileFilter::accept(const LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const -{ - Q_UNUSED(newText) - Q_UNUSED(selectionStart) - Q_UNUSED(selectionLength) - openEditorAt(selection); -} - -void BaseFileFilter::openEditorAt(const LocatorFilterEntry &entry) -{ - if (entry.linkForEditor) { - EditorManager::openEditorAt(*entry.linkForEditor, {}, EditorManager::AllowExternalEditor); - return; - } - EditorManager::openEditor(entry.filePath, {}, EditorManager::AllowExternalEditor); -} - -/*! - Takes ownership of the \a iterator. The previously set iterator might not be deleted until - a currently running search is finished. -*/ - -void BaseFileFilter::setFileIterator(BaseFileFilter::Iterator *iterator) -{ - d->m_data.clear(); - d->m_data.forceNewSearchList = true; - d->m_data.iterator.reset(iterator); -} - -/*! - Returns the file iterator. -*/ -QSharedPointer BaseFileFilter::fileIterator() -{ - return d->m_data.iterator; -} - -void BaseFileFilter::updatePreviousResultData() -{ - if (d->m_data.forceNewSearchList) // in the meantime the iterator was reset / cache invalidated - return; // do not update with the new result list etc - d->m_data.previousEntry = d->m_current.previousEntry; - d->m_data.previousResultPaths = d->m_current.previousResultPaths; - // forceNewSearchList was already reset in prepareSearch -} - -BaseFileFilter::ListIterator::ListIterator(const FilePaths &filePaths) -{ - m_filePaths = filePaths; - toFront(); -} - -void BaseFileFilter::ListIterator::toFront() -{ - m_pathPosition = m_filePaths.constBegin() - 1; -} - -bool BaseFileFilter::ListIterator::hasNext() const -{ - QTC_ASSERT(m_pathPosition != m_filePaths.constEnd(), return false); - return m_pathPosition + 1 != m_filePaths.constEnd(); -} - -FilePath BaseFileFilter::ListIterator::next() -{ - QTC_ASSERT(m_pathPosition != m_filePaths.constEnd(), return {}); - ++m_pathPosition; - QTC_ASSERT(m_pathPosition != m_filePaths.constEnd(), return {}); - return *m_pathPosition; -} - -FilePath BaseFileFilter::ListIterator::filePath() const -{ - QTC_ASSERT(m_pathPosition != m_filePaths.constEnd(), return {}); - return *m_pathPosition; -} - -} // Core diff --git a/src/plugins/coreplugin/locator/basefilefilter.h b/src/plugins/coreplugin/locator/basefilefilter.h deleted file mode 100644 index 710cd21edf4..00000000000 --- a/src/plugins/coreplugin/locator/basefilefilter.h +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "ilocatorfilter.h" - -#include - -#include - -namespace Core { - -namespace Internal { class BaseFileFilterPrivate; } - -class CORE_EXPORT BaseFileFilter : public ILocatorFilter -{ - Q_OBJECT - -public: - class CORE_EXPORT Iterator { - public: - virtual ~Iterator(); - virtual void toFront() = 0; - virtual bool hasNext() const = 0; - virtual Utils::FilePath next() = 0; - virtual Utils::FilePath filePath() const = 0; - }; - - class CORE_EXPORT ListIterator final : public Iterator { - public: - ListIterator(const Utils::FilePaths &filePaths); - - void toFront() override; - bool hasNext() const override; - Utils::FilePath next() override; - Utils::FilePath filePath() const override; - - private: - Utils::FilePaths m_filePaths; - Utils::FilePaths::const_iterator m_pathPosition; - }; - - BaseFileFilter(); - ~BaseFileFilter() override; - void prepareSearch(const QString &entry) override; - QList matchesFor(QFutureInterface &future, - const QString &entry) override; - void accept(const LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const override; - static void openEditorAt(const LocatorFilterEntry &entry); - -protected: - void setFileIterator(Iterator *iterator); - QSharedPointer fileIterator(); - -private: - static MatchLevel matchLevelFor(const QRegularExpressionMatch &match, - const QString &matchText); - void updatePreviousResultData(); - - Internal::BaseFileFilterPrivate *d = nullptr; -}; - -} // namespace Core diff --git a/src/plugins/coreplugin/locator/commandlocator.cpp b/src/plugins/coreplugin/locator/commandlocator.cpp index de2989bc17c..e960b4df85d 100644 --- a/src/plugins/coreplugin/locator/commandlocator.cpp +++ b/src/plugins/coreplugin/locator/commandlocator.cpp @@ -5,7 +5,6 @@ #include -#include #include #include @@ -14,97 +13,58 @@ using namespace Utils; namespace Core { -struct CommandLocatorPrivate -{ - QList commands; - QList> commandsData; -}; - -/*! - \class Core::CommandLocator - \inmodule QtCreator - \internal -*/ - -CommandLocator::CommandLocator(Id id, - const QString &displayName, - const QString &shortCutString, - QObject *parent) : - ILocatorFilter(parent), - d(new CommandLocatorPrivate) +CommandLocator::CommandLocator(Id id, const QString &displayName, const QString &shortCutString, + QObject *parent) + : ILocatorFilter(parent) { setId(id); setDisplayName(displayName); setDefaultShortcutString(shortCutString); } -CommandLocator::~CommandLocator() +LocatorMatcherTasks CommandLocator::matchers() { - delete d; -} + using namespace Tasking; -void CommandLocator::appendCommand(Command *cmd) -{ - d->commands.push_back(cmd); -} + TreeStorage storage; -void CommandLocator::prepareSearch(const QString &entry) -{ - Q_UNUSED(entry) - d->commandsData = {}; - const int count = d->commands.size(); - // Get active, enabled actions matching text, store in list. - // Reference via index in extraInfo. - for (int i = 0; i < count; ++i) { - Command *command = d->commands.at(i); - if (!command->isActive()) - continue; - QAction *action = command->action(); - if (action && action->isEnabled()) - d->commandsData.append({i, action->text()}); - } -} + const auto onSetup = [storage, commands = m_commands] { + const QString input = storage->input(); + const Qt::CaseSensitivity inputCaseSensitivity = caseSensitivity(input); + LocatorFilterEntries goodEntries; + LocatorFilterEntries betterEntries; + for (Command *command : commands) { + if (!command->isActive()) + continue; -QList CommandLocator::matchesFor(QFutureInterface &future, const QString &entry) -{ - QList goodEntries; - QList betterEntries; - const Qt::CaseSensitivity entryCaseSensitivity = caseSensitivity(entry); - for (const auto &pair : std::as_const(d->commandsData)) { - if (future.isCanceled()) - break; + QAction *action = command->action(); + if (!action || !action->isEnabled()) + continue; - const QString text = Utils::stripAccelerator(pair.second); - const int index = text.indexOf(entry, 0, entryCaseSensitivity); - if (index >= 0) { - LocatorFilterEntry filterEntry(this, text, QVariant(pair.first)); - filterEntry.highlightInfo = {index, int(entry.length())}; - - if (index == 0) - betterEntries.append(filterEntry); - else - goodEntries.append(filterEntry); + const QString text = Utils::stripAccelerator(action->text()); + const int index = text.indexOf(input, 0, inputCaseSensitivity); + if (index >= 0) { + LocatorFilterEntry entry; + entry.displayName = text; + entry.acceptor = [actionPointer = QPointer(action)] { + if (actionPointer) { + QMetaObject::invokeMethod(actionPointer, [actionPointer] { + if (actionPointer && actionPointer->isEnabled()) + actionPointer->trigger(); + }, Qt::QueuedConnection); + } + return AcceptResult(); + }; + entry.highlightInfo = {index, int(input.length())}; + if (index == 0) + betterEntries.append(entry); + else + goodEntries.append(entry); + } } - } - betterEntries.append(goodEntries); - return betterEntries; -} - -void CommandLocator::accept(const LocatorFilterEntry &entry, - QString *newText, int *selectionStart, int *selectionLength) const -{ - Q_UNUSED(newText) - Q_UNUSED(selectionStart) - Q_UNUSED(selectionLength) - // Retrieve action via index. - const int index = entry.internalData.toInt(); - QTC_ASSERT(index >= 0 && index < d->commands.size(), return); - QAction *action = d->commands.at(index)->action(); - // avoid nested stack trace and blocking locator by delayed triggering - QMetaObject::invokeMethod(action, [action] { - if (action->isEnabled()) - action->trigger(); - }, Qt::QueuedConnection); + storage->reportOutput(betterEntries + goodEntries); + }; + return {{Sync(onSetup), storage}}; } } // namespace Core diff --git a/src/plugins/coreplugin/locator/commandlocator.h b/src/plugins/coreplugin/locator/commandlocator.h index 8199661bed6..8e7d1ead035 100644 --- a/src/plugins/coreplugin/locator/commandlocator.h +++ b/src/plugins/coreplugin/locator/commandlocator.h @@ -10,27 +10,18 @@ namespace Core { /* Command locators: Provides completion for a set of * Core::Command's by sub-string of their action's text. */ class Command; -struct CommandLocatorPrivate; class CORE_EXPORT CommandLocator : public ILocatorFilter { - Q_OBJECT - public: - CommandLocator(Utils::Id id, const QString &displayName, - const QString &shortCutString, QObject *parent = nullptr); - ~CommandLocator() override; - - void appendCommand(Command *cmd); - - void prepareSearch(const QString &entry) override; - QList matchesFor(QFutureInterface &future, - const QString &entry) override; - void accept(const LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const override; + CommandLocator(Utils::Id id, const QString &displayName, const QString &shortCutString, + QObject *parent = nullptr); + void appendCommand(Command *cmd) { m_commands.push_back(cmd); } private: - CommandLocatorPrivate *d = nullptr; + LocatorMatcherTasks matchers() final; + + QList m_commands; }; } // namespace Core diff --git a/src/plugins/coreplugin/locator/directoryfilter.cpp b/src/plugins/coreplugin/locator/directoryfilter.cpp index 19a9f098e62..42cf51addf7 100644 --- a/src/plugins/coreplugin/locator/directoryfilter.cpp +++ b/src/plugins/coreplugin/locator/directoryfilter.cpp @@ -7,12 +7,12 @@ #include "../coreplugintr.h" #include +#include #include #include #include #include -#include #include #include #include @@ -46,6 +46,28 @@ static QString defaultDisplayName() return Tr::tr("Generic Directory Filter"); } +static void refresh(QPromise &promise, const FilePaths &directories, + const QStringList &filters, const QStringList &exclusionFilters, + const QString &displayName) +{ + SubDirFileIterator subDirIterator(directories, filters, exclusionFilters); + promise.setProgressRange(0, subDirIterator.maxProgress()); + FilePaths files; + const auto end = subDirIterator.end(); + for (auto it = subDirIterator.begin(); it != end; ++it) { + if (promise.isCanceled()) { + promise.setProgressValueAndText(subDirIterator.currentProgress(), + Tr::tr("%1 filter update: canceled").arg(displayName)); + return; + } + files << (*it).filePath; + promise.setProgressValueAndText(subDirIterator.currentProgress(), + Tr::tr("%1 filter update: %n files", nullptr, files.size()).arg(displayName)); + } + promise.setProgressValue(subDirIterator.maxProgress()); + promise.addResult(files); +} + DirectoryFilter::DirectoryFilter(Id id) : m_filters(kFiltersDefault) , m_exclusionFilters(kExclusionFiltersDefault) @@ -53,15 +75,34 @@ DirectoryFilter::DirectoryFilter(Id id) setId(id); setDefaultIncludedByDefault(true); setDisplayName(defaultDisplayName()); - setDescription(Tr::tr("Matches all files from a custom set of directories. Append \"+\" " + setDescription(Tr::tr("Locates files from a custom set of directories. Append \"+\" " "or \":\" to jump to the given line number. Append another " "\"+\" or \":\" to jump to the column number as well.")); + + using namespace Tasking; + const auto groupSetup = [this] { + if (!m_directories.isEmpty()) + return TaskAction::Continue; // Async task will run + m_cache.setFilePaths({}); + return TaskAction::StopWithDone; // Group stops, skips async task + }; + const auto asyncSetup = [this](Async &async) { + async.setConcurrentCallData(&refresh, m_directories, m_filters, m_exclusionFilters, + displayName()); + }; + const auto asyncDone = [this](const Async &async) { + if (async.isResultAvailable()) + m_cache.setFilePaths(async.result()); + }; + const Group root { + onGroupSetup(groupSetup), + AsyncTask(asyncSetup, asyncDone) + }; + setRefreshRecipe(root); } void DirectoryFilter::saveState(QJsonObject &object) const { - QMutexLocker locker(&m_lock); // m_files is modified in other thread - if (displayName() != defaultDisplayName()) object.insert(kDisplayNameKey, displayName()); if (!m_directories.isEmpty()) { @@ -71,10 +112,11 @@ void DirectoryFilter::saveState(QJsonObject &object) const } if (m_filters != kFiltersDefault) object.insert(kFiltersKey, QJsonArray::fromStringList(m_filters)); - if (!m_files.isEmpty()) - object.insert(kFilesKey, - QJsonArray::fromStringList( - Utils::transform(m_files, &Utils::FilePath::toString))); + const std::optional files = m_cache.filePaths(); + if (files) { + object.insert(kFilesKey, QJsonArray::fromStringList( + Utils::transform(*files, &FilePath::toString))); + } if (m_exclusionFilters != kExclusionFiltersDefault) object.insert(kExclusionFiltersKey, QJsonArray::fromStringList(m_exclusionFilters)); } @@ -92,12 +134,14 @@ static FilePaths toFilePaths(const QJsonArray &array) void DirectoryFilter::restoreState(const QJsonObject &object) { - QMutexLocker locker(&m_lock); setDisplayName(object.value(kDisplayNameKey).toString(defaultDisplayName())); m_directories = toFilePaths(object.value(kDirectoriesKey).toArray()); m_filters = toStringList( object.value(kFiltersKey).toArray(QJsonArray::fromStringList(kFiltersDefault))); - m_files = FileUtils::toFilePathList(toStringList(object.value(kFilesKey).toArray())); + if (object.contains(kFilesKey)) { + m_cache.setFilePaths(FileUtils::toFilePathList( + toStringList(object.value(kFilesKey).toArray()))); + } m_exclusionFilters = toStringList( object.value(kExclusionFiltersKey) .toArray(QJsonArray::fromStringList(kExclusionFiltersDefault))); @@ -107,8 +151,6 @@ void DirectoryFilter::restoreState(const QByteArray &state) { if (isOldSetting(state)) { // TODO read old settings, remove some time after Qt Creator 4.15 - QMutexLocker locker(&m_lock); - QString name; QStringList directories; QString shortcut; @@ -122,7 +164,7 @@ void DirectoryFilter::restoreState(const QByteArray &state) in >> shortcut; in >> defaultFilter; in >> files; - m_files = FileUtils::toFilePathList(files); + m_cache.setFilePaths(FileUtils::toFilePathList(files)); if (!in.atEnd()) // Qt Creator 4.3 and later in >> m_exclusionFilters; else @@ -136,12 +178,9 @@ void DirectoryFilter::restoreState(const QByteArray &state) setDisplayName(name); setShortcutString(shortcut); setIncludedByDefault(defaultFilter); - - locker.unlock(); } else { ILocatorFilter::restoreState(state); } - updateFileIterator(); } class DirectoryFilterOptions : public QDialog @@ -263,8 +302,6 @@ bool DirectoryFilter::openConfigDialog(QWidget *parent, bool &needsRefresh) &DirectoryFilter::updateOptionButtons, Qt::DirectConnection); m_dialog->directoryList->clear(); - // Note: assuming we only change m_directories in the Gui thread, - // we don't need to protect it here with mutex m_dialog->directoryList->addItems(Utils::transform(m_directories, &FilePath::toString)); m_dialog->nameLabel->setVisible(m_isCustomFilter); m_dialog->nameEdit->setVisible(m_isCustomFilter); @@ -276,14 +313,10 @@ bool DirectoryFilter::openConfigDialog(QWidget *parent, bool &needsRefresh) m_dialog->filePatternLabel->setText(Utils::msgFilePatternLabel()); m_dialog->filePatternLabel->setBuddy(m_dialog->filePattern); m_dialog->filePattern->setToolTip(Utils::msgFilePatternToolTip()); - // Note: assuming we only change m_filters in the Gui thread, - // we don't need to protect it here with mutex m_dialog->filePattern->setText(Utils::transform(m_filters, &QDir::toNativeSeparators).join(',')); m_dialog->exclusionPatternLabel->setText(Utils::msgExclusionPatternLabel()); m_dialog->exclusionPatternLabel->setBuddy(m_dialog->exclusionPattern); - m_dialog->exclusionPattern->setToolTip(Utils::msgFilePatternToolTip()); - // Note: assuming we only change m_exclusionFilters in the Gui thread, - // we don't need to protect it here with mutex + m_dialog->exclusionPattern->setToolTip(Utils::msgFilePatternToolTip(InclusionType::Excluded)); m_dialog->exclusionPattern->setText( Utils::transform(m_exclusionFilters, &QDir::toNativeSeparators).join(',')); m_dialog->shortcutEdit->setText(shortcutString()); @@ -291,7 +324,6 @@ bool DirectoryFilter::openConfigDialog(QWidget *parent, bool &needsRefresh) updateOptionButtons(); dialog.adjustSize(); if (dialog.exec() == QDialog::Accepted) { - QMutexLocker locker(&m_lock); bool directoriesChanged = false; const FilePaths oldDirectories = m_directories; const QStringList oldFilters = m_filters; @@ -351,55 +383,6 @@ void DirectoryFilter::updateOptionButtons() m_dialog->removeButton->setEnabled(haveSelectedItem); } -void DirectoryFilter::updateFileIterator() -{ - QMutexLocker locker(&m_lock); - setFileIterator(new BaseFileFilter::ListIterator(m_files)); -} - -void DirectoryFilter::refresh(QFutureInterface &future) -{ - FilePaths directories; - QStringList filters, exclusionFilters; - { - QMutexLocker locker(&m_lock); - if (m_directories.isEmpty()) { - m_files.clear(); - QMetaObject::invokeMethod(this, &DirectoryFilter::updateFileIterator, - Qt::QueuedConnection); - future.setProgressRange(0, 1); - future.setProgressValueAndText(1, Tr::tr("%1 filter update: 0 files").arg(displayName())); - return; - } - directories = m_directories; - filters = m_filters; - exclusionFilters = m_exclusionFilters; - } - Utils::SubDirFileIterator subDirIterator(directories, filters, exclusionFilters); - future.setProgressRange(0, subDirIterator.maxProgress()); - Utils::FilePaths filesFound; - auto end = subDirIterator.end(); - for (auto it = subDirIterator.begin(); it != end; ++it) { - if (future.isCanceled()) - break; - filesFound << (*it).filePath; - if (future.isProgressUpdateNeeded() - || future.progressValue() == 0 /*workaround for regression in Qt*/) { - future.setProgressValueAndText(subDirIterator.currentProgress(), - Tr::tr("%1 filter update: %n files", nullptr, filesFound.size()).arg(displayName())); - } - } - - if (!future.isCanceled()) { - QMutexLocker locker(&m_lock); - m_files = filesFound; - QMetaObject::invokeMethod(this, &DirectoryFilter::updateFileIterator, Qt::QueuedConnection); - future.setProgressValue(subDirIterator.maxProgress()); - } else { - future.setProgressValueAndText(subDirIterator.currentProgress(), Tr::tr("%1 filter update: canceled").arg(displayName())); - } -} - void DirectoryFilter::setIsCustomFilter(bool value) { m_isCustomFilter = value; @@ -409,10 +392,7 @@ void DirectoryFilter::setDirectories(const FilePaths &directories) { if (directories == m_directories) return; - { - QMutexLocker locker(&m_lock); - m_directories = directories; - } + m_directories = directories; Internal::Locator::instance()->refresh({this}); } @@ -428,20 +408,13 @@ void DirectoryFilter::removeDirectory(const FilePath &directory) setDirectories(directories); } -FilePaths DirectoryFilter::directories() const -{ - return m_directories; -} - void DirectoryFilter::setFilters(const QStringList &filters) { - QMutexLocker locker(&m_lock); m_filters = filters; } void DirectoryFilter::setExclusionFilters(const QStringList &exclusionFilters) { - QMutexLocker locker(&m_lock); m_exclusionFilters = exclusionFilters; } diff --git a/src/plugins/coreplugin/locator/directoryfilter.h b/src/plugins/coreplugin/locator/directoryfilter.h index 7fb88ce112d..485377d2a15 100644 --- a/src/plugins/coreplugin/locator/directoryfilter.h +++ b/src/plugins/coreplugin/locator/directoryfilter.h @@ -3,45 +3,36 @@ #pragma once -#include "basefilefilter.h" +#include "ilocatorfilter.h" #include -#include -#include -#include -#include - namespace Core { -class CORE_EXPORT DirectoryFilter : public BaseFileFilter +class CORE_EXPORT DirectoryFilter : public ILocatorFilter { - Q_OBJECT - public: DirectoryFilter(Utils::Id id); void restoreState(const QByteArray &state) override; bool openConfigDialog(QWidget *parent, bool &needsRefresh) override; - void refresh(QFutureInterface &future) override; +protected: void setIsCustomFilter(bool value); - void setDirectories(const Utils::FilePaths &directories); void addDirectory(const Utils::FilePath &directory); void removeDirectory(const Utils::FilePath &directory); - Utils::FilePaths directories() const; void setFilters(const QStringList &filters); void setExclusionFilters(const QStringList &exclusionFilters); -protected: void saveState(QJsonObject &object) const override; void restoreState(const QJsonObject &object) override; private: + LocatorMatcherTasks matchers() final { return {m_cache.matcher()}; } + void setDirectories(const Utils::FilePaths &directories); void handleAddDirectory(); void handleEditDirectory(); void handleRemoveDirectory(); void updateOptionButtons(); - void updateFileIterator(); Utils::FilePaths m_directories; QStringList m_filters; @@ -49,9 +40,8 @@ private: // Our config dialog, uses in addDirectory and editDirectory // to give their dialogs the right parent class DirectoryFilterOptions *m_dialog = nullptr; - mutable QMutex m_lock; - Utils::FilePaths m_files; bool m_isCustomFilter = true; + LocatorFileCache m_cache; }; } // namespace Core diff --git a/src/plugins/coreplugin/locator/executefilter.cpp b/src/plugins/coreplugin/locator/executefilter.cpp index 6be23fff3f5..2a405c369ea 100644 --- a/src/plugins/coreplugin/locator/executefilter.cpp +++ b/src/plugins/coreplugin/locator/executefilter.cpp @@ -11,8 +11,8 @@ #include #include #include +#include #include -#include #include #include @@ -31,7 +31,6 @@ ExecuteFilter::ExecuteFilter() "environment variable if needed. Note that the command is run directly, not in a shell.")); setDefaultShortcutString("!"); setPriority(High); - setDefaultIncludedByDefault(false); } ExecuteFilter::~ExecuteFilter() @@ -39,77 +38,78 @@ ExecuteFilter::~ExecuteFilter() removeProcess(); } -QList ExecuteFilter::matchesFor(QFutureInterface &future, - const QString &entry) +LocatorMatcherTasks ExecuteFilter::matchers() { - QList value; - if (!entry.isEmpty()) // avoid empty entry - value.append(LocatorFilterEntry(this, entry)); - QList others; - const Qt::CaseSensitivity entryCaseSensitivity = caseSensitivity(entry); - for (const QString &cmd : std::as_const(m_commandHistory)) { - if (future.isCanceled()) - break; - if (cmd == entry) // avoid repeated entry - continue; - LocatorFilterEntry filterEntry(this, cmd); - const int index = cmd.indexOf(entry, 0, entryCaseSensitivity); - if (index >= 0) { - filterEntry.highlightInfo = {index, int(entry.length())}; - value.append(filterEntry); - } else { - others.append(filterEntry); + using namespace Tasking; + + TreeStorage storage; + + const auto onSetup = [=] { + const QString input = storage->input(); + LocatorFilterEntries entries; + if (!input.isEmpty()) { // avoid empty entry + LocatorFilterEntry entry; + entry.displayName = input; + entry.acceptor = [this, input] { acceptCommand(input); return AcceptResult(); }; + entries.append(entry); } - } - value.append(others); - return value; + LocatorFilterEntries others; + const Qt::CaseSensitivity entryCaseSensitivity = caseSensitivity(input); + for (const QString &cmd : std::as_const(m_commandHistory)) { + if (cmd == input) // avoid repeated entry + continue; + LocatorFilterEntry entry; + entry.displayName = cmd; + entry.acceptor = [this, cmd] { acceptCommand(cmd); return AcceptResult(); }; + const int index = cmd.indexOf(input, 0, entryCaseSensitivity); + if (index >= 0) { + entry.highlightInfo = {index, int(input.length())}; + entries.append(entry); + } else { + others.append(entry); + } + } + storage->reportOutput(entries + others); + }; + return {{Sync(onSetup), storage}}; } -void ExecuteFilter::accept(const LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const +void ExecuteFilter::acceptCommand(const QString &cmd) { - Q_UNUSED(newText) - Q_UNUSED(selectionStart) - Q_UNUSED(selectionLength) - auto p = const_cast(this); - - const QString value = selection.displayName.trimmed(); - - const int index = m_commandHistory.indexOf(value); + const QString displayName = cmd.trimmed(); + const int index = m_commandHistory.indexOf(displayName); if (index != -1 && index != 0) - p->m_commandHistory.removeAt(index); + m_commandHistory.removeAt(index); if (index != 0) - p->m_commandHistory.prepend(value); + m_commandHistory.prepend(displayName); static const int maxHistory = 100; - while (p->m_commandHistory.size() > maxHistory) - p->m_commandHistory.removeLast(); + while (m_commandHistory.size() > maxHistory) + m_commandHistory.removeLast(); bool found; - QString workingDirectory = Utils::globalMacroExpander()->value("CurrentDocument:Path", &found); + QString workingDirectory = globalMacroExpander()->value("CurrentDocument:Path", &found); if (!found || workingDirectory.isEmpty()) - workingDirectory = Utils::globalMacroExpander()->value("CurrentDocument:Project:Path", &found); - - ExecuteData d; - d.command = CommandLine::fromUserInput(value, Utils::globalMacroExpander()); - d.workingDirectory = FilePath::fromString(workingDirectory); + workingDirectory = globalMacroExpander()->value("CurrentDocument:Project:Path", &found); + const ExecuteData data{CommandLine::fromUserInput(displayName, globalMacroExpander()), + FilePath::fromString(workingDirectory)}; if (m_process) { - const QString info(Tr::tr("Previous command is still running (\"%1\").\nDo you want to kill it?") - .arg(p->headCommand())); - int r = QMessageBox::question(ICore::dialogParent(), Tr::tr("Kill Previous Process?"), info, - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, - QMessageBox::Yes); - if (r == QMessageBox::Cancel) + const QString info(Tr::tr("Previous command is still running (\"%1\").\n" + "Do you want to kill it?").arg(headCommand())); + const auto result = QMessageBox::question(ICore::dialogParent(), + Tr::tr("Kill Previous Process?"), info, + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, + QMessageBox::Yes); + if (result == QMessageBox::Cancel) return; - if (r == QMessageBox::No) { - p->m_taskQueue.enqueue(d); + if (result == QMessageBox::No) { + m_taskQueue.append(data); return; } - p->removeProcess(); + removeProcess(); } - - p->m_taskQueue.enqueue(d); - p->runHeadCommand(); + m_taskQueue.append(data); + runHeadCommand(); } void ExecuteFilter::done() @@ -122,7 +122,7 @@ void ExecuteFilter::done() runHeadCommand(); } -void ExecuteFilter::readStandardOutput() +void ExecuteFilter::readStdOutput() { QTC_ASSERT(m_process, return); const QByteArray data = m_process->readAllRawStandardOutput(); @@ -130,7 +130,7 @@ void ExecuteFilter::readStandardOutput() QTextCodec::codecForLocale()->toUnicode(data.constData(), data.size(), &m_stdoutState)); } -void ExecuteFilter::readStandardError() +void ExecuteFilter::readStdError() { QTC_ASSERT(m_process, return); const QByteArray data = m_process->readAllRawStandardError(); @@ -141,11 +141,11 @@ void ExecuteFilter::readStandardError() void ExecuteFilter::runHeadCommand() { if (!m_taskQueue.isEmpty()) { - const ExecuteData &d = m_taskQueue.head(); + const ExecuteData &d = m_taskQueue.first(); if (d.command.executable().isEmpty()) { - MessageManager::writeDisrupting( - Tr::tr("Could not find executable for \"%1\".").arg(d.command.executable().toUserOutput())); - m_taskQueue.dequeue(); + MessageManager::writeDisrupting(Tr::tr("Could not find executable for \"%1\".") + .arg(d.command.executable().toUserOutput())); + m_taskQueue.removeFirst(); runHeadCommand(); return; } @@ -163,11 +163,11 @@ void ExecuteFilter::createProcess() if (m_process) return; - m_process = new Utils::QtcProcess; - m_process->setEnvironment(Utils::Environment::systemEnvironment()); - connect(m_process, &QtcProcess::done, this, &ExecuteFilter::done); - connect(m_process, &QtcProcess::readyReadStandardOutput, this, &ExecuteFilter::readStandardOutput); - connect(m_process, &QtcProcess::readyReadStandardError, this, &ExecuteFilter::readStandardError); + m_process = new Process; + m_process->setEnvironment(Environment::systemEnvironment()); + connect(m_process, &Process::done, this, &ExecuteFilter::done); + connect(m_process, &Process::readyReadStandardOutput, this, &ExecuteFilter::readStdOutput); + connect(m_process, &Process::readyReadStandardError, this, &ExecuteFilter::readStdError); } void ExecuteFilter::removeProcess() @@ -175,7 +175,7 @@ void ExecuteFilter::removeProcess() if (!m_process) return; - m_taskQueue.dequeue(); + m_taskQueue.removeFirst(); m_process->deleteLater(); m_process = nullptr; } @@ -198,8 +198,8 @@ QString ExecuteFilter::headCommand() const { if (m_taskQueue.isEmpty()) return QString(); - const ExecuteData &data = m_taskQueue.head(); + const ExecuteData &data = m_taskQueue.first(); return data.command.toUserOutput(); } -} // Core::Internal +} // namespace Core::Internal diff --git a/src/plugins/coreplugin/locator/executefilter.h b/src/plugins/coreplugin/locator/executefilter.h index 0d499e88606..2e51d320d3c 100644 --- a/src/plugins/coreplugin/locator/executefilter.h +++ b/src/plugins/coreplugin/locator/executefilter.h @@ -7,19 +7,15 @@ #include -#include #include #include -namespace Utils { class QtcProcess; } +namespace Utils { class Process; } -namespace Core { -namespace Internal { +namespace Core::Internal { class ExecuteFilter : public Core::ILocatorFilter { - Q_OBJECT - struct ExecuteData { Utils::CommandLine command; @@ -29,15 +25,13 @@ class ExecuteFilter : public Core::ILocatorFilter public: ExecuteFilter(); ~ExecuteFilter() override; - QList matchesFor(QFutureInterface &future, - const QString &entry) override; - void accept(const LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const override; private: + LocatorMatcherTasks matchers() final; + void acceptCommand(const QString &cmd); void done(); - void readStandardOutput(); - void readStandardError(); + void readStdOutput(); + void readStdError(); void runHeadCommand(); void createProcess(); @@ -48,12 +42,11 @@ private: QString headCommand() const; - QQueue m_taskQueue; + QList m_taskQueue; QStringList m_commandHistory; - Utils::QtcProcess *m_process = nullptr; + Utils::Process *m_process = nullptr; QTextCodec::ConverterState m_stdoutState; QTextCodec::ConverterState m_stderrState; }; -} // namespace Internal -} // namespace Core +} // namespace Core::Internal diff --git a/src/plugins/coreplugin/locator/externaltoolsfilter.cpp b/src/plugins/coreplugin/locator/externaltoolsfilter.cpp index 96ce58e20b3..36fc78cba9b 100644 --- a/src/plugins/coreplugin/locator/externaltoolsfilter.cpp +++ b/src/plugins/coreplugin/locator/externaltoolsfilter.cpp @@ -4,6 +4,7 @@ #include "externaltoolsfilter.h" #include "../coreconstants.h" +#include "../coreplugin.h" #include "../coreplugintr.h" #include "../externaltool.h" #include "../externaltoolmanager.h" @@ -24,64 +25,62 @@ ExternalToolsFilter::ExternalToolsFilter() setPriority(Medium); } -QList ExternalToolsFilter::matchesFor(QFutureInterface &, - const QString &) +LocatorMatcherTasks ExternalToolsFilter::matchers() { - return m_results; -} + using namespace Tasking; -void ExternalToolsFilter::accept(const LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const -{ - Q_UNUSED(newText) - Q_UNUSED(selectionStart) - Q_UNUSED(selectionLength) + TreeStorage storage; - if (!selection.internalData.isValid()) { - ICore::showOptionsDialog(Constants::SETTINGS_ID_TOOLS); - return; - } + const auto onSetup = [storage] { + const QString input = storage->input(); - auto tool = selection.internalData.value(); - QTC_ASSERT(tool, return); + LocatorFilterEntries bestEntries; + LocatorFilterEntries betterEntries; + LocatorFilterEntries goodEntries; + const Qt::CaseSensitivity entryCaseSensitivity = caseSensitivity(input); + const QMap externalToolsById = ExternalToolManager::toolsById(); + for (ExternalTool *tool : externalToolsById) { + int index = tool->displayName().indexOf(input, 0, entryCaseSensitivity); + LocatorFilterEntry::HighlightInfo::DataType hDataType = LocatorFilterEntry::HighlightInfo::DisplayName; + if (index < 0) { + index = tool->description().indexOf(input, 0, entryCaseSensitivity); + hDataType = LocatorFilterEntry::HighlightInfo::ExtraInfo; + } - auto runner = new ExternalToolRunner(tool); - if (runner->hasError()) - MessageManager::writeFlashing(runner->errorString()); -} + if (index >= 0) { + LocatorFilterEntry filterEntry; + filterEntry.displayName = tool->displayName(); + filterEntry.acceptor = [tool] { + auto runner = new ExternalToolRunner(tool); + if (runner->hasError()) + MessageManager::writeFlashing(runner->errorString()); + return AcceptResult(); + }; + filterEntry.extraInfo = tool->description(); + filterEntry.highlightInfo = LocatorFilterEntry::HighlightInfo(index, input.length(), hDataType); -void ExternalToolsFilter::prepareSearch(const QString &entry) -{ - QList bestEntries; - QList betterEntries; - QList goodEntries; - const Qt::CaseSensitivity entryCaseSensitivity = caseSensitivity(entry); - const QMap externalToolsById = ExternalToolManager::toolsById(); - for (ExternalTool *tool : externalToolsById) { - int index = tool->displayName().indexOf(entry, 0, entryCaseSensitivity); - LocatorFilterEntry::HighlightInfo::DataType hDataType = LocatorFilterEntry::HighlightInfo::DisplayName; - if (index < 0) { - index = tool->description().indexOf(entry, 0, entryCaseSensitivity); - hDataType = LocatorFilterEntry::HighlightInfo::ExtraInfo; + if (filterEntry.displayName.startsWith(input, entryCaseSensitivity)) + bestEntries.append(filterEntry); + else if (filterEntry.displayName.contains(input, entryCaseSensitivity)) + betterEntries.append(filterEntry); + else + goodEntries.append(filterEntry); + } } + LocatorFilterEntry configEntry; + configEntry.displayName = "Configure External Tool..."; + configEntry.extraInfo = "Opens External Tool settings"; + configEntry.acceptor = [] { + QMetaObject::invokeMethod(CorePlugin::instance(), [] { + ICore::showOptionsDialog(Constants::SETTINGS_ID_TOOLS); + }, Qt::QueuedConnection); + return AcceptResult(); + }; - if (index >= 0) { - LocatorFilterEntry filterEntry(this, tool->displayName(), QVariant::fromValue(tool)); - filterEntry.extraInfo = tool->description(); - filterEntry.highlightInfo = LocatorFilterEntry::HighlightInfo(index, entry.length(), hDataType); - - if (filterEntry.displayName.startsWith(entry, entryCaseSensitivity)) - bestEntries.append(filterEntry); - else if (filterEntry.displayName.contains(entry, entryCaseSensitivity)) - betterEntries.append(filterEntry); - else - goodEntries.append(filterEntry); - } - } - LocatorFilterEntry configEntry(this, "Configure External Tool...", {}); - configEntry.extraInfo = "Opens External Tool settings"; - m_results = {}; - m_results << bestEntries << betterEntries << goodEntries << configEntry; + storage->reportOutput(bestEntries + betterEntries + goodEntries + + LocatorFilterEntries{configEntry}); + }; + return {{Sync(onSetup), storage}}; } } // Core::Internal diff --git a/src/plugins/coreplugin/locator/externaltoolsfilter.h b/src/plugins/coreplugin/locator/externaltoolsfilter.h index 74d5214aa38..bc8fca6e705 100644 --- a/src/plugins/coreplugin/locator/externaltoolsfilter.h +++ b/src/plugins/coreplugin/locator/externaltoolsfilter.h @@ -5,24 +5,15 @@ #include "ilocatorfilter.h" -namespace Core { -namespace Internal { +namespace Core::Internal { class ExternalToolsFilter : public ILocatorFilter { - Q_OBJECT public: ExternalToolsFilter(); - QList matchesFor(QFutureInterface &future, - const QString &entry) override; - void accept(const LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const override; - void prepareSearch(const QString &entry) override; - private: - QList m_results; + LocatorMatcherTasks matchers() final; }; -} // namespace Internal -} // namespace Core +} // namespace Core::Internal diff --git a/src/plugins/coreplugin/locator/filesystemfilter.cpp b/src/plugins/coreplugin/locator/filesystemfilter.cpp index 17c54c748dc..7c6ac13abda 100644 --- a/src/plugins/coreplugin/locator/filesystemfilter.cpp +++ b/src/plugins/coreplugin/locator/filesystemfilter.cpp @@ -3,19 +3,25 @@ #include "filesystemfilter.h" -#include "basefilefilter.h" #include "../coreplugintr.h" #include "../documentmanager.h" #include "../editormanager/editormanager.h" #include "../icore.h" #include "../vcsmanager.h" +#include "locatormanager.h" +#include + +#include +#include #include #include #include +#include #include #include +#include #include #include #include @@ -25,26 +31,84 @@ #include #include #include +#include using namespace Utils; -namespace Core { -namespace Internal { +namespace Core::Internal { -ILocatorFilter::MatchLevel FileSystemFilter::matchLevelFor(const QRegularExpressionMatch &match, - const QString &matchText) +Q_GLOBAL_STATIC(QIcon, sDeviceRootIcon); + +static const char kAlwaysCreate[] = "Locator/FileSystemFilter/AlwaysCreate"; + +static ILocatorFilter::MatchLevel matchLevelFor(const QRegularExpressionMatch &match, + const QString &matchText) { const int consecutivePos = match.capturedStart(1); if (consecutivePos == 0) - return MatchLevel::Best; + return ILocatorFilter::MatchLevel::Best; if (consecutivePos > 0) { const QChar prevChar = matchText.at(consecutivePos - 1); if (prevChar == '_' || prevChar == '.') - return MatchLevel::Better; + return ILocatorFilter::MatchLevel::Better; } if (match.capturedStart() == 0) - return MatchLevel::Good; - return MatchLevel::Normal; + return ILocatorFilter::MatchLevel::Good; + return ILocatorFilter::MatchLevel::Normal; +} + +static bool askForCreating(const QString &title, const FilePath &filePath) +{ + QMessageBox::StandardButton selected + = CheckableMessageBox::question(ICore::dialogParent(), + title, + Tr::tr("Create \"%1\"?").arg(filePath.shortNativePath()), + QString(kAlwaysCreate), + QMessageBox::Yes | QMessageBox::Cancel, + QMessageBox::Cancel, + QMessageBox::Yes, + {{QMessageBox::Yes, Tr::tr("Create")}}, + Tr::tr("Always create")); + return selected == QMessageBox::Yes; +} + +static void createAndOpenFile(const FilePath &filePath) +{ + if (!filePath.exists()) { + if (askForCreating(Tr::tr("Create File"), filePath)) { + QFile file(filePath.toFSPathString()); + file.open(QFile::WriteOnly); + file.close(); + VcsManager::promptToAdd(filePath.absolutePath(), {filePath}); + } + } + if (filePath.exists()) + EditorManager::openEditor(filePath); +} + +static bool createDirectory(const FilePath &filePath) +{ + if (!filePath.exists()) { + if (askForCreating(Tr::tr("Create Directory"), filePath)) + filePath.createDir(); + } + if (filePath.exists()) + return true; + return false; +} + +static FilePaths deviceRoots() +{ + const QString rootPath = FilePath::specialRootPath(); + const QStringList roots = QDir(rootPath).entryList(); + FilePaths devices; + for (const QString &root : roots) { + const QString prefix = rootPath + '/' + root; + devices += Utils::transform(QDir(prefix).entryList(), [prefix](const QString &s) { + return FilePath::fromString(prefix + '/' + s); + }); + } + return devices; } FileSystemFilter::FileSystemFilter() @@ -55,146 +119,199 @@ FileSystemFilter::FileSystemFilter() "path. \"~\" refers to your home directory. You have the option to create a " "file if it does not exist yet.")); setDefaultShortcutString("f"); - setDefaultIncludedByDefault(false); + *sDeviceRootIcon = qApp->style()->standardIcon(QStyle::SP_DriveHDIcon); } -void FileSystemFilter::prepareSearch(const QString &entry) +static void matches(QPromise &promise, const LocatorStorage &storage, + const QString &shortcutString, const FilePath ¤tDocumentDir, + bool includeHidden) { - Q_UNUSED(entry) - m_currentDocumentDirectory = DocumentManager::fileDialogInitialDirectory().toString(); - m_currentIncludeHidden = m_includeHidden; -} + const QString input = storage.input(); + LocatorFilterEntries entries[int(ILocatorFilter::MatchLevel::Count)]; -QList FileSystemFilter::matchesFor(QFutureInterface &future, - const QString &entry) -{ - QList entries[int(MatchLevel::Count)]; + const Environment env = Environment::systemEnvironment(); + const QString expandedEntry = env.expandVariables(input); + const auto expandedEntryPath = FilePath::fromUserInput(expandedEntry); + const FilePath absoluteEntryPath = currentDocumentDir.isEmpty() + ? expandedEntryPath + : currentDocumentDir.resolvePath(expandedEntryPath); + // The case of e.g. "ssh://", "ssh://*p", etc + const bool isPartOfDeviceRoot = expandedEntryPath.needsDevice() + && expandedEntryPath.path().isEmpty(); - Environment env = Environment::systemEnvironment(); - const QString expandedEntry = env.expandVariables(entry); + // Consider the entered path a directory if it ends with slash/backslash. + // If it is a dir but doesn't end with a backslash, we want to still show all (other) matching + // items from the same parent directory. + // Unfortunately fromUserInput removes slash/backslash at the end, so manually check the original. + const bool isDir = expandedEntry.isEmpty() || expandedEntry.endsWith('/') + || expandedEntry.endsWith('\\'); + const FilePath directory = isDir ? absoluteEntryPath : absoluteEntryPath.parentDir(); + const QString entryFileName = isDir ? QString() : absoluteEntryPath.fileName(); - const QFileInfo entryInfo(expandedEntry); - const QString entryFileName = entryInfo.fileName(); - QString directory = entryInfo.path(); - if (entryInfo.isRelative()) { - if (entryInfo.filePath().startsWith("~/")) - directory.replace(0, 1, QDir::homePath()); - else if (!m_currentDocumentDirectory.isEmpty()) - directory.prepend(m_currentDocumentDirectory + "/"); - } - const QDir dirInfo(directory); - QDir::Filters dirFilter = QDir::Dirs|QDir::Drives|QDir::NoDot|QDir::NoDotDot; + QDir::Filters dirFilter = QDir::Dirs | QDir::Drives | QDir::NoDot | QDir::NoDotDot; QDir::Filters fileFilter = QDir::Files; - if (m_currentIncludeHidden) { + if (includeHidden) { dirFilter |= QDir::Hidden; fileFilter |= QDir::Hidden; } // use only 'name' for case sensitivity decision, because we need to make the path // match the case on the file system for case-sensitive file systems - const Qt::CaseSensitivity caseSensitivity_ = caseSensitivity(entryFileName); - const QStringList dirs = QStringList("..") - + dirInfo.entryList(dirFilter, QDir::Name|QDir::IgnoreCase|QDir::LocaleAware); - const QStringList files = dirInfo.entryList(fileFilter, - QDir::Name|QDir::IgnoreCase|QDir::LocaleAware); + const Qt::CaseSensitivity caseSensitivity = ILocatorFilter::caseSensitivity(entryFileName); + const FilePaths dirs = isPartOfDeviceRoot + ? FilePaths() + : FilePaths({directory / ".."}) + + directory.dirEntries({{}, dirFilter}, + QDir::Name | QDir::IgnoreCase + | QDir::LocaleAware); + const FilePaths files = isPartOfDeviceRoot ? FilePaths() + : directory.dirEntries({{}, fileFilter}, + QDir::Name | QDir::IgnoreCase + | QDir::LocaleAware); - QRegularExpression regExp = createRegExp(entryFileName, caseSensitivity_); - if (!regExp.isValid()) - return {}; + // directories + QRegularExpression regExp = ILocatorFilter::createRegExp(entryFileName, caseSensitivity); + if (regExp.isValid()) { + for (const FilePath &dir : dirs) { + if (promise.isCanceled()) + return; - for (const QString &dir : dirs) { - if (future.isCanceled()) - break; + const QString dirString = dir.relativeChildPath(directory).nativePath(); + const QRegularExpressionMatch match = regExp.match(dirString); + if (match.hasMatch()) { + const ILocatorFilter::MatchLevel level = matchLevelFor(match, dirString); + LocatorFilterEntry filterEntry; + filterEntry.displayName = dirString; + filterEntry.acceptor = [shortcutString, dir] { + const QString value + = shortcutString + ' ' + + dir.absoluteFilePath().cleanPath().pathAppended("/").toUserOutput(); + return AcceptResult{value, int(value.length())}; + }; + filterEntry.filePath = dir; + filterEntry.highlightInfo = ILocatorFilter::highlightInfo(match); - const QRegularExpressionMatch match = regExp.match(dir); - if (match.hasMatch()) { - const MatchLevel level = matchLevelFor(match, dir); - const QString fullPath = dirInfo.filePath(dir); - LocatorFilterEntry filterEntry(this, dir); - filterEntry.filePath = FilePath::fromString(fullPath); - filterEntry.highlightInfo = highlightInfo(match); - - entries[int(level)].append(filterEntry); + entries[int(level)].append(filterEntry); + } } } // file names can match with +linenumber or :linenumber const Link link = Link::fromString(entryFileName, true); - regExp = createRegExp(link.targetFilePath.toString(), caseSensitivity_); - if (!regExp.isValid()) - return {}; - for (const QString &file : files) { - if (future.isCanceled()) - break; + regExp = ILocatorFilter::createRegExp(link.targetFilePath.toString(), caseSensitivity); + if (regExp.isValid()) { + for (const FilePath &file : files) { + if (promise.isCanceled()) + return; - const QRegularExpressionMatch match = regExp.match(file); - if (match.hasMatch()) { - const MatchLevel level = matchLevelFor(match, file); - const QString fullPath = dirInfo.filePath(file); - LocatorFilterEntry filterEntry(this, file); - filterEntry.filePath = FilePath::fromString(fullPath); - filterEntry.highlightInfo = highlightInfo(match); - filterEntry.linkForEditor = Link(filterEntry.filePath, link.targetLine, - link.targetColumn); - entries[int(level)].append(filterEntry); + const QString fileString = file.relativeChildPath(directory).nativePath(); + const QRegularExpressionMatch match = regExp.match(fileString); + if (match.hasMatch()) { + const ILocatorFilter::MatchLevel level = matchLevelFor(match, fileString); + LocatorFilterEntry filterEntry; + filterEntry.displayName = fileString; + filterEntry.filePath = file; + filterEntry.highlightInfo = ILocatorFilter::highlightInfo(match); + filterEntry.linkForEditor = Link(filterEntry.filePath, + link.targetLine, + link.targetColumn); + entries[int(level)].append(filterEntry); + } + } + } + // device roots + // check against full search text + regExp = ILocatorFilter::createRegExp(expandedEntryPath.toUserOutput(), caseSensitivity); + if (regExp.isValid()) { + const FilePaths roots = deviceRoots(); + for (const FilePath &root : roots) { + if (promise.isCanceled()) + return; + + const QString displayString = root.toUserOutput(); + const QRegularExpressionMatch match = regExp.match(displayString); + if (match.hasMatch()) { + LocatorFilterEntry filterEntry; + filterEntry.displayName = displayString; + filterEntry.acceptor = [shortcutString, root] { + const QString value + = shortcutString + ' ' + + root.absoluteFilePath().cleanPath().pathAppended("/").toUserOutput(); + return AcceptResult{value, int(value.length())}; + }; + filterEntry.filePath = root; + filterEntry.displayIcon = *sDeviceRootIcon; + filterEntry.highlightInfo = ILocatorFilter::highlightInfo(match); + + entries[int(ILocatorFilter::MatchLevel::Normal)].append(filterEntry); + } } } // "create and open" functionality - const QString fullFilePath = dirInfo.filePath(entryFileName); + const FilePath fullFilePath = directory / entryFileName; const bool containsWildcard = expandedEntry.contains('?') || expandedEntry.contains('*'); - if (!containsWildcard && !QFileInfo::exists(fullFilePath) && dirInfo.exists()) { - LocatorFilterEntry createAndOpen(this, Tr::tr("Create and Open \"%1\"").arg(expandedEntry)); - createAndOpen.filePath = FilePath::fromString(fullFilePath); - createAndOpen.extraInfo = FilePath::fromString(dirInfo.absolutePath()).shortNativePath(); - entries[int(MatchLevel::Normal)].append(createAndOpen); + if (!containsWildcard && !fullFilePath.exists() && directory.exists()) { + // create and open file + { + LocatorFilterEntry filterEntry; + filterEntry.displayName = Tr::tr("Create and Open File \"%1\"").arg(expandedEntry); + filterEntry.displayIcon = Utils::FileIconProvider::icon(QFileIconProvider::File); + filterEntry.acceptor = [fullFilePath] { + QMetaObject::invokeMethod( + EditorManager::instance(), + [fullFilePath] { createAndOpenFile(fullFilePath); }, + Qt::QueuedConnection); + return AcceptResult(); + }; + filterEntry.filePath = fullFilePath; + filterEntry.extraInfo = directory.absoluteFilePath().shortNativePath(); + entries[int(ILocatorFilter::MatchLevel::Normal)].append(filterEntry); + } + + // create directory + { + LocatorFilterEntry filterEntry; + filterEntry.displayName = Tr::tr("Create Directory \"%1\"").arg(expandedEntry); + filterEntry.displayIcon = Utils::FileIconProvider::icon(QFileIconProvider::Folder); + filterEntry.acceptor = [fullFilePath, shortcutString] { + QMetaObject::invokeMethod( + EditorManager::instance(), + [fullFilePath, shortcutString] { + if (createDirectory(fullFilePath)) { + const QString value = shortcutString + ' ' + + fullFilePath.absoluteFilePath() + .cleanPath() + .pathAppended("/") + .toUserOutput(); + LocatorManager::show(value, value.length()); + } + }, + Qt::QueuedConnection); + return AcceptResult(); + }; + filterEntry.filePath = fullFilePath; + filterEntry.extraInfo = directory.absoluteFilePath().shortNativePath(); + entries[int(ILocatorFilter::MatchLevel::Normal)].append(filterEntry); + } } - return std::accumulate(std::begin(entries), std::end(entries), QList()); + storage.reportOutput(std::accumulate(std::begin(entries), std::end(entries), + LocatorFilterEntries())); } -const char kAlwaysCreate[] = "Locator/FileSystemFilter/AlwaysCreate"; - -void FileSystemFilter::accept(const LocatorFilterEntry &selection, - QString *newText, - int *selectionStart, - int *selectionLength) const +LocatorMatcherTasks FileSystemFilter::matchers() { - Q_UNUSED(selectionLength) - if (selection.filePath.isDir()) { - const QString value - = shortcutString() + ' ' - + selection.filePath.absoluteFilePath().cleanPath().pathAppended("/").toUserOutput(); - *newText = value; - *selectionStart = value.length(); - } else { - // Don't block locator filter execution with dialog - QMetaObject::invokeMethod(EditorManager::instance(), [selection] { - if (!selection.filePath.exists()) { - if (CheckableMessageBox::shouldAskAgain(ICore::settings(), kAlwaysCreate)) { - CheckableMessageBox messageBox(ICore::dialogParent()); - messageBox.setWindowTitle(Tr::tr("Create File")); - messageBox.setIcon(QMessageBox::Question); - messageBox.setText(Tr::tr("Create \"%1\"?").arg(selection.filePath.shortNativePath())); - messageBox.setCheckBoxVisible(true); - messageBox.setCheckBoxText(Tr::tr("Always create")); - messageBox.setChecked(false); - messageBox.setStandardButtons(QDialogButtonBox::Cancel); - QPushButton *createButton = messageBox.addButton(Tr::tr("Create"), - QDialogButtonBox::AcceptRole); - messageBox.setDefaultButton(QDialogButtonBox::Cancel); - messageBox.exec(); - if (messageBox.clickedButton() != createButton) - return; - if (messageBox.isChecked()) - CheckableMessageBox::doNotAskAgain(ICore::settings(), kAlwaysCreate); - } - QFile file(selection.filePath.toString()); - file.open(QFile::WriteOnly); - file.close(); - VcsManager::promptToAdd(selection.filePath.absolutePath(), {selection.filePath}); - } - BaseFileFilter::openEditorAt(selection); - }, Qt::QueuedConnection); - } + using namespace Tasking; + + TreeStorage storage; + + const auto onSetup = [storage, includeHidden = m_includeHidden, shortcut = shortcutString()] + (Async &async) { + async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer()); + async.setConcurrentCallData(matches, *storage, shortcut, + DocumentManager::fileDialogInitialDirectory(), includeHidden); + }; + + return {{AsyncTask(onSetup), storage}}; } class FileSystemFilterOptions : public QDialog @@ -260,13 +377,13 @@ const char kIncludeHiddenKey[] = "includeHidden"; void FileSystemFilter::saveState(QJsonObject &object) const { - if (m_includeHidden != kIncludeHiddenDefault) + if (m_includeHidden != s_includeHiddenDefault) object.insert(kIncludeHiddenKey, m_includeHidden); } void FileSystemFilter::restoreState(const QJsonObject &object) { - m_currentIncludeHidden = object.value(kIncludeHiddenKey).toBool(kIncludeHiddenDefault); + m_includeHidden = object.value(kIncludeHiddenKey).toBool(s_includeHiddenDefault); } void FileSystemFilter::restoreState(const QByteArray &state) @@ -290,5 +407,4 @@ void FileSystemFilter::restoreState(const QByteArray &state) } } -} // Internal -} // Core +} // namespace Core::Internal diff --git a/src/plugins/coreplugin/locator/filesystemfilter.h b/src/plugins/coreplugin/locator/filesystemfilter.h index 33338d626e2..3dfac11b3a5 100644 --- a/src/plugins/coreplugin/locator/filesystemfilter.h +++ b/src/plugins/coreplugin/locator/filesystemfilter.h @@ -5,40 +5,24 @@ #include "ilocatorfilter.h" -#include -#include -#include -#include - -namespace Core { -namespace Internal { +namespace Core::Internal { class FileSystemFilter : public ILocatorFilter { - Q_OBJECT - public: - explicit FileSystemFilter(); - void prepareSearch(const QString &entry) override; - QList matchesFor(QFutureInterface &future, - const QString &entry) override; - void accept(const LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const override; - void restoreState(const QByteArray &state) override; - bool openConfigDialog(QWidget *parent, bool &needsRefresh) override; + FileSystemFilter(); + void restoreState(const QByteArray &state) final; + bool openConfigDialog(QWidget *parent, bool &needsRefresh) final; protected: void saveState(QJsonObject &object) const final; void restoreState(const QJsonObject &object) final; private: - static MatchLevel matchLevelFor(const QRegularExpressionMatch &match, const QString &matchText); + LocatorMatcherTasks matchers() final; - static const bool kIncludeHiddenDefault = true; - bool m_includeHidden = kIncludeHiddenDefault; - bool m_currentIncludeHidden = kIncludeHiddenDefault; - QString m_currentDocumentDirectory; + static const bool s_includeHiddenDefault = true; + bool m_includeHidden = s_includeHiddenDefault; }; -} // namespace Internal -} // namespace Core +} // namespace Core::Internal diff --git a/src/plugins/coreplugin/locator/ilocatorfilter.cpp b/src/plugins/coreplugin/locator/ilocatorfilter.cpp index 4523c719fb6..3a84a0b548b 100644 --- a/src/plugins/coreplugin/locator/ilocatorfilter.cpp +++ b/src/plugins/coreplugin/locator/ilocatorfilter.cpp @@ -5,19 +5,27 @@ #include "../coreplugintr.h" +#include + +#include +#include #include #include #include -#include #include #include +#include #include #include #include #include #include +#include +#include + +using namespace Tasking; using namespace Utils; /*! @@ -44,6 +52,509 @@ using namespace Utils; namespace Core { +// ResultsDeduplicator squashes upcoming results from various filters and removes +// duplicated entries. It also reports intermediate results (to be displayed in LocatorWidget). +// +// Assuming the results from filters come in this order (numbers are indices to filter results): +// 2, 4, 3, 1 - the various strategies are possible. The current strategy looks like this: +// - When 2nd result came - the result is stored +// - When 4th result came - the result is stored +// - When 3rd result came - it's being squased to the 2nd result and afterwards the 4th result +// result is being squashed into the common result list. +// - When 1st result came - the stored common list is squashed into the 1st result +// and intermediate results are reported (for 1-4 results). +// If the filterCount is 4, the deduplicator finishes now. +// If the filterCount is greater than 4, it waits for the remaining +// results. +// +// TODO: The other possible startegy would be to just store the newly reported data +// and do the actual deduplication only when new results are reachable from 1st index +// (i.e. skip the intermediate deduplication). +class ResultsDeduplicator +{ + enum class State { + Awaiting, // Waiting in a separate thread for new data, or fetched the last new data and + // is currently deduplicating. + // This happens when all previous data were squashed in the separate thread but + // still some data needs to come (reportOutput wasn't called for all filters, + // yet). The expected number of calls to reportOutput equals m_filterCount. + NewData, // The new data came and the separate thread is being awaken in order to squash + // it. After the separate thread is awaken it transitions to Awaiting state. + Canceled // The Deduplicator task has been canceled. + }; + + // A separate item for keeping squashed entries. Call mergeWith() to squash a consecutive + // results into this results. + struct WorkingData { + WorkingData() = default; + WorkingData(const LocatorFilterEntries &entries, std::atomic &state) { + mergeWith(entries, state); + } + LocatorFilterEntries mergeWith(const LocatorFilterEntries &entries, + std::atomic &state) { + LocatorFilterEntries results; + results.reserve(entries.size()); + for (const LocatorFilterEntry &entry : entries) { + if (state == State::Canceled) + return {}; + const auto &link = entry.linkForEditor; + if (!link || m_cache.emplace(*link).second) + results.append(entry); + } + if (state == State::Canceled) + return {}; + + m_data += results; + return results; + } + LocatorFilterEntries entries() const { return m_data; } + private: + LocatorFilterEntries m_data; + std::unordered_set m_cache; + }; + +public: + // filterCount is the expected numbers of running filters. The separate thread executing run() + // will stop after reportOutput was called filterCount times, for all different indices + // in range [0, filterCount). + ResultsDeduplicator(int filterCount) + : m_filterCount(filterCount) + , m_outputData(filterCount, {}) + {} + + void reportOutput(int index, const LocatorFilterEntries &outputData) + // Called directly by running filters. The calls may come from main thread in case of + // e.g. Sync task or directly from other threads when AsyncTask was used. + { + QTC_ASSERT(index >= 0, return); + + QMutexLocker locker(&m_mutex); + // It may happen that the task tree was canceled, while tasks are still running in other + // threads and are about to be canceled. In this case we just ignore the call. + if (m_state == State::Canceled) + return; + QTC_ASSERT(index < m_filterCount, return); + QTC_ASSERT(!m_outputData.at(index).has_value(), return); + + m_outputData[index] = outputData; + m_state = State::NewData; + m_waitCondition.wakeOne(); + } + + // Called when the LocatorMatcher was canceled. It wakes the separate thread in order to + // finish it, soon. + void cancel() + { + QMutexLocker locker(&m_mutex); + m_state = State::Canceled; + m_waitCondition.wakeOne(); + } + + // Called from the separate thread (ResultsCollector's thread) + void run(QPromise &promise) + { + QList> data; + QList> workingList(m_filterCount, {}); + while (waitForData(&data)) { + // Emit new results only when new data is reachable from the beginning (i.e. no gaps) + int currentIndex = 0; + int mergeToIndex = 0; + bool hasGap = false; + while (currentIndex < m_filterCount) { + if (m_state == State::Canceled) + return; + const auto &outputData = data.at(currentIndex); + if (!outputData.has_value()) { + ++currentIndex; + mergeToIndex = currentIndex; + hasGap = true; + continue; + } + const auto &workingData = workingList.at(currentIndex); + if (!workingData.has_value()) { + const bool mergeToCurrent = currentIndex == mergeToIndex; + const LocatorFilterEntries dataForIndex = mergeToCurrent ? *outputData + : LocatorFilterEntries(); + workingList[currentIndex] = std::make_optional(WorkingData(dataForIndex, + m_state)); + if (m_state == State::Canceled) + return; + const LocatorFilterEntries newData = mergeToCurrent + ? workingList[currentIndex]->entries() + : workingList[mergeToIndex]->mergeWith(*outputData, m_state); + if (m_state == State::Canceled) + return; + if (!hasGap && !newData.isEmpty()) + promise.addResult(newData); + } else if (currentIndex != mergeToIndex) { + const LocatorFilterEntries newData + = workingList[mergeToIndex]->mergeWith(workingData->entries(), m_state); + workingList[currentIndex] = std::make_optional({}); + if (m_state == State::Canceled) + return; + if (!hasGap && !newData.isEmpty()) + promise.addResult(newData); + } + ++currentIndex; + } + // All data arrived (no gap), so finish here + if (!hasGap) + return; + } + } + +private: + // Called from the separate thread, exclusively by run(). Checks if the new data already + // came before sleeping with wait condition. If so, it doesn't sleep with wait condition, + // but returns the data collected in meantime. Otherwise, it calls wait() on wait condition. + bool waitForData(QList> *data) + { + QMutexLocker locker(&m_mutex); + if (m_state == State::Canceled) + return false; + if (m_state == State::NewData) { + m_state = State::Awaiting; // Mark the state as awaiting to detect new calls to + // setOutputData while the separate thread deduplicates the + // new data. + *data = m_outputData; + return true; + } + m_waitCondition.wait(&m_mutex); + QTC_ASSERT(m_state != State::Awaiting, return false); + if (m_state == State::Canceled) + return false; + m_state = State::Awaiting; // Mark the state as awaiting to detect new calls to + // setOutputData while the separate thread deduplicates the + // new data. + *data = m_outputData; + return true; + } + + QMutex m_mutex; + QWaitCondition m_waitCondition; + const int m_filterCount = 0; + std::atomic m_state = State::Awaiting; + QList> m_outputData; +}; + +// This instance of this object is created by LocatorMatcher tree. +// It starts a separate thread which collects and deduplicates the results reported +// by LocatorStorage instances. The ResultsCollector is started as a first task in +// LocatorMatcher and runs in parallel to all the filters started by LocatorMatcher. +// When all the results are reported (the expected number of reports is set with setFilterCount()), +// the ResultsCollector finishes. The intermediate results are reported with +// serialOutputDataReady() signal. +// The object of ResultsCollector is registered in Tasking namespace under the +// ResultsCollectorTask name. +class ResultsCollector : public QObject +{ + Q_OBJECT + +public: + ~ResultsCollector(); + void setFilterCount(int count); + void start(); + + bool isRunning() const { return m_watcher.get(); } + + std::shared_ptr deduplicator() const { return m_deduplicator; } + +signals: + void serialOutputDataReady(const LocatorFilterEntries &serialOutputData); + void done(); + +private: + int m_filterCount = 0; + std::unique_ptr> m_watcher; + std::shared_ptr m_deduplicator; +}; + +ResultsCollector::~ResultsCollector() +{ + if (!isRunning()) + return; + + m_deduplicator->cancel(); + if (ExtensionSystem::PluginManager::futureSynchronizer()) { + ExtensionSystem::PluginManager::futureSynchronizer()->addFuture(m_watcher->future()); + return; + } + m_watcher->future().waitForFinished(); +} + +void ResultsCollector::setFilterCount(int count) +{ + QTC_ASSERT(!isRunning(), return); + QTC_ASSERT(count >= 0, return); + + m_filterCount = count; +} + +void ResultsCollector::start() +{ + QTC_ASSERT(!m_watcher, return); + QTC_ASSERT(!isRunning(), return); + if (m_filterCount == 0) { + emit done(); + return; + } + + m_deduplicator.reset(new ResultsDeduplicator(m_filterCount)); + m_watcher.reset(new QFutureWatcher); + connect(m_watcher.get(), &QFutureWatcherBase::resultReadyAt, this, [this](int index) { + emit serialOutputDataReady(m_watcher->resultAt(index)); + }); + connect(m_watcher.get(), &QFutureWatcherBase::finished, this, [this] { + emit done(); + m_watcher.release()->deleteLater(); + m_deduplicator.reset(); + }); + + // TODO: When filterCount == 1, deliver results directly and finish? + auto deduplicate = [](QPromise &promise, + const std::shared_ptr &deduplicator) { + deduplicator->run(promise); + }; + m_watcher->setFuture(Utils::asyncRun(deduplicate, m_deduplicator)); +} + +class ResultsCollectorTaskAdapter : public TaskAdapter +{ +public: + ResultsCollectorTaskAdapter() { + connect(task(), &ResultsCollector::done, this, [this] { emit done(true); }); + } + void start() final { task()->start(); } +}; + +} // namespace Core + +TASKING_DECLARE_TASK(ResultsCollectorTask, Core::ResultsCollectorTaskAdapter); + +namespace Core { + +class LocatorStoragePrivate +{ +public: + LocatorStoragePrivate(const QString &input, int index, + const std::shared_ptr &deduplicator) + : m_input(input) + , m_index(index) + , m_deduplicator(deduplicator) + {} + + QString input() const { return m_input; } + + void reportOutput(const LocatorFilterEntries &outputData) + { + QMutexLocker locker(&m_mutex); + QTC_ASSERT(m_deduplicator, return); + reportOutputImpl(outputData); + } + + void finalize() + { + QMutexLocker locker(&m_mutex); + if (m_deduplicator) + reportOutputImpl({}); + } + +private: + // Call me with mutex locked + void reportOutputImpl(const LocatorFilterEntries &outputData) + { + QTC_ASSERT(m_index >= 0, return); + m_deduplicator->reportOutput(m_index, outputData); + // Deliver results only once for all copies of the storage, drop ref afterwards + m_deduplicator.reset(); + } + + const QString m_input; + const int m_index = -1; + std::shared_ptr m_deduplicator; + QMutex m_mutex = {}; +}; + +QString LocatorStorage::input() const +{ + QTC_ASSERT(d, return {}); + return d->input(); +} + +void LocatorStorage::reportOutput(const LocatorFilterEntries &outputData) const +{ + QTC_ASSERT(d, return); + d->reportOutput(outputData); +} + +void LocatorStorage::finalize() const +{ + QTC_ASSERT(d, return); + d->finalize(); +} + +class LocatorMatcherPrivate +{ +public: + LocatorMatcherTasks m_tasks; + QString m_input; + LocatorFilterEntries m_output; + int m_parallelLimit = 0; + std::unique_ptr m_taskTree; +}; + +LocatorMatcher::LocatorMatcher() + : d(new LocatorMatcherPrivate) {} + +LocatorMatcher::~LocatorMatcher() = default; + +void LocatorMatcher::setTasks(const LocatorMatcherTasks &tasks) +{ + d->m_tasks = tasks; +} + +void LocatorMatcher::setInputData(const QString &inputData) +{ + d->m_input = inputData; +} + +void LocatorMatcher::setParallelLimit(int limit) +{ + d->m_parallelLimit = limit; +} + +void LocatorMatcher::start() +{ + QTC_ASSERT(!isRunning(), return); + d->m_output = {}; + d->m_taskTree.reset(new TaskTree); + + struct CollectorStorage + { + ResultsCollector *m_collector = nullptr; + }; + TreeStorage collectorStorage; + + const int filterCount = d->m_tasks.size(); + const auto onCollectorSetup = [this, filterCount, collectorStorage](ResultsCollector &collector) { + collectorStorage->m_collector = &collector; + collector.setFilterCount(filterCount); + connect(&collector, &ResultsCollector::serialOutputDataReady, + this, [this](const LocatorFilterEntries &serialOutputData) { + d->m_output += serialOutputData; + emit serialOutputDataReady(serialOutputData); + }); + }; + const auto onCollectorDone = [collectorStorage](const ResultsCollector &collector) { + Q_UNUSED(collector) + collectorStorage->m_collector = nullptr; + }; + + QList parallelTasks {parallelLimit(d->m_parallelLimit)}; + + const auto onSetup = [this, collectorStorage](const TreeStorage &storage, + int index) { + return [this, collectorStorage, storage, index] { + ResultsCollector *collector = collectorStorage->m_collector; + QTC_ASSERT(collector, return); + *storage = std::make_shared(d->m_input, index, + collector->deduplicator()); + }; + }; + + const auto onDone = [](const TreeStorage &storage) { + return [storage] { storage->finalize(); }; + }; + + int index = 0; + for (const LocatorMatcherTask &task : std::as_const(d->m_tasks)) { + const auto storage = task.storage; + const Group group { + finishAllAndDone, + Storage(storage), + onGroupSetup(onSetup(storage, index)), + onGroupDone(onDone(storage)), + onGroupError(onDone(storage)), + task.task + }; + parallelTasks << group; + ++index; + } + + const Group root { + parallel, + Storage(collectorStorage), + ResultsCollectorTask(onCollectorSetup, onCollectorDone, onCollectorDone), + Group { + parallelTasks + } + }; + + d->m_taskTree->setupRoot(root); + + const auto onFinish = [this](bool success) { + return [this, success] { + emit done(success); + d->m_taskTree.release()->deleteLater(); + }; + }; + connect(d->m_taskTree.get(), &TaskTree::done, this, onFinish(true)); + connect(d->m_taskTree.get(), &TaskTree::errorOccurred, this, onFinish(false)); + d->m_taskTree->start(); +} + +void LocatorMatcher::stop() +{ + if (!isRunning()) + return; + + d->m_taskTree->stop(); + d->m_taskTree.reset(); +} + +bool LocatorMatcher::isRunning() const +{ + return d->m_taskTree.get() && d->m_taskTree->isRunning(); +} + +LocatorFilterEntries LocatorMatcher::outputData() const +{ + return d->m_output; +} + +LocatorFilterEntries LocatorMatcher::runBlocking(const LocatorMatcherTasks &tasks, + const QString &input, int parallelLimit) +{ + LocatorMatcher tree; + tree.setTasks(tasks); + tree.setInputData(input); + tree.setParallelLimit(parallelLimit); + + QEventLoop loop; + connect(&tree, &LocatorMatcher::done, &loop, [&loop] { loop.quit(); }); + tree.start(); + if (tree.isRunning()) + loop.exec(QEventLoop::ExcludeUserInputEvents); + return tree.outputData(); +} + +static QHash> s_matcherCreators = {}; + +void LocatorMatcher::addMatcherCreator(MatcherType type, const LocatorMatcherTaskCreator &creator) +{ + QTC_ASSERT(creator, return); + s_matcherCreators[type].append(creator); +} + +LocatorMatcherTasks LocatorMatcher::matchers(MatcherType type) +{ + const QList creators = s_matcherCreators.value(type); + LocatorMatcherTasks result; + for (const LocatorMatcherTaskCreator &creator : creators) + result << creator(); + return result; +} + static QList g_locatorFilters; /*! @@ -83,19 +594,21 @@ QString ILocatorFilter::shortcutString() const } /*! - Performs actions that need to be done in the main thread before actually - running the search for \a entry. - - Called on the main thread before matchesFor() is called in a separate - thread. - - The default implementation does nothing. - - \sa matchesFor() + \internal + Sets the refresh recipe for refreshing cached data. */ -void ILocatorFilter::prepareSearch(const QString &entry) +void ILocatorFilter::setRefreshRecipe(const std::optional &recipe) { - Q_UNUSED(entry) + m_refreshRecipe = recipe; +} + +/*! + Returns the refresh recipe for refreshing cached data. By default, the locator filter has + no recipe set, so that it won't be refreshed. +*/ +std::optional ILocatorFilter::refreshRecipe() const +{ + return m_refreshRecipe; } /*! @@ -201,13 +714,13 @@ void ILocatorFilter::restoreState(const QByteArray &state) various aspects of the filter. Called when the user requests to configure the filter. - Set \a needsRefresh to \c true, if a refresh() should be done after + Set \a needsRefresh to \c true, if a refresh should be done after closing the dialog. Return \c false if the user canceled the dialog. The default implementation allows changing the shortcut and whether the filter is included by default. - \sa refresh() + \sa refreshRecipe() */ bool ILocatorFilter::openConfigDialog(QWidget *parent, bool &needsRefresh) { @@ -425,7 +938,10 @@ ILocatorFilter::Priority ILocatorFilter::priority() const */ void ILocatorFilter::setEnabled(bool enabled) { + if (enabled == m_enabled) + return; m_enabled = enabled; + emit enabledChanged(m_enabled); } /*! @@ -581,39 +1097,6 @@ bool ILocatorFilter::isOldSetting(const QByteArray &state) return !doc.isObject(); } -/*! - \fn QList Core::ILocatorFilter::matchesFor(QFutureInterface &future, const QString &entry) - - Returns the list of results of this filter for the search term \a entry. - This is run in a separate thread, but is guaranteed to only run in a single - thread at any given time. Quickly running preparations can be done in the - GUI thread in prepareSearch(). - - Implementations should do a case sensitive or case insensitive search - depending on caseSensitivity(). If \a future is \c canceled, the search - should be aborted. - - \sa prepareSearch() - \sa caseSensitivity() -*/ - -/*! - \fn void Core::ILocatorFilter::accept(Core::const LocatorFilterEntry &selection, QString *newText, int *selectionStart, int *selectionLength) const - - Called with the entry specified by \a selection when the user activates it - in the result list. - Implementations can return a new search term \a newText, which has \a selectionLength characters - starting from \a selectionStart preselected, and the cursor set to the end of the selection. -*/ - -/*! - \fn void Core::ILocatorFilter::refresh(QFutureInterface &future) - - Refreshes cached data asynchronously. - - If \a future is \c canceled, the refresh should be aborted. -*/ - /*! \enum Core::ILocatorFilter::Priority @@ -647,4 +1130,410 @@ bool ILocatorFilter::isOldSetting(const QByteArray &state) expression. */ +std::atomic_int s_executeId = 0; + + +class LocatorFileCachePrivate +{ +public: + bool isValid() const { return bool(m_generator); } + void invalidate(); + bool ensureValidated(); + void bumpExecutionId() { m_executionId = s_executeId.fetch_add(1) + 1; } + void update(const LocatorFileCachePrivate &newCache); + void setGeneratorProvider(const LocatorFileCache::GeneratorProvider &provider) + { m_provider = provider; } + void setGenerator(const LocatorFileCache::FilePathsGenerator &generator); + LocatorFilterEntries generate(const QFuture &future, const QString &input); + + // Is persistent, does not reset on invalidate + LocatorFileCache::GeneratorProvider m_provider; + LocatorFileCache::FilePathsGenerator m_generator; + int m_executionId = 0; + + std::optional m_filePaths; + + QString m_lastInput; + std::optional m_cache; +}; + +// Clears all but provider +void LocatorFileCachePrivate::invalidate() +{ + LocatorFileCachePrivate that; + that.m_provider = m_provider; + *this = that; +} + +/*! + \internal + + Returns true if the cache is valid. Otherwise, tries to validate the cache and returns whether + the validation succeeded. + + When the cache is valid, it does nothing and returns true. + Otherwise, when the GeneratorProvider is not set, it does nothing and returns false. + Otherwise, the GeneratorProvider is used for recreating the FilePathsGenerator. + If the recreated FilePathsGenerator is not empty, it return true. + Otherwise, it returns false; +*/ +bool LocatorFileCachePrivate::ensureValidated() +{ + if (isValid()) + return true; + + if (!m_provider) + return false; + + invalidate(); + m_generator = m_provider(); + return isValid(); +} + +void LocatorFileCachePrivate::update(const LocatorFileCachePrivate &newCache) +{ + if (m_executionId != newCache.m_executionId) + return; // The mismatching executionId was detected, ignoring the update... + auto provider = m_provider; + *this = newCache; + m_provider = provider; +} + +void LocatorFileCachePrivate::setGenerator(const LocatorFileCache::FilePathsGenerator &generator) +{ + invalidate(); + m_generator = generator; +} + +static bool containsPathSeparator(const QString &candidate) +{ + return candidate.contains('/') || candidate.contains('*'); +}; + +/*! + \internal + + Uses the generator to update the cache if needed and returns entries for the input. + Uses the cached data when no need for re-generation. Updates the cache accordingly. +*/ +LocatorFilterEntries LocatorFileCachePrivate::generate(const QFuture &future, + const QString &input) +{ + QTC_ASSERT(isValid(), return {}); + + // If search string contains spaces, treat them as wildcard '*' and search in full path + const QString wildcardInput = QDir::fromNativeSeparators(input).replace(' ', '*'); + const Link inputLink = Link::fromString(wildcardInput, true); + const QString newInput = inputLink.targetFilePath.toString(); + const QRegularExpression regExp = ILocatorFilter::createRegExp(newInput); + if (!regExp.isValid()) + return {}; // Don't clear the cache - still remember the cache for the last valid input. + + if (future.isCanceled()) + return {}; + + const bool hasPathSeparator = containsPathSeparator(newInput); + const bool containsLastInput = !m_lastInput.isEmpty() && newInput.contains(m_lastInput); + const bool pathSeparatorAdded = !containsPathSeparator(m_lastInput) && hasPathSeparator; + const bool searchInCache = m_filePaths && m_cache && containsLastInput && !pathSeparatorAdded; + + std::optional newPaths = m_filePaths; + if (!searchInCache && !newPaths) { + newPaths = m_generator(future); + if (future.isCanceled()) // Ensure we got not canceled results from generator. + return {}; + } + + const FilePaths &sourcePaths = searchInCache ? *m_cache : *newPaths; + LocatorFileCache::MatchedEntries entries = {}; + const FilePaths newCache = LocatorFileCache::processFilePaths( + future, sourcePaths, hasPathSeparator, regExp, inputLink, &entries); + for (auto &entry : entries) { + if (future.isCanceled()) + return {}; + + if (entry.size() < 1000) + Utils::sort(entry, LocatorFilterEntry::compareLexigraphically); + } + + if (future.isCanceled()) + return {}; + + // Update all the cache data in one go + m_filePaths = newPaths; + m_lastInput = newInput; + m_cache = newCache; + + return std::accumulate(std::begin(entries), std::end(entries), LocatorFilterEntries()); +} + +/*! + \class Core::LocatorFileCache + \inmodule QtCreator + + \brief The LocatorFileCache class encapsulates all the responsibilities needed for + implementing a cache for file filters. + + LocatorFileCache serves as a replacement for the old BaseFileFilter interface. +*/ + +/*! + Constructs an invalid cache. + + The cache is considered to be in an invalid state after a call to invalidate(), + of after a call to setFilePathsGenerator() when passed functions was empty. + + It it possible to setup the automatic validator for the cache through the + setGeneratorProvider(). + + \sa invalidate, setGeneratorProvider, setFilePathsGenerator, setFilePaths +*/ + +LocatorFileCache::LocatorFileCache() + : d(new LocatorFileCachePrivate) {} + +/*! + Invalidates the cache. + + In order to validate it, use either setFilePathsGenerator() or setFilePaths(). + The cache may be automatically validated if the GeneratorProvider was set + through the setGeneratorProvider(). + + \note This function invalidates the cache permanently, clearing all the cached data, + and removing the stored generator. The stored generator provider is preserved. +*/ +void LocatorFileCache::invalidate() +{ + d->invalidate(); +} + +/*! + Sets the file path generator provider. + + The \a provider serves for an automatic validation of the invalid cache by recreating + the FilePathsGenerator. The automatic validation happens when the LocatorMatcherTask returned + by matcher() is being started, and the cache is not valid at that moment. In this case + the stored \a provider is being called. + + The passed \a provider function is always called from the main thread. If needed, it is + called prior to starting an asynchronous task that collects the locator filter results. + + When this function is called, the cache isn't invalidated. + Whenever cache's invalidation happens, e.g. when invalidate(), setFilePathsGenerator() or + setFilePaths() is called, the stored GeneratorProvider is being preserved. + In order to clear the stored GeneratorProvider, call this method with an empty + function {}. +*/ +void LocatorFileCache::setGeneratorProvider(const GeneratorProvider &provider) +{ + d->setGeneratorProvider(provider); +} + +std::optional LocatorFileCache::filePaths() const +{ + return d->m_filePaths; +} + +/*! + Sets the file path generator. + + The \a generator serves for returning the full input list of file paths when the + associated LocatorMatherTask is being run in a separate thread. When the computation of the + full list of file paths takes a considerable amount of time, this computation may + be potentially moved to the separate thread, provided that all the dependent data may be safely + passed to the \a generator function when this function is being set in the main thread. + + The passed \a generator is always called exclusively from the non-main thread when running + LocatorMatcherTask returned by matcher(). It is called when the cached data is + empty or when it needs to be regenerated due to a new search which can't reuse + the cache from the previous search. + + Generating a new file path list may be a time consuming task. In order to finish the task early + when being canceled, the \e future argument of the FilePathsGenerator may be used. + The FilePathsGenerator returns the full list of file paths used for file filter's processing. + + Whenever it is possible to postpone the creation of a file path list so that it may be done + safely later from the non-main thread, based on some other reentrant/thread-safe data, + this method should be used. The other dependent data should be passed by lambda capture. + The body of the passed \a generator should take extra care for ensuring that the passed + other data via lambda captures are reentrant and the lambda body is thread safe. + See the example usage of the generator inside CppIncludesFilter implementation. + + Otherwise, when postponing the creation of file paths list isn't safe, use setFilePaths() + with ready made list, prepared in main thread. + + \note This function invalidates the cache, clearing all the cached data, + and if the passed generator is non-empty, the cache is set to a valid state. + The stored generator provider is preserved. + + \sa setGeneratorProvider, setFilePaths +*/ +void LocatorFileCache::setFilePathsGenerator(const FilePathsGenerator &generator) +{ + d->setGenerator(generator); +} + +/*! + Wraps the passed \a filePaths into a trivial FilePathsGenerator and sets it + as a cache's generator. + + \note This function invalidates the cache temporarily, clearing all the cached data, + and sets it to a valid state with the new generator for the passed \a filePaths. + The stored generator provider is preserved. + + \sa setGeneratorProvider +*/ +void LocatorFileCache::setFilePaths(const FilePaths &filePaths) +{ + setFilePathsGenerator(filePathsGenerator(filePaths)); + d->m_filePaths = filePaths; +} + +/*! + Adapts the \a filePaths list into a LocatorFileCacheGenerator. + Useful when implementing GeneratorProvider in case a creation of file paths + can't be invoked from the non-main thread. +*/ +LocatorFileCache::FilePathsGenerator LocatorFileCache::filePathsGenerator( + const FilePaths &filePaths) +{ + return [filePaths](const QFuture &) { return filePaths; }; +} + +static ILocatorFilter::MatchLevel matchLevelFor(const QRegularExpressionMatch &match, + const QString &matchText) +{ + const int consecutivePos = match.capturedStart(1); + if (consecutivePos == 0) + return ILocatorFilter::MatchLevel::Best; + if (consecutivePos > 0) { + const QChar prevChar = matchText.at(consecutivePos - 1); + if (prevChar == '_' || prevChar == '.') + return ILocatorFilter::MatchLevel::Better; + } + if (match.capturedStart() == 0) + return ILocatorFilter::MatchLevel::Good; + return ILocatorFilter::MatchLevel::Normal; +} + +/*! + Helper used internally and by SpotlightLocatorFilter. + + To be called from non-main thread. The cancellation is controlled by the passed \a future. + This function periodically checks for the cancellation state of the \a future and returns + early when cancellation was detected. + Creates lists of matching LocatorFilterEntries categorized by MatcherType. These lists + are returned through the \a entries argument. + + Returns a list of all matching files. + + This function checks for each file in \a filePaths if it matches the passed \a regExp. + If so, a new entry is created using \a hasPathSeparator and \a inputLink and + it's being added into the \a entries argument and the results list. +*/ +FilePaths LocatorFileCache::processFilePaths(const QFuture &future, + const FilePaths &filePaths, + bool hasPathSeparator, + const QRegularExpression ®Exp, + const Link &inputLink, + LocatorFileCache::MatchedEntries *entries) +{ + FilePaths cache; + for (const FilePath &path : filePaths) { + if (future.isCanceled()) + return {}; + + const QString matchText = hasPathSeparator ? path.toString() : path.fileName(); + const QRegularExpressionMatch match = regExp.match(matchText); + + if (match.hasMatch()) { + LocatorFilterEntry filterEntry; + filterEntry.displayName = path.fileName(); + filterEntry.filePath = path; + filterEntry.extraInfo = path.shortNativePath(); + filterEntry.linkForEditor = Link(path, inputLink.targetLine, inputLink.targetColumn); + filterEntry.highlightInfo = hasPathSeparator + ? ILocatorFilter::highlightInfo(regExp.match(filterEntry.extraInfo), + LocatorFilterEntry::HighlightInfo::ExtraInfo) + : ILocatorFilter::highlightInfo(match); + const ILocatorFilter::MatchLevel matchLevel = matchLevelFor(match, matchText); + (*entries)[int(matchLevel)].append(filterEntry); + cache << path; + } + } + return cache; +} + +static void filter(QPromise &promise, const LocatorStorage &storage, + const LocatorFileCachePrivate &cache) +{ + QTC_ASSERT(cache.isValid(), return); + auto newCache = cache; + const LocatorFilterEntries output = newCache.generate(QFuture(promise.future()), + storage.input()); + if (promise.isCanceled()) + return; + storage.reportOutput(output); + promise.addResult(newCache); +} + +/*! + Returns the locator matcher task for the cache. The task, when successfully finished, + updates this LocatorFileCache instance if needed. + + This method is to be used directly by the FilePaths filters. The FilePaths filter should + keep an instance of a LocatorFileCache internally. Ensure the LocatorFileCache instance + outlives the running matcher, otherwise the cache won't be updated after the task finished. + + When returned LocatorMatcherTask is being run it checks if this cache is valid. + When the cache is invalid, it uses GeneratorProvider to update the + cache's FilePathsGenerator and validates the cache. If that failed, the task + is not started. When the cache is valid, the running task will reuse cached data for + calculating the LocatorMatcherTask's results. + + After a successful run of the task, this cache is updated according to the last search. + When this cache started a new search in meantime, the cache was invalidated or even deleted, + the update of the cache after a successful run of the task is ignored. +*/ +LocatorMatcherTask LocatorFileCache::matcher() const +{ + TreeStorage storage; + std::weak_ptr weak = d; + + const auto onSetup = [storage, weak](Async &async) { + auto that = weak.lock(); + if (!that) // LocatorMatcher is running after *this LocatorFileCache was destructed. + return TaskAction::StopWithDone; + + if (!that->ensureValidated()) + return TaskAction::StopWithDone; // The cache is invalid and + // no provider is set or it returned empty generator + that->bumpExecutionId(); + + async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer()); + async.setConcurrentCallData(&filter, *storage, *that); + return TaskAction::Continue; + }; + const auto onDone = [weak](const Async &async) { + auto that = weak.lock(); + if (!that) + return; // LocatorMatcherTask finished after *this LocatorFileCache was destructed. + + if (!that->isValid()) + return; // The cache has been invalidated in meantime. + + if (that->m_executionId == 0) + return; // The cache has been invalidated and not started. + + if (!async.isResultAvailable()) + return; // The async task didn't report updated cache. + + that->update(async.result()); + }; + + return {AsyncTask(onSetup, onDone), storage}; +} + } // Core + +#include "ilocatorfilter.moc" diff --git a/src/plugins/coreplugin/locator/ilocatorfilter.h b/src/plugins/coreplugin/locator/ilocatorfilter.h index e6b49ab5e9c..b008c12bb5c 100644 --- a/src/plugins/coreplugin/locator/ilocatorfilter.h +++ b/src/plugins/coreplugin/locator/ilocatorfilter.h @@ -5,24 +5,44 @@ #include +#include + #include #include #include -#include #include -#include -#include #include #include +QT_BEGIN_NAMESPACE +template +class QFuture; +QT_END_NAMESPACE + namespace Core { -class ILocatorFilter; +namespace Internal { +class Locator; +class LocatorWidget; +} -struct LocatorFilterEntry +class ILocatorFilter; +class LocatorStoragePrivate; +class LocatorFileCachePrivate; + +class AcceptResult { +public: + QString newText; + int selectionStart = -1; + int selectionLength = 0; +}; + +class LocatorFilterEntry +{ +public: struct HighlightInfo { enum DataType { DisplayName, @@ -66,18 +86,7 @@ struct LocatorFilterEntry LocatorFilterEntry() = default; - LocatorFilterEntry(ILocatorFilter *fromFilter, - const QString &name, - const QVariant &data = {}, - std::optional icon = std::nullopt) - : filter(fromFilter) - , displayName(name) - , internalData(data) - , displayIcon(icon) - {} - - /* backpointer to creating filter */ - ILocatorFilter *filter = nullptr; + using Acceptor = std::function; /* displayed string */ QString displayName; /* extra information displayed in parentheses and light-gray next to display name (optional)*/ @@ -86,8 +95,9 @@ struct LocatorFilterEntry QString extraInfo; /* additional tooltip */ QString toolTip; - /* can be used by the filter to save more information about the entry */ - QVariant internalData; + /* called by locator widget on accept. By default, when acceptor is empty, + EditorManager::openEditor(LocatorFilterEntry) will be used instead. */ + Acceptor acceptor; /* icon to display along with the entry */ std::optional displayIcon; /* file path, if the entry is related to a file, is used e.g. for resolving a file icon */ @@ -108,6 +118,76 @@ struct LocatorFilterEntry } }; +using LocatorFilterEntries = QList; + +class CORE_EXPORT LocatorStorage final +{ +public: + LocatorStorage() = default; + QString input() const; + void reportOutput(const LocatorFilterEntries &outputData) const; + +private: + friend class LocatorMatcher; + LocatorStorage(const std::shared_ptr &priv) { d = priv; } + void finalize() const; + std::shared_ptr d; +}; + +class CORE_EXPORT LocatorMatcherTask final +{ +public: + // The main task. Initial data (searchTerm) should be taken from storage.input(). + // Results reporting is done via the storage.reportOutput(). + Tasking::TaskItem task = Tasking::Group{}; + + // When constructing the task, don't place the storage inside the task above. + Tasking::TreeStorage storage; +}; + +using LocatorMatcherTasks = QList; +using LocatorMatcherTaskCreator = std::function; +class LocatorMatcherPrivate; + +enum class MatcherType { + AllSymbols, + Classes, + Functions, + CurrentDocumentSymbols +}; + +class CORE_EXPORT LocatorMatcher final : public QObject +{ + Q_OBJECT + +public: + LocatorMatcher(); + ~LocatorMatcher(); + void setTasks(const LocatorMatcherTasks &tasks); + void setInputData(const QString &inputData); + void setParallelLimit(int limit); // by default 0 = parallel + void start(); + void stop(); + + bool isRunning() const; + // Total data collected so far, even when running. + LocatorFilterEntries outputData() const; + + // Note: Starts internal event loop. + static LocatorFilterEntries runBlocking(const LocatorMatcherTasks &tasks, + const QString &input, int parallelLimit = 0); + + static void addMatcherCreator(MatcherType type, const LocatorMatcherTaskCreator &creator); + static LocatorMatcherTasks matchers(MatcherType type); + +signals: + void serialOutputDataReady(const LocatorFilterEntries &serialOutputData); + void done(bool success); + +private: + std::unique_ptr d; +}; + class CORE_EXPORT ILocatorFilter : public QObject { Q_OBJECT @@ -126,8 +206,6 @@ public: ILocatorFilter(QObject *parent = nullptr); ~ILocatorFilter() override; - static const QList allLocatorFilters(); - Utils::Id id() const; Utils::Id actionId() const; @@ -149,15 +227,6 @@ public: std::optional defaultSearchText() const; void setDefaultSearchText(const QString &defaultSearchText); - virtual void prepareSearch(const QString &entry); - - virtual QList matchesFor(QFutureInterface &future, const QString &entry) = 0; - - virtual void accept(const LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const = 0; - - virtual void refresh(QFutureInterface &future) { Q_UNUSED(future) }; - virtual QByteArray saveState() const; virtual void restoreState(const QByteArray &state); @@ -188,6 +257,9 @@ public: public slots: void setEnabled(bool enabled); +signals: + void enabledChanged(bool enabled); + protected: void setHidden(bool hidden); void setId(Utils::Id id); @@ -198,9 +270,18 @@ protected: virtual void saveState(QJsonObject &object) const; virtual void restoreState(const QJsonObject &object); + void setRefreshRecipe(const std::optional &recipe); + std::optional refreshRecipe() const; + static bool isOldSetting(const QByteArray &state); private: + virtual LocatorMatcherTasks matchers() = 0; + + friend class Internal::Locator; + friend class Internal::LocatorWidget; + static const QList allLocatorFilters(); + Utils::Id m_id; QString m_shortcut; Priority m_priority = Medium; @@ -208,6 +289,7 @@ private: QString m_description; QString m_defaultShortcut; std::optional m_defaultSearchText; + std::optional m_refreshRecipe; QKeySequence m_defaultKeySequence; bool m_defaultIncludedByDefault = false; bool m_includedByDefault = m_defaultIncludedByDefault; @@ -216,4 +298,37 @@ private: bool m_isConfigurable = true; }; +class CORE_EXPORT LocatorFileCache final +{ + Q_DISABLE_COPY_MOVE(LocatorFileCache) + +public: + // Always called from non-main thread. + using FilePathsGenerator = std::function &)>; + // Always called from main thread. + using GeneratorProvider = std::function; + + LocatorFileCache(); + + void invalidate(); + void setFilePathsGenerator(const FilePathsGenerator &generator); + void setFilePaths(const Utils::FilePaths &filePaths); + void setGeneratorProvider(const GeneratorProvider &provider); + + std::optional filePaths() const; + + static FilePathsGenerator filePathsGenerator(const Utils::FilePaths &filePaths); + LocatorMatcherTask matcher() const; + + using MatchedEntries = std::array; + static Utils::FilePaths processFilePaths(const QFuture &future, + const Utils::FilePaths &filePaths, + bool hasPathSeparator, + const QRegularExpression ®Exp, + const Utils::Link &inputLink, + LocatorFileCache::MatchedEntries *entries); +private: + std::shared_ptr d; +}; + } // namespace Core diff --git a/src/plugins/coreplugin/locator/javascriptfilter.cpp b/src/plugins/coreplugin/locator/javascriptfilter.cpp index b97b054051f..3656d673205 100644 --- a/src/plugins/coreplugin/locator/javascriptfilter.cpp +++ b/src/plugins/coreplugin/locator/javascriptfilter.cpp @@ -5,122 +5,434 @@ #include "../coreplugintr.h" +#include + +#include +#include + #include #include #include +#include +#include +#include -namespace Core { -namespace Internal { +#include -enum class EngineAction { Reset = 1, Abort }; +using namespace Core; +using namespace Core::Internal; +using namespace Tasking; +using namespace Utils; + +using namespace std::chrono_literals; + +static const char s_initData[] = R"( + function abs(x) { return Math.abs(x); } + function acos(x) { return Math.acos(x); } + function asin(x) { return Math.asin(x); } + function atan(x) { return Math.atan(x); } + function atan2(x, y) { return Math.atan2(x, y); } + function bin(x) { return '0b' + x.toString(2); } + function ceil(x) { return Math.ceil(x); } + function cos(x) { return Math.cos(x); } + function exp(x) { return Math.exp(x); } + function e() { return Math.E; } + function floor(x) { return Math.floor(x); } + function hex(x) { return '0x' + x.toString(16); } + function log(x) { return Math.log(x); } + function max() { return Math.max.apply(null, arguments); } + function min() { return Math.min.apply(null, arguments); } + function oct(x) { return '0' + x.toString(8); } + function pi() { return Math.PI; } + function pow(x, y) { return Math.pow(x, y); } + function random() { return Math.random(); } + function round(x) { return Math.round(x); } + function sin(x) { return Math.sin(x); } + function sqrt(x) { return Math.sqrt(x); } + function tan(x) { return Math.tan(x); } +)"; + +enum class JavaScriptResult { + FinishedWithSuccess, + FinishedWithError, + TimedOut, + Canceled +}; + +class JavaScriptOutput +{ +public: + QString m_output; + JavaScriptResult m_result = JavaScriptResult::Canceled; +}; + +using JavaScriptCallback = std::function; + +class JavaScriptInput +{ +public: + bool m_reset = false; // Recreates the QJSEngine, re-inits it and continues the request queue + QString m_input; + JavaScriptCallback m_callback = {}; +}; + +class JavaScriptThread : public QObject +{ + Q_OBJECT + +public: + // Called from the other thread, scheduled from the main thread through the queued + // invocation. + void run(); + + // Called from main thread exclusively + void cancel(); + // Called from main thread exclusively + int addRequest(const JavaScriptInput &input); + // Called from main thread exclusively + void removeRequest(int id); + + // Called from the main thread exclusively, scheduled from the other thread through the queued + // invocation when the new result is ready. + void flush(); + +signals: + void newOutput(); + +private: + struct QueueItem { + int m_id = 0; + JavaScriptInput m_input; + std::optional m_output = {}; + }; + + // Called from the main thread exclusively + QList takeOutputQueue() { + QMutexLocker locker(&m_mutex); + return std::exchange(m_outputQueue, {}); + } + + int m_maxId = 0; + std::unique_ptr m_engine; + + mutable QMutex m_mutex; + QWaitCondition m_waitCondition; + bool m_canceled = false; + QList m_inputQueue; + std::optional m_currentItem; + QList m_outputQueue; +}; + +void JavaScriptThread::run() +{ + const auto evaluate = [this](const QString &input) { + const QJSValue result = m_engine->evaluate(input); + if (m_engine->isInterrupted()) { + return JavaScriptOutput{Tr::tr("The evaluation was interrupted."), + JavaScriptResult::Canceled}; + } + return JavaScriptOutput{result.toString(), + result.isError() ? JavaScriptResult::FinishedWithError + : JavaScriptResult::FinishedWithSuccess}; + }; + const auto reset = [evaluate] { + JavaScriptOutput output = evaluate(s_initData); + output.m_output = output.m_result == JavaScriptResult::FinishedWithSuccess + ? Tr::tr("Engine reinitialized properly.") + : Tr::tr("Engine did not reinitialize properly."); + return output; + }; + + { + QMutexLocker locker(&m_mutex); + if (m_canceled) + return; + m_engine.reset(new QJSEngine); + } + + // TODO: consider placing a reset request as the first input instead + const JavaScriptOutput output = reset(); + QTC_ASSERT(output.m_result == JavaScriptResult::FinishedWithSuccess, + qWarning() << output.m_output); + + QueueItem currentItem; + while (true) { + { + QMutexLocker locker(&m_mutex); + if (m_canceled) + return; + if (m_currentItem) { + QTC_CHECK(m_currentItem->m_id == currentItem.m_id); + m_outputQueue.append(currentItem); + m_currentItem = {}; + emit newOutput(); + } + while (m_inputQueue.isEmpty()) { + m_waitCondition.wait(&m_mutex); + if (m_canceled) + return; + } + m_currentItem = currentItem = m_inputQueue.takeFirst(); + if (currentItem.m_input.m_reset) + m_engine.reset(new QJSEngine); + m_engine->setInterrupted(false); + } + const JavaScriptInput &input = currentItem.m_input; + if (input.m_reset) { + currentItem.m_output = reset(); + QTC_ASSERT(currentItem.m_output->m_result == JavaScriptResult::FinishedWithSuccess, + qWarning() << currentItem.m_output->m_output); + continue; + } + currentItem.m_output = evaluate(input.m_input); + } +} + +void JavaScriptThread::cancel() +{ + QMutexLocker locker(&m_mutex); + m_canceled = true; + if (m_engine) // we may be canceling before the run() started + m_engine->setInterrupted(true); + m_waitCondition.wakeOne(); +} + +int JavaScriptThread::addRequest(const JavaScriptInput &input) +{ + QMutexLocker locker(&m_mutex); + if (input.m_reset) { + if (m_currentItem) { + m_outputQueue += *m_currentItem; + m_engine->setInterrupted(true); + } + m_outputQueue += m_inputQueue; + m_currentItem = {}; + m_inputQueue.clear(); + for (int i = 0; i < m_outputQueue.size(); ++i) + m_outputQueue[i].m_output = {{}, JavaScriptResult::Canceled}; + QMetaObject::invokeMethod(this, &JavaScriptThread::newOutput, Qt::QueuedConnection); + } + m_inputQueue.append({++m_maxId, input}); + m_waitCondition.wakeOne(); + return m_maxId; +} + +void JavaScriptThread::removeRequest(int id) +{ + QMutexLocker locker(&m_mutex); + if (m_currentItem && m_currentItem->m_id == id) { + m_currentItem = {}; + m_engine->setInterrupted(true); + m_waitCondition.wakeOne(); + return; + } + const auto predicate = [id](const QueueItem &item) { return item.m_id == id; }; + if (Utils::eraseOne(m_inputQueue, predicate)) + return; + Utils::eraseOne(m_outputQueue, predicate); +} + +void JavaScriptThread::flush() +{ + const QList outputQueue = takeOutputQueue(); + for (const QueueItem &item : outputQueue) { + if (item.m_input.m_callback) + item.m_input.m_callback(*item.m_output); + } +} + +class JavaScriptEngine : public QObject +{ + Q_OBJECT + +public: + JavaScriptEngine() : m_javaScriptThread(new JavaScriptThread) { + connect(m_javaScriptThread, &JavaScriptThread::newOutput, this, [this] { + m_javaScriptThread->flush(); + }); + m_javaScriptThread->moveToThread(&m_thread); + QObject::connect(&m_thread, &QThread::finished, m_javaScriptThread, &QObject::deleteLater); + m_thread.start(); + QMetaObject::invokeMethod(m_javaScriptThread, &JavaScriptThread::run); + } + ~JavaScriptEngine() { + m_javaScriptThread->cancel(); + m_thread.quit(); + m_thread.wait(); + } + + int addRequest(const JavaScriptInput &input) { return m_javaScriptThread->addRequest(input); } + void removeRequest(int id) { m_javaScriptThread->removeRequest(id); } + +private: + QThread m_thread; + JavaScriptThread *m_javaScriptThread = nullptr; +}; + +class JavaScriptRequest : public QObject +{ + Q_OBJECT + +public: + virtual ~JavaScriptRequest() + { + if (m_engine && m_id) // In order to not to invoke a response callback anymore + m_engine->removeRequest(*m_id); + } + + void setEngine(JavaScriptEngine *engine) { + QTC_ASSERT(!isRunning(), return); + m_engine = engine; + } + void setReset(bool reset) { + QTC_ASSERT(!isRunning(), return); + m_input.m_reset = reset; // Reply: "Engine has been reset"? + } + void setEvaluateData(const QString &input) { + QTC_ASSERT(!isRunning(), return); + m_input.m_input = input; + } + void setTimeout(std::chrono::milliseconds timeout) { + QTC_ASSERT(!isRunning(), return); + m_timeout = timeout; + } + + void start() { + QTC_ASSERT(!isRunning(), return); + QTC_ASSERT(m_engine, return); + + JavaScriptInput input = m_input; + input.m_callback = [this](const JavaScriptOutput &output) { + m_timer.reset(); + m_output = output; + m_id = {}; + emit done(output.m_result == JavaScriptResult::FinishedWithSuccess); + }; + m_id = m_engine->addRequest(input); + if (m_timeout > 0ms) { + m_timer.reset(new QTimer); + m_timer->setSingleShot(true); + m_timer->setInterval(m_timeout); + connect(m_timer.get(), &QTimer::timeout, this, [this] { + if (m_engine && m_id) + m_engine->removeRequest(*m_id); + m_timer.release()->deleteLater(); + m_id = {}; + m_output = {Tr::tr("Engine aborted after timeout."), JavaScriptResult::Canceled}; + emit done(false); + }); + m_timer->start(); + } + } + + bool isRunning() const { return m_id.has_value(); } + JavaScriptOutput output() const { return m_output; } + +signals: + void done(bool success); + +private: + QPointer m_engine; + JavaScriptInput m_input; + std::chrono::milliseconds m_timeout = 1000ms; + + std::unique_ptr m_timer; + + std::optional m_id; + JavaScriptOutput m_output; +}; + +class JavaScriptRequestAdapter : public TaskAdapter +{ +public: + JavaScriptRequestAdapter() { connect(task(), &JavaScriptRequest::done, + this, &TaskInterface::done); } + void start() final { task()->start(); } +}; + +TASKING_DECLARE_TASK(JavaScriptRequestTask, JavaScriptRequestAdapter); + +namespace Core::Internal { JavaScriptFilter::JavaScriptFilter() { setId("JavaScriptFilter"); setDisplayName(Tr::tr("Evaluate JavaScript")); setDescription(Tr::tr("Evaluates arbitrary JavaScript expressions and copies the result.")); - setDefaultIncludedByDefault(false); setDefaultShortcutString("="); - m_abortTimer.setSingleShot(true); - m_abortTimer.setInterval(1000); - connect(&m_abortTimer, &QTimer::timeout, this, [this] { - m_aborted = true; - if (m_engine) - m_engine->setInterrupted(true); - }); } -JavaScriptFilter::~JavaScriptFilter() +JavaScriptFilter::~JavaScriptFilter() = default; + +LocatorMatcherTasks JavaScriptFilter::matchers() { -} + TreeStorage storage; + if (!m_javaScriptEngine) + m_javaScriptEngine.reset(new JavaScriptEngine); + QPointer engine = m_javaScriptEngine.get(); -void JavaScriptFilter::prepareSearch(const QString &entry) -{ - Q_UNUSED(entry) - - if (!m_engine) - setupEngine(); - m_engine->setInterrupted(false); - m_aborted = false; - m_abortTimer.start(); -} - -QList JavaScriptFilter::matchesFor( - QFutureInterface &future, const QString &entry) -{ - Q_UNUSED(future) - - QList entries; - if (entry.trimmed().isEmpty()) { - entries.append({this, Tr::tr("Reset Engine"), QVariant::fromValue(EngineAction::Reset)}); - } else { - const QString result = m_engine->evaluate(entry).toString(); - if (m_aborted) { - const QString message = entry + " = " + Tr::tr("Engine aborted after timeout."); - entries.append({this, message, QVariant::fromValue(EngineAction::Abort)}); - } else { - const QString expression = entry + " = " + result; - entries.append({this, expression}); - entries.append({this, Tr::tr("Copy to clipboard: %1").arg(result), result}); - entries.append({this, Tr::tr("Copy to clipboard: %1").arg(expression), expression}); + const auto onSetup = [storage, engine] { + if (!engine) + return TaskAction::StopWithError; + if (storage->input().trimmed().isEmpty()) { + LocatorFilterEntry entry; + entry.displayName = Tr::tr("Reset Engine"); + entry.acceptor = [engine] { + if (engine) { + JavaScriptInput request; + request.m_reset = true; + engine->addRequest(request); // TODO: timeout not handled + } + return AcceptResult(); + }; + storage->reportOutput({entry}); + return TaskAction::StopWithDone; } - } + return TaskAction::Continue; + }; - return entries; + const auto onJavaScriptSetup = [storage, engine](JavaScriptRequest &request) { + request.setEngine(engine); + request.setEvaluateData(storage->input()); + }; + const auto onJavaScriptDone = [storage](const JavaScriptRequest &request) { + const auto acceptor = [](const QString &clipboardContents) { + return [clipboardContents] { + QGuiApplication::clipboard()->setText(clipboardContents); + return AcceptResult(); + }; + }; + const QString input = storage->input(); + const QString result = request.output().m_output; + const QString expression = input + " = " + result; + + LocatorFilterEntry entry; + entry.displayName = expression; + + LocatorFilterEntry copyResultEntry; + copyResultEntry.displayName = Tr::tr("Copy to clipboard: %1").arg(result); + copyResultEntry.acceptor = acceptor(result); + + LocatorFilterEntry copyExpressionEntry; + copyExpressionEntry.displayName = Tr::tr("Copy to clipboard: %1").arg(expression); + copyExpressionEntry.acceptor = acceptor(expression); + + storage->reportOutput({entry, copyResultEntry, copyExpressionEntry}); + }; + const auto onJavaScriptError = [storage](const JavaScriptRequest &request) { + LocatorFilterEntry entry; + entry.displayName = request.output().m_output; + storage->reportOutput({entry}); + }; + + const Group root { + onGroupSetup(onSetup), + JavaScriptRequestTask(onJavaScriptSetup, onJavaScriptDone, onJavaScriptError) + }; + + return {{root, storage}}; } -void JavaScriptFilter::accept(const LocatorFilterEntry &selection, QString *newText, - int *selectionStart, int *selectionLength) const -{ - Q_UNUSED(newText) - Q_UNUSED(selectionStart) - Q_UNUSED(selectionLength) +} // namespace Core::Internal - if (selection.internalData.isNull()) - return; - - const EngineAction action = selection.internalData.value(); - if (action == EngineAction::Reset) { - m_engine.reset(); - return; - } - if (action == EngineAction::Abort) - return; - - QClipboard *clipboard = QGuiApplication::clipboard(); - clipboard->setText(selection.internalData.toString()); -} - -void JavaScriptFilter::setupEngine() -{ - m_engine.reset(new QJSEngine); - m_engine->evaluate( - "function abs(x) { return Math.abs(x); }\n" - "function acos(x) { return Math.acos(x); }\n" - "function asin(x) { return Math.asin(x); }\n" - "function atan(x) { return Math.atan(x); }\n" - "function atan2(x, y) { return Math.atan2(x, y); }\n" - "function bin(x) { return '0b' + x.toString(2); }\n" - "function ceil(x) { return Math.ceil(x); }\n" - "function cos(x) { return Math.cos(x); }\n" - "function exp(x) { return Math.exp(x); }\n" - "function e() { return Math.E; }\n" - "function floor(x) { return Math.floor(x); }\n" - "function hex(x) { return '0x' + x.toString(16); }\n" - "function log(x) { return Math.log(x); }\n" - "function max() { return Math.max.apply(null, arguments); }\n" - "function min() { return Math.min.apply(null, arguments); }\n" - "function oct(x) { return '0' + x.toString(8); }\n" - "function pi() { return Math.PI; }\n" - "function pow(x, y) { return Math.pow(x, y); }\n" - "function random() { return Math.random(); }\n" - "function round(x) { return Math.round(x); }\n" - "function sin(x) { return Math.sin(x); }\n" - "function sqrt(x) { return Math.sqrt(x); }\n" - "function tan(x) { return Math.tan(x); }\n"); -} - -} // namespace Internal -} // namespace Core - -Q_DECLARE_METATYPE(Core::Internal::EngineAction) +#include "javascriptfilter.moc" diff --git a/src/plugins/coreplugin/locator/javascriptfilter.h b/src/plugins/coreplugin/locator/javascriptfilter.h index 5153e0b7b56..8b7112fd27d 100644 --- a/src/plugins/coreplugin/locator/javascriptfilter.h +++ b/src/plugins/coreplugin/locator/javascriptfilter.h @@ -5,38 +5,19 @@ #include -#include +class JavaScriptEngine; -#include -#include - -QT_BEGIN_NAMESPACE -class QJSEngine; -QT_END_NAMESPACE - -namespace Core { -namespace Internal { +namespace Core::Internal { class JavaScriptFilter : public Core::ILocatorFilter { - Q_OBJECT public: JavaScriptFilter(); - ~JavaScriptFilter() override; - - void prepareSearch(const QString &entry) override; - QList matchesFor(QFutureInterface &future, - const QString &entry) override; - void accept(const Core::LocatorFilterEntry &selection, QString *newText, - int *selectionStart, int *selectionLength) const override; + ~JavaScriptFilter(); private: - void setupEngine(); - - mutable std::unique_ptr m_engine; - QTimer m_abortTimer; - std::atomic_bool m_aborted = false; + LocatorMatcherTasks matchers() final; + std::unique_ptr m_javaScriptEngine; }; -} // namespace Internal -} // namespace Core +} // namespace Core::Internal diff --git a/src/plugins/coreplugin/locator/locator.cpp b/src/plugins/coreplugin/locator/locator.cpp index afc060385bc..2deaef9fe09 100644 --- a/src/plugins/coreplugin/locator/locator.cpp +++ b/src/plugins/coreplugin/locator/locator.cpp @@ -26,8 +26,10 @@ #include "../settingsdatabase.h" #include "../statusbarmanager.h" +#include + #include -#include +#include #include #include @@ -66,6 +68,7 @@ public: LocatorData::LocatorData() { + m_urlFilter.setDescription(Tr::tr("Triggers a web search with the selected search engine.")); m_urlFilter.setDefaultShortcutString("r"); m_urlFilter.addDefaultUrl("https://www.bing.com/search?q=%1"); m_urlFilter.addDefaultUrl("https://www.google.com/search?q=%1"); @@ -75,6 +78,7 @@ LocatorData::LocatorData() "http://en.cppreference.com/mwiki/index.php?title=Special%3ASearch&search=%1"); m_urlFilter.addDefaultUrl("https://en.wikipedia.org/w/index.php?search=%1"); + m_bugFilter.setDescription(Tr::tr("Triggers a search in the Qt bug tracker.")); m_bugFilter.setDefaultShortcutString("bug"); m_bugFilter.addDefaultUrl("https://bugreports.qt.io/secure/QuickSearch.jspa?searchString=%1"); } @@ -143,13 +147,10 @@ bool Locator::delayedInitialize() return true; } -ExtensionSystem::IPlugin::ShutdownFlag Locator::aboutToShutdown( - const std::function &emitAsynchronousShutdownFinished) +void Locator::aboutToShutdown() { - m_shuttingDown = true; m_refreshTimer.stop(); m_taskTree.reset(); - return LocatorWidget::aboutToShutdown(emitAsynchronousShutdownFinished); } void Locator::loadSettings() @@ -373,7 +374,7 @@ void Locator::setUseCenteredPopupForShortcut(bool center) void Locator::refresh(const QList &filters) { - if (m_shuttingDown) + if (ExtensionSystem::PluginManager::isShuttingDown()) return; m_taskTree.reset(); // Superfluous, just for clarity. The next reset() below is enough. @@ -382,14 +383,16 @@ void Locator::refresh(const QList &filters) using namespace Tasking; QList tasks{parallel}; for (ILocatorFilter *filter : std::as_const(m_refreshingFilters)) { - const auto setupRefresh = [filter](AsyncTask &async) { - async.setAsyncCallData(&ILocatorFilter::refresh, filter); + const auto task = filter->refreshRecipe(); + if (!task.has_value()) + continue; + + const Group group { + finishAllAndDone, + *task, + onGroupDone([this, filter] { m_refreshingFilters.removeOne(filter); }) }; - const auto onRefreshDone = [this, filter](const AsyncTask &async) { - Q_UNUSED(async) - m_refreshingFilters.removeOne(filter); - }; - tasks.append(Async(setupRefresh, onRefreshDone)); + tasks.append(group); } m_taskTree.reset(new TaskTree{tasks}); diff --git a/src/plugins/coreplugin/locator/locator.h b/src/plugins/coreplugin/locator/locator.h index 59da8767d8a..45d4b650508 100644 --- a/src/plugins/coreplugin/locator/locator.h +++ b/src/plugins/coreplugin/locator/locator.h @@ -13,7 +13,7 @@ #include -namespace Utils { class TaskTree; } +namespace Tasking { class TaskTree; } namespace Core { namespace Internal { @@ -30,8 +30,7 @@ public: ~Locator() override; static Locator *instance(); - ExtensionSystem::IPlugin::ShutdownFlag aboutToShutdown( - const std::function &emitAsynchronousShutdownFinished); + void aboutToShutdown(); void initialize(); void extensionsInitialized(); @@ -68,14 +67,13 @@ private: bool useCenteredPopup = false; }; - bool m_shuttingDown = false; bool m_settingsInitialized = false; Settings m_settings; QList m_filters; QList m_customFilters; QMap m_filterActionMap; QTimer m_refreshTimer; - std::unique_ptr m_taskTree; + std::unique_ptr m_taskTree; QList m_refreshingFilters; }; diff --git a/src/plugins/coreplugin/locator/locator_test.cpp b/src/plugins/coreplugin/locator/locator_test.cpp index eca70f2eaab..c83a2d8779c 100644 --- a/src/plugins/coreplugin/locator/locator_test.cpp +++ b/src/plugins/coreplugin/locator/locator_test.cpp @@ -3,35 +3,22 @@ #include "../coreplugin.h" -#include "basefilefilter.h" #include "locatorfiltertest.h" #include #include -#include #include #include -#include #include using namespace Core::Tests; +using namespace Utils; namespace { QTC_DECLARE_MYTESTDATADIR("../../../tests/locators/") -class MyBaseFileFilter : public Core::BaseFileFilter -{ -public: - MyBaseFileFilter(const Utils::FilePaths &theFiles) - { - setFileIterator(new BaseFileFilter::ListIterator(theFiles)); - } - - void refresh(QFutureInterface &) override {} -}; - class ReferenceData { public: @@ -53,14 +40,13 @@ void Core::Internal::CorePlugin::test_basefilefilter() QFETCH(QStringList, testFiles); QFETCH(QList, referenceDataList); - MyBaseFileFilter filter(Utils::FileUtils::toFilePathList(testFiles)); - BasicLocatorFilterTest test(&filter); - + LocatorFileCache cache; + cache.setFilePaths(FileUtils::toFilePathList(testFiles)); + const LocatorMatcherTasks tasks = {cache.matcher()}; for (const ReferenceData &reference : std::as_const(referenceDataList)) { - const QList filterEntries = test.matchesFor(reference.searchText); + const LocatorFilterEntries filterEntries = LocatorMatcher::runBlocking( + tasks, reference.searchText); const ResultDataList results = ResultData::fromFilterEntryList(filterEntries); -// QTextStream(stdout) << "----" << endl; -// ResultData::printFilterEntries(results); QCOMPARE(results, reference.results); } } @@ -68,7 +54,7 @@ void Core::Internal::CorePlugin::test_basefilefilter() void Core::Internal::CorePlugin::test_basefilefilter_data() { auto shortNativePath = [](const QString &file) { - return Utils::FilePath::fromString(file).shortNativePath(); + return FilePath::fromString(file).shortNativePath(); }; QTest::addColumn("testFiles"); diff --git a/src/plugins/coreplugin/locator/locatorfiltersfilter.cpp b/src/plugins/coreplugin/locator/locatorfiltersfilter.cpp index 800c9171dbc..782cb3f5576 100644 --- a/src/plugins/coreplugin/locator/locatorfiltersfilter.cpp +++ b/src/plugins/coreplugin/locator/locatorfiltersfilter.cpp @@ -7,10 +7,9 @@ #include "../actionmanager/actionmanager.h" #include "../coreplugintr.h" -#include #include -Q_DECLARE_METATYPE(Core::ILocatorFilter*) +using namespace Utils; namespace Core::Internal { @@ -25,65 +24,45 @@ LocatorFiltersFilter::LocatorFiltersFilter(): setConfigurable(false); } -void LocatorFiltersFilter::prepareSearch(const QString &entry) +LocatorMatcherTasks LocatorFiltersFilter::matchers() { - m_filterShortcutStrings.clear(); - m_filterDisplayNames.clear(); - m_filterDescriptions.clear(); - if (!entry.isEmpty()) - return; + using namespace Tasking; - QMap uniqueFilters; - const QList allFilters = Locator::filters(); - for (ILocatorFilter *filter : allFilters) { - const QString filterId = filter->shortcutString() + ',' + filter->displayName(); - uniqueFilters.insert(filterId, filter); - } + TreeStorage storage; - for (ILocatorFilter *filter : std::as_const(uniqueFilters)) { - if (!filter->shortcutString().isEmpty() && !filter->isHidden() && filter->isEnabled()) { - m_filterShortcutStrings.append(filter->shortcutString()); - m_filterDisplayNames.append(filter->displayName()); - m_filterDescriptions.append(filter->description()); - QString keyboardShortcut; - if (auto command = ActionManager::command(filter->actionId())) - keyboardShortcut = command->keySequence().toString(QKeySequence::NativeText); - m_filterKeyboardShortcuts.append(keyboardShortcut); + const auto onSetup = [storage, icon = m_icon] { + if (!storage->input().isEmpty()) + return; + + QMap uniqueFilters; + const QList allFilters = Locator::filters(); + for (ILocatorFilter *filter : allFilters) { + const QString filterId = filter->shortcutString() + ',' + filter->displayName(); + uniqueFilters.insert(filterId, filter); } - } -} -QList LocatorFiltersFilter::matchesFor(QFutureInterface &future, const QString &entry) -{ - Q_UNUSED(entry) // search is already done in the GUI thread in prepareSearch - QList entries; - for (int i = 0; i < m_filterShortcutStrings.size(); ++i) { - if (future.isCanceled()) - break; - LocatorFilterEntry filterEntry(this, - m_filterShortcutStrings.at(i), - i, - m_icon); - filterEntry.extraInfo = m_filterDisplayNames.at(i); - filterEntry.toolTip = m_filterDescriptions.at(i); - filterEntry.displayExtra = m_filterKeyboardShortcuts.at(i); - entries.append(filterEntry); - } - return entries; -} - -void LocatorFiltersFilter::accept(const LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const -{ - Q_UNUSED(selectionLength) - bool ok; - int index = selection.internalData.toInt(&ok); - QTC_ASSERT(ok && index >= 0 && index < m_filterShortcutStrings.size(), return); - const QString shortcutString = m_filterShortcutStrings.at(index); - if (!shortcutString.isEmpty()) { - *newText = shortcutString + ' '; - *selectionStart = shortcutString.length() + 1; - } + LocatorFilterEntries entries; + for (ILocatorFilter *filter : std::as_const(uniqueFilters)) { + const QString shortcutString = filter->shortcutString(); + if (!shortcutString.isEmpty() && !filter->isHidden() && filter->isEnabled()) { + LocatorFilterEntry entry; + entry.displayName = shortcutString; + entry.acceptor = [shortcutString] { + return AcceptResult{shortcutString + ' ', int(shortcutString.size() + 1)}; + }; + entry.displayIcon = icon; + entry.extraInfo = filter->displayName(); + entry.toolTip = filter->description(); + QString keyboardShortcut; + if (auto command = ActionManager::command(filter->actionId())) + keyboardShortcut = command->keySequence().toString(QKeySequence::NativeText); + entry.displayExtra = keyboardShortcut; + entries.append(entry); + } + } + storage->reportOutput(entries); + }; + return {{Sync(onSetup), storage}}; } } // Core::Internal diff --git a/src/plugins/coreplugin/locator/locatorfiltersfilter.h b/src/plugins/coreplugin/locator/locatorfiltersfilter.h index f4679ec9c10..f6dea092eb6 100644 --- a/src/plugins/coreplugin/locator/locatorfiltersfilter.h +++ b/src/plugins/coreplugin/locator/locatorfiltersfilter.h @@ -5,12 +5,7 @@ #include "ilocatorfilter.h" -#include - -namespace Core { -namespace Internal { - -class Locator; +namespace Core::Internal { /*! This filter provides the user with the list of available Locator filters. @@ -18,19 +13,12 @@ class Locator; */ class LocatorFiltersFilter : public ILocatorFilter { - Q_OBJECT - public: LocatorFiltersFilter(); - // ILocatorFilter - void prepareSearch(const QString &entry) override; - QList matchesFor(QFutureInterface &future, - const QString &entry) override; - void accept(const LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const override; - private: + LocatorMatcherTasks matchers() final; + QStringList m_filterShortcutStrings; QStringList m_filterDisplayNames; QStringList m_filterDescriptions; @@ -38,5 +26,4 @@ private: QIcon m_icon; }; -} // namespace Internal -} // namespace Core +} // namespace Core::Internal diff --git a/src/plugins/coreplugin/locator/locatorfiltertest.cpp b/src/plugins/coreplugin/locator/locatorfiltertest.cpp index c134db16696..152ac117336 100644 --- a/src/plugins/coreplugin/locator/locatorfiltertest.cpp +++ b/src/plugins/coreplugin/locator/locatorfiltertest.cpp @@ -3,52 +3,11 @@ #include "locatorfiltertest.h" -#include "locatorsearchutils.h" - -#include - -#include -#include -#include #include using namespace Core; using namespace Core::Tests; -/*! - \class Core::Tests::BasicLocatorFilterTest - \inmodule QtCreator - \internal -*/ - -/*! - \class Core::Tests::TestDataDir - \inmodule QtCreator - \internal -*/ - -/*! - \namespace Core::Tests - \inmodule QtCreator - \internal -*/ -BasicLocatorFilterTest::BasicLocatorFilterTest(ILocatorFilter *filter) : m_filter(filter) -{ -} - -BasicLocatorFilterTest::~BasicLocatorFilterTest() = default; - -QList BasicLocatorFilterTest::matchesFor(const QString &searchText) -{ - doBeforeLocatorRun(); - m_filter->prepareSearch(searchText); - QFuture locatorSearch = Utils::runAsync( - &Internal::runSearch, QList({m_filter}), searchText); - locatorSearch.waitForFinished(); - doAfterLocatorRun(); - return locatorSearch.results(); -} - /*! \class Core::Tests::ResultData \inmodule QtCreator @@ -71,7 +30,7 @@ bool ResultData::operator==(const ResultData &other) const return textColumn1 == other.textColumn1 && textColumn2 == other.textColumn2 && highlightEqual; } -ResultData::ResultDataList ResultData::fromFilterEntryList(const QList &entries) +ResultData::ResultDataList ResultData::fromFilterEntryList(const LocatorFilterEntries &entries) { ResultDataList result; for (const LocatorFilterEntry &entry : entries) { diff --git a/src/plugins/coreplugin/locator/locatorfiltertest.h b/src/plugins/coreplugin/locator/locatorfiltertest.h index f6d5d595011..2ad18ac2363 100644 --- a/src/plugins/coreplugin/locator/locatorfiltertest.h +++ b/src/plugins/coreplugin/locator/locatorfiltertest.h @@ -10,22 +10,6 @@ namespace Core { namespace Tests { -/// Runs a locator filter for a search text and returns the results. -class CORE_EXPORT BasicLocatorFilterTest -{ -public: - BasicLocatorFilterTest(ILocatorFilter *filter); - virtual ~BasicLocatorFilterTest(); - - QList matchesFor(const QString &searchText = QString()); - -private: - virtual void doBeforeLocatorRun() {} - virtual void doAfterLocatorRun() {} - - ILocatorFilter *m_filter = nullptr; -}; - class CORE_EXPORT ResultData { public: @@ -37,7 +21,7 @@ public: bool operator==(const ResultData &other) const; - static ResultDataList fromFilterEntryList(const QList &entries); + static ResultDataList fromFilterEntryList(const LocatorFilterEntries &entries); /// For debugging and creating reference data static void printFilterEntries(const ResultDataList &entries, const QString &msg = QString()); diff --git a/src/plugins/coreplugin/locator/locatorsearchutils.cpp b/src/plugins/coreplugin/locator/locatorsearchutils.cpp deleted file mode 100644 index 77e460ad4f3..00000000000 --- a/src/plugins/coreplugin/locator/locatorsearchutils.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "locatorsearchutils.h" - -#include - -#include - -void Core::Internal::runSearch(QFutureInterface &future, - const QList &filters, const QString &searchText) -{ - std::unordered_set addedCache; - const bool checkDuplicates = (filters.size() > 1); - const auto duplicatesRemoved = [&](const QList &entries) { - if (!checkDuplicates) - return entries; - QList results; - results.reserve(entries.size()); - for (const LocatorFilterEntry &entry : entries) { - const auto &link = entry.linkForEditor; - if (!link || addedCache.emplace(link->targetFilePath).second) - results.append(entry); - } - return results; - }; - - for (ILocatorFilter *filter : filters) { - if (future.isCanceled()) - break; - const auto results = duplicatesRemoved(filter->matchesFor(future, searchText)); - if (!results.isEmpty()) - future.reportResults(results); - } -} diff --git a/src/plugins/coreplugin/locator/locatorsearchutils.h b/src/plugins/coreplugin/locator/locatorsearchutils.h deleted file mode 100644 index d863b580a67..00000000000 --- a/src/plugins/coreplugin/locator/locatorsearchutils.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "ilocatorfilter.h" - -namespace Core { -namespace Internal { - -void CORE_EXPORT runSearch(QFutureInterface &future, - const QList &filters, - const QString &searchText); - -} // namespace Internal -} // namespace Core diff --git a/src/plugins/coreplugin/locator/locatorsettingspage.cpp b/src/plugins/coreplugin/locator/locatorsettingspage.cpp index 0193a791c37..74c3b45692e 100644 --- a/src/plugins/coreplugin/locator/locatorsettingspage.cpp +++ b/src/plugins/coreplugin/locator/locatorsettingspage.cpp @@ -20,12 +20,15 @@ #include #include +#include #include #include #include #include +#include #include #include +#include using namespace Utils; @@ -77,8 +80,14 @@ QVariant FilterItem::data(int column, int role) const { switch (column) { case FilterName: - if (role == Qt::DisplayRole || role == SortRole) + if (role == SortRole) return m_filter->displayName(); + if (role == Qt::DisplayRole) { + if (m_filter->description().isEmpty()) + return m_filter->displayName(); + return QString("%1
%2") + .arg(m_filter->displayName(), m_filter->description().toHtmlEscaped()); + } break; case FilterPrefix: if (role == Qt::DisplayRole || role == SortRole || role == Qt::EditRole) @@ -92,8 +101,10 @@ QVariant FilterItem::data(int column, int role) const break; } - if (role == Qt::ToolTipRole) - return m_filter->description(); + if (role == Qt::ToolTipRole) { + const QString description = m_filter->description(); + return description.isEmpty() ? QString() : ("" + description.toHtmlEscaped()); + } return QVariant(); } @@ -146,6 +157,97 @@ QVariant CategoryItem::data(int column, int role) const return QVariant(); } +class RichTextDelegate : public QStyledItemDelegate +{ +public: + RichTextDelegate(QObject *parent); + ~RichTextDelegate(); + + QTextDocument &doc() { return m_doc; } + + void setMaxWidth(int width); + int maxWidth() const; + +private: + void paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + + int m_maxWidth = -1; + mutable QTextDocument m_doc; +}; + +RichTextDelegate::RichTextDelegate(QObject *parent) + : QStyledItemDelegate(parent) +{} + +void RichTextDelegate::setMaxWidth(int width) +{ + m_maxWidth = width; + emit sizeHintChanged({}); +} + +int RichTextDelegate::maxWidth() const +{ + return m_maxWidth; +} + +RichTextDelegate::~RichTextDelegate() = default; + +void RichTextDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QStyleOptionViewItem options = option; + initStyleOption(&options, index); + + painter->save(); + QTextOption textOption; + if (m_maxWidth > 0) { + textOption.setWrapMode(QTextOption::WordWrap); + m_doc.setDefaultTextOption(textOption); + if (options.rect.width() > m_maxWidth) + options.rect.setWidth(m_maxWidth); + } + m_doc.setHtml(options.text); + m_doc.setTextWidth(options.rect.width()); + options.text = ""; + options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter); + painter->translate(options.rect.left(), options.rect.top()); + QRect clip(0, 0, options.rect.width(), options.rect.height()); + QAbstractTextDocumentLayout::PaintContext paintContext; + paintContext.palette = options.palette; + painter->setClipRect(clip); + paintContext.clip = clip; + if (qobject_cast(options.widget)->selectionModel()->isSelected(index)) { + QAbstractTextDocumentLayout::Selection selection; + selection.cursor = QTextCursor(&m_doc); + selection.cursor.select(QTextCursor::Document); + selection.format.setBackground(options.palette.brush(QPalette::Highlight)); + selection.format.setForeground(options.palette.brush(QPalette::HighlightedText)); + paintContext.selections << selection; + } + m_doc.documentLayout()->draw(painter, paintContext); + painter->restore(); +} + +QSize RichTextDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyleOptionViewItem options = option; + initStyleOption(&options, index); + QTextOption textOption; + if (m_maxWidth > 0) { + textOption.setWrapMode(QTextOption::WordWrap); + m_doc.setDefaultTextOption(textOption); + if (!options.rect.isValid() || options.rect.width() > m_maxWidth) + options.rect.setWidth(m_maxWidth); + } + m_doc.setHtml(options.text); + m_doc.setTextWidth(options.rect.width()); + return QSize(m_doc.idealWidth(), m_doc.size().height()); +} + class LocatorSettingsWidget : public IOptionsPageWidget { public: @@ -178,8 +280,17 @@ public: m_filterList->setSelectionMode(QAbstractItemView::SingleSelection); m_filterList->setSelectionBehavior(QAbstractItemView::SelectRows); m_filterList->setSortingEnabled(true); - m_filterList->setUniformRowHeights(true); m_filterList->setActivationMode(Utils::DoubleClickActivation); + m_filterList->setAlternatingRowColors(true); + auto nameDelegate = new RichTextDelegate(m_filterList); + connect(m_filterList->header(), + &QHeaderView::sectionResized, + nameDelegate, + [nameDelegate](int col, [[maybe_unused]] int old, int updated) { + if (col == 0) + nameDelegate->setMaxWidth(updated); + }); + m_filterList->setItemDelegateForColumn(0, nameDelegate); m_model = new TreeModel<>(m_filterList); initializeModel(); @@ -230,12 +341,13 @@ public: auto addMenu = new QMenu(addButton); addMenu->addAction(Tr::tr("Files in Directories"), this, [this] { - addCustomFilter(new DirectoryFilter(Id(Constants::CUSTOM_DIRECTORY_FILTER_BASEID) + addCustomFilter(new DirectoryFilter(Utils::Id(Constants::CUSTOM_DIRECTORY_FILTER_BASEID) .withSuffix(m_customFilters.size() + 1))); }); addMenu->addAction(Tr::tr("URL Template"), this, [this] { auto filter = new UrlLocatorFilter( - Id(Constants::CUSTOM_URL_FILTER_BASEID).withSuffix(m_customFilters.size() + 1)); + Utils::Id(Constants::CUSTOM_URL_FILTER_BASEID) + .withSuffix(m_customFilters.size() + 1)); filter->setIsCustomFilter(true); addCustomFilter(filter); }); diff --git a/src/plugins/coreplugin/locator/locatorsettingspage.h b/src/plugins/coreplugin/locator/locatorsettingspage.h index 4818fc2e236..408fe7b91f8 100644 --- a/src/plugins/coreplugin/locator/locatorsettingspage.h +++ b/src/plugins/coreplugin/locator/locatorsettingspage.h @@ -5,16 +5,12 @@ #include -namespace Core { -namespace Internal { +namespace Core::Internal { class LocatorSettingsPage : public IOptionsPage { - Q_OBJECT - public: LocatorSettingsPage(); }; -} // namespace Internal -} // namespace Core +} // Core::Internal diff --git a/src/plugins/coreplugin/locator/locatorwidget.cpp b/src/plugins/coreplugin/locator/locatorwidget.cpp index ba717ea0f98..8af3f4b753d 100644 --- a/src/plugins/coreplugin/locator/locatorwidget.cpp +++ b/src/plugins/coreplugin/locator/locatorwidget.cpp @@ -4,17 +4,15 @@ #include "locatorwidget.h" #include "ilocatorfilter.h" -#include "locator.h" #include "locatorconstants.h" #include "locatormanager.h" -#include "locatorsearchutils.h" #include "../actionmanager/actionmanager.h" #include "../coreplugintr.h" +#include "../editormanager/editormanager.h" #include "../icore.h" #include "../modemanager.h" #include -#include #include #include #include @@ -22,24 +20,20 @@ #include #include #include -#include -#include +#include #include #include #include #include #include -#include #include #include #include +#include #include -#include #include -#include #include -#include Q_DECLARE_METATYPE(Core::LocatorFilterEntry) @@ -50,10 +44,6 @@ const int LocatorEntryRole = int(HighlightingItemRole::User); namespace Core { namespace Internal { -bool LocatorWidget::m_shuttingDown = false; -QFuture LocatorWidget::m_sharedFuture; -LocatorWidget *LocatorWidget::m_sharedFutureOrigin = nullptr; - /* A model to represent the Locator results. */ class LocatorModel : public QAbstractListModel { @@ -67,8 +57,8 @@ public: LocatorModel(QObject *parent = nullptr) : QAbstractListModel(parent) - , mBackgroundColor(Utils::creatorTheme()->color(Utils::Theme::TextColorHighlightBackground)) - , mForegroundColor(Utils::creatorTheme()->color(Utils::Theme::TextColorNormal)) + , m_backgroundColor(Utils::creatorTheme()->color(Theme::TextColorHighlightBackground)) + , m_foregroundColor(Utils::creatorTheme()->color(Theme::TextColorNormal)) {} void clear(); @@ -76,13 +66,13 @@ public: int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - void addEntries(const QList &entries); + void addEntries(const LocatorFilterEntries &entries); private: - mutable QList mEntries; - bool hasExtraInfo = false; - QColor mBackgroundColor; - QColor mForegroundColor; + mutable LocatorFilterEntries m_entries; + bool m_hasExtraInfo = false; + QColor m_backgroundColor; + QColor m_foregroundColor; }; class CompletionDelegate : public HighlightingItemDelegate @@ -93,7 +83,7 @@ public: QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; }; -class CompletionList : public Utils::TreeView +class CompletionList : public TreeView { public: CompletionList(QWidget *parent = nullptr); @@ -144,8 +134,8 @@ protected: void LocatorModel::clear() { beginResetModel(); - mEntries.clear(); - hasExtraInfo = false; + m_entries.clear(); + m_hasExtraInfo = false; endResetModel(); } @@ -153,30 +143,30 @@ int LocatorModel::rowCount(const QModelIndex & parent) const { if (parent.isValid()) return 0; - return mEntries.size(); + return m_entries.size(); } int LocatorModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; - return hasExtraInfo ? ColumnCount : 1; + return m_hasExtraInfo ? ColumnCount : 1; } QVariant LocatorModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() >= mEntries.size()) + if (!index.isValid() || index.row() >= m_entries.size()) return QVariant(); switch (role) { case Qt::DisplayRole: if (index.column() == DisplayNameColumn) - return mEntries.at(index.row()).displayName; + return m_entries.at(index.row()).displayName; else if (index.column() == ExtraInfoColumn) - return mEntries.at(index.row()).extraInfo; + return m_entries.at(index.row()).extraInfo; break; case Qt::ToolTipRole: { - const LocatorFilterEntry &entry = mEntries.at(index.row()); + const LocatorFilterEntry &entry = m_entries.at(index.row()); QString toolTip = entry.displayName; if (!entry.extraInfo.isEmpty()) toolTip += "\n\n" + entry.extraInfo; @@ -186,7 +176,7 @@ QVariant LocatorModel::data(const QModelIndex &index, int role) const } case Qt::DecorationRole: if (index.column() == DisplayNameColumn) { - LocatorFilterEntry &entry = mEntries[index.row()]; + LocatorFilterEntry &entry = m_entries[index.row()]; if (!entry.displayIcon && !entry.filePath.isEmpty()) entry.displayIcon = FileIconProvider::icon(entry.filePath); return entry.displayIcon ? entry.displayIcon.value() : QIcon(); @@ -197,10 +187,10 @@ QVariant LocatorModel::data(const QModelIndex &index, int role) const return QColor(Qt::darkGray); break; case LocatorEntryRole: - return QVariant::fromValue(mEntries.at(index.row())); + return QVariant::fromValue(m_entries.at(index.row())); case int(HighlightingItemRole::StartColumn): case int(HighlightingItemRole::Length): { - const LocatorFilterEntry &entry = mEntries[index.row()]; + const LocatorFilterEntry &entry = m_entries[index.row()]; auto highlights = [&](LocatorFilterEntry::HighlightInfo::DataType type){ const bool startIndexRole = role == int(HighlightingItemRole::StartColumn); return startIndexRole ? QVariant::fromValue(entry.highlightInfo.starts(type)) @@ -214,7 +204,7 @@ QVariant LocatorModel::data(const QModelIndex &index, int role) const } case int(HighlightingItemRole::DisplayExtra): { if (index.column() == LocatorFilterEntry::HighlightInfo::DisplayName) { - LocatorFilterEntry &entry = mEntries[index.row()]; + LocatorFilterEntry &entry = m_entries[index.row()]; if (!entry.displayExtra.isEmpty()) return QString(" (" + entry.displayExtra + ')'); } @@ -223,9 +213,9 @@ QVariant LocatorModel::data(const QModelIndex &index, int role) const case int(HighlightingItemRole::DisplayExtraForeground): return QColor(Qt::darkGray); case int(HighlightingItemRole::Background): - return mBackgroundColor; + return m_backgroundColor; case int(HighlightingItemRole::Foreground): - return mForegroundColor; + return m_foregroundColor; } return QVariant(); @@ -233,14 +223,14 @@ QVariant LocatorModel::data(const QModelIndex &index, int role) const void LocatorModel::addEntries(const QList &entries) { - beginInsertRows(QModelIndex(), mEntries.size(), mEntries.size() + entries.size() - 1); - mEntries.append(entries); + beginInsertRows(QModelIndex(), m_entries.size(), m_entries.size() + entries.size() - 1); + m_entries.append(entries); endInsertRows(); - if (hasExtraInfo) + if (m_hasExtraInfo) return; if (Utils::anyOf(entries, [](const LocatorFilterEntry &e) { return !e.extraInfo.isEmpty();})) { beginInsertColumns(QModelIndex(), 1, 1); - hasExtraInfo = true; + m_hasExtraInfo = true; endInsertColumns(); } } @@ -248,7 +238,7 @@ void LocatorModel::addEntries(const QList &entries) // =========== CompletionList =========== CompletionList::CompletionList(QWidget *parent) - : Utils::TreeView(parent) + : TreeView(parent) { // on macOS and Windows the popup doesn't really get focus, so fake the selection color // which would then just be a very light gray, but should look as if it had focus @@ -265,7 +255,7 @@ CompletionList::CompletionList(QWidget *parent) header()->setStretchLastSection(true); // This is too slow when done on all results //header()->setSectionResizeMode(QHeaderView::ResizeToContents); - if (Utils::HostOsInfo::isMacHost()) { + if (HostOsInfo::isMacHost()) { if (horizontalScrollBar()) horizontalScrollBar()->setAttribute(Qt::WA_MacMiniSize); if (verticalScrollBar()) @@ -416,7 +406,7 @@ LocatorPopup::LocatorPopup(LocatorWidget *locatorWidget, QWidget *parent) m_tree(new CompletionList(this)), m_inputWidget(locatorWidget) { - if (Utils::HostOsInfo::isMacHost()) + if (HostOsInfo::isMacHost()) m_tree->setFrameStyle(QFrame::NoFrame); // tool tip already includes a frame m_tree->setModel(locatorWidget->model()); m_tree->setTextElideMode(Qt::ElideMiddle); @@ -444,9 +434,10 @@ LocatorPopup::LocatorPopup(LocatorWidget *locatorWidget, QWidget *parent) }, Qt::DirectConnection); // must be handled directly before event is deleted connect(m_tree, &QAbstractItemView::activated, locatorWidget, [this, locatorWidget](const QModelIndex &index) { - if (isVisible()) - locatorWidget->scheduleAcceptEntry(index); - }); + if (!index.isValid() || !isVisible()) + return; + locatorWidget->acceptEntry(index.row()); + }); } CompletionList *LocatorPopup::completionList() const @@ -459,29 +450,28 @@ LocatorWidget *LocatorPopup::inputWidget() const return m_inputWidget; } -void LocatorPopup::focusOutEvent(QFocusEvent *event) { +void LocatorPopup::focusOutEvent(QFocusEvent *event) +{ if (event->reason() == Qt::ActiveWindowFocusReason) hide(); QWidget::focusOutEvent(event); } -void CompletionList::next() { +void CompletionList::next() +{ int index = currentIndex().row(); ++index; - if (index >= model()->rowCount(QModelIndex())) { - // wrap - index = 0; - } + if (index >= model()->rowCount(QModelIndex())) + index = 0; // wrap setCurrentIndex(model()->index(index, 0)); } -void CompletionList::previous() { +void CompletionList::previous() +{ int index = currentIndex().row(); --index; - if (index < 0) { - // wrap - index = model()->rowCount(QModelIndex()) - 1; - } + if (index < 0) + index = model()->rowCount(QModelIndex()) - 1; // wrap setCurrentIndex(model()->index(index, 0)); } @@ -510,7 +500,7 @@ void CompletionList::keyPressEvent(QKeyEvent *event) return; case Qt::Key_P: case Qt::Key_N: - if (event->modifiers() == Qt::KeyboardModifiers(Utils::HostOsInfo::controlModifier())) { + if (event->modifiers() == Qt::KeyboardModifiers(HostOsInfo::controlModifier())) { if (event->key() == Qt::Key_P) previous(); else @@ -529,7 +519,7 @@ void CompletionList::keyPressEvent(QKeyEvent *event) } break; } - Utils::TreeView::keyPressEvent(event); + TreeView::keyPressEvent(event); } bool CompletionList::eventFilter(QObject *watched, QEvent *event) @@ -545,14 +535,14 @@ bool CompletionList::eventFilter(QObject *watched, QEvent *event) break; case Qt::Key_P: case Qt::Key_N: - if (ke->modifiers() == Qt::KeyboardModifiers(Utils::HostOsInfo::controlModifier())) { + if (ke->modifiers() == Qt::KeyboardModifiers(HostOsInfo::controlModifier())) { event->accept(); return true; } break; } } - return Utils::TreeView::eventFilter(watched, event); + return TreeView::eventFilter(watched, event); } // =========== LocatorWidget =========== @@ -563,7 +553,7 @@ LocatorWidget::LocatorWidget(Locator *locator) , m_centeredPopupAction(new QAction(Tr::tr("Open as Centered Popup"), this)) , m_refreshAction(new QAction(Tr::tr("Refresh"), this)) , m_configureAction(new QAction(ICore::msgShowOptionsDialog(), this)) - , m_fileLineEdit(new Utils::FancyLineEdit) + , m_fileLineEdit(new FancyLineEdit) { setAttribute(Qt::WA_Hover); setFocusProxy(m_fileLineEdit); @@ -581,12 +571,12 @@ LocatorWidget::LocatorWidget(Locator *locator) const QIcon icon = Utils::Icons::MAGNIFIER.icon(); m_fileLineEdit->setFiltering(true); - m_fileLineEdit->setButtonIcon(Utils::FancyLineEdit::Left, icon); - m_fileLineEdit->setButtonToolTip(Utils::FancyLineEdit::Left, Tr::tr("Options")); + m_fileLineEdit->setButtonIcon(FancyLineEdit::Left, icon); + m_fileLineEdit->setButtonToolTip(FancyLineEdit::Left, Tr::tr("Options")); m_fileLineEdit->setFocusPolicy(Qt::ClickFocus); - m_fileLineEdit->setButtonVisible(Utils::FancyLineEdit::Left, true); + m_fileLineEdit->setButtonVisible(FancyLineEdit::Left, true); // We set click focus since otherwise you will always get two popups - m_fileLineEdit->setButtonFocusPolicy(Utils::FancyLineEdit::Left, Qt::ClickFocus); + m_fileLineEdit->setButtonFocusPolicy(FancyLineEdit::Left, Qt::ClickFocus); m_fileLineEdit->setAttribute(Qt::WA_MacShowFocusRect, false); m_fileLineEdit->installEventFilter(this); @@ -597,6 +587,10 @@ LocatorWidget::LocatorWidget(Locator *locator) connect(m_filterMenu, &QMenu::aboutToShow, this, [this] { m_centeredPopupAction->setChecked(Locator::useCenteredPopupForShortcut()); }); + connect(m_filterMenu, &QMenu::hovered, this, [this](QAction *action) { + ToolTip::show(m_filterMenu->mapToGlobal(m_filterMenu->actionGeometry(action).topRight()), + action->toolTip()); + }); connect(m_centeredPopupAction, &QAction::toggled, locator, [locator](bool toggled) { if (toggled != Locator::useCenteredPopupForShortcut()) { Locator::setUseCenteredPopupForShortcut(toggled); @@ -608,27 +602,15 @@ LocatorWidget::LocatorWidget(Locator *locator) m_filterMenu->addAction(m_refreshAction); m_filterMenu->addAction(m_configureAction); - m_fileLineEdit->setButtonMenu(Utils::FancyLineEdit::Left, m_filterMenu); + m_fileLineEdit->setButtonMenu(FancyLineEdit::Left, m_filterMenu); connect(m_refreshAction, &QAction::triggered, locator, [locator] { locator->refresh(Locator::filters()); }); connect(m_configureAction, &QAction::triggered, this, &LocatorWidget::showConfigureDialog); - connect(m_fileLineEdit, &QLineEdit::textChanged, - this, &LocatorWidget::showPopupDelayed); + connect(m_fileLineEdit, &QLineEdit::textChanged, this, &LocatorWidget::showPopupNow); - m_entriesWatcher = new QFutureWatcher(this); - connect(m_entriesWatcher, &QFutureWatcher::resultsReadyAt, - this, &LocatorWidget::addSearchResults); - connect(m_entriesWatcher, &QFutureWatcher::finished, - this, &LocatorWidget::handleSearchFinished); - - m_showPopupTimer.setInterval(100); - m_showPopupTimer.setSingleShot(true); - connect(&m_showPopupTimer, &QTimer::timeout, this, &LocatorWidget::showPopupNow); - - m_progressIndicator = new Utils::ProgressIndicator(Utils::ProgressIndicatorSize::Small, - m_fileLineEdit); + m_progressIndicator = new ProgressIndicator(ProgressIndicatorSize::Small, m_fileLineEdit); m_progressIndicator->raise(); m_progressIndicator->hide(); m_showProgressTimer.setSingleShot(true); @@ -638,7 +620,7 @@ LocatorWidget::LocatorWidget(Locator *locator) Command *locateCmd = ActionManager::command(Constants::LOCATE); if (QTC_GUARD(locateCmd)) { - connect(locateCmd, &Command::keySequenceChanged, this, [this,locateCmd] { + connect(locateCmd, &Command::keySequenceChanged, this, [this, locateCmd] { updatePlaceholderText(locateCmd); }); updatePlaceholderText(locateCmd); @@ -650,12 +632,7 @@ LocatorWidget::LocatorWidget(Locator *locator) updateFilterList(); } -LocatorWidget::~LocatorWidget() -{ - // no need to completely finish a running search, cancel it - if (m_entriesWatcher->future().isRunning()) - m_entriesWatcher->future().cancel(); -} +LocatorWidget::~LocatorWidget() = default; void LocatorWidget::updatePlaceholderText(Command *command) { @@ -670,12 +647,19 @@ void LocatorWidget::updatePlaceholderText(Command *command) void LocatorWidget::updateFilterList() { m_filterMenu->clear(); - const QList filters = Locator::filters(); + const QList filters = Utils::sorted( + Locator::filters(), [](ILocatorFilter *a, ILocatorFilter *b) { + return a->displayName() < b->displayName(); + }); for (ILocatorFilter *filter : filters) { if (filter->shortcutString().isEmpty() || filter->isHidden()) continue; QAction *action = m_filterMenu->addAction(filter->displayName()); - action->setToolTip(filter->description()); + action->setEnabled(filter->isEnabled()); + const QString description = filter->description(); + action->setToolTip(description.isEmpty() ? QString() + : ("" + description.toHtmlEscaped())); + connect(filter, &ILocatorFilter::enabledChanged, action, &QAction::setEnabled); connect(action, &QAction::triggered, this, [this, filter] { Locator::showFilter(filter, this); }); @@ -715,7 +699,7 @@ bool LocatorWidget::eventFilter(QObject *obj, QEvent *event) switch (keyEvent->key()) { case Qt::Key_P: case Qt::Key_N: - if (keyEvent->modifiers() == Qt::KeyboardModifiers(Utils::HostOsInfo::controlModifier())) { + if (keyEvent->modifiers() == Qt::KeyboardModifiers(HostOsInfo::controlModifier())) { event->accept(); return true; } @@ -772,7 +756,7 @@ bool LocatorWidget::eventFilter(QObject *obj, QEvent *event) return true; case Qt::Key_Home: case Qt::Key_End: - if (Utils::HostOsInfo::isMacHost() + if (HostOsInfo::isMacHost() != (keyEvent->modifiers() == Qt::KeyboardModifiers(Qt::ControlModifier))) { emit showPopup(); emit handleKey(keyEvent); @@ -794,7 +778,7 @@ bool LocatorWidget::eventFilter(QObject *obj, QEvent *event) break; case Qt::Key_P: case Qt::Key_N: - if (keyEvent->modifiers() == Qt::KeyboardModifiers(Utils::HostOsInfo::controlModifier())) { + if (keyEvent->modifiers() == Qt::KeyboardModifiers(HostOsInfo::controlModifier())) { emit showPopup(); emit handleKey(keyEvent); return true; @@ -847,16 +831,9 @@ bool LocatorWidget::eventFilter(QObject *obj, QEvent *event) return QWidget::eventFilter(obj, event); } -void LocatorWidget::showPopupDelayed() -{ - m_updateRequested = true; - m_showPopupTimer.start(); -} - void LocatorWidget::showPopupNow() { - m_showPopupTimer.stop(); - updateCompletionList(m_fileLineEdit->text()); + runMatcher(m_fileLineEdit->text()); emit showPopup(); } @@ -894,7 +871,7 @@ void LocatorWidget::setProgressIndicatorVisible(bool visible) return; } const QSize iconSize = m_progressIndicator->sizeHint(); - m_progressIndicator->setGeometry(m_fileLineEdit->button(Utils::FancyLineEdit::Right)->geometry().x() + m_progressIndicator->setGeometry(m_fileLineEdit->button(FancyLineEdit::Right)->geometry().x() - iconSize.width(), (m_fileLineEdit->height() - iconSize.height()) / 2 /*center*/, iconSize.width(), @@ -902,118 +879,78 @@ void LocatorWidget::setProgressIndicatorVisible(bool visible) m_progressIndicator->show(); } -void LocatorWidget::updateCompletionList(const QString &text) +void LocatorWidget::runMatcher(const QString &text) { - if (m_shuttingDown) - return; - - m_updateRequested = true; - if (m_sharedFuture.isRunning()) { - // Cancel the old future. We may not just block the UI thread to wait for the search to - // actually cancel. - m_requestedCompletionText = text; - if (m_sharedFutureOrigin == this) { - // This locator widget is currently running. Make handleSearchFinished trigger another - // update. - m_rerunAfterFinished = true; - } else { - // Another locator widget is running. Trigger another update when that is finished. - Utils::onFinished(m_sharedFuture, this, [this](const QFuture &) { - const QString text = m_requestedCompletionText; - m_requestedCompletionText.clear(); - updateCompletionList(text); - }); - } - m_sharedFuture.cancel(); - return; - } - - m_showProgressTimer.start(); - m_needsClearResult = true; QString searchText; const QList filters = filtersFor(text, searchText); + LocatorMatcherTasks tasks; for (ILocatorFilter *filter : filters) - filter->prepareSearch(searchText); - QFuture future = Utils::runAsync(&runSearch, filters, searchText); - m_sharedFuture = QFuture(future); - m_sharedFutureOrigin = this; - m_entriesWatcher->setFuture(future); -} + tasks += filter->matchers(); -void LocatorWidget::handleSearchFinished() -{ - m_showProgressTimer.stop(); - setProgressIndicatorVisible(false); - m_updateRequested = false; - if (m_rowRequestedForAccept) { - acceptEntry(m_rowRequestedForAccept.value()); - m_rowRequestedForAccept.reset(); - return; - } - if (m_rerunAfterFinished) { - m_rerunAfterFinished = false; - const QString text = m_requestedCompletionText; - m_requestedCompletionText.clear(); - updateCompletionList(text); - return; - } + m_locatorMatcher.reset(new LocatorMatcher); + m_locatorMatcher->setTasks(tasks); + m_locatorMatcher->setInputData(searchText); + m_rowRequestedForAccept.reset(); - if (m_needsClearResult) { - m_locatorModel->clear(); - m_needsClearResult = false; - } -} + std::shared_ptr needsClearResult = std::make_shared(true); + connect(m_locatorMatcher.get(), &LocatorMatcher::done, this, [this, needsClearResult] { + m_showProgressTimer.stop(); + setProgressIndicatorVisible(false); + m_locatorMatcher.release()->deleteLater(); + if (m_rowRequestedForAccept) { + acceptEntry(m_rowRequestedForAccept.value()); + m_rowRequestedForAccept.reset(); + return; + } + if (needsClearResult->exchange(false)) + m_locatorModel->clear(); + }); + connect(m_locatorMatcher.get(), &LocatorMatcher::serialOutputDataReady, + this, [this, needsClearResult](const LocatorFilterEntries &serialOutputData) { + if (needsClearResult->exchange(false)) + m_locatorModel->clear(); + const bool selectFirst = m_locatorModel->rowCount() == 0; + m_locatorModel->addEntries(serialOutputData); + if (selectFirst) { + emit selectRow(0); + if (m_rowRequestedForAccept) + m_rowRequestedForAccept = 0; + } + }); -void LocatorWidget::scheduleAcceptEntry(const QModelIndex &index) -{ - if (m_updateRequested) { - // don't just accept the selected entry, since the list is not up to date - // accept will be called after the update finished - m_rowRequestedForAccept = index.row(); - // do not wait for the rest of the search to finish - m_entriesWatcher->future().cancel(); - } else { - acceptEntry(index.row()); - } -} - -ExtensionSystem::IPlugin::ShutdownFlag LocatorWidget::aboutToShutdown( - const std::function &emitAsynchronousShutdownFinished) -{ - m_shuttingDown = true; - if (m_sharedFuture.isRunning()) { - Utils::onFinished(m_sharedFuture, - Locator::instance(), - [emitAsynchronousShutdownFinished](const QFuture &) { - emitAsynchronousShutdownFinished(); - }); - m_sharedFuture.cancel(); - return ExtensionSystem::IPlugin::AsynchronousShutdown; - } - return ExtensionSystem::IPlugin::SynchronousShutdown; + m_showProgressTimer.start(); + m_locatorMatcher->start(); } void LocatorWidget::acceptEntry(int row) { + if (m_locatorMatcher) { + m_rowRequestedForAccept = row; + return; + } if (row < 0 || row >= m_locatorModel->rowCount()) return; const QModelIndex index = m_locatorModel->index(row, 0); if (!index.isValid()) return; - const LocatorFilterEntry entry = m_locatorModel->data(index, LocatorEntryRole).value(); - Q_ASSERT(entry.filter != nullptr); - QString newText; - int selectionStart = -1; - int selectionLength = 0; + const LocatorFilterEntry entry + = m_locatorModel->data(index, LocatorEntryRole).value(); + + if (!entry.acceptor) { + // Opening editors can open dialogs (e.g. the ssh prompt, or showing erros), so delay until + // we have hidden the popup with emit hidePopup below and Qt actually processed that + QMetaObject::invokeMethod(EditorManager::instance(), + [entry] { EditorManager::openEditor(entry); }, Qt::QueuedConnection); + } QWidget *focusBeforeAccept = QApplication::focusWidget(); - entry.filter->accept(entry, &newText, &selectionStart, &selectionLength); - if (newText.isEmpty()) { + const AcceptResult result = entry.acceptor ? entry.acceptor() : AcceptResult(); + if (result.newText.isEmpty()) { emit hidePopup(); if (QApplication::focusWidget() == focusBeforeAccept) resetFocus(m_previousFocusWidget, isInMainWindow()); } else { - showText(newText, selectionStart, selectionLength); + showText(result.newText, result.selectionStart, result.selectionLength); } } @@ -1049,24 +986,6 @@ void LocatorWidget::showConfigureDialog() ICore::showOptionsDialog(Constants::FILTER_OPTIONS_PAGE); } -void LocatorWidget::addSearchResults(int firstIndex, int endIndex) -{ - if (m_needsClearResult) { - m_locatorModel->clear(); - m_needsClearResult = false; - } - const bool selectFirst = m_locatorModel->rowCount() == 0; - QList entries; - for (int i = firstIndex; i < endIndex; ++i) - entries.append(m_entriesWatcher->resultAt(i)); - m_locatorModel->addEntries(entries); - if (selectFirst) { - emit selectRow(0); - if (m_rowRequestedForAccept) - m_rowRequestedForAccept = 0; - } -} - LocatorWidget *createStaticLocatorWidget(Locator *locator) { auto widget = new LocatorWidget(locator); diff --git a/src/plugins/coreplugin/locator/locatorwidget.h b/src/plugins/coreplugin/locator/locatorwidget.h index eef5a144147..c766637c3dc 100644 --- a/src/plugins/coreplugin/locator/locatorwidget.h +++ b/src/plugins/coreplugin/locator/locatorwidget.h @@ -7,8 +7,8 @@ #include -#include #include +#include #include #include @@ -28,8 +28,7 @@ namespace Internal { class LocatorModel; class CompletionList; -class LocatorWidget - : public QWidget +class LocatorWidget : public QWidget { Q_OBJECT @@ -42,11 +41,7 @@ public: QAbstractItemModel *model() const; void updatePlaceholderText(Command *command); - - void scheduleAcceptEntry(const QModelIndex &index); - - static ExtensionSystem::IPlugin::ShutdownFlag aboutToShutdown( - const std::function &emitAsynchronousShutdownFinished); + void acceptEntry(int row); signals: void showCurrentItemToolTip(); @@ -58,44 +53,30 @@ signals: void showPopup(); private: - void showPopupDelayed(); void showPopupNow(); - void acceptEntry(int row); static void showConfigureDialog(); - void addSearchResults(int firstIndex, int endIndex); - void handleSearchFinished(); void updateFilterList(); bool isInMainWindow() const; void updatePreviousFocusWidget(QWidget *previous, QWidget *current); bool eventFilter(QObject *obj, QEvent *event) override; - void updateCompletionList(const QString &text); + void runMatcher(const QString &text); static QList filtersFor(const QString &text, QString &searchText); void setProgressIndicatorVisible(bool visible); LocatorModel *m_locatorModel = nullptr; - - static bool m_shuttingDown; - static QFuture m_sharedFuture; - static LocatorWidget *m_sharedFutureOrigin; - QMenu *m_filterMenu = nullptr; QAction *m_centeredPopupAction = nullptr; QAction *m_refreshAction = nullptr; QAction *m_configureAction = nullptr; Utils::FancyLineEdit *m_fileLineEdit = nullptr; - QTimer m_showPopupTimer; - QFutureWatcher *m_entriesWatcher = nullptr; - QString m_requestedCompletionText; - bool m_needsClearResult = true; - bool m_updateRequested = false; - bool m_rerunAfterFinished = false; bool m_possibleToolTipRequest = false; QWidget *m_progressIndicator = nullptr; QTimer m_showProgressTimer; std::optional m_rowRequestedForAccept; QPointer m_previousFocusWidget; + std::unique_ptr m_locatorMatcher; }; class LocatorPopup : public QWidget diff --git a/src/plugins/coreplugin/locator/opendocumentsfilter.cpp b/src/plugins/coreplugin/locator/opendocumentsfilter.cpp index f8febc69f30..95e076ec2bd 100644 --- a/src/plugins/coreplugin/locator/opendocumentsfilter.cpp +++ b/src/plugins/coreplugin/locator/opendocumentsfilter.cpp @@ -3,102 +3,63 @@ #include "opendocumentsfilter.h" -#include "basefilefilter.h" #include "../coreplugintr.h" -#include -#include -#include +#include + +#include + +#include +#include +#include -#include -#include #include +using namespace Tasking; using namespace Utils; namespace Core::Internal { +class Entry +{ +public: + Utils::FilePath fileName; + QString displayName; +}; + OpenDocumentsFilter::OpenDocumentsFilter() { setId("Open documents"); setDisplayName(Tr::tr("Open Documents")); + setDescription(Tr::tr("Switches to an open document.")); setDefaultShortcutString("o"); setPriority(High); setDefaultIncludedByDefault(true); - - connect(DocumentModel::model(), &QAbstractItemModel::dataChanged, - this, &OpenDocumentsFilter::slotDataChanged); - connect(DocumentModel::model(), &QAbstractItemModel::rowsInserted, - this, &OpenDocumentsFilter::slotRowsInserted); - connect(DocumentModel::model(), &QAbstractItemModel::rowsRemoved, - this, &OpenDocumentsFilter::slotRowsRemoved); } -void OpenDocumentsFilter::slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, - const QVector &roles) +static void matchEditors(QPromise &promise, const LocatorStorage &storage, + const QList &editorsData) { - Q_UNUSED(roles) - - const int topIndex = std::max(0, topLeft.row() - 1 /**/); - const int bottomIndex = bottomRight.row() - 1 /**/; - - QMutexLocker lock(&m_mutex); - - const QList documentEntries = DocumentModel::entries(); - for (int i = topIndex; i <= bottomIndex; ++i) { - QTC_ASSERT(i < m_editors.size(), break); - DocumentModel::Entry *e = documentEntries.at(i); - m_editors[i] = {e->filePath(), e->displayName()}; - } -} - -void OpenDocumentsFilter::slotRowsInserted(const QModelIndex &, int first, int last) -{ - const int firstIndex = std::max(0, first - 1 /**/); - - QMutexLocker lock(&m_mutex); - - const QList documentEntries = DocumentModel::entries(); - for (int i = firstIndex; i < last; ++i) { - DocumentModel::Entry *e = documentEntries.at(i); - m_editors.insert(i, {e->filePath(), e->displayName()}); - } -} - -void OpenDocumentsFilter::slotRowsRemoved(const QModelIndex &, int first, int last) -{ - QMutexLocker lock(&m_mutex); - - const int firstIndex = std::max(0, first - 1 /**/); - for (int i = firstIndex; i < last; ++i) - m_editors.removeAt(i); -} - -QList OpenDocumentsFilter::matchesFor(QFutureInterface &future, - const QString &entry) -{ - QList goodEntries; - QList betterEntries; - const Link link = Link::fromString(entry, true); - - const QRegularExpression regexp = createRegExp(link.targetFilePath.toString()); + const Link link = Link::fromString(storage.input(), true); + const QRegularExpression regexp = ILocatorFilter::createRegExp(link.targetFilePath.toString()); if (!regexp.isValid()) - return goodEntries; + return; - const QList editorEntries = editors(); - for (const Entry &editorEntry : editorEntries) { - if (future.isCanceled()) - break; - QString fileName = editorEntry.fileName.toString(); - if (fileName.isEmpty()) + LocatorFilterEntries goodEntries; + LocatorFilterEntries betterEntries; + + for (const Entry &editorData : editorsData) { + if (promise.isCanceled()) + return; + if (editorData.fileName.isEmpty()) continue; - QString displayName = editorEntry.displayName; - const QRegularExpressionMatch match = regexp.match(displayName); + const QRegularExpressionMatch match = regexp.match(editorData.displayName); if (match.hasMatch()) { - LocatorFilterEntry filterEntry(this, displayName); - filterEntry.filePath = FilePath::fromString(fileName); + LocatorFilterEntry filterEntry; + filterEntry.displayName = editorData.displayName; + filterEntry.filePath = editorData.fileName; filterEntry.extraInfo = filterEntry.filePath.shortNativePath(); - filterEntry.highlightInfo = highlightInfo(match); + filterEntry.highlightInfo = ILocatorFilter::highlightInfo(match); filterEntry.linkForEditor = Link(filterEntry.filePath, link.targetLine, link.targetColumn); if (match.capturedStart() == 0) @@ -107,23 +68,21 @@ QList OpenDocumentsFilter::matchesFor(QFutureInterface OpenDocumentsFilter::editors() const +LocatorMatcherTasks OpenDocumentsFilter::matchers() { - QMutexLocker lock(&m_mutex); - return m_editors; + TreeStorage storage; + + const auto onSetup = [storage](Async &async) { + const QList editorsData = Utils::transform(DocumentModel::entries(), + [](const DocumentModel::Entry *e) { return Entry{e->filePath(), e->displayName()}; }); + async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer()); + async.setConcurrentCallData(matchEditors, *storage, editorsData); + }; + + return {{AsyncTask(onSetup), storage}}; } -void OpenDocumentsFilter::accept(const LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const -{ - Q_UNUSED(newText) - Q_UNUSED(selectionStart) - Q_UNUSED(selectionLength) - BaseFileFilter::openEditorAt(selection); -} - -} // Core::Internal +} // namespace Core::Internal diff --git a/src/plugins/coreplugin/locator/opendocumentsfilter.h b/src/plugins/coreplugin/locator/opendocumentsfilter.h index 537bdd45a5a..94306d0515c 100644 --- a/src/plugins/coreplugin/locator/opendocumentsfilter.h +++ b/src/plugins/coreplugin/locator/opendocumentsfilter.h @@ -5,46 +5,15 @@ #include "ilocatorfilter.h" -#include - -#include -#include -#include -#include - -namespace Core { -namespace Internal { +namespace Core::Internal { class OpenDocumentsFilter : public ILocatorFilter { - Q_OBJECT - public: OpenDocumentsFilter(); - QList matchesFor(QFutureInterface &future, - const QString &entry) override; - void accept(const LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const override; - -public slots: - void slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, - const QVector &roles); - void slotRowsInserted(const QModelIndex &, int first, int last); - void slotRowsRemoved(const QModelIndex &, int first, int last); private: - class Entry - { - public: - Utils::FilePath fileName; - QString displayName; - }; - - QList editors() const; - - mutable QMutex m_mutex; - QList m_editors; + LocatorMatcherTasks matchers() final; }; -} // namespace Internal -} // namespace Core +} // namespace Core::Internal diff --git a/src/plugins/coreplugin/locator/spotlightlocatorfilter.cpp b/src/plugins/coreplugin/locator/spotlightlocatorfilter.cpp index cc62ec91c06..64ea71998b7 100644 --- a/src/plugins/coreplugin/locator/spotlightlocatorfilter.cpp +++ b/src/plugins/coreplugin/locator/spotlightlocatorfilter.cpp @@ -6,149 +6,28 @@ #include "../coreplugintr.h" #include "../messagemanager.h" +#include + #include +#include #include #include #include #include #include #include +#include #include -#include #include #include #include -#include #include -#include -#include #include -#include using namespace Utils; -namespace Core { -namespace Internal { - -// #pragma mark -- SpotlightIterator - -class SpotlightIterator : public BaseFileFilter::Iterator -{ -public: - SpotlightIterator(const CommandLine &command); - ~SpotlightIterator() override; - - void toFront() override; - bool hasNext() const override; - Utils::FilePath next() override; - Utils::FilePath filePath() const override; - - void scheduleKillProcess(); - void killProcess(); - -private: - void ensureNext(); - - std::unique_ptr m_process; - QMutex m_mutex; - QWaitCondition m_waitForItems; - FilePaths m_queue; - FilePaths m_filePaths; - int m_index; - bool m_finished; -}; - -SpotlightIterator::SpotlightIterator(const CommandLine &command) - : m_index(-1) - , m_finished(false) -{ - QTC_ASSERT(!command.isEmpty(), return ); - m_process.reset(new QtcProcess); - m_process->setCommand(command); - m_process->setEnvironment(Utils::Environment::systemEnvironment()); - QObject::connect(m_process.get(), &QtcProcess::done, - m_process.get(), [this, exe = command.executable().toUserOutput()] { - if (m_process->result() != ProcessResult::FinishedWithSuccess) { - MessageManager::writeFlashing(Tr::tr( - "Locator: Error occurred when running \"%1\".").arg(exe)); - } - scheduleKillProcess(); - }); - QObject::connect(m_process.get(), &QtcProcess::readyReadStandardOutput, - m_process.get(), [this] { - QString output = m_process->readAllStandardOutput(); - output.replace("\r\n", "\n"); - const QStringList items = output.split('\n'); - QMutexLocker lock(&m_mutex); - m_queue.append(Utils::transform(items, &FilePath::fromUserInput)); - if (m_filePaths.size() + m_queue.size() > 10000) // limit the amount of data - scheduleKillProcess(); - m_waitForItems.wakeAll(); - }); - m_process->start(); -} - -SpotlightIterator::~SpotlightIterator() -{ - killProcess(); -} - -void SpotlightIterator::toFront() -{ - m_index = -1; -} - -bool SpotlightIterator::hasNext() const -{ - auto that = const_cast(this); - that->ensureNext(); - return (m_index + 1 < m_filePaths.size()); -} - -Utils::FilePath SpotlightIterator::next() -{ - ensureNext(); - ++m_index; - QTC_ASSERT(m_index < m_filePaths.size(), return FilePath()); - return m_filePaths.at(m_index); -} - -Utils::FilePath SpotlightIterator::filePath() const -{ - QTC_ASSERT(m_index < m_filePaths.size(), return FilePath()); - return m_filePaths.at(m_index); -} - -void SpotlightIterator::scheduleKillProcess() -{ - QMetaObject::invokeMethod(m_process.get(), [this] { killProcess(); }, Qt::QueuedConnection); -} - -void SpotlightIterator::killProcess() -{ - if (!m_process) - return; - m_process->disconnect(); - QMutexLocker lock(&m_mutex); - m_finished = true; - m_waitForItems.wakeAll(); - m_process.reset(); -} - -void SpotlightIterator::ensureNext() -{ - if (m_index + 1 < m_filePaths.size()) // nothing to do - return; - // check if there are items in the queue, otherwise wait for some - QMutexLocker lock(&m_mutex); - if (m_queue.isEmpty() && !m_finished) - m_waitForItems.wait(&m_mutex); - m_filePaths.append(m_queue); - m_queue.clear(); -} - -// #pragma mark -- SpotlightLocatorFilter +namespace Core::Internal { static QString defaultCommand() { @@ -223,33 +102,97 @@ SpotlightLocatorFilter::SpotlightLocatorFilter() { setId("SpotlightFileNamesLocatorFilter"); setDefaultShortcutString("md"); - setDefaultIncludedByDefault(false); setDisplayName(Tr::tr("File Name Index")); - setDescription( - Tr::tr("Matches files from a global file system index (Spotlight, Locate, Everything). Append " - "\"+\" or \":\" to jump to the given line number. Append another " - "\"+\" or \":\" to jump to the column number as well.")); - setConfigurable(true); - reset(); + setDescription(Tr::tr( + "Locates files from a global file system index (Spotlight, Locate, Everything). Append " + "\"+\" or \":\" to jump to the given line number. Append another " + "\"+\" or \":\" to jump to the column number as well.")); + m_command = defaultCommand(); + m_arguments = defaultArguments(); + m_caseSensitiveArguments = defaultArguments(Qt::CaseSensitive); } -void SpotlightLocatorFilter::prepareSearch(const QString &entry) +static void matches(QPromise &promise, const LocatorStorage &storage, + const CommandLine &command) { - Link link = Utils::Link::fromString(entry, true); - if (link.targetFilePath.isEmpty()) { - setFileIterator(new BaseFileFilter::ListIterator(Utils::FilePaths())); - } else { - // only pass the file name part to allow searches like "somepath/*foo" + // If search string contains spaces, treat them as wildcard '*' and search in full path + const QString wildcardInput = QDir::fromNativeSeparators(storage.input()).replace(' ', '*'); + const Link inputLink = Link::fromString(wildcardInput, true); + const QString newInput = inputLink.targetFilePath.toString(); + const QRegularExpression regExp = ILocatorFilter::createRegExp(newInput); + if (!regExp.isValid()) + return; - std::unique_ptr expander(createMacroExpander(link.targetFilePath.fileName())); - const QString argumentString = expander->expand( - caseSensitivity(link.targetFilePath.toString()) == Qt::CaseInsensitive - ? m_arguments - : m_caseSensitiveArguments); - const CommandLine cmd(FilePath::fromString(m_command), argumentString, CommandLine::Raw); - setFileIterator(new SpotlightIterator(cmd)); + const bool hasPathSeparator = newInput.contains('/') || newInput.contains('*'); + LocatorFileCache::MatchedEntries entries = {}; + QEventLoop loop; + Process process; + process.setCommand(command); + process.setEnvironment(Environment::systemEnvironment()); // TODO: Is it needed? + + QObject::connect(&process, &Process::readyReadStandardOutput, &process, + [&, entriesPtr = &entries] { + QString output = process.readAllStandardOutput(); + output.replace("\r\n", "\n"); + const QStringList items = output.split('\n'); + const FilePaths filePaths = Utils::transform(items, &FilePath::fromUserInput); + LocatorFileCache::processFilePaths(promise.future(), filePaths, hasPathSeparator, regExp, + inputLink, entriesPtr); + if (promise.isCanceled()) + loop.exit(); + }); + QObject::connect(&process, &Process::done, &process, [&] { + if (process.result() != ProcessResult::FinishedWithSuccess) { + MessageManager::writeFlashing(Tr::tr("Locator: Error occurred when running \"%1\".") + .arg(command.executable().toUserOutput())); + } + loop.exit(); + }); + QFutureWatcher watcher; + watcher.setFuture(promise.future()); + QObject::connect(&watcher, &QFutureWatcherBase::canceled, &watcher, [&loop] { loop.exit(); }); + if (promise.isCanceled()) + return; + process.start(); + loop.exec(); + + for (auto &entry : entries) { + if (promise.isCanceled()) + return; + if (entry.size() < 1000) + Utils::sort(entry, LocatorFilterEntry::compareLexigraphically); } - BaseFileFilter::prepareSearch(entry); + if (promise.isCanceled()) + return; + storage.reportOutput(std::accumulate(std::begin(entries), std::end(entries), + LocatorFilterEntries())); +} + +LocatorMatcherTasks SpotlightLocatorFilter::matchers() +{ + using namespace Tasking; + + TreeStorage storage; + + const auto onSetup = [storage, command = m_command, insensArgs = m_arguments, + sensArgs = m_caseSensitiveArguments](Async &async) { + const Link link = Link::fromString(storage->input(), true); + const FilePath input = link.targetFilePath; + if (input.isEmpty()) + return TaskAction::StopWithDone; + + // only pass the file name part to allow searches like "somepath/*foo" + const std::unique_ptr expander(createMacroExpander(input.fileName())); + const QString args = caseSensitivity(input.toString()) == Qt::CaseInsensitive + ? insensArgs : sensArgs; + const CommandLine cmd(FilePath::fromString(command), expander->expand(args), + CommandLine::Raw); + async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer()); + async.setConcurrentCallData(matches, *storage, cmd); + return TaskAction::Continue; + }; + + return {{AsyncTask(onSetup), storage}}; } bool SpotlightLocatorFilter::openConfigDialog(QWidget *parent, bool &needsRefresh) @@ -275,7 +218,7 @@ bool SpotlightLocatorFilter::openConfigDialog(QWidget *parent, bool &needsRefres chooser->addMacroExpanderProvider([expander = expander.get()] { return expander; }); chooser->addSupportedWidget(argumentsEdit); chooser->addSupportedWidget(caseSensitiveArgumentsEdit); - const bool accepted = openConfigDialog(parent, &configWidget); + const bool accepted = ILocatorFilter::openConfigDialog(parent, &configWidget); if (accepted) { m_command = commandEdit->rawFilePath().toString(); m_arguments = argumentsEdit->text(); @@ -301,12 +244,4 @@ void SpotlightLocatorFilter::restoreState(const QJsonObject &obj) m_caseSensitiveArguments = obj.value(kCaseSensitiveKey).toString(defaultArguments(Qt::CaseSensitive)); } -void SpotlightLocatorFilter::reset() -{ - m_command = defaultCommand(); - m_arguments = defaultArguments(); - m_caseSensitiveArguments = defaultArguments(Qt::CaseSensitive); -} - -} // Internal -} // Core +} // namespace Core::Internal diff --git a/src/plugins/coreplugin/locator/spotlightlocatorfilter.h b/src/plugins/coreplugin/locator/spotlightlocatorfilter.h index 174319d4349..06114d4e264 100644 --- a/src/plugins/coreplugin/locator/spotlightlocatorfilter.h +++ b/src/plugins/coreplugin/locator/spotlightlocatorfilter.h @@ -3,22 +3,15 @@ #pragma once -#include "basefilefilter.h" +#include "ilocatorfilter.h" -#include +namespace Core::Internal { -namespace Core { -namespace Internal { - -class SpotlightLocatorFilter : public BaseFileFilter +class SpotlightLocatorFilter : public ILocatorFilter { - Q_OBJECT public: SpotlightLocatorFilter(); - void prepareSearch(const QString &entry) override; - - using ILocatorFilter::openConfigDialog; bool openConfigDialog(QWidget *parent, bool &needsRefresh) final; protected: @@ -26,12 +19,11 @@ protected: void restoreState(const QJsonObject &obj) final; private: - void reset(); + LocatorMatcherTasks matchers() final; QString m_command; QString m_arguments; QString m_caseSensitiveArguments; }; -} // Internal -} // Core +} // namespace Core::Internal diff --git a/src/plugins/coreplugin/locator/urllocatorfilter.cpp b/src/plugins/coreplugin/locator/urllocatorfilter.cpp index 70651e74eea..c9025b2237a 100644 --- a/src/plugins/coreplugin/locator/urllocatorfilter.cpp +++ b/src/plugins/coreplugin/locator/urllocatorfilter.cpp @@ -7,7 +7,6 @@ #include #include -#include #include #include @@ -17,9 +16,7 @@ #include #include #include -#include #include -#include using namespace Utils; @@ -162,38 +159,32 @@ UrlLocatorFilter::UrlLocatorFilter(const QString &displayName, Id id) setId(id); m_defaultDisplayName = displayName; setDisplayName(displayName); - setDefaultIncludedByDefault(false); } -UrlLocatorFilter::~UrlLocatorFilter() = default; - -QList UrlLocatorFilter::matchesFor( - QFutureInterface &future, const QString &entry) +LocatorMatcherTasks UrlLocatorFilter::matchers() { - QList entries; - const QStringList urls = remoteUrls(); - for (const QString &url : urls) { - if (future.isCanceled()) - break; - const QString name = url.arg(entry); - Core::LocatorFilterEntry filterEntry(this, name); - filterEntry.highlightInfo = {int(name.lastIndexOf(entry)), int(entry.length())}; - entries.append(filterEntry); - } - return entries; -} + using namespace Tasking; -void UrlLocatorFilter::accept(const Core::LocatorFilterEntry &selection, - QString *newText, - int *selectionStart, - int *selectionLength) const -{ - Q_UNUSED(newText) - Q_UNUSED(selectionStart) - Q_UNUSED(selectionLength) - const QString &url = selection.displayName; - if (!url.isEmpty()) - QDesktopServices::openUrl(url); + TreeStorage storage; + + const auto onSetup = [storage, urls = remoteUrls()] { + const QString input = storage->input(); + LocatorFilterEntries entries; + for (const QString &url : urls) { + const QString name = url.arg(input); + LocatorFilterEntry entry; + entry.displayName = name; + entry.acceptor = [name] { + if (!name.isEmpty()) + QDesktopServices::openUrl(name); + return AcceptResult(); + }; + entry.highlightInfo = {int(name.lastIndexOf(input)), int(input.length())}; + entries.append(entry); + } + storage->reportOutput(entries); + }; + return {{Sync(onSetup), storage}}; } const char kDisplayNameKey[] = "displayName"; @@ -249,7 +240,6 @@ bool UrlLocatorFilter::openConfigDialog(QWidget *parent, bool &needsRefresh) Q_UNUSED(needsRefresh) Internal::UrlFilterOptions optionsDialog(this, parent); if (optionsDialog.exec() == QDialog::Accepted) { - QMutexLocker lock(&m_mutex); m_remoteUrls.clear(); setIncludedByDefault(optionsDialog.includeByDefault->isChecked()); setShortcutString(optionsDialog.shortcutEdit->text().trimmed()); @@ -268,20 +258,4 @@ void UrlLocatorFilter::addDefaultUrl(const QString &urlTemplate) m_defaultUrls.append(urlTemplate); } -QStringList UrlLocatorFilter::remoteUrls() const -{ - QMutexLocker lock(&m_mutex); - return m_remoteUrls; -} - -void UrlLocatorFilter::setIsCustomFilter(bool value) -{ - m_isCustomFilter = value; -} - -bool UrlLocatorFilter::isCustomFilter() const -{ - return m_isCustomFilter; -} - } // namespace Core diff --git a/src/plugins/coreplugin/locator/urllocatorfilter.h b/src/plugins/coreplugin/locator/urllocatorfilter.h index befe162d60e..57f652aeca1 100644 --- a/src/plugins/coreplugin/locator/urllocatorfilter.h +++ b/src/plugins/coreplugin/locator/urllocatorfilter.h @@ -8,7 +8,6 @@ #include #include -#include QT_BEGIN_NAMESPACE class QCheckBox; @@ -21,36 +20,30 @@ namespace Core { class CORE_EXPORT UrlLocatorFilter final : public Core::ILocatorFilter { - Q_OBJECT public: UrlLocatorFilter(Utils::Id id); UrlLocatorFilter(const QString &displayName, Utils::Id id); - ~UrlLocatorFilter() final; - // ILocatorFilter - QList matchesFor(QFutureInterface &future, - const QString &entry) override; - void accept(const Core::LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const override; - void restoreState(const QByteArray &state) override; - bool openConfigDialog(QWidget *parent, bool &needsRefresh) override; + void restoreState(const QByteArray &state) final; + bool openConfigDialog(QWidget *parent, bool &needsRefresh) final; void addDefaultUrl(const QString &urlTemplate); - QStringList remoteUrls() const; + QStringList remoteUrls() const { return m_remoteUrls; } - void setIsCustomFilter(bool value); - bool isCustomFilter() const; + void setIsCustomFilter(bool value) { m_isCustomFilter = value; } + bool isCustomFilter() const { return m_isCustomFilter; } protected: void saveState(QJsonObject &object) const final; void restoreState(const QJsonObject &object) final; private: + LocatorMatcherTasks matchers() final; + QString m_defaultDisplayName; QStringList m_defaultUrls; QStringList m_remoteUrls; bool m_isCustomFilter = false; - mutable QMutex m_mutex; }; namespace Internal { @@ -81,5 +74,4 @@ private: }; } // namespace Internal - } // namespace Core diff --git a/src/plugins/coreplugin/loggingviewer.cpp b/src/plugins/coreplugin/loggingviewer.cpp index 4392ef63445..a81ed5936db 100644 --- a/src/plugins/coreplugin/loggingviewer.cpp +++ b/src/plugins/coreplugin/loggingviewer.cpp @@ -622,7 +622,7 @@ void LoggingViewManagerWidget::saveLoggingsToFile() const void LoggingViewManagerWidget::saveEnabledCategoryPreset() const { Utils::FilePath fp = Utils::FileUtils::getSaveFilePath(ICore::dialogParent(), - Tr::tr("Save Enabled Categories As")); + Tr::tr("Save Enabled Categories As...")); if (fp.isEmpty()) return; const QList enabled = m_categoryModel->enabledCategories(); @@ -656,7 +656,7 @@ void LoggingViewManagerWidget::loadAndUpdateFromPreset() if (!contents) { QMessageBox::critical(ICore::dialogParent(), Tr::tr("Error"), - Tr::tr("Failed to open preset file \"%1\" for reading") + Tr::tr("Failed to open preset file \"%1\" for reading.") .arg(fp.toUserOutput())); return; } diff --git a/src/plugins/coreplugin/mainwindow.cpp b/src/plugins/coreplugin/mainwindow.cpp index 168fff92208..2d7ec70c8eb 100644 --- a/src/plugins/coreplugin/mainwindow.cpp +++ b/src/plugins/coreplugin/mainwindow.cpp @@ -1384,67 +1384,6 @@ public: } }; -class MarkdownHighlighter : public QSyntaxHighlighter -{ - QBrush h2Brush; -public: - MarkdownHighlighter(QTextDocument *parent) - : QSyntaxHighlighter(parent) - , h2Brush(Qt::NoBrush) - { - parent->setIndentWidth(30); // default value is 40 - } - - void highlightBlock(const QString &text) - { - if (text.isEmpty()) - return; - - QTextBlockFormat fmt = currentBlock().blockFormat(); - QTextCursor cur(currentBlock()); - if (fmt.hasProperty(QTextFormat::HeadingLevel)) { - fmt.setTopMargin(10); - fmt.setBottomMargin(10); - - // Draw an underline for Heading 2, by creating a texture brush - // with the last pixel visible - if (fmt.property(QTextFormat::HeadingLevel) == 2) { - QTextCharFormat charFmt = currentBlock().charFormat(); - charFmt.setBaselineOffset(15); - setFormat(0, text.length(), charFmt); - - if (h2Brush.style() == Qt::NoBrush) { - const int height = QFontMetrics(charFmt.font()).height(); - QImage image(1, height, QImage::Format_ARGB32); - - image.fill(QColor(0, 0, 0, 0).rgba()); - image.setPixel(0, - height - 1, - Utils::creatorTheme()->color(Theme::TextColorDisabled).rgba()); - - h2Brush = QBrush(image); - } - fmt.setBackground(h2Brush); - } - cur.setBlockFormat(fmt); - } else if (fmt.hasProperty(QTextFormat::BlockCodeLanguage) && fmt.indent() == 0) { - // set identation for code blocks - fmt.setIndent(1); - cur.setBlockFormat(fmt); - } - - // Show the bulet points as filled circles - QTextList *list = cur.currentList(); - if (list) { - QTextListFormat listFmt = list->format(); - if (listFmt.indent() == 1 && listFmt.style() == QTextListFormat::ListCircle) { - listFmt.setStyle(QTextListFormat::ListDisc); - list->setFormat(listFmt); - } - } - } -}; - void MainWindow::changeLog() { static QPointer dialog; @@ -1484,8 +1423,7 @@ void MainWindow::changeLog() aggregate->add(textEdit); aggregate->add(new Core::BaseTextFind(textEdit)); - auto highlighter = new MarkdownHighlighter(textEdit->document()); - (void)highlighter; + new MarkdownHighlighter(textEdit->document()); auto textEditWidget = new QFrame; textEditWidget->setFrameStyle(QFrame::NoFrame); diff --git a/src/plugins/coreplugin/manhattanstyle.cpp b/src/plugins/coreplugin/manhattanstyle.cpp index 3d51642e44f..0f16100e11e 100644 --- a/src/plugins/coreplugin/manhattanstyle.cpp +++ b/src/plugins/coreplugin/manhattanstyle.cpp @@ -3,9 +3,12 @@ #include "manhattanstyle.h" +#include "generalsettings.h" + #include #include #include +#include #include #include #include @@ -24,6 +27,8 @@ #include #include #include +#include +#include #include #include #include @@ -53,7 +58,7 @@ static bool isInUnstyledDialogOrPopup(const QWidget *widget) { // Do not style contents of dialogs or popups without "panelwidget" property const QWidget *window = widget->window(); - if (window->property("panelwidget").toBool()) + if (window->property(StyleHelper::C_PANEL_WIDGET).toBool()) return false; const Qt::WindowType windowType = window->windowType(); return (windowType == Qt::Dialog || windowType == Qt::Popup); @@ -74,12 +79,15 @@ bool panelWidget(const QWidget *widget) if (qobject_cast(widget)) return styleEnabled(widget); + if (qobject_cast(widget)) // See DebuggerMainWindowPrivate + return widget->property(StyleHelper::C_PANEL_WIDGET_SINGLE_ROW).toBool(); + const QWidget *p = widget; while (p) { if (qobject_cast(p) || qobject_cast(p) || qobject_cast(p) || - p->property("panelwidget").toBool()) + p->property(StyleHelper::C_PANEL_WIDGET).toBool()) return styleEnabled(widget); p = p->parentWidget(); } @@ -97,7 +105,7 @@ bool lightColored(const QWidget *widget) const QWidget *p = widget; while (p) { - if (p->property("lightColored").toBool()) + if (p->property(StyleHelper::C_LIGHT_COLORED).toBool()) return true; p = p->parentWidget(); } @@ -131,6 +139,7 @@ ManhattanStyle::ManhattanStyle(const QString &baseStyleName) : QProxyStyle(QStyleFactory::create(baseStyleName)) , d(new ManhattanStylePrivate()) { + Core::Internal::GeneralSettings::applyToolbarStyleFromSettings(); } ManhattanStyle::~ManhattanStyle() @@ -154,7 +163,7 @@ QSize ManhattanStyle::sizeFromContents(ContentsType type, const QStyleOption *op switch (type) { case CT_Splitter: - if (widget && widget->property("minisplitter").toBool()) + if (widget && widget->property(StyleHelper::C_MINI_SPLITTER).toBool()) newSize = QSize(1, 1); break; case CT_ComboBox: @@ -203,8 +212,14 @@ int ManhattanStyle::pixelMetric(PixelMetric metric, const QStyleOption *option, int retval = QProxyStyle::pixelMetric(metric, option, widget); switch (metric) { +#ifdef Q_OS_MACOS + case PM_MenuButtonIndicator: + if (widget && option->type == QStyleOption::SO_ToolButton) + return 12; + break; +#endif case PM_SplitterWidth: - if (widget && widget->property("minisplitter").toBool()) + if (widget && widget->property(StyleHelper::C_MINI_SPLITTER).toBool()) retval = 1; break; case PM_ToolBarIconSize: @@ -295,16 +310,26 @@ void ManhattanStyle::polish(QWidget *widget) widget->setAttribute(Qt::WA_LayoutUsesWidgetRect, true); // So that text isn't cutoff in line-edits, comboboxes... etc. const int height = qMax(StyleHelper::navigationWidgetHeight(), QApplication::fontMetrics().height()); - if (qobject_cast(widget) || qobject_cast(widget)) { + if (qobject_cast(widget)) { + widget->setMinimumWidth( + StyleHelper::toolbarStyle() == StyleHelper::ToolbarStyleCompact ? 24 : 28); widget->setAttribute(Qt::WA_Hover); widget->setMaximumHeight(height - 2); + } else if (qobject_cast(widget)) { + widget->setAttribute(Qt::WA_Hover); + widget->setFixedHeight(height - (StyleHelper::toolbarStyle() + == StyleHelper::ToolbarStyleCompact ? 1 : 3)); } else if (qobject_cast(widget) || qobject_cast(widget) || qobject_cast(widget)) { widget->setPalette(panelPalette(widget->palette(), lightColored(widget))); - } else if (widget->property("panelwidget_singlerow").toBool()) { + } else if ((qobject_cast(widget) && !StyleHelper::isQDSTheme()) + || widget->property(StyleHelper::C_PANEL_WIDGET_SINGLE_ROW).toBool()) { widget->setFixedHeight(height); } else if (qobject_cast(widget)) { - widget->setFixedHeight(height + 2); + const bool flatAndNotCompact = + StyleHelper::toolbarStyle() != StyleHelper::ToolbarStyleCompact + && creatorTheme()->flag(Theme::FlatToolBars); + widget->setFixedHeight(height + (flatAndNotCompact ? 3 : 2)); } else if (qobject_cast(widget)) { const bool isLightColored = lightColored(widget); QPalette palette = panelPalette(widget->palette(), isLightColored); @@ -314,6 +339,9 @@ void ManhattanStyle::polish(QWidget *widget) widget->setPalette(palette); widget->setMaximumHeight(height - 2); widget->setAttribute(Qt::WA_Hover); + } else if (qobject_cast(widget) + && widget->property(StyleHelper::C_PANEL_WIDGET_SINGLE_ROW).toBool()) { + widget->setFixedHeight(height); } } } @@ -393,7 +421,7 @@ int ManhattanStyle::styleHint(StyleHint hint, const QStyleOption *option, const case QStyle::SH_ItemView_ActivateItemOnSingleClick: // default depends on the style if (widget) { - QVariant activationMode = widget->property("ActivationMode"); + QVariant activationMode = widget->property(activationModeC); if (activationMode.isValid()) ret = activationMode.toBool(); } @@ -633,76 +661,76 @@ void ManhattanStyle::drawPrimitiveForPanelWidget(PrimitiveElement element, const bool animating = (state & State_Animating); if (widget && !animating) { - auto w = const_cast (widget); - int oldState = w->property("_q_stylestate").toInt(); - QRect oldRect = w->property("_q_stylerect").toRect(); - QRect newRect = w->rect(); - w->setProperty("_q_stylestate", (int)option->state); - w->setProperty("_q_stylerect", w->rect()); + auto w = const_cast (widget); + int oldState = w->property("_q_stylestate").toInt(); + QRect oldRect = w->property("_q_stylerect").toRect(); + QRect newRect = w->rect(); + w->setProperty("_q_stylestate", (int)option->state); + w->setProperty("_q_stylerect", w->rect()); - // Determine the animated transition - bool doTransition = ((state & State_On) != (oldState & State_On) || - (state & State_MouseOver) != (oldState & State_MouseOver)); - if (oldRect != newRect) { - doTransition = false; - d->animator.stopAnimation(widget); - } - - if (doTransition) { - QImage startImage(option->rect.size(), QImage::Format_ARGB32_Premultiplied); - QImage endImage(option->rect.size(), QImage::Format_ARGB32_Premultiplied); - Animation *anim = d->animator.widgetAnimation(widget); - QStyleOption opt = *option; - opt.state = (QStyle::State)oldState; - opt.state |= State_Animating; - startImage.fill(0); - auto t = new Transition; - t->setWidget(w); - QPainter startPainter(&startImage); - if (!anim) { - drawPrimitive(element, &opt, &startPainter, widget); - } else { - anim->paint(&startPainter, &opt); + // Determine the animated transition + bool doTransition = ((state & State_On) != (oldState & State_On) || + (state & State_MouseOver) != (oldState & State_MouseOver)); + if (oldRect != newRect) { + doTransition = false; d->animator.stopAnimation(widget); } - QStyleOption endOpt = *option; - endOpt.state |= State_Animating; - t->setStartImage(startImage); - d->animator.startAnimation(t); - endImage.fill(0); - QPainter endPainter(&endImage); - drawPrimitive(element, &endOpt, &endPainter, widget); - t->setEndImage(endImage); - if (oldState & State_MouseOver) - t->setDuration(150); - else - t->setDuration(75); - t->setStartTime(QTime::currentTime()); - } + + if (doTransition) { + QImage startImage(option->rect.size(), QImage::Format_ARGB32_Premultiplied); + QImage endImage(option->rect.size(), QImage::Format_ARGB32_Premultiplied); + Animation *anim = d->animator.widgetAnimation(widget); + QStyleOption opt = *option; + opt.state = (QStyle::State)oldState; + opt.state |= State_Animating; + startImage.fill(0); + auto t = new Transition; + t->setWidget(w); + QPainter startPainter(&startImage); + if (!anim) { + drawPrimitive(element, &opt, &startPainter, widget); + } else { + anim->paint(&startPainter, &opt); + d->animator.stopAnimation(widget); + } + QStyleOption endOpt = *option; + endOpt.state |= State_Animating; + t->setStartImage(startImage); + d->animator.startAnimation(t); + endImage.fill(0); + QPainter endPainter(&endImage); + drawPrimitive(element, &endOpt, &endPainter, widget); + t->setEndImage(endImage); + if (oldState & State_MouseOver) + t->setDuration(150); + else + t->setDuration(75); + t->setStartTime(QTime::currentTime()); + } } Animation *anim = d->animator.widgetAnimation(widget); if (!animating && anim) { - anim->paint(painter, option); + anim->paint(painter, option); } else { - bool pressed = option->state & State_Sunken || option->state & State_On; - painter->setPen(StyleHelper::sidebarShadow()); - if (pressed) { - const QColor shade = creatorTheme()->color(Theme::FancyToolButtonSelectedColor); - painter->fillRect(rect, shade); - if (!creatorTheme()->flag(Theme::FlatToolBars)) { - const QRectF borderRect = QRectF(rect).adjusted(0.5, 0.5, -0.5, -0.5); - painter->drawLine(borderRect.topLeft() + QPointF(1, 0), borderRect.topRight() - QPointF(1, 0)); - painter->drawLine(borderRect.topLeft(), borderRect.bottomLeft()); - painter->drawLine(borderRect.topRight(), borderRect.bottomRight()); + const bool pressed = option->state & State_Sunken || option->state & State_On + || (widget && widget->property(StyleHelper::C_HIGHLIGHT_WIDGET).toBool()); + painter->setPen(StyleHelper::sidebarShadow()); + if (pressed) { + StyleHelper::drawPanelBgRect( + painter, rect, creatorTheme()->color(Theme::FancyToolButtonSelectedColor)); + if (StyleHelper::toolbarStyle() == StyleHelper::ToolbarStyleCompact + && !creatorTheme()->flag(Theme::FlatToolBars)) { + const QRectF borderRect = QRectF(rect).adjusted(0.5, 0.5, -0.5, -0.5); + painter->drawLine(borderRect.topLeft() + QPointF(1, 0), borderRect.topRight() - QPointF(1, 0)); + painter->drawLine(borderRect.topLeft(), borderRect.bottomLeft()); + painter->drawLine(borderRect.topRight(), borderRect.bottomRight()); + } + } else if (option->state & State_Enabled && option->state & State_MouseOver) { + StyleHelper::drawPanelBgRect( + painter, rect, creatorTheme()->color(Theme::FancyToolButtonHoverColor)); } - } else if (option->state & State_Enabled && option->state & State_MouseOver) { - painter->fillRect(rect, creatorTheme()->color(Theme::FancyToolButtonHoverColor)); - } else if (widget && widget->property("highlightWidget").toBool()) { - QColor shade(0, 0, 0, 128); - painter->fillRect(rect, shade); - } if (option->state & State_HasFocus && (option->state & State_KeyboardFocusChange)) { QColor highlight = option->palette.highlight().color(); highlight.setAlphaF(0.4f); @@ -884,7 +912,8 @@ void ManhattanStyle::drawControl( painter->save(); QRect editRect = subControlRect(CC_ComboBox, cb, SC_ComboBoxEditField, widget); QPalette customPal = cb->palette; - bool drawIcon = !(widget && widget->property("hideicon").toBool()); + const bool drawIcon = + !(widget && widget->property(StyleHelper::C_HIDE_ICON).toBool()); if (!cb->currentIcon.isNull() && drawIcon) { QIcon::Mode mode = cb->state & State_Enabled ? QIcon::Normal @@ -909,14 +938,15 @@ void ManhattanStyle::drawControl( } Qt::TextElideMode elideMode = Qt::ElideRight; - if (widget && widget->dynamicPropertyNames().contains("elidemode")) - elideMode = widget->property("elidemode").value(); + if (widget && widget->dynamicPropertyNames().contains(StyleHelper::C_ELIDE_MODE)) + elideMode = widget->property(StyleHelper::C_ELIDE_MODE) + .value(); QLatin1Char asterisk('*'); int elideWidth = editRect.width(); bool notElideAsterisk = elideMode == Qt::ElideRight && widget - && widget->property("notelideasterisk").toBool() + && widget->property(StyleHelper::C_NOT_ELIDE_ASTERISK).toBool() && cb->currentText.endsWith(asterisk) && option->fontMetrics.horizontalAdvance(cb->currentText) > elideWidth; @@ -1031,7 +1061,7 @@ void ManhattanStyle::drawControl( : StyleHelper::sidebarHighlight(); const QColor borderColor = drawLightColored ? QColor(255, 255, 255, 180) : hightLight; - if (widget && widget->property("topBorder").toBool()) { + if (widget && widget->property(StyleHelper::C_TOP_BORDER).toBool()) { painter->drawLine(borderRect.topLeft(), borderRect.topRight()); painter->setPen(borderColor); painter->drawLine(borderRect.topLeft() + QPointF(0, 1), borderRect.topRight() + QPointF(0, 1)); @@ -1047,7 +1077,7 @@ void ManhattanStyle::drawControl( } if (creatorTheme()->flag(Theme::DrawToolBarBorders)) { painter->setPen(StyleHelper::toolBarBorderColor()); - if (widget && widget->property("topBorder").toBool()) + if (widget && widget->property(StyleHelper::C_TOP_BORDER).toBool()) painter->drawLine(borderRect.topLeft(), borderRect.topRight()); else painter->drawLine(borderRect.bottomLeft(), borderRect.bottomRight()); @@ -1076,7 +1106,8 @@ void ManhattanStyle::drawComplexControl(ComplexControl control, const QStyleOpti case CC_ToolButton: if (const auto toolbutton = qstyleoption_cast(option)) { bool reverse = option->direction == Qt::RightToLeft; - bool drawborder = (widget && widget->property("showborder").toBool()); + const bool drawborder = + (widget && widget->property(StyleHelper::C_SHOW_BORDER).toBool()); if (drawborder) drawButtonSeparator(painter, rect, reverse); @@ -1110,7 +1141,7 @@ void ManhattanStyle::drawComplexControl(ComplexControl control, const QStyleOpti QStyleOptionToolButton label = *toolbutton; label.palette = panelPalette(option->palette, lightColored(widget)); - if (widget && widget->property("highlightWidget").toBool()) { + if (widget && widget->property(StyleHelper::C_HIGHLIGHT_WIDGET).toBool()) { label.palette.setColor(QPalette::ButtonText, creatorTheme()->color(Theme::IconsWarningToolBarColor)); } @@ -1137,7 +1168,7 @@ void ManhattanStyle::drawComplexControl(ComplexControl control, const QStyleOpti tool.rect = tool.rect.adjusted(2, 2, -2, -2); drawPrimitive(PE_IndicatorArrowDown, &tool, painter, widget); } else if (toolbutton->features & QStyleOptionToolButton::HasMenu - && widget && !widget->property("noArrow").toBool()) { + && widget && !widget->property(StyleHelper::C_NO_ARROW).toBool()) { int arrowSize = 6; QRect ir = toolbutton->rect.adjusted(1, 1, -1, -1); QStyleOptionToolButton newBtn = *toolbutton; @@ -1154,9 +1185,12 @@ void ManhattanStyle::drawComplexControl(ComplexControl control, const QStyleOpti painter->save(); bool isEmpty = cb->currentText.isEmpty() && cb->currentIcon.isNull(); bool reverse = option->direction == Qt::RightToLeft; - bool drawborder = !(widget && widget->property("hideborder").toBool()); - bool drawleftborder = (widget && widget->property("drawleftborder").toBool()); - bool alignarrow = !(widget && widget->property("alignarrow").toBool()); + const bool drawborder = + !(widget && widget->property(StyleHelper::C_HIDE_BORDER).toBool()); + const bool drawleftborder = + (widget && widget->property(StyleHelper::C_DRAW_LEFT_BORDER).toBool()); + const bool alignarrow = + !(widget && widget->property(StyleHelper::C_ALIGN_ARROW).toBool()); if (drawborder) { drawButtonSeparator(painter, rect, reverse); diff --git a/src/plugins/coreplugin/mimetypemagicdialog.cpp b/src/plugins/coreplugin/mimetypemagicdialog.cpp index 475cc57dd34..2a55539bf76 100644 --- a/src/plugins/coreplugin/mimetypemagicdialog.cpp +++ b/src/plugins/coreplugin/mimetypemagicdialog.cpp @@ -78,7 +78,7 @@ MimeTypeMagicDialog::MimeTypeMagicDialog(QWidget *parent) : auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); - using namespace Utils::Layouting; + using namespace Layouting; Column { Form { diff --git a/src/plugins/coreplugin/mimetypesettings.cpp b/src/plugins/coreplugin/mimetypesettings.cpp index a4efe1c30ef..116fcf4a06e 100644 --- a/src/plugins/coreplugin/mimetypesettings.cpp +++ b/src/plugins/coreplugin/mimetypesettings.cpp @@ -82,8 +82,6 @@ public: // MimeTypeSettingsModel class MimeTypeSettingsModel : public QAbstractTableModel { - Q_OBJECT - public: enum class Role { DefaultHandler = Qt::UserRole @@ -227,8 +225,6 @@ void MimeTypeSettingsModel::resetUserDefaults() // MimeTypeSettingsPrivate class MimeTypeSettingsPrivate : public QObject { - Q_OBJECT - public: MimeTypeSettingsPrivate(); ~MimeTypeSettingsPrivate() override; @@ -291,6 +287,31 @@ MimeTypeSettingsPrivate::MimeTypeSettingsPrivate() MimeTypeSettingsPrivate::~MimeTypeSettingsPrivate() = default; +class MimeTypeSettingsWidget : public IOptionsPageWidget +{ +public: + MimeTypeSettingsWidget(MimeTypeSettingsPrivate *d) + : d(d) + { + d->configureUi(this); + } + + void apply() final + { + MimeTypeSettingsPrivate::applyUserModifiedMimeTypes(d->m_pendingModifiedMimeTypes); + Core::Internal::setUserPreferredEditorTypes(d->m_model->m_userDefault); + d->m_pendingModifiedMimeTypes.clear(); + d->m_model->load(); + } + + void finish() final + { + d->m_pendingModifiedMimeTypes.clear(); + } + + MimeTypeSettingsPrivate *d; +}; + void MimeTypeSettingsPrivate::configureUi(QWidget *w) { auto filterLineEdit = new FancyLineEdit; @@ -713,6 +734,7 @@ MimeTypeSettings::MimeTypeSettings() setId(Constants::SETTINGS_ID_MIMETYPES); setDisplayName(Tr::tr("MIME Types")); setCategory(Constants::SETTINGS_CATEGORY_CORE); + setWidgetCreator([this] { return new MimeTypeSettingsWidget(d); }); } MimeTypeSettings::~MimeTypeSettings() @@ -720,29 +742,6 @@ MimeTypeSettings::~MimeTypeSettings() delete d; } -QWidget *MimeTypeSettings::widget() -{ - if (!d->m_widget) { - d->m_widget = new QWidget; - d->configureUi(d->m_widget); - } - return d->m_widget; -} - -void MimeTypeSettings::apply() -{ - MimeTypeSettingsPrivate::applyUserModifiedMimeTypes(d->m_pendingModifiedMimeTypes); - Core::Internal::setUserPreferredEditorTypes(d->m_model->m_userDefault); - d->m_pendingModifiedMimeTypes.clear(); - d->m_model->load(); -} - -void MimeTypeSettings::finish() -{ - d->m_pendingModifiedMimeTypes.clear(); - delete d->m_widget; -} - void MimeTypeSettings::restoreSettings() { MimeTypeSettingsPrivate::UserMimeTypeHash mimetypes @@ -784,5 +783,3 @@ void MimeEditorDelegate::setModelData(QWidget *editor, } } // Core::Internal - -#include "mimetypesettings.moc" diff --git a/src/plugins/coreplugin/mimetypesettings.h b/src/plugins/coreplugin/mimetypesettings.h index f676de53e62..0127e227685 100644 --- a/src/plugins/coreplugin/mimetypesettings.h +++ b/src/plugins/coreplugin/mimetypesettings.h @@ -11,17 +11,12 @@ class MimeTypeSettingsPrivate; class MimeTypeSettings : public IOptionsPage { - Q_OBJECT - public: MimeTypeSettings(); ~MimeTypeSettings() override; - QWidget *widget() override; - void apply() override; - void finish() override; - static void restoreSettings(); + private: MimeTypeSettingsPrivate *d; }; diff --git a/src/plugins/coreplugin/minisplitter.cpp b/src/plugins/coreplugin/minisplitter.cpp index d589a7dfc62..e4e9db129bb 100644 --- a/src/plugins/coreplugin/minisplitter.cpp +++ b/src/plugins/coreplugin/minisplitter.cpp @@ -84,7 +84,7 @@ MiniSplitter::MiniSplitter(QWidget *parent, SplitterStyle style) { setHandleWidth(1); setChildrenCollapsible(false); - setProperty("minisplitter", true); + setProperty(Utils::StyleHelper::C_MINI_SPLITTER, true); } MiniSplitter::MiniSplitter(Qt::Orientation orientation, QWidget *parent, SplitterStyle style) @@ -93,7 +93,7 @@ MiniSplitter::MiniSplitter(Qt::Orientation orientation, QWidget *parent, Splitte { setHandleWidth(1); setChildrenCollapsible(false); - setProperty("minisplitter", true); + setProperty(Utils::StyleHelper::C_MINI_SPLITTER, true); } /*! diff --git a/src/plugins/coreplugin/navigationsubwidget.cpp b/src/plugins/coreplugin/navigationsubwidget.cpp index 1d5b0e4bb68..838635c7b2f 100644 --- a/src/plugins/coreplugin/navigationsubwidget.cpp +++ b/src/plugins/coreplugin/navigationsubwidget.cpp @@ -11,6 +11,7 @@ #include "navigationwidget.h" #include +#include #include #include @@ -53,7 +54,7 @@ NavigationSubWidget::NavigationSubWidget(NavigationWidget *parentWidget, int pos splitAction->setIcon(Utils::Icons::SPLIT_HORIZONTAL_TOOLBAR.icon()); splitAction->setToolTip(Tr::tr("Split")); splitAction->setPopupMode(QToolButton::InstantPopup); - splitAction->setProperty("noArrow", true); + splitAction->setProperty(StyleHelper::C_NO_ARROW, true); m_splitMenu = new QMenu(splitAction); splitAction->setMenu(m_splitMenu); connect(m_splitMenu, &QMenu::aboutToShow, this, &NavigationSubWidget::populateSplitMenu); diff --git a/src/plugins/coreplugin/outputpanemanager.cpp b/src/plugins/coreplugin/outputpanemanager.cpp index 97b41776941..037779f0bfb 100644 --- a/src/plugins/coreplugin/outputpanemanager.cpp +++ b/src/plugins/coreplugin/outputpanemanager.cpp @@ -166,10 +166,15 @@ void IOutputPane::setFilteringEnabled(bool enable) } void IOutputPane::setupContext(const char *context, QWidget *widget) +{ + return setupContext(Context(context), widget); +} + +void IOutputPane::setupContext(const Context &context, QWidget *widget) { QTC_ASSERT(!m_context, return); m_context = new IContext(this); - m_context->setContext(Context(context)); + m_context->setContext(context); m_context->setWidget(widget); ICore::addContextObject(m_context); @@ -813,6 +818,13 @@ QSize OutputPaneToggleButton::sizeHint() const return s; } +static QRect bgRect(const QRect &widgetRect) +{ + // Removes/compensates the left and right margins of StyleHelper::drawPanelBgRect + return StyleHelper::toolbarStyle() == StyleHelper::ToolbarStyleCompact + ? widgetRect : widgetRect.adjusted(-2, 0, 2, 0); +} + void OutputPaneToggleButton::paintEvent(QPaintEvent*) { const QFontMetrics fm = fontMetrics(); @@ -834,7 +846,7 @@ void OutputPaneToggleButton::paintEvent(QPaintEvent*) c = Theme::BackgroundColorSelected; if (c != Theme::BackgroundColorDark) - p.fillRect(rect(), creatorTheme()->color(c)); + StyleHelper::drawPanelBgRect(&p, bgRect(rect()), creatorTheme()->color(c)); } else { const QImage *image = nullptr; if (isDown()) { @@ -870,9 +882,10 @@ void OutputPaneToggleButton::paintEvent(QPaintEvent*) { QColor c = creatorTheme()->color(Theme::OutputPaneButtonFlashColor); c.setAlpha (m_flashTimer->currentFrame()); - QRect r = creatorTheme()->flag(Theme::FlatToolBars) - ? rect() : rect().adjusted(numberAreaWidth(), 1, -1, -1); - p.fillRect(r, c); + if (creatorTheme()->flag(Theme::FlatToolBars)) + StyleHelper::drawPanelBgRect(&p, bgRect(rect()), c); + else + p.fillRect(rect().adjusted(numberAreaWidth(), 1, -1, -1), c); } p.setFont(font()); @@ -932,13 +945,7 @@ OutputPaneManageButton::OutputPaneManageButton() { setFocusPolicy(Qt::NoFocus); setCheckable(true); - setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); -} - -QSize OutputPaneManageButton::sizeHint() const -{ - ensurePolished(); - return QSize(numberAreaWidth(), 16); + setFixedWidth(StyleHelper::toolbarStyle() == Utils::StyleHelper::ToolbarStyleCompact ? 17 : 21); } void OutputPaneManageButton::paintEvent(QPaintEvent*) @@ -951,7 +958,9 @@ void OutputPaneManageButton::paintEvent(QPaintEvent*) QStyle *s = style(); QStyleOption arrowOpt; arrowOpt.initFrom(this); - arrowOpt.rect = QRect(6, rect().center().y() - 3, 8, 8); + constexpr int arrowSize = 8; + arrowOpt.rect = QRect(0, 0, arrowSize, arrowSize); + arrowOpt.rect.moveCenter(rect().center()); arrowOpt.rect.translate(0, -3); s->drawPrimitive(QStyle::PE_IndicatorArrowUp, &arrowOpt, &p, this); arrowOpt.rect.translate(0, 6); diff --git a/src/plugins/coreplugin/outputpanemanager.h b/src/plugins/coreplugin/outputpanemanager.h index aa3a550a28e..7dc7085d534 100644 --- a/src/plugins/coreplugin/outputpanemanager.h +++ b/src/plugins/coreplugin/outputpanemanager.h @@ -134,7 +134,6 @@ class OutputPaneManageButton : public QToolButton Q_OBJECT public: OutputPaneManageButton(); - QSize sizeHint() const override; void paintEvent(QPaintEvent*) override; }; diff --git a/src/plugins/coreplugin/patchtool.cpp b/src/plugins/coreplugin/patchtool.cpp index 9af08353807..744282728a8 100644 --- a/src/plugins/coreplugin/patchtool.cpp +++ b/src/plugins/coreplugin/patchtool.cpp @@ -7,7 +7,7 @@ #include "patchtool.h" #include -#include +#include #include @@ -76,7 +76,7 @@ static bool runPatchHelper(const QByteArray &input, const FilePath &workingDirec return false; } - QtcProcess patchProcess; + Process patchProcess; if (!workingDirectory.isEmpty()) patchProcess.setWorkingDirectory(workingDirectory); Environment env = Environment::systemEnvironment(); @@ -93,7 +93,8 @@ static bool runPatchHelper(const QByteArray &input, const FilePath &workingDirec if (patchAction == PatchAction::Revert) args << "-R"; args << "--binary"; - MessageManager::writeDisrupting(Tr::tr("Running in %1: %2 %3") + MessageManager::writeDisrupting( + Tr::tr("Running in \"%1\": %2 %3.") .arg(workingDirectory.toUserOutput(), patch.toUserOutput(), args.join(' '))); patchProcess.setCommand({patch, args}); patchProcess.setWriteData(input); diff --git a/src/plugins/coreplugin/plugindialog.cpp b/src/plugins/coreplugin/plugindialog.cpp index 1298238722c..24d8c8dff12 100644 --- a/src/plugins/coreplugin/plugindialog.cpp +++ b/src/plugins/coreplugin/plugindialog.cpp @@ -17,14 +17,11 @@ #include #include +#include -#include #include #include -#include -#include #include -#include using namespace Utils; @@ -35,29 +32,27 @@ PluginDialog::PluginDialog(QWidget *parent) : QDialog(parent), m_view(new ExtensionSystem::PluginView(this)) { - auto vl = new QVBoxLayout(this); - - auto filterLayout = new QHBoxLayout; - vl->addLayout(filterLayout); auto filterEdit = new Utils::FancyLineEdit(this); + filterEdit->setFocus(); filterEdit->setFiltering(true); connect(filterEdit, &Utils::FancyLineEdit::filterChanged, m_view, &ExtensionSystem::PluginView::setFilter); - filterLayout->addWidget(filterEdit); - - vl->addWidget(m_view); - - m_detailsButton = new QPushButton(Tr::tr("Details"), this); - m_errorDetailsButton = new QPushButton(Tr::tr("Error Details"), this); - m_installButton = new QPushButton(Tr::tr("Install Plugin..."), this); - m_detailsButton->setEnabled(false); - m_errorDetailsButton->setEnabled(false); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - buttonBox->addButton(m_detailsButton, QDialogButtonBox::ActionRole); - buttonBox->addButton(m_errorDetailsButton, QDialogButtonBox::ActionRole); - buttonBox->addButton(m_installButton, QDialogButtonBox::ActionRole); - vl->addWidget(buttonBox); + m_detailsButton = buttonBox->addButton(Tr::tr("Details"), QDialogButtonBox::ActionRole); + m_detailsButton->setEnabled(false); + m_errorDetailsButton = buttonBox->addButton(Tr::tr("Error Details"), + QDialogButtonBox::ActionRole); + m_errorDetailsButton->setEnabled(false); + m_installButton = buttonBox->addButton(Tr::tr("Install Plugin..."), + QDialogButtonBox::ActionRole); + + using namespace Layouting; + Column { + filterEdit, + m_view, + buttonBox, + }.attachTo(this); resize(650, 400); setWindowTitle(Tr::tr("Installed Plugins")); @@ -115,13 +110,16 @@ void PluginDialog::openDetails(ExtensionSystem::PluginSpec *spec) return; QDialog dialog(this); dialog.setWindowTitle(Tr::tr("Plugin Details of %1").arg(spec->name())); - auto layout = new QVBoxLayout; - dialog.setLayout(layout); auto details = new ExtensionSystem::PluginDetailsView(&dialog); - layout->addWidget(details); details->update(spec); QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Close, Qt::Horizontal, &dialog); - layout->addWidget(buttons); + + using namespace Layouting; + Column { + details, + buttons, + }.attachTo(&dialog); + connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); dialog.resize(400, 500); @@ -135,13 +133,16 @@ void PluginDialog::openErrorDetails() return; QDialog dialog(this); dialog.setWindowTitle(Tr::tr("Plugin Errors of %1").arg(spec->name())); - auto layout = new QVBoxLayout; - dialog.setLayout(layout); auto errors = new ExtensionSystem::PluginErrorView(&dialog); - layout->addWidget(errors); errors->update(spec); QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Close, Qt::Horizontal, &dialog); - layout->addWidget(buttons); + + using namespace Layouting; + Column { + errors, + buttons, + }.attachTo(&dialog); + connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); dialog.resize(500, 300); diff --git a/src/plugins/coreplugin/plugininstallwizard.cpp b/src/plugins/coreplugin/plugininstallwizard.cpp index 6547c78835f..ac2dfd6c240 100644 --- a/src/plugins/coreplugin/plugininstallwizard.cpp +++ b/src/plugins/coreplugin/plugininstallwizard.cpp @@ -11,13 +11,14 @@ #include #include +#include #include #include #include +#include #include +#include #include -#include -#include #include #include #include @@ -33,7 +34,6 @@ #include #include #include -#include #include @@ -80,19 +80,15 @@ public: , m_data(data) { setTitle(Tr::tr("Source")); - auto vlayout = new QVBoxLayout; - setLayout(vlayout); auto label = new QLabel( "

" + Tr::tr("Choose source location. This can be a plugin library file or a zip file.") + "

"); label->setWordWrap(true); - vlayout->addWidget(label); auto chooser = new PathChooser; chooser->setExpectedKind(PathChooser::Any); - vlayout->addWidget(chooser); connect(chooser, &PathChooser::textChanged, this, [this, chooser] { m_data->sourcePath = chooser->filePath(); updateWarnings(); @@ -101,7 +97,8 @@ public: m_info = new InfoLabel; m_info->setType(InfoLabel::Error); m_info->setVisible(false); - vlayout->addWidget(m_info); + + Layouting::Column { label, chooser, m_info }.attachTo(this); } void updateWarnings() @@ -153,8 +150,6 @@ public: , m_data(data) { setTitle(Tr::tr("Check Archive")); - auto vlayout = new QVBoxLayout; - setLayout(vlayout); m_label = new InfoLabel; m_label->setElideMode(Qt::ElideNone); @@ -163,13 +158,11 @@ public: m_output = new QTextEdit; m_output->setReadOnly(true); - auto hlayout = new QHBoxLayout; - hlayout->addWidget(m_label, 1); - hlayout->addStretch(); - hlayout->addWidget(m_cancelButton); - - vlayout->addLayout(hlayout); - vlayout->addWidget(m_output); + using namespace Layouting; + Column { + Row { m_label, st, m_cancelButton }, + m_output, + }.attachTo(this); } void initializePage() final @@ -221,8 +214,8 @@ public: m_label->setText(Tr::tr("There was an error while unarchiving.")); } } else { // unarchiving was successful, run a check - m_archiveCheck = Utils::runAsync( - [this](QFutureInterface &fi) { return checkContents(fi); }); + m_archiveCheck = Utils::asyncRun([this](QPromise &promise) + { return checkContents(promise); }); Utils::onFinished(m_archiveCheck, this, [this](const QFuture &f) { m_cancelButton->setVisible(false); m_cancelButton->disconnect(); @@ -248,7 +241,7 @@ public: } // Async. Result is set if any issue was found. - void checkContents(QFutureInterface &fi) + void checkContents(QPromise &promise) { QTC_ASSERT(m_tempDir.get(), return ); @@ -260,7 +253,7 @@ public: QDir::Files | QDir::NoSymLinks, QDirIterator::Subdirectories); while (it.hasNext()) { - if (fi.isCanceled()) + if (promise.isCanceled()) return; it.next(); PluginSpec *spec = PluginSpec::read(it.filePath()); @@ -275,18 +268,18 @@ public: }); if (found != dependencies.constEnd()) { if (!coreplugin->provides(found->name, found->version)) { - fi.reportResult({Tr::tr("Plugin requires an incompatible version of %1 (%2).") - .arg(Constants::IDE_DISPLAY_NAME) - .arg(found->version), - InfoLabel::Error}); + promise.addResult(ArchiveIssue{ + Tr::tr("Plugin requires an incompatible version of %1 (%2).") + .arg(Constants::IDE_DISPLAY_NAME).arg(found->version), + InfoLabel::Error}); return; } } return; // successful / no error } } - fi.reportResult({Tr::tr("Did not find %1 plugin.").arg(Constants::IDE_DISPLAY_NAME), - InfoLabel::Error}); + promise.addResult(ArchiveIssue{Tr::tr("Did not find %1 plugin.") + .arg(Constants::IDE_DISPLAY_NAME), InfoLabel::Error}); } void cleanupPage() final @@ -322,13 +315,9 @@ public: , m_data(data) { setTitle(Tr::tr("Install Location")); - auto vlayout = new QVBoxLayout; - setLayout(vlayout); auto label = new QLabel("

" + Tr::tr("Choose install location.") + "

"); label->setWordWrap(true); - vlayout->addWidget(label); - vlayout->addSpacing(10); auto localInstall = new QRadioButton(Tr::tr("User plugins")); localInstall->setChecked(!m_data->installIntoApplication); @@ -338,10 +327,6 @@ public: localLabel->setWordWrap(true); localLabel->setAttribute(Qt::WA_MacSmallSize, true); - vlayout->addWidget(localInstall); - vlayout->addWidget(localLabel); - vlayout->addSpacing(10); - auto appInstall = new QRadioButton( Tr::tr("%1 installation").arg(Constants::IDE_DISPLAY_NAME)); appInstall->setChecked(m_data->installIntoApplication); @@ -351,8 +336,11 @@ public: .arg(Constants::IDE_DISPLAY_NAME)); appLabel->setWordWrap(true); appLabel->setAttribute(Qt::WA_MacSmallSize, true); - vlayout->addWidget(appInstall); - vlayout->addWidget(appLabel); + + using namespace Layouting; + Column { + label, Space(10), localInstall, localLabel, Space(10), appInstall, appLabel, + }.attachTo(this); auto group = new QButtonGroup(this); group->addButton(localInstall); @@ -375,12 +363,9 @@ public: { setTitle(Tr::tr("Summary")); - auto vlayout = new QVBoxLayout; - setLayout(vlayout); - m_summaryLabel = new QLabel(this); m_summaryLabel->setWordWrap(true); - vlayout->addWidget(m_summaryLabel); + Layouting::Column { m_summaryLabel }.attachTo(this); } void initializePage() final @@ -403,7 +388,7 @@ static std::function postCopyOperation() return; // On macOS, downloaded files get a quarantine flag, remove it, otherwise it is a hassle // to get it loaded as a plugin in Qt Creator. - QtcProcess xattr; + Process xattr; xattr.setTimeoutS(1); xattr.setCommand({"/usr/bin/xattr", {"-d", "com.apple.quarantine", filePath.absoluteFilePath().toString()}}); xattr.runBlocking(); diff --git a/src/plugins/coreplugin/progressmanager/processprogress.cpp b/src/plugins/coreplugin/progressmanager/processprogress.cpp index 9a545561a78..9aeebf4ecf0 100644 --- a/src/plugins/coreplugin/progressmanager/processprogress.cpp +++ b/src/plugins/coreplugin/progressmanager/processprogress.cpp @@ -6,8 +6,8 @@ #include "progressmanager.h" #include "../coreplugintr.h" +#include #include -#include #include @@ -18,13 +18,13 @@ namespace Core { class ProcessProgressPrivate : public QObject { public: - explicit ProcessProgressPrivate(ProcessProgress *progress, QtcProcess *process); + explicit ProcessProgressPrivate(ProcessProgress *progress, Process *process); ~ProcessProgressPrivate(); QString displayName() const; void parseProgress(const QString &inputText); - QtcProcess *m_process = nullptr; + Process *m_process = nullptr; ProgressParser m_parser = {}; QFutureWatcher m_watcher; QFutureInterface m_futureInterface; @@ -33,7 +33,7 @@ public: FutureProgress::KeepOnFinishType m_keep = FutureProgress::HideOnFinish; }; -ProcessProgressPrivate::ProcessProgressPrivate(ProcessProgress *progress, QtcProcess *process) +ProcessProgressPrivate::ProcessProgressPrivate(ProcessProgress *progress, Process *process) : QObject(progress) , m_process(process) { @@ -70,21 +70,22 @@ void ProcessProgressPrivate::parseProgress(const QString &inputText) /*! \class Core::ProcessProgress + \inmodule QtCreator \brief The ProcessProgress class is responsible for showing progress of the running process. It's possible to cancel the running process automatically after pressing a small 'x' - indicator on progress panel. In this case QtcProcess::stop() method is being called. + indicator on progress panel. In this case Process::stop() method is being called. */ -ProcessProgress::ProcessProgress(QtcProcess *process) +ProcessProgress::ProcessProgress(Process *process) : QObject(process) , d(new ProcessProgressPrivate(this, process)) { connect(&d->m_watcher, &QFutureWatcher::canceled, this, [this] { d->m_process->stop(); // TODO: See TaskProgress::setAutoStopOnCancel }); - connect(d->m_process, &QtcProcess::starting, this, [this] { + connect(d->m_process, &Process::starting, this, [this] { d->m_futureInterface = QFutureInterface(); d->m_futureInterface.setProgressRange(0, 1); d->m_watcher.setFuture(d->m_futureInterface.future()); @@ -100,7 +101,7 @@ ProcessProgress::ProcessProgress(QtcProcess *process) } d->m_futureProgress->setKeepOnFinish(d->m_keep); }); - connect(d->m_process, &QtcProcess::done, this, [this] { + connect(d->m_process, &Process::done, this, [this] { if (d->m_process->result() != ProcessResult::FinishedWithSuccess) d->m_futureInterface.reportCanceled(); d->m_futureInterface.reportFinished(); @@ -124,9 +125,9 @@ void ProcessProgress::setKeepOnFinish(FutureProgress::KeepOnFinishType keepType) void ProcessProgress::setProgressParser(const ProgressParser &parser) { if (d->m_parser) { - disconnect(d->m_process, &QtcProcess::textOnStandardOutput, + disconnect(d->m_process, &Process::textOnStandardOutput, d.get(), &ProcessProgressPrivate::parseProgress); - disconnect(d->m_process, &QtcProcess::textOnStandardError, + disconnect(d->m_process, &Process::textOnStandardError, d.get(), &ProcessProgressPrivate::parseProgress); } d->m_parser = parser; @@ -137,9 +138,9 @@ void ProcessProgress::setProgressParser(const ProgressParser &parser) qWarning() << "Setting progress parser on a process without changing process' " "text channel mode is no-op."); - connect(d->m_process, &QtcProcess::textOnStandardOutput, + connect(d->m_process, &Process::textOnStandardOutput, d.get(), &ProcessProgressPrivate::parseProgress); - connect(d->m_process, &QtcProcess::textOnStandardError, + connect(d->m_process, &Process::textOnStandardError, d.get(), &ProcessProgressPrivate::parseProgress); } diff --git a/src/plugins/coreplugin/progressmanager/processprogress.h b/src/plugins/coreplugin/progressmanager/processprogress.h index fd2f64caf03..e91c0b7b3c7 100644 --- a/src/plugins/coreplugin/progressmanager/processprogress.h +++ b/src/plugins/coreplugin/progressmanager/processprogress.h @@ -9,7 +9,7 @@ #include -namespace Utils { class QtcProcess; } +namespace Utils { class Process; } namespace Core { @@ -20,7 +20,7 @@ class ProcessProgressPrivate; class CORE_EXPORT ProcessProgress : public QObject { public: - ProcessProgress(Utils::QtcProcess *process); // Makes ProcessProgress a child of process + ProcessProgress(Utils::Process *process); // Makes ProcessProgress a child of process ~ProcessProgress() override; void setDisplayName(const QString &name); diff --git a/src/plugins/coreplugin/progressmanager/progressmanager.cpp b/src/plugins/coreplugin/progressmanager/progressmanager.cpp index efc97312217..5504f8fe0ce 100644 --- a/src/plugins/coreplugin/progressmanager/progressmanager.cpp +++ b/src/plugins/coreplugin/progressmanager/progressmanager.cpp @@ -109,7 +109,7 @@ namespace Core { start a task concurrently in a different thread. QtConcurrent has several different functions to run e.g. a class function in a different thread. Qt Creator itself - adds a few more in \c{src/libs/qtconcurrent/runextensions.h}. + adds a few more in \c{src/libs/utils/async.h}. The QtConcurrent functions to run a concurrent task return a \c QFuture object. This is what you want to give the ProgressManager in the addTask() function. @@ -740,6 +740,33 @@ FutureProgress *ProgressManager::addTimedTask(const QFutureInterface &futu return fp; } +FutureProgress *ProgressManager::addTimedTask(const QFuture &future, const QString &title, + Id type, int expectedSeconds, ProgressFlags flags) +{ + QFutureInterface dummyFutureInterface; + QFuture dummyFuture = dummyFutureInterface.future(); + FutureProgress *fp = m_instance->doAddTask(dummyFuture, title, type, flags); + (void) new ProgressTimer(dummyFutureInterface, expectedSeconds, fp); + + QFutureWatcher *dummyWatcher = new QFutureWatcher(fp); + connect(dummyWatcher, &QFutureWatcher::canceled, dummyWatcher, [future] { + QFuture mutableFuture = future; + mutableFuture.cancel(); + }); + dummyWatcher->setFuture(dummyFuture); + + QFutureWatcher *origWatcher = new QFutureWatcher(fp); + connect(origWatcher, &QFutureWatcher::finished, origWatcher, [future, dummyFutureInterface] { + QFutureInterface mutableDummyFutureInterface = dummyFutureInterface; + if (future.isCanceled()) + mutableDummyFutureInterface.reportCanceled(); + mutableDummyFutureInterface.reportFinished(); + }); + origWatcher->setFuture(future); + + return fp; +} + /*! Shows the given \a text in a platform dependent way in the application icon in the system's task bar or dock. This is used to show the number diff --git a/src/plugins/coreplugin/progressmanager/progressmanager.h b/src/plugins/coreplugin/progressmanager/progressmanager.h index 78c1b771aea..8c51bf4ccd4 100644 --- a/src/plugins/coreplugin/progressmanager/progressmanager.h +++ b/src/plugins/coreplugin/progressmanager/progressmanager.h @@ -40,6 +40,8 @@ public: Utils::Id type, ProgressFlags flags = {}); static FutureProgress *addTimedTask(const QFutureInterface &fi, const QString &title, Utils::Id type, int expectedSeconds, ProgressFlags flags = {}); + static FutureProgress *addTimedTask(const QFuture &future, const QString &title, + Utils::Id type, int expectedSeconds, ProgressFlags flags = {}); static void setApplicationLabel(const QString &text); public slots: diff --git a/src/plugins/coreplugin/progressmanager/progressview.cpp b/src/plugins/coreplugin/progressmanager/progressview.cpp index 498ec8ae7b0..45b10286dc4 100644 --- a/src/plugins/coreplugin/progressmanager/progressview.cpp +++ b/src/plugins/coreplugin/progressmanager/progressview.cpp @@ -5,11 +5,19 @@ #include "../coreplugintr.h" +#include +#include + #include #include #include +#include #include +using namespace Utils; + +const int PIN_SIZE = 12; + namespace Core::Internal { ProgressView::ProgressView(QWidget *parent) @@ -21,15 +29,29 @@ ProgressView::ProgressView(QWidget *parent) m_layout->setSpacing(0); m_layout->setSizeConstraint(QLayout::SetFixedSize); setWindowTitle(Tr::tr("Processes")); + + auto pinButton = new OverlayWidget(this); + pinButton->attachToWidget(this); + pinButton->setAttribute(Qt::WA_TransparentForMouseEvents, false); // override OverlayWidget + pinButton->setPaintFunction([](QWidget *that, QPainter &p, QPaintEvent *) { + static const QIcon icon = Icon({{":/utils/images/pinned_small.png", Theme::IconsBaseColor}}, + Icon::Tint) + .icon(); + QRect iconRect(0, 0, PIN_SIZE, PIN_SIZE); + iconRect.moveTopRight(that->rect().topRight()); + icon.paint(&p, iconRect); + }); + pinButton->setVisible(false); + pinButton->installEventFilter(this); + m_pinButton = pinButton; } ProgressView::~ProgressView() = default; void ProgressView::addProgressWidget(QWidget *widget) { - if (m_layout->count() == 0) - m_anchorBottomRight = {}; // reset temporarily user-moved progress details m_layout->insertWidget(0, widget); + m_pinButton->raise(); } void ProgressView::removeProgressWidget(QWidget *widget) @@ -63,9 +85,12 @@ bool ProgressView::event(QEvent *event) reposition(); } else if (event->type() == QEvent::Enter) { m_hovered = true; + if (m_anchorBottomRight != QPoint()) + m_pinButton->setVisible(true); emit hoveredChanged(m_hovered); } else if (event->type() == QEvent::Leave) { m_hovered = false; + m_pinButton->setVisible(false); emit hoveredChanged(m_hovered); } else if (event->type() == QEvent::Show) { m_anchorBottomRight = {}; // reset temporarily user-moved progress details @@ -78,6 +103,16 @@ bool ProgressView::eventFilter(QObject *obj, QEvent *event) { if ((obj == parentWidget() || obj == m_referenceWidget) && event->type() == QEvent::Resize) reposition(); + if (obj == m_pinButton && event->type() == QEvent::MouseButtonRelease) { + auto me = static_cast(event); + if (me->button() == Qt::LeftButton + && QRectF(m_pinButton->width() - PIN_SIZE, 0, PIN_SIZE, PIN_SIZE) + .contains(me->position())) { + me->accept(); + m_anchorBottomRight = {}; + reposition(); + } + } return false; } @@ -133,6 +168,9 @@ void ProgressView::reposition() { if (!parentWidget() || !m_referenceWidget) return; + + m_pinButton->setVisible(m_anchorBottomRight != QPoint() && m_hovered); + move(boundedInParent(this, topRightReferenceInParent() + m_anchorBottomRight, parentWidget()) - rect().bottomRight()); } diff --git a/src/plugins/coreplugin/progressmanager/progressview.h b/src/plugins/coreplugin/progressmanager/progressview.h index 37cfa248265..e0b42c8547e 100644 --- a/src/plugins/coreplugin/progressmanager/progressview.h +++ b/src/plugins/coreplugin/progressmanager/progressview.h @@ -44,6 +44,7 @@ private: QVBoxLayout *m_layout; QWidget *m_referenceWidget = nullptr; + QWidget *m_pinButton = nullptr; // dragging std::optional m_clickPosition; diff --git a/src/plugins/coreplugin/progressmanager/taskprogress.cpp b/src/plugins/coreplugin/progressmanager/taskprogress.cpp index 6c7ee0bbb79..5ee5feb24a8 100644 --- a/src/plugins/coreplugin/progressmanager/taskprogress.cpp +++ b/src/plugins/coreplugin/progressmanager/taskprogress.cpp @@ -6,14 +6,16 @@ #include "futureprogress.h" #include "progressmanager.h" +#include + #include #include -#include #include #include using namespace Utils; +using namespace Tasking; namespace Core { @@ -89,6 +91,7 @@ void TaskProgressPrivate::updateProgress() /*! \class Core::TaskProgress + \inmodule QtCreator \brief The TaskProgress class is responsible for showing progress of the running task tree. diff --git a/src/plugins/coreplugin/progressmanager/taskprogress.h b/src/plugins/coreplugin/progressmanager/taskprogress.h index e15967fa2b8..d82fc433d5e 100644 --- a/src/plugins/coreplugin/progressmanager/taskprogress.h +++ b/src/plugins/coreplugin/progressmanager/taskprogress.h @@ -9,7 +9,7 @@ #include -namespace Utils { class TaskTree; } +namespace Tasking { class TaskTree; } namespace Core { @@ -20,7 +20,7 @@ class CORE_EXPORT TaskProgress : public QObject Q_OBJECT public: - TaskProgress(Utils::TaskTree *taskTree); // Makes TaskProgress a child of task tree + TaskProgress(Tasking::TaskTree *taskTree); // Makes TaskProgress a child of task tree ~TaskProgress() override; void setId(Utils::Id id); diff --git a/src/plugins/coreplugin/session.cpp b/src/plugins/coreplugin/session.cpp new file mode 100644 index 00000000000..7238a09a2ee --- /dev/null +++ b/src/plugins/coreplugin/session.cpp @@ -0,0 +1,745 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "session.h" +#include "session_p.h" + +#include "sessiondialog.h" + +#include "actionmanager/actioncontainer.h" +#include "actionmanager/actionmanager.h" +#include "coreconstants.h" +#include "coreplugin.h" +#include "editormanager/editormanager.h" +#include "icore.h" +#include "modemanager.h" +#include "progressmanager/progressmanager.h" + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace ExtensionSystem; +using namespace Utils; + +namespace Core { + +namespace PE { +struct Tr +{ + Q_DECLARE_TR_FUNCTIONS(QtC::ProjectExplorer) +}; +} // namespace PE + +const char DEFAULT_SESSION[] = "default"; +const char LAST_ACTIVE_TIMES_KEY[] = "LastActiveTimes"; +const char STARTUPSESSION_KEY[] = "ProjectExplorer/SessionToRestore"; +const char LASTSESSION_KEY[] = "ProjectExplorer/StartupSession"; +const char AUTO_RESTORE_SESSION_SETTINGS_KEY[] = "ProjectExplorer/Settings/AutoRestoreLastSession"; +static bool kIsAutoRestoreLastSessionDefault = false; +const char M_SESSION[] = "ProjectExplorer.Menu.Session"; + +/*! + \class Core::SessionManager + \inmodule QtCreator + + \brief The SessionManager class manages sessions. + + TODO the interface of this class is not really great. + The implementation suffers from that all the functions from the + public interface just wrap around functions which do the actual work. + This could be improved. +*/ + +static SessionManager *m_instance = nullptr; +SessionManagerPrivate *sb_d = nullptr; + +SessionManager::SessionManager() +{ + m_instance = this; + sb_d = new SessionManagerPrivate; + + connect(ICore::instance(), &ICore::coreOpened, this, [] { sb_d->restoreStartupSession(); }); + + connect(ModeManager::instance(), &ModeManager::currentModeChanged, + this, &SessionManager::saveActiveMode); + + connect(ICore::instance(), &ICore::saveSettingsRequested, this, [] { + if (!SessionManager::isLoadingSession()) + SessionManager::saveSession(); + sb_d->saveSettings(); + }); + + connect(EditorManager::instance(), &EditorManager::editorOpened, + this, &SessionManager::markSessionFileDirty); + connect(EditorManager::instance(), &EditorManager::editorsClosed, + this, &SessionManager::markSessionFileDirty); + connect(EditorManager::instance(), &EditorManager::autoSaved, this, [] { + if (!PluginManager::isShuttingDown() && !SessionManager::isLoadingSession()) + SessionManager::saveSession(); + }); + + // session menu + ActionContainer *mfile = ActionManager::actionContainer(Core::Constants::M_FILE); + ActionContainer *msession = ActionManager::createMenu(M_SESSION); + msession->menu()->setTitle(PE::Tr::tr("S&essions")); + msession->setOnAllDisabledBehavior(ActionContainer::Show); + mfile->addMenu(msession, Core::Constants::G_FILE_OPEN); + sb_d->m_sessionMenu = msession->menu(); + connect(mfile->menu(), &QMenu::aboutToShow, this, [] { sb_d->updateSessionMenu(); }); + + // session manager action + sb_d->m_sessionManagerAction = new QAction(PE::Tr::tr("&Manage..."), this); + sb_d->m_sessionMenu->addAction(sb_d->m_sessionManagerAction); + sb_d->m_sessionMenu->addSeparator(); + Command *cmd = ActionManager::registerAction(sb_d->m_sessionManagerAction, + "ProjectExplorer.ManageSessions"); + cmd->setDefaultKeySequence(QKeySequence()); + connect(sb_d->m_sessionManagerAction, + &QAction::triggered, + SessionManager::instance(), + &SessionManager::showSessionManager); + + MacroExpander *expander = Utils::globalMacroExpander(); + expander->registerFileVariables("Session", + PE::Tr::tr("File where current session is saved."), + [] { + return SessionManager::sessionNameToFileName( + SessionManager::activeSession()); + }); + expander->registerVariable("Session:Name", PE::Tr::tr("Name of current session."), [] { + return SessionManager::activeSession(); + }); + + sb_d->restoreSettings(); +} + +SessionManager::~SessionManager() +{ + emit m_instance->aboutToUnloadSession(sb_d->m_sessionName); + delete sb_d->m_writer; + delete sb_d; + sb_d = nullptr; +} + +SessionManager *SessionManager::instance() +{ + return m_instance; +} + +bool SessionManager::isDefaultVirgin() +{ + return isDefaultSession(sb_d->m_sessionName) && sb_d->m_virginSession; +} + +bool SessionManager::isDefaultSession(const QString &session) +{ + return session == QLatin1String(DEFAULT_SESSION); +} + +void SessionManager::saveActiveMode(Id mode) +{ + if (mode != Core::Constants::MODE_WELCOME) + setValue(QLatin1String("ActiveMode"), mode.toString()); +} + +bool SessionManager::isLoadingSession() +{ + return sb_d->m_loadingSession; +} + +/*! + Lets other plugins store persistent values specified by \a name and \a value + within the session file. +*/ + +void SessionManager::setValue(const QString &name, const QVariant &value) +{ + if (sb_d->m_values.value(name) == value) + return; + sb_d->m_values.insert(name, value); +} + +QVariant SessionManager::value(const QString &name) +{ + auto it = sb_d->m_values.constFind(name); + return (it == sb_d->m_values.constEnd()) ? QVariant() : *it; +} + +void SessionManager::setSessionValue(const QString &name, const QVariant &value) +{ + sb_d->m_sessionValues.insert(name, value); +} + +QVariant SessionManager::sessionValue(const QString &name, const QVariant &defaultValue) +{ + auto it = sb_d->m_sessionValues.constFind(name); + return (it == sb_d->m_sessionValues.constEnd()) ? defaultValue : *it; +} + +QString SessionManager::activeSession() +{ + return sb_d->m_sessionName; +} + +QStringList SessionManager::sessions() +{ + if (sb_d->m_sessions.isEmpty()) { + // We are not initialized yet, so do that now + const FilePaths sessionFiles = + ICore::userResourcePath().dirEntries({{"*qws"}}, QDir::Time | QDir::Reversed); + const QVariantMap lastActiveTimes = ICore::settings()->value(LAST_ACTIVE_TIMES_KEY).toMap(); + for (const FilePath &file : sessionFiles) { + const QString &name = file.completeBaseName(); + sb_d->m_sessionDateTimes.insert(name, file.lastModified()); + const auto lastActiveTime = lastActiveTimes.find(name); + sb_d->m_lastActiveTimes.insert(name, lastActiveTime != lastActiveTimes.end() + ? lastActiveTime->toDateTime() + : file.lastModified()); + if (name != QLatin1String(DEFAULT_SESSION)) + sb_d->m_sessions << name; + } + sb_d->m_sessions.prepend(QLatin1String(DEFAULT_SESSION)); + } + return sb_d->m_sessions; +} + +QDateTime SessionManager::sessionDateTime(const QString &session) +{ + return sb_d->m_sessionDateTimes.value(session); +} + +QDateTime SessionManager::lastActiveTime(const QString &session) +{ + return sb_d->m_lastActiveTimes.value(session); +} + +FilePath SessionManager::sessionNameToFileName(const QString &session) +{ + return ICore::userResourcePath(session + ".qws"); +} + +/*! + Creates \a session, but does not actually create the file. + + Returns whether the creation was successful. + +*/ + +bool SessionManager::createSession(const QString &session) +{ + if (sessions().contains(session)) + return false; + Q_ASSERT(sb_d->m_sessions.size() > 0); + sb_d->m_sessions.insert(1, session); + sb_d->m_lastActiveTimes.insert(session, QDateTime::currentDateTime()); + emit instance()->sessionCreated(session); + return true; +} + +bool SessionManager::renameSession(const QString &original, const QString &newName) +{ + if (!cloneSession(original, newName)) + return false; + if (original == activeSession()) + loadSession(newName); + emit instance()->sessionRenamed(original, newName); + return deleteSession(original); +} + +void SessionManager::showSessionManager() +{ + saveSession(); + Internal::SessionDialog sessionDialog(ICore::dialogParent()); + sessionDialog.setAutoLoadSession(sb_d->isAutoRestoreLastSession()); + sessionDialog.exec(); + sb_d->setAutoRestoreLastSession(sessionDialog.autoLoadSession()); +} + +/*! + Shows a dialog asking the user to confirm the deletion of the specified + \a sessions. + + Returns whether the user confirmed the deletion. +*/ +bool SessionManager::confirmSessionDelete(const QStringList &sessions) +{ + const QString title = sessions.size() == 1 ? PE::Tr::tr("Delete Session") + : PE::Tr::tr("Delete Sessions"); + const QString question + = sessions.size() == 1 + ? PE::Tr::tr("Delete session %1?").arg(sessions.first()) + : PE::Tr::tr("Delete these sessions?\n %1").arg(sessions.join("\n ")); + return QMessageBox::question(ICore::dialogParent(), + title, + question, + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes; +} + +/*! + Deletes \a session name from session list and the file from disk. + + Returns whether the deletion was successful. +*/ +bool SessionManager::deleteSession(const QString &session) +{ + if (!sb_d->m_sessions.contains(session)) + return false; + sb_d->m_sessions.removeOne(session); + sb_d->m_lastActiveTimes.remove(session); + emit instance()->sessionRemoved(session); + FilePath sessionFile = sessionNameToFileName(session); + if (sessionFile.exists()) + return sessionFile.removeFile(); + return false; +} + +void SessionManager::deleteSessions(const QStringList &sessions) +{ + for (const QString &session : sessions) + deleteSession(session); +} + +bool SessionManager::cloneSession(const QString &original, const QString &clone) +{ + if (!sb_d->m_sessions.contains(original)) + return false; + + FilePath sessionFile = sessionNameToFileName(original); + // If the file does not exist, we can still clone + if (!sessionFile.exists() || sessionFile.copyFile(sessionNameToFileName(clone))) { + sb_d->m_sessions.insert(1, clone); + sb_d->m_sessionDateTimes.insert(clone, sessionNameToFileName(clone).lastModified()); + emit instance()->sessionCreated(clone); + + return true; + } + return false; +} + +static QString determineSessionToRestoreAtStartup() +{ + // TODO (session) move argument to core + // Process command line arguments first: + const bool lastSessionArg = PluginManager::specForPlugin(Internal::CorePlugin::instance()) + ->arguments() + .contains("-lastsession"); + if (lastSessionArg && !SessionManager::startupSession().isEmpty()) + return SessionManager::startupSession(); + const QStringList arguments = PluginManager::arguments(); + QStringList sessions = SessionManager::sessions(); + // We have command line arguments, try to find a session in them + // Default to no session loading + for (const QString &arg : arguments) { + if (sessions.contains(arg)) { + // Session argument + return arg; + } + } + // Handle settings only after command line arguments: + if (sb_d->m_isAutoRestoreLastSession) + return SessionManager::startupSession(); + return {}; +} + +void SessionManagerPrivate::restoreStartupSession() +{ + m_isStartupSessionRestored = true; + QString sessionToRestoreAtStartup = determineSessionToRestoreAtStartup(); + if (!sessionToRestoreAtStartup.isEmpty()) + ModeManager::activateMode(Core::Constants::MODE_EDIT); + + // We have command line arguments, try to find a session in them + QStringList arguments = PluginManager::arguments(); + if (!sessionToRestoreAtStartup.isEmpty() && !arguments.isEmpty()) + arguments.removeOne(sessionToRestoreAtStartup); + + // Massage the argument list. + // Be smart about directories: If there is a session of that name, load it. + // Other than that, look for project files in it. The idea is to achieve + // 'Do what I mean' functionality when starting Creator in a directory with + // the single command line argument '.' and avoid editor warnings about not + // being able to open directories. + // In addition, convert "filename" "+45" or "filename" ":23" into + // "filename+45" and "filename:23". + if (!arguments.isEmpty()) { + const QStringList sessions = SessionManager::sessions(); + for (int a = 0; a < arguments.size();) { + const QString &arg = arguments.at(a); + const QFileInfo fi(arg); + if (fi.isDir()) { + const QDir dir(fi.absoluteFilePath()); + // Does the directory name match a session? + if (sessionToRestoreAtStartup.isEmpty() && sessions.contains(dir.dirName())) { + sessionToRestoreAtStartup = dir.dirName(); + arguments.removeAt(a); + continue; + } + } // Done directories. + // Converts "filename" "+45" or "filename" ":23" into "filename+45" and "filename:23" + if (a && (arg.startsWith(QLatin1Char('+')) || arg.startsWith(QLatin1Char(':')))) { + arguments[a - 1].append(arguments.takeAt(a)); + continue; + } + ++a; + } // for arguments + } // !arguments.isEmpty() + + // Restore latest session or what was passed on the command line + SessionManager::loadSession(!sessionToRestoreAtStartup.isEmpty() ? sessionToRestoreAtStartup + : QString(), + true); + + // delay opening projects from the command line even more + QTimer::singleShot(0, m_instance, [arguments] { + ICore::openFiles(Utils::transform(arguments, &FilePath::fromUserInput), + ICore::OpenFilesFlags(ICore::CanContainLineAndColumnNumbers + | ICore::SwitchMode)); + emit m_instance->startupSessionRestored(); + }); +} + +void SessionManagerPrivate::saveSettings() +{ + QtcSettings *s = ICore::settings(); + QVariantMap times; + for (auto it = sb_d->m_lastActiveTimes.cbegin(); it != sb_d->m_lastActiveTimes.cend(); ++it) + times.insert(it.key(), it.value()); + s->setValue(LAST_ACTIVE_TIMES_KEY, times); + if (SessionManager::isDefaultVirgin()) { + s->remove(STARTUPSESSION_KEY); + } else { + s->setValue(STARTUPSESSION_KEY, SessionManager::activeSession()); + s->setValue(LASTSESSION_KEY, SessionManager::activeSession()); + } + s->setValueWithDefault(AUTO_RESTORE_SESSION_SETTINGS_KEY, + sb_d->m_isAutoRestoreLastSession, + kIsAutoRestoreLastSessionDefault); +} + +void SessionManagerPrivate::restoreSettings() +{ + sb_d->m_isAutoRestoreLastSession = ICore::settings() + ->value(AUTO_RESTORE_SESSION_SETTINGS_KEY, + kIsAutoRestoreLastSessionDefault) + .toBool(); +} + +bool SessionManagerPrivate::isAutoRestoreLastSession() +{ + return sb_d->m_isAutoRestoreLastSession; +} + +void SessionManagerPrivate::setAutoRestoreLastSession(bool restore) +{ + sb_d->m_isAutoRestoreLastSession = restore; +} + +void SessionManagerPrivate::updateSessionMenu() +{ + // Delete group of previous actions (the actions are owned by the group and are deleted with it) + auto oldGroup = m_sessionMenu->findChild(); + if (oldGroup) + delete oldGroup; + m_sessionMenu->clear(); + + m_sessionMenu->addAction(m_sessionManagerAction); + m_sessionMenu->addSeparator(); + auto *ag = new QActionGroup(m_sessionMenu); + const QString activeSession = SessionManager::activeSession(); + const bool isDefaultVirgin = SessionManager::isDefaultVirgin(); + + QStringList sessions = SessionManager::sessions(); + std::sort(std::next(sessions.begin()), sessions.end(), [](const QString &s1, const QString &s2) { + return SessionManager::lastActiveTime(s1) > SessionManager::lastActiveTime(s2); + }); + for (int i = 0; i < sessions.size(); ++i) { + const QString &session = sessions[i]; + + const QString actionText + = ActionManager::withNumberAccelerator(Utils::quoteAmpersands(session), i + 1); + QAction *act = ag->addAction(actionText); + act->setCheckable(true); + if (session == activeSession && !isDefaultVirgin) + act->setChecked(true); + QObject::connect(act, &QAction::triggered, SessionManager::instance(), [session] { + SessionManager::loadSession(session); + }); + } + m_sessionMenu->addActions(ag->actions()); + m_sessionMenu->setEnabled(true); +} + +void SessionManagerPrivate::restoreValues(const PersistentSettingsReader &reader) +{ + const QStringList keys = reader.restoreValue(QLatin1String("valueKeys")).toStringList(); + for (const QString &key : keys) { + QVariant value = reader.restoreValue(QLatin1String("value-") + key); + m_values.insert(key, value); + } +} + +void SessionManagerPrivate::restoreSessionValues(const PersistentSettingsReader &reader) +{ + const QVariantMap values = reader.restoreValues(); + // restore toplevel items that are not restored by restoreValues + const auto end = values.constEnd(); + for (auto it = values.constBegin(); it != end; ++it) { + if (it.key() == "valueKeys" || it.key().startsWith("value-")) + continue; + m_sessionValues.insert(it.key(), it.value()); + } +} + +void SessionManagerPrivate::restoreEditors() +{ + const QVariant editorsettings = m_sessionValues.value("EditorSettings"); + if (editorsettings.isValid()) { + EditorManager::restoreState(QByteArray::fromBase64(editorsettings.toByteArray())); + SessionManager::sessionLoadingProgress(); + } +} + +/*! + Returns the last session that was opened by the user. +*/ +QString SessionManager::lastSession() +{ + return ICore::settings()->value(LASTSESSION_KEY).toString(); +} + +/*! + Returns the session that was active when \QC was last closed, if any. +*/ +QString SessionManager::startupSession() +{ + return ICore::settings()->value(STARTUPSESSION_KEY).toString(); +} + +void SessionManager::markSessionFileDirty() +{ + sb_d->m_virginSession = false; +} + +void SessionManager::sessionLoadingProgress() +{ + sb_d->m_future.setProgressValue(sb_d->m_future.progressValue() + 1); + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); +} + +void SessionManager::addSessionLoadingSteps(int steps) +{ + sb_d->m_future.setProgressRange(0, sb_d->m_future.progressMaximum() + steps); +} + +/* + * ========== Notes on storing and loading the default session ========== + * The default session comes in two flavors: implicit and explicit. The implicit one, + * also referred to as "default virgin" in the code base, is the one that is active + * at start-up, if no session has been explicitly loaded due to command-line arguments + * or the "restore last session" setting in the session manager. + * The implicit default session silently turns into the explicit default session + * by loading a project or a file or changing settings in the Dependencies panel. The explicit + * default session can also be loaded by the user via the Welcome Screen. + * This mechanism somewhat complicates the handling of session-specific settings such as + * the ones in the task pane: Users expect that changes they make there become persistent, even + * when they are in the implicit default session. However, we can't just blindly store + * the implicit default session, because then we'd overwrite the project list of the explicit + * default session. Therefore, we use the following logic: + * - Upon start-up, if no session is to be explicitly loaded, we restore the parts of the + * explicit default session that are not related to projects, editors etc; the + * "general settings" of the session, so to speak. + * - When storing the implicit default session, we overwrite only these "general settings" + * of the explicit default session and keep the others as they are. + * - When switching from the implicit to the explicit default session, we keep the + * "general settings" and load everything else from the session file. + * This guarantees that user changes are properly transferred and nothing gets lost from + * either the implicit or the explicit default session. + * + */ +bool SessionManager::loadSession(const QString &session, bool initial) +{ + const bool loadImplicitDefault = session.isEmpty(); + const bool switchFromImplicitToExplicitDefault = session == DEFAULT_SESSION + && sb_d->m_sessionName == DEFAULT_SESSION + && !initial; + + // Do nothing if we have that session already loaded, + // exception if the session is the default virgin session + // we still want to be able to load the default session + if (session == sb_d->m_sessionName && !SessionManager::isDefaultVirgin()) + return true; + + if (!loadImplicitDefault && !SessionManager::sessions().contains(session)) + return false; + + // Try loading the file + FilePath fileName = SessionManager::sessionNameToFileName(loadImplicitDefault ? DEFAULT_SESSION + : session); + PersistentSettingsReader reader; + if (fileName.exists()) { + if (!reader.load(fileName)) { + QMessageBox::warning(ICore::dialogParent(), + PE::Tr::tr("Error while restoring session"), + PE::Tr::tr("Could not restore session %1") + .arg(fileName.toUserOutput())); + + return false; + } + + if (loadImplicitDefault) { + sb_d->restoreValues(reader); + emit SessionManager::instance()->sessionLoaded(DEFAULT_SESSION); + return true; + } + } else if (loadImplicitDefault) { + return true; + } + + sb_d->m_loadingSession = true; + + // Allow everyone to set something in the session and before saving + emit SessionManager::instance()->aboutToUnloadSession(sb_d->m_sessionName); + + if (!saveSession()) { + sb_d->m_loadingSession = false; + return false; + } + + // Clean up + if (!EditorManager::closeAllEditors()) { + sb_d->m_loadingSession = false; + return false; + } + + if (!switchFromImplicitToExplicitDefault) + sb_d->m_values.clear(); + sb_d->m_sessionValues.clear(); + + sb_d->m_sessionName = session; + delete sb_d->m_writer; + sb_d->m_writer = nullptr; + EditorManager::updateWindowTitles(); + + sb_d->m_virginSession = false; + + ProgressManager::addTask(sb_d->m_future.future(), + PE::Tr::tr("Loading Session"), + "ProjectExplorer.SessionFile.Load"); + + sb_d->m_future.setProgressRange(0, 1 /*initialization*/ + 1 /*editors*/); + sb_d->m_future.setProgressValue(0); + + if (fileName.exists()) { + if (!switchFromImplicitToExplicitDefault) + sb_d->restoreValues(reader); + sb_d->restoreSessionValues(reader); + } + + QColor c = QColor(SessionManager::sessionValue("Color").toString()); + if (c.isValid()) + StyleHelper::setBaseColor(c); + + SessionManager::sessionLoadingProgress(); + + sb_d->restoreEditors(); + + // let other code restore the session + emit SessionManager::instance()->aboutToLoadSession(session); + + sb_d->m_future.reportFinished(); + sb_d->m_future = QFutureInterface(); + + sb_d->m_lastActiveTimes.insert(session, QDateTime::currentDateTime()); + + emit SessionManager::instance()->sessionLoaded(session); + + sb_d->m_loadingSession = false; + return true; +} + +bool SessionManager::saveSession() +{ + emit SessionManager::instance()->aboutToSaveSession(); + + const FilePath filePath = SessionManager::sessionNameToFileName(sb_d->m_sessionName); + QVariantMap data; + + // See the explanation at loadSession() for how we handle the implicit default session. + if (SessionManager::isDefaultVirgin()) { + if (filePath.exists()) { + PersistentSettingsReader reader; + if (!reader.load(filePath)) { + QMessageBox::warning(ICore::dialogParent(), + PE::Tr::tr("Error while saving session"), + PE::Tr::tr("Could not save session %1") + .arg(filePath.toUserOutput())); + return false; + } + data = reader.restoreValues(); + } + } else { + const QColor c = StyleHelper::requestedBaseColor(); + if (c.isValid()) { + QString tmp = QString::fromLatin1("#%1%2%3") + .arg(c.red(), 2, 16, QLatin1Char('0')) + .arg(c.green(), 2, 16, QLatin1Char('0')) + .arg(c.blue(), 2, 16, QLatin1Char('0')); + setSessionValue("Color", tmp); + } + setSessionValue("EditorSettings", EditorManager::saveState().toBase64()); + + const auto end = sb_d->m_sessionValues.constEnd(); + for (auto it = sb_d->m_sessionValues.constBegin(); it != end; ++it) + data.insert(it.key(), it.value()); + } + + const auto end = sb_d->m_values.constEnd(); + QStringList keys; + for (auto it = sb_d->m_values.constBegin(); it != end; ++it) { + data.insert("value-" + it.key(), it.value()); + keys << it.key(); + } + data.insert("valueKeys", keys); + + if (!sb_d->m_writer || sb_d->m_writer->fileName() != filePath) { + delete sb_d->m_writer; + sb_d->m_writer = new PersistentSettingsWriter(filePath, "QtCreatorSession"); + } + const bool result = sb_d->m_writer->save(data, ICore::dialogParent()); + if (result) { + if (!SessionManager::isDefaultVirgin()) + sb_d->m_sessionDateTimes.insert(SessionManager::activeSession(), + QDateTime::currentDateTime()); + } else { + QMessageBox::warning(ICore::dialogParent(), + PE::Tr::tr("Error while saving session"), + PE::Tr::tr("Could not save session to file \"%1\"") + .arg(sb_d->m_writer->fileName().toUserOutput())); + } + + return result; +} + +bool SessionManager::isStartupSessionRestored() +{ + return sb_d->m_isStartupSessionRestored; +} + +} // namespace Core diff --git a/src/plugins/coreplugin/session.h b/src/plugins/coreplugin/session.h new file mode 100644 index 00000000000..30dc70edf4d --- /dev/null +++ b/src/plugins/coreplugin/session.h @@ -0,0 +1,90 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "core_global.h" + +#include +#include + +#include +#include +#include + +namespace Core { + +class CORE_EXPORT SessionManager : public QObject +{ + Q_OBJECT + +public: + SessionManager(); + ~SessionManager() override; + + static SessionManager *instance(); + + // higher level session management + static QString activeSession(); + static QString lastSession(); + static QString startupSession(); + static QStringList sessions(); + static QDateTime sessionDateTime(const QString &session); + static QDateTime lastActiveTime(const QString &session); + + static bool createSession(const QString &session); + + static bool confirmSessionDelete(const QStringList &sessions); + static bool deleteSession(const QString &session); + static void deleteSessions(const QStringList &sessions); + + static bool cloneSession(const QString &original, const QString &clone); + static bool renameSession(const QString &original, const QString &newName); + static void showSessionManager(); + + static Utils::FilePath sessionNameToFileName(const QString &session); + + static bool isDefaultVirgin(); + static bool isDefaultSession(const QString &session); + + // Let other plugins store persistent values within the session file + // These are settings that are also saved and loaded at startup, and are taken over + // to the default session when switching from implicit to explicit default session + static void setValue(const QString &name, const QVariant &value); + static QVariant value(const QString &name); + + // These are settings that are specific to a session and are not loaded + // at startup and also not taken over to the default session when switching from implicit + static void setSessionValue(const QString &name, const QVariant &value); + static QVariant sessionValue(const QString &name, const QVariant &defaultValue = {}); + + static bool isLoadingSession(); + static void markSessionFileDirty(); + + static void sessionLoadingProgress(); + static void addSessionLoadingSteps(int steps); + + static bool loadSession(const QString &session, bool initial = false); + static bool saveSession(); + +signals: + void startupSessionRestored(); + void aboutToUnloadSession(QString sessionName); + // Sent during session loading, after the values of the session are available via value() and + // sessionValue. Use to restore values from the new session + void aboutToLoadSession(QString sessionName); + void sessionLoaded(QString sessionName); + void aboutToSaveSession(); + + void sessionCreated(const QString &name); + void sessionRenamed(const QString &oldName, const QString &newName); + void sessionRemoved(const QString &name); + +public: // internal + static bool isStartupSessionRestored(); + +private: + static void saveActiveMode(Utils::Id mode); +}; + +} // namespace Core diff --git a/src/plugins/coreplugin/session_p.h b/src/plugins/coreplugin/session_p.h new file mode 100644 index 00000000000..20ceecbcbc3 --- /dev/null +++ b/src/plugins/coreplugin/session_p.h @@ -0,0 +1,57 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include + +#include + +QT_BEGIN_NAMESPACE +class QAction; +class QMenu; +QT_END_NAMESPACE + +using namespace Utils; + +namespace Core { + +class SessionManagerPrivate +{ +public: + void restoreStartupSession(); + + void restoreValues(const PersistentSettingsReader &reader); + void restoreSessionValues(const PersistentSettingsReader &reader); + void restoreEditors(); + + void saveSettings(); + void restoreSettings(); + bool isAutoRestoreLastSession(); + void setAutoRestoreLastSession(bool restore); + + void updateSessionMenu(); + + static QString windowTitleAddition(const FilePath &filePath); + static QString sessionTitle(const FilePath &filePath); + + QString m_sessionName = "default"; + bool m_isStartupSessionRestored = false; + bool m_isAutoRestoreLastSession = false; + bool m_virginSession = true; + bool m_loadingSession = false; + + mutable QStringList m_sessions; + mutable QHash m_sessionDateTimes; + QHash m_lastActiveTimes; + + QMap m_values; + QMap m_sessionValues; + QFutureInterface m_future; + PersistentSettingsWriter *m_writer = nullptr; + + QMenu *m_sessionMenu; + QAction *m_sessionManagerAction; +}; + +extern SessionManagerPrivate *sb_d; + +} // namespace Core diff --git a/src/plugins/projectexplorer/sessiondialog.cpp b/src/plugins/coreplugin/sessiondialog.cpp similarity index 84% rename from src/plugins/projectexplorer/sessiondialog.cpp rename to src/plugins/coreplugin/sessiondialog.cpp index 745a876230a..dd81f43fd09 100644 --- a/src/plugins/projectexplorer/sessiondialog.cpp +++ b/src/plugins/coreplugin/sessiondialog.cpp @@ -1,9 +1,8 @@ -// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "sessiondialog.h" -#include "projectexplorertr.h" #include "session.h" #include "sessionview.h" @@ -11,13 +10,21 @@ #include #include +#include #include #include #include #include #include -namespace ProjectExplorer::Internal { +namespace Core::Internal { + +namespace PE { +struct Tr +{ + Q_DECLARE_TR_FUNCTIONS(QtC::ProjectExplorer) +}; +} // namespace PE class SessionValidator : public QValidator { @@ -77,12 +84,14 @@ SessionNameInputDialog::SessionNameInputDialog(QWidget *parent) m_usedSwitchTo = true; }); - using namespace Utils::Layouting; + // clang-format off + using namespace Layouting; Column { - Tr::tr("Enter the name of the session:"), + PE::Tr::tr("Enter the name of the session:"), m_newSessionLineEdit, buttons, }.attachTo(this); + // clang-format on connect(m_newSessionLineEdit, &QLineEdit::textChanged, [this](const QString &text) { m_okButton->setEnabled(!text.isEmpty()); @@ -120,37 +129,35 @@ SessionDialog::SessionDialog(QWidget *parent) : QDialog(parent) { setObjectName("ProjectExplorer.SessionDialog"); resize(550, 400); - setWindowTitle(Tr::tr("Session Manager")); - + setWindowTitle(PE::Tr::tr("Session Manager")); auto sessionView = new SessionView(this); sessionView->setObjectName("sessionView"); sessionView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); sessionView->setActivationMode(Utils::DoubleClickActivation); - auto createNewButton = new QPushButton(Tr::tr("&New")); + auto createNewButton = new QPushButton(PE::Tr::tr("&New")); createNewButton->setObjectName("btCreateNew"); - m_openButton = new QPushButton(Tr::tr("&Open")); + m_openButton = new QPushButton(PE::Tr::tr("&Open")); m_openButton->setObjectName("btOpen"); - m_renameButton = new QPushButton(Tr::tr("&Rename")); - m_cloneButton = new QPushButton(Tr::tr("C&lone")); - m_deleteButton = new QPushButton(Tr::tr("&Delete")); + m_renameButton = new QPushButton(PE::Tr::tr("&Rename")); + m_cloneButton = new QPushButton(PE::Tr::tr("C&lone")); + m_deleteButton = new QPushButton(PE::Tr::tr("&Delete")); - m_autoLoadCheckBox = new QCheckBox(Tr::tr("Restore last session on startup")); + m_autoLoadCheckBox = new QCheckBox(PE::Tr::tr("Restore last session on startup")); auto buttonBox = new QDialogButtonBox(this); buttonBox->setStandardButtons(QDialogButtonBox::Close); m_openButton->setDefault(true); - // FIXME: Simplify translator's work. - auto whatsASessionLabel = new QLabel( - Tr::tr("" - "What is a Session?")); + auto whatsASessionLabel = new QLabel(QString("%1") + .arg(PE::Tr::tr("What is a Session?"))); whatsASessionLabel->setOpenExternalLinks(true); - using namespace Utils::Layouting; + using namespace Layouting; Column { Row { @@ -220,4 +227,4 @@ void SessionDialog::updateActions(const QStringList &sessions) m_deleteButton->setEnabled(!defaultIsSelected && !activeIsSelected); } -} // ProjectExplorer::Internal +} // namespace Core::Internal diff --git a/src/plugins/projectexplorer/sessiondialog.h b/src/plugins/coreplugin/sessiondialog.h similarity index 91% rename from src/plugins/projectexplorer/sessiondialog.h rename to src/plugins/coreplugin/sessiondialog.h index 56cab430ea7..b6512feab6c 100644 --- a/src/plugins/projectexplorer/sessiondialog.h +++ b/src/plugins/coreplugin/sessiondialog.h @@ -1,4 +1,4 @@ -// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #pragma once @@ -12,7 +12,7 @@ class QLineEdit; class QPushButton; QT_END_NAMESPACE -namespace ProjectExplorer::Internal { +namespace Core::Internal { class SessionDialog : public QDialog { @@ -53,4 +53,4 @@ private: bool m_usedSwitchTo = false; }; -} // ProjectExplorer::Internal +} // namespace Core::Internal diff --git a/src/plugins/projectexplorer/sessionmodel.cpp b/src/plugins/coreplugin/sessionmodel.cpp similarity index 75% rename from src/plugins/projectexplorer/sessionmodel.cpp rename to src/plugins/coreplugin/sessionmodel.cpp index d8c9aa935e2..fa0d8ee087e 100644 --- a/src/plugins/projectexplorer/sessionmodel.cpp +++ b/src/plugins/coreplugin/sessionmodel.cpp @@ -1,9 +1,8 @@ -// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "sessionmodel.h" -#include "projectexplorertr.h" #include "session.h" #include "sessiondialog.h" @@ -16,11 +15,17 @@ #include #include -using namespace Core; using namespace Utils; +using namespace Core::Internal; -namespace ProjectExplorer { -namespace Internal { +namespace Core { + +namespace PE { +struct Tr +{ + Q_DECLARE_TR_FUNCTIONS(QtC::ProjectExplorer) +}; +} // namespace PE SessionModel::SessionModel(QObject *parent) : QAbstractTableModel(parent) @@ -47,9 +52,11 @@ QVariant SessionModel::headerData(int section, Qt::Orientation orientation, int switch (role) { case Qt::DisplayRole: switch (section) { - case 0: result = Tr::tr("Session"); + case 0: + result = PE::Tr::tr("Session"); break; - case 1: result = Tr::tr("Last Modified"); + case 1: + result = PE::Tr::tr("Last Modified"); break; } // switch (section) break; @@ -87,17 +94,16 @@ QStringList pathsWithTildeHomePath(const FilePaths &paths) QVariant SessionModel::data(const QModelIndex &index, int role) const { - QVariant result; if (index.isValid()) { QString sessionName = m_sortedSessions.at(index.row()); switch (role) { case Qt::DisplayRole: switch (index.column()) { - case 0: result = sessionName; - break; - case 1: result = SessionManager::sessionDateTime(sessionName); - break; + case 0: + return sessionName; + case 1: + return SessionManager::sessionDateTime(sessionName); } // switch (section) break; case Qt::FontRole: { @@ -110,44 +116,30 @@ QVariant SessionModel::data(const QModelIndex &index, int role) const font.setBold(true); else font.setBold(false); - result = font; - } break; + return font; + } case DefaultSessionRole: - result = SessionManager::isDefaultSession(sessionName); - break; + return SessionManager::isDefaultSession(sessionName); case LastSessionRole: - result = SessionManager::lastSession() == sessionName; - break; + return SessionManager::lastSession() == sessionName; case ActiveSessionRole: - result = SessionManager::activeSession() == sessionName; - break; - case ProjectsPathRole: - result = pathsWithTildeHomePath(SessionManager::projectsForSessionName(sessionName)); - break; - case ProjectsDisplayRole: - result = pathsToBaseNames(SessionManager::projectsForSessionName(sessionName)); - break; + return SessionManager::activeSession() == sessionName; case ShortcutRole: { const Id sessionBase = SESSION_BASE_ID; if (Command *cmd = ActionManager::command(sessionBase.withSuffix(index.row() + 1))) - result = cmd->keySequence().toString(QKeySequence::NativeText); + return cmd->keySequence().toString(QKeySequence::NativeText); } break; } // switch (role) } - - return result; + return {}; } QHash SessionModel::roleNames() const { - static const QHash extraRoles{ - {Qt::DisplayRole, "sessionName"}, - {DefaultSessionRole, "defaultSession"}, - {ActiveSessionRole, "activeSession"}, - {LastSessionRole, "lastSession"}, - {ProjectsPathRole, "projectsPath"}, - {ProjectsDisplayRole, "projectsName"} - }; + static const QHash extraRoles{{Qt::DisplayRole, "sessionName"}, + {DefaultSessionRole, "defaultSession"}, + {ActiveSessionRole, "activeSession"}, + {LastSessionRole, "lastSession"}}; QHash roles = QAbstractTableModel::roleNames(); Utils::addToHash(&roles, extraRoles); return roles; @@ -195,8 +187,8 @@ void SessionModel::resetSessions() void SessionModel::newSession(QWidget *parent) { SessionNameInputDialog sessionInputDialog(parent); - sessionInputDialog.setWindowTitle(Tr::tr("New Session Name")); - sessionInputDialog.setActionText(Tr::tr("&Create"), Tr::tr("Create and &Open")); + sessionInputDialog.setWindowTitle(PE::Tr::tr("New Session Name")); + sessionInputDialog.setActionText(PE::Tr::tr("&Create"), PE::Tr::tr("Create and &Open")); runSessionNameInputDialog(&sessionInputDialog, [](const QString &newName) { SessionManager::createSession(newName); @@ -206,8 +198,8 @@ void SessionModel::newSession(QWidget *parent) void SessionModel::cloneSession(QWidget *parent, const QString &session) { SessionNameInputDialog sessionInputDialog(parent); - sessionInputDialog.setWindowTitle(Tr::tr("New Session Name")); - sessionInputDialog.setActionText(Tr::tr("&Clone"), Tr::tr("Clone and &Open")); + sessionInputDialog.setWindowTitle(PE::Tr::tr("New Session Name")); + sessionInputDialog.setActionText(PE::Tr::tr("&Clone"), PE::Tr::tr("Clone and &Open")); sessionInputDialog.setValue(session + " (2)"); runSessionNameInputDialog(&sessionInputDialog, [session](const QString &newName) { @@ -229,8 +221,8 @@ void SessionModel::deleteSessions(const QStringList &sessions) void SessionModel::renameSession(QWidget *parent, const QString &session) { SessionNameInputDialog sessionInputDialog(parent); - sessionInputDialog.setWindowTitle(Tr::tr("Rename Session")); - sessionInputDialog.setActionText(Tr::tr("&Rename"), Tr::tr("Rename and &Open")); + sessionInputDialog.setWindowTitle(PE::Tr::tr("Rename Session")); + sessionInputDialog.setActionText(PE::Tr::tr("&Rename"), PE::Tr::tr("Rename and &Open")); sessionInputDialog.setValue(session); runSessionNameInputDialog(&sessionInputDialog, [session](const QString &newName) { @@ -244,7 +236,8 @@ void SessionModel::switchToSession(const QString &session) emit sessionSwitched(); } -void SessionModel::runSessionNameInputDialog(SessionNameInputDialog *sessionInputDialog, std::function createSession) +void SessionModel::runSessionNameInputDialog(SessionNameInputDialog *sessionInputDialog, + std::function createSession) { if (sessionInputDialog->exec() == QDialog::Accepted) { QString newSession = sessionInputDialog->value(); @@ -262,5 +255,4 @@ void SessionModel::runSessionNameInputDialog(SessionNameInputDialog *sessionInpu } } -} // namespace Internal -} // namespace ProjectExplorer +} // namespace Core diff --git a/src/plugins/projectexplorer/sessionmodel.h b/src/plugins/coreplugin/sessionmodel.h similarity index 78% rename from src/plugins/projectexplorer/sessionmodel.h rename to src/plugins/coreplugin/sessionmodel.h index 82f19942109..79abd7ae3f1 100644 --- a/src/plugins/projectexplorer/sessionmodel.h +++ b/src/plugins/coreplugin/sessionmodel.h @@ -1,20 +1,21 @@ -// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #pragma once +#include "core_global.h" + #include #include -namespace ProjectExplorer { -namespace Internal { +namespace Core { const char SESSION_BASE_ID[] = "Welcome.OpenSession"; -class SessionNameInputDialog; +namespace Internal { class SessionNameInputDialog; } -class SessionModel : public QAbstractTableModel +class CORE_EXPORT SessionModel : public QAbstractTableModel { Q_OBJECT @@ -23,8 +24,6 @@ public: DefaultSessionRole = Qt::UserRole+1, LastSessionRole, ActiveSessionRole, - ProjectsPathRole, - ProjectsDisplayRole, ShortcutRole }; @@ -56,12 +55,12 @@ public slots: void switchToSession(const QString &session); private: - void runSessionNameInputDialog(ProjectExplorer::Internal::SessionNameInputDialog *sessionInputDialog, std::function createSession); + void runSessionNameInputDialog(Internal::SessionNameInputDialog *sessionInputDialog, + std::function createSession); QStringList m_sortedSessions; int m_currentSortColumn = 0; Qt::SortOrder m_currentSortOrder = Qt::AscendingOrder; }; -} // namespace Internal -} // namespace ProjectExplorer +} // namespace Core diff --git a/src/plugins/projectexplorer/sessionview.cpp b/src/plugins/coreplugin/sessionview.cpp similarity index 97% rename from src/plugins/projectexplorer/sessionview.cpp rename to src/plugins/coreplugin/sessionview.cpp index e4b3b1a5d0a..c3d7ed25b30 100644 --- a/src/plugins/projectexplorer/sessionview.cpp +++ b/src/plugins/coreplugin/sessionview.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "sessionview.h" @@ -12,7 +12,7 @@ #include #include -namespace ProjectExplorer { +namespace Core { namespace Internal { // custom item delegate class @@ -149,4 +149,4 @@ QStringList SessionView::selectedSessions() const } } // namespace Internal -} // namespace ProjectExplorer +} // namespace Core diff --git a/src/plugins/projectexplorer/sessionview.h b/src/plugins/coreplugin/sessionview.h similarity index 90% rename from src/plugins/projectexplorer/sessionview.h rename to src/plugins/coreplugin/sessionview.h index 656da080ea3..2581a8b1716 100644 --- a/src/plugins/projectexplorer/sessionview.h +++ b/src/plugins/coreplugin/sessionview.h @@ -1,4 +1,4 @@ -// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #pragma once @@ -9,7 +9,7 @@ #include -namespace ProjectExplorer::Internal { +namespace Core::Internal { class SessionView : public Utils::TreeView { @@ -44,4 +44,4 @@ private: SessionModel m_sessionModel; }; -} // ProjectExplorer::Internal +} // namespace Core::Internal diff --git a/src/plugins/coreplugin/statusbarmanager.cpp b/src/plugins/coreplugin/statusbarmanager.cpp index 48413379756..83d910867bf 100644 --- a/src/plugins/coreplugin/statusbarmanager.cpp +++ b/src/plugins/coreplugin/statusbarmanager.cpp @@ -59,7 +59,6 @@ static void createStatusBarManager() m_statusBarWidgets.append(w); QWidget *w2 = createWidget(m_splitter); - w2->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); m_splitter->addWidget(w2); // second w = createWidget(w2); diff --git a/src/plugins/coreplugin/systemsettings.cpp b/src/plugins/coreplugin/systemsettings.cpp index f34e7fb5b70..f6641bad08f 100644 --- a/src/plugins/coreplugin/systemsettings.cpp +++ b/src/plugins/coreplugin/systemsettings.cpp @@ -51,8 +51,7 @@ const char showCrashButtonKey[] = "ShowCrashButton"; // TODO: move to somewhere in Utils static QString formatSize(qint64 size) { - QStringList units {Tr::tr("Bytes"), Tr::tr("KB"), Tr::tr("MB"), - Tr::tr("GB"), Tr::tr("TB")}; + QStringList units{Tr::tr("Bytes"), Tr::tr("KiB"), Tr::tr("MiB"), Tr::tr("GiB"), Tr::tr("TiB")}; double outputSize = size; int i; for (i = 0; i < units.size() - 1; ++i) { @@ -170,19 +169,18 @@ public: {Tr::tr("When files are externally modified:"), Span(2, Row{m_reloadBehavior, st})}); form.addRow( {m_autoSaveCheckBox, Span(2, Row{Tr::tr("Interval:"), m_autoSaveInterval, st})}); - form.addRow(Span(3, m_autoSaveRefactoringCheckBox)); + form.addRow({Span(3, m_autoSaveRefactoringCheckBox)}); form.addRow({m_autoSuspendCheckBox, Span(2, Row{autoSuspendLabel, m_autoSuspendMinDocumentCount, st})}); - form.addRow(Span(3, Row{m_warnBeforeOpeningBigFiles, m_bigFilesLimitSpinBox, st})); - form.addRow(Span(3, + form.addRow({Span(3, Row{m_warnBeforeOpeningBigFiles, m_bigFilesLimitSpinBox, st})}); + form.addRow({Span(3, Row{Tr::tr("Maximum number of entries in \"Recent Files\":"), m_maxRecentFilesSpinBox, - st})); - form.addRow(m_askBeforeExitCheckBox); + st})}); + form.addRow({m_askBeforeExitCheckBox}); #ifdef ENABLE_CRASHPAD - form.addRow( - Span(3, Row{m_enableCrashReportingCheckBox, helpCrashReportingButton, st})); - form.addRow(Span(3, Row{m_clearCrashReportsButton, m_crashReportsSizeText, st})); + form.addRow({Span(3, Row{m_enableCrashReportingCheckBox, helpCrashReportingButton, st})}); + form.addRow({Span(3, Row{m_clearCrashReportsButton, m_crashReportsSizeText, st})}); #endif Column { @@ -438,9 +436,9 @@ void SystemSettingsWidget::resetFileBrowser() void SystemSettingsWidget::updatePath() { - EnvironmentChange change; - change.addAppendToPath(VcsManager::additionalToolsPath()); - m_patchChooser->setEnvironmentChange(change); + Environment env; + env.appendToPath(VcsManager::additionalToolsPath()); + m_patchChooser->setEnvironment(env); } void SystemSettingsWidget::updateEnvironmentChangesLabel() diff --git a/src/plugins/coreplugin/welcomepagehelper.cpp b/src/plugins/coreplugin/welcomepagehelper.cpp index 0c46ea98f30..d0b1c9f3f91 100644 --- a/src/plugins/coreplugin/welcomepagehelper.cpp +++ b/src/plugins/coreplugin/welcomepagehelper.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -25,9 +26,11 @@ #include +using namespace Utils; + namespace Core { -using namespace Utils; +using namespace WelcomePageHelpers; static QColor themeColor(Theme::Color role) { @@ -123,6 +126,17 @@ SectionGridView::SectionGridView(QWidget *parent) : GridView(parent) {} +void SectionGridView::setMaxRows(std::optional max) +{ + m_maxRows = max; + updateGeometry(); +} + +std::optional SectionGridView::maxRows() const +{ + return m_maxRows; +} + bool SectionGridView::hasHeightForWidth() const { return true; @@ -130,12 +144,39 @@ bool SectionGridView::hasHeightForWidth() const int SectionGridView::heightForWidth(int width) const { - const int columnCount = width / Core::ListItemDelegate::GridItemWidth; + const int columnCount = qMax(1, width / Core::WelcomePageHelpers::GridItemWidth); const int rowCount = (model()->rowCount() + columnCount - 1) / columnCount; - return rowCount * Core::ListItemDelegate::GridItemHeight; + const int maxRowCount = m_maxRows ? std::min(*m_maxRows, rowCount) : rowCount; + return maxRowCount * Core::WelcomePageHelpers::GridItemHeight; } -const QSize ListModel::defaultImageSize(214, 160); +void SectionGridView::wheelEvent(QWheelEvent *e) +{ + if (m_maxRows) // circumvent scrolling of the list view + QWidget::wheelEvent(e); + else + GridView::wheelEvent(e); +} + +bool SectionGridView::event(QEvent *e) +{ + if (e->type() == QEvent::Resize) { + const auto itemsFit = [this](const QSize &size) { + const int maxColumns = std::max(size.width() / WelcomePageHelpers::GridItemWidth, 1); + const int maxRows = std::max(size.height() / WelcomePageHelpers::GridItemHeight, 1); + const int maxItems = maxColumns * maxRows; + const int items = model()->rowCount(); + return maxItems >= items; + }; + auto resizeEvent = static_cast(e); + const bool itemsCurrentyFit = itemsFit(size()); + if (!resizeEvent->oldSize().isValid() + || itemsFit(resizeEvent->oldSize()) != itemsCurrentyFit) { + emit itemsFitChanged(itemsCurrentyFit); + } + } + return GridView::event(e); +} ListModel::ListModel(QObject *parent) : QAbstractListModel(parent) @@ -403,6 +444,7 @@ ListItemDelegate::ListItemDelegate() : backgroundPrimaryColor(themeColor(Theme::Welcome_BackgroundPrimaryColor)) , backgroundSecondaryColor(themeColor(Theme::Welcome_BackgroundSecondaryColor)) , foregroundPrimaryColor(themeColor(Theme::Welcome_ForegroundPrimaryColor)) + , foregroundSecondaryColor(themeColor(Theme::Welcome_ForegroundSecondaryColor)) , hoverColor(themeColor(Theme::Welcome_HoverColor)) , textColor(themeColor(Theme::Welcome_TextColor)) { @@ -415,13 +457,14 @@ void ListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti const QRect rc = option.rect; const QRect tileRect(0, 0, rc.width() - GridItemGap, rc.height() - GridItemGap); - const QSize thumbnailBgSize = ListModel::defaultImageSize.grownBy(QMargins(1, 1, 1, 1)); + const QSize thumbnailBgSize = GridItemImageSize.grownBy(QMargins(1, 1, 1, 1)); const QRect thumbnailBgRect((tileRect.width() - thumbnailBgSize.width()) / 2, GridItemGap, thumbnailBgSize.width(), thumbnailBgSize.height()); const QRect textArea = tileRect.adjusted(GridItemGap, GridItemGap, -GridItemGap, -GridItemGap); const bool hovered = option.state & QStyle::State_MouseOver; + constexpr int TagsSeparatorY = GridItemHeight - GridItemGap - 52; constexpr int tagsBase = TagsSeparatorY + 17; constexpr int shiftY = TagsSeparatorY - 16; constexpr int nameY = TagsSeparatorY - 20; @@ -524,7 +567,7 @@ void ListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti // The separator line below the example title. const int ll = nameRect.height() + 3; const QLine line = QLine(0, ll, textArea.width(), ll).translated(shiftedTextRect.topLeft()); - painter->setPen(foregroundPrimaryColor); + painter->setPen(foregroundSecondaryColor); painter->setOpacity(animationProgress); // "fade in" separator line and description painter->drawLine(line); @@ -543,7 +586,7 @@ void ListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti } // Separator line between text and 'Tags:' section - painter->setPen(foregroundPrimaryColor); + painter->setPen(foregroundSecondaryColor); painter->drawLine(QLineF(textArea.topLeft(), textArea.topRight()) .translated(0, TagsSeparatorY)); @@ -633,13 +676,9 @@ void ListItemDelegate::goon() SectionedGridView::SectionedGridView(QWidget *parent) : QStackedWidget(parent) - , m_allItemsView(new Core::GridView(this)) { - auto allItemsModel = new ListModel(this); - allItemsModel->setPixmapFunction(m_pixmapFunction); - // it just "borrows" the items from the section models: - allItemsModel->setOwnsItems(false); - m_filteredAllItemsModel = new Core::ListModelFilter(allItemsModel, this); + m_allItemsModel.reset(new ListModel); + m_allItemsModel->setPixmapFunction(m_pixmapFunction); auto area = new QScrollArea(this); area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -650,21 +689,23 @@ SectionedGridView::SectionedGridView(QWidget *parent) auto sectionedView = new QWidget; auto layout = new QVBoxLayout; layout->setContentsMargins(0, 0, 0, 0); - layout->addStretch(); + layout->addStretch(1); sectionedView->setLayout(layout); area->setWidget(sectionedView); addWidget(area); - - m_allItemsView->setModel(m_filteredAllItemsModel); - addWidget(m_allItemsView); } -SectionedGridView::~SectionedGridView() = default; +SectionedGridView::~SectionedGridView() +{ + clear(); +} void SectionedGridView::setItemDelegate(QAbstractItemDelegate *delegate) { - m_allItemsView->setItemDelegate(delegate); + m_itemDelegate = delegate; + if (m_allItemsView) + m_allItemsView->setItemDelegate(delegate); for (GridView *view : std::as_const(m_gridViews)) view->setItemDelegate(delegate); } @@ -672,38 +713,97 @@ void SectionedGridView::setItemDelegate(QAbstractItemDelegate *delegate) void SectionedGridView::setPixmapFunction(const Core::ListModel::PixmapFunction &pixmapFunction) { m_pixmapFunction = pixmapFunction; - auto allProducts = static_cast(m_filteredAllItemsModel->sourceModel()); - allProducts->setPixmapFunction(pixmapFunction); + m_allItemsModel->setPixmapFunction(pixmapFunction); for (ListModel *model : std::as_const(m_sectionModels)) model->setPixmapFunction(pixmapFunction); } void SectionedGridView::setSearchString(const QString &searchString) { - int view = searchString.isEmpty() ? 0 // sectioned view - : 1; // search view - setCurrentIndex(view); - m_filteredAllItemsModel->setSearchString(searchString); + if (searchString.isEmpty()) { + // back to previous view + m_allItemsView.reset(); + if (m_zoomedInWidget) + setCurrentWidget(m_zoomedInWidget); + else + setCurrentIndex(0); + return; + } + if (!m_allItemsView) { + // We don't have a grid set for searching yet. + // Create all items view for filtering. + m_allItemsView.reset(new GridView); + m_allItemsView->setModel(new ListModelFilter(m_allItemsModel.get(), m_allItemsView.get())); + if (m_itemDelegate) + m_allItemsView->setItemDelegate(m_itemDelegate); + addWidget(m_allItemsView.get()); + } + setCurrentWidget(m_allItemsView.get()); + auto filterModel = static_cast(m_allItemsView.get()->model()); + filterModel->setSearchString(searchString); +} + +static QWidget *createSeparator(QWidget *parent) +{ + QWidget *line = Layouting::createHr(parent); + QSizePolicy linePolicy(QSizePolicy::Expanding, QSizePolicy::Ignored); + linePolicy.setHorizontalStretch(2); + line->setSizePolicy(linePolicy); + QPalette pal = line->palette(); + pal.setColor(QPalette::Dark, Qt::transparent); + pal.setColor(QPalette::Light, themeColor(Theme::Welcome_ForegroundSecondaryColor)); + line->setPalette(pal); + return line; +} + +static QLabel *createLinkLabel(const QString &text, QWidget *parent) +{ + const QString linkColor = themeColor(Theme::Welcome_LinkColor).name(); + auto link = new QLabel("" + + text + "", parent); + return link; } ListModel *SectionedGridView::addSection(const Section §ion, const QList &items) { auto model = new ListModel(this); model->setPixmapFunction(m_pixmapFunction); + // the sections only keep a weak reference to the items, + // they are owned by the allProducts model, since multiple sections can contain duplicates + // of the same item + model->setOwnsItems(false); model->appendItems(items); auto gridView = new SectionGridView(this); - gridView->setItemDelegate(m_allItemsView->itemDelegate()); + gridView->setItemDelegate(m_itemDelegate); gridView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); gridView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); gridView->setModel(model); + gridView->setMaxRows(section.maxRows); m_sectionModels.insert(section, model); const auto it = m_gridViews.insert(section, gridView); - auto sectionLabel = new QLabel(section.name); + QLabel *seeAllLink = createLinkLabel(Tr::tr("Show All") + " >", this); + if (gridView->maxRows().has_value()) { + seeAllLink->setVisible(true); + connect(gridView, &SectionGridView::itemsFitChanged, seeAllLink, [seeAllLink](bool fits) { + seeAllLink->setVisible(!fits); + }); + } else { + seeAllLink->setVisible(false); + } + connect(seeAllLink, &QLabel::linkActivated, this, [this, section] { zoomInSection(section); }); + using namespace Layouting; + QWidget *sectionLabel = Row { + section.name, + createSeparator(this), + seeAllLink, + Space(HSpacing), + noMargin + }.emerge(); m_sectionLabels.append(sectionLabel); - sectionLabel->setContentsMargins(0, Core::WelcomePageHelpers::ItemGap, 0, 0); + sectionLabel->setContentsMargins(0, ItemGap, 0, 0); sectionLabel->setFont(Core::WelcomePageHelpers::brandFont()); auto scrollArea = qobject_cast(widget(0)); auto vbox = qobject_cast(scrollArea->widget()->layout()); @@ -715,8 +815,11 @@ ListModel *SectionedGridView::addSection(const Section §ion, const QListinsertWidget(position + 1, gridView); // add the items also to the all products model to be able to search correctly - auto allProducts = static_cast(m_filteredAllItemsModel->sourceModel()); - allProducts->appendItems(items); + const QSet allItems = toSet(m_allItemsModel->items()); + const QList newItems = filtered(items, [&allItems](ListItem *item) { + return !allItems.contains(item); + }); + m_allItemsModel->appendItems(newItems); // only show section label(s) if there is more than one section m_sectionLabels.at(0)->setVisible(m_sectionLabels.size() > 1); @@ -726,14 +829,62 @@ ListModel *SectionedGridView::addSection(const Section §ion, const QList(m_filteredAllItemsModel->sourceModel()); - allProducts->clear(); + m_allItemsModel->clear(); qDeleteAll(m_sectionModels); qDeleteAll(m_sectionLabels); qDeleteAll(m_gridViews); m_sectionModels.clear(); m_sectionLabels.clear(); m_gridViews.clear(); + m_allItemsView.reset(); } +void SectionedGridView::zoomInSection(const Section §ion) +{ + auto zoomedInWidget = new QWidget(this); + auto layout = new QVBoxLayout; + layout->setContentsMargins(0, 0, 0, 0); + zoomedInWidget->setLayout(layout); + + QLabel *backLink = createLinkLabel("< " + Tr::tr("Back"), this); + connect(backLink, &QLabel::linkActivated, this, [this, zoomedInWidget] { + removeWidget(zoomedInWidget); + delete zoomedInWidget; + setCurrentIndex(0); + }); + using namespace Layouting; + QWidget *sectionLabel = Row { + section.name, + createSeparator(this), + backLink, + Space(HSpacing), + noMargin + }.emerge(); + sectionLabel->setContentsMargins(0, ItemGap, 0, 0); + sectionLabel->setFont(Core::WelcomePageHelpers::brandFont()); + + auto gridView = new GridView(zoomedInWidget); + gridView->setItemDelegate(m_itemDelegate); + gridView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + gridView->setModel(m_sectionModels.value(section)); + + layout->addWidget(sectionLabel); + layout->addWidget(gridView); + + m_zoomedInWidget = zoomedInWidget; + addWidget(zoomedInWidget); + setCurrentWidget(zoomedInWidget); +} + +Section::Section(const QString &name, int priority) + : name(name) + , priority(priority) +{} + +Section::Section(const QString &name, int priority, std::optional maxRows) + : name(name) + , priority(priority) + , maxRows(maxRows) +{} + } // namespace Core diff --git a/src/plugins/coreplugin/welcomepagehelper.h b/src/plugins/coreplugin/welcomepagehelper.h index 77760000e9e..8599c227b90 100644 --- a/src/plugins/coreplugin/welcomepagehelper.h +++ b/src/plugins/coreplugin/welcomepagehelper.h @@ -24,6 +24,16 @@ namespace WelcomePageHelpers { constexpr int HSpacing = 20; constexpr int ItemGap = 4; + +constexpr int GridItemGap = 3 * ItemGap; +constexpr int GridItemWidth = 240 + GridItemGap; // Extra GridItemGap as "spacing" +constexpr int GridItemHeight = GridItemWidth; +constexpr QSize GridItemImageSize(GridItemWidth - GridItemGap + - 2 * (GridItemGap + 1), // Horizontal margins + 1 pixel + GridItemHeight - GridItemGap + - GridItemGap - 1 // Upper margin + 1 pixel + - 67); // Bottom margin (for title + tags) + CORE_EXPORT QFont brandFont(); CORE_EXPORT QWidget *panelBar(QWidget *parent = nullptr); @@ -40,7 +50,7 @@ public: class CORE_EXPORT GridView : public QListView { public: - explicit GridView(QWidget *parent); + explicit GridView(QWidget *parent = nullptr); protected: void leaveEvent(QEvent *) final; @@ -48,11 +58,25 @@ protected: class CORE_EXPORT SectionGridView : public GridView { + Q_OBJECT + public: explicit SectionGridView(QWidget *parent); - bool hasHeightForWidth() const; - int heightForWidth(int width) const; + void setMaxRows(std::optional max); + std::optional maxRows() const; + + bool hasHeightForWidth() const override; + int heightForWidth(int width) const override; + + void wheelEvent(QWheelEvent *e) override; + bool event(QEvent *e) override; + +signals: + void itemsFitChanged(bool fit); + +private: + std::optional m_maxRows; }; using OptModelIndex = std::optional; @@ -74,7 +98,7 @@ public: using PixmapFunction = std::function; - explicit ListModel(QObject *parent); + explicit ListModel(QObject *parent = nullptr); ~ListModel() override; void appendItems(const QList &items); @@ -85,8 +109,6 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; void setPixmapFunction(const PixmapFunction &fetchPixmapAndUpdatePixmapCache); - static const QSize defaultImageSize; - void setOwnsItems(bool owns); private: @@ -128,11 +150,6 @@ public: void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; - static constexpr int GridItemGap = 3 * WelcomePageHelpers::ItemGap; - static constexpr int GridItemWidth = 240 + GridItemGap; - static constexpr int GridItemHeight = GridItemWidth; - static constexpr int TagsSeparatorY = GridItemHeight - GridItemGap - 52; - signals: void tagClicked(const QString &tag); @@ -151,6 +168,7 @@ protected: const QColor backgroundPrimaryColor; const QColor backgroundSecondaryColor; const QColor foregroundPrimaryColor; + const QColor foregroundSecondaryColor; const QColor hoverColor; const QColor textColor; @@ -165,6 +183,9 @@ private: class CORE_EXPORT Section { public: + Section(const QString &name, int priority); + Section(const QString &name, int priority, std::optional maxRows); + friend bool operator<(const Section &lhs, const Section &rhs) { if (lhs.priority < rhs.priority) @@ -179,6 +200,7 @@ public: QString name; int priority; + std::optional maxRows; }; class CORE_EXPORT SectionedGridView : public QStackedWidget @@ -196,12 +218,16 @@ public: void clear(); private: + void zoomInSection(const Section §ion); + QMap m_sectionModels; QList m_sectionLabels; QMap m_gridViews; - Core::GridView *m_allItemsView = nullptr; - Core::ListModelFilter *m_filteredAllItemsModel = nullptr; + std::unique_ptr m_allItemsModel; + std::unique_ptr m_allItemsView; + QPointer m_zoomedInWidget; Core::ListModel::PixmapFunction m_pixmapFunction; + QAbstractItemDelegate *m_itemDelegate = nullptr; }; } // namespace Core diff --git a/src/plugins/cpaster/cpasterplugin.cpp b/src/plugins/cpaster/cpasterplugin.cpp index 65b3d251798..a18b454c860 100644 --- a/src/plugins/cpaster/cpasterplugin.cpp +++ b/src/plugins/cpaster/cpasterplugin.cpp @@ -75,8 +75,6 @@ public: &dpasteProto }; - SettingsPage m_settingsPage{&m_settings}; - QStringList m_fetchedSnippets; UrlOpenProtocol m_urlOpen; @@ -247,8 +245,8 @@ void CodePasterPluginPrivate::post(QString data, const QString &mimeType) const FileDataList diffChunks = splitDiffToFiles(data); const int dialogResult = diffChunks.isEmpty() ? - view.show(username, {}, {}, m_settings.expiryDays.value(), data) : - view.show(username, {}, {}, m_settings.expiryDays.value(), diffChunks); + view.show(username, {}, {}, m_settings.expiryDays(), data) : + view.show(username, {}, {}, m_settings.expiryDays(), diffChunks); // Save new protocol in case user changed it. if (dialogResult == QDialog::Accepted && m_settings.protocols.value() != view.protocol()) { diff --git a/src/plugins/cpaster/fileshareprotocol.cpp b/src/plugins/cpaster/fileshareprotocol.cpp index 2ca4bfa6771..e9b85ee1af1 100644 --- a/src/plugins/cpaster/fileshareprotocol.cpp +++ b/src/plugins/cpaster/fileshareprotocol.cpp @@ -6,7 +6,6 @@ #include "cpastertr.h" #include "fileshareprotocolsettingspage.h" -#include #include #include @@ -29,20 +28,13 @@ const char textElementC[] = "text"; namespace CodePaster { -FileShareProtocol::FileShareProtocol() : - m_settingsPage(new FileShareProtocolSettingsPage(&m_settings)) -{ - m_settings.readSettings(Core::ICore::settings()); -} +FileShareProtocol::FileShareProtocol() = default; -FileShareProtocol::~FileShareProtocol() -{ - delete m_settingsPage; -} +FileShareProtocol::~FileShareProtocol() = default; QString FileShareProtocol::name() const { - return m_settingsPage->displayName(); + return m_settings.displayName(); } unsigned FileShareProtocol::capabilities() const @@ -55,9 +47,9 @@ bool FileShareProtocol::hasSettings() const return true; } -Core::IOptionsPage *FileShareProtocol::settingsPage() const +const Core::IOptionsPage *FileShareProtocol::settingsPage() const { - return m_settingsPage; + return &m_settings; } static bool parse(const QString &fileName, @@ -141,7 +133,7 @@ void FileShareProtocol::list() QString errorMessage; const QChar blank = QLatin1Char(' '); const QFileInfoList entryInfoList = dir.entryInfoList(); - const int count = qMin(int(m_settings.displayCount.value()), entryInfoList.size()); + const int count = qMin(int(m_settings.displayCount()), entryInfoList.size()); for (int i = 0; i < count; i++) { const QFileInfo& entryFi = entryInfoList.at(i); if (parse(entryFi.absoluteFilePath(), &errorMessage, &user, &description)) { diff --git a/src/plugins/cpaster/fileshareprotocol.h b/src/plugins/cpaster/fileshareprotocol.h index 100c0aece03..db03bb11bf6 100644 --- a/src/plugins/cpaster/fileshareprotocol.h +++ b/src/plugins/cpaster/fileshareprotocol.h @@ -8,8 +8,6 @@ namespace CodePaster { -class FileShareProtocolSettingsPage; - /* FileShareProtocol: Allows for pasting via a shared network * drive by writing XML files. */ @@ -22,7 +20,7 @@ public: QString name() const override; unsigned capabilities() const override; bool hasSettings() const override; - Core::IOptionsPage *settingsPage() const override; + const Core::IOptionsPage *settingsPage() const override; bool checkConfiguration(QString *errorMessage = nullptr) override; void fetch(const QString &id) override; @@ -35,7 +33,6 @@ public: private: FileShareProtocolSettings m_settings; - FileShareProtocolSettingsPage *m_settingsPage; }; } // CodePaster diff --git a/src/plugins/cpaster/fileshareprotocolsettingspage.cpp b/src/plugins/cpaster/fileshareprotocolsettingspage.cpp index 775c0438ab9..99599a3cd80 100644 --- a/src/plugins/cpaster/fileshareprotocolsettingspage.cpp +++ b/src/plugins/cpaster/fileshareprotocolsettingspage.cpp @@ -15,33 +15,22 @@ namespace CodePaster { FileShareProtocolSettings::FileShareProtocolSettings() { + setId("X.CodePaster.FileSharePaster"); + setDisplayName(Tr::tr("Fileshare")); + setCategory(Constants::CPASTER_SETTINGS_CATEGORY); setSettingsGroup("FileSharePasterSettings"); - setAutoApply(false); - registerAspect(&path); path.setSettingsKey("Path"); - path.setDisplayStyle(StringAspect::PathChooserDisplay); path.setExpectedKind(PathChooser::ExistingDirectory); path.setDefaultValue(TemporaryDirectory::masterDirectoryPath()); path.setLabelText(Tr::tr("&Path:")); - registerAspect(&displayCount); displayCount.setSettingsKey("DisplayCount"); displayCount.setDefaultValue(10); displayCount.setSuffix(' ' + Tr::tr("entries")); displayCount.setLabelText(Tr::tr("&Display:")); -} -// Settings page - -FileShareProtocolSettingsPage::FileShareProtocolSettingsPage(FileShareProtocolSettings *settings) -{ - setId("X.CodePaster.FileSharePaster"); - setDisplayName(Tr::tr("Fileshare")); - setCategory(Constants::CPASTER_SETTINGS_CATEGORY); - setSettings(settings); - - setLayouter([&s = *settings](QWidget *widget) { + setLayouter([this] { using namespace Layouting; auto label = new QLabel(Tr::tr( @@ -49,15 +38,17 @@ FileShareProtocolSettingsPage::FileShareProtocolSettingsPage(FileShareProtocolSe "simple files on a shared network drive. Files are never deleted.")); label->setWordWrap(true); - Column { + return Column { Form { label, br, - s.path, - s.displayCount + path, br, + displayCount }, st - }.attachTo(widget); + }; }); + + readSettings(); } } // namespace CodePaster diff --git a/src/plugins/cpaster/fileshareprotocolsettingspage.h b/src/plugins/cpaster/fileshareprotocolsettingspage.h index 3b92c3596d4..8775fe16096 100644 --- a/src/plugins/cpaster/fileshareprotocolsettingspage.h +++ b/src/plugins/cpaster/fileshareprotocolsettingspage.h @@ -5,23 +5,15 @@ #include -#include - namespace CodePaster { -class FileShareProtocolSettings : public Utils::AspectContainer +class FileShareProtocolSettings : public Core::PagedSettings { public: FileShareProtocolSettings(); - Utils::StringAspect path; - Utils::IntegerAspect displayCount; -}; - -class FileShareProtocolSettingsPage final : public Core::IOptionsPage -{ -public: - explicit FileShareProtocolSettingsPage(FileShareProtocolSettings *settings); + Utils::FilePathAspect path{this}; + Utils::IntegerAspect displayCount{this}; }; } // CodePaster diff --git a/src/plugins/cpaster/pasteselectdialog.cpp b/src/plugins/cpaster/pasteselectdialog.cpp index 5b4c164c3b1..2df87e71370 100644 --- a/src/plugins/cpaster/pasteselectdialog.cpp +++ b/src/plugins/cpaster/pasteselectdialog.cpp @@ -54,7 +54,7 @@ PasteSelectDialog::PasteSelectDialog(const QList &protocols, QWidget listFont.setStyleHint(QFont::TypeWriter); m_listWidget->setFont(listFont); - using namespace Utils::Layouting; + using namespace Layouting; Column { Form { Tr::tr("Protocol:"), m_protocolBox, br, diff --git a/src/plugins/cpaster/pasteview.cpp b/src/plugins/cpaster/pasteview.cpp index 02ae0f65305..b1f4864b6e0 100644 --- a/src/plugins/cpaster/pasteview.cpp +++ b/src/plugins/cpaster/pasteview.cpp @@ -100,7 +100,7 @@ PasteView::PasteView(const QList &protocols, m_uiPatchList->setSortingEnabled(false); m_uiPatchList->setSortingEnabled(__sortingEnabled); - using namespace Utils::Layouting; + using namespace Layouting; Column { m_uiPatchList, @@ -108,6 +108,7 @@ PasteView::PasteView(const QList &protocols, }.attachTo(groupBox); Column { + spacing(2), Form { Tr::tr("Protocol:"), m_protocolBox, br, Tr::tr("&Expires after:"), m_expirySpinBox, br, @@ -117,7 +118,7 @@ PasteView::PasteView(const QList &protocols, m_uiComment, m_stackedWidget, buttonBox - }.setSpacing(2).attachTo(this); + }.attachTo(this); connect(m_uiPatchList, &QListWidget::itemChanged, this, &PasteView::contentChanged); diff --git a/src/plugins/cpaster/protocol.cpp b/src/plugins/cpaster/protocol.cpp index d26396205a8..c2630d09c13 100644 --- a/src/plugins/cpaster/protocol.cpp +++ b/src/plugins/cpaster/protocol.cpp @@ -47,7 +47,7 @@ bool Protocol::checkConfiguration(QString *) return true; } -Core::IOptionsPage *Protocol::settingsPage() const +const Core::IOptionsPage *Protocol::settingsPage() const { return nullptr; } diff --git a/src/plugins/cpaster/protocol.h b/src/plugins/cpaster/protocol.h index a8d233619b4..90464768c2a 100644 --- a/src/plugins/cpaster/protocol.h +++ b/src/plugins/cpaster/protocol.h @@ -38,7 +38,7 @@ public: virtual unsigned capabilities() const = 0; virtual bool hasSettings() const; - virtual Core::IOptionsPage *settingsPage() const; + virtual const Core::IOptionsPage *settingsPage() const; virtual bool checkConfiguration(QString *errorMessage = nullptr); virtual void fetch(const QString &id) = 0; diff --git a/src/plugins/cpaster/settings.cpp b/src/plugins/cpaster/settings.cpp index 7459ae1098a..1c3d9e5e4db 100644 --- a/src/plugins/cpaster/settings.cpp +++ b/src/plugins/cpaster/settings.cpp @@ -16,13 +16,16 @@ Settings::Settings() { setSettingsGroup("CodePaster"); setAutoApply(false); + setId("A.CodePaster.General"); + setDisplayName(Tr::tr("General")); + setCategory(Constants::CPASTER_SETTINGS_CATEGORY); + setDisplayCategory(Tr::tr("Code Pasting")); + setCategoryIconPath(":/cpaster/images/settingscategory_cpaster.png"); - registerAspect(&username); username.setDisplayStyle(StringAspect::LineEditDisplay); username.setSettingsKey("UserName"); username.setLabelText(Tr::tr("Username:")); - registerAspect(&protocols); protocols.setSettingsKey("DefaultProtocol"); protocols.setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox); protocols.setLabelText(Tr::tr("Default protocol:")); @@ -33,48 +36,31 @@ Settings::Settings() return protocols.indexForDisplay(val.toString()); }); - registerAspect(&expiryDays); expiryDays.setSettingsKey("ExpiryDays"); expiryDays.setDefaultValue(1); expiryDays.setSuffix(Tr::tr(" Days")); expiryDays.setLabelText(Tr::tr("&Expires after:")); - registerAspect(©ToClipboard); copyToClipboard.setSettingsKey("CopyToClipboard"); copyToClipboard.setDefaultValue(true); copyToClipboard.setLabelText(Tr::tr("Copy-paste URL to clipboard")); - registerAspect(&displayOutput); displayOutput.setSettingsKey("DisplayOutput"); displayOutput.setDefaultValue(true); displayOutput.setLabelText(Tr::tr("Display General Messages after sending a post")); -} -// SettingsPage - -SettingsPage::SettingsPage(Settings *settings) -{ - setId("A.CodePaster.General"); - setDisplayName(Tr::tr("General")); - setCategory(Constants::CPASTER_SETTINGS_CATEGORY); - setDisplayCategory(Tr::tr("Code Pasting")); - setCategoryIconPath(":/cpaster/images/settingscategory_cpaster.png"); - setSettings(settings); - - setLayouter([settings](QWidget *widget) { - Settings &s = *settings; + setLayouter([this] { using namespace Layouting; - - Column { + return Column { Form { - s.protocols, - s.username, - s.expiryDays + protocols, br, + username, br, + expiryDays }, - s.copyToClipboard, - s.displayOutput, + copyToClipboard, + displayOutput, st - }.attachTo(widget); + }; }); } diff --git a/src/plugins/cpaster/settings.h b/src/plugins/cpaster/settings.h index d54cc393bcf..1e7d03be761 100644 --- a/src/plugins/cpaster/settings.h +++ b/src/plugins/cpaster/settings.h @@ -5,26 +5,18 @@ #include -#include - namespace CodePaster { -class Settings : public Utils::AspectContainer +class Settings : public Core::PagedSettings { public: Settings(); - Utils::StringAspect username; - Utils::SelectionAspect protocols; - Utils::IntegerAspect expiryDays; - Utils::BoolAspect copyToClipboard; - Utils::BoolAspect displayOutput; -}; - -class SettingsPage final : public Core::IOptionsPage -{ -public: - SettingsPage(Settings *settings); + Utils::StringAspect username{this}; + Utils::SelectionAspect protocols{this}; + Utils::IntegerAspect expiryDays{this}; + Utils::BoolAspect copyToClipboard{this}; + Utils::BoolAspect displayOutput{this}; }; } // CodePaster diff --git a/src/plugins/cpaster/stickynotespasteprotocol.h b/src/plugins/cpaster/stickynotespasteprotocol.h index e17508e084f..f212c883ef2 100644 --- a/src/plugins/cpaster/stickynotespasteprotocol.h +++ b/src/plugins/cpaster/stickynotespasteprotocol.h @@ -38,7 +38,6 @@ private: QNetworkReply *m_listReply = nullptr; QString m_fetchId; - int m_postId = -1; bool m_hostChecked = false; }; diff --git a/src/plugins/cppcheck/cppcheckconstants.h b/src/plugins/cppcheck/cppcheckconstants.h index 6efbb2f6158..427bdfc10fb 100644 --- a/src/plugins/cppcheck/cppcheckconstants.h +++ b/src/plugins/cppcheck/cppcheckconstants.h @@ -9,23 +9,6 @@ const char TEXTMARK_CATEGORY_ID[] = "Cppcheck"; const char OPTIONS_PAGE_ID[] = "Analyzer.Cppcheck.Settings"; -const char SETTINGS_ID[] = "Cppcheck"; -const char SETTINGS_BINARY[] = "binary"; -const char SETTINGS_WARNING[] = "warning"; -const char SETTINGS_STYLE[] = "style"; -const char SETTINGS_PERFORMANCE[] = "performance"; -const char SETTINGS_PORTABILITY[] = "portability"; -const char SETTINGS_INFORMATION[] = "information"; -const char SETTINGS_UNUSED_FUNCTION[] = "unusedFunction"; -const char SETTINGS_MISSING_INCLUDE[] = "missingInclude"; -const char SETTINGS_INCONCLUSIVE[] = "inconclusive"; -const char SETTINGS_FORCE_DEFINES[] = "forceDefines"; -const char SETTINGS_CUSTOM_ARGUMENTS[] = "customArguments"; -const char SETTINGS_IGNORE_PATTERNS[] = "ignorePatterns"; -const char SETTINGS_SHOW_OUTPUT[] = "showOutput"; -const char SETTINGS_ADD_INCLUDE_PATHS[] = "addIncludePaths"; -const char SETTINGS_GUESS_ARGUMENTS[] = "guessArguments"; - const char CHECK_PROGRESS_ID[] = "Cppcheck.CheckingTask"; const char MANUAL_CHECK_PROGRESS_ID[] = "Cppcheck.ManualCheckingTask"; diff --git a/src/plugins/cppcheck/cppcheckmanualrundialog.cpp b/src/plugins/cppcheck/cppcheckmanualrundialog.cpp index 1f53e6835bd..060f888cc25 100644 --- a/src/plugins/cppcheck/cppcheckmanualrundialog.cpp +++ b/src/plugins/cppcheck/cppcheckmanualrundialog.cpp @@ -18,11 +18,9 @@ namespace Cppcheck::Internal { -ManualRunDialog::ManualRunDialog(const CppcheckOptions &options, +ManualRunDialog::ManualRunDialog(QWidget *optionsWidget, const ProjectExplorer::Project *project) - : QDialog(), - m_options(new OptionsWidget(this)), - m_model(new ProjectExplorer::SelectableFilesFromDirModel(this)) + : m_model(new ProjectExplorer::SelectableFilesFromDirModel(this)) { QTC_ASSERT(project, return ); @@ -55,21 +53,12 @@ ManualRunDialog::ManualRunDialog(const CppcheckOptions &options, }); auto layout = new QVBoxLayout(this); - layout->addWidget(m_options); + layout->addWidget(optionsWidget); layout->addWidget(view); layout->addWidget(buttons); - if (auto layout = m_options->layout()) + if (auto layout = optionsWidget->layout()) layout->setContentsMargins(0, 0, 0, 0); - - m_options->load(options); -} - -CppcheckOptions ManualRunDialog::options() const -{ - CppcheckOptions result; - m_options->save(result); - return result; } Utils::FilePaths ManualRunDialog::filePaths() const diff --git a/src/plugins/cppcheck/cppcheckmanualrundialog.h b/src/plugins/cppcheck/cppcheckmanualrundialog.h index 8ffadcc48a2..460a85e75c4 100644 --- a/src/plugins/cppcheck/cppcheckmanualrundialog.h +++ b/src/plugins/cppcheck/cppcheckmanualrundialog.h @@ -17,21 +17,15 @@ class SelectableFilesFromDirModel; namespace Cppcheck::Internal { -class OptionsWidget; -class CppcheckOptions; - class ManualRunDialog : public QDialog { public: - ManualRunDialog(const CppcheckOptions &options, - const ProjectExplorer::Project *project); + ManualRunDialog(QWidget *optionsWidget, const ProjectExplorer::Project *project); - CppcheckOptions options() const; Utils::FilePaths filePaths() const; QSize sizeHint() const override; private: - OptionsWidget *m_options; ProjectExplorer::SelectableFilesFromDirModel *m_model; }; diff --git a/src/plugins/cppcheck/cppcheckoptions.cpp b/src/plugins/cppcheck/cppcheckoptions.cpp index 11563430cc1..1289ba0e0e2 100644 --- a/src/plugins/cppcheck/cppcheckoptions.cpp +++ b/src/plugins/cppcheck/cppcheckoptions.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -20,211 +21,119 @@ #include #include -#include -#include - using namespace Utils; namespace Cppcheck::Internal { -OptionsWidget::OptionsWidget(QWidget *parent) - : QWidget(parent), - m_binary(new Utils::PathChooser(this)), - m_customArguments(new QLineEdit(this)), - m_ignorePatterns(new QLineEdit(this)), - m_warning(new QCheckBox(Tr::tr("Warnings"), this)), - m_style(new QCheckBox(Tr::tr("Style"), this)), - m_performance(new QCheckBox(Tr::tr("Performance"), this)), - m_portability(new QCheckBox(Tr::tr("Portability"), this)), - m_information(new QCheckBox(Tr::tr("Information"), this)), - m_unusedFunction(new QCheckBox(Tr::tr("Unused functions"), this)), - m_missingInclude(new QCheckBox(Tr::tr("Missing includes"), this)), - m_inconclusive(new QCheckBox(Tr::tr("Inconclusive errors"), this)), - m_forceDefines(new QCheckBox(Tr::tr("Check all define combinations"), this)), - m_showOutput(new QCheckBox(Tr::tr("Show raw output"), this)), - m_addIncludePaths(new QCheckBox(Tr::tr("Add include paths"), this)), - m_guessArguments(new QCheckBox(Tr::tr("Calculate additional arguments"), this)) -{ - m_binary->setExpectedKind(Utils::PathChooser::ExistingCommand); - m_binary->setCommandVersionArguments({"--version"}); - - auto variableChooser = new Utils::VariableChooser(this); - variableChooser->addSupportedWidget (m_customArguments); - - m_unusedFunction->setToolTip(Tr::tr("Disables multithreaded check.")); - m_ignorePatterns->setToolTip(Tr::tr("Comma-separated wildcards of full file paths. " - "Files still can be checked if others include them.")); - m_addIncludePaths->setToolTip(Tr::tr("Can find missing includes but makes " - "checking slower. Use only when needed.")); - m_guessArguments->setToolTip(Tr::tr("Like C++ standard and language.")); - - auto layout = new QFormLayout(this); - layout->addRow(Tr::tr("Binary:"), m_binary); - - auto checks = new Utils::FlowLayout; - layout->addRow(Tr::tr("Checks:"), checks); - checks->addWidget(m_warning); - checks->addWidget(m_style); - checks->addWidget(m_performance); - checks->addWidget(m_portability); - checks->addWidget(m_information); - checks->addWidget(m_unusedFunction); - checks->addWidget(m_missingInclude); - - layout->addRow(Tr::tr("Custom arguments:"), m_customArguments); - layout->addRow(Tr::tr("Ignored file patterns:"), m_ignorePatterns); - auto flags = new Utils::FlowLayout; - layout->addRow(flags); - flags->addWidget(m_inconclusive); - flags->addWidget(m_forceDefines); - flags->addWidget(m_showOutput); - flags->addWidget(m_addIncludePaths); - flags->addWidget(m_guessArguments); -} - -void OptionsWidget::load(const CppcheckOptions &options) -{ - m_binary->setFilePath(options.binary); - m_customArguments->setText(options.customArguments); - m_ignorePatterns->setText(options.ignoredPatterns); - m_warning->setChecked(options.warning); - m_style->setChecked(options.style); - m_performance->setChecked(options.performance); - m_portability->setChecked(options.portability); - m_information->setChecked(options.information); - m_unusedFunction->setChecked(options.unusedFunction); - m_missingInclude->setChecked(options.missingInclude); - m_inconclusive->setChecked(options.inconclusive); - m_forceDefines->setChecked(options.forceDefines); - m_showOutput->setChecked(options.showOutput); - m_addIncludePaths->setChecked(options.addIncludePaths); - m_guessArguments->setChecked(options.guessArguments); -} - -void OptionsWidget::save(CppcheckOptions &options) const -{ - options.binary = m_binary->filePath(); - options.customArguments = m_customArguments->text(); - options.ignoredPatterns = m_ignorePatterns->text(); - options.warning = m_warning->isChecked(); - options.style = m_style->isChecked(); - options.performance = m_performance->isChecked(); - options.portability = m_portability->isChecked(); - options.information = m_information->isChecked(); - options.unusedFunction = m_unusedFunction->isChecked(); - options.missingInclude = m_missingInclude->isChecked(); - options.inconclusive = m_inconclusive->isChecked(); - options.forceDefines = m_forceDefines->isChecked(); - options.showOutput = m_showOutput->isChecked(); - options.addIncludePaths = m_addIncludePaths->isChecked(); - options.guessArguments = m_guessArguments->isChecked(); -} - -CppcheckOptionsPage::CppcheckOptionsPage(CppcheckTool &tool, CppcheckTrigger &trigger): - m_tool(tool), - m_trigger(trigger) +CppcheckOptions::CppcheckOptions() { setId(Constants::OPTIONS_PAGE_ID); setDisplayName(Tr::tr("Cppcheck")); setCategory("T.Analyzer"); setDisplayCategory(::Debugger::Tr::tr("Analyzer")); setCategoryIconPath(Analyzer::Icons::SETTINGSCATEGORY_ANALYZER); + setSettingsGroup("Cppcheck"); - CppcheckOptions options; + binary.setSettingsKey("binary"); + binary.setExpectedKind(PathChooser::ExistingCommand); + binary.setCommandVersionArguments({"--version"}); + binary.setLabelText(Tr::tr("Binary:")); if (HostOsInfo::isAnyUnixHost()) { - options.binary = "cppcheck"; + binary.setDefaultValue("cppcheck"); } else { FilePath programFiles = FilePath::fromUserInput(qtcEnvironmentVariable("PROGRAMFILES")); if (programFiles.isEmpty()) programFiles = "C:/Program Files"; - options.binary = programFiles / "Cppcheck/cppcheck.exe"; + binary.setDefaultValue(programFiles.pathAppended("Cppcheck/cppcheck.exe").toString()); } - load(options); + warning.setSettingsKey("warning"); + warning.setDefaultValue(true); + warning.setLabelText(Tr::tr("Warnings")); - m_tool.updateOptions(options); + style.setSettingsKey("style"); + style.setDefaultValue(true); + style.setLabelText(Tr::tr("Style")); + + performance.setSettingsKey("performance"); + performance.setDefaultValue(true); + performance.setLabelText(Tr::tr("Performance")); + + portability.setSettingsKey("portability"); + portability.setDefaultValue(true); + portability.setLabelText(Tr::tr("Portability")); + + information.setSettingsKey("information"); + information.setDefaultValue(true); + information.setLabelText(Tr::tr("Information")); + + unusedFunction.setSettingsKey("unusedFunction"); + unusedFunction.setLabelText(Tr::tr("Unused functions")); + unusedFunction.setToolTip(Tr::tr("Disables multithreaded check.")); + + missingInclude.setSettingsKey("missingInclude"); + missingInclude.setLabelText(Tr::tr("Missing includes")); + + inconclusive.setSettingsKey("inconclusive"); + inconclusive.setLabelText(Tr::tr("Inconclusive errors")); + + forceDefines.setSettingsKey("forceDefines"); + forceDefines.setLabelText(Tr::tr("Check all define combinations")); + + customArguments.setSettingsKey("customArguments"); + customArguments.setDisplayStyle(StringAspect::LineEditDisplay); + customArguments.setLabelText(Tr::tr("Custom arguments:")); + + ignoredPatterns.setSettingsKey("ignoredPatterns"); + ignoredPatterns.setDisplayStyle(StringAspect::LineEditDisplay); + ignoredPatterns.setLabelText(Tr::tr("Ignored file patterns:")); + ignoredPatterns.setToolTip(Tr::tr("Comma-separated wildcards of full file paths. " + "Files still can be checked if others include them.")); + + showOutput.setSettingsKey("showOutput"); + showOutput.setLabelText(Tr::tr("Show raw output")); + + addIncludePaths.setSettingsKey("addIncludePaths"); + addIncludePaths.setLabelText(Tr::tr("Add include paths")); + addIncludePaths.setToolTip(Tr::tr("Can find missing includes but makes " + "checking slower. Use only when needed.")); + + guessArguments.setSettingsKey("guessArguments"); + guessArguments.setDefaultValue(true); + guessArguments.setLabelText(Tr::tr("Calculate additional arguments")); + guessArguments.setToolTip(Tr::tr("Like C++ standard and language.")); + + setLayouter(layouter()); + + readSettings(); } -QWidget *CppcheckOptionsPage::widget() +std::function CppcheckOptions::layouter() { - if (!m_widget) - m_widget = new OptionsWidget; - m_widget->load(m_tool.options()); - return m_widget.data(); -} - -void CppcheckOptionsPage::apply() -{ - CppcheckOptions options; - m_widget->save(options); - save(options); - m_tool.updateOptions(options); - m_trigger.recheck(); -} - -void CppcheckOptionsPage::finish() -{ -} - -void CppcheckOptionsPage::save(const CppcheckOptions &options) const -{ - QSettings *s = Core::ICore::settings(); - QTC_ASSERT(s, return); - s->beginGroup(Constants::SETTINGS_ID); - s->setValue(Constants::SETTINGS_BINARY, options.binary.toString()); - s->setValue(Constants::SETTINGS_CUSTOM_ARGUMENTS, options.customArguments); - s->setValue(Constants::SETTINGS_IGNORE_PATTERNS, options.ignoredPatterns); - s->setValue(Constants::SETTINGS_WARNING, options.warning); - s->setValue(Constants::SETTINGS_STYLE, options.style); - s->setValue(Constants::SETTINGS_PERFORMANCE, options.performance); - s->setValue(Constants::SETTINGS_PORTABILITY, options.portability); - s->setValue(Constants::SETTINGS_INFORMATION, options.information); - s->setValue(Constants::SETTINGS_UNUSED_FUNCTION, options.unusedFunction); - s->setValue(Constants::SETTINGS_MISSING_INCLUDE, options.missingInclude); - s->setValue(Constants::SETTINGS_INCONCLUSIVE, options.inconclusive); - s->setValue(Constants::SETTINGS_FORCE_DEFINES, options.forceDefines); - s->setValue(Constants::SETTINGS_SHOW_OUTPUT, options.showOutput); - s->setValue(Constants::SETTINGS_ADD_INCLUDE_PATHS, options.addIncludePaths); - s->setValue(Constants::SETTINGS_GUESS_ARGUMENTS, options.guessArguments); - s->endGroup(); -} - -void CppcheckOptionsPage::load(CppcheckOptions &options) const -{ - QSettings *s = Core::ICore::settings(); - QTC_ASSERT(s, return); - s->beginGroup(Constants::SETTINGS_ID); - options.binary = FilePath::fromString(s->value(Constants::SETTINGS_BINARY, - options.binary.toString()).toString()); - options.customArguments = s->value(Constants::SETTINGS_CUSTOM_ARGUMENTS, - options.customArguments).toString(); - options.ignoredPatterns = s->value(Constants::SETTINGS_IGNORE_PATTERNS, - options.ignoredPatterns).toString(); - options.warning = s->value(Constants::SETTINGS_WARNING, - options.warning).toBool(); - options.style = s->value(Constants::SETTINGS_STYLE, - options.style).toBool(); - options.performance = s->value(Constants::SETTINGS_PERFORMANCE, - options.performance).toBool(); - options.portability = s->value(Constants::SETTINGS_PORTABILITY, - options.portability).toBool(); - options.information = s->value(Constants::SETTINGS_INFORMATION, - options.information).toBool(); - options.unusedFunction = s->value(Constants::SETTINGS_UNUSED_FUNCTION, - options.unusedFunction).toBool(); - options.missingInclude = s->value(Constants::SETTINGS_MISSING_INCLUDE, - options.missingInclude).toBool(); - options.inconclusive = s->value(Constants::SETTINGS_INCONCLUSIVE, - options.inconclusive).toBool(); - options.forceDefines = s->value(Constants::SETTINGS_FORCE_DEFINES, - options.forceDefines).toBool(); - options.showOutput = s->value(Constants::SETTINGS_SHOW_OUTPUT, - options.showOutput).toBool(); - options.addIncludePaths = s->value(Constants::SETTINGS_ADD_INCLUDE_PATHS, - options.addIncludePaths).toBool(); - options.guessArguments = s->value(Constants::SETTINGS_GUESS_ARGUMENTS, - options.guessArguments).toBool(); - s->endGroup(); + return [this] { + using namespace Layouting; + return Form { + binary, br, + Tr::tr("Checks:"), Flow { + warning, + style, + performance, + portability, + information, + unusedFunction, + missingInclude + }, br, + customArguments, br, + ignoredPatterns, br, + Flow { + inconclusive, + forceDefines, + showOutput, + addIncludePaths, + guessArguments + } + }; + }; } } // Cppcheck::Internal diff --git a/src/plugins/cppcheck/cppcheckoptions.h b/src/plugins/cppcheck/cppcheckoptions.h index 7c23d69f605..eb3e1c7ddbf 100644 --- a/src/plugins/cppcheck/cppcheckoptions.h +++ b/src/plugins/cppcheck/cppcheckoptions.h @@ -4,88 +4,32 @@ #pragma once #include -#include - -#include -#include -#include - -QT_BEGIN_NAMESPACE -class QLineEdit; -class QCheckBox; -QT_END_NAMESPACE - -namespace Utils { class PathChooser; } namespace Cppcheck::Internal { -class CppcheckTool; -class CppcheckTrigger; -class OptionsWidget; - -class CppcheckOptions final +class CppcheckOptions final : public Core::PagedSettings { public: - Utils::FilePath binary; + CppcheckOptions(); - bool warning = true; - bool style = true; - bool performance = true; - bool portability = true; - bool information = true; - bool unusedFunction = false; - bool missingInclude = false; - bool inconclusive = false; - bool forceDefines = false; + std::function layouter(); - QString customArguments; - QString ignoredPatterns; - bool showOutput = false; - bool addIncludePaths = false; - bool guessArguments = true; -}; + Utils::FilePathAspect binary{this}; + Utils::BoolAspect warning{this}; + Utils::BoolAspect style{this}; + Utils::BoolAspect performance{this}; + Utils::BoolAspect portability{this}; + Utils::BoolAspect information{this}; + Utils::BoolAspect unusedFunction{this}; + Utils::BoolAspect missingInclude{this}; + Utils::BoolAspect inconclusive{this}; + Utils::BoolAspect forceDefines{this}; -class OptionsWidget final : public QWidget -{ -public: - explicit OptionsWidget(QWidget *parent = nullptr); - void load(const CppcheckOptions &options); - void save(CppcheckOptions &options) const; - -private: - Utils::PathChooser *m_binary = nullptr; - QLineEdit *m_customArguments = nullptr; - QLineEdit *m_ignorePatterns = nullptr; - QCheckBox *m_warning = nullptr; - QCheckBox *m_style = nullptr; - QCheckBox *m_performance = nullptr; - QCheckBox *m_portability = nullptr; - QCheckBox *m_information = nullptr; - QCheckBox *m_unusedFunction = nullptr; - QCheckBox *m_missingInclude = nullptr; - QCheckBox *m_inconclusive = nullptr; - QCheckBox *m_forceDefines = nullptr; - QCheckBox *m_showOutput = nullptr; - QCheckBox *m_addIncludePaths = nullptr; - QCheckBox *m_guessArguments = nullptr; -}; - -class CppcheckOptionsPage final : public Core::IOptionsPage -{ -public: - explicit CppcheckOptionsPage(CppcheckTool &tool, CppcheckTrigger &trigger); - - QWidget *widget() final; - void apply() final; - void finish() final; - -private: - void save(const CppcheckOptions &options) const; - void load(CppcheckOptions &options) const; - - CppcheckTool &m_tool; - CppcheckTrigger &m_trigger; - QPointer m_widget; + Utils::StringAspect customArguments{this}; + Utils::StringAspect ignoredPatterns{this}; + Utils::BoolAspect showOutput{this}; + Utils::BoolAspect addIncludePaths{this}; + Utils::BoolAspect guessArguments{this}; }; } // Cppcheck::Internal diff --git a/src/plugins/cppcheck/cppcheckplugin.cpp b/src/plugins/cppcheck/cppcheckplugin.cpp index 5dd09a1820b..b025762c8c6 100644 --- a/src/plugins/cppcheck/cppcheckplugin.cpp +++ b/src/plugins/cppcheck/cppcheckplugin.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include #include @@ -25,9 +25,12 @@ #include #include +#include #include #include +using namespace Utils; + namespace Cppcheck::Internal { class CppcheckPluginPrivate final : public QObject @@ -36,11 +39,11 @@ public: explicit CppcheckPluginPrivate(); CppcheckTextMarkManager marks; - CppcheckTool tool{marks, Constants::CHECK_PROGRESS_ID}; + CppcheckOptions options; + CppcheckTool tool{options, marks, Constants::CHECK_PROGRESS_ID}; CppcheckTrigger trigger{marks, tool}; - CppcheckOptionsPage options{tool, trigger}; DiagnosticsModel manualRunModel; - CppcheckTool manualRunTool{manualRunModel, Constants::MANUAL_CHECK_PROGRESS_ID}; + CppcheckTool manualRunTool{options, manualRunModel, Constants::MANUAL_CHECK_PROGRESS_ID}; Utils::Perspective perspective{Constants::PERSPECTIVE_ID, ::Cppcheck::Tr::tr("Cppcheck")}; QAction *manualRunAction; @@ -51,7 +54,11 @@ public: CppcheckPluginPrivate::CppcheckPluginPrivate() { - manualRunTool.updateOptions(tool.options()); + tool.updateOptions(); + connect(&options, &AspectContainer::changed, [this] { + tool.updateOptions(); + trigger.recheck(); + }); auto manualRunView = new DiagnosticView; manualRunView->setModel(&manualRunModel); @@ -97,12 +104,17 @@ CppcheckPluginPrivate::CppcheckPluginPrivate() } } -void CppcheckPluginPrivate::startManualRun() { - auto project = ProjectExplorer::SessionManager::startupProject(); +void CppcheckPluginPrivate::startManualRun() +{ + auto project = ProjectExplorer::ProjectManager::startupProject(); if (!project) return; - ManualRunDialog dialog(manualRunTool.options(), project); + manualRunTool.updateOptions(); + + auto optionsWidget = options.layouter()().emerge(); + + ManualRunDialog dialog(optionsWidget, project); if (dialog.exec() == ManualRunDialog::Rejected) return; @@ -113,7 +125,7 @@ void CppcheckPluginPrivate::startManualRun() { return; manualRunTool.setProject(project); - manualRunTool.updateOptions(dialog.options()); + manualRunTool.updateOptions(); manualRunTool.check(files); perspective.select(); } @@ -121,8 +133,8 @@ void CppcheckPluginPrivate::startManualRun() { void CppcheckPluginPrivate::updateManualRunAction() { using namespace ProjectExplorer; - const Project *project = SessionManager::startupProject(); - const Target *target = SessionManager::startupTarget(); + const Project *project = ProjectManager::startupProject(); + const Target *target = ProjectManager::startupTarget(); const Utils::Id cxx = ProjectExplorer::Constants::CXX_LANGUAGE_ID; const bool canRun = target && project->projectLanguages().contains(cxx) && ToolChainKitAspect::cxxToolChain(target->kit()); diff --git a/src/plugins/cppcheck/cppcheckrunner.cpp b/src/plugins/cppcheck/cppcheckrunner.cpp index 8595eac28ba..436c8ab392c 100644 --- a/src/plugins/cppcheck/cppcheckrunner.cpp +++ b/src/plugins/cppcheck/cppcheckrunner.cpp @@ -16,7 +16,7 @@ namespace Cppcheck::Internal { CppcheckRunner::CppcheckRunner(CppcheckTool &tool) : m_tool(tool) { if (HostOsInfo::hostOs() == OsTypeLinux) { - QtcProcess getConf; + Process getConf; getConf.setCommand({"getconf", {"ARG_MAX"}}); getConf.start(); getConf.waitForFinished(2000); @@ -31,8 +31,8 @@ CppcheckRunner::CppcheckRunner(CppcheckTool &tool) : m_tool(tool) m_tool.parseErrorLine(line); }); - connect(&m_process, &QtcProcess::started, &m_tool, &CppcheckTool::startParsing); - connect(&m_process, &QtcProcess::done, this, &CppcheckRunner::handleDone); + connect(&m_process, &Process::started, &m_tool, &CppcheckTool::startParsing); + connect(&m_process, &Process::done, this, &CppcheckRunner::handleDone); m_queueTimer.setSingleShot(true); const int checkDelayInMs = 200; diff --git a/src/plugins/cppcheck/cppcheckrunner.h b/src/plugins/cppcheck/cppcheckrunner.h index 23689b2b05b..686a6c9eb97 100644 --- a/src/plugins/cppcheck/cppcheckrunner.h +++ b/src/plugins/cppcheck/cppcheckrunner.h @@ -4,7 +4,7 @@ #pragma once #include -#include +#include #include #include @@ -35,7 +35,7 @@ private: void handleDone(); CppcheckTool &m_tool; - Utils::QtcProcess m_process; + Utils::Process m_process; Utils::FilePath m_binary; QString m_arguments; QHash m_queue; diff --git a/src/plugins/cppcheck/cppchecktool.cpp b/src/plugins/cppcheck/cppchecktool.cpp index a5c3bc6afb5..cd824b3db47 100644 --- a/src/plugins/cppcheck/cppchecktool.cpp +++ b/src/plugins/cppcheck/cppchecktool.cpp @@ -26,7 +26,8 @@ using namespace Utils; namespace Cppcheck::Internal { -CppcheckTool::CppcheckTool(CppcheckDiagnosticManager &manager, const Id &progressId) : +CppcheckTool::CppcheckTool(CppcheckOptions &options, CppcheckDiagnosticManager &manager, const Id &progressId) : + m_options(options), m_manager(manager), m_progressRegexp("^.* checked (\\d+)% done$"), m_messageRegexp("^(.+),(\\d+),(\\w+),(\\w+),(.*)$"), @@ -39,11 +40,10 @@ CppcheckTool::CppcheckTool(CppcheckDiagnosticManager &manager, const Id &progres CppcheckTool::~CppcheckTool() = default; -void CppcheckTool::updateOptions(const CppcheckOptions &options) +void CppcheckTool::updateOptions() { - m_options = options; m_filters.clear(); - for (const QString &pattern : m_options.ignoredPatterns.split(',')) { + for (const QString &pattern : m_options.ignoredPatterns().split(',')) { const QString trimmedPattern = pattern.trimmed(); if (trimmedPattern.isEmpty()) continue; @@ -70,44 +70,44 @@ void CppcheckTool::updateArguments() m_cachedAdditionalArguments.clear(); QStringList arguments; - if (!m_options.customArguments.isEmpty()) { + if (!m_options.customArguments().isEmpty()) { Utils::MacroExpander *expander = Utils::globalMacroExpander(); - const QString expanded = expander->expand(m_options.customArguments); + const QString expanded = expander->expand(m_options.customArguments()); arguments.push_back(expanded); } - if (m_options.warning) + if (m_options.warning()) arguments.push_back("--enable=warning"); - if (m_options.style) + if (m_options.style()) arguments.push_back("--enable=style"); - if (m_options.performance) + if (m_options.performance()) arguments.push_back("--enable=performance"); - if (m_options.portability) + if (m_options.portability()) arguments.push_back("--enable=portability"); - if (m_options.information) + if (m_options.information()) arguments.push_back("--enable=information"); - if (m_options.unusedFunction) + if (m_options.unusedFunction()) arguments.push_back("--enable=unusedFunction"); - if (m_options.missingInclude) + if (m_options.missingInclude()) arguments.push_back("--enable=missingInclude"); - if (m_options.inconclusive) + if (m_options.inconclusive()) arguments.push_back("--inconclusive"); - if (m_options.forceDefines) + if (m_options.forceDefines()) arguments.push_back("--force"); - if (!m_options.unusedFunction && !m_options.customArguments.contains("-j ")) + if (!m_options.unusedFunction() && !m_options.customArguments().contains("-j ")) arguments.push_back("-j " + QString::number(QThread::idealThreadCount())); arguments.push_back("--template=\"{file},{line},{severity},{id},{message}\""); - m_runner->reconfigure(m_options.binary, arguments.join(' ')); + m_runner->reconfigure(m_options.binary(), arguments.join(' ')); } QStringList CppcheckTool::additionalArguments(const CppEditor::ProjectPart &part) const { QStringList result; - if (m_options.addIncludePaths) { + if (m_options.addIncludePaths()) { for (const ProjectExplorer::HeaderPath &path : part.headerPaths) { const QString projectDir = m_project->projectDirectory().toString(); if (path.type == ProjectExplorer::HeaderPathType::User @@ -116,7 +116,7 @@ QStringList CppcheckTool::additionalArguments(const CppEditor::ProjectPart &part } } - if (!m_options.guessArguments) + if (!m_options.guessArguments()) return result; using Version = Utils::LanguageVersion; @@ -158,11 +158,6 @@ QStringList CppcheckTool::additionalArguments(const CppEditor::ProjectPart &part return result; } -const CppcheckOptions &CppcheckTool::options() const -{ - return m_options; -} - void CppcheckTool::check(const Utils::FilePaths &files) { QTC_ASSERT(m_project, return); @@ -226,7 +221,7 @@ void CppcheckTool::stop(const Utils::FilePaths &files) void CppcheckTool::startParsing() { - if (m_options.showOutput) { + if (m_options.showOutput()) { const QString message = Tr::tr("Cppcheck started: \"%1\".").arg(m_runner->currentCommand()); Core::MessageManager::writeSilently(message); } @@ -245,7 +240,7 @@ void CppcheckTool::parseOutputLine(const QString &line) if (line.isEmpty()) return; - if (m_options.showOutput) + if (m_options.showOutput()) Core::MessageManager::writeSilently(line); enum Matches { Percentage = 1 }; @@ -276,7 +271,7 @@ void CppcheckTool::parseErrorLine(const QString &line) if (line.isEmpty()) return; - if (m_options.showOutput) + if (m_options.showOutput()) Core::MessageManager::writeSilently(line); enum Matches { File = 1, Line, Severity, Id, Message }; @@ -301,7 +296,7 @@ void CppcheckTool::parseErrorLine(const QString &line) void CppcheckTool::finishParsing() { - if (m_options.showOutput) + if (m_options.showOutput()) Core::MessageManager::writeSilently(Tr::tr("Cppcheck finished.")); QTC_ASSERT(m_progress, return); diff --git a/src/plugins/cppcheck/cppchecktool.h b/src/plugins/cppcheck/cppchecktool.h index ccb8e19d0fe..4dc6699e336 100644 --- a/src/plugins/cppcheck/cppchecktool.h +++ b/src/plugins/cppcheck/cppchecktool.h @@ -31,10 +31,10 @@ class CppcheckTool final : public QObject Q_OBJECT public: - CppcheckTool(CppcheckDiagnosticManager &manager, const Utils::Id &progressId); + CppcheckTool(CppcheckOptions &options, CppcheckDiagnosticManager &manager, const Utils::Id &progressId); ~CppcheckTool() override; - void updateOptions(const CppcheckOptions &options); + void updateOptions(); void setProject(ProjectExplorer::Project *project); void check(const Utils::FilePaths &files); void stop(const Utils::FilePaths &files); @@ -44,15 +44,13 @@ public: void parseErrorLine(const QString &line); void finishParsing(); - const CppcheckOptions &options() const; - private: void updateArguments(); void addToQueue(const Utils::FilePaths &files, const CppEditor::ProjectPart &part); QStringList additionalArguments(const CppEditor::ProjectPart &part) const; + CppcheckOptions &m_options; CppcheckDiagnosticManager &m_manager; - CppcheckOptions m_options; QPointer m_project; std::unique_ptr m_runner; std::unique_ptr> m_progress; diff --git a/src/plugins/cppcheck/cppchecktrigger.cpp b/src/plugins/cppcheck/cppchecktrigger.cpp index 55b1cf7ac01..8be098df558 100644 --- a/src/plugins/cppcheck/cppchecktrigger.cpp +++ b/src/plugins/cppcheck/cppchecktrigger.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include using namespace Core; using namespace ProjectExplorer; @@ -34,7 +34,7 @@ CppcheckTrigger::CppcheckTrigger(CppcheckTextMarkManager &marks, CppcheckTool &t connect(EditorManager::instance(), &EditorManager::aboutToSave, this, &CppcheckTrigger::checkChangedDocument); - connect(SessionManager::instance(), &SessionManager::startupProjectChanged, + connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, this, &CppcheckTrigger::changeCurrentProject); connect(CppModelManager::instance(), &CppModelManager::projectPartsUpdated, diff --git a/src/plugins/cppeditor/CMakeLists.txt b/src/plugins/cppeditor/CMakeLists.txt index b3db8c4d1dc..021bf0b7630 100644 --- a/src/plugins/cppeditor/CMakeLists.txt +++ b/src/plugins/cppeditor/CMakeLists.txt @@ -32,7 +32,6 @@ add_qtc_plugin(CppEditor cppcompletionassist.cpp cppcompletionassist.h cppcompletionassistprocessor.cpp cppcompletionassistprocessor.h cppcompletionassistprovider.cpp cppcompletionassistprovider.h - cppcurrentdocumentfilter.cpp cppcurrentdocumentfilter.h cppcursorinfo.h cppdoxygen.cpp cppdoxygen.h cppeditor.qrc @@ -107,7 +106,6 @@ add_qtc_plugin(CppEditor resourcepreviewhoverhandler.cpp resourcepreviewhoverhandler.h searchsymbols.cpp searchsymbols.h semantichighlighter.cpp semantichighlighter.h - senddocumenttracker.cpp senddocumenttracker.h symbolfinder.cpp symbolfinder.h symbolsfindfilter.cpp symbolsfindfilter.h typehierarchybuilder.cpp typehierarchybuilder.h diff --git a/src/plugins/cppeditor/baseeditordocumentparser.cpp b/src/plugins/cppeditor/baseeditordocumentparser.cpp index 8a8d1bc9afd..cbfa6f96859 100644 --- a/src/plugins/cppeditor/baseeditordocumentparser.cpp +++ b/src/plugins/cppeditor/baseeditordocumentparser.cpp @@ -8,6 +8,8 @@ #include "cppprojectpartchooser.h" #include "editordocumenthandle.h" +#include + using namespace Utils; namespace CppEditor { @@ -59,15 +61,16 @@ void BaseEditorDocumentParser::setConfiguration(const Configuration &configurati void BaseEditorDocumentParser::update(const UpdateParams &updateParams) { - QFutureInterface dummy; + QPromise dummy; + dummy.start(); update(dummy, updateParams); } -void BaseEditorDocumentParser::update(const QFutureInterface &future, +void BaseEditorDocumentParser::update(const QPromise &promise, const UpdateParams &updateParams) { QMutexLocker locker(&m_updateIsRunning); - updateImpl(future, updateParams); + updateImpl(promise, updateParams); } BaseEditorDocumentParser::State BaseEditorDocumentParser::state() const diff --git a/src/plugins/cppeditor/baseeditordocumentparser.h b/src/plugins/cppeditor/baseeditordocumentparser.h index fb7f79d1016..45f6b953990 100644 --- a/src/plugins/cppeditor/baseeditordocumentparser.h +++ b/src/plugins/cppeditor/baseeditordocumentparser.h @@ -6,14 +6,17 @@ #include "cppeditor_global.h" #include "cpptoolsreuse.h" #include "cppworkingcopy.h" -#include "projectpart.h" #include -#include #include #include +QT_BEGIN_NAMESPACE +template +class QPromise; +QT_END_NAMESPACE + namespace ProjectExplorer { class Project; } namespace CppEditor { @@ -66,7 +69,7 @@ public: void setConfiguration(const Configuration &configuration); void update(const UpdateParams &updateParams); - void update(const QFutureInterface &future, const UpdateParams &updateParams); + void update(const QPromise &promise, const UpdateParams &updateParams); ProjectPartInfo projectPartInfo() const; @@ -91,7 +94,7 @@ protected: mutable QMutex m_stateAndConfigurationMutex; private: - virtual void updateImpl(const QFutureInterface &future, + virtual void updateImpl(const QPromise &promise, const UpdateParams &updateParams) = 0; const Utils::FilePath m_filePath; diff --git a/src/plugins/cppeditor/baseeditordocumentprocessor.cpp b/src/plugins/cppeditor/baseeditordocumentprocessor.cpp index ca344aa6c7c..c6c51675eb9 100644 --- a/src/plugins/cppeditor/baseeditordocumentprocessor.cpp +++ b/src/plugins/cppeditor/baseeditordocumentprocessor.cpp @@ -8,9 +8,12 @@ #include "cpptoolsreuse.h" #include "editordocumenthandle.h" -#include +#include + #include +#include + namespace CppEditor { /*! @@ -37,7 +40,7 @@ void BaseEditorDocumentProcessor::run(bool projectsUpdated) : Utils::Language::Cxx; runImpl({CppModelManager::instance()->workingCopy(), - ProjectExplorer::SessionManager::startupProject(), + ProjectExplorer::ProjectManager::startupProject(), languagePreference, projectsUpdated}); } @@ -58,20 +61,20 @@ void BaseEditorDocumentProcessor::setParserConfig( parser()->setConfiguration(config); } -void BaseEditorDocumentProcessor::runParser(QFutureInterface &future, +void BaseEditorDocumentProcessor::runParser(QPromise &promise, BaseEditorDocumentParser::Ptr parser, BaseEditorDocumentParser::UpdateParams updateParams) { - future.setProgressRange(0, 1); - if (future.isCanceled()) { - future.setProgressValue(1); + promise.setProgressRange(0, 1); + if (promise.isCanceled()) { + promise.setProgressValue(1); return; } - parser->update(future, updateParams); + parser->update(promise, updateParams); CppModelManager::instance()->finishedRefreshingSourceFiles({parser->filePath().toString()}); - future.setProgressValue(1); + promise.setProgressValue(1); } } // namespace CppEditor diff --git a/src/plugins/cppeditor/baseeditordocumentprocessor.h b/src/plugins/cppeditor/baseeditordocumentprocessor.h index afb74f46e64..78c0f55aabc 100644 --- a/src/plugins/cppeditor/baseeditordocumentprocessor.h +++ b/src/plugins/cppeditor/baseeditordocumentprocessor.h @@ -23,6 +23,11 @@ #include +QT_BEGIN_NAMESPACE +template +class QPromise; +QT_END_NAMESPACE + namespace TextEditor { class TextDocument; } namespace CppEditor { @@ -83,7 +88,7 @@ signals: void semanticInfoUpdated(const SemanticInfo semanticInfo); // TODO: Remove me protected: - static void runParser(QFutureInterface &future, + static void runParser(QPromise &promise, BaseEditorDocumentParser::Ptr parser, BaseEditorDocumentParser::UpdateParams updateParams); diff --git a/src/plugins/cppeditor/builtincursorinfo.cpp b/src/plugins/cppeditor/builtincursorinfo.cpp index 14d99ce4db8..4545ebe748d 100644 --- a/src/plugins/cppeditor/builtincursorinfo.cpp +++ b/src/plugins/cppeditor/builtincursorinfo.cpp @@ -14,9 +14,9 @@ #include #include -#include +#include #include -#include +#include #include @@ -322,7 +322,7 @@ QFuture BuiltinCursorInfo::run(const CursorInfoParams &cursorInfoPar QString expression; Scope *scope = canonicalSymbol.getScopeAndExpression(textCursor, &expression); - return Utils::runAsync(&FindUses::find, document, snapshot, line, column, scope, expression); + return Utils::asyncRun(&FindUses::find, document, snapshot, line, column + 1, scope, expression); } SemanticInfo::LocalUseMap diff --git a/src/plugins/cppeditor/builtineditordocumentparser.cpp b/src/plugins/cppeditor/builtineditordocumentparser.cpp index 60c768b3a15..f6d7d6faf22 100644 --- a/src/plugins/cppeditor/builtineditordocumentparser.cpp +++ b/src/plugins/cppeditor/builtineditordocumentparser.cpp @@ -11,6 +11,8 @@ #include #include +#include + using namespace CPlusPlus; using namespace Utils; @@ -42,7 +44,7 @@ BuiltinEditorDocumentParser::BuiltinEditorDocumentParser(const FilePath &filePat qRegisterMetaType("CPlusPlus::Snapshot"); } -void BuiltinEditorDocumentParser::updateImpl(const QFutureInterface &future, +void BuiltinEditorDocumentParser::updateImpl(const QPromise &promise, const UpdateParams &updateParams) { if (filePath().isEmpty()) @@ -142,8 +144,8 @@ void BuiltinEditorDocumentParser::updateImpl(const QFutureInterface &futur QSet toRemove; for (const Document::Ptr &doc : std::as_const(state.snapshot)) { const Utils::FilePath filePath = doc->filePath(); - if (workingCopy.contains(filePath)) { - if (workingCopy.get(filePath).second != doc->editorRevision()) + if (const auto entry = workingCopy.get(filePath)) { + if (entry->second != doc->editorRevision()) addFileAndDependencies(&state.snapshot, &toRemove, filePath); continue; } @@ -180,7 +182,7 @@ void BuiltinEditorDocumentParser::updateImpl(const QFutureInterface &futur doc->releaseSourceAndAST(); }); sourceProcessor.setFileSizeLimitInMb(m_fileSizeLimitInMb); - sourceProcessor.setCancelChecker([future]() { return future.isCanceled(); }); + sourceProcessor.setCancelChecker([&promise] { return promise.isCanceled(); }); Snapshot globalSnapshot = modelManager->snapshot(); globalSnapshot.remove(filePath()); diff --git a/src/plugins/cppeditor/builtineditordocumentparser.h b/src/plugins/cppeditor/builtineditordocumentparser.h index b1a400a7c43..31ac126c694 100644 --- a/src/plugins/cppeditor/builtineditordocumentparser.h +++ b/src/plugins/cppeditor/builtineditordocumentparser.h @@ -34,8 +34,7 @@ public: static Ptr get(const Utils::FilePath &filePath); private: - void updateImpl(const QFutureInterface &future, - const UpdateParams &updateParams) override; + void updateImpl(const QPromise &promise, const UpdateParams &updateParams) override; void addFileAndDependencies(CPlusPlus::Snapshot *snapshot, QSet *toRemove, const Utils::FilePath &fileName) const; diff --git a/src/plugins/cppeditor/builtineditordocumentprocessor.cpp b/src/plugins/cppeditor/builtineditordocumentprocessor.cpp index c94a639eefb..8e551fd15be 100644 --- a/src/plugins/cppeditor/builtineditordocumentprocessor.cpp +++ b/src/plugins/cppeditor/builtineditordocumentprocessor.cpp @@ -6,11 +6,14 @@ #include "builtincursorinfo.h" #include "cppchecksymbols.h" #include "cppcodemodelsettings.h" +#include "cppeditordocument.h" #include "cppeditorplugin.h" #include "cppmodelmanager.h" #include "cpptoolsreuse.h" #include "cppworkingcopy.h" +#include + #include #include #include @@ -18,9 +21,9 @@ #include #include -#include +#include #include -#include +#include #include #include @@ -89,10 +92,10 @@ CheckSymbols *createHighlighter(const CPlusPlus::Document::Ptr &doc, for (const CPlusPlus::Macro ¯o : doc->definedMacros()) { int line, column; convertPosition(textDocument, macro.utf16CharOffset(), &line, &column); - QTC_ASSERT(line >= 0 && column >= 0, qDebug() << doc->filePath() << macro.toString(); + QTC_ASSERT(line > 0 && column >= 0, qDebug() << doc->filePath() << macro.toString(); continue); - Result use(line, column, macro.nameToQString().size(), SemanticHighlighter::MacroUse); + Result use(line, column + 1, macro.nameToQString().size(), SemanticHighlighter::MacroUse); macroUses.append(use); } @@ -116,10 +119,10 @@ CheckSymbols *createHighlighter(const CPlusPlus::Document::Ptr &doc, int line, column; convertPosition(textDocument, macro.utf16charsBegin(), &line, &column); - QTC_ASSERT(line >= 0 && column >= 0, qDebug() << doc->filePath() + QTC_ASSERT(line > 0 && column >= 0, qDebug() << doc->filePath() << macro.macro().toString(); continue); - Result use(line, column, name.size(), SemanticHighlighter::MacroUse); + Result use(line, column + 1, name.size(), SemanticHighlighter::MacroUse); macroUses.append(use); } @@ -180,10 +183,8 @@ BuiltinEditorDocumentProcessor::~BuiltinEditorDocumentProcessor() void BuiltinEditorDocumentProcessor::runImpl( const BaseEditorDocumentParser::UpdateParams &updateParams) { - m_parserFuture = Utils::runAsync(CppModelManager::instance()->sharedThreadPool(), - runParser, - parser(), - updateParams); + m_parserFuture = Utils::asyncRun(CppModelManager::instance()->sharedThreadPool(), + runParser, parser(), updateParams); } BaseEditorDocumentParser::Ptr BuiltinEditorDocumentProcessor::parser() @@ -266,6 +267,22 @@ void BuiltinEditorDocumentProcessor::onParserFinished(CPlusPlus::Document::Ptr d const auto source = createSemanticInfoSource(false); QTC_CHECK(source.snapshot.contains(document->filePath())); m_semanticInfoUpdater.updateDetached(source); + + const QList openDocuments = Core::DocumentModel::openedDocuments(); + for (Core::IDocument * const openDocument : openDocuments) { + const auto cppEditorDoc = qobject_cast(openDocument); + if (!cppEditorDoc) + continue; + if (cppEditorDoc->filePath() == document->filePath()) + continue; + CPlusPlus::Document::Ptr cppDoc = CppModelManager::instance()->document( + cppEditorDoc->filePath()); + if (!cppDoc) + continue; + if (!cppDoc->includedFiles().contains(document->filePath())) + continue; + cppEditorDoc->scheduleProcessDocument(); + } } void BuiltinEditorDocumentProcessor::onSemanticInfoUpdated(const SemanticInfo semanticInfo) @@ -304,12 +321,13 @@ void BuiltinEditorDocumentProcessor::onCodeWarningsUpdated( SemanticInfo::Source BuiltinEditorDocumentProcessor::createSemanticInfoSource(bool force) const { - const WorkingCopy workingCopy = CppModelManager::instance()->workingCopy(); - return SemanticInfo::Source(filePath().toString(), - workingCopy.source(filePath()), - workingCopy.revision(filePath()), - m_documentSnapshot, - force); + QByteArray source; + int revision = 0; + if (const auto entry = CppModelManager::instance()->workingCopy().get(filePath())) { + source = entry->first; + revision = entry->second; + } + return SemanticInfo::Source(filePath().toString(), source, revision, m_documentSnapshot, force); } } // namespace CppEditor diff --git a/src/plugins/cppeditor/clangdiagnosticconfig.h b/src/plugins/cppeditor/clangdiagnosticconfig.h index c1d21cf7d73..3442c95db50 100644 --- a/src/plugins/cppeditor/clangdiagnosticconfig.h +++ b/src/plugins/cppeditor/clangdiagnosticconfig.h @@ -42,10 +42,8 @@ public: // Clang-Tidy enum class TidyMode { - // Disabled, // Used by Qt Creator 4.10 and below. UseCustomChecks = 1, - UseConfigFile, - UseDefaultChecks, + UseDefaultChecks = 3, }; TidyMode clangTidyMode() const; void setClangTidyMode(TidyMode mode); diff --git a/src/plugins/cppeditor/clangdiagnosticconfigswidget.cpp b/src/plugins/cppeditor/clangdiagnosticconfigswidget.cpp index f5fc8cfe425..8e7e1b8f84f 100644 --- a/src/plugins/cppeditor/clangdiagnosticconfigswidget.cpp +++ b/src/plugins/cppeditor/clangdiagnosticconfigswidget.cpp @@ -120,9 +120,11 @@ class ClangBaseChecksWidget : public QWidget public: ClangBaseChecksWidget() { - auto label = new QLabel(Tr::tr("For appropriate options, consult the GCC or Clang manual " - "pages or the %1 GCC online documentation.") - .arg("")); + auto label = new QLabel; + label->setTextFormat(Qt::MarkdownText); + label->setText(Tr::tr("For appropriate options, consult the GCC or Clang manual " + "pages or the [GCC online documentation](%1).") + .arg("https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html")); label->setOpenExternalLinks(true); useFlagsFromBuildSystemCheckBox = new QCheckBox(Tr::tr("Use diagnostic flags from build system")); diff --git a/src/plugins/cppeditor/compileroptionsbuilder.cpp b/src/plugins/cppeditor/compileroptionsbuilder.cpp index d2c5e16325f..5f092121214 100644 --- a/src/plugins/cppeditor/compileroptionsbuilder.cpp +++ b/src/plugins/cppeditor/compileroptionsbuilder.cpp @@ -130,6 +130,7 @@ QStringList CompilerOptionsBuilder::build(ProjectFile::Kind fileKind, undefineCppLanguageFeatureMacrosForMsvc2015(); addDefineFunctionMacrosMsvc(); addDefineFunctionMacrosQnx(); + addQtMacros(); addHeaderPathOptions(); @@ -786,6 +787,12 @@ void CompilerOptionsBuilder::addDefineFunctionMacrosQnx() addMacros({{"_LIBCPP_HAS_NO_BUILTIN_OPERATOR_NEW_DELETE"}}); } +void CompilerOptionsBuilder::addQtMacros() +{ + if (m_projectPart.qtVersion != QtMajorVersion::None) + addMacros({{"QT_ANNOTATE_FUNCTION(x)", "__attribute__((annotate(#x)))"}}); +} + void CompilerOptionsBuilder::reset() { m_options.clear(); diff --git a/src/plugins/cppeditor/compileroptionsbuilder.h b/src/plugins/cppeditor/compileroptionsbuilder.h index 0e8c1a5fad0..d47a7e87e3d 100644 --- a/src/plugins/cppeditor/compileroptionsbuilder.h +++ b/src/plugins/cppeditor/compileroptionsbuilder.h @@ -62,6 +62,7 @@ public: void undefineClangVersionMacrosForMsvc(); void addDefineFunctionMacrosQnx(); + void addQtMacros(); // Add custom options void add(const QString &arg, bool gccOnlyOption = false); diff --git a/src/plugins/cppeditor/compileroptionsbuilder_test.cpp b/src/plugins/cppeditor/compileroptionsbuilder_test.cpp index 3a19c16a663..b09a8061fc7 100644 --- a/src/plugins/cppeditor/compileroptionsbuilder_test.cpp +++ b/src/plugins/cppeditor/compileroptionsbuilder_test.cpp @@ -593,6 +593,7 @@ void CompilerOptionsBuilderTest::testBuildAllOptions() (QStringList{"-nostdinc", "-nostdinc++", "-arch", "x86_64", "-fsyntax-only", "-m64", "--target=x86_64-apple-darwin10", "-x", "c++", "-std=c++17", "-DprojectFoo=projectBar", + "-DQT_ANNOTATE_FUNCTION(x)=__attribute__((annotate(#x)))", wrappedQtHeadersPath, // contains -I already wrappedQtCoreHeadersPath, // contains -I already "-I" + t.toNative("/tmp/path"), @@ -621,6 +622,7 @@ void CompilerOptionsBuilderTest::testBuildAllOptionsMsvc() "-D__FUNCSIG__=\"void __cdecl someLegalAndLongishFunctionNameThatWorksAroundQTCREATORBUG-24580(void)\"", "-D__FUNCTION__=\"someLegalAndLongishFunctionNameThatWorksAroundQTCREATORBUG-24580\"", "-D__FUNCDNAME__=\"?someLegalAndLongishFunctionNameThatWorksAroundQTCREATORBUG-24580@@YAXXZ\"", + "-DQT_ANNOTATE_FUNCTION(x)=__attribute__((annotate(#x)))", wrappedQtHeadersPath, // contains -I already wrappedQtCoreHeadersPath, // contains -I already "-I" + t.toNative("/tmp/path"), @@ -651,6 +653,7 @@ void CompilerOptionsBuilderTest::testBuildAllOptionsMsvcWithExceptions() "-D__FUNCSIG__=\"void __cdecl someLegalAndLongishFunctionNameThatWorksAroundQTCREATORBUG-24580(void)\"", "-D__FUNCTION__=\"someLegalAndLongishFunctionNameThatWorksAroundQTCREATORBUG-24580\"", "-D__FUNCDNAME__=\"?someLegalAndLongishFunctionNameThatWorksAroundQTCREATORBUG-24580@@YAXXZ\"", + "-DQT_ANNOTATE_FUNCTION(x)=__attribute__((annotate(#x)))", wrappedQtHeadersPath, // contains -I already wrappedQtCoreHeadersPath, // contains -I already "-I" + t.toNative("/tmp/path"), diff --git a/src/plugins/cppeditor/cppcanonicalsymbol.cpp b/src/plugins/cppeditor/cppcanonicalsymbol.cpp index 1df0c71cf96..102a5d07819 100644 --- a/src/plugins/cppeditor/cppcanonicalsymbol.cpp +++ b/src/plugins/cppeditor/cppcanonicalsymbol.cpp @@ -51,7 +51,7 @@ Scope *CanonicalSymbol::getScopeAndExpression(const QTextCursor &cursor, QString ExpressionUnderCursor expressionUnderCursor(m_document->languageFeatures()); *code = expressionUnderCursor(tc); - return m_document->scopeAt(line, column - 1); + return m_document->scopeAt(line, column); } Symbol *CanonicalSymbol::operator()(const QTextCursor &cursor) diff --git a/src/plugins/cppeditor/cppcodeformatter.cpp b/src/plugins/cppeditor/cppcodeformatter.cpp index ce250f672b0..6b30e78fa6c 100644 --- a/src/plugins/cppeditor/cppcodeformatter.cpp +++ b/src/plugins/cppeditor/cppcodeformatter.cpp @@ -924,6 +924,7 @@ bool CodeFormatter::tryStatement() return true; switch (kind) { case T_RETURN: + case T_CO_RETURN: enter(return_statement); enter(expression); return true; @@ -1651,6 +1652,7 @@ void QtStyleCodeFormatter::adjustIndent(const Tokens &tokens, int lexerState, in case T_BREAK: case T_CONTINUE: case T_RETURN: + case T_CO_RETURN: if (topState.type == case_cont) { *indentDepth = topState.savedIndentDepth; if (m_styleSettings.indentControlFlowRelativeToSwitchLabels) diff --git a/src/plugins/cppeditor/cppcodemodelinspectordialog.cpp b/src/plugins/cppeditor/cppcodemodelinspectordialog.cpp index 8b9cba0d630..48ee4c80f1e 100644 --- a/src/plugins/cppeditor/cppcodemodelinspectordialog.cpp +++ b/src/plugins/cppeditor/cppcodemodelinspectordialog.cpp @@ -1429,7 +1429,9 @@ CppCodeModelInspectorDialog::CppCodeModelInspectorDialog(QWidget *parent) m_workingCopyView->setModel(m_proxyWorkingCopyModel); using namespace Layouting; - m_projectPartTab = qobject_cast(TabWidget{ + + TabWidget projectPart { + bindTo(&m_projectPartTab), Tab("&General", Row { m_partGeneralView, @@ -1454,10 +1456,10 @@ CppCodeModelInspectorDialog::CppCodeModelInspectorDialog(QWidget *parent) ), Tab("&Header Paths", Column{ projectHeaderPathsView }), Tab("Pre&compiled Headers", Column{ m_partPrecompiledHeadersEdit }), - }.widget); - QTC_CHECK(m_projectPartTab); + }; - m_docTab = qobject_cast(TabWidget{ + TabWidget docTab { + bindTo(&m_docTab), Tab("&General", Column { m_docGeneralView }), Tab("&Includes", Column { m_docIncludesView }), Tab("&Diagnostic Messages", Column { m_docDiagnosticMessagesView }), @@ -1465,8 +1467,7 @@ CppCodeModelInspectorDialog::CppCodeModelInspectorDialog(QWidget *parent) Tab("P&reprocessed Source", Column { m_docPreprocessedSourceEdit }), Tab("&Symbols", Column { m_docSymbolsView }), Tab("&Tokens", Column { m_docTokensView }), - }.widget); - QTC_CHECK(m_docTab); + }; Column { TabWidget { @@ -1474,7 +1475,7 @@ CppCodeModelInspectorDialog::CppCodeModelInspectorDialog(QWidget *parent) Column { Splitter { m_projectPartsView, - m_projectPartTab, + projectPart, }, } ), @@ -1484,8 +1485,9 @@ CppCodeModelInspectorDialog::CppCodeModelInspectorDialog(QWidget *parent) Column { Form { QString("Sn&apshot:"), m_snapshotSelector }, m_snapshotView, - }.emerge(Utils::Layouting::WithoutMargins), - m_docTab, + noMargin, + }.emerge(), + docTab, }, } ), @@ -1506,6 +1508,7 @@ CppCodeModelInspectorDialog::CppCodeModelInspectorDialog(QWidget *parent) } }.attachTo(this); + QTC_CHECK(m_projectPartTab); m_projectPartTab->setCurrentIndex(3); connect(m_snapshotView->selectionModel(), diff --git a/src/plugins/cppeditor/cppcodemodelinspectordumper.cpp b/src/plugins/cppeditor/cppcodemodelinspectordumper.cpp index 45de8f30539..2a6dfb6e1e8 100644 --- a/src/plugins/cppeditor/cppcodemodelinspectordumper.cpp +++ b/src/plugins/cppeditor/cppcodemodelinspectordumper.cpp @@ -254,12 +254,19 @@ QString Utils::toString(CPlusPlus::Kind kind) TOKEN(T_CASE); TOKEN(T_CATCH); TOKEN(T_CHAR); + TOKEN(T_CHAR8_T); TOKEN(T_CHAR16_T); TOKEN(T_CHAR32_T); TOKEN(T_CLASS); + TOKEN(T_CO_AWAIT); + TOKEN(T_CO_RETURN); + TOKEN(T_CO_YIELD); + TOKEN(T_CONCEPT); TOKEN_AND_ALIASES(T_CONST, T___CONST/T___CONST__); TOKEN(T_CONST_CAST); TOKEN(T_CONSTEXPR); + TOKEN(T_CONSTEVAL); + TOKEN(T_CONSTINIT); TOKEN(T_CONTINUE); TOKEN_AND_ALIASES(T_DECLTYPE, T___DECLTYPE); TOKEN(T_DEFAULT); @@ -292,6 +299,7 @@ QString Utils::toString(CPlusPlus::Kind kind) TOKEN(T_PUBLIC); TOKEN(T_REGISTER); TOKEN(T_REINTERPRET_CAST); + TOKEN(T_REQUIRES); TOKEN(T_RETURN); TOKEN(T_SHORT); TOKEN(T_SIGNED); diff --git a/src/plugins/cppeditor/cppcodemodelsettings.cpp b/src/plugins/cppeditor/cppcodemodelsettings.cpp index e0873427f79..d0976b8128d 100644 --- a/src/plugins/cppeditor/cppcodemodelsettings.cpp +++ b/src/plugins/cppeditor/cppcodemodelsettings.cpp @@ -9,13 +9,14 @@ #include "cpptoolsreuse.h" #include +#include + #include -#include #include #include +#include #include -#include #include #include @@ -63,6 +64,9 @@ static QString useClangdKey() { return QLatin1String("UseClangdV7"); } static QString clangdPathKey() { return QLatin1String("ClangdPath"); } static QString clangdIndexingKey() { return QLatin1String("ClangdIndexing"); } static QString clangdIndexingPriorityKey() { return QLatin1String("ClangdIndexingPriority"); } +static QString clangdHeaderSourceSwitchModeKey() { + return QLatin1String("ClangdHeaderSourceSwitchMode"); +} static QString clangdHeaderInsertionKey() { return QLatin1String("ClangdHeaderInsertion"); } static QString clangdThreadLimitKey() { return QLatin1String("ClangdThreadLimit"); } static QString clangdDocumentThresholdKey() { return QLatin1String("ClangdDocumentThreshold"); } @@ -228,6 +232,16 @@ QString ClangdSettings::priorityToDisplayString(const IndexingPriority &priority return {}; } +QString ClangdSettings::headerSourceSwitchModeToDisplayString(HeaderSourceSwitchMode mode) +{ + switch (mode) { + case HeaderSourceSwitchMode::BuiltinOnly: return Tr::tr("Use Built-in Only"); + case HeaderSourceSwitchMode::ClangdOnly: return Tr::tr("Use Clangd Only"); + case HeaderSourceSwitchMode::Both: return Tr::tr("Try Both"); + } + return {}; +} + ClangdSettings &ClangdSettings::instance() { static ClangdSettings settings; @@ -237,15 +251,18 @@ ClangdSettings &ClangdSettings::instance() ClangdSettings::ClangdSettings() { loadSettings(); - const auto sessionMgr = ProjectExplorer::SessionManager::instance(); - connect(sessionMgr, &ProjectExplorer::SessionManager::sessionRemoved, - this, [this](const QString &name) { m_data.sessionsWithOneClangd.removeOne(name); }); - connect(sessionMgr, &ProjectExplorer::SessionManager::sessionRenamed, - this, [this](const QString &oldName, const QString &newName) { - const auto index = m_data.sessionsWithOneClangd.indexOf(oldName); - if (index != -1) - m_data.sessionsWithOneClangd[index] = newName; + const auto sessionMgr = Core::SessionManager::instance(); + connect(sessionMgr, &Core::SessionManager::sessionRemoved, this, [this](const QString &name) { + m_data.sessionsWithOneClangd.removeOne(name); }); + connect(sessionMgr, + &Core::SessionManager::sessionRenamed, + this, + [this](const QString &oldName, const QString &newName) { + const auto index = m_data.sessionsWithOneClangd.indexOf(oldName); + if (index != -1) + m_data.sessionsWithOneClangd[index] = newName; + }); } bool ClangdSettings::useClangd() const @@ -319,7 +336,7 @@ ClangDiagnosticConfig ClangdSettings::diagnosticConfig() const ClangdSettings::Granularity ClangdSettings::granularity() const { - if (m_data.sessionsWithOneClangd.contains(ProjectExplorer::SessionManager::activeSession())) + if (m_data.sessionsWithOneClangd.contains(Core::SessionManager::activeSession())) return Granularity::Session; return Granularity::Project; } @@ -339,7 +356,7 @@ static FilePath getClangHeadersPathFromClang(const FilePath &clangdFilePath) .withExecutableSuffix(); if (!clangFilePath.exists()) return {}; - QtcProcess clang; + Process clang; clang.setCommand({clangFilePath, {"-print-resource-dir"}}); clang.start(); if (!clang.waitForFinished()) @@ -527,6 +544,7 @@ QVariantMap ClangdSettings::Data::toMap() const : QString()); map.insert(clangdIndexingKey(), indexingPriority != IndexingPriority::Off); map.insert(clangdIndexingPriorityKey(), int(indexingPriority)); + map.insert(clangdHeaderSourceSwitchModeKey(), int(headerSourceSwitchMode)); map.insert(clangdHeaderInsertionKey(), autoIncludeHeaders); map.insert(clangdThreadLimitKey(), workerThreadLimit); map.insert(clangdDocumentThresholdKey(), documentUpdateThreshold); @@ -548,6 +566,8 @@ void ClangdSettings::Data::fromMap(const QVariantMap &map) const auto it = map.find(clangdIndexingKey()); if (it != map.end() && !it->toBool()) indexingPriority = IndexingPriority::Off; + headerSourceSwitchMode = HeaderSourceSwitchMode(map.value(clangdHeaderSourceSwitchModeKey(), + int(headerSourceSwitchMode)).toInt()); autoIncludeHeaders = map.value(clangdHeaderInsertionKey(), false).toBool(); workerThreadLimit = map.value(clangdThreadLimitKey(), 0).toInt(); documentUpdateThreshold = map.value(clangdDocumentThresholdKey(), 500).toInt(); diff --git a/src/plugins/cppeditor/cppcodemodelsettings.h b/src/plugins/cppeditor/cppcodemodelsettings.h index 7bfdc2e85e8..c22fb88faa5 100644 --- a/src/plugins/cppeditor/cppcodemodelsettings.h +++ b/src/plugins/cppeditor/cppcodemodelsettings.h @@ -84,9 +84,11 @@ class CPPEDITOR_EXPORT ClangdSettings : public QObject Q_OBJECT public: enum class IndexingPriority { Off, Background, Normal, Low, }; + enum class HeaderSourceSwitchMode { BuiltinOnly, ClangdOnly, Both }; static QString priorityToString(const IndexingPriority &priority); static QString priorityToDisplayString(const IndexingPriority &priority); + static QString headerSourceSwitchModeToDisplayString(HeaderSourceSwitchMode mode); class CPPEDITOR_EXPORT Data { @@ -103,6 +105,7 @@ public: && s1.diagnosticConfigId == s2.diagnosticConfigId && s1.workerThreadLimit == s2.workerThreadLimit && s1.indexingPriority == s2.indexingPriority + && s1.headerSourceSwitchMode == s2.headerSourceSwitchMode && s1.autoIncludeHeaders == s2.autoIncludeHeaders && s1.documentUpdateThreshold == s2.documentUpdateThreshold && s1.sizeThresholdEnabled == s2.sizeThresholdEnabled @@ -123,6 +126,7 @@ public: qint64 sizeThresholdInKb = 1024; bool useClangd = true; IndexingPriority indexingPriority = IndexingPriority::Low; + HeaderSourceSwitchMode headerSourceSwitchMode = HeaderSourceSwitchMode::Both; bool autoIncludeHeaders = false; bool sizeThresholdEnabled = false; bool haveCheckedHardwareReqirements = false; @@ -143,6 +147,7 @@ public: static void setCustomDiagnosticConfigs(const ClangDiagnosticConfigs &configs); Utils::FilePath clangdFilePath() const; IndexingPriority indexingPriority() const { return m_data.indexingPriority; } + HeaderSourceSwitchMode headerSourceSwitchMode() const { return m_data.headerSourceSwitchMode; } bool autoIncludeHeaders() const { return m_data.autoIncludeHeaders; } int workerThreadLimit() const { return m_data.workerThreadLimit; } int documentUpdateThreshold() const { return m_data.documentUpdateThreshold; } diff --git a/src/plugins/cppeditor/cppcodemodelsettingspage.cpp b/src/plugins/cppeditor/cppcodemodelsettingspage.cpp index 46870ba2520..289cd3dc86c 100644 --- a/src/plugins/cppeditor/cppcodemodelsettingspage.cpp +++ b/src/plugins/cppeditor/cppcodemodelsettingspage.cpp @@ -10,8 +10,7 @@ #include "cpptoolsreuse.h" #include - -#include +#include #include #include @@ -73,8 +72,10 @@ CppCodeModelSettingsWidget::CppCodeModelSettingsWidget(CppCodeModelSettings *s) m_bigFilesLimitSpinBox->setValue(m_settings->indexerFileSizeLimitInMb()); m_ignoreFilesCheckBox = new QCheckBox(Tr::tr("Ignore files")); - m_ignoreFilesCheckBox->setToolTip(Tr::tr( - "

Ignore files that match these wildcard patterns, one wildcard per line.

")); + m_ignoreFilesCheckBox->setToolTip( + "

" + + Tr::tr("Ignore files that match these wildcard patterns, one wildcard per line.") + + "

"); m_ignoreFilesCheckBox->setChecked(m_settings->ignoreFiles()); m_ignorePatternTextEdit = new QPlainTextEdit(m_settings->ignorePattern()); @@ -103,7 +104,7 @@ CppCodeModelSettingsWidget::CppCodeModelSettingsWidget(CppCodeModelSettings *s) m_ignorePchCheckBox->setChecked(m_settings->pchUsage() == CppCodeModelSettings::PchUse_None); m_useBuiltinPreprocessorCheckBox->setChecked(m_settings->useBuiltinPreprocessor()); - using namespace Utils::Layouting; + using namespace Layouting; Column { Group { @@ -192,6 +193,7 @@ class ClangdSettingsWidget::Private public: QCheckBox useClangdCheckBox; QComboBox indexingComboBox; + QComboBox headerSourceSwitchComboBox; QCheckBox autoIncludeHeadersCheckBox; QCheckBox sizeThresholdCheckBox; QSpinBox threadLimitSpinBox; @@ -220,6 +222,12 @@ ClangdSettingsWidget::ClangdSettingsWidget(const ClangdSettings::Data &settingsD "cores unused.

" "

Normal Priority: Reduced priority compared to interactive work.

" "Low Priority: Same priority as other clangd work."); + const QString headerSourceSwitchToolTip = Tr::tr( + "

Which C/C++ backend to use when switching between header and source file." + "

The clangd implementation has more capabilities, but also has some bugs not present " + "in the built-in variant." + "

When \"Try Both\" is selected, clangd will be employed only if the built-in variant " + "does not find anything."); const QString workerThreadsToolTip = Tr::tr( "Number of worker threads used by clangd. Background indexing also uses this many " "worker threads."); @@ -241,6 +249,7 @@ ClangdSettingsWidget::ClangdSettingsWidget(const ClangdSettings::Data &settingsD d->clangdChooser.setFilePath(settings.clangdFilePath()); d->clangdChooser.setAllowPathFromDevice(true); d->clangdChooser.setEnabled(d->useClangdCheckBox.isChecked()); + d->clangdChooser.setCommandVersionArguments({"--version"}); using Priority = ClangdSettings::IndexingPriority; for (Priority prio : {Priority::Off, Priority::Background, Priority::Low, Priority::Normal}) { d->indexingComboBox.addItem(ClangdSettings::priorityToDisplayString(prio), int(prio)); @@ -248,6 +257,15 @@ ClangdSettingsWidget::ClangdSettingsWidget(const ClangdSettings::Data &settingsD d->indexingComboBox.setCurrentIndex(d->indexingComboBox.count() - 1); } d->indexingComboBox.setToolTip(indexingToolTip); + using SwitchMode = ClangdSettings::HeaderSourceSwitchMode; + for (SwitchMode mode : {SwitchMode::BuiltinOnly, SwitchMode::ClangdOnly, SwitchMode::Both}) { + d->headerSourceSwitchComboBox.addItem( + ClangdSettings::headerSourceSwitchModeToDisplayString(mode), int(mode)); + if (mode == settings.headerSourceSwitchMode()) + d->headerSourceSwitchComboBox.setCurrentIndex( + d->headerSourceSwitchComboBox.count() - 1); + } + d->headerSourceSwitchComboBox.setToolTip(headerSourceSwitchToolTip); d->autoIncludeHeadersCheckBox.setText(Tr::tr("Insert header files on completion")); d->autoIncludeHeadersCheckBox.setChecked(settings.autoIncludeHeaders()); d->autoIncludeHeadersCheckBox.setToolTip(autoIncludeToolTip); @@ -293,6 +311,13 @@ ClangdSettingsWidget::ClangdSettingsWidget(const ClangdSettings::Data &settingsD indexingPriorityLabel->setToolTip(indexingToolTip); formLayout->addRow(indexingPriorityLabel, indexingPriorityLayout); + const auto headerSourceSwitchLayout = new QHBoxLayout; + headerSourceSwitchLayout->addWidget(&d->headerSourceSwitchComboBox); + headerSourceSwitchLayout->addStretch(1); + const auto headerSourceSwitchLabel = new QLabel(Tr::tr("Header/source switch mode:")); + headerSourceSwitchLabel->setToolTip(headerSourceSwitchToolTip); + formLayout->addRow(headerSourceSwitchLabel, headerSourceSwitchLayout); + const auto threadLimitLayout = new QHBoxLayout; threadLimitLayout->addWidget(&d->threadLimitSpinBox); threadLimitLayout->addStretch(1); @@ -368,7 +393,7 @@ ClangdSettingsWidget::ClangdSettingsWidget(const ClangdSettings::Data &settingsD connect(addButton, &QPushButton::clicked, this, [this, sessionsView] { QInputDialog dlg(sessionsView); - QStringList sessions = ProjectExplorer::SessionManager::sessions(); + QStringList sessions = Core::SessionManager::sessions(); QStringList currentSessions = d->sessionsModel.stringList(); for (const QString &s : std::as_const(currentSessions)) sessions.removeOne(s); @@ -400,7 +425,7 @@ ClangdSettingsWidget::ClangdSettingsWidget(const ClangdSettings::Data &settingsD else Core::EditorManager::openEditor(Utils::FilePath::fromString(link)); }); - layout->addWidget(Utils::Layouting::createHr()); + layout->addWidget(Layouting::createHr()); layout->addWidget(configFilesHelpLabel); layout->addStretch(1); @@ -448,6 +473,8 @@ ClangdSettingsWidget::ClangdSettingsWidget(const ClangdSettings::Data &settingsD this, &ClangdSettingsWidget::settingsDataChanged); connect(&d->indexingComboBox, &QComboBox::currentIndexChanged, this, &ClangdSettingsWidget::settingsDataChanged); + connect(&d->headerSourceSwitchComboBox, &QComboBox::currentIndexChanged, + this, &ClangdSettingsWidget::settingsDataChanged); connect(&d->autoIncludeHeadersCheckBox, &QCheckBox::toggled, this, &ClangdSettingsWidget::settingsDataChanged); connect(&d->threadLimitSpinBox, &QSpinBox::valueChanged, @@ -478,6 +505,8 @@ ClangdSettings::Data ClangdSettingsWidget::settingsData() const data.executableFilePath = d->clangdChooser.filePath(); data.indexingPriority = ClangdSettings::IndexingPriority( d->indexingComboBox.currentData().toInt()); + data.headerSourceSwitchMode = ClangdSettings::HeaderSourceSwitchMode( + d->headerSourceSwitchComboBox.currentData().toInt()); data.autoIncludeHeaders = d->autoIncludeHeadersCheckBox.isChecked(); data.workerThreadLimit = d->threadLimitSpinBox.value(); data.documentUpdateThreshold = d->documentUpdateThreshold.value(); diff --git a/src/plugins/cppeditor/cppcodestylesettingspage.cpp b/src/plugins/cppeditor/cppcodestylesettingspage.cpp index ebb4a37485d..84573bdeed5 100644 --- a/src/plugins/cppeditor/cppcodestylesettingspage.cpp +++ b/src/plugins/cppeditor/cppcodestylesettingspage.cpp @@ -34,6 +34,7 @@ #include #include #include +#include using namespace TextEditor; using namespace Utils; @@ -161,11 +162,8 @@ public: , m_bindStarToLeftSpecifier(createCheckBox(Tr::tr("Left const/volatile"))) , m_bindStarToRightSpecifier(createCheckBox(Tr::tr("Right const/volatile"), Tr::tr("This does not apply to references."))) - , m_categoryTab(new QTabWidget) , m_tabSettingsWidget(new TabSettingsWidget) { - m_categoryTab->setProperty("_q_custom_style_disabled", true); - QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); sizePolicy.setHorizontalStretch(0); sizePolicy.setVerticalStretch(0); @@ -175,10 +173,17 @@ public: QObject::connect(m_tabSettingsWidget, &TabSettingsWidget::settingsChanged, q, &CppCodeStylePreferencesWidget::slotTabSettingsChanged); - using namespace Utils::Layouting; + using namespace Layouting; + + QWidget *contentGroupWidget = nullptr; + QWidget *bracesGroupWidget = nullptr; + QWidget *switchGroupWidget = nullptr; + QWidget *alignmentGroupWidget = nullptr; + QWidget *typesGroupWidget = nullptr; const Group contentGroup { title(Tr::tr("Indent")), + bindTo(&contentGroupWidget), Column { m_indentAccessSpecifiers, m_indentDeclarationsRelativeToAccessSpecifiers, @@ -191,6 +196,7 @@ public: const Group bracesGroup { title(Tr::tr("Indent Braces")), + bindTo(&bracesGroupWidget), Column { m_indentClassBraces, m_indentNamespaceBraces, @@ -203,6 +209,7 @@ public: const Group switchGroup { title(Tr::tr("Indent within \"switch\"")), + bindTo(&switchGroupWidget), Column { m_indentSwitchLabels, m_indentCaseStatements, @@ -214,6 +221,7 @@ public: const Group alignmentGroup { title(Tr::tr("Align")), + bindTo(&alignmentGroupWidget), Column { m_alignAssignments, m_extraPaddingConditions, @@ -223,6 +231,7 @@ public: const Group typesGroup { title(Tr::tr("Bind '*' and '&&' in types/declarations to")), + bindTo(&typesGroupWidget), Column { m_bindStarToIdentifier, m_bindStarToTypeName, @@ -233,7 +242,8 @@ public: }; Row { - TabWidget { m_categoryTab, { + TabWidget { + bindTo(&m_categoryTab), Tab { Tr::tr("General"), Row { Column { m_tabSettingsWidget, st }, createPreview(0) } }, @@ -242,15 +252,17 @@ public: Tab { Tr::tr("\"switch\""), Row { switchGroup, createPreview(3) } }, Tab { Tr::tr("Alignment"), Row { alignmentGroup, createPreview(4) } }, Tab { Tr::tr("Pointers and References"), Row { typesGroup, createPreview(5) } } - } } + } }.attachTo(q); + m_categoryTab->setProperty("_q_custom_style_disabled", true); + m_controllers.append(m_tabSettingsWidget); - m_controllers.append(contentGroup.widget); - m_controllers.append(bracesGroup.widget); - m_controllers.append(switchGroup.widget); - m_controllers.append(alignmentGroup.widget); - m_controllers.append(typesGroup.widget); + m_controllers.append(contentGroupWidget); + m_controllers.append(bracesGroupWidget); + m_controllers.append(switchGroupWidget); + m_controllers.append(alignmentGroupWidget); + m_controllers.append(typesGroupWidget); } QCheckBox *createCheckBox(const QString &text, const QString &toolTip = {}) @@ -414,7 +426,8 @@ void CppCodeStylePreferencesWidget::setCodeStyleSettings(const CppCodeStyleSetti void CppCodeStylePreferencesWidget::slotCurrentPreferencesChanged(ICodeStylePreferences *preferences, bool preview) { - const bool enable = !preferences->isReadOnly() && !preferences->isTemporarilyReadOnly(); + const bool enable = !preferences->isReadOnly() && (!preferences->isTemporarilyReadOnly() + || preferences->isAdditionalTabDisabled()); for (QWidget *widget : d->m_controllers) widget->setEnabled(enable); @@ -548,18 +561,13 @@ void CppCodeStylePreferencesWidget::finish() emit finishEmitted(); } -// ------------------ CppCodeStyleSettingsPage +// CppCodeStyleSettingsPageWidget -CppCodeStyleSettingsPage::CppCodeStyleSettingsPage() +class CppCodeStyleSettingsPageWidget : public Core::IOptionsPageWidget { - setId(Constants::CPP_CODE_STYLE_SETTINGS_ID); - setDisplayName(Tr::tr("Code Style")); - setCategory(Constants::CPP_SETTINGS_CATEGORY); -} - -QWidget *CppCodeStyleSettingsPage::widget() -{ - if (!m_widget) { +public: + CppCodeStyleSettingsPageWidget() + { CppCodeStylePreferences *originalCodeStylePreferences = CppToolsSettings::instance() ->cppCodeStyle(); m_pageCppCodeStylePreferences = new CppCodeStylePreferences(); @@ -571,15 +579,16 @@ QWidget *CppCodeStyleSettingsPage::widget() originalCodeStylePreferences->currentDelegate()); // we set id so that it won't be possible to set delegate to the original prefs m_pageCppCodeStylePreferences->setId(originalCodeStylePreferences->id()); - m_widget = TextEditorSettings::codeStyleFactory(CppEditor::Constants::CPP_SETTINGS_ID) - ->createCodeStyleEditor(m_pageCppCodeStylePreferences); - } - return m_widget; -} -void CppCodeStyleSettingsPage::apply() -{ - if (m_widget) { + m_codeStyleEditor = TextEditorSettings::codeStyleFactory(CppEditor::Constants::CPP_SETTINGS_ID) + ->createCodeStyleEditor(m_pageCppCodeStylePreferences); + + auto hbox = new QVBoxLayout(this); + hbox->addWidget(m_codeStyleEditor); + } + + void apply() final + { QSettings *s = Core::ICore::settings(); CppCodeStylePreferences *originalCppCodeStylePreferences = CppToolsSettings::instance()->cppCodeStyle(); @@ -596,15 +605,21 @@ void CppCodeStyleSettingsPage::apply() originalCppCodeStylePreferences->toSettings(QLatin1String(CppEditor::Constants::CPP_SETTINGS_ID), s); } - m_widget->apply(); + m_codeStyleEditor->apply(); } -} -void CppCodeStyleSettingsPage::finish() + CppCodeStylePreferences *m_pageCppCodeStylePreferences = nullptr; + CodeStyleEditorWidget *m_codeStyleEditor; +}; + +// CppCodeStyleSettingsPage + +CppCodeStyleSettingsPage::CppCodeStyleSettingsPage() { - if (m_widget) - m_widget->finish(); - delete m_widget; + setId(Constants::CPP_CODE_STYLE_SETTINGS_ID); + setDisplayName(Tr::tr("Code Style")); + setCategory(Constants::CPP_SETTINGS_CATEGORY); + setWidgetCreator([] { return new CppCodeStyleSettingsPageWidget; }); } } // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/cppcodestylesettingspage.h b/src/plugins/cppeditor/cppcodestylesettingspage.h index da15c38948a..666b3ab4a29 100644 --- a/src/plugins/cppeditor/cppcodestylesettingspage.h +++ b/src/plugins/cppeditor/cppcodestylesettingspage.h @@ -86,14 +86,6 @@ class CppCodeStyleSettingsPage : public Core::IOptionsPage { public: CppCodeStyleSettingsPage(); - - QWidget *widget() override; - void apply() override; - void finish() override; - -private: - CppCodeStylePreferences *m_pageCppCodeStylePreferences = nullptr; - QPointer m_widget; }; } // namespace Internal diff --git a/src/plugins/cppeditor/cppcompletion_test.cpp b/src/plugins/cppeditor/cppcompletion_test.cpp index 78407d59599..1e212dde609 100644 --- a/src/plugins/cppeditor/cppcompletion_test.cpp +++ b/src/plugins/cppeditor/cppcompletion_test.cpp @@ -70,6 +70,10 @@ public: // Get Document const Document::Ptr document = waitForFileInGlobalSnapshot(filePath); QVERIFY(document); + if (!document->diagnosticMessages().isEmpty()) { + for (const Document::DiagnosticMessage &m : document->diagnosticMessages()) + qDebug().noquote() << m.text(); + } QVERIFY(document->diagnosticMessages().isEmpty()); m_snapshot.insert(document); @@ -409,16 +413,16 @@ static void enumTestCase(const QByteArray &tag, const QByteArray &source, const QByteArray &prefix = QByteArray()) { QByteArray fullSource = source; - fullSource.replace('$', "enum E { val1, val2, val3 };"); - QTest::newRow(tag) << fullSource << (prefix + "val") - << QStringList({"val1", "val2", "val3"}); + fullSource.replace('$', "enum E { value1, value2, value3 };"); + QTest::newRow(tag) << fullSource << (prefix + "value") + << QStringList({"value1", "value2", "value3"}); QTest::newRow(QByteArray{tag + "_cxx11"}) << fullSource << QByteArray{prefix + "E::"} - << QStringList({"E", "val1", "val2", "val3"}); + << QStringList({"E", "value1", "value2", "value3"}); fullSource.replace("enum E ", "enum "); - QTest::newRow(QByteArray{tag + "_anon"}) << fullSource << QByteArray{prefix + "val"} - << QStringList({"val1", "val2", "val3"}); + QTest::newRow(QByteArray{tag + "_anon"}) << fullSource << QByteArray{prefix + "value"} + << QStringList({"value1", "value2", "value3"}); } void CompletionTest::testCompletion_data() diff --git a/src/plugins/cppeditor/cppcompletionassist.cpp b/src/plugins/cppeditor/cppcompletionassist.cpp index e47bee37c44..5466f675eaf 100644 --- a/src/plugins/cppeditor/cppcompletionassist.cpp +++ b/src/plugins/cppeditor/cppcompletionassist.cpp @@ -1041,7 +1041,7 @@ int InternalCppCompletionAssistProcessor::startCompletionHelper() int line = 0, column = 0; Utils::Text::convertPosition(interface()->textDocument(), startOfExpression, &line, &column); return startCompletionInternal(interface()->filePath(), - line, column - 1, expression, endOfExpression); + line, column, expression, endOfExpression); } bool InternalCppCompletionAssistProcessor::tryObjCCompletion() @@ -1074,7 +1074,7 @@ bool InternalCppCompletionAssistProcessor::tryObjCCompletion() int line = 0, column = 0; Utils::Text::convertPosition(interface()->textDocument(), interface()->position(), &line, &column); - Scope *scope = thisDocument->scopeAt(line, column - 1); + Scope *scope = thisDocument->scopeAt(line, column); if (!scope) return false; @@ -1989,7 +1989,7 @@ bool InternalCppCompletionAssistProcessor::completeConstructorOrFunction(const Q int lineSigned = 0, columnSigned = 0; Utils::Text::convertPosition(interface()->textDocument(), interface()->position(), &lineSigned, &columnSigned); - unsigned line = lineSigned, column = columnSigned - 1; + unsigned line = lineSigned, column = columnSigned; // find a scope that encloses the current location, starting from the lastVisibileSymbol // and moving outwards diff --git a/src/plugins/cppeditor/cppcurrentdocumentfilter.cpp b/src/plugins/cppeditor/cppcurrentdocumentfilter.cpp deleted file mode 100644 index 0285fd71fd1..00000000000 --- a/src/plugins/cppeditor/cppcurrentdocumentfilter.cpp +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "cppcurrentdocumentfilter.h" - -#include "cppeditorconstants.h" -#include "cppeditortr.h" -#include "cppmodelmanager.h" - -#include -#include -#include - -#include - -using namespace CPlusPlus; - -namespace CppEditor::Internal { - -CppCurrentDocumentFilter::CppCurrentDocumentFilter(CppModelManager *manager) - : m_modelManager(manager) -{ - setId(Constants::CURRENT_DOCUMENT_FILTER_ID); - setDisplayName(Tr::tr(Constants::CURRENT_DOCUMENT_FILTER_DISPLAY_NAME)); - setDefaultShortcutString("."); - setPriority(High); - setDefaultIncludedByDefault(false); - - search.setSymbolsToSearchFor(SymbolSearcher::Declarations | - SymbolSearcher::Enums | - SymbolSearcher::Functions | - SymbolSearcher::Classes); - - connect(manager, &CppModelManager::documentUpdated, - this, &CppCurrentDocumentFilter::onDocumentUpdated); - connect(Core::EditorManager::instance(), &Core::EditorManager::currentEditorChanged, - this, &CppCurrentDocumentFilter::onCurrentEditorChanged); - connect(Core::EditorManager::instance(), &Core::EditorManager::editorAboutToClose, - this, &CppCurrentDocumentFilter::onEditorAboutToClose); -} - -void CppCurrentDocumentFilter::makeAuxiliary() -{ - setId({}); - setDisplayName({}); - setDefaultShortcutString({}); - setEnabled(false); - setHidden(true); -} - -QList CppCurrentDocumentFilter::matchesFor( - QFutureInterface &future, const QString & entry) -{ - QList goodEntries; - QList betterEntries; - - const QRegularExpression regexp = createRegExp(entry); - if (!regexp.isValid()) - return goodEntries; - - const QList items = itemsOfCurrentDocument(); - for (const IndexItem::Ptr &info : items) { - if (future.isCanceled()) - break; - - QString matchString = info->symbolName(); - if (info->type() == IndexItem::Declaration) - matchString = info->representDeclaration(); - else if (info->type() == IndexItem::Function) - matchString += info->symbolType(); - - QRegularExpressionMatch match = regexp.match(matchString); - if (match.hasMatch()) { - const bool betterMatch = match.capturedStart() == 0; - QVariant id = QVariant::fromValue(info); - QString name = matchString; - QString extraInfo = info->symbolScope(); - if (info->type() == IndexItem::Function) { - if (info->unqualifiedNameAndScope(matchString, &name, &extraInfo)) { - name += info->symbolType(); - match = regexp.match(name); - } - } - - Core::LocatorFilterEntry filterEntry(this, name, id, info->icon()); - filterEntry.extraInfo = extraInfo; - if (match.hasMatch()) { - filterEntry.highlightInfo = highlightInfo(match); - } else { - match = regexp.match(extraInfo); - filterEntry.highlightInfo = - highlightInfo(match, Core::LocatorFilterEntry::HighlightInfo::ExtraInfo); - } - - if (betterMatch) - betterEntries.append(filterEntry); - else - goodEntries.append(filterEntry); - } - } - - // entries are unsorted by design! - - betterEntries += goodEntries; - return betterEntries; -} - -void CppCurrentDocumentFilter::accept(const Core::LocatorFilterEntry &selection, - QString *newText, int *selectionStart, - int *selectionLength) const -{ - Q_UNUSED(newText) - Q_UNUSED(selectionStart) - Q_UNUSED(selectionLength) - IndexItem::Ptr info = qvariant_cast(selection.internalData); - Core::EditorManager::openEditorAt({info->filePath(), info->line(), info->column()}); -} - -void CppCurrentDocumentFilter::onDocumentUpdated(Document::Ptr doc) -{ - QMutexLocker locker(&m_mutex); - if (m_currentFileName == doc->filePath()) - m_itemsOfCurrentDoc.clear(); -} - -void CppCurrentDocumentFilter::onCurrentEditorChanged(Core::IEditor *currentEditor) -{ - QMutexLocker locker(&m_mutex); - if (currentEditor) - m_currentFileName = currentEditor->document()->filePath(); - else - m_currentFileName.clear(); - m_itemsOfCurrentDoc.clear(); -} - -void CppCurrentDocumentFilter::onEditorAboutToClose(Core::IEditor *editorAboutToClose) -{ - if (!editorAboutToClose) - return; - - QMutexLocker locker(&m_mutex); - if (m_currentFileName == editorAboutToClose->document()->filePath()) { - m_currentFileName.clear(); - m_itemsOfCurrentDoc.clear(); - } -} - -QList CppCurrentDocumentFilter::itemsOfCurrentDocument() -{ - QMutexLocker locker(&m_mutex); - - if (m_currentFileName.isEmpty()) - return QList(); - - if (m_itemsOfCurrentDoc.isEmpty()) { - const Snapshot snapshot = m_modelManager->snapshot(); - if (const Document::Ptr thisDocument = snapshot.document(m_currentFileName)) { - IndexItem::Ptr rootNode = search(thisDocument); - rootNode->visitAllChildren([&](const IndexItem::Ptr &info) -> IndexItem::VisitorResult { - m_itemsOfCurrentDoc.append(info); - return IndexItem::Recurse; - }); - } - } - - return m_itemsOfCurrentDoc; -} - -} // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/cppcurrentdocumentfilter.h b/src/plugins/cppeditor/cppcurrentdocumentfilter.h deleted file mode 100644 index 484812a0cba..00000000000 --- a/src/plugins/cppeditor/cppcurrentdocumentfilter.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "searchsymbols.h" - -#include - -namespace Core { class IEditor; } - -namespace CppEditor { - -class CppModelManager; - -namespace Internal { - -class CppCurrentDocumentFilter : public Core::ILocatorFilter -{ - Q_OBJECT - -public: - explicit CppCurrentDocumentFilter(CppModelManager *manager); - ~CppCurrentDocumentFilter() override = default; - - void makeAuxiliary(); - - QList matchesFor(QFutureInterface &future, - const QString &entry) override; - void accept(const Core::LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const override; - -private: - void onDocumentUpdated(CPlusPlus::Document::Ptr doc); - void onCurrentEditorChanged(Core::IEditor *currentEditor); - void onEditorAboutToClose(Core::IEditor *currentEditor); - - QList itemsOfCurrentDocument(); - - CppModelManager * m_modelManager; - SearchSymbols search; - - mutable QMutex m_mutex; - Utils::FilePath m_currentFileName; - QList m_itemsOfCurrentDoc; -}; - -} // namespace Internal -} // namespace CppEditor diff --git a/src/plugins/cppeditor/cppeditor.qbs b/src/plugins/cppeditor/cppeditor.qbs index cb7b145078c..b7d2aea0314 100644 --- a/src/plugins/cppeditor/cppeditor.qbs +++ b/src/plugins/cppeditor/cppeditor.qbs @@ -82,8 +82,6 @@ QtcPlugin { "cppcompletionassistprocessor.h", "cppcompletionassistprovider.cpp", "cppcompletionassistprovider.h", - "cppcurrentdocumentfilter.cpp", - "cppcurrentdocumentfilter.h", "cppcursorinfo.h", "cppdoxygen.cpp", "cppdoxygen.h", @@ -91,7 +89,8 @@ QtcPlugin { "cppeditorwidget.cpp", "cppeditorwidget.h", "cppeditor.qrc", - "cppeditor_global.h", "cppeditortr.h", + "cppeditor_global.h", + "cppeditortr.h", "cppeditorconstants.h", "cppeditordocument.cpp", "cppeditordocument.h", @@ -224,8 +223,6 @@ QtcPlugin { "searchsymbols.h", "semantichighlighter.cpp", "semantichighlighter.h", - "senddocumenttracker.cpp", - "senddocumenttracker.h", "symbolfinder.cpp", "symbolfinder.h", "symbolsfindfilter.cpp", @@ -245,9 +242,7 @@ QtcPlugin { ] } - Group { - name: "Tests" - condition: qtc.testsEnabled + QtcTestFiles { cpp.defines: outer.concat(['SRCDIR="' + FileInfo.path(filePath) + '"']) files: [ "compileroptionsbuilder_test.cpp", diff --git a/src/plugins/cppeditor/cppeditorconstants.h b/src/plugins/cppeditor/cppeditorconstants.h index 3c87aa6d63b..179c3b3b9b0 100644 --- a/src/plugins/cppeditor/cppeditorconstants.h +++ b/src/plugins/cppeditor/cppeditorconstants.h @@ -110,12 +110,18 @@ const char CPP_SETTINGS_NAME[] = QT_TRANSLATE_NOOP("QtC::CppEditor", "C++"); const char CURRENT_DOCUMENT_FILTER_ID[] = "Methods in current Document"; const char CURRENT_DOCUMENT_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("QtC::CppEditor", "C++ Symbols in Current Document"); +const char CURRENT_DOCUMENT_FILTER_DESCRIPTION[] + = QT_TRANSLATE_NOOP("QtC::CppEditor", "Locates C++ symbols in the current document."); const char CLASSES_FILTER_ID[] = "Classes"; const char CLASSES_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("QtC::CppEditor", "C++ Classes"); +const char CLASSES_FILTER_DESCRIPTION[] + = QT_TRANSLATE_NOOP("QtC::CppEditor", "Locates C++ classes in any open project."); const char FUNCTIONS_FILTER_ID[] = "Methods"; const char FUNCTIONS_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("QtC::CppEditor", "C++ Functions"); +const char FUNCTIONS_FILTER_DESCRIPTION[] + = QT_TRANSLATE_NOOP("QtC::CppEditor", "Locates C++ functions in any open project."); const char INCLUDES_FILTER_ID[] = "All Included C/C++ Files"; const char INCLUDES_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("QtC::CppEditor", @@ -124,6 +130,8 @@ const char INCLUDES_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("QtC::CppEditor", const char LOCATOR_FILTER_ID[] = "Classes and Methods"; const char LOCATOR_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("QtC::CppEditor", "C++ Classes, Enums, Functions and Type Aliases"); +const char LOCATOR_FILTER_DESCRIPTION[] = QT_TRANSLATE_NOOP( + "QtC::CppEditor", "Locates C++ classes, enums, functions and type aliases in any open project."); const char SYMBOLS_FIND_FILTER_ID[] = "Symbols"; const char SYMBOLS_FIND_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("QtC::CppEditor", "C++ Symbols"); diff --git a/src/plugins/cppeditor/cppeditordocument.cpp b/src/plugins/cppeditor/cppeditordocument.cpp index 7db63ec7afb..328b2c23460 100644 --- a/src/plugins/cppeditor/cppeditordocument.cpp +++ b/src/plugins/cppeditor/cppeditordocument.cpp @@ -16,8 +16,7 @@ #include "cppquickfixassistant.h" #include - -#include +#include #include #include @@ -217,7 +216,7 @@ void CppEditorDocument::reparseWithPreferredParseContext(const QString &parseCon // Remember the setting const QString key = Constants::PREFERRED_PARSE_CONTEXT + filePath().toString(); - ProjectExplorer::SessionManager::setValue(key, parseContextId); + Core::SessionManager::setValue(key, parseContextId); // Reprocess scheduleProcessDocument(); @@ -284,7 +283,7 @@ void CppEditorDocument::applyPreferredParseContextFromSettings() return; const QString key = Constants::PREFERRED_PARSE_CONTEXT + filePath().toString(); - const QString parseContextId = ProjectExplorer::SessionManager::value(key).toString(); + const QString parseContextId = Core::SessionManager::value(key).toString(); setPreferredParseContext(parseContextId); } @@ -295,7 +294,7 @@ void CppEditorDocument::applyExtraPreprocessorDirectivesFromSettings() return; const QString key = Constants::EXTRA_PREPROCESSOR_DIRECTIVES + filePath().toString(); - const QByteArray directives = ProjectExplorer::SessionManager::value(key).toString().toUtf8(); + const QByteArray directives = Core::SessionManager::value(key).toString().toUtf8(); setExtraPreprocessorDirectives(directives); } diff --git a/src/plugins/cppeditor/cppeditoroutline.cpp b/src/plugins/cppeditor/cppeditoroutline.cpp index b0d14ca1192..a34d2543612 100644 --- a/src/plugins/cppeditor/cppeditoroutline.cpp +++ b/src/plugins/cppeditor/cppeditoroutline.cpp @@ -14,7 +14,6 @@ #include #include -#include #include #include diff --git a/src/plugins/cppeditor/cppeditorplugin.cpp b/src/plugins/cppeditor/cppeditorplugin.cpp index f1fc63b49ab..5791a5cb62a 100644 --- a/src/plugins/cppeditor/cppeditorplugin.cpp +++ b/src/plugins/cppeditor/cppeditorplugin.cpp @@ -49,7 +49,6 @@ #include "functionutils.h" #include "includeutils.h" #include "projectinfo_test.h" -#include "senddocumenttracker.h" #include "symbolsearcher_test.h" #include "typehierarchybuilder_test.h" #endif @@ -431,6 +430,10 @@ void CppEditorPlugin::initialize() contextMenu->addAction(cmd, Constants::G_CONTEXT_FIRST); cppToolsMenu->addAction(cmd); + cmd = ActionManager::command(TextEditor::Constants::OPEN_CALL_HIERARCHY); + contextMenu->addAction(cmd, Constants::G_CONTEXT_FIRST); + cppToolsMenu->addAction(cmd); + // Refactoring sub-menu Command *sep = contextMenu->addSeparator(); sep->action()->setObjectName(QLatin1String(Constants::M_REFACTORING_MENU_INSERTION_POINT)); @@ -482,7 +485,6 @@ void CppEditorPlugin::initialize() addTest(); addTest(); addTest(); - addTest(); addTest(); addTest(); addTest(); diff --git a/src/plugins/cppeditor/cppeditorwidget.cpp b/src/plugins/cppeditor/cppeditorwidget.cpp index 0430f210a0b..edc65f76526 100644 --- a/src/plugins/cppeditor/cppeditorwidget.cpp +++ b/src/plugins/cppeditor/cppeditorwidget.cpp @@ -30,9 +30,9 @@ #include #include +#include #include #include -#include #include #include @@ -56,6 +56,7 @@ #include #include #include +#include #include #include @@ -684,13 +685,13 @@ void CppEditorWidget::updateWidgetHighlighting(QWidget *widget, bool highlight) if (!widget) return; - widget->setProperty("highlightWidget", highlight); + widget->setProperty(StyleHelper::C_HIGHLIGHT_WIDGET, highlight); widget->update(); } bool CppEditorWidget::isWidgetHighlighted(QWidget *widget) { - return widget ? widget->property("highlightWidget").toBool() : false; + return widget ? widget->property(StyleHelper::C_HIGHLIGHT_WIDGET).toBool() : false; } namespace { @@ -755,7 +756,7 @@ void CppEditorWidget::showRenameWarningIfFileIsGenerated(const Utils::FilePath & { if (filePath.isEmpty()) return; - for (const Project * const project : SessionManager::projects()) { + for (const Project * const project : ProjectManager::projects()) { const Node * const node = project->nodeForFilePath(filePath); if (!node) continue; @@ -779,11 +780,11 @@ void CppEditorWidget::showRenameWarningIfFileIsGenerated(const Utils::FilePath & static const Id infoId("cppeditor.renameWarning"); InfoBarEntry info(infoId, warning); if (ec) { - info.addCustomButton(CppEditor::Tr::tr("Open %1").arg(ec->source().fileName()), - [source = ec->source()] { - EditorManager::openEditor(source); - ICore::infoBar()->removeInfo(infoId); - }); + info.addCustomButton(CppEditor::Tr::tr("Open \"%1\"").arg(ec->source().fileName()), + [source = ec->source()] { + EditorManager::openEditor(source); + ICore::infoBar()->removeInfo(infoId); + }); } ICore::infoBar()->addInfo(info); return; @@ -989,7 +990,7 @@ void CppEditorWidget::findLinkAt(const QTextCursor &cursor, const QString fileName = filePath.fileName(); if (fileName.startsWith("ui_") && fileName.endsWith(".h")) { const QString uiFileName = fileName.mid(3, fileName.length() - 4) + "ui"; - for (const Project * const project : SessionManager::projects()) { + for (const Project * const project : ProjectManager::projects()) { const auto nodeMatcher = [uiFileName](Node *n) { return n->filePath().fileName() == uiFileName; }; diff --git a/src/plugins/cppeditor/cppelementevaluator.cpp b/src/plugins/cppeditor/cppelementevaluator.cpp index dabb8cffc0d..27b481cd0c7 100644 --- a/src/plugins/cppeditor/cppelementevaluator.cpp +++ b/src/plugins/cppeditor/cppelementevaluator.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include #include #include @@ -140,20 +140,20 @@ CppClass *CppClass::toCppClass() return this; } -void CppClass::lookupBases(QFutureInterfaceBase &futureInterface, - Symbol *declaration, const LookupContext &context) +void CppClass::lookupBases(const QFuture &future, Symbol *declaration, + const LookupContext &context) { ClassOrNamespace *hierarchy = context.lookupType(declaration); if (!hierarchy) return; QSet visited; - addBaseHierarchy(futureInterface, context, hierarchy, &visited); + addBaseHierarchy(future, context, hierarchy, &visited); } -void CppClass::addBaseHierarchy(QFutureInterfaceBase &futureInterface, const LookupContext &context, +void CppClass::addBaseHierarchy(const QFuture &future, const LookupContext &context, ClassOrNamespace *hierarchy, QSet *visited) { - if (futureInterface.isCanceled()) + if (future.isCanceled()) return; visited->insert(hierarchy); const QList &baseClasses = hierarchy->usings(); @@ -165,21 +165,21 @@ void CppClass::addBaseHierarchy(QFutureInterfaceBase &futureInterface, const Loo ClassOrNamespace *baseHierarchy = context.lookupType(symbol); if (baseHierarchy && !visited->contains(baseHierarchy)) { CppClass classSymbol(symbol); - classSymbol.addBaseHierarchy(futureInterface, context, baseHierarchy, visited); + classSymbol.addBaseHierarchy(future, context, baseHierarchy, visited); bases.append(classSymbol); } } } } -void CppClass::lookupDerived(QFutureInterfaceBase &futureInterface, - Symbol *declaration, const Snapshot &snapshot) +void CppClass::lookupDerived(const QFuture &future, Symbol *declaration, + const Snapshot &snapshot) { - snapshot.updateDependencyTable(futureInterface); - if (futureInterface.isCanceled()) + snapshot.updateDependencyTable(future); + if (future.isCanceled()) return; addDerivedHierarchy(TypeHierarchyBuilder::buildDerivedTypeHierarchy( - futureInterface, declaration, snapshot)); + declaration, snapshot, future)); } void CppClass::addDerivedHierarchy(const TypeHierarchy &hierarchy) @@ -340,13 +340,13 @@ static Symbol *followTemplateAsClass(Symbol *symbol) return symbol; } -static void createTypeHierarchy(QFutureInterface> &futureInterface, +static void createTypeHierarchy(QPromise> &promise, const Snapshot &snapshot, const LookupItem &lookupItem, const LookupContext &context, SymbolFinder symbolFinder) { - if (futureInterface.isCanceled()) + if (promise.isCanceled()) return; Symbol *declaration = lookupItem.declaration(); @@ -360,16 +360,17 @@ static void createTypeHierarchy(QFutureInterface> &fu declaration = followClassDeclaration(declaration, snapshot, symbolFinder, &contextToUse); declaration = followTemplateAsClass(declaration); - if (futureInterface.isCanceled()) + if (promise.isCanceled()) return; QSharedPointer cppClass(new CppClass(declaration)); - cppClass->lookupBases(futureInterface, declaration, contextToUse); - if (futureInterface.isCanceled()) + const QFuture future = QFuture(promise.future()); + cppClass->lookupBases(future, declaration, contextToUse); + if (promise.isCanceled()) return; - cppClass->lookupDerived(futureInterface, declaration, snapshot); - if (futureInterface.isCanceled()) + cppClass->lookupDerived(future, declaration, snapshot); + if (promise.isCanceled()) return; - futureInterface.reportResult(cppClass); + promise.addResult(cppClass); } static QSharedPointer handleLookupItemMatch(const Snapshot &snapshot, @@ -495,7 +496,7 @@ static QFuture> asyncExec( const CPlusPlus::Snapshot &snapshot, const CPlusPlus::LookupItem &lookupItem, const CPlusPlus::LookupContext &lookupContext) { - return Utils::runAsync(&createTypeHierarchy, snapshot, lookupItem, lookupContext, + return Utils::asyncRun(&createTypeHierarchy, snapshot, lookupItem, lookupContext, *CppModelManager::instance()->symbolFinder()); } @@ -561,7 +562,7 @@ public: expression = expressionUnderCursor(m_tc); // Fetch the expression's code - *scope = doc->scopeAt(line, column - 1); + *scope = doc->scopeAt(line, column); return true; } QFuture> syncExec(const CPlusPlus::Snapshot &, diff --git a/src/plugins/cppeditor/cppelementevaluator.h b/src/plugins/cppeditor/cppelementevaluator.h index 7d03df67121..47ddcbeae19 100644 --- a/src/plugins/cppeditor/cppelementevaluator.h +++ b/src/plugins/cppeditor/cppelementevaluator.h @@ -92,16 +92,16 @@ public: CppClass *toCppClass() final; - void lookupBases(QFutureInterfaceBase &futureInterface, - CPlusPlus::Symbol *declaration, const CPlusPlus::LookupContext &context); - void lookupDerived(QFutureInterfaceBase &futureInterface, - CPlusPlus::Symbol *declaration, const CPlusPlus::Snapshot &snapshot); + void lookupBases(const QFuture &future, CPlusPlus::Symbol *declaration, + const CPlusPlus::LookupContext &context); + void lookupDerived(const QFuture &future, CPlusPlus::Symbol *declaration, + const CPlusPlus::Snapshot &snapshot); QList bases; QList derived; private: - void addBaseHierarchy(QFutureInterfaceBase &futureInterface, + void addBaseHierarchy(const QFuture &future, const CPlusPlus::LookupContext &context, CPlusPlus::ClassOrNamespace *hierarchy, QSet *visited); diff --git a/src/plugins/cppeditor/cppfindreferences.cpp b/src/plugins/cppeditor/cppfindreferences.cpp index 940eb358e93..5da22854386 100644 --- a/src/plugins/cppeditor/cppfindreferences.cpp +++ b/src/plugins/cppeditor/cppfindreferences.cpp @@ -10,25 +10,27 @@ #include "cpptoolsreuse.h" #include "cppworkingcopy.h" +#include + #include #include #include #include + #include +#include #include #include -#include + #include #include +#include #include -#include #include -#include #include #include -#include #include #include @@ -103,8 +105,8 @@ namespace Internal { static QByteArray getSource(const Utils::FilePath &fileName, const WorkingCopy &workingCopy) { - if (workingCopy.contains(fileName)) { - return workingCopy.source(fileName); + if (const auto source = workingCopy.source(fileName)) { + return *source; } else { QString fileContents; Utils::TextFileFormat format; @@ -218,7 +220,7 @@ class ProcessFile const CPlusPlus::Snapshot snapshot; CPlusPlus::Document::Ptr symbolDocument; CPlusPlus::Symbol *symbol; - QFutureInterface *future; + QPromise *m_promise; const bool categorize; public: @@ -230,22 +232,21 @@ public: const CPlusPlus::Snapshot snapshot, CPlusPlus::Document::Ptr symbolDocument, CPlusPlus::Symbol *symbol, - QFutureInterface *future, + QPromise *promise, bool categorize) : workingCopy(workingCopy), snapshot(snapshot), symbolDocument(symbolDocument), symbol(symbol), - future(future), + m_promise(promise), categorize(categorize) { } QList operator()(const Utils::FilePath &filePath) { QList usages; - if (future->isPaused()) - future->waitForResume(); - if (future->isCanceled()) + m_promise->suspendIfRequested(); + if (m_promise->isCanceled()) return usages; const CPlusPlus::Identifier *symbolId = symbol->identifier(); @@ -275,25 +276,24 @@ public: usages = process.usages(); } - if (future->isPaused()) - future->waitForResume(); + m_promise->suspendIfRequested(); return usages; } }; class UpdateUI { - QFutureInterface *future; + QPromise *m_promise; public: - explicit UpdateUI(QFutureInterface *future): future(future) {} + explicit UpdateUI(QPromise *promise): m_promise(promise) {} void operator()(QList &, const QList &usages) { for (const CPlusPlus::Usage &u : usages) - future->reportResult(u); + m_promise->addResult(u); - future->setProgressValue(future->progressValue() + 1); + m_promise->setProgressValue(m_promise->future().progressValue() + 1); } }; @@ -319,7 +319,7 @@ QList CppFindReferences::references(CPlusPlus::Symbol *symbol, return references; } -static void find_helper(QFutureInterface &future, +static void find_helper(QPromise &promise, const WorkingCopy workingCopy, const CPlusPlus::LookupContext &context, CPlusPlus::Symbol *symbol, @@ -353,16 +353,16 @@ static void find_helper(QFutureInterface &future, } files = Utils::filteredUnique(files); - future.setProgressRange(0, files.size()); + promise.setProgressRange(0, files.size()); - ProcessFile process(workingCopy, snapshot, context.thisDocument(), symbol, &future, categorize); - UpdateUI reduce(&future); + ProcessFile process(workingCopy, snapshot, context.thisDocument(), symbol, &promise, categorize); + UpdateUI reduce(&promise); // This thread waits for blockingMappedReduced to finish, so reduce the pool's used thread count // so the blockingMappedReduced can use one more thread, and increase it again afterwards. QThreadPool::globalInstance()->releaseThread(); QtConcurrent::blockingMappedReduced > (files, process, reduce); QThreadPool::globalInstance()->reserveThread(); - future.setProgressValue(files.size()); + promise.setProgressValue(files.size()); } void CppFindReferences::findUsages(CPlusPlus::Symbol *symbol, @@ -437,7 +437,7 @@ void CppFindReferences::findAll_helper(SearchResult *search, CPlusPlus::Symbol * SearchResultWindow::instance()->popup(IOutputPane::ModeSwitch | IOutputPane::WithFocus); const WorkingCopy workingCopy = m_modelManager->workingCopy(); QFuture result; - result = Utils::runAsync(m_modelManager->sharedThreadPool(), find_helper, + result = Utils::asyncRun(m_modelManager->sharedThreadPool(), find_helper, workingCopy, context, symbol, categorize); createWatcher(result, search); @@ -456,10 +456,8 @@ void CppFindReferences::setupSearch(Core::SearchResult *search) std::bind(&CppFindReferences::onReplaceButtonClicked, this, search, _1, _2, _3)); } -void CppFindReferences::onReplaceButtonClicked(Core::SearchResult *search, - const QString &text, - const QList &items, - bool preserveCase) +void CppFindReferences::onReplaceButtonClicked(Core::SearchResult *search, const QString &text, + const SearchResultItems &items, bool preserveCase) { const Utils::FilePaths filePaths = TextEditor::BaseFileFind::replaceAll(text, items, preserveCase); if (!filePaths.isEmpty()) { @@ -577,7 +575,7 @@ static void displayResults(SearchResult *search, item.setStyle(colorStyleForUsageType(result.tags)); item.setUseTextEditorFont(true); if (search->supportsReplace()) - item.setSelectForReplacement(SessionManager::projectForFile(result.path)); + item.setSelectForReplacement(ProjectManager::projectForFile(result.path)); search->addResult(item); if (parameters.prettySymbolName.isEmpty()) @@ -586,7 +584,7 @@ static void displayResults(SearchResult *search, if (parameters.filesToRename.contains(result.path)) continue; - if (!SessionManager::projectForFile(result.path)) + if (!ProjectManager::projectForFile(result.path)) continue; if (result.path.baseName().compare(parameters.prettySymbolName, Qt::CaseInsensitive) == 0) @@ -623,7 +621,7 @@ class FindMacroUsesInFile const WorkingCopy workingCopy; const CPlusPlus::Snapshot snapshot; const CPlusPlus::Macro ¯o; - QFutureInterface *future; + QPromise *m_promise; public: // needed by QtConcurrent @@ -633,8 +631,8 @@ public: FindMacroUsesInFile(const WorkingCopy &workingCopy, const CPlusPlus::Snapshot snapshot, const CPlusPlus::Macro ¯o, - QFutureInterface *future) - : workingCopy(workingCopy), snapshot(snapshot), macro(macro), future(future) + QPromise *promise) + : workingCopy(workingCopy), snapshot(snapshot), macro(macro), m_promise(promise) { } QList operator()(const Utils::FilePath &fileName) @@ -644,9 +642,8 @@ public: QByteArray source; restart_search: - if (future->isPaused()) - future->waitForResume(); - if (future->isCanceled()) + m_promise->suspendIfRequested(); + if (m_promise->isCanceled()) return usages; usages.clear(); @@ -674,8 +671,7 @@ restart_search: } } - if (future->isPaused()) - future->waitForResume(); + m_promise->suspendIfRequested(); return usages; } @@ -704,7 +700,7 @@ restart_search: } // end of anonymous namespace -static void findMacroUses_helper(QFutureInterface &future, +static void findMacroUses_helper(QPromise &promise, const WorkingCopy workingCopy, const CPlusPlus::Snapshot snapshot, const CPlusPlus::Macro macro) @@ -713,15 +709,15 @@ static void findMacroUses_helper(QFutureInterface &future, FilePaths files{sourceFile}; files = Utils::filteredUnique(files + snapshot.filesDependingOn(sourceFile)); - future.setProgressRange(0, files.size()); - FindMacroUsesInFile process(workingCopy, snapshot, macro, &future); - UpdateUI reduce(&future); + promise.setProgressRange(0, files.size()); + FindMacroUsesInFile process(workingCopy, snapshot, macro, &promise); + UpdateUI reduce(&promise); // This thread waits for blockingMappedReduced to finish, so reduce the pool's used thread count // so the blockingMappedReduced can use one more thread, and increase it again afterwards. QThreadPool::globalInstance()->releaseThread(); QtConcurrent::blockingMappedReduced > (files, process, reduce); QThreadPool::globalInstance()->reserveThread(); - future.setProgressValue(files.size()); + promise.setProgressValue(files.size()); } void CppFindReferences::findMacroUses(const CPlusPlus::Macro ¯o) @@ -745,10 +741,9 @@ void CppFindReferences::findMacroUses(const CPlusPlus::Macro ¯o, const QStri setupSearch(search); SearchResultWindow::instance()->popup(IOutputPane::ModeSwitch | IOutputPane::WithFocus); - connect(search, &SearchResult::activated, - [](const Core::SearchResultItem& item) { - Core::EditorManager::openEditorAtSearchResult(item); - }); + connect(search, &SearchResult::activated, search, [](const SearchResultItem &item) { + Core::EditorManager::openEditorAtSearchResult(item); + }); const CPlusPlus::Snapshot snapshot = m_modelManager->snapshot(); const WorkingCopy workingCopy = m_modelManager->workingCopy(); @@ -766,12 +761,12 @@ void CppFindReferences::findMacroUses(const CPlusPlus::Macro ¯o, const QStri item.setMainRange(macro.line(), column, macro.nameToQString().length()); item.setUseTextEditorFont(true); if (search->supportsReplace()) - item.setSelectForReplacement(SessionManager::projectForFile(filePath)); + item.setSelectForReplacement(ProjectManager::projectForFile(filePath)); search->addResult(item); } QFuture result; - result = Utils::runAsync(m_modelManager->sharedThreadPool(), findMacroUses_helper, + result = Utils::asyncRun(m_modelManager->sharedThreadPool(), findMacroUses_helper, workingCopy, snapshot, macro); createWatcher(result, search); @@ -832,7 +827,7 @@ void CppFindReferences::checkUnused(Core::SearchResult *search, const Link &link }); connect(search, &SearchResult::canceled, watcher, [watcher] { watcher->cancel(); }); connect(search, &SearchResult::destroyed, watcher, [watcher] { watcher->cancel(); }); - watcher->setFuture(Utils::runAsync(m_modelManager->sharedThreadPool(), find_helper, + watcher->setFuture(Utils::asyncRun(m_modelManager->sharedThreadPool(), find_helper, m_modelManager->workingCopy(), context, symbol, true)); } diff --git a/src/plugins/cppeditor/cppfindreferences.h b/src/plugins/cppeditor/cppfindreferences.h index 54f40bf4cfe..b05ce723c4e 100644 --- a/src/plugins/cppeditor/cppfindreferences.h +++ b/src/plugins/cppeditor/cppfindreferences.h @@ -9,30 +9,29 @@ #include #include #include +#include -#include -#include #include +#include #include -QT_FORWARD_DECLARE_CLASS(QTimer) +QT_BEGIN_NAMESPACE +class QTimer; +QT_END_NAMESPACE -namespace Core { -class SearchResultItem; -class SearchResult; -} // namespace Core +namespace Core { class SearchResult; } namespace CppEditor { class CppModelManager; -Core::SearchResultColor::Style CPPEDITOR_EXPORT +Utils::SearchResultColor::Style CPPEDITOR_EXPORT colorStyleForUsageType(CPlusPlus::Usage::Tags tags); class CPPEDITOR_EXPORT CppSearchResultFilter : public Core::SearchResultFilter { QWidget *createWidget() override; - bool matches(const Core::SearchResultItem &item) const override; + bool matches(const Utils::SearchResultItem &item) const override; void setValue(bool &member, bool value); @@ -79,7 +78,7 @@ public: private: void setupSearch(Core::SearchResult *search); void onReplaceButtonClicked(Core::SearchResult *search, const QString &text, - const QList &items, bool preserveCase); + const Utils::SearchResultItems &items, bool preserveCase); void searchAgain(Core::SearchResult *search); void findUsages(CPlusPlus::Symbol *symbol, const CPlusPlus::LookupContext &context, diff --git a/src/plugins/cppeditor/cppfollowsymbolundercursor.cpp b/src/plugins/cppeditor/cppfollowsymbolundercursor.cpp index d9ac0da2003..0353e16c302 100644 --- a/src/plugins/cppeditor/cppfollowsymbolundercursor.cpp +++ b/src/plugins/cppeditor/cppfollowsymbolundercursor.cpp @@ -485,7 +485,6 @@ void FollowSymbolUnderCursor::findLink( int line = 0; int column = 0; Utils::Text::convertPosition(document, cursor.position(), &line, &column); - const int positionInBlock = column - 1; Snapshot snapshot = theSnapshot; @@ -530,7 +529,7 @@ void FollowSymbolUnderCursor::findLink( for (int i = 0; i < tokens.size(); ++i) { const Token &tk = tokens.at(i); - if (positionInBlock >= tk.utf16charsBegin() && positionInBlock < tk.utf16charsEnd()) { + if (column >= tk.utf16charsBegin() && column < tk.utf16charsEnd()) { int closingParenthesisPos = tokens.size(); if (i >= 2 && tokens.at(i).is(T_IDENTIFIER) && tokens.at(i - 1).is(T_LPAREN) && (tokens.at(i - 2).is(T_SIGNAL) || tokens.at(i - 2).is(T_SLOT))) { @@ -572,7 +571,7 @@ void FollowSymbolUnderCursor::findLink( // In this case we want to look at one token before the current position to recognize // an operator if the cursor is inside the actual operator: operator[$] - if (positionInBlock >= tk.utf16charsBegin() && positionInBlock <= tk.utf16charsEnd()) { + if (column >= tk.utf16charsBegin() && column <= tk.utf16charsEnd()) { cursorRegionReached = true; if (tk.is(T_OPERATOR)) { link = attemptDeclDef(cursor, theSnapshot, @@ -582,7 +581,7 @@ void FollowSymbolUnderCursor::findLink( } else if (tk.isPunctuationOrOperator() && i > 0 && tokens.at(i - 1).is(T_OPERATOR)) { QTextCursor c = cursor; c.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, - positionInBlock - tokens.at(i - 1).utf16charsBegin()); + column - tokens.at(i - 1).utf16charsBegin()); link = attemptDeclDef(c, theSnapshot, documentFromSemanticInfo, symbolFinder); if (link.hasValidLinkText()) return processLinkCallback(link); @@ -662,7 +661,7 @@ void FollowSymbolUnderCursor::findLink( } // Find the last symbol up to the cursor position - Scope *scope = doc->scopeAt(line, positionInBlock); + Scope *scope = doc->scopeAt(line, column); if (!scope) return processLinkCallback(link); @@ -684,7 +683,7 @@ void FollowSymbolUnderCursor::findLink( if (Symbol *d = r.declaration()) { if (d->asDeclaration() || d->asFunction()) { if (data.filePath() == d->filePath()) { - if (line == d->line() && positionInBlock >= d->column()) { + if (line == d->line() && column >= d->column()) { // TODO: check the end result = r; // take the symbol under cursor. break; @@ -697,7 +696,7 @@ void FollowSymbolUnderCursor::findLink( &tokenBeginColumnNumber); if (tokenBeginLineNumber > d->line() || (tokenBeginLineNumber == d->line() - && tokenBeginColumnNumber >= d->column())) { + && tokenBeginColumnNumber + 1 >= d->column())) { result = r; // take the symbol under cursor. break; } diff --git a/src/plugins/cppeditor/cppfunctiondecldeflink.cpp b/src/plugins/cppeditor/cppfunctiondecldeflink.cpp index fa271108cf0..b43e87758e2 100644 --- a/src/plugins/cppeditor/cppfunctiondecldeflink.cpp +++ b/src/plugins/cppeditor/cppfunctiondecldeflink.cpp @@ -21,9 +21,9 @@ #include #include +#include #include #include -#include #include #include @@ -232,7 +232,7 @@ void FunctionDeclDefLinkFinder::startFindLinkAt( // handle the rest in a thread m_watcher.reset(new QFutureWatcher >()); connect(m_watcher.data(), &QFutureWatcherBase::finished, this, &FunctionDeclDefLinkFinder::onFutureDone); - m_watcher->setFuture(Utils::runAsync(findLinkHelper, result, refactoringChanges)); + m_watcher->setFuture(Utils::asyncRun(findLinkHelper, result, refactoringChanges)); } bool FunctionDeclDefLink::isValid() const diff --git a/src/plugins/cppeditor/cpphighlighter.cpp b/src/plugins/cppeditor/cpphighlighter.cpp index 1f83b317ada..f5ef94efdee 100644 --- a/src/plugins/cppeditor/cpphighlighter.cpp +++ b/src/plugins/cppeditor/cpphighlighter.cpp @@ -76,6 +76,7 @@ void CppHighlighter::highlightBlock(const QString &text) setFormat(0, text.length(), formatForCategory(C_VISUAL_WHITESPACE)); } TextDocumentLayout::setFoldingIndent(currentBlock(), foldingIndent); + TextDocumentLayout::setExpectedRawStringSuffix(currentBlock(), inheritedRawStringSuffix); return; } @@ -161,10 +162,8 @@ void CppHighlighter::highlightBlock(const QString &text) } else if (tk.is(T_NUMERIC_LITERAL)) { setFormat(tk.utf16charsBegin(), tk.utf16chars(), formatForCategory(C_NUMBER)); } else if (tk.isStringLiteral() || tk.isCharLiteral()) { - if (!highlightRawStringLiteral(text, tk, QString::fromUtf8(inheritedRawStringSuffix))) { - setFormatWithSpaces(text, tk.utf16charsBegin(), tk.utf16chars(), - formatForCategory(C_STRING)); - } + if (!highlightRawStringLiteral(text, tk, QString::fromUtf8(inheritedRawStringSuffix))) + highlightStringLiteral(text, tk); } else if (tk.isComment()) { const int startPosition = initialLexerState ? previousTokenEnd : tk.utf16charsBegin(); if (tk.is(T_COMMENT) || tk.is(T_CPP_COMMENT)) { @@ -413,8 +412,18 @@ bool CppHighlighter::highlightRawStringLiteral(QStringView text, const Token &tk stringOffset = delimiterOffset + delimiter.length() + 1; stringLength -= delimiter.length() + 1; }(); - if (text.mid(tk.utf16charsBegin(), tk.utf16chars()).endsWith(expectedSuffix)) { - endDelimiterOffset = tk.utf16charsBegin() + tk.utf16chars() - expectedSuffix.size(); + int operatorOffset = tk.utf16charsBegin() + tk.utf16chars(); + int operatorLength = 0; + if (tk.f.userDefinedLiteral) { + const int closingQuoteOffset = text.lastIndexOf('"', operatorOffset); + QTC_ASSERT(closingQuoteOffset >= tk.utf16charsBegin(), return false); + operatorOffset = closingQuoteOffset + 1; + operatorLength = tk.utf16charsBegin() + tk.utf16chars() - operatorOffset; + stringLength -= operatorLength; + } + if (text.mid(tk.utf16charsBegin(), operatorOffset - tk.utf16charsBegin()) + .endsWith(expectedSuffix)) { + endDelimiterOffset = operatorOffset - expectedSuffix.size(); stringLength -= expectedSuffix.size(); } @@ -422,13 +431,52 @@ bool CppHighlighter::highlightRawStringLiteral(QStringView text, const Token &tk // a string, and the rest (including the delimiter) as a keyword. const QTextCharFormat delimiterFormat = formatForCategory(C_KEYWORD); if (delimiterOffset != -1) - setFormat(tk.utf16charsBegin(), stringOffset, delimiterFormat); + setFormat(tk.utf16charsBegin(), stringOffset - tk.utf16charsBegin(), delimiterFormat); setFormatWithSpaces(text.toString(), stringOffset, stringLength, formatForCategory(C_STRING)); if (endDelimiterOffset != -1) setFormat(endDelimiterOffset, expectedSuffix.size(), delimiterFormat); + if (operatorLength > 0) + setFormat(operatorOffset, operatorLength, formatForCategory(C_OPERATOR)); return true; } +void CppHighlighter::highlightStringLiteral(QStringView text, const CPlusPlus::Token &tk) +{ + switch (tk.kind()) { + case T_WIDE_STRING_LITERAL: + case T_UTF8_STRING_LITERAL: + case T_UTF16_STRING_LITERAL: + case T_UTF32_STRING_LITERAL: + break; + default: + if (!tk.userDefinedLiteral()) { // Simple case: No prefix, no suffix. + setFormatWithSpaces(text.toString(), tk.utf16charsBegin(), tk.utf16chars(), + formatForCategory(C_STRING)); + return; + } + } + + int stringOffset = 0; + if (!tk.f.joined) { + stringOffset = text.indexOf('"', tk.utf16charsBegin()); + QTC_ASSERT(stringOffset > 0, return); + setFormat(tk.utf16charsBegin(), stringOffset - tk.utf16charsBegin(), + formatForCategory(C_KEYWORD)); + } + int operatorOffset = tk.utf16charsBegin() + tk.utf16chars(); + if (tk.userDefinedLiteral()) { + const int closingQuoteOffset = text.lastIndexOf('"', operatorOffset); + QTC_ASSERT(closingQuoteOffset >= tk.utf16charsBegin(), return); + operatorOffset = closingQuoteOffset + 1; + } + setFormatWithSpaces(text.toString(), stringOffset, operatorOffset - tk.utf16charsBegin(), + formatForCategory(C_STRING)); + if (const int operatorLength = tk.utf16charsBegin() + tk.utf16chars() - operatorOffset; + operatorLength > 0) { + setFormat(operatorOffset, operatorLength, formatForCategory(C_OPERATOR)); + } +} + void CppHighlighter::highlightDoxygenComment(const QString &text, int position, int) { int initial = position; @@ -513,6 +561,33 @@ void CppHighlighterTest::test_data() QTest::newRow("struct keyword") << 25 << 1 << 25 << 6 << C_KEYWORD; QTest::newRow("operator keyword") << 26 << 5 << 26 << 12 << C_KEYWORD; QTest::newRow("type in conversion operator") << 26 << 14 << 26 << 16 << C_PRIMITIVE_TYPE; + QTest::newRow("concept keyword") << 29 << 22 << 29 << 28 << C_KEYWORD; + QTest::newRow("user-defined UTF-16 string literal (prefix)") + << 32 << 16 << 32 << 16 << C_KEYWORD; + QTest::newRow("user-defined UTF-16 string literal (content)") + << 32 << 17 << 32 << 21 << C_STRING; + QTest::newRow("user-defined UTF-16 string literal (suffix)") + << 32 << 22 << 32 << 23 << C_OPERATOR; + QTest::newRow("wide string literal (prefix)") << 33 << 17 << 33 << 17 << C_KEYWORD; + QTest::newRow("wide string literal (content)") << 33 << 18 << 33 << 24 << C_STRING; + QTest::newRow("UTF-8 string literal (prefix)") << 34 << 17 << 34 << 18 << C_KEYWORD; + QTest::newRow("UTF-8 string literal (content)") << 34 << 19 << 34 << 24 << C_STRING; + QTest::newRow("UTF-32 string literal (prefix)") << 35 << 17 << 35 << 17 << C_KEYWORD; + QTest::newRow("UTF-8 string literal (content)") << 35 << 18 << 35 << 23 << C_STRING; + QTest::newRow("user-defined UTF-16 raw string literal (prefix)") + << 36 << 17 << 36 << 20 << C_KEYWORD; + QTest::newRow("user-defined UTF-16 raw string literal (content)") + << 36 << 38 << 37 << 8 << C_STRING; + QTest::newRow("user-defined UTF-16 raw string literal (suffix 1)") + << 37 << 9 << 37 << 10 << C_KEYWORD; + QTest::newRow("user-defined UTF-16 raw string literal (suffix 2)") + << 37 << 11 << 37 << 12 << C_OPERATOR; + QTest::newRow("multi-line user-defined UTF-16 string literal (prefix)") + << 38 << 17 << 38 << 17 << C_KEYWORD; + QTest::newRow("multi-line user-defined UTF-16 string literal (content)") + << 38 << 18 << 39 << 3 << C_STRING; + QTest::newRow("multi-line user-defined UTF-16 string literal (suffix)") + << 39 << 4 << 39 << 5 << C_OPERATOR; } void CppHighlighterTest::test() diff --git a/src/plugins/cppeditor/cpphighlighter.h b/src/plugins/cppeditor/cpphighlighter.h index 358fcb2760e..1728ffeb777 100644 --- a/src/plugins/cppeditor/cpphighlighter.h +++ b/src/plugins/cppeditor/cpphighlighter.h @@ -29,6 +29,7 @@ private: void highlightWord(QStringView word, int position, int length); bool highlightRawStringLiteral(QStringView text, const CPlusPlus::Token &tk, const QString &inheritedSuffix); + void highlightStringLiteral(QStringView text, const CPlusPlus::Token &tk); void highlightDoxygenComment(const QString &text, int position, int length); diff --git a/src/plugins/cppeditor/cppincludehierarchy.cpp b/src/plugins/cppeditor/cppincludehierarchy.cpp index c2a9ead6b78..caad2e0937b 100644 --- a/src/plugins/cppeditor/cppincludehierarchy.cpp +++ b/src/plugins/cppeditor/cppincludehierarchy.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -382,6 +383,7 @@ CppIncludeHierarchyWidget::CppIncludeHierarchyWidget() this, &CppIncludeHierarchyWidget::perform); m_toggleSync = new QToolButton(this); + StyleHelper::setPanelWidget(m_toggleSync); m_toggleSync->setIcon(Utils::Icons::LINK_TOOLBAR.icon()); m_toggleSync->setCheckable(true); m_toggleSync->setToolTip(Tr::tr("Synchronize with Editor")); diff --git a/src/plugins/cppeditor/cppincludesfilter.cpp b/src/plugins/cppeditor/cppincludesfilter.cpp index 9f637f6582b..9711b6f52de 100644 --- a/src/plugins/cppeditor/cppincludesfilter.cpp +++ b/src/plugins/cppeditor/cppincludesfilter.cpp @@ -7,11 +7,11 @@ #include "cppeditortr.h" #include "cppmodelmanager.h" -#include #include + #include #include -#include +#include using namespace Core; using namespace ProjectExplorer; @@ -19,79 +19,33 @@ using namespace Utils; namespace CppEditor::Internal { -class CppIncludesIterator final : public BaseFileFilter::Iterator +static FilePaths generateFilePaths(const QFuture &future, + const CPlusPlus::Snapshot &snapshot, + const std::unordered_set &inputFilePaths) { -public: - CppIncludesIterator(CPlusPlus::Snapshot snapshot, const QSet &seedPaths) - : m_snapshot(snapshot), - m_paths(seedPaths) - { - toFront(); - } + FilePaths results; + std::unordered_set resultsCache; + std::unordered_set queuedPaths = inputFilePaths; - void toFront() override; - bool hasNext() const override; - Utils::FilePath next() override; - Utils::FilePath filePath() const override; + while (!queuedPaths.empty()) { + if (future.isCanceled()) + return {}; -private: - void fetchMore(); - - CPlusPlus::Snapshot m_snapshot; - QSet m_paths; - QSet m_queuedPaths; - QSet m_allResultPaths; - FilePaths m_resultQueue; - FilePath m_currentPath; -}; - - - -void CppIncludesIterator::toFront() -{ - m_queuedPaths = m_paths; - m_allResultPaths.clear(); - m_resultQueue.clear(); - fetchMore(); -} - -bool CppIncludesIterator::hasNext() const -{ - return !m_resultQueue.isEmpty(); -} - -FilePath CppIncludesIterator::next() -{ - if (m_resultQueue.isEmpty()) - return {}; - m_currentPath = m_resultQueue.takeFirst(); - if (m_resultQueue.isEmpty()) - fetchMore(); - return m_currentPath; -} - -FilePath CppIncludesIterator::filePath() const -{ - return m_currentPath; -} - -void CppIncludesIterator::fetchMore() -{ - while (!m_queuedPaths.isEmpty() && m_resultQueue.isEmpty()) { - const FilePath filePath = *m_queuedPaths.begin(); - m_queuedPaths.remove(filePath); - CPlusPlus::Document::Ptr doc = m_snapshot.document(filePath); + const auto iterator = queuedPaths.cbegin(); + const FilePath filePath = *iterator; + queuedPaths.erase(iterator); + const CPlusPlus::Document::Ptr doc = snapshot.document(filePath); if (!doc) continue; const FilePaths includedFiles = doc->includedFiles(); - for (const FilePath &includedPath : includedFiles ) { - if (!m_allResultPaths.contains(includedPath)) { - m_allResultPaths.insert(includedPath); - m_queuedPaths.insert(includedPath); - m_resultQueue.append(includedPath); + for (const FilePath &includedFile : includedFiles) { + if (resultsCache.emplace(includedFile).second) { + queuedPaths.emplace(includedFile); + results.append(includedFile); } } } + return results; } CppIncludesFilter::CppIncludesFilter() @@ -99,61 +53,50 @@ CppIncludesFilter::CppIncludesFilter() setId(Constants::INCLUDES_FILTER_ID); setDisplayName(Tr::tr(Constants::INCLUDES_FILTER_DISPLAY_NAME)); setDescription( - Tr::tr("Matches all files that are included by all C++ files in all projects. Append " + Tr::tr("Locates files that are included by C++ files of any open project. Append " "\"+\" or \":\" to jump to the given line number. Append another " "\"+\" or \":\" to jump to the column number as well.")); setDefaultShortcutString("ai"); setDefaultIncludedByDefault(true); + const auto invalidate = [this] { m_cache.invalidate(); }; + setRefreshRecipe(Tasking::Sync([invalidate] { invalidate(); return true; })); setPriority(ILocatorFilter::Low); connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::fileListChanged, - this, &CppIncludesFilter::markOutdated); + this, invalidate); connect(CppModelManager::instance(), &CppModelManager::documentUpdated, - this, &CppIncludesFilter::markOutdated); + this, invalidate); connect(CppModelManager::instance(), &CppModelManager::aboutToRemoveFiles, - this, &CppIncludesFilter::markOutdated); + this, invalidate); connect(DocumentModel::model(), &QAbstractItemModel::rowsInserted, - this, &CppIncludesFilter::markOutdated); + this, invalidate); connect(DocumentModel::model(), &QAbstractItemModel::rowsRemoved, - this, &CppIncludesFilter::markOutdated); + this, invalidate); connect(DocumentModel::model(), &QAbstractItemModel::dataChanged, - this, &CppIncludesFilter::markOutdated); + this, invalidate); connect(DocumentModel::model(), &QAbstractItemModel::modelReset, - this, &CppIncludesFilter::markOutdated); -} + this, invalidate); -void CppIncludesFilter::prepareSearch(const QString &entry) -{ - Q_UNUSED(entry) - if (m_needsUpdate) { - m_needsUpdate = false; - QSet seedPaths; - for (Project *project : SessionManager::projects()) { + const auto generatorProvider = [] { + // This body runs in main thread + std::unordered_set inputFilePaths; + for (Project *project : ProjectManager::projects()) { const FilePaths allFiles = project->files(Project::SourceFiles); - for (const FilePath &filePath : allFiles ) - seedPaths.insert(filePath); + for (const FilePath &filePath : allFiles) + inputFilePaths.insert(filePath); } const QList entries = DocumentModel::entries(); for (DocumentModel::Entry *entry : entries) { if (entry) - seedPaths.insert(entry->filePath()); + inputFilePaths.insert(entry->filePath()); } - CPlusPlus::Snapshot snapshot = CppModelManager::instance()->snapshot(); - setFileIterator(new CppIncludesIterator(snapshot, seedPaths)); - } - BaseFileFilter::prepareSearch(entry); -} - -void CppIncludesFilter::refresh(QFutureInterface &future) -{ - Q_UNUSED(future) - QMetaObject::invokeMethod(this, &CppIncludesFilter::markOutdated, Qt::QueuedConnection); -} - -void CppIncludesFilter::markOutdated() -{ - m_needsUpdate = true; - setFileIterator(nullptr); // clean up + const CPlusPlus::Snapshot snapshot = CppModelManager::instance()->snapshot(); + return [snapshot, inputFilePaths](const QFuture &future) { + // This body runs in non-main thread + return generateFilePaths(future, snapshot, inputFilePaths); + }; + }; + m_cache.setGeneratorProvider(generatorProvider); } } // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/cppincludesfilter.h b/src/plugins/cppeditor/cppincludesfilter.h index 6ae5dbb91c4..4ab5ffba97f 100644 --- a/src/plugins/cppeditor/cppincludesfilter.h +++ b/src/plugins/cppeditor/cppincludesfilter.h @@ -3,24 +3,18 @@ #pragma once -#include +#include namespace CppEditor::Internal { -class CppIncludesFilter : public Core::BaseFileFilter +class CppIncludesFilter : public Core::ILocatorFilter { public: CppIncludesFilter(); - // ILocatorFilter interface -public: - void prepareSearch(const QString &entry) override; - void refresh(QFutureInterface &future) override; - private: - void markOutdated(); - - bool m_needsUpdate = true; + Core::LocatorMatcherTasks matchers() final { return {m_cache.matcher()}; } + Core::LocatorFileCache m_cache; }; } // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/cppindexingsupport.cpp b/src/plugins/cppeditor/cppindexingsupport.cpp index 2d0e2b55194..22ff93cdaa0 100644 --- a/src/plugins/cppeditor/cppindexingsupport.cpp +++ b/src/plugins/cppeditor/cppindexingsupport.cpp @@ -10,13 +10,13 @@ #include "cppsourceprocessor.h" #include "searchsymbols.h" -#include #include #include +#include #include -#include +#include #include #include @@ -54,7 +54,7 @@ class WriteTaskFileForDiagnostics public: WriteTaskFileForDiagnostics() { - const QString fileName = Utils::TemporaryDirectory::masterDirectoryPath() + const QString fileName = TemporaryDirectory::masterDirectoryPath() + "/qtc_findErrorsIndexing.diagnostics." + QDateTime::currentDateTime().toString("yyMMdd_HHmm") + ".tasks"; @@ -116,8 +116,7 @@ void classifyFiles(const QSet &files, QStringList *headers, QStringList } } -void indexFindErrors(QFutureInterface &indexingFuture, - const ParseParams params) +void indexFindErrors(QPromise &promise, const ParseParams params) { QStringList sources, headers; classifyFiles(params.sourceFiles, &headers, &sources); @@ -130,7 +129,7 @@ void indexFindErrors(QFutureInterface &indexingFuture, timer.start(); for (int i = 0, end = files.size(); i < end ; ++i) { - if (indexingFuture.isCanceled()) + if (promise.isCanceled()) break; const QString file = files.at(i); @@ -140,7 +139,7 @@ void indexFindErrors(QFutureInterface &indexingFuture, BuiltinEditorDocumentParser parser(FilePath::fromString(file)); parser.setReleaseSourceAndAST(false); parser.update({CppModelManager::instance()->workingCopy(), nullptr, - Utils::Language::Cxx, false}); + Language::Cxx, false}); CPlusPlus::Document::Ptr document = parser.document(); QTC_ASSERT(document, return); @@ -153,15 +152,14 @@ void indexFindErrors(QFutureInterface &indexingFuture, document->releaseSourceAndAST(); - indexingFuture.setProgressValue(i + 1); + promise.setProgressValue(i + 1); } const QString elapsedTime = Utils::formatElapsedTime(timer.elapsed()); qDebug("FindErrorsIndexing: %s", qPrintable(elapsedTime)); } -void index(QFutureInterface &indexingFuture, - const ParseParams params) +void index(QPromise &promise, const ParseParams params) { QScopedPointer sourceProcessor(CppModelManager::createSourceProcessor()); sourceProcessor->setFileSizeLimitInMb(params.indexerFileSizeLimitInMb); @@ -190,7 +188,7 @@ void index(QFutureInterface &indexingFuture, qCDebug(indexerLog) << "About to index" << files.size() << "files."; for (int i = 0; i < files.size(); ++i) { - if (indexingFuture.isCanceled()) + if (promise.isCanceled()) break; const QString fileName = files.at(i); @@ -216,7 +214,7 @@ void index(QFutureInterface &indexingFuture, sourceProcessor->setHeaderPaths(headerPaths); sourceProcessor->run(FilePath::fromString(fileName)); - indexingFuture.setProgressValue(files.size() - sourceProcessor->todo().size()); + promise.setProgressValue(files.size() - sourceProcessor->todo().size()); if (isSourceFile) sourceProcessor->resetEnvironment(); @@ -224,29 +222,29 @@ void index(QFutureInterface &indexingFuture, qCDebug(indexerLog) << "Indexing finished."; } -void parse(QFutureInterface &indexingFuture, const ParseParams params) +void parse(QPromise &promise, const ParseParams params) { const QSet &files = params.sourceFiles; if (files.isEmpty()) return; - indexingFuture.setProgressRange(0, files.size()); + promise.setProgressRange(0, files.size()); if (CppIndexingSupport::isFindErrorsIndexingActive()) - indexFindErrors(indexingFuture, params); + indexFindErrors(promise, params); else - index(indexingFuture, params); + index(promise, params); - indexingFuture.setProgressValue(files.size()); + promise.setProgressValue(files.size()); CppModelManager::instance()->finishedRefreshingSourceFiles(files); } } // anonymous namespace -void SymbolSearcher::runSearch(QFutureInterface &future) +void SymbolSearcher::runSearch(QPromise &promise) { - future.setProgressRange(0, m_snapshot.size()); - future.setProgressValue(0); + promise.setProgressRange(0, m_snapshot.size()); + promise.setProgressValue(0); int progress = 0; SearchSymbols search; @@ -262,12 +260,11 @@ void SymbolSearcher::runSearch(QFutureInterface &future) : QRegularExpression::CaseInsensitiveOption)); matcher.optimize(); while (it != m_snapshot.end()) { - if (future.isPaused()) - future.waitForResume(); - if (future.isCanceled()) + promise.suspendIfRequested(); + if (promise.isCanceled()) break; if (m_fileNames.isEmpty() || m_fileNames.contains(it.value()->filePath().path())) { - QVector resultItems; + SearchResultItems resultItems; auto filter = [&](const IndexItem::Ptr &info) -> IndexItem::VisitorResult { if (matcher.match(info->symbolName()).hasMatch()) { QString text = info->symbolName(); @@ -280,7 +277,7 @@ void SymbolSearcher::runSearch(QFutureInterface &future) text = info->representDeclaration(); } - Core::SearchResultItem item; + SearchResultItem item; item.setPath(scope.split(QLatin1String("::"), Qt::SkipEmptyParts)); item.setLineText(text); item.setIcon(info->icon()); @@ -291,24 +288,16 @@ void SymbolSearcher::runSearch(QFutureInterface &future) return IndexItem::Recurse; }; search(it.value())->visitAllChildren(filter); - if (!resultItems.isEmpty()) - future.reportResults(resultItems); + for (const SearchResultItem &item : std::as_const(resultItems)) + promise.addResult(item); } ++it; ++progress; - future.setProgressValue(progress); + promise.setProgressValue(progress); } - if (future.isPaused()) - future.waitForResume(); + promise.suspendIfRequested(); } -CppIndexingSupport::CppIndexingSupport() -{ - m_synchronizer.setCancelOnWait(true); -} - -CppIndexingSupport::~CppIndexingSupport() = default; - bool CppIndexingSupport::isFindErrorsIndexingActive() { return Utils::qtcEnvironmentVariable("QTC_FIND_ERRORS_INDEXING") == "1"; @@ -325,7 +314,7 @@ QFuture CppIndexingSupport::refreshSourceFiles(const QSet &source params.workingCopy = mgr->workingCopy(); params.sourceFiles = sourceFiles; - QFuture result = Utils::runAsync(mgr->sharedThreadPool(), parse, params); + QFuture result = Utils::asyncRun(mgr->sharedThreadPool(), parse, params); m_synchronizer.addFuture(result); if (mode == CppModelManager::ForcedProgressNotification || sourceFiles.count() > 1) { diff --git a/src/plugins/cppeditor/cppindexingsupport.h b/src/plugins/cppeditor/cppindexingsupport.h index 584632a4f00..b5aa68585d6 100644 --- a/src/plugins/cppeditor/cppindexingsupport.h +++ b/src/plugins/cppeditor/cppindexingsupport.h @@ -11,7 +11,7 @@ #include -namespace Core { class SearchResultItem; } +namespace Utils { class SearchResultItem; } namespace CppEditor { @@ -44,7 +44,7 @@ public: }; SymbolSearcher(const SymbolSearcher::Parameters ¶meters, const QSet &fileNames); - void runSearch(QFutureInterface &future); + void runSearch(QPromise &promise); private: const CPlusPlus::Snapshot m_snapshot; @@ -55,9 +55,6 @@ private: class CPPEDITOR_EXPORT CppIndexingSupport { public: - CppIndexingSupport(); - ~CppIndexingSupport(); - static bool isFindErrorsIndexingActive(); QFuture refreshSourceFiles(const QSet &sourceFiles, diff --git a/src/plugins/cppeditor/cpplocatordata.cpp b/src/plugins/cppeditor/cpplocatordata.cpp index 9b260ccc2ea..a0899b0ddde 100644 --- a/src/plugins/cppeditor/cpplocatordata.cpp +++ b/src/plugins/cppeditor/cpplocatordata.cpp @@ -20,6 +20,22 @@ CppLocatorData::CppLocatorData() m_pendingDocuments.reserve(MaxPendingDocuments); } +QList CppLocatorData::findSymbols(IndexItem::ItemType type, + const QString &symbolName) const +{ + QList matches; + filterAllFiles([&](const IndexItem::Ptr &info) { + if (info->type() & type) { + if (info->symbolName() == symbolName || info->scopedSymbolName() == symbolName) + matches << info; + } + if (info->type() & IndexItem::Enum) + return IndexItem::Continue; + return IndexItem::Recurse; + }); + return matches; +} + void CppLocatorData::onDocumentUpdated(const CPlusPlus::Document::Ptr &document) { QMutexLocker locker(&m_pendingDocumentsMutex); diff --git a/src/plugins/cppeditor/cpplocatordata.h b/src/plugins/cppeditor/cpplocatordata.h index 2170deb75dd..d981eb7c04e 100644 --- a/src/plugins/cppeditor/cpplocatordata.h +++ b/src/plugins/cppeditor/cpplocatordata.h @@ -33,6 +33,8 @@ public: return; } + QList findSymbols(IndexItem::ItemType type, const QString &symbolName) const; + public slots: void onDocumentUpdated(const CPlusPlus::Document::Ptr &document); void onAboutToRemoveFiles(const QStringList &files); diff --git a/src/plugins/cppeditor/cpplocatorfilter.cpp b/src/plugins/cppeditor/cpplocatorfilter.cpp index 083c5df1b2f..cd1add7786e 100644 --- a/src/plugins/cppeditor/cpplocatorfilter.cpp +++ b/src/plugins/cppeditor/cpplocatorfilter.cpp @@ -4,60 +4,49 @@ #include "cpplocatorfilter.h" #include "cppeditorconstants.h" +#include "cppeditorplugin.h" #include "cppeditortr.h" +#include "cpplocatordata.h" +#include "cppmodelmanager.h" #include +#include + +#include + #include +#include +#include #include -#include -#include +using namespace Core; +using namespace CPlusPlus; +using namespace Utils; namespace CppEditor { -CppLocatorFilter::CppLocatorFilter(CppLocatorData *locatorData) - : m_data(locatorData) +using EntryFromIndex = std::function; + +void matchesFor(QPromise &promise, const LocatorStorage &storage, + IndexItem::ItemType wantedType, const EntryFromIndex &converter) { - setId(Constants::LOCATOR_FILTER_ID); - setDisplayName(Tr::tr(Constants::LOCATOR_FILTER_DISPLAY_NAME)); - setDefaultShortcutString(":"); - setDefaultIncludedByDefault(false); -} - -CppLocatorFilter::~CppLocatorFilter() = default; - -Core::LocatorFilterEntry CppLocatorFilter::filterEntryFromIndexItem(IndexItem::Ptr info) -{ - const QVariant id = QVariant::fromValue(info); - Core::LocatorFilterEntry filterEntry(this, info->scopedSymbolName(), id, info->icon()); - if (info->type() == IndexItem::Class || info->type() == IndexItem::Enum) - filterEntry.extraInfo = info->shortNativeFilePath(); - else - filterEntry.extraInfo = info->symbolType(); - - return filterEntry; -} - -QList CppLocatorFilter::matchesFor( - QFutureInterface &future, const QString &entry) -{ - QList entries[int(MatchLevel::Count)]; - const Qt::CaseSensitivity caseSensitivityForPrefix = caseSensitivity(entry); - const IndexItem::ItemType wanted = matchTypes(); - - const QRegularExpression regexp = createRegExp(entry); + const QString input = storage.input(); + LocatorFilterEntries entries[int(ILocatorFilter::MatchLevel::Count)]; + const Qt::CaseSensitivity caseSensitivityForPrefix = ILocatorFilter::caseSensitivity(input); + const QRegularExpression regexp = ILocatorFilter::createRegExp(input); if (!regexp.isValid()) - return {}; - const bool hasColonColon = entry.contains("::"); - const QRegularExpression shortRegexp = - hasColonColon ? createRegExp(entry.mid(entry.lastIndexOf("::") + 2)) : regexp; + return; - m_data->filterAllFiles([&](const IndexItem::Ptr &info) -> IndexItem::VisitorResult { - if (future.isCanceled()) + const bool hasColonColon = input.contains("::"); + const QRegularExpression shortRegexp = hasColonColon + ? ILocatorFilter::createRegExp(input.mid(input.lastIndexOf("::") + 2)) : regexp; + CppLocatorData *locatorData = CppModelManager::instance()->locatorData(); + locatorData->filterAllFiles([&](const IndexItem::Ptr &info) { + if (promise.isCanceled()) return IndexItem::Break; const IndexItem::ItemType type = info->type(); - if (type & wanted) { + if (type & wantedType) { const QString symbolName = info->symbolName(); QString matchString = hasColonColon ? info->scopedSymbolName() : symbolName; int matchOffset = hasColonColon ? matchString.size() - symbolName.size() : 0; @@ -70,7 +59,7 @@ QList CppLocatorFilter::matchesFor( } if (match.hasMatch()) { - Core::LocatorFilterEntry filterEntry = filterEntryFromIndexItem(info); + LocatorFilterEntry filterEntry = converter(info); // Highlight the matched characters, therefore it may be necessary // to update the match if the displayName is different from matchString @@ -78,103 +67,323 @@ QList CppLocatorFilter::matchesFor( match = shortRegexp.match(filterEntry.displayName); matchOffset = 0; } - filterEntry.highlightInfo = highlightInfo(match); + filterEntry.highlightInfo = ILocatorFilter::highlightInfo(match); if (matchInParameterList && filterEntry.highlightInfo.startsDisplay.isEmpty()) { match = regexp.match(filterEntry.extraInfo); - filterEntry.highlightInfo - = highlightInfo(match, Core::LocatorFilterEntry::HighlightInfo::ExtraInfo); + filterEntry.highlightInfo = ILocatorFilter::highlightInfo( + match, LocatorFilterEntry::HighlightInfo::ExtraInfo); } else if (matchOffset > 0) { for (int &start : filterEntry.highlightInfo.startsDisplay) start -= matchOffset; } if (matchInParameterList) - entries[int(MatchLevel::Normal)].append(filterEntry); - else if (filterEntry.displayName.startsWith(entry, caseSensitivityForPrefix)) - entries[int(MatchLevel::Best)].append(filterEntry); - else if (filterEntry.displayName.contains(entry, caseSensitivityForPrefix)) - entries[int(MatchLevel::Better)].append(filterEntry); + entries[int(ILocatorFilter::MatchLevel::Normal)].append(filterEntry); + else if (filterEntry.displayName.startsWith(input, caseSensitivityForPrefix)) + entries[int(ILocatorFilter::MatchLevel::Best)].append(filterEntry); + else if (filterEntry.displayName.contains(input, caseSensitivityForPrefix)) + entries[int(ILocatorFilter::MatchLevel::Better)].append(filterEntry); else - entries[int(MatchLevel::Good)].append(filterEntry); + entries[int(ILocatorFilter::MatchLevel::Good)].append(filterEntry); } } if (info->type() & IndexItem::Enum) return IndexItem::Continue; - else - return IndexItem::Recurse; + return IndexItem::Recurse; }); for (auto &entry : entries) { if (entry.size() < 1000) - Utils::sort(entry, Core::LocatorFilterEntry::compareLexigraphically); + Utils::sort(entry, LocatorFilterEntry::compareLexigraphically); } - return std::accumulate(std::begin(entries), std::end(entries), QList()); + storage.reportOutput(std::accumulate(std::begin(entries), std::end(entries), + LocatorFilterEntries())); } -void CppLocatorFilter::accept(const Core::LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const +LocatorMatcherTask locatorMatcher(IndexItem::ItemType type, const EntryFromIndex &converter) { - Q_UNUSED(newText) - Q_UNUSED(selectionStart) - Q_UNUSED(selectionLength) - IndexItem::Ptr info = qvariant_cast(selection.internalData); - Core::EditorManager::openEditorAt({info->filePath(), info->line(), info->column()}, - {}, - Core::EditorManager::AllowExternalEditor); + using namespace Tasking; + + TreeStorage storage; + + const auto onSetup = [=](Async &async) { + async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer()); + async.setConcurrentCallData(matchesFor, *storage, type, converter); + }; + return {AsyncTask(onSetup), storage}; } -CppClassesFilter::CppClassesFilter(CppLocatorData *locatorData) - : CppLocatorFilter(locatorData) +LocatorMatcherTask allSymbolsMatcher() +{ + const auto converter = [](const IndexItem::Ptr &info) { + LocatorFilterEntry filterEntry; + filterEntry.displayName = info->scopedSymbolName(); + filterEntry.displayIcon = info->icon(); + filterEntry.linkForEditor = {info->filePath(), info->line(), info->column()}; + if (info->type() == IndexItem::Class || info->type() == IndexItem::Enum) + filterEntry.extraInfo = info->shortNativeFilePath(); + else + filterEntry.extraInfo = info->symbolType(); + return filterEntry; + }; + return locatorMatcher(IndexItem::All, converter); +} + +LocatorMatcherTask classMatcher() +{ + const auto converter = [](const IndexItem::Ptr &info) { + LocatorFilterEntry filterEntry; + filterEntry.displayName = info->symbolName(); + filterEntry.displayIcon = info->icon(); + filterEntry.linkForEditor = {info->filePath(), info->line(), info->column()}; + filterEntry.extraInfo = info->symbolScope().isEmpty() + ? info->shortNativeFilePath() + : info->symbolScope(); + filterEntry.filePath = info->filePath(); + return filterEntry; + }; + return locatorMatcher(IndexItem::Class, converter); +} + +LocatorMatcherTask functionMatcher() +{ + const auto converter = [](const IndexItem::Ptr &info) { + QString name = info->symbolName(); + QString extraInfo = info->symbolScope(); + info->unqualifiedNameAndScope(name, &name, &extraInfo); + if (extraInfo.isEmpty()) + extraInfo = info->shortNativeFilePath(); + else + extraInfo.append(" (" + info->filePath().fileName() + ')'); + LocatorFilterEntry filterEntry; + filterEntry.displayName = name + info->symbolType(); + filterEntry.displayIcon = info->icon(); + filterEntry.linkForEditor = {info->filePath(), info->line(), info->column()}; + filterEntry.extraInfo = extraInfo; + + return filterEntry; + }; + return locatorMatcher(IndexItem::Function, converter); +} + +QList itemsOfCurrentDocument(const FilePath ¤tFileName) +{ + if (currentFileName.isEmpty()) + return {}; + + QList results; + const Snapshot snapshot = CppModelManager::instance()->snapshot(); + if (const Document::Ptr thisDocument = snapshot.document(currentFileName)) { + SearchSymbols search; + search.setSymbolsToSearchFor(SymbolSearcher::Declarations | + SymbolSearcher::Enums | + SymbolSearcher::Functions | + SymbolSearcher::Classes); + IndexItem::Ptr rootNode = search(thisDocument); + rootNode->visitAllChildren([&](const IndexItem::Ptr &info) { + results.append(info); + return IndexItem::Recurse; + }); + } + return results; +} + +LocatorFilterEntry::HighlightInfo highlightInfo(const QRegularExpressionMatch &match, + LocatorFilterEntry::HighlightInfo::DataType dataType) +{ + const FuzzyMatcher::HighlightingPositions positions = + FuzzyMatcher::highlightingPositions(match); + + return LocatorFilterEntry::HighlightInfo(positions.starts, positions.lengths, dataType); +} + +void matchesForCurrentDocument(QPromise &promise, const LocatorStorage &storage, + const FilePath ¤tFileName) +{ + const QString input = storage.input(); + const QRegularExpression regexp = ILocatorFilter::createRegExp(input); + if (!regexp.isValid()) + return; + + struct Entry + { + LocatorFilterEntry entry; + IndexItem::Ptr info; + }; + QList goodEntries; + QList betterEntries; + const QList items = itemsOfCurrentDocument(currentFileName); + for (const IndexItem::Ptr &info : items) { + if (promise.isCanceled()) + break; + + QString matchString = info->symbolName(); + if (info->type() == IndexItem::Declaration) + matchString = info->representDeclaration(); + else if (info->type() == IndexItem::Function) + matchString += info->symbolType(); + + QRegularExpressionMatch match = regexp.match(matchString); + if (match.hasMatch()) { + const bool betterMatch = match.capturedStart() == 0; + QString name = matchString; + QString extraInfo = info->symbolScope(); + if (info->type() == IndexItem::Function) { + if (info->unqualifiedNameAndScope(matchString, &name, &extraInfo)) { + name += info->symbolType(); + match = regexp.match(name); + } + } + + LocatorFilterEntry filterEntry; + filterEntry.displayName = name; + filterEntry.displayIcon = info->icon(); + filterEntry.linkForEditor = {info->filePath(), info->line(), info->column()}; + filterEntry.extraInfo = extraInfo; + if (match.hasMatch()) { + filterEntry.highlightInfo = highlightInfo(match, + LocatorFilterEntry::HighlightInfo::DisplayName); + } else { + match = regexp.match(extraInfo); + filterEntry.highlightInfo = + highlightInfo(match, LocatorFilterEntry::HighlightInfo::ExtraInfo); + } + + if (betterMatch) + betterEntries.append({filterEntry, info}); + else + goodEntries.append({filterEntry, info}); + } + } + + // entries are unsorted by design! + betterEntries += goodEntries; + + QHash> possibleDuplicates; + for (const Entry &e : std::as_const(betterEntries)) + possibleDuplicates[e.info->scopedSymbolName() + e.info->symbolType()] << e; + for (auto it = possibleDuplicates.cbegin(); it != possibleDuplicates.cend(); ++it) { + const QList &duplicates = it.value(); + if (duplicates.size() == 1) + continue; + QList declarations; + QList definitions; + for (const Entry &candidate : duplicates) { + const IndexItem::Ptr info = candidate.info; + if (info->type() != IndexItem::Function) + break; + if (info->isFunctionDefinition()) + definitions << candidate; + else + declarations << candidate; + } + if (definitions.size() == 1 + && declarations.size() + definitions.size() == duplicates.size()) { + for (const Entry &decl : std::as_const(declarations)) { + Utils::erase(betterEntries, [&decl](const Entry &e) { + return e.info == decl.info; + }); + } + } + } + storage.reportOutput(Utils::transform(betterEntries, + [](const Entry &entry) { return entry.entry; })); +} + +FilePath currentFileName() +{ + IEditor *currentEditor = EditorManager::currentEditor(); + return currentEditor ? currentEditor->document()->filePath() : FilePath(); +} + +LocatorMatcherTask currentDocumentMatcher() +{ + using namespace Tasking; + + TreeStorage storage; + + const auto onSetup = [=](Async &async) { + async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer()); + async.setConcurrentCallData(matchesForCurrentDocument, *storage, currentFileName()); + }; + return {AsyncTask(onSetup), storage}; +} + +using MatcherCreator = std::function; + +static MatcherCreator creatorForType(MatcherType type) +{ + switch (type) { + case MatcherType::AllSymbols: return &allSymbolsMatcher; + case MatcherType::Classes: return &classMatcher; + case MatcherType::Functions: return &functionMatcher; + case MatcherType::CurrentDocumentSymbols: return ¤tDocumentMatcher; + } + return {}; +} + +LocatorMatcherTasks cppMatchers(MatcherType type) +{ + const MatcherCreator creator = creatorForType(type); + if (!creator) + return {}; + return {creator()}; +} + +CppAllSymbolsFilter::CppAllSymbolsFilter() +{ + setId(Constants::LOCATOR_FILTER_ID); + setDisplayName(Tr::tr(Constants::LOCATOR_FILTER_DISPLAY_NAME)); + setDescription(Tr::tr(Constants::LOCATOR_FILTER_DESCRIPTION)); + setDefaultShortcutString(":"); +} + +LocatorMatcherTasks CppAllSymbolsFilter::matchers() +{ + return {allSymbolsMatcher()}; +} + + +CppClassesFilter::CppClassesFilter() { setId(Constants::CLASSES_FILTER_ID); setDisplayName(Tr::tr(Constants::CLASSES_FILTER_DISPLAY_NAME)); + setDescription(Tr::tr(Constants::CLASSES_FILTER_DESCRIPTION)); setDefaultShortcutString("c"); - setDefaultIncludedByDefault(false); } -CppClassesFilter::~CppClassesFilter() = default; - -Core::LocatorFilterEntry CppClassesFilter::filterEntryFromIndexItem(IndexItem::Ptr info) +LocatorMatcherTasks CppClassesFilter::matchers() { - const QVariant id = QVariant::fromValue(info); - Core::LocatorFilterEntry filterEntry(this, info->symbolName(), id, info->icon()); - filterEntry.extraInfo = info->symbolScope().isEmpty() - ? info->shortNativeFilePath() - : info->symbolScope(); - filterEntry.filePath = info->filePath(); - return filterEntry; + return {classMatcher()}; } -CppFunctionsFilter::CppFunctionsFilter(CppLocatorData *locatorData) - : CppLocatorFilter(locatorData) +CppFunctionsFilter::CppFunctionsFilter() { setId(Constants::FUNCTIONS_FILTER_ID); setDisplayName(Tr::tr(Constants::FUNCTIONS_FILTER_DISPLAY_NAME)); + setDescription(Tr::tr(Constants::FUNCTIONS_FILTER_DESCRIPTION)); setDefaultShortcutString("m"); - setDefaultIncludedByDefault(false); } -CppFunctionsFilter::~CppFunctionsFilter() = default; - -Core::LocatorFilterEntry CppFunctionsFilter::filterEntryFromIndexItem(IndexItem::Ptr info) +LocatorMatcherTasks CppFunctionsFilter::matchers() { - const QVariant id = QVariant::fromValue(info); + return {functionMatcher()}; +} - QString name = info->symbolName(); - QString extraInfo = info->symbolScope(); - info->unqualifiedNameAndScope(name, &name, &extraInfo); - if (extraInfo.isEmpty()) { - extraInfo = info->shortNativeFilePath(); - } else { - extraInfo.append(" (" + info->filePath().fileName() + ')'); - } +CppCurrentDocumentFilter::CppCurrentDocumentFilter() +{ + setId(Constants::CURRENT_DOCUMENT_FILTER_ID); + setDisplayName(Tr::tr(Constants::CURRENT_DOCUMENT_FILTER_DISPLAY_NAME)); + setDescription(Tr::tr(Constants::CURRENT_DOCUMENT_FILTER_DESCRIPTION)); + setDefaultShortcutString("."); + setPriority(High); +} - Core::LocatorFilterEntry filterEntry(this, name + info->symbolType(), id, info->icon()); - filterEntry.extraInfo = extraInfo; - - return filterEntry; +LocatorMatcherTasks CppCurrentDocumentFilter::matchers() +{ + return {currentDocumentMatcher()}; } } // namespace CppEditor diff --git a/src/plugins/cppeditor/cpplocatorfilter.h b/src/plugins/cppeditor/cpplocatorfilter.h index 9770afe21b4..0b012fac6b4 100644 --- a/src/plugins/cppeditor/cpplocatorfilter.h +++ b/src/plugins/cppeditor/cpplocatorfilter.h @@ -4,58 +4,47 @@ #pragma once #include "cppeditor_global.h" -#include "cpplocatordata.h" -#include "searchsymbols.h" #include namespace CppEditor { -class CPPEDITOR_EXPORT CppLocatorFilter : public Core::ILocatorFilter +Core::LocatorMatcherTasks CPPEDITOR_EXPORT cppMatchers(Core::MatcherType type); + +class CppAllSymbolsFilter : public Core::ILocatorFilter { - Q_OBJECT - public: - explicit CppLocatorFilter(CppLocatorData *locatorData); - ~CppLocatorFilter() override; + CppAllSymbolsFilter(); - QList matchesFor(QFutureInterface &future, - const QString &entry) override; - void accept(const Core::LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const override; - -protected: - virtual IndexItem::ItemType matchTypes() const { return IndexItem::All; } - virtual Core::LocatorFilterEntry filterEntryFromIndexItem(IndexItem::Ptr info); - -protected: - CppLocatorData *m_data = nullptr; +private: + Core::LocatorMatcherTasks matchers() final; }; -class CPPEDITOR_EXPORT CppClassesFilter : public CppLocatorFilter +class CppClassesFilter : public Core::ILocatorFilter { - Q_OBJECT - public: - explicit CppClassesFilter(CppLocatorData *locatorData); - ~CppClassesFilter() override; + CppClassesFilter(); -protected: - IndexItem::ItemType matchTypes() const override { return IndexItem::Class; } - Core::LocatorFilterEntry filterEntryFromIndexItem(IndexItem::Ptr info) override; +private: + Core::LocatorMatcherTasks matchers() final; }; -class CPPEDITOR_EXPORT CppFunctionsFilter : public CppLocatorFilter +class CppFunctionsFilter : public Core::ILocatorFilter { - Q_OBJECT - public: - explicit CppFunctionsFilter(CppLocatorData *locatorData); - ~CppFunctionsFilter() override; + CppFunctionsFilter(); -protected: - IndexItem::ItemType matchTypes() const override { return IndexItem::Function; } - Core::LocatorFilterEntry filterEntryFromIndexItem(IndexItem::Ptr info) override; +private: + Core::LocatorMatcherTasks matchers() final; +}; + +class CppCurrentDocumentFilter : public Core::ILocatorFilter +{ +public: + CppCurrentDocumentFilter(); + +private: + Core::LocatorMatcherTasks matchers() final; }; } // namespace CppEditor diff --git a/src/plugins/cppeditor/cpplocatorfilter_test.cpp b/src/plugins/cppeditor/cpplocatorfilter_test.cpp index 598a6e85640..18e9b933cbe 100644 --- a/src/plugins/cppeditor/cpplocatorfilter_test.cpp +++ b/src/plugins/cppeditor/cpplocatorfilter_test.cpp @@ -3,17 +3,12 @@ #include "cpplocatorfilter_test.h" -#include "cppcurrentdocumentfilter.h" -#include "cpplocatorfilter.h" -#include "cppmodelmanager.h" #include "cpptoolstestcase.h" #include #include #include -#include #include -#include #include #include @@ -21,7 +16,6 @@ using namespace Core; using namespace Core::Tests; -using namespace ExtensionSystem; using namespace Utils; namespace CppEditor::Internal { @@ -31,23 +25,22 @@ const bool debug = qtcEnvironmentVariable("QTC_DEBUG_CPPLOCATORFILTERTESTCASE") QTC_DECLARE_MYTESTDATADIR("../../../tests/cpplocators/") -class CppLocatorFilterTestCase - : public BasicLocatorFilterTest - , public CppEditor::Tests::TestCase +class CppLocatorFilterTestCase : public CppEditor::Tests::TestCase { public: - CppLocatorFilterTestCase(ILocatorFilter *filter, + CppLocatorFilterTestCase(const QList &matchers, const QString &fileName, const QString &searchText, const ResultDataList &expectedResults) - : BasicLocatorFilterTest(filter) - , m_fileName(fileName) { QVERIFY(succeededSoFar()); - QVERIFY(!m_fileName.isEmpty()); + QVERIFY(!fileName.isEmpty()); QVERIFY(garbageCollectGlobalSnapshot()); - ResultDataList results = ResultData::fromFilterEntryList(matchesFor(searchText)); + QVERIFY(parseFiles(fileName)); + const LocatorFilterEntries entries = LocatorMatcher::runBlocking(matchers, searchText); + QVERIFY(garbageCollectGlobalSnapshot()); + const ResultDataList results = ResultData::fromFilterEntryList(entries); if (debug) { ResultData::printFilterEntries(expectedResults, "Expected:"); ResultData::printFilterEntries(results, "Results:"); @@ -55,30 +48,32 @@ public: QVERIFY(!results.isEmpty()); QCOMPARE(results, expectedResults); } - -private: - void doBeforeLocatorRun() override { QVERIFY(parseFiles(m_fileName)); } - void doAfterLocatorRun() override { QVERIFY(garbageCollectGlobalSnapshot()); } - -private: - const QString m_fileName; }; -class CppCurrentDocumentFilterTestCase - : public BasicLocatorFilterTest - , public CppEditor::Tests::TestCase +class CppCurrentDocumentFilterTestCase : public CppEditor::Tests::TestCase { public: CppCurrentDocumentFilterTestCase(const FilePath &filePath, + const QList &matchers, const ResultDataList &expectedResults, const QString &searchText = QString()) - : BasicLocatorFilterTest(CppModelManager::instance()->currentDocumentFilter()) - , m_filePath(filePath) { QVERIFY(succeededSoFar()); - QVERIFY(!m_filePath.isEmpty()); + QVERIFY(!filePath.isEmpty()); - ResultDataList results = ResultData::fromFilterEntryList(matchesFor(searchText)); + QVERIFY(DocumentModel::openedDocuments().isEmpty()); + QVERIFY(garbageCollectGlobalSnapshot()); + + const auto editor = EditorManager::openEditor(filePath); + QVERIFY(editor); + + QVERIFY(waitForFileInGlobalSnapshot(filePath)); + const LocatorFilterEntries entries = LocatorMatcher::runBlocking(matchers, searchText); + QVERIFY(closeEditorWithoutGarbageCollectorInvocation(editor)); + QCoreApplication::processEvents(); + QVERIFY(DocumentModel::openedDocuments().isEmpty()); + QVERIFY(garbageCollectGlobalSnapshot()); + const ResultDataList results = ResultData::fromFilterEntryList(entries); if (debug) { ResultData::printFilterEntries(expectedResults, "Expected:"); ResultData::printFilterEntries(results, "Results:"); @@ -86,30 +81,6 @@ public: QVERIFY(!results.isEmpty()); QCOMPARE(results, expectedResults); } - -private: - void doBeforeLocatorRun() override - { - QVERIFY(DocumentModel::openedDocuments().isEmpty()); - QVERIFY(garbageCollectGlobalSnapshot()); - - m_editor = EditorManager::openEditor(m_filePath); - QVERIFY(m_editor); - - QVERIFY(waitForFileInGlobalSnapshot(m_filePath)); - } - - void doAfterLocatorRun() override - { - QVERIFY(closeEditorWithoutGarbageCollectorInvocation(m_editor)); - QCoreApplication::processEvents(); - QVERIFY(DocumentModel::openedDocuments().isEmpty()); - QVERIFY(garbageCollectGlobalSnapshot()); - } - -private: - IEditor *m_editor = nullptr; - const FilePath m_filePath; }; } // anonymous namespace @@ -117,28 +88,22 @@ private: void LocatorFilterTest::testLocatorFilter() { QFETCH(QString, testFile); - QFETCH(ILocatorFilter *, filter); + QFETCH(MatcherType, matcherType); QFETCH(QString, searchText); QFETCH(ResultDataList, expectedResults); Tests::VerifyCleanCppModelManager verify; - - CppLocatorFilterTestCase(filter, testFile, searchText, expectedResults); + CppLocatorFilterTestCase(LocatorMatcher::matchers(matcherType), testFile, searchText, + expectedResults); } void LocatorFilterTest::testLocatorFilter_data() { QTest::addColumn("testFile"); - QTest::addColumn("filter"); + QTest::addColumn("matcherType"); QTest::addColumn("searchText"); QTest::addColumn("expectedResults"); - CppModelManager *cppModelManager = CppModelManager::instance(); - - ILocatorFilter *cppFunctionsFilter = cppModelManager->functionsFilter(); - ILocatorFilter *cppClassesFilter = cppModelManager->classesFilter(); - ILocatorFilter *cppLocatorFilter = cppModelManager->locatorFilter(); - MyTestDataDir testDirectory("testdata_basic"); QString testFile = testDirectory.file("file1.cpp"); testFile[0] = testFile[0].toLower(); // Ensure Windows path sorts after scope names. @@ -148,7 +113,7 @@ void LocatorFilterTest::testLocatorFilter_data() QTest::newRow("CppFunctionsFilter") << testFile - << cppFunctionsFilter + << MatcherType::Functions << "function" << ResultDataList{ ResultData("functionDefinedInClass(bool, int)", @@ -170,7 +135,7 @@ void LocatorFilterTest::testLocatorFilter_data() QTest::newRow("CppFunctionsFilter-Sorting") << testFile - << cppFunctionsFilter + << MatcherType::Functions << "pos" << ResultDataList{ ResultData("positiveNumber()", testFileShort), @@ -181,7 +146,7 @@ void LocatorFilterTest::testLocatorFilter_data() QTest::newRow("CppFunctionsFilter-arguments") << testFile - << cppFunctionsFilter + << MatcherType::Functions << "function*bool" << ResultDataList{ ResultData("functionDefinedInClass(bool, int)", @@ -197,7 +162,7 @@ void LocatorFilterTest::testLocatorFilter_data() QTest::newRow("CppFunctionsFilter-WithNamespacePrefix") << testFile - << cppFunctionsFilter + << MatcherType::Functions << "mynamespace::" << ResultDataList{ ResultData("MyClass()", "MyNamespace::MyClass (file1.cpp)"), @@ -212,7 +177,7 @@ void LocatorFilterTest::testLocatorFilter_data() QTest::newRow("CppFunctionsFilter-WithClassPrefix") << testFile - << cppFunctionsFilter + << MatcherType::Functions << "MyClass::func" << ResultDataList{ ResultData("functionDefinedInClass(bool, int)", @@ -233,7 +198,7 @@ void LocatorFilterTest::testLocatorFilter_data() QTest::newRow("CppClassesFilter") << testFile - << cppClassesFilter + << MatcherType::Classes << "myclass" << ResultDataList{ ResultData("MyClass", ""), @@ -243,7 +208,7 @@ void LocatorFilterTest::testLocatorFilter_data() QTest::newRow("CppClassesFilter-WithNamespacePrefix") << testFile - << cppClassesFilter + << MatcherType::Classes << "mynamespace::" << ResultDataList{ ResultData("MyClass", "MyNamespace") @@ -252,7 +217,7 @@ void LocatorFilterTest::testLocatorFilter_data() // all symbols in the left column are expected to be fully qualified. QTest::newRow("CppLocatorFilter-filtered") << testFile - << cppLocatorFilter + << MatcherType::AllSymbols << "my" << ResultDataList{ ResultData("MyClass", testFileShort), @@ -278,7 +243,7 @@ void LocatorFilterTest::testLocatorFilter_data() QTest::newRow("CppClassesFilter-ObjC") << objTestFile - << cppClassesFilter + << MatcherType::Classes << "M" << ResultDataList{ ResultData("MyClass", objTestFileShort), @@ -289,7 +254,7 @@ void LocatorFilterTest::testLocatorFilter_data() QTest::newRow("CppFunctionsFilter-ObjC") << objTestFile - << cppFunctionsFilter + << MatcherType::Functions << "M" << ResultDataList{ ResultData("anotherMethod", "MyClass (file1.mm)"), @@ -319,7 +284,6 @@ void LocatorFilterTest::testCurrentDocumentFilter() ResultData("functionDeclaredOnly()", "MyClass"), ResultData("functionDefinedInClass(bool, int)", "MyClass"), ResultData("functionDefinedOutSideClass(char)", "MyClass"), - ResultData("functionDefinedOutSideClass(char)", "MyClass"), ResultData("int myVariable", "MyNamespace"), ResultData("myFunction(bool, int)", "MyNamespace"), ResultData("MyEnum", "MyNamespace"), @@ -330,9 +294,6 @@ void LocatorFilterTest::testCurrentDocumentFilter() ResultData("functionDeclaredOnly()", "MyNamespace::MyClass"), ResultData("functionDefinedInClass(bool, int)", "MyNamespace::MyClass"), ResultData("functionDefinedOutSideClass(char)", "MyNamespace::MyClass"), - ResultData("functionDefinedOutSideClassAndNamespace(float)", - "MyNamespace::MyClass"), - ResultData("functionDefinedOutSideClass(char)", "MyNamespace::MyClass"), ResultData("functionDefinedOutSideClassAndNamespace(float)", "MyNamespace::MyClass"), ResultData("int myVariable", ""), @@ -345,11 +306,12 @@ void LocatorFilterTest::testCurrentDocumentFilter() ResultData("functionDeclaredOnly()", "::MyClass"), ResultData("functionDefinedInClass(bool, int)", "::MyClass"), ResultData("functionDefinedOutSideClass(char)", "::MyClass"), - ResultData("functionDefinedOutSideClass(char)", "::MyClass"), ResultData("main()", ""), }; - CppCurrentDocumentFilterTestCase(testFile, expectedResults); + Tests::VerifyCleanCppModelManager verify; + CppCurrentDocumentFilterTestCase( + testFile, LocatorMatcher::matchers(MatcherType::CurrentDocumentSymbols), expectedResults); } void LocatorFilterTest::testCurrentDocumentHighlighting() @@ -372,8 +334,8 @@ void LocatorFilterTest::testCurrentDocumentHighlighting() }; Tests::VerifyCleanCppModelManager verify; - - CppCurrentDocumentFilterTestCase(testFile, expectedResults, searchText); + CppCurrentDocumentFilterTestCase(testFile, + LocatorMatcher::matchers(MatcherType::CurrentDocumentSymbols), expectedResults, searchText); } void LocatorFilterTest::testFunctionsFilterHighlighting() @@ -394,12 +356,9 @@ void LocatorFilterTest::testFunctionsFilterHighlighting() " ~~~ ") }; - CppModelManager *cppModelManager = CppModelManager::instance(); - ILocatorFilter *filter = cppModelManager->functionsFilter(); - Tests::VerifyCleanCppModelManager verify; - - CppLocatorFilterTestCase(filter, testFile, searchText, expectedResults); + CppLocatorFilterTestCase(LocatorMatcher::matchers(MatcherType::Functions), testFile, + searchText, expectedResults); } } // namespace CppEditor::Internal diff --git a/src/plugins/cppeditor/cppmodelmanager.cpp b/src/plugins/cppeditor/cppmodelmanager.cpp index 8fc44622ab2..2b5d4e3fb82 100644 --- a/src/plugins/cppeditor/cppmodelmanager.cpp +++ b/src/plugins/cppeditor/cppmodelmanager.cpp @@ -6,10 +6,10 @@ #include "abstracteditorsupport.h" #include "baseeditordocumentprocessor.h" #include "compileroptionsbuilder.h" +#include "cppbuiltinmodelmanagersupport.h" #include "cppcanonicalsymbol.h" #include "cppcodemodelinspectordumper.h" #include "cppcodemodelsettings.h" -#include "cppcurrentdocumentfilter.h" #include "cppeditorconstants.h" #include "cppeditortr.h" #include "cppfindreferences.h" @@ -17,7 +17,6 @@ #include "cppindexingsupport.h" #include "cpplocatordata.h" #include "cpplocatorfilter.h" -#include "cppbuiltinmodelmanagersupport.h" #include "cppprojectfile.h" #include "cppsourceprocessor.h" #include "cpptoolsjsextension.h" @@ -37,11 +36,15 @@ #include #include #include + #include #include #include + #include +#include + #include #include #include @@ -49,10 +52,10 @@ #include #include #include +#include #include #include #include -#include #include #include @@ -60,9 +63,8 @@ #include #include #include +#include #include -#include -#include #include #include @@ -90,6 +92,7 @@ #include #endif +using namespace Core; using namespace CPlusPlus; using namespace ProjectExplorer; using namespace Utils; @@ -155,7 +158,7 @@ public: class CppModelManagerPrivate { public: - void setupWatcher(const QFuture &future, ProjectExplorer::Project *project, + void setupWatcher(const QFuture &future, Project *project, ProjectData *projectData, CppModelManager *q); // Snapshot @@ -164,15 +167,15 @@ public: // Project integration QReadWriteLock m_projectLock; - QHash m_projectData; - QMap > m_fileToProjectParts; + QHash m_projectData; + QMap > m_fileToProjectParts; QMap m_projectPartIdToProjectProjectPart; // The members below are cached/(re)calculated from the projects and/or their project parts bool m_dirty; - Utils::FilePaths m_projectFiles; - ProjectExplorer::HeaderPaths m_headerPaths; - ProjectExplorer::Macros m_definedMacros; + FilePaths m_projectFiles; + HeaderPaths m_headerPaths; + Macros m_definedMacros; // Editor integration mutable QMutex m_cppEditorDocumentsMutex; @@ -201,12 +204,12 @@ public: QTimer m_fallbackProjectPartTimer; CppLocatorData m_locatorData; - std::unique_ptr m_locatorFilter; - std::unique_ptr m_classesFilter; - std::unique_ptr m_includesFilter; - std::unique_ptr m_functionsFilter; - std::unique_ptr m_symbolsFindFilter; - std::unique_ptr m_currentDocumentFilter; + std::unique_ptr m_locatorFilter; + std::unique_ptr m_classesFilter; + std::unique_ptr m_includesFilter; + std::unique_ptr m_functionsFilter; + std::unique_ptr m_symbolsFindFilter; + std::unique_ptr m_currentDocumentFilter; QList m_diagnosticMessages; }; @@ -338,7 +341,7 @@ void CppModelManager::findUsages(const CursorInEditor &data, Backend backend) void CppModelManager::switchHeaderSource(bool inNextSplit, Backend backend) { - const Core::IDocument *currentDocument = Core::EditorManager::currentDocument(); + const IDocument *currentDocument = EditorManager::currentDocument(); QTC_ASSERT(currentDocument, return); instance()->modelManagerSupport(backend)->switchHeaderSource(currentDocument->filePath(), inNextSplit); @@ -346,16 +349,16 @@ void CppModelManager::switchHeaderSource(bool inNextSplit, Backend backend) void CppModelManager::showPreprocessedFile(bool inNextSplit) { - const Core::IDocument *doc = Core::EditorManager::currentDocument(); + const IDocument *doc = EditorManager::currentDocument(); QTC_ASSERT(doc, return); static const auto showError = [](const QString &reason) { - Core::MessageManager::writeFlashing(Tr::tr("Cannot show preprocessed file: %1") - .arg(reason)); + MessageManager::writeFlashing(Tr::tr("Cannot show preprocessed file: %1") + .arg(reason)); }; static const auto showFallbackWarning = [](const QString &reason) { - Core::MessageManager::writeSilently( - Tr::tr("Falling back to built-in preprocessor: %1").arg(reason)); + MessageManager::writeSilently(Tr::tr("Falling back to built-in preprocessor: %1") + .arg(reason)); }; static const auto saveAndOpen = [](const FilePath &filePath, const QByteArray &contents, bool inNextSplit) { @@ -446,10 +449,10 @@ void CppModelManager::showPreprocessedFile(bool inNextSplit) compilerArgs.append("/E"); compilerArgs.append(filePath.toUserOutput()); const CommandLine compilerCommandLine(tc->compilerCommand(), compilerArgs); - const auto compiler = new QtcProcess(instance()); + const auto compiler = new Process(instance()); compiler->setCommand(compilerCommandLine); compiler->setEnvironment(project->activeTarget()->activeBuildConfiguration()->environment()); - connect(compiler, &QtcProcess::done, instance(), [compiler, outFilePath, inNextSplit, + connect(compiler, &Process::done, instance(), [compiler, outFilePath, inNextSplit, useBuiltinPreprocessor, isMsvc] { compiler->deleteLater(); if (compiler->result() != ProcessResult::FinishedWithSuccess) { @@ -469,24 +472,24 @@ class FindUnusedActionsEnabledSwitcher { public: FindUnusedActionsEnabledSwitcher() - : actions{Core::ActionManager::command("CppTools.FindUnusedFunctions"), - Core::ActionManager::command("CppTools.FindUnusedFunctionsInSubProject")} + : actions{ActionManager::command("CppTools.FindUnusedFunctions"), + ActionManager::command("CppTools.FindUnusedFunctionsInSubProject")} { - for (Core::Command * const action : actions) + for (Command * const action : actions) action->action()->setEnabled(false); } ~FindUnusedActionsEnabledSwitcher() { - for (Core::Command * const action : actions) + for (Command * const action : actions) action->action()->setEnabled(true); } private: - const QList actions; + const QList actions; }; using FindUnusedActionsEnabledSwitcherPtr = std::shared_ptr; static void checkNextFunctionForUnused( - const QPointer &search, + const QPointer &search, const std::shared_ptr> &findRefsFuture, const FindUnusedActionsEnabledSwitcherPtr &actionsSwitcher) { @@ -531,54 +534,43 @@ void CppModelManager::findUnusedFunctions(const FilePath &folder) const auto actionsSwitcher = std::make_shared(); // Step 1: Employ locator to find all functions - Core::ILocatorFilter *const functionsFilter - = Utils::findOrDefault(Core::ILocatorFilter::allLocatorFilters(), - Utils::equal(&Core::ILocatorFilter::id, - Id(Constants::FUNCTIONS_FILTER_ID))); - QTC_ASSERT(functionsFilter, return); - const QPointer search - = Core::SearchResultWindow::instance() - ->startNewSearch(Tr::tr("Find Unused Functions"), + LocatorMatcher *matcher = new LocatorMatcher; + matcher->setTasks(LocatorMatcher::matchers(MatcherType::Functions)); + const QPointer search + = SearchResultWindow::instance()->startNewSearch(Tr::tr("Find Unused Functions"), {}, {}, - Core::SearchResultWindow::SearchOnly, - Core::SearchResultWindow::PreserveCaseDisabled, + SearchResultWindow::SearchOnly, + SearchResultWindow::PreserveCaseDisabled, "CppEditor"); - connect(search, &Core::SearchResult::activated, [](const Core::SearchResultItem &item) { - Core::EditorManager::openEditorAtSearchResult(item); + matcher->setParent(search); + connect(search, &SearchResult::activated, [](const SearchResultItem &item) { + EditorManager::openEditorAtSearchResult(item); }); - Core::SearchResultWindow::instance()->popup(Core::IOutputPane::ModeSwitch - | Core::IOutputPane::WithFocus); - const auto locatorWatcher = new QFutureWatcher(search); - functionsFilter->prepareSearch({}); - connect(search, &Core::SearchResult::canceled, locatorWatcher, [locatorWatcher] { - locatorWatcher->cancel(); - }); - connect(locatorWatcher, &QFutureWatcher::finished, search, - [locatorWatcher, search, folder, actionsSwitcher] { - locatorWatcher->deleteLater(); - if (locatorWatcher->isCanceled()) { + SearchResultWindow::instance()->popup(IOutputPane::ModeSwitch | IOutputPane::WithFocus); + connect(search, &SearchResult::canceled, matcher, [matcher] { delete matcher; }); + connect(matcher, &LocatorMatcher::done, search, + [matcher, search, folder, actionsSwitcher](bool success) { + matcher->deleteLater(); + if (!success) { search->finishSearch(true); return; } Links links; - for (int i = 0; i < locatorWatcher->future().resultCount(); ++i) { - const Core::LocatorFilterEntry &entry = locatorWatcher->resultAt(i); + const LocatorFilterEntries entries = matcher->outputData(); + for (const LocatorFilterEntry &entry : entries) { static const QStringList prefixBlacklist{"main(", "~", "qHash(", "begin()", "end()", "cbegin()", "cend()", "constBegin()", "constEnd()"}; if (Utils::anyOf(prefixBlacklist, [&entry](const QString &prefix) { return entry.displayName.startsWith(prefix); })) { continue; } - Link link; - if (entry.internalData.canConvert()) - link = qvariant_cast(entry.internalData); - else if (const auto item = qvariant_cast(entry.internalData)) - link = Link(item->filePath(), item->line(), item->column()); - + if (!entry.linkForEditor) + continue; + const Link link = *entry.linkForEditor; if (link.hasValidTarget() && link.targetFilePath.isReadableFile() && (folder.isEmpty() || link.targetFilePath.isChildOf(folder)) - && SessionManager::projectForFile(link.targetFilePath)) { + && ProjectManager::projectForFile(link.targetFilePath)) { links << link; } } @@ -593,12 +585,11 @@ void CppModelManager::findUnusedFunctions(const FilePath &folder) })); search->setUserData(remainingAndActiveLinks); const auto findRefsFuture = std::make_shared>(); - Core::FutureProgress *const progress - = Core::ProgressManager::addTask(findRefsFuture->future(), - Tr::tr("Finding Unused Functions"), - "CppEditor.FindUnusedFunctions"); + FutureProgress *const progress = ProgressManager::addTask(findRefsFuture->future(), + Tr::tr("Finding Unused Functions"), + "CppEditor.FindUnusedFunctions"); connect(progress, - &Core::FutureProgress::canceled, + &FutureProgress::canceled, search, [search, future = std::weak_ptr>(findRefsFuture)] { search->finishSearch(true); @@ -608,7 +599,7 @@ void CppModelManager::findUnusedFunctions(const FilePath &folder) } }); findRefsFuture->setProgressRange(0, links.size()); - connect(search, &Core::SearchResult::canceled, [findRefsFuture] { + connect(search, &SearchResult::canceled, [findRefsFuture] { findRefsFuture->cancel(); findRefsFuture->reportFinished(); }); @@ -619,13 +610,10 @@ void CppModelManager::findUnusedFunctions(const FilePath &folder) for (int i = 0; i < inFlightCount; ++i) checkNextFunctionForUnused(search, findRefsFuture, actionsSwitcher); }); - locatorWatcher->setFuture( - Utils::runAsync([functionsFilter](QFutureInterface &future) { - future.reportResults(functionsFilter->matchesFor(future, {})); - })); + matcher->start(); } -void CppModelManager::checkForUnusedSymbol(Core::SearchResult *search, +void CppModelManager::checkForUnusedSymbol(SearchResult *search, const Link &link, CPlusPlus::Symbol *symbol, const CPlusPlus::LookupContext &context, @@ -785,62 +773,62 @@ static void setFilter(std::unique_ptr &filter, filter = std::move(newFilter); } -void CppModelManager::setLocatorFilter(std::unique_ptr &&filter) +void CppModelManager::setLocatorFilter(std::unique_ptr &&filter) { setFilter(d->m_locatorFilter, std::move(filter)); } -void CppModelManager::setClassesFilter(std::unique_ptr &&filter) +void CppModelManager::setClassesFilter(std::unique_ptr &&filter) { setFilter(d->m_classesFilter, std::move(filter)); } -void CppModelManager::setIncludesFilter(std::unique_ptr &&filter) +void CppModelManager::setIncludesFilter(std::unique_ptr &&filter) { setFilter(d->m_includesFilter, std::move(filter)); } -void CppModelManager::setFunctionsFilter(std::unique_ptr &&filter) +void CppModelManager::setFunctionsFilter(std::unique_ptr &&filter) { setFilter(d->m_functionsFilter, std::move(filter)); } -void CppModelManager::setSymbolsFindFilter(std::unique_ptr &&filter) +void CppModelManager::setSymbolsFindFilter(std::unique_ptr &&filter) { setFilter(d->m_symbolsFindFilter, std::move(filter)); } -void CppModelManager::setCurrentDocumentFilter(std::unique_ptr &&filter) +void CppModelManager::setCurrentDocumentFilter(std::unique_ptr &&filter) { setFilter(d->m_currentDocumentFilter, std::move(filter)); } -Core::ILocatorFilter *CppModelManager::locatorFilter() const +ILocatorFilter *CppModelManager::locatorFilter() const { return d->m_locatorFilter.get(); } -Core::ILocatorFilter *CppModelManager::classesFilter() const +ILocatorFilter *CppModelManager::classesFilter() const { return d->m_classesFilter.get(); } -Core::ILocatorFilter *CppModelManager::includesFilter() const +ILocatorFilter *CppModelManager::includesFilter() const { return d->m_includesFilter.get(); } -Core::ILocatorFilter *CppModelManager::functionsFilter() const +ILocatorFilter *CppModelManager::functionsFilter() const { return d->m_functionsFilter.get(); } -Core::IFindFilter *CppModelManager::symbolsFindFilter() const +IFindFilter *CppModelManager::symbolsFindFilter() const { return d->m_symbolsFindFilter.get(); } -Core::ILocatorFilter *CppModelManager::currentDocumentFilter() const +ILocatorFilter *CppModelManager::currentDocumentFilter() const { return d->m_currentDocumentFilter.get(); } @@ -880,7 +868,7 @@ CppModelManager *CppModelManager::instance() void CppModelManager::registerJsExtension() { - Core::JsExpander::registerGlobalObject("Cpp", [this] { + JsExpander::registerGlobalObject("Cpp", [this] { return new CppToolsJsExtension(&d->m_locatorData); }); } @@ -888,10 +876,10 @@ void CppModelManager::registerJsExtension() void CppModelManager::initCppTools() { // Objects - connect(Core::VcsManager::instance(), &Core::VcsManager::repositoryChanged, + connect(VcsManager::instance(), &VcsManager::repositoryChanged, this, &CppModelManager::updateModifiedSourceFiles); - connect(Core::DocumentManager::instance(), &Core::DocumentManager::filesChangedInternally, - [this](const FilePaths &filePaths) { + connect(DocumentManager::instance(), &DocumentManager::filesChangedInternally, + this, [this](const FilePaths &filePaths) { updateSourceFiles(toSet(filePaths)); }); @@ -902,13 +890,25 @@ void CppModelManager::initCppTools() &d->m_locatorData, &CppLocatorData::onAboutToRemoveFiles); // Set up builtin filters - setLocatorFilter(std::make_unique(&d->m_locatorData)); - setClassesFilter(std::make_unique(&d->m_locatorData)); + setLocatorFilter(std::make_unique()); + setClassesFilter(std::make_unique()); setIncludesFilter(std::make_unique()); - setFunctionsFilter(std::make_unique(&d->m_locatorData)); + setFunctionsFilter(std::make_unique()); setSymbolsFindFilter(std::make_unique(this)); - setCurrentDocumentFilter( - std::make_unique(this)); + setCurrentDocumentFilter(std::make_unique()); + // Setup matchers + LocatorMatcher::addMatcherCreator(MatcherType::AllSymbols, [] { + return cppMatchers(MatcherType::AllSymbols); + }); + LocatorMatcher::addMatcherCreator(MatcherType::Classes, [] { + return cppMatchers(MatcherType::Classes); + }); + LocatorMatcher::addMatcherCreator(MatcherType::Functions, [] { + return cppMatchers(MatcherType::Functions); + }); + LocatorMatcher::addMatcherCreator(MatcherType::CurrentDocumentSymbols, [] { + return cppMatchers(MatcherType::CurrentDocumentSymbols); + }); } CppModelManager::CppModelManager() @@ -924,7 +924,7 @@ CppModelManager::CppModelManager() d->m_enableGC = true; // Visual C++ has 1MiB, macOSX has 512KiB - if (Utils::HostOsInfo::isWindowsHost() || Utils::HostOsInfo::isMacHost()) + if (HostOsInfo::isWindowsHost() || HostOsInfo::isMacHost()) d->m_threadPool.setStackSize(2 * 1024 * 1024); qRegisterMetaType >(); @@ -940,23 +940,23 @@ CppModelManager::CppModelManager() d->m_delayedGcTimer.setSingleShot(true); connect(&d->m_delayedGcTimer, &QTimer::timeout, this, &CppModelManager::GC); - auto sessionManager = ProjectExplorer::SessionManager::instance(); - connect(sessionManager, &ProjectExplorer::SessionManager::projectAdded, + auto projectManager = ProjectManager::instance(); + connect(projectManager, &ProjectManager::projectAdded, this, &CppModelManager::onProjectAdded); - connect(sessionManager, &ProjectExplorer::SessionManager::aboutToRemoveProject, + connect(projectManager, &ProjectManager::aboutToRemoveProject, this, &CppModelManager::onAboutToRemoveProject); - connect(sessionManager, &ProjectExplorer::SessionManager::aboutToLoadSession, + connect(SessionManager::instance(), &SessionManager::aboutToLoadSession, this, &CppModelManager::onAboutToLoadSession); - connect(sessionManager, &ProjectExplorer::SessionManager::startupProjectChanged, + connect(projectManager, &ProjectManager::startupProjectChanged, this, &CppModelManager::onActiveProjectChanged); - connect(Core::EditorManager::instance(), &Core::EditorManager::currentEditorChanged, + connect(EditorManager::instance(), &EditorManager::currentEditorChanged, this, &CppModelManager::onCurrentEditorChanged); - connect(Core::DocumentManager::instance(), &Core::DocumentManager::allDocumentsRenamed, + connect(DocumentManager::instance(), &DocumentManager::allDocumentsRenamed, this, &CppModelManager::renameIncludes); - connect(Core::ICore::instance(), &Core::ICore::coreAboutToClose, + connect(ICore::instance(), &ICore::coreAboutToClose, this, &CppModelManager::onCoreAboutToClose); d->m_fallbackProjectPartTimer.setSingleShot(true); @@ -1002,7 +1002,7 @@ Document::Ptr CppModelManager::document(const FilePath &filePath) const /// Replace the document in the snapshot. /// -/// \returns true if successful, false if the new document is out-dated. +/// Returns true if successful, false if the new document is out-dated. bool CppModelManager::replaceDocument(Document::Ptr newDoc) { QMutexLocker locker(&d->m_snapshotMutex); @@ -1041,13 +1041,13 @@ FilePaths CppModelManager::internalProjectFiles() const return files; } -ProjectExplorer::HeaderPaths CppModelManager::internalHeaderPaths() const +HeaderPaths CppModelManager::internalHeaderPaths() const { - ProjectExplorer::HeaderPaths headerPaths; + HeaderPaths headerPaths; for (const ProjectData &projectData: std::as_const(d->m_projectData)) { for (const ProjectPart::ConstPtr &part : projectData.projectInfo->projectParts()) { - for (const ProjectExplorer::HeaderPath &path : part->headerPaths) { - ProjectExplorer::HeaderPath hp(QDir::cleanPath(path.path), path.type); + for (const HeaderPath &path : part->headerPaths) { + HeaderPath hp(QDir::cleanPath(path.path), path.type); if (!headerPaths.contains(hp)) headerPaths.push_back(std::move(hp)); } @@ -1056,8 +1056,7 @@ ProjectExplorer::HeaderPaths CppModelManager::internalHeaderPaths() const return headerPaths; } -static void addUnique(const ProjectExplorer::Macros &newMacros, - ProjectExplorer::Macros ¯os, +static void addUnique(const Macros &newMacros, Macros ¯os, QSet &alreadyIn) { for (const ProjectExplorer::Macro ¯o : newMacros) { @@ -1068,9 +1067,9 @@ static void addUnique(const ProjectExplorer::Macros &newMacros, } } -ProjectExplorer::Macros CppModelManager::internalDefinedMacros() const +Macros CppModelManager::internalDefinedMacros() const { - ProjectExplorer::Macros macros; + Macros macros; QSet alreadyIn; for (const ProjectData &projectData : std::as_const(d->m_projectData)) { for (const ProjectPart::ConstPtr &part : projectData.projectInfo->projectParts()) { @@ -1093,7 +1092,7 @@ void CppModelManager::dumpModelManagerConfiguration(const QString &logFileId) dumper.dumpSnapshot(globalSnapshot, globalSnapshotTitle, /*isGlobalSnapshot=*/ true); dumper.dumpWorkingCopy(workingCopy()); dumper.dumpMergedEntities(headerPaths(), - ProjectExplorer:: Macro::toByteArray(definedMacros())); + ProjectExplorer::Macro::toByteArray(definedMacros())); } QSet CppModelManager::abstractEditorSupports() const @@ -1268,8 +1267,8 @@ static QSet filteredFilesRemoved(const QSet &files, int fileSi const QString msg = Tr::tr("C++ Indexer: Skipping file \"%1\" " "because its path matches the ignore pattern.") .arg(filePath.displayName()); - QMetaObject::invokeMethod(Core::MessageManager::instance(), - [msg]() { Core::MessageManager::writeSilently(msg); }); + QMetaObject::invokeMethod(MessageManager::instance(), + [msg] { MessageManager::writeSilently(msg); }); skip = true; break; } @@ -1304,7 +1303,7 @@ ProjectInfoList CppModelManager::projectInfos() const [](const ProjectData &d) { return d.projectInfo; }); } -ProjectInfo::ConstPtr CppModelManager::projectInfo(ProjectExplorer::Project *project) const +ProjectInfo::ConstPtr CppModelManager::projectInfo(Project *project) const { QReadLocker locker(&d->m_projectLock); return d->m_projectData.value(project).projectInfo; @@ -1424,8 +1423,7 @@ void CppModelManager::recalculateProjectPartMappings() d->m_symbolFinder.clearCache(); } -void CppModelManagerPrivate::setupWatcher(const QFuture &future, - ProjectExplorer::Project *project, +void CppModelManagerPrivate::setupWatcher(const QFuture &future, Project *project, ProjectData *projectData, CppModelManager *q) { projectData->indexer = new QFutureWatcher(q); @@ -1446,10 +1444,10 @@ void CppModelManagerPrivate::setupWatcher(const QFuture &future, void CppModelManager::updateCppEditorDocuments(bool projectsUpdated) const { // Refresh visible documents - QSet visibleCppEditorDocuments; - const QList editors = Core::EditorManager::visibleEditors(); - for (Core::IEditor *editor: editors) { - if (Core::IDocument *document = editor->document()) { + QSet visibleCppEditorDocuments; + const QList editors = EditorManager::visibleEditors(); + for (IEditor *editor: editors) { + if (IDocument *document = editor->document()) { const FilePath filePath = document->filePath(); if (CppEditorDocumentHandle *theCppEditorDocument = cppEditorDocument(filePath)) { visibleCppEditorDocuments.insert(document); @@ -1459,10 +1457,10 @@ void CppModelManager::updateCppEditorDocuments(bool projectsUpdated) const } // Mark invisible documents dirty - QSet invisibleCppEditorDocuments - = Utils::toSet(Core::DocumentModel::openedDocuments()); + QSet invisibleCppEditorDocuments + = Utils::toSet(DocumentModel::openedDocuments()); invisibleCppEditorDocuments.subtract(visibleCppEditorDocuments); - for (Core::IDocument *document : std::as_const(invisibleCppEditorDocuments)) { + for (IDocument *document : std::as_const(invisibleCppEditorDocuments)) { const FilePath filePath = document->filePath(); if (CppEditorDocumentHandle *theCppEditorDocument = cppEditorDocument(filePath)) { const CppEditorDocumentHandle::RefreshReason refreshReason = projectsUpdated @@ -1483,7 +1481,7 @@ QFuture CppModelManager::updateProjectInfo(const ProjectInfo::ConstPtr &ne QStringList removedProjectParts; bool filesRemoved = false; - ProjectExplorer::Project * const project = projectForProjectInfo(*newProjectInfo); + Project * const project = projectForProjectInfo(*newProjectInfo); if (!project) return {}; @@ -1589,20 +1587,20 @@ ProjectPart::ConstPtr CppModelManager::projectPartForId(const QString &projectPa return d->m_projectPartIdToProjectProjectPart.value(projectPartId); } -QList CppModelManager::projectPart(const Utils::FilePath &fileName) const +QList CppModelManager::projectPart(const FilePath &fileName) const { QReadLocker locker(&d->m_projectLock); return d->m_fileToProjectParts.value(fileName.canonicalPath()); } QList CppModelManager::projectPartFromDependencies( - const Utils::FilePath &fileName) const + const FilePath &fileName) const { QSet parts; - const Utils::FilePaths deps = snapshot().filesDependingOn(fileName); + const FilePaths deps = snapshot().filesDependingOn(fileName); QReadLocker locker(&d->m_projectLock); - for (const Utils::FilePath &dep : deps) + for (const FilePath &dep : deps) parts.unite(Utils::toSet(d->m_fileToProjectParts.value(dep.canonicalPath()))); return parts.values(); @@ -1614,7 +1612,7 @@ ProjectPart::ConstPtr CppModelManager::fallbackProjectPart() return d->m_fallbackProjectPart; } -bool CppModelManager::isCppEditor(Core::IEditor *editor) +bool CppModelManager::isCppEditor(IEditor *editor) { return editor->context().contains(ProjectExplorer::Constants::CXX_LANGUAGE_ID); } @@ -1647,7 +1645,7 @@ void CppModelManager::emitAbstractEditorSupportRemoved(const QString &filePath) emit abstractEditorSupportRemoved(filePath); } -void CppModelManager::onProjectAdded(ProjectExplorer::Project *) +void CppModelManager::onProjectAdded(Project *) { QWriteLocker locker(&d->m_projectLock); d->m_dirty = true; @@ -1667,7 +1665,7 @@ static QStringList removedProjectParts(const QStringList &before, const QStringL return Utils::toList(b); } -void CppModelManager::onAboutToRemoveProject(ProjectExplorer::Project *project) +void CppModelManager::onAboutToRemoveProject(Project *project) { QStringList idsOfRemovedProjectParts; @@ -1689,7 +1687,7 @@ void CppModelManager::onAboutToRemoveProject(ProjectExplorer::Project *project) delayedGC(); } -void CppModelManager::onActiveProjectChanged(ProjectExplorer::Project *project) +void CppModelManager::onActiveProjectChanged(Project *project) { if (!project) return; // Last project closed. @@ -1711,7 +1709,7 @@ void CppModelManager::onSourceFilesRefreshed() const } } -void CppModelManager::onCurrentEditorChanged(Core::IEditor *editor) +void CppModelManager::onCurrentEditorChanged(IEditor *editor) { if (!editor || !editor->document()) return; @@ -1752,7 +1750,7 @@ QSet CppModelManager::dependingInternalTargets(const FilePath &file) co return result; } -QSet CppModelManager::internalTargets(const Utils::FilePath &filePath) const +QSet CppModelManager::internalTargets(const FilePath &filePath) const { const QList projectParts = projectPart(filePath); // if we have no project parts it's most likely a header with declarations only and CMake based @@ -1761,14 +1759,13 @@ QSet CppModelManager::internalTargets(const Utils::FilePath &filePath) QSet targets; for (const ProjectPart::ConstPtr &part : projectParts) { targets.insert(part->buildSystemTarget); - if (part->buildTargetType != ProjectExplorer::BuildTargetType::Executable) + if (part->buildTargetType != BuildTargetType::Executable) targets.unite(dependingInternalTargets(filePath)); } return targets; } -void CppModelManager::renameIncludes(const Utils::FilePath &oldFilePath, - const Utils::FilePath &newFilePath) +void CppModelManager::renameIncludes(const FilePath &oldFilePath, const FilePath &newFilePath) { if (oldFilePath.isEmpty() || newFilePath.isEmpty()) return; @@ -1815,7 +1812,7 @@ void CppModelManager::renameIncludes(const Utils::FilePath &oldFilePath, const QTextBlock &block = file->document()->findBlockByNumber(loc.second - 1); const int replaceStart = block.text().indexOf(oldFileName); if (replaceStart > -1) { - Utils::ChangeSet changeSet; + ChangeSet changeSet; changeSet.replace(block.position() + replaceStart, block.position() + replaceStart + oldFileName.length(), newFileName); @@ -1843,13 +1840,13 @@ static const char *belongingClassName(const Function *function) return nullptr; } -QSet CppModelManager::symbolsInFiles(const QSet &files) const +QSet CppModelManager::symbolsInFiles(const QSet &files) const { QSet uniqueSymbols; const Snapshot cppSnapShot = snapshot(); // Iterate over the files and get interesting symbols - for (const Utils::FilePath &file : files) { + for (const FilePath &file : files) { // Add symbols from the C++ code model const CPlusPlus::Document::Ptr doc = cppSnapShot.document(file); if (!doc.isNull() && doc->control()) { @@ -1881,7 +1878,7 @@ QSet CppModelManager::symbolsInFiles(const QSet &files void CppModelManager::onCoreAboutToClose() { - Core::ProgressManager::cancelTasks(Constants::TASK_INDEX); + ProgressManager::cancelTasks(Constants::TASK_INDEX); d->m_enableGC = false; } @@ -1891,27 +1888,27 @@ void CppModelManager::setupFallbackProjectPart() RawProjectPart rpp; rpp.setMacros(definedMacros()); rpp.setHeaderPaths(headerPaths()); - rpp.setQtVersion(Utils::QtMajorVersion::Qt5); + rpp.setQtVersion(QtMajorVersion::Qt5); // Do not activate ObjectiveCExtensions since this will lead to the // "objective-c++" language option for a project-less *.cpp file. - Utils::LanguageExtensions langExtensions = Utils::LanguageExtension::All; - langExtensions &= ~Utils::LanguageExtensions(Utils::LanguageExtension::ObjectiveC); + LanguageExtensions langExtensions = LanguageExtension::All; + langExtensions &= ~LanguageExtensions(LanguageExtension::ObjectiveC); // TODO: Use different fallback toolchain for different kinds of files? const Kit * const defaultKit = KitManager::isLoaded() ? KitManager::defaultKit() : nullptr; const ToolChain * const defaultTc = defaultKit ? ToolChainKitAspect::cxxToolChain(defaultKit) : nullptr; if (defaultKit && defaultTc) { - Utils::FilePath sysroot = SysRootKitAspect::sysRoot(defaultKit); + FilePath sysroot = SysRootKitAspect::sysRoot(defaultKit); if (sysroot.isEmpty()) - sysroot = Utils::FilePath::fromString(defaultTc->sysRoot()); + sysroot = FilePath::fromString(defaultTc->sysRoot()); Utils::Environment env = defaultKit->buildEnvironment(); tcInfo = ToolChainInfo(defaultTc, sysroot, env); const auto macroInspectionWrapper = [runner = tcInfo.macroInspectionRunner]( const QStringList &flags) { ToolChain::MacroInspectionReport report = runner(flags); - report.languageVersion = Utils::LanguageVersion::LatestCxx; + report.languageVersion = LanguageVersion::LatestCxx; return report; }; tcInfo.macroInspectionRunner = macroInspectionWrapper; @@ -1941,7 +1938,7 @@ void CppModelManager::GC() filesInEditorSupports << abstractEditorSupport->filePath(); Snapshot currentSnapshot = snapshot(); - QSet reachableFiles; + QSet reachableFiles; // The configuration file is part of the project files, which is just fine. // If single files are open, without any project, then there is no need to // keep the configuration file around. @@ -1964,7 +1961,7 @@ void CppModelManager::GC() QStringList notReachableFiles; Snapshot newSnapshot; for (Snapshot::const_iterator it = currentSnapshot.begin(); it != currentSnapshot.end(); ++it) { - const Utils::FilePath &fileName = it.key(); + const FilePath &fileName = it.key(); if (reachableFiles.contains(fileName)) newSnapshot.insert(it.value()); @@ -2001,7 +1998,7 @@ TextEditor::BaseHoverHandler *CppModelManager::createHoverHandler() const } void CppModelManager::followSymbol(const CursorInEditor &data, - const Utils::LinkHandler &processLinkCallback, + const LinkHandler &processLinkCallback, bool resolveTarget, bool inNextSplit, Backend backend) { instance()->modelManagerSupport(backend)->followSymbol(data, processLinkCallback, @@ -2009,7 +2006,7 @@ void CppModelManager::followSymbol(const CursorInEditor &data, } void CppModelManager::followSymbolToType(const CursorInEditor &data, - const Utils::LinkHandler &processLinkCallback, + const LinkHandler &processLinkCallback, bool inNextSplit, Backend backend) { instance()->modelManagerSupport(backend)->followSymbolToType(data, processLinkCallback, @@ -2017,19 +2014,12 @@ void CppModelManager::followSymbolToType(const CursorInEditor &data, } void CppModelManager::switchDeclDef(const CursorInEditor &data, - const Utils::LinkHandler &processLinkCallback, + const LinkHandler &processLinkCallback, Backend backend) { instance()->modelManagerSupport(backend)->switchDeclDef(data, processLinkCallback); } -Core::ILocatorFilter *CppModelManager::createAuxiliaryCurrentDocumentFilter() -{ - const auto filter = new Internal::CppCurrentDocumentFilter(instance()); - filter->makeAuxiliary(); - return filter; -} - BaseEditorDocumentProcessor *CppModelManager::createEditorDocumentProcessor( TextEditor::TextDocument *baseTextDocument) const { @@ -2049,7 +2039,7 @@ FilePaths CppModelManager::projectFiles() return d->m_projectFiles; } -ProjectExplorer::HeaderPaths CppModelManager::headerPaths() +HeaderPaths CppModelManager::headerPaths() { QWriteLocker locker(&d->m_projectLock); ensureUpdated(); @@ -2057,13 +2047,13 @@ ProjectExplorer::HeaderPaths CppModelManager::headerPaths() return d->m_headerPaths; } -void CppModelManager::setHeaderPaths(const ProjectExplorer::HeaderPaths &headerPaths) +void CppModelManager::setHeaderPaths(const HeaderPaths &headerPaths) { QWriteLocker locker(&d->m_projectLock); d->m_headerPaths = headerPaths; } -ProjectExplorer::Macros CppModelManager::definedMacros() +Macros CppModelManager::definedMacros() { QWriteLocker locker(&d->m_projectLock); ensureUpdated(); diff --git a/src/plugins/cppeditor/cppmodelmanager.h b/src/plugins/cppeditor/cppmodelmanager.h index 2929d615e62..3ab9ee26898 100644 --- a/src/plugins/cppeditor/cppmodelmanager.h +++ b/src/plugins/cppeditor/cppmodelmanager.h @@ -196,8 +196,6 @@ public: const CPlusPlus::LookupContext &context, const Utils::LinkHandler &callback); - static Core::ILocatorFilter *createAuxiliaryCurrentDocumentFilter(); - CppIndexingSupport *indexingSupport(); Utils::FilePaths projectFiles(); diff --git a/src/plugins/cppeditor/cppmodelmanager_test.cpp b/src/plugins/cppeditor/cppmodelmanager_test.cpp index 97d13d0704c..c3fb3416469 100644 --- a/src/plugins/cppeditor/cppmodelmanager_test.cpp +++ b/src/plugins/cppeditor/cppmodelmanager_test.cpp @@ -18,7 +18,7 @@ #include #include -#include +#include #include #include @@ -649,7 +649,7 @@ void ModelManagerTest::testGcIfLastCppeditorClosed() QVERIFY(editor); QCOMPARE(Core::DocumentModel::openedDocuments().size(), 1); QVERIFY(mm->isCppEditor(editor)); - QVERIFY(mm->workingCopy().contains(file)); + QVERIFY(mm->workingCopy().get(file)); // Wait until the file is refreshed helper.waitForRefreshedSourceFiles(); @@ -659,7 +659,7 @@ void ModelManagerTest::testGcIfLastCppeditorClosed() helper.waitForFinishedGc(); // Check: File is removed from the snapshpt - QVERIFY(!mm->workingCopy().contains(file)); + QVERIFY(!mm->workingCopy().get(file)); QVERIFY(!mm->snapshot().contains(file)); } @@ -684,13 +684,13 @@ void ModelManagerTest::testDontGcOpenedFiles() // Wait until the file is refreshed and check whether it is in the working copy helper.waitForRefreshedSourceFiles(); - QVERIFY(mm->workingCopy().contains(file)); + QVERIFY(mm->workingCopy().get(file)); // Run the garbage collector mm->GC(); // Check: File is still there - QVERIFY(mm->workingCopy().contains(file)); + QVERIFY(mm->workingCopy().get(file)); QVERIFY(mm->snapshot().contains(file)); // Close editor @@ -1090,7 +1090,7 @@ void ModelManagerTest::testRenameIncludesInEditor() }); QCOMPARE(Core::DocumentModel::openedDocuments().size(), 1); QVERIFY(modelManager->isCppEditor(editor)); - QVERIFY(modelManager->workingCopy().contains(mainFile)); + QVERIFY(modelManager->workingCopy().get(mainFile)); // Test the renaming of a header file where a pragma once guard is present QVERIFY(Core::FileUtils::renameFile(headerWithPragmaOnce, diff --git a/src/plugins/cppeditor/cppoutline.cpp b/src/plugins/cppeditor/cppoutline.cpp index 8c28c7e30a6..0d8883f631d 100644 --- a/src/plugins/cppeditor/cppoutline.cpp +++ b/src/plugins/cppeditor/cppoutline.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include @@ -184,8 +183,8 @@ void CppOutlineWidget::updateIndexNow() void CppOutlineWidget::updateTextCursor(const QModelIndex &proxyIndex) { QModelIndex index = m_proxyModel->mapToSource(proxyIndex); - Utils::LineColumn lineColumn - = m_editor->cppEditorDocument()->outlineModel().lineColumnFromIndex(index); + Utils::Text::Position lineColumn + = m_editor->cppEditorDocument()->outlineModel().positionFromIndex(index); if (!lineColumn.isValid()) return; @@ -194,8 +193,7 @@ void CppOutlineWidget::updateTextCursor(const QModelIndex &proxyIndex) Core::EditorManager::cutForwardNavigationHistory(); Core::EditorManager::addCurrentPositionToNavigationHistory(); - // line has to be 1 based, column 0 based! - m_editor->gotoLine(lineColumn.line, lineColumn.column - 1, true, true); + m_editor->gotoLine(lineColumn.line, lineColumn.column, true, true); m_blockCursorSync = false; } diff --git a/src/plugins/cppeditor/cppoutlinemodel.cpp b/src/plugins/cppeditor/cppoutlinemodel.cpp index d1875b6bb3b..068dc310b1e 100644 --- a/src/plugins/cppeditor/cppoutlinemodel.cpp +++ b/src/plugins/cppeditor/cppoutlinemodel.cpp @@ -9,8 +9,8 @@ #include #include -#include #include +#include #include @@ -103,6 +103,24 @@ public: return name; } + case Qt::ForegroundRole: { + const auto isFwdDecl = [&] { + const FullySpecifiedType type = symbol->type(); + if (type->asForwardClassDeclarationType()) + return true; + if (const Template * const tmpl = type->asTemplateType()) + return tmpl->declaration() && tmpl->declaration()->asForwardClassDeclaration(); + if (type->asObjCForwardClassDeclarationType()) + return true; + if (type->asObjCForwardProtocolDeclarationType()) + return true; + return false; + }; + if (isFwdDecl()) + return Utils::creatorTheme()->color(Utils::Theme::TextColorDisabled); + return TreeItem::data(column, role); + } + case Qt::DecorationRole: return Icons::iconForSymbol(symbol); @@ -220,20 +238,20 @@ Utils::Link OutlineModel::linkFromIndex(const QModelIndex &sourceIndex) const return symbol->toLink(); } -Utils::LineColumn OutlineModel::lineColumnFromIndex(const QModelIndex &sourceIndex) const +Utils::Text::Position OutlineModel::positionFromIndex(const QModelIndex &sourceIndex) const { - Utils::LineColumn lineColumn; + Utils::Text::Position lineColumn; CPlusPlus::Symbol *symbol = symbolFromIndex(sourceIndex); if (!symbol) return lineColumn; lineColumn.line = symbol->line(); - lineColumn.column = symbol->column(); + lineColumn.column = symbol->column() - 1; return lineColumn; } OutlineModel::Range OutlineModel::rangeFromIndex(const QModelIndex &sourceIndex) const { - Utils::LineColumn lineColumn = lineColumnFromIndex(sourceIndex); + Utils::Text::Position lineColumn = positionFromIndex(sourceIndex); return {lineColumn, lineColumn}; } diff --git a/src/plugins/cppeditor/cppoutlinemodel.h b/src/plugins/cppeditor/cppoutlinemodel.h index 08b7adb4424..ce499c91a0a 100644 --- a/src/plugins/cppeditor/cppoutlinemodel.h +++ b/src/plugins/cppeditor/cppoutlinemodel.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include @@ -44,10 +45,11 @@ public: bool isGenerated(const QModelIndex &sourceIndex) const; Utils::Link linkFromIndex(const QModelIndex &sourceIndex) const; - Utils::LineColumn lineColumnFromIndex(const QModelIndex &sourceIndex) const; - using Range = std::pair; + Utils::Text::Position positionFromIndex(const QModelIndex &sourceIndex) const; + using Range = std::pair; Range rangeFromIndex(const QModelIndex &sourceIndex) const; + // line is 1-based and column is 0-based QModelIndex indexForPosition(int line, int column, const QModelIndex &rootIndex = {}) const; private: diff --git a/src/plugins/cppeditor/cpppreprocessordialog.cpp b/src/plugins/cppeditor/cpppreprocessordialog.cpp index b6b1d9d2a63..87afd9761a2 100644 --- a/src/plugins/cppeditor/cpppreprocessordialog.cpp +++ b/src/plugins/cppeditor/cpppreprocessordialog.cpp @@ -7,7 +7,7 @@ #include "cppeditortr.h" #include "cpptoolsreuse.h" -#include +#include #include @@ -27,7 +27,7 @@ CppPreProcessorDialog::CppPreProcessorDialog(const FilePath &filePath, QWidget * setWindowTitle(Tr::tr("Additional C++ Preprocessor Directives")); const QString key = Constants::EXTRA_PREPROCESSOR_DIRECTIVES + m_filePath.toString(); - const QString directives = ProjectExplorer::SessionManager::value(key).toString(); + const QString directives = Core::SessionManager::value(key).toString(); m_editWidget = new TextEditor::SnippetEditorWidget; m_editWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); @@ -56,7 +56,7 @@ int CppPreProcessorDialog::exec() return Rejected; const QString key = Constants::EXTRA_PREPROCESSOR_DIRECTIVES + m_filePath.toString(); - ProjectExplorer::SessionManager::setValue(key, extraPreprocessorDirectives()); + Core::SessionManager::setValue(key, extraPreprocessorDirectives()); return Accepted; } diff --git a/src/plugins/cppeditor/cppprojectinfogenerator.cpp b/src/plugins/cppeditor/cppprojectinfogenerator.cpp index fe077bd3771..5c4c03de4cc 100644 --- a/src/plugins/cppeditor/cppprojectinfogenerator.cpp +++ b/src/plugins/cppeditor/cppprojectinfogenerator.cpp @@ -10,30 +10,24 @@ #include #include -#include - +#include #include -#include - using namespace ProjectExplorer; using namespace Utils; namespace CppEditor::Internal { -ProjectInfoGenerator::ProjectInfoGenerator( - const QFutureInterface &futureInterface, - const ProjectUpdateInfo &projectUpdateInfo) - : m_futureInterface(futureInterface) - , m_projectUpdateInfo(projectUpdateInfo) +ProjectInfoGenerator::ProjectInfoGenerator(const ProjectUpdateInfo &projectUpdateInfo) + : m_projectUpdateInfo(projectUpdateInfo) { } -ProjectInfo::ConstPtr ProjectInfoGenerator::generate() +ProjectInfo::ConstPtr ProjectInfoGenerator::generate(const QPromise &promise) { QVector projectParts; for (const RawProjectPart &rpp : m_projectUpdateInfo.rawProjectParts) { - if (m_futureInterface.isCanceled()) + if (promise.isCanceled()) return {}; for (const ProjectPart::ConstPtr &part : createProjectParts( rpp, m_projectUpdateInfo.projectFilePath)) { diff --git a/src/plugins/cppeditor/cppprojectinfogenerator.h b/src/plugins/cppeditor/cppprojectinfogenerator.h index c090974c042..8720d3e1cd2 100644 --- a/src/plugins/cppeditor/cppprojectinfogenerator.h +++ b/src/plugins/cppeditor/cppprojectinfogenerator.h @@ -5,17 +5,19 @@ #include "projectinfo.h" -#include +QT_BEGIN_NAMESPACE +template +class QPromise; +QT_END_NAMESPACE namespace CppEditor::Internal { class ProjectInfoGenerator { public: - ProjectInfoGenerator(const QFutureInterface &futureInterface, - const ProjectExplorer::ProjectUpdateInfo &projectUpdateInfo); + ProjectInfoGenerator(const ProjectExplorer::ProjectUpdateInfo &projectUpdateInfo); - ProjectInfo::ConstPtr generate(); + ProjectInfo::ConstPtr generate(const QPromise &promise); private: const QVector createProjectParts( @@ -29,7 +31,6 @@ private: Utils::LanguageExtensions languageExtensions); private: - const QFutureInterface m_futureInterface; const ProjectExplorer::ProjectUpdateInfo &m_projectUpdateInfo; bool m_cToolchainMissing = false; bool m_cxxToolchainMissing = false; diff --git a/src/plugins/cppeditor/cppprojectupdater.cpp b/src/plugins/cppeditor/cppprojectupdater.cpp index a784d3bb794..c92d091396e 100644 --- a/src/plugins/cppeditor/cppprojectupdater.cpp +++ b/src/plugins/cppeditor/cppprojectupdater.cpp @@ -13,21 +13,15 @@ #include #include -#include +#include #include -#include - using namespace ProjectExplorer; using namespace Utils; namespace CppEditor { -CppProjectUpdater::CppProjectUpdater() -{ - m_futureSynchronizer.setCancelOnWait(true); -} - +CppProjectUpdater::CppProjectUpdater() = default; CppProjectUpdater::~CppProjectUpdater() = default; void CppProjectUpdater::update(const ProjectUpdateInfo &projectUpdateInfo) @@ -49,12 +43,12 @@ void CppProjectUpdater::update(const ProjectUpdateInfo &projectUpdateInfo, using namespace ProjectExplorer; // Run the project info generator in a worker thread and continue if that one is finished. - const auto infoGenerator = [=](QFutureInterface &futureInterface) { + const auto infoGenerator = [=](QPromise &promise) { ProjectUpdateInfo fullProjectUpdateInfo = projectUpdateInfo; if (fullProjectUpdateInfo.rppGenerator) fullProjectUpdateInfo.rawProjectParts = fullProjectUpdateInfo.rppGenerator(); - Internal::ProjectInfoGenerator generator(futureInterface, fullProjectUpdateInfo); - futureInterface.reportResult(generator.generate()); + Internal::ProjectInfoGenerator generator(fullProjectUpdateInfo); + promise.addResult(generator.generate(promise)); }; using namespace Tasking; @@ -62,16 +56,16 @@ void CppProjectUpdater::update(const ProjectUpdateInfo &projectUpdateInfo, ProjectInfo::ConstPtr projectInfo = nullptr; }; const TreeStorage storage; - const auto setupInfoGenerator = [=](AsyncTask &async) { - async.setAsyncCallData(infoGenerator); + const auto setupInfoGenerator = [=](Async &async) { + async.setConcurrentCallData(infoGenerator); async.setFutureSynchronizer(&m_futureSynchronizer); }; - const auto onInfoGeneratorDone = [=](const AsyncTask &async) { + const auto onInfoGeneratorDone = [=](const Async &async) { if (async.isResultAvailable()) storage->projectInfo = async.result(); }; QList tasks{parallel}; - tasks.append(Async(setupInfoGenerator, onInfoGeneratorDone)); + tasks.append(AsyncTask(setupInfoGenerator, onInfoGeneratorDone)); for (QPointer compiler : compilers) { if (compiler && compiler->isDirty()) tasks.append(compiler->compileFileItem()); @@ -99,8 +93,8 @@ void CppProjectUpdater::update(const ProjectUpdateInfo &projectUpdateInfo, const Group root { Storage(storage), Group(tasks), - OnGroupDone(onDone), - OnGroupError(onError) + onGroupDone(onDone), + onGroupError(onError) }; m_taskTree.reset(new TaskTree(root)); auto progress = new Core::TaskProgress(m_taskTree.get()); diff --git a/src/plugins/cppeditor/cppprojectupdater.h b/src/plugins/cppeditor/cppprojectupdater.h index 50910e83207..a8cebc5b1e0 100644 --- a/src/plugins/cppeditor/cppprojectupdater.h +++ b/src/plugins/cppeditor/cppprojectupdater.h @@ -10,7 +10,7 @@ #include namespace ProjectExplorer { class ExtraCompiler; } -namespace Utils { class TaskTree; } +namespace Tasking { class TaskTree; } namespace CppEditor { @@ -44,7 +44,7 @@ public: private: Utils::FutureSynchronizer m_futureSynchronizer; - std::unique_ptr m_taskTree; + std::unique_ptr m_taskTree; }; } // namespace CppEditor diff --git a/src/plugins/cppeditor/cppquickfix_test.cpp b/src/plugins/cppeditor/cppquickfix_test.cpp index 560a27fe240..f57670c1611 100644 --- a/src/plugins/cppeditor/cppquickfix_test.cpp +++ b/src/plugins/cppeditor/cppquickfix_test.cpp @@ -1725,7 +1725,7 @@ void QuickfixTest::testGeneric_data() << _(R"(const char *str = @"\xc3\xa0""f23\xd0\xb1g\xd0\xb1""1";)") << _(R"(const char *str = "àf23бgб1";)"); QTest::newRow("AddLocalDeclaration_QTCREATORBUG-26004") - << CppQuickFixFactoryPtr(new AddLocalDeclaration) + << CppQuickFixFactoryPtr(new AddDeclarationForUndeclaredIdentifier) << _("void func() {\n" " QStringList list;\n" " @it = list.cbegin();\n" @@ -2315,7 +2315,7 @@ signals: void newFooBarTestValue(); private: - Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue) + Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL) }; )-"; QTest::addRow("create right names") << QByteArrayList{originalSource, expectedSource} << 4; @@ -2346,7 +2346,7 @@ signals: void newFooBarTestValue(); private: - Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue) + Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL) }; )-"; expectedSource = ""; @@ -2355,7 +2355,7 @@ private: // create from Q_PROPERTY with custom names originalSource = R"-( class Test { - Q_PROPER@TY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue) + Q_PROPER@TY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL) public: int give_me_foo_bar_test() const @@ -2380,7 +2380,7 @@ signals: )-"; expectedSource = R"-( class Test { - Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue) + Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL) public: int give_me_foo_bar_test() const @@ -2411,14 +2411,14 @@ private: // create from Q_PROPERTY with custom names originalSource = R"-( class Test { - Q_PROPE@RTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue) + Q_PROPE@RTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL) int mem_fooBar_test; public: }; )-"; expectedSource = R"-( class Test { - Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue) + Q_PROPERTY(int fooBar_test READ give_me_foo_bar_test WRITE Seet_FooBar_test RESET set_fooBarTest_toDefault NOTIFY newFooBarTestValue FINAL) int mem_fooBar_test; public: int give_me_foo_bar_test() const @@ -2766,7 +2766,7 @@ public: signals: void barChanged(N2::test *bar); private: - Q_PROPERTY(N2::test *bar READ getBar NOTIFY barChanged) + Q_PROPERTY(N2::test *bar READ getBar NOTIFY barChanged FINAL) }; })--"; testDocuments << CppTestDocument::create("file.h", original, expected); @@ -3303,7 +3303,7 @@ void QuickfixTest::testGenerateGetterSetterAnonymousClass() void fooChanged(); private: - Q_PROPERTY(int foo READ foo WRITE setFoo RESET resetFoo NOTIFY fooChanged) + Q_PROPERTY(int foo READ foo WRITE setFoo RESET resetFoo NOTIFY fooChanged FINAL) } bar; )"; testDocuments << CppTestDocument::create("file.h", original, expected); @@ -3334,7 +3334,7 @@ public: signals: void barChanged(); private: - Q_PROPERTY(int bar READ getBar WRITE setBar RESET resetBar NOTIFY barChanged) + Q_PROPERTY(int bar READ getBar WRITE setBar RESET resetBar NOTIFY barChanged FINAL) }; inline int Foo::getBar() const @@ -3560,8 +3560,8 @@ private: int m_bar; int bar2_; QString bar3; - Q_PROPERTY(int bar2 READ getBar2 WRITE setBar2 RESET resetBar2 NOTIFY bar2Changed) - Q_PROPERTY(QString bar3 READ getBar3 WRITE setBar3 RESET resetBar3 NOTIFY bar3Changed) + Q_PROPERTY(int bar2 READ getBar2 WRITE setBar2 RESET resetBar2 NOTIFY bar2Changed FINAL) + Q_PROPERTY(QString bar3 READ getBar3 WRITE setBar3 RESET resetBar3 NOTIFY bar3Changed FINAL) }; inline void Foo::resetBar() { @@ -3780,7 +3780,7 @@ void QuickfixTest::testInsertQtPropertyMembers() QuickFixOperationTest({CppTestDocument::create("file.cpp", original, expected)}, &factory); } -void QuickfixTest::testInsertMemberFromInitialization_data() +void QuickfixTest::testInsertMemberFromUse_data() { QTest::addColumn("original"); QTest::addColumn("expected"); @@ -3852,9 +3852,241 @@ void QuickfixTest::testInsertMemberFromInitialization_data() " int m_x;\n" "};\n"; QTest::addRow("initialization via function call") << original << expected; + + original = + "struct S {\n\n};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { m_s.@value = v; }\n" + "private:\n" + " S m_s;\n" + "};\n"; + expected = + "struct S {\n\n" + " int value;\n" + "};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { m_s.value = v; }\n" + "private:\n" + " S m_s;\n" + "};\n"; + QTest::addRow("add member to other struct") << original << expected; + + original = + "struct S {\n\n};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { S::@value = v; }\n" + "};\n"; + expected = + "struct S {\n\n" + " static int value;\n" + "};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { S::value = v; }\n" + "};\n"; + QTest::addRow("add static member to other struct (explicit)") << original << expected; + + original = + "struct S {\n\n};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { m_s.@value = v; }\n" + "private:\n" + " static S m_s;\n" + "};\n"; + expected = + "struct S {\n\n" + " static int value;\n" + "};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { m_s.value = v; }\n" + "private:\n" + " static S m_s;\n" + "};\n"; + QTest::addRow("add static member to other struct (implicit)") << original << expected; + + original = + "class C {\n" + "public:\n" + " void setValue(int v);\n" + "};\n" + "void C::setValue(int v) { this->@m_value = v; }\n"; + expected = + "class C {\n" + "public:\n" + " void setValue(int v);\n" + "private:\n" + " int m_value;\n" + "};\n" + "void C::setValue(int v) { this->@m_value = v; }\n"; + QTest::addRow("add member to this (explicit)") << original << expected; + + original = + "class C {\n" + "public:\n" + " void setValue(int v) { @m_value = v; }\n" + "};\n"; + expected = + "class C {\n" + "public:\n" + " void setValue(int v) { m_value = v; }\n" + "private:\n" + " int m_value;\n" + "};\n"; + QTest::addRow("add member to this (implicit)") << original << expected; + + original = + "class C {\n" + "public:\n" + " static void setValue(int v) { @m_value = v; }\n" + "};\n"; + expected = + "class C {\n" + "public:\n" + " static void setValue(int v) { m_value = v; }\n" + "private:\n" + " static int m_value;\n" + "};\n"; + QTest::addRow("add static member to this (inline)") << original << expected; + + original = + "class C {\n" + "public:\n" + " static void setValue(int v);\n" + "};\n" + "void C::setValue(int v) { @m_value = v; }\n"; + expected = + "class C {\n" + "public:\n" + " static void setValue(int v);\n" + "private:\n" + " static int m_value;\n" + "};\n" + "void C::setValue(int v) { @m_value = v; }\n"; + QTest::addRow("add static member to this (non-inline)") << original << expected; + + original = + "struct S {\n\n};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { m_s.@setValue(v); }\n" + "private:\n" + " S m_s;\n" + "};\n"; + expected = + "struct S {\n\n" + " void setValue(int);\n" + "};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { m_s.setValue(v); }\n" + "private:\n" + " S m_s;\n" + "};\n"; + QTest::addRow("add member function to other struct") << original << expected; + + original = + "struct S {\n\n};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { S::@setValue(v); }\n" + "};\n"; + expected = + "struct S {\n\n" + " static void setValue(int);\n" + "};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { S::setValue(v); }\n" + "};\n"; + QTest::addRow("add static member function to other struct (explicit)") << original << expected; + + original = + "struct S {\n\n};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { m_s.@setValue(v); }\n" + "private:\n" + " static S m_s;\n" + "};\n"; + expected = + "struct S {\n\n" + " static void setValue(int);\n" + "};\n" + "class C {\n" + "public:\n" + " void setValue(int v) { m_s.setValue(v); }\n" + "private:\n" + " static S m_s;\n" + "};\n"; + QTest::addRow("add static member function to other struct (implicit)") << original << expected; + + original = + "class C {\n" + "public:\n" + " void setValue(int v);\n" + "};\n" + "void C::setValue(int v) { this->@setValueInternal(v); }\n"; + expected = + "class C {\n" + "public:\n" + " void setValue(int v);\n" + "private:\n" + " void setValueInternal(int);\n" + "};\n" + "void C::setValue(int v) { this->setValueInternal(v); }\n"; + QTest::addRow("add member function to this (explicit)") << original << expected; + + original = + "class C {\n" + "public:\n" + " void setValue(int v) { @setValueInternal(v); }\n" + "};\n"; + expected = + "class C {\n" + "public:\n" + " void setValue(int v) { setValueInternal(v); }\n" + "private:\n" + " void setValueInternal(int);\n" + "};\n"; + QTest::addRow("add member function to this (implicit)") << original << expected; + + original = + "class C {\n" + "public:\n" + " static int value() { int i = @valueInternal(); return i; }\n" + "};\n"; + expected = + "class C {\n" + "public:\n" + " static int value() { int i = @valueInternal(); return i; }\n" + "private:\n" + " static int valueInternal();\n" + "};\n"; + QTest::addRow("add static member function to this (inline)") << original << expected; + + original = + "class C {\n" + "public:\n" + " static int value();\n" + "};\n" + "int C::value() { return @valueInternal(); }\n"; + expected = + "class C {\n" + "public:\n" + " static int value();\n" + "private:\n" + " static int valueInternal();\n" + "};\n" + "int C::value() { return valueInternal(); }\n"; + QTest::addRow("add static member function to this (non-inline)") << original << expected; } -void QuickfixTest::testInsertMemberFromInitialization() +void QuickfixTest::testInsertMemberFromUse() { QFETCH(QByteArray, original); QFETCH(QByteArray, expected); @@ -3863,7 +4095,8 @@ void QuickfixTest::testInsertMemberFromInitialization() CppTestDocument::create("file.h", original, expected) }); - InsertMemberFromInitialization factory; + AddDeclarationForUndeclaredIdentifier factory; + factory.setMembersOnly(); QuickFixOperationTest(testDocuments, &factory); } diff --git a/src/plugins/cppeditor/cppquickfix_test.h b/src/plugins/cppeditor/cppquickfix_test.h index 56cacb367ca..2d0dd0ebc9d 100644 --- a/src/plugins/cppeditor/cppquickfix_test.h +++ b/src/plugins/cppeditor/cppquickfix_test.h @@ -101,8 +101,8 @@ private slots: void testInsertQtPropertyMembers_data(); void testInsertQtPropertyMembers(); - void testInsertMemberFromInitialization_data(); - void testInsertMemberFromInitialization(); + void testInsertMemberFromUse_data(); + void testInsertMemberFromUse(); void testConvertQt4ConnectConnectOutOfClass(); void testConvertQt4ConnectConnectWithinClass_data(); diff --git a/src/plugins/cppeditor/cppquickfixes.cpp b/src/plugins/cppeditor/cppquickfixes.cpp index 3dae225cea3..85489b877e7 100644 --- a/src/plugins/cppeditor/cppquickfixes.cpp +++ b/src/plugins/cppeditor/cppquickfixes.cpp @@ -10,6 +10,7 @@ #include "cppeditorwidget.h" #include "cppfunctiondecldeflink.h" #include "cppinsertvirtualmethods.h" +#include "cpplocatordata.h" #include "cpppointerdeclarationformatter.h" #include "cppquickfixassistant.h" #include "cppquickfixprojectsettings.h" @@ -26,6 +27,7 @@ #include #include #include +#include #include #include @@ -33,7 +35,7 @@ #include #include -#include +#include #include #include @@ -301,6 +303,64 @@ ClassSpecifierAST *astForClassOperations(const CppQuickFixInterface &interface) return nullptr; } +QString nameString(const NameAST *name) +{ + return CppCodeStyleSettings::currentProjectCodeStyleOverview().prettyName(name->name); +} + +// FIXME: Needs to consider the scope at the insertion site. +QString declFromExpr(const TypeOrExpr &typeOrExpr, const CallAST *call, const NameAST *varName, + const Snapshot &snapshot, const LookupContext &context, + const CppRefactoringFilePtr &file) +{ + const auto getTypeFromUser = [varName, call]() -> QString { + if (call) + return {}; + const QString typeFromUser = QInputDialog::getText(Core::ICore::dialogParent(), + Tr::tr("Provide the type"), + Tr::tr("Data type:"), QLineEdit::Normal); + if (!typeFromUser.isEmpty()) + return typeFromUser + ' ' + nameString(varName); + return {}; + }; + const auto getTypeOfExpr = [&](const ExpressionAST *expr) -> FullySpecifiedType { + TypeOfExpression typeOfExpression; + typeOfExpression.init(file->cppDocument(), snapshot, context.bindings()); + Scope *scope = file->scopeAt(expr->firstToken()); + const QList result = typeOfExpression( + file->textOf(expr).toUtf8(), scope, TypeOfExpression::Preprocess); + if (result.isEmpty()) + return {}; + + SubstitutionEnvironment env; + env.setContext(context); + env.switchScope(result.first().scope()); + ClassOrNamespace *con = typeOfExpression.context().lookupType(scope); + if (!con) + con = typeOfExpression.context().globalNamespace(); + UseMinimalNames q(con); + env.enter(&q); + + Control *control = context.bindings()->control().data(); + return rewriteType(result.first().type(), &env, control); + }; + + const Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + const FullySpecifiedType type = std::holds_alternative(typeOrExpr) + ? std::get(typeOrExpr) + : getTypeOfExpr(std::get(typeOrExpr)); + if (!call) + return type.isValid() ? oo.prettyType(type, varName->name) : getTypeFromUser(); + + Function func(file->cppDocument()->translationUnit(), 0, varName->name); + for (ExpressionListAST *it = call->expression_list; it; it = it->next) { + Argument * const arg = new Argument(nullptr, 0, nullptr); + arg->setType(getTypeOfExpr(it->value)); + func.addMember(arg); + } + return oo.prettyType(type) + ' ' + oo.prettyType(func.type(), varName->name); +} + } // anonymous namespace namespace { @@ -1610,33 +1670,8 @@ private: if (currentFile->cppDocument()->languageFeatures().cxx11Enabled && settings->useAuto) return "auto " + oo.prettyName(simpleNameAST->name); - - TypeOfExpression typeOfExpression; - typeOfExpression.init(semanticInfo().doc, snapshot(), context().bindings()); - Scope *scope = currentFile->scopeAt(binaryAST->firstToken()); - const QList result = - typeOfExpression(currentFile->textOf(binaryAST->right_expression).toUtf8(), - scope, - TypeOfExpression::Preprocess); - - if (!result.isEmpty()) { - SubstitutionEnvironment env; - env.setContext(context()); - env.switchScope(result.first().scope()); - ClassOrNamespace *con = typeOfExpression.context().lookupType(scope); - if (!con) - con = typeOfExpression.context().globalNamespace(); - UseMinimalNames q(con); - env.enter(&q); - - Control *control = context().bindings()->control().data(); - FullySpecifiedType tn = rewriteType(result.first().type(), &env, control); - - QString declaration = oo.prettyType(tn, simpleNameAST->name); - return declaration; - } - - return {}; + return declFromExpr(binaryAST->right_expression, nullptr, simpleNameAST, snapshot(), + context(), currentFile); } const BinaryExpressionAST *binaryAST; @@ -1645,42 +1680,6 @@ private: } // anonymous namespace -void AddLocalDeclaration::match(const CppQuickFixInterface &interface, QuickFixOperations &result) -{ - const QList &path = interface.path(); - CppRefactoringFilePtr file = interface.currentFile(); - - for (int index = path.size() - 1; index != -1; --index) { - if (BinaryExpressionAST *binary = path.at(index)->asBinaryExpression()) { - if (binary->left_expression && binary->right_expression - && file->tokenAt(binary->binary_op_token).is(T_EQUAL)) { - IdExpressionAST *idExpr = binary->left_expression->asIdExpression(); - if (interface.isCursorOn(binary->left_expression) && idExpr - && idExpr->name->asSimpleName() != nullptr) { - SimpleNameAST *nameAST = idExpr->name->asSimpleName(); - const QList results = interface.context().lookup(nameAST->name, file->scopeAt(nameAST->firstToken())); - Declaration *decl = nullptr; - for (const LookupItem &r : results) { - if (!r.declaration()) - continue; - if (Declaration *d = r.declaration()->asDeclaration()) { - if (!d->type()->asFunctionType()) { - decl = d; - break; - } - } - } - - if (!decl) { - result << new AddLocalDeclarationOp(interface, index, binary, nameAST); - return; - } - } - } - } - } -} - namespace { class ConvertToCamelCaseOp: public CppQuickFixOperation @@ -1990,45 +1989,45 @@ Snapshot forwardingHeaders(const CppQuickFixInterface &interface) return result; } -bool matchName(const Name *name, QList *matches, QString *className) { +QList matchName(const Name *name, QString *className) +{ if (!name) - return false; + return {}; QString simpleName; - if (Core::ILocatorFilter *classesFilter = CppModelManager::instance()->classesFilter()) { - QFutureInterface dummy; - - const Overview oo; - if (const QualifiedNameId *qualifiedName = name->asQualifiedNameId()) { - const Name *name = qualifiedName->name(); - if (const TemplateNameId *templateName = name->asTemplateNameId()) { - *className = templateNameAsString(templateName); - } else { - simpleName = oo.prettyName(name); - *className = simpleName; - *matches = classesFilter->matchesFor(dummy, *className); - if (matches->empty()) { - if (const Name *name = qualifiedName->base()) { - if (const TemplateNameId *templateName = name->asTemplateNameId()) - *className = templateNameAsString(templateName); - else - *className = oo.prettyName(name); - } - } - } - } else if (const TemplateNameId *templateName = name->asTemplateNameId()) { + QList matches; + CppLocatorData *locatorData = CppModelManager::instance()->locatorData(); + const Overview oo; + if (const QualifiedNameId *qualifiedName = name->asQualifiedNameId()) { + const Name *name = qualifiedName->name(); + if (const TemplateNameId *templateName = name->asTemplateNameId()) { *className = templateNameAsString(templateName); } else { - *className = oo.prettyName(name); - } - - if (matches->empty()) - *matches = classesFilter->matchesFor(dummy, *className); - if (matches->empty() && !simpleName.isEmpty()) + simpleName = oo.prettyName(name); *className = simpleName; + matches = locatorData->findSymbols(IndexItem::Class, *className); + if (matches.isEmpty()) { + if (const Name *name = qualifiedName->base()) { + if (const TemplateNameId *templateName = name->asTemplateNameId()) + *className = templateNameAsString(templateName); + else + *className = oo.prettyName(name); + } + } + } + } else if (const TemplateNameId *templateName = name->asTemplateNameId()) { + *className = templateNameAsString(templateName); + } else { + *className = oo.prettyName(name); } - return !matches->empty(); + if (matches.isEmpty()) + matches = locatorData->findSymbols(IndexItem::Class, *className); + + if (matches.isEmpty() && !simpleName.isEmpty()) + *className = simpleName; + + return matches; } } // anonymous namespace @@ -2045,17 +2044,16 @@ void AddIncludeForUndefinedIdentifier::match(const CppQuickFixInterface &interfa return; QString className; - QList matches; const QString currentDocumentFilePath = interface.semanticInfo().doc->filePath().toString(); const ProjectExplorer::HeaderPaths headerPaths = relevantHeaderPaths(currentDocumentFilePath); FilePaths headers; + const QList matches = matchName(nameAst->name, &className); // Find an include file through the locator - if (matchName(nameAst->name, &matches, &className)) { + if (!matches.isEmpty()) { QList indexItems; const Snapshot forwardHeaders = forwardingHeaders(interface); - for (const Core::LocatorFilterEntry &entry : std::as_const(matches)) { - IndexItem::Ptr info = entry.internalData.value(); + for (const IndexItem::Ptr &info : matches) { if (!info || info->symbolName() != className) continue; indexItems << info; @@ -2935,70 +2933,239 @@ class InsertMemberFromInitializationOp : public CppQuickFixOperation { public: InsertMemberFromInitializationOp( - const CppQuickFixInterface &interface, - const Class *theClass, - const QString &member, - const QString &type) - : CppQuickFixOperation(interface), m_class(theClass), m_member(member), m_type(type) + const CppQuickFixInterface &interface, + const Class *theClass, + const NameAST *memberName, + const TypeOrExpr &typeOrExpr, + const CallAST *call, + InsertionPointLocator::AccessSpec accessSpec, + bool makeStatic) + : CppQuickFixOperation(interface), + m_class(theClass), m_memberName(memberName), m_typeOrExpr(typeOrExpr), m_call(call), + m_accessSpec(accessSpec), m_makeStatic(makeStatic) { - setDescription(Tr::tr("Add Class Member \"%1\"").arg(m_member)); + if (call) + setDescription(Tr::tr("Add Member Function \"%1\"").arg(nameString(memberName))); + else + setDescription(Tr::tr("Add Class Member \"%1\"").arg(nameString(memberName))); } private: void perform() override { - QString type = m_type; - if (type.isEmpty()) { - type = QInputDialog::getText( - Core::ICore::dialogParent(), - Tr::tr("Provide the type"), - Tr::tr("Data type:"), - QLineEdit::Normal); - } - if (type.isEmpty()) + QString decl = declFromExpr(m_typeOrExpr, m_call, m_memberName, snapshot(), context(), + currentFile()); + if (decl.isEmpty()) return; + if (m_makeStatic) + decl.prepend("static "); const CppRefactoringChanges refactoring(snapshot()); const InsertionPointLocator locator(refactoring); const FilePath filePath = FilePath::fromUtf8(m_class->fileName()); const InsertionLocation loc = locator.methodDeclarationInClass( - filePath, m_class, InsertionPointLocator::Private); + filePath, m_class, m_accessSpec); QTC_ASSERT(loc.isValid(), return); CppRefactoringFilePtr targetFile = refactoring.file(filePath); const int targetPosition1 = targetFile->position(loc.line(), loc.column()); const int targetPosition2 = qMax(0, targetFile->position(loc.line(), 1) - 1); ChangeSet target; - target.insert(targetPosition1, loc.prefix() + type + ' ' + m_member + ";\n"); + target.insert(targetPosition1, loc.prefix() + decl + ";\n"); targetFile->setChangeSet(target); targetFile->appendIndentRange(ChangeSet::Range(targetPosition2, targetPosition1)); targetFile->apply(); } const Class * const m_class; - const QString m_member; - const QString m_type; + const NameAST * const m_memberName; + const TypeOrExpr m_typeOrExpr; + const CallAST * m_call; + const InsertionPointLocator::AccessSpec m_accessSpec; + const bool m_makeStatic; }; -void InsertMemberFromInitialization::match(const CppQuickFixInterface &interface, - QuickFixOperations &result) +void AddDeclarationForUndeclaredIdentifier::match(const CppQuickFixInterface &interface, + QuickFixOperations &result) { - // First check whether we are on a member initialization. - const QList path = interface.path(); + // Are we on a name? + const QList &path = interface.path(); + if (path.isEmpty()) + return; + if (!path.last()->asSimpleName()) + return; + + // Special case: Member initializer. + if (!checkForMemberInitializer(interface, result)) + return; + + // Are we inside a function? + const FunctionDefinitionAST *func = nullptr; + for (auto it = path.rbegin(); !func && it != path.rend(); ++it) + func = (*it)->asFunctionDefinition(); + if (!func) + return; + + // Is this name declared somewhere already? + const CursorInEditor cursorInEditor(interface.cursor(), interface.filePath(), + interface.editor(), interface.editor()->textDocument()); + const auto followSymbolFallback = [&](const Link &link) { + if (!link.hasValidTarget()) + collectOperations(interface, result); + }; + CppModelManager::followSymbol(cursorInEditor, followSymbolFallback, false, false, + CppModelManager::Backend::Builtin); +} + +void AddDeclarationForUndeclaredIdentifier::collectOperations( + const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) +{ + const QList &path = interface.path(); + const CppRefactoringFilePtr &file = interface.currentFile(); + for (int index = path.size() - 1; index != -1; --index) { + if (const auto call = path.at(index)->asCall()) + return handleCall(call, interface, result); + + // We only trigger if the identifier appears on the left-hand side of an + // assignment expression. + const auto binExpr = path.at(index)->asBinaryExpression(); + if (!binExpr) + continue; + if (!binExpr->left_expression || !binExpr->right_expression + || file->tokenAt(binExpr->binary_op_token).kind() != T_EQUAL + || !interface.isCursorOn(binExpr->left_expression)) { + return; + } + + // In the case of "a.|b = c", find out the type of a, locate the class declaration + // and add a member b there. + if (const auto memberAccess = binExpr->left_expression->asMemberAccess()) { + if (interface.isCursorOn(memberAccess->member_name) + && memberAccess->member_name == path.last()) { + maybeAddMember(interface, file->scopeAt(memberAccess->firstToken()), + file->textOf(memberAccess->base_expression).toUtf8(), + binExpr->right_expression, nullptr, result); + } + return; + } + + const auto idExpr = binExpr->left_expression->asIdExpression(); + if (!idExpr || !idExpr->name) + return; + + // In the case of "A::|b = c", add a static member b to A. + if (const auto qualName = idExpr->name->asQualifiedName()) { + return maybeAddStaticMember(interface, qualName, binExpr->right_expression, nullptr, + result); + } + + // For an unqualified access, offer a local declaration and, if we are + // in a member function, a member declaration. + if (const auto simpleName = idExpr->name->asSimpleName()) { + if (!m_membersOnly) + result << new AddLocalDeclarationOp(interface, index, binExpr, simpleName); + maybeAddMember(interface, file->scopeAt(idExpr->firstToken()), "this", + binExpr->right_expression, nullptr, result); + return; + } + } +} + +void AddDeclarationForUndeclaredIdentifier::handleCall( + const CallAST *call, const CppQuickFixInterface &interface, + TextEditor::QuickFixOperations &result) +{ + if (!call->base_expression) + return; + + // In order to find out the return type, we need to check the context of the call. + // If it is a statement expression, the type is void, if it's a binary expression, + // we assume the type of the other side of the expression, if it's a return statement, + // we use the return type of the surrounding function, and if it's a declaration, + // we use the type of the variable. Other cases are not supported. + const QList &path = interface.path(); + const CppRefactoringFilePtr &file = interface.currentFile(); + TypeOrExpr returnTypeOrExpr; + for (auto it = path.rbegin(); it != path.rend(); ++it) { + if ((*it)->asCompoundStatement()) + return; + if ((*it)->asExpressionStatement()) { + returnTypeOrExpr = FullySpecifiedType(new VoidType); + break; + } + if (const auto binExpr = (*it)->asBinaryExpression()) { + returnTypeOrExpr = interface.isCursorOn(binExpr->left_expression) + ? binExpr->right_expression : binExpr->left_expression; + break; + } + if (const auto returnExpr = (*it)->asReturnStatement()) { + for (auto it2 = std::next(it); it2 != path.rend(); ++it2) { + if (const auto func = (*it2)->asFunctionDefinition()) { + if (!func->symbol) + return; + returnTypeOrExpr = func->symbol->returnType(); + break; + } + } + break; + } + if (const auto declarator = (*it)->asDeclarator()) { + if (!interface.isCursorOn(declarator->initializer)) + return; + const auto decl = (*std::next(it))->asSimpleDeclaration(); + if (!decl || !decl->symbols) + return; + if (!decl->symbols->value->type().isValid()) + return; + returnTypeOrExpr = decl->symbols->value->type(); + break; + } + } + + if (std::holds_alternative(returnTypeOrExpr) + && !std::get(returnTypeOrExpr)) { + return; + } + + // a.f() + if (const auto memberAccess = call->base_expression->asMemberAccess()) { + if (!interface.isCursorOn(memberAccess->member_name)) + return; + maybeAddMember( + interface, file->scopeAt(call->firstToken()), + file->textOf(memberAccess->base_expression).toUtf8(), returnTypeOrExpr, call, result); + } + + const auto idExpr = call->base_expression->asIdExpression(); + if (!idExpr || !idExpr->name) + return; + + // A::f() + if (const auto qualName = idExpr->name->asQualifiedName()) + return maybeAddStaticMember(interface, qualName, returnTypeOrExpr, call, result); + + // f() + if (const auto simpleName = idExpr->name->asSimpleName()) { + maybeAddMember(interface, file->scopeAt(idExpr->firstToken()), "this", + returnTypeOrExpr, call, result); + } +} + +bool AddDeclarationForUndeclaredIdentifier::checkForMemberInitializer( + const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) +{ + const QList &path = interface.path(); const int size = path.size(); if (size < 4) - return; - const SimpleNameAST * const name = path.at(size - 1)->asSimpleName(); - if (!name) - return; + return true; const MemInitializerAST * const memInitializer = path.at(size - 2)->asMemInitializer(); if (!memInitializer) - return; + return true; if (!path.at(size - 3)->asCtorInitializer()) - return; + return true; const FunctionDefinitionAST * ctor = path.at(size - 4)->asFunctionDefinition(); if (!ctor) - return; + return false; // Now find the class. const Class *theClass = nullptr; @@ -3011,65 +3178,141 @@ void InsertMemberFromInitialization::match(const CppQuickFixInterface &interface // Out-of-line constructor. We need to find the class. SymbolFinder finder; const QList matches = finder.findMatchingDeclaration( - LookupContext(interface.currentFile()->cppDocument(), interface.snapshot()), - ctor->symbol); + LookupContext(interface.currentFile()->cppDocument(), interface.snapshot()), + ctor->symbol); if (!matches.isEmpty()) theClass = matches.first()->enclosingClass(); } if (!theClass) - return; + return false; + + const SimpleNameAST * const name = path.at(size - 1)->asSimpleName(); + QTC_ASSERT(name, return false); // Check whether the member exists already. if (theClass->find(interface.currentFile()->cppDocument()->translationUnit()->identifier( - name->identifier_token))) { - return; + name->identifier_token))) { + return false; } - const QString type = getType(interface, memInitializer, ctor); - const Identifier * const memberId = interface.currentFile()->cppDocument() - ->translationUnit()->identifier(name->identifier_token); - const QString member = QString::fromUtf8(memberId->chars(), memberId->size()); - - result << new InsertMemberFromInitializationOp(interface, theClass, member, type); + result << new InsertMemberFromInitializationOp( + interface, theClass, memInitializer->name->asSimpleName(), memInitializer->expression, + nullptr, InsertionPointLocator::Private, false); + return false; } -QString InsertMemberFromInitialization::getType( - const CppQuickFixInterface &interface, - const MemInitializerAST *memInitializer, - const FunctionDefinitionAST *ctor) const +void AddDeclarationForUndeclaredIdentifier::maybeAddMember( + const CppQuickFixInterface &interface, Scope *scope, const QByteArray &classTypeExpr, + const TypeOrExpr &typeOrExpr, const CallAST *call, TextEditor::QuickFixOperations &result) { - // Try to deduce the type: If the initialization expression is just a name - // (e.g. a constructor argument) or a function call, we don't bother the user. - if (!memInitializer->expression) - return {}; - const ExpressionListParenAST * const lParenAst - = memInitializer->expression->asExpressionListParen(); - if (!lParenAst || !lParenAst->expression_list || !lParenAst->expression_list->value) - return {}; - const IdExpressionAST *idExpr = lParenAst->expression_list->value->asIdExpression(); - if (!idExpr) { // Not a variable, so check for function call. - const CallAST * const call = lParenAst->expression_list->value->asCall(); - if (!call || !call->base_expression) - return {}; - idExpr = call->base_expression->asIdExpression(); - } - if (!idExpr || !idExpr->name) - return {}; + const QList &path = interface.path(); - LookupContext context(interface.currentFile()->cppDocument(), interface.snapshot()); - const QList matches = context.lookup(idExpr->name->name, ctor->symbol); - if (matches.isEmpty()) - return {}; - Overview o = CppCodeStyleSettings::currentProjectCodeStyleOverview(); - TypePrettyPrinter tpp(&o); - FullySpecifiedType type = matches.first().type(); - if (!type.type()) - return {}; - const Function * const funcType = type.type()->asFunctionType(); - if (funcType) - type = funcType->returnType(); - return tpp(type); + TypeOfExpression typeOfExpression; + typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot(), + interface.context().bindings()); + const QList lhsTypes = typeOfExpression( + classTypeExpr, scope, + TypeOfExpression::Preprocess); + if (lhsTypes.isEmpty()) + return; + + const Type *type = lhsTypes.first().type().type(); + if (!type) + return; + if (type->asPointerType()) { + type = type->asPointerType()->elementType().type(); + if (!type) + return; + } + const auto namedType = type->asNamedType(); + if (!namedType) + return; + const ClassOrNamespace * const classOrNamespace + = interface.context().lookupType(namedType->name(), scope); + if (!classOrNamespace || !classOrNamespace->rootClass()) + return; + + const Class * const theClass = classOrNamespace->rootClass(); + bool needsStatic = lhsTypes.first().type().isStatic(); + + // If the base expression refers to the same class that the member function is in, + // then we want to insert a private member, otherwise a public one. + const FunctionDefinitionAST *func = nullptr; + for (auto it = path.rbegin(); !func && it != path.rend(); ++it) + func = (*it)->asFunctionDefinition(); + QTC_ASSERT(func, return); + InsertionPointLocator::AccessSpec accessSpec = InsertionPointLocator::Public; + for (int i = 0; i < theClass->memberCount(); ++i) { + if (theClass->memberAt(i) == func->symbol) { + accessSpec = InsertionPointLocator::Private; + needsStatic = func->symbol->isStatic(); + break; + } + } + if (accessSpec == InsertionPointLocator::Public) { + QList decls; + QList dummy; + SymbolFinder().findMatchingDeclaration(interface.context(), func->symbol, &decls, + &dummy, &dummy); + for (const Declaration * const decl : std::as_const(decls)) { + for (int i = 0; i < theClass->memberCount(); ++i) { + if (theClass->memberAt(i) == decl) { + accessSpec = InsertionPointLocator::Private; + needsStatic = decl->isStatic(); + break; + } + } + if (accessSpec == InsertionPointLocator::Private) + break; + } + } + result << new InsertMemberFromInitializationOp(interface, theClass, path.last()->asName(), + typeOrExpr, call, accessSpec, needsStatic); +} + +void AddDeclarationForUndeclaredIdentifier::maybeAddStaticMember( + const CppQuickFixInterface &interface, const QualifiedNameAST *qualName, + const TypeOrExpr &typeOrExpr, const CallAST *call, TextEditor::QuickFixOperations &result) +{ + const QList &path = interface.path(); + + if (!interface.isCursorOn(qualName->unqualified_name)) + return; + if (qualName->unqualified_name != path.last()) + return; + if (!qualName->nested_name_specifier_list) + return; + + const NameAST * const topLevelName + = qualName->nested_name_specifier_list->value->class_or_namespace_name; + if (!topLevelName) + return; + ClassOrNamespace * const classOrNamespace = interface.context().lookupType( + topLevelName->name, interface.currentFile()->scopeAt(qualName->firstToken())); + if (!classOrNamespace) + return; + QList otherNames; + for (auto it = qualName->nested_name_specifier_list->next; it; it = it->next) { + if (!it->value || !it->value->class_or_namespace_name) + return; + otherNames << it->value->class_or_namespace_name->name; + } + + const Class *theClass = nullptr; + if (!otherNames.isEmpty()) { + const Symbol * const symbol = classOrNamespace->lookupInScope(otherNames); + if (!symbol) + return; + theClass = symbol->asClass(); + } else { + theClass = classOrNamespace->rootClass(); + } + if (theClass) { + result << new InsertMemberFromInitializationOp( + interface, theClass, path.last()->asName(), typeOrExpr, call, + InsertionPointLocator::Public, true); + } } class MemberFunctionImplSetting @@ -3697,6 +3940,7 @@ public: GenerateProperty = 1 << 5, GenerateConstantProperty = 1 << 6, HaveExistingQProperty = 1 << 7, + Invalid = -1, }; GenerateGetterSetterOp(const CppQuickFixInterface &interface, @@ -4182,7 +4426,7 @@ void GetterSetterRefactoringHelper::performGeneration(ExistingGetterSetterData d propertyDeclaration.append(QLatin1String(" NOTIFY ")).append(data.signalName); } - propertyDeclaration.append(QLatin1String(")\n")); + propertyDeclaration.append(QLatin1String(" FINAL)\n")); addHeaderCode(InsertionPointLocator::Private, propertyDeclaration); } } @@ -4394,7 +4638,7 @@ public: }; using Flag = GenerateGetterSetterOp::GenerateFlag; constexpr static Flag ColumnFlag[] = { - static_cast(-1), + Flag::Invalid, Flag::GenerateGetter, Flag::GenerateSetter, Flag::GenerateSignal, @@ -7986,7 +8230,7 @@ private: } }; - if (const Project *project = SessionManager::projectForFile(filePath())) { + if (const Project *project = ProjectManager::projectForFile(filePath())) { const FilePaths files = project->files(ProjectExplorer::Project::SourceFiles); QSet projectFiles(files.begin(), files.end()); for (const auto &file : files) { @@ -9072,7 +9316,6 @@ void createCppQuickFixes() new SplitIfStatement; new SplitSimpleDeclaration; - new AddLocalDeclaration; new AddBracesToIf; new RearrangeParamDeclarationList; new ReformatPointerDeclaration; @@ -9089,7 +9332,7 @@ void createCppQuickFixes() new GenerateGettersSettersForClass; new InsertDeclFromDef; new InsertDefFromDecl; - new InsertMemberFromInitialization; + new AddDeclarationForUndeclaredIdentifier; new InsertDefsFromDecls; new MoveFuncDefOutside; diff --git a/src/plugins/cppeditor/cppquickfixes.h b/src/plugins/cppeditor/cppquickfixes.h index 376bfa99bf4..61db3ebf53d 100644 --- a/src/plugins/cppeditor/cppquickfixes.h +++ b/src/plugins/cppeditor/cppquickfixes.h @@ -3,9 +3,10 @@ #pragma once -#include "cppeditor_global.h" #include "cppquickfix.h" +#include + /// /// Adding New Quick Fixes /// @@ -16,6 +17,7 @@ namespace CppEditor { namespace Internal { +using TypeOrExpr = std::variant; void createCppQuickFixes(); void destroyCppQuickFixes(); @@ -284,23 +286,6 @@ public: void match(const CppQuickFixInterface &interface, QuickFixOperations &result) override; }; -/*! - Rewrites - a = foo(); - - As - Type a = foo(); - - Where Type is the return type of foo() - - Activates on: the assignee, if the type of the right-hand side of the assignment is known. -*/ -class AddLocalDeclaration: public CppQuickFixFactory -{ -public: - void match(const CppQuickFixInterface &interface, QuickFixOperations &result) override; -}; - /*! Add curly braces to a if statement that doesn't already contain a compound statement. I.e. @@ -374,20 +359,36 @@ public: void match(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; }; -/*! - Adds a class member from an initialization in the constructor. - */ -class InsertMemberFromInitialization : public CppQuickFixFactory +class AddDeclarationForUndeclaredIdentifier : public CppQuickFixFactory { public: void match(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override; +#ifdef WITH_TESTS + void setMembersOnly() { m_membersOnly = true; } +#endif + private: - QString getType( - const CppQuickFixInterface &interface, - const CPlusPlus::MemInitializerAST *memInitializer, - const CPlusPlus::FunctionDefinitionAST *ctor) const; + void collectOperations(const CppQuickFixInterface &interface, + TextEditor::QuickFixOperations &result); + void handleCall(const CPlusPlus::CallAST *call, const CppQuickFixInterface &interface, + TextEditor::QuickFixOperations &result); + + // Returns whether to still do other checks. + bool checkForMemberInitializer(const CppQuickFixInterface &interface, + TextEditor::QuickFixOperations &result); + + void maybeAddMember(const CppQuickFixInterface &interface, CPlusPlus::Scope *scope, + const QByteArray &classTypeExpr, const TypeOrExpr &typeOrExpr, + const CPlusPlus::CallAST *call, TextEditor::QuickFixOperations &result); + + void maybeAddStaticMember( + const CppQuickFixInterface &interface, const CPlusPlus::QualifiedNameAST *qualName, + const TypeOrExpr &typeOrExpr, const CPlusPlus::CallAST *call, + TextEditor::QuickFixOperations &result); + + bool m_membersOnly = false; }; /*! diff --git a/src/plugins/cppeditor/cppquickfixprojectsettingswidget.cpp b/src/plugins/cppeditor/cppquickfixprojectsettingswidget.cpp index c0fb8646651..c82985043f2 100644 --- a/src/plugins/cppeditor/cppquickfixprojectsettingswidget.cpp +++ b/src/plugins/cppeditor/cppquickfixprojectsettingswidget.cpp @@ -28,7 +28,7 @@ CppQuickFixProjectSettingsWidget::CppQuickFixProjectSettingsWidget(ProjectExplor auto layout = new QVBoxLayout(); gridLayout->addLayout(layout, 2, 0, 1, 2); - m_settingsWidget = new CppQuickFixSettingsWidget(this); + m_settingsWidget = new CppQuickFixSettingsWidget; m_settingsWidget->loadSettings(m_projectSettings->getSettings()); if (QLayout *layout = m_settingsWidget->layout()) diff --git a/src/plugins/cppeditor/cppquickfixsettingspage.cpp b/src/plugins/cppeditor/cppquickfixsettingspage.cpp index 05aa4c586a8..735dbea3b4d 100644 --- a/src/plugins/cppeditor/cppquickfixsettingspage.cpp +++ b/src/plugins/cppeditor/cppquickfixsettingspage.cpp @@ -5,12 +5,8 @@ #include "cppeditorconstants.h" #include "cppeditortr.h" -#include "cppquickfixsettings.h" #include "cppquickfixsettingswidget.h" -#include -#include - namespace CppEditor::Internal { CppQuickFixSettingsPage::CppQuickFixSettingsPage() @@ -18,27 +14,7 @@ CppQuickFixSettingsPage::CppQuickFixSettingsPage() setId(Constants::QUICK_FIX_SETTINGS_ID); setDisplayName(Tr::tr(Constants::QUICK_FIX_SETTINGS_DISPLAY_NAME)); setCategory(Constants::CPP_SETTINGS_CATEGORY); -} - -QWidget *CppQuickFixSettingsPage::widget() -{ - if (!m_widget) { - m_widget = new CppQuickFixSettingsWidget; - m_widget->loadSettings(CppQuickFixSettings::instance()); - } - return m_widget; -} - -void CppQuickFixSettingsPage::apply() -{ - const auto s = CppQuickFixSettings::instance(); - m_widget->saveSettings(s); - s->saveAsGlobalSettings(); -} - -void CppQuickFixSettingsPage::finish() -{ - delete m_widget; + setWidgetCreator([] { return new CppQuickFixSettingsWidget; }); } } // CppEditor::Internal diff --git a/src/plugins/cppeditor/cppquickfixsettingspage.h b/src/plugins/cppeditor/cppquickfixsettingspage.h index 3e14fde864a..1965aae9569 100644 --- a/src/plugins/cppeditor/cppquickfixsettingspage.h +++ b/src/plugins/cppeditor/cppquickfixsettingspage.h @@ -2,25 +2,15 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #pragma once -#include "coreplugin/dialogs/ioptionspage.h" -#include -namespace CppEditor { -namespace Internal { -class CppQuickFixSettingsWidget; +#include + +namespace CppEditor::Internal { class CppQuickFixSettingsPage : public Core::IOptionsPage { public: CppQuickFixSettingsPage(); - - QWidget *widget() override; - void apply() override; - void finish() override; - -private: - QPointer m_widget; }; -} // namespace Internal -} // namespace CppEditor +} // CppEditor::Internal diff --git a/src/plugins/cppeditor/cppquickfixsettingswidget.cpp b/src/plugins/cppeditor/cppquickfixsettingswidget.cpp index 689a519ce37..679d140967f 100644 --- a/src/plugins/cppeditor/cppquickfixsettingswidget.cpp +++ b/src/plugins/cppeditor/cppquickfixsettingswidget.cpp @@ -20,8 +20,6 @@ #include #include -using namespace Utils; - namespace CppEditor::Internal { class LineCountSpinBox : public QWidget @@ -56,7 +54,7 @@ LineCountSpinBox::LineCountSpinBox(QWidget *parent) m_unitLabel = new QLabel(Tr::tr("lines")); using namespace Layouting; - Row { m_checkBox, m_opLabel, m_spinBox, m_unitLabel, }.attachTo(this, WithoutMargins); + Row { m_checkBox, m_opLabel, m_spinBox, m_unitLabel, noMargin }.attachTo(this); auto handleChange = [this] { updateFields(); @@ -88,9 +86,8 @@ void LineCountSpinBox::updateFields() m_unitLabel->setEnabled(enabled); } -CppQuickFixSettingsWidget::CppQuickFixSettingsWidget(QWidget *parent) - : QWidget(parent) - , m_typeSplitter("\\s*,\\s*") +CppQuickFixSettingsWidget::CppQuickFixSettingsWidget() + : m_typeSplitter("\\s*,\\s*") { m_lines_getterOutsideClass = new LineCountSpinBox; m_lines_getterInCppFile = new LineCountSpinBox; @@ -223,7 +220,8 @@ e.g. name = "m_test_foo_": Tr::tr("Inside class:"), Tr::tr("Default"), Tr::tr("Default"), br, Tr::tr("Outside class:"), m_lines_setterOutsideClass, m_lines_getterOutsideClass, br, Tr::tr("In .cpp file:"), m_lines_setterInCppFile, m_lines_getterInCppFile, br, - }.attachTo(functionLocationsGrid, WithoutMargins); + noMargin, + }.attachTo(functionLocationsGrid); if (QGridLayout *gl = qobject_cast(functionLocationsGrid->layout())) gl->setHorizontalSpacing(48); @@ -319,6 +317,8 @@ e.g. name = "m_test_foo_": connect(m_radioButton_addUsingnamespace, &QRadioButton::clicked, then); connect(m_radioButton_generateMissingNamespace, &QRadioButton::clicked, then); connect(m_radioButton_rewriteTypes, &QRadioButton::clicked, then); + + loadSettings(CppQuickFixSettings::instance()); } void CppQuickFixSettingsWidget::loadSettings(CppQuickFixSettings *settings) @@ -426,6 +426,13 @@ void CppQuickFixSettingsWidget::saveSettings(CppQuickFixSettings *settings) } } +void CppQuickFixSettingsWidget::apply() +{ + const auto s = CppQuickFixSettings::instance(); + saveSettings(s); + s->saveAsGlobalSettings(); +} + void CppQuickFixSettingsWidget::currentCustomItemChanged(QListWidgetItem *newItem, QListWidgetItem *oldItem) { diff --git a/src/plugins/cppeditor/cppquickfixsettingswidget.h b/src/plugins/cppeditor/cppquickfixsettingswidget.h index f921afbff03..e11d81a9f23 100644 --- a/src/plugins/cppeditor/cppquickfixsettingswidget.h +++ b/src/plugins/cppeditor/cppquickfixsettingswidget.h @@ -3,6 +3,8 @@ #pragma once +#include + #include #include #include @@ -23,7 +25,7 @@ namespace CppEditor::Internal { class LineCountSpinBox; -class CppQuickFixSettingsWidget : public QWidget +class CppQuickFixSettingsWidget : public Core::IOptionsPageWidget { Q_OBJECT @@ -36,7 +38,7 @@ class CppQuickFixSettingsWidget : public QWidget }; public: - explicit CppQuickFixSettingsWidget(QWidget *parent = nullptr); + CppQuickFixSettingsWidget(); void loadSettings(CppQuickFixSettings *settings); void saveSettings(CppQuickFixSettings *settings); @@ -45,6 +47,7 @@ signals: void settingsChanged(); private: + void apply() final; void currentCustomItemChanged(QListWidgetItem *newItem, QListWidgetItem *oldItem); bool m_isLoadingSettings = false; diff --git a/src/plugins/cppeditor/cpprefactoringchanges.cpp b/src/plugins/cppeditor/cpprefactoringchanges.cpp index 1cce747fc66..bb34174d06a 100644 --- a/src/plugins/cppeditor/cpprefactoringchanges.cpp +++ b/src/plugins/cppeditor/cpprefactoringchanges.cpp @@ -57,8 +57,8 @@ CppRefactoringFilePtr CppRefactoringChanges::file(const FilePath &filePath) cons CppRefactoringFileConstPtr CppRefactoringChanges::fileNoEditor(const FilePath &filePath) const { QTextDocument *document = nullptr; - if (data()->m_workingCopy.contains(filePath)) - document = new QTextDocument(QString::fromUtf8(data()->m_workingCopy.source(filePath))); + if (const auto source = data()->m_workingCopy.source(filePath)) + document = new QTextDocument(QString::fromUtf8(*source)); CppRefactoringFilePtr result(new CppRefactoringFile(document, filePath)); result->m_data = m_data; diff --git a/src/plugins/cppeditor/cppsemanticinfoupdater.cpp b/src/plugins/cppeditor/cppsemanticinfoupdater.cpp index 3e2438dbf80..4fc9dd7c66b 100644 --- a/src/plugins/cppeditor/cppsemanticinfoupdater.cpp +++ b/src/plugins/cppeditor/cppsemanticinfoupdater.cpp @@ -3,11 +3,10 @@ #include "cppsemanticinfoupdater.h" -#include "cpplocalsymbols.h" #include "cppmodelmanager.h" +#include #include -#include #include #include @@ -29,11 +28,11 @@ public: class FuturizedTopLevelDeclarationProcessor: public TopLevelDeclarationProcessor { public: - explicit FuturizedTopLevelDeclarationProcessor(QFutureInterface &future): m_future(future) {} + explicit FuturizedTopLevelDeclarationProcessor(QPromise &promise): m_promise(promise) {} bool processDeclaration(DeclarationAST *) override { return !isCanceled(); } - bool isCanceled() { return m_future.isCanceled(); } + bool isCanceled() { return m_promise.isCanceled(); } private: - QFutureInterface m_future; + QPromise &m_promise; }; public: @@ -49,7 +48,7 @@ public: bool reuseCurrentSemanticInfo(const SemanticInfo::Source &source, bool emitSignalWhenFinished); - void update_helper(QFutureInterface &future, const SemanticInfo::Source &source); + void update_helper(QPromise &promise, const SemanticInfo::Source &source); public: SemanticInfoUpdater *q; @@ -136,10 +135,10 @@ bool SemanticInfoUpdaterPrivate::reuseCurrentSemanticInfo(const SemanticInfo::So return false; } -void SemanticInfoUpdaterPrivate::update_helper(QFutureInterface &future, +void SemanticInfoUpdaterPrivate::update_helper(QPromise &promise, const SemanticInfo::Source &source) { - FuturizedTopLevelDeclarationProcessor processor(future); + FuturizedTopLevelDeclarationProcessor processor(promise); update(source, true, &processor); } @@ -179,7 +178,7 @@ void SemanticInfoUpdater::updateDetached(const SemanticInfo::Source &source) return; } - d->m_future = Utils::runAsync(CppModelManager::instance()->sharedThreadPool(), + d->m_future = Utils::asyncRun(CppModelManager::instance()->sharedThreadPool(), &SemanticInfoUpdaterPrivate::update_helper, d.data(), source); } diff --git a/src/plugins/cppeditor/cppsourceprocessor.cpp b/src/plugins/cppeditor/cppsourceprocessor.cpp index 66cca58a0f7..55dc1f10e05 100644 --- a/src/plugins/cppeditor/cppsourceprocessor.cpp +++ b/src/plugins/cppeditor/cppsourceprocessor.cpp @@ -81,7 +81,8 @@ inline const CPlusPlus::Macro revision(const WorkingCopy &workingCopy, const CPlusPlus::Macro ¯o) { CPlusPlus::Macro newMacro(macro); - newMacro.setFileRevision(workingCopy.get(macro.filePath()).second); + if (const auto entry = workingCopy.get(macro.filePath())) + newMacro.setFileRevision(entry->second); return newMacro; } @@ -189,10 +190,9 @@ bool CppSourceProcessor::getFileContents(const FilePath &absoluteFilePath, return false; // Get from working copy - if (m_workingCopy.contains(absoluteFilePath)) { - const QPair entry = m_workingCopy.get(absoluteFilePath); - *contents = entry.first; - *revision = entry.second; + if (const auto entry = m_workingCopy.get(absoluteFilePath)) { + *contents = entry->first; + *revision = entry->second; return true; } @@ -216,7 +216,7 @@ bool CppSourceProcessor::checkFile(const FilePath &absoluteFilePath) const { if (absoluteFilePath.isEmpty() || m_included.contains(absoluteFilePath) - || m_workingCopy.contains(absoluteFilePath)) { + || m_workingCopy.get(absoluteFilePath)) { return true; } @@ -281,7 +281,7 @@ FilePath CppSourceProcessor::resolveFile_helper(const FilePath &filePath, } else { path = FilePath::fromString(headerPathsIt->path) / fileName; } - if (m_workingCopy.contains(path) || checkFile(path)) + if (m_workingCopy.get(path) || checkFile(path)) return path; } } @@ -468,8 +468,8 @@ void CppSourceProcessor::sourceNeeded(int line, const FilePath &filePath, Includ document->setUtf8Source(preprocessedCode); document->keepSourceAndAST(); document->tokenize(); - document->check(m_workingCopy.contains(document->filePath()) ? Document::FullCheck - : Document::FastCheck); + document->check(m_workingCopy.get(document->filePath()) ? Document::FullCheck + : Document::FastCheck); m_documentFinished(document); diff --git a/src/plugins/cppeditor/cpptoolsjsextension.cpp b/src/plugins/cppeditor/cpptoolsjsextension.cpp index a3cbfe12041..73a846593c1 100644 --- a/src/plugins/cppeditor/cpptoolsjsextension.cpp +++ b/src/plugins/cppeditor/cpptoolsjsextension.cpp @@ -10,12 +10,13 @@ #include #include +#include #include -#include #include #include #include + #include #include @@ -134,13 +135,13 @@ bool CppToolsJsExtension::hasQObjectParent(const QString &klassName) const // Find class in AST. const CPlusPlus::Snapshot snapshot = CppModelManager::instance()->snapshot(); const WorkingCopy workingCopy = CppModelManager::instance()->workingCopy(); - QByteArray source = workingCopy.source(item->filePath()); - if (source.isEmpty()) { + std::optional source = workingCopy.source(item->filePath()); + if (!source) { const Utils::expected_str contents = item->filePath().fileContents(); QTC_ASSERT_EXPECTED(contents, return false); source = *contents; } - const auto doc = snapshot.preprocessedDocument(source, item->filePath()); + const auto doc = snapshot.preprocessedDocument(*source, item->filePath()); if (!doc) return false; doc->check(); @@ -234,7 +235,7 @@ QString CppToolsJsExtension::includeStatement( } return false; }; - for (const Project * const p : SessionManager::projects()) { + for (const Project * const p : ProjectManager::projects()) { const Node *theNode = p->rootProjectNode()->findNode(nodeMatchesFileName); if (theNode) { const bool sameDir = pathOfIncludingFile == theNode->filePath().toFileInfo().path(); diff --git a/src/plugins/cppeditor/cpptoolsreuse.cpp b/src/plugins/cppeditor/cpptoolsreuse.cpp index 7d17e30e4bc..8f68ed3ec9d 100644 --- a/src/plugins/cppeditor/cpptoolsreuse.cpp +++ b/src/plugins/cppeditor/cpptoolsreuse.cpp @@ -21,7 +21,9 @@ #include #include #include -#include + +#include + #include #include @@ -29,6 +31,7 @@ #include #include #include + #include #include #include @@ -263,10 +266,7 @@ const Macro *findCanonicalMacro(const QTextCursor &cursor, Document::Ptr documen { QTC_ASSERT(document, return nullptr); - int line, column; - Utils::Text::convertPosition(cursor.document(), cursor.position(), &line, &column); - - if (const Macro *macro = document->findMacroDefinitionAt(line)) { + if (const Macro *macro = document->findMacroDefinitionAt(cursor.blockNumber() + 1)) { QTextCursor macroCursor = cursor; const QByteArray name = identifierUnderCursor(¯oCursor).toUtf8(); if (macro->name() == name) @@ -596,12 +596,12 @@ NamespaceAST *NSCheckerVisitor::currentNamespace() ProjectExplorer::Project *projectForProjectPart(const ProjectPart &part) { - return ProjectExplorer::SessionManager::projectWithProjectFilePath(part.topLevelProject); + return ProjectExplorer::ProjectManager::projectWithProjectFilePath(part.topLevelProject); } ProjectExplorer::Project *projectForProjectInfo(const ProjectInfo &info) { - return ProjectExplorer::SessionManager::projectWithProjectFilePath(info.projectFilePath()); + return ProjectExplorer::ProjectManager::projectWithProjectFilePath(info.projectFilePath()); } void openEditor(const Utils::FilePath &filePath, bool inNextSplit, Utils::Id editorId) diff --git a/src/plugins/cppeditor/cpptoolssettings.cpp b/src/plugins/cppeditor/cpptoolssettings.cpp index 3d5b4658b84..0b5536fdd72 100644 --- a/src/plugins/cppeditor/cpptoolssettings.cpp +++ b/src/plugins/cppeditor/cpptoolssettings.cpp @@ -134,54 +134,6 @@ CppToolsSettings::CppToolsSettings() // load global settings (after built-in settings are added to the pool) d->m_globalCodeStyle->fromSettings(QLatin1String(Constants::CPP_SETTINGS_ID), s); - // legacy handling start (Qt Creator Version < 2.4) - const bool legacyTransformed = - s->value(QLatin1String("CppCodeStyleSettings/LegacyTransformed"), false).toBool(); - - if (!legacyTransformed) { - // creator 2.4 didn't mark yet the transformation (first run of creator 2.4) - - // we need to transform the settings only if at least one from - // below settings was already written - otherwise we use - // defaults like it would be the first run of creator 2.4 without stored settings - const QStringList groups = s->childGroups(); - const bool needTransform = groups.contains(QLatin1String("textTabPreferences")) || - groups.contains(QLatin1String("CppTabPreferences")) || - groups.contains(QLatin1String("CppCodeStyleSettings")); - if (needTransform) { - CppCodeStyleSettings legacyCodeStyleSettings; - if (groups.contains(QLatin1String("CppCodeStyleSettings"))) { - Utils::fromSettings(QLatin1String("CppCodeStyleSettings"), - QString(), s, &legacyCodeStyleSettings); - } - - const QString currentFallback = s->value(QLatin1String("CppTabPreferences/CurrentFallback")).toString(); - TabSettings legacyTabSettings; - if (currentFallback == QLatin1String("CppGlobal")) { - // no delegate, global overwritten - Utils::fromSettings(QLatin1String("CppTabPreferences"), - QString(), s, &legacyTabSettings); - } else { - // delegating to global - legacyTabSettings = TextEditorSettings::codeStyle()->currentTabSettings(); - } - - // create custom code style out of old settings - QVariant v; - v.setValue(legacyCodeStyleSettings); - ICodeStylePreferences *oldCreator = pool->createCodeStyle( - "legacy", legacyTabSettings, v, Tr::tr("Old Creator")); - - // change the current delegate and save - d->m_globalCodeStyle->setCurrentDelegate(oldCreator); - d->m_globalCodeStyle->toSettings(QLatin1String(Constants::CPP_SETTINGS_ID), s); - } - // mark old settings as transformed - s->setValue(QLatin1String("CppCodeStyleSettings/LegacyTransformed"), true); - // legacy handling stop - } - - // mimetypes to be handled TextEditorSettings::registerMimeTypeForLanguageId(Constants::C_SOURCE_MIMETYPE, Constants::CPP_SETTINGS_ID); TextEditorSettings::registerMimeTypeForLanguageId(Constants::C_HEADER_MIMETYPE, Constants::CPP_SETTINGS_ID); diff --git a/src/plugins/cppeditor/cpptoolstestcase.cpp b/src/plugins/cppeditor/cpptoolstestcase.cpp index b7124b01ca4..706423c0515 100644 --- a/src/plugins/cppeditor/cpptoolstestcase.cpp +++ b/src/plugins/cppeditor/cpptoolstestcase.cpp @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include #include @@ -158,7 +158,7 @@ bool VerifyCleanCppModelManager::isClean(bool testOnlyForCleanedProjects) if (!testOnlyForCleanedProjects) { RETURN_FALSE_IF_NOT(mm->snapshot().isEmpty()); RETURN_FALSE_IF_NOT(mm->workingCopy().size() == 1); - RETURN_FALSE_IF_NOT(mm->workingCopy().contains(mm->configurationFileName())); + RETURN_FALSE_IF_NOT(mm->workingCopy().get(mm->configurationFileName())); } return true; } @@ -348,8 +348,8 @@ bool TestCase::waitUntilProjectIsFullyOpened(Project *project, int timeOutInMs) return QTest::qWaitFor( [project]() { - return SessionManager::startupBuildSystem() - && !SessionManager::startupBuildSystem()->isParsing() + return ProjectManager::startupBuildSystem() + && !ProjectManager::startupBuildSystem()->isParsing() && CppModelManager::instance()->projectInfo(project); }, timeOutInMs); @@ -367,7 +367,7 @@ bool TestCase::writeFile(const FilePath &filePath, const QByteArray &contents) ProjectOpenerAndCloser::ProjectOpenerAndCloser() { - QVERIFY(!SessionManager::hasProjects()); + QVERIFY(!ProjectManager::hasProjects()); } ProjectOpenerAndCloser::~ProjectOpenerAndCloser() diff --git a/src/plugins/cppeditor/cpptypehierarchy.cpp b/src/plugins/cppeditor/cpptypehierarchy.cpp index c7a9e472ca5..837375b6f37 100644 --- a/src/plugins/cppeditor/cpptypehierarchy.cpp +++ b/src/plugins/cppeditor/cpptypehierarchy.cpp @@ -154,8 +154,6 @@ CppTypeHierarchyWidget::CppTypeHierarchyWidget() this, &CppTypeHierarchyWidget::perform); connect(&m_futureWatcher, &QFutureWatcher::finished, this, &CppTypeHierarchyWidget::displayHierarchy); - - m_synchronizer.setCancelOnWait(true); } void CppTypeHierarchyWidget::perform() @@ -183,7 +181,8 @@ void CppTypeHierarchyWidget::perform() m_futureWatcher.setFuture(QFuture(m_future)); m_synchronizer.addFuture(m_future); - Core::ProgressManager::addTask(m_future, Tr::tr("Evaluating Type Hierarchy"), "TypeHierarchy"); + Core::ProgressManager::addTimedTask(m_futureWatcher.future(), + Tr::tr("Evaluating Type Hierarchy"), "TypeHierarchy", 2); } void CppTypeHierarchyWidget::performFromExpression(const QString &expression, const FilePath &filePath) diff --git a/src/plugins/cppeditor/cppuseselections_test.cpp b/src/plugins/cppeditor/cppuseselections_test.cpp index f2f13ea55f2..c9c56752844 100644 --- a/src/plugins/cppeditor/cppuseselections_test.cpp +++ b/src/plugins/cppeditor/cppuseselections_test.cpp @@ -104,7 +104,7 @@ SelectionList UseSelectionsTestCase::toSelectionList( int line, column; const int position = qMin(selection.cursor.position(), selection.cursor.anchor()); m_editorWidget->convertPosition(position, &line, &column); - result << Selection(line, column - 1, selection.cursor.selectedText().length()); + result << Selection(line, column, selection.cursor.selectedText().length()); } return result; } diff --git a/src/plugins/cppeditor/cppworkingcopy.cpp b/src/plugins/cppeditor/cppworkingcopy.cpp index b8922b7076d..828388c96ce 100644 --- a/src/plugins/cppeditor/cppworkingcopy.cpp +++ b/src/plugins/cppeditor/cppworkingcopy.cpp @@ -20,4 +20,18 @@ namespace CppEditor { WorkingCopy::WorkingCopy() = default; +std::optional WorkingCopy::source(const Utils::FilePath &fileName) const +{ + if (const auto value = get(fileName)) + return value->first; + return {}; +} + +std::optional> WorkingCopy::get(const Utils::FilePath &fileName) const +{ + const auto it = _elements.constFind(fileName); + if (it == _elements.constEnd()) + return {}; + return it.value(); +} } // namespace CppEditor diff --git a/src/plugins/cppeditor/cppworkingcopy.h b/src/plugins/cppeditor/cppworkingcopy.h index ccc8258745d..87a613bc7ac 100644 --- a/src/plugins/cppeditor/cppworkingcopy.h +++ b/src/plugins/cppeditor/cppworkingcopy.h @@ -11,6 +11,8 @@ #include #include +#include + namespace CppEditor { class CPPEDITOR_EXPORT WorkingCopy @@ -21,17 +23,12 @@ public: void insert(const Utils::FilePath &fileName, const QByteArray &source, unsigned revision = 0) { _elements.insert(fileName, {source, revision}); } - bool contains(const Utils::FilePath &fileName) const - { return _elements.contains(fileName); } - - QByteArray source(const Utils::FilePath &fileName) const - { return _elements.value(fileName).first; } + std::optional source(const Utils::FilePath &fileName) const; unsigned revision(const Utils::FilePath &fileName) const { return _elements.value(fileName).second; } - QPair get(const Utils::FilePath &fileName) const - { return _elements.value(fileName); } + std::optional> get(const Utils::FilePath &fileName) const; using Table = QHash >; const Table &elements() const diff --git a/src/plugins/cppeditor/doxygengenerator.cpp b/src/plugins/cppeditor/doxygengenerator.cpp index 24cfba4abfa..3d88ddd0f55 100644 --- a/src/plugins/cppeditor/doxygengenerator.cpp +++ b/src/plugins/cppeditor/doxygengenerator.cpp @@ -44,16 +44,6 @@ void DoxygenGenerator::setAddLeadingAsterisks(bool add) m_addLeadingAsterisks = add; } -static int lineBeforeCursor(const QTextCursor &cursor) -{ - int line, column; - const bool converted = Utils::Text::convertPosition(cursor.document(), cursor.position(), &line, - &column); - QTC_ASSERT(converted, return std::numeric_limits::max()); - - return line - 1; -} - QString DoxygenGenerator::generate(QTextCursor cursor, const CPlusPlus::Snapshot &snapshot, const Utils::FilePath &documentFilePath) @@ -104,7 +94,7 @@ QString DoxygenGenerator::generate(QTextCursor cursor, Document::Ptr doc = snapshot.preprocessedDocument(declCandidate.toUtf8(), documentFilePath, - lineBeforeCursor(initialCursor)); + cursor.blockNumber()); doc->parse(Document::ParseDeclaration); doc->check(Document::FastCheck); diff --git a/src/plugins/cppeditor/editordocumenthandle.cpp b/src/plugins/cppeditor/editordocumenthandle.cpp index 89b20f2ef3f..fe1638f85a2 100644 --- a/src/plugins/cppeditor/editordocumenthandle.cpp +++ b/src/plugins/cppeditor/editordocumenthandle.cpp @@ -14,11 +14,6 @@ namespace CppEditor { CppEditorDocumentHandle::~CppEditorDocumentHandle() = default; -SendDocumentTracker &CppEditorDocumentHandle::sendTracker() -{ - return m_sendTracker; -} - CppEditorDocumentHandle::RefreshReason CppEditorDocumentHandle::refreshReason() const { return m_refreshReason; diff --git a/src/plugins/cppeditor/editordocumenthandle.h b/src/plugins/cppeditor/editordocumenthandle.h index 28c60ddae9f..ef9b8403b58 100644 --- a/src/plugins/cppeditor/editordocumenthandle.h +++ b/src/plugins/cppeditor/editordocumenthandle.h @@ -4,7 +4,6 @@ #pragma once #include "cppeditor_global.h" -#include "senddocumenttracker.h" namespace Utils { class FilePath; } @@ -35,10 +34,7 @@ public: virtual void resetProcessor() = 0; - SendDocumentTracker &sendTracker(); - private: - SendDocumentTracker m_sendTracker; RefreshReason m_refreshReason = None; }; diff --git a/src/plugins/cppeditor/fileandtokenactions_test.cpp b/src/plugins/cppeditor/fileandtokenactions_test.cpp index b4b60d6828c..2ad6b6b4ef2 100644 --- a/src/plugins/cppeditor/fileandtokenactions_test.cpp +++ b/src/plugins/cppeditor/fileandtokenactions_test.cpp @@ -163,7 +163,7 @@ TestActionsTestCase::TestActionsTestCase(const Actions &tokenActions, const Acti QCOMPARE(DocumentModel::openedDocuments().size(), 1); QVERIFY(m_modelManager->isCppEditor(editor)); - QVERIFY(m_modelManager->workingCopy().contains(filePath)); + QVERIFY(m_modelManager->workingCopy().get(filePath)); // Rehighlight waitForRehighlightedSemanticDocument(editorWidget); diff --git a/src/plugins/cppeditor/followsymbol_switchmethoddecldef_test.cpp b/src/plugins/cppeditor/followsymbol_switchmethoddecldef_test.cpp index 90ddfbdf905..31d69f7363d 100644 --- a/src/plugins/cppeditor/followsymbol_switchmethoddecldef_test.cpp +++ b/src/plugins/cppeditor/followsymbol_switchmethoddecldef_test.cpp @@ -414,6 +414,7 @@ F2TestCase::F2TestCase(CppEditorAction action, } else { currentTextEditor->convertPosition(targetTestFile->m_targetCursorPosition, &expectedLine, &expectedColumn); + ++expectedColumn; if (useClangd && (tag == "classDestructor" || tag == "fromDestructorDefinitionSymbol" || tag == "fromDestructorBody")) { --expectedColumn; // clangd goes before the ~, built-in code model after diff --git a/src/plugins/cppeditor/generatedcodemodelsupport.cpp b/src/plugins/cppeditor/generatedcodemodelsupport.cpp index 400e77fb378..22a9b2735eb 100644 --- a/src/plugins/cppeditor/generatedcodemodelsupport.cpp +++ b/src/plugins/cppeditor/generatedcodemodelsupport.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/plugins/cppeditor/generatedcodemodelsupport.h b/src/plugins/cppeditor/generatedcodemodelsupport.h index cd7591a3b00..d37177d9910 100644 --- a/src/plugins/cppeditor/generatedcodemodelsupport.h +++ b/src/plugins/cppeditor/generatedcodemodelsupport.h @@ -20,7 +20,7 @@ public: const Utils::FilePath &generatedFile); ~GeneratedCodeModelSupport() override; - /// \returns the contents encoded in UTF-8. + /// Returns the contents encoded in UTF-8. QByteArray contents() const override; Utils::FilePath filePath() const override; // The generated file Utils::FilePath sourceFilePath() const override; diff --git a/src/plugins/cppeditor/indexitem.cpp b/src/plugins/cppeditor/indexitem.cpp index 3094c9d4945..879625263e2 100644 --- a/src/plugins/cppeditor/indexitem.cpp +++ b/src/plugins/cppeditor/indexitem.cpp @@ -9,7 +9,8 @@ namespace CppEditor { IndexItem::Ptr IndexItem::create(const QString &symbolName, const QString &symbolType, const QString &symbolScope, IndexItem::ItemType type, - const QString &fileName, int line, int column, const QIcon &icon) + const QString &fileName, int line, int column, const QIcon &icon, + bool isFunctionDefinition) { Ptr ptr(new IndexItem); @@ -21,6 +22,7 @@ IndexItem::Ptr IndexItem::create(const QString &symbolName, const QString &symbo ptr->m_line = line; ptr->m_column = column; ptr->m_icon = icon; + ptr->m_isFuncDef = isFunctionDefinition; return ptr; } diff --git a/src/plugins/cppeditor/indexitem.h b/src/plugins/cppeditor/indexitem.h index eda023394df..135bd60adcd 100644 --- a/src/plugins/cppeditor/indexitem.h +++ b/src/plugins/cppeditor/indexitem.h @@ -40,7 +40,8 @@ public: const QString &fileName, int line, int column, - const QIcon &icon); + const QIcon &icon, + bool isFunctionDefinition); static Ptr create(const QString &fileName, int sizeHint); QString scopedSymbolName() const @@ -64,6 +65,7 @@ public: ItemType type() const { return m_type; } int line() const { return m_line; } int column() const { return m_column; } + bool isFunctionDefinition() const { return m_isFuncDef; } void addChild(IndexItem::Ptr childItem) { m_children.append(childItem); } void squeeze(); @@ -106,6 +108,7 @@ private: ItemType m_type = All; int m_line = 0; int m_column = 0; + bool m_isFuncDef = false; QVector m_children; }; diff --git a/src/plugins/cppeditor/insertionpointlocator.h b/src/plugins/cppeditor/insertionpointlocator.h index c4091d5f9dc..d6fd5d735cc 100644 --- a/src/plugins/cppeditor/insertionpointlocator.h +++ b/src/plugins/cppeditor/insertionpointlocator.h @@ -23,27 +23,21 @@ public: InsertionLocation(const Utils::FilePath &filePath, const QString &prefix, const QString &suffix, int line, int column); - const Utils::FilePath &filePath() const - { return m_filePath; } + const Utils::FilePath &filePath() const { return m_filePath; } - /// \returns The prefix to insert before any other text. - QString prefix() const - { return m_prefix; } + /// Returns the prefix to insert before any other text. + QString prefix() const { return m_prefix; } - /// \returns The suffix to insert after the other inserted text. - QString suffix() const - { return m_suffix; } + /// Returns the suffix to insert after the other inserted text. + QString suffix() const { return m_suffix; } - /// \returns The line where to insert. The line number is 1-based. - int line() const - { return m_line; } + /// Returns the line where to insert. The line number is 1-based. + int line() const { return m_line; } - /// \returns The column where to insert. The column number is 1-based. - int column() const - { return m_column; } + /// Returns the column where to insert. The column number is 1-based. + int column() const { return m_column; } - bool isValid() const - { return !m_filePath.isEmpty() && m_line > 0 && m_column > 0; } + bool isValid() const { return !m_filePath.isEmpty() && m_line > 0 && m_column > 0; } private: Utils::FilePath m_filePath; diff --git a/src/plugins/cppeditor/modelmanagertesthelper.cpp b/src/plugins/cppeditor/modelmanagertesthelper.cpp index f4114c33b27..a133ac7fef2 100644 --- a/src/plugins/cppeditor/modelmanagertesthelper.cpp +++ b/src/plugins/cppeditor/modelmanagertesthelper.cpp @@ -6,7 +6,7 @@ #include "cpptoolstestcase.h" #include "projectinfo.h" -#include +#include #include @@ -59,7 +59,7 @@ void ModelManagerTestHelper::cleanup() CppModelManager *mm = CppModelManager::instance(); QList pies = mm->projectInfos(); for (Project * const p : std::as_const(m_projects)) { - ProjectExplorer::SessionManager::removeProject(p); + ProjectExplorer::ProjectManager::removeProject(p); emit aboutToRemoveProject(p); } @@ -72,7 +72,7 @@ ModelManagerTestHelper::Project *ModelManagerTestHelper::createProject( { auto tp = new TestProject(name, this, filePath); m_projects.push_back(tp); - ProjectExplorer::SessionManager::addProject(tp); + ProjectExplorer::ProjectManager::addProject(tp); emit projectAdded(tp); return tp; } diff --git a/src/plugins/cppeditor/projectinfo_test.cpp b/src/plugins/cppeditor/projectinfo_test.cpp index 73254647f5e..88115e2ea4f 100644 --- a/src/plugins/cppeditor/projectinfo_test.cpp +++ b/src/plugins/cppeditor/projectinfo_test.cpp @@ -362,12 +362,12 @@ public: ProjectInfo::ConstPtr generate() { - QFutureInterface fi; + QPromise promise; projectUpdateInfo.rawProjectParts += rawProjectPart; - ProjectInfoGenerator generator(fi, projectUpdateInfo); + ProjectInfoGenerator generator(projectUpdateInfo); - return generator.generate(); + return generator.generate(promise); } ProjectUpdateInfo projectUpdateInfo; diff --git a/src/plugins/cppeditor/projectpart.cpp b/src/plugins/cppeditor/projectpart.cpp index 9cab9a6df2b..bea37f91ab4 100644 --- a/src/plugins/cppeditor/projectpart.cpp +++ b/src/plugins/cppeditor/projectpart.cpp @@ -163,6 +163,7 @@ CPlusPlus::LanguageFeatures ProjectPart::deriveLanguageFeatures() const CPlusPlus::LanguageFeatures features; features.cxx11Enabled = languageVersion >= Utils::LanguageVersion::CXX11; features.cxx14Enabled = languageVersion >= Utils::LanguageVersion::CXX14; + features.cxx17Enabled = languageVersion >= Utils::LanguageVersion::CXX17; features.cxx20Enabled = languageVersion >= Utils::LanguageVersion::CXX20; features.cxxEnabled = hasCxx; features.c99Enabled = languageVersion >= Utils::LanguageVersion::C99; diff --git a/src/plugins/cppeditor/resourcepreviewhoverhandler.cpp b/src/plugins/cppeditor/resourcepreviewhoverhandler.cpp index 0937df32ab6..6c282b879cc 100644 --- a/src/plugins/cppeditor/resourcepreviewhoverhandler.cpp +++ b/src/plugins/cppeditor/resourcepreviewhoverhandler.cpp @@ -166,7 +166,7 @@ void ResourcePreviewHoverHandler::operateTooltip(TextEditorWidget *editorWidget, { const QString tt = makeTooltip(); if (!tt.isEmpty()) - Utils::ToolTip::show(point, tt, editorWidget); + Utils::ToolTip::show(point, tt, Qt::MarkdownText, editorWidget); else Utils::ToolTip::hide(); } @@ -180,10 +180,8 @@ QString ResourcePreviewHoverHandler::makeTooltip() const const Utils::MimeType mimeType = Utils::mimeTypeForFile(m_resPath); if (mimeType.name().startsWith("image", Qt::CaseInsensitive)) - ret += QString("
").arg(m_resPath); - - ret += QString("
%2") - .arg(m_resPath, QDir::toNativeSeparators(m_resPath)); + ret += QString("![image](%1) \n").arg(m_resPath); + ret += QString("[%1](%2)").arg(QDir::toNativeSeparators(m_resPath), m_resPath); return ret; } diff --git a/src/plugins/cppeditor/searchsymbols.cpp b/src/plugins/cppeditor/searchsymbols.cpp index 22e0c281975..2aea7f8e1bf 100644 --- a/src/plugins/cppeditor/searchsymbols.cpp +++ b/src/plugins/cppeditor/searchsymbols.cpp @@ -285,7 +285,8 @@ IndexItem::Ptr SearchSymbols::addChildItem(const QString &symbolName, const QStr StringTable::insert(path), symbol->line(), symbol->column() - 1, // 1-based vs 0-based column - icon); + icon, + symbol->asFunction()); _parent->addChild(newItem); return newItem; } diff --git a/src/plugins/cppeditor/semantichighlighter.cpp b/src/plugins/cppeditor/semantichighlighter.cpp index 473991787d0..156a1d40524 100644 --- a/src/plugins/cppeditor/semantichighlighter.cpp +++ b/src/plugins/cppeditor/semantichighlighter.cpp @@ -63,15 +63,21 @@ void SemanticHighlighter::run() connectWatcher(); m_revision = documentRevision(); + m_seenBlocks.clear(); + m_nextResultToHandle = m_resultCount = 0; qCDebug(log) << "starting runner for document revision" << m_revision; m_watcher->setFuture(m_highlightingRunner()); } -static Parentheses getClearedParentheses(const QTextBlock &block) +Parentheses SemanticHighlighter::getClearedParentheses(const QTextBlock &block) { - return Utils::filtered(TextDocumentLayout::parentheses(block), [](const Parenthesis &p) { - return p.source != parenSource(); - }); + Parentheses parens = TextDocumentLayout::parentheses(block); + if (m_seenBlocks.insert(block.blockNumber()).second) { + parens = Utils::filtered(parens, [](const Parenthesis &p) { + return p.source != parenSource(); + }); + } + return parens; } void SemanticHighlighter::onHighlighterResultAvailable(int from, int to) @@ -87,6 +93,21 @@ void SemanticHighlighter::onHighlighterResultAvailable(int from, int to) return; } + QTC_CHECK(from == m_resultCount); + m_resultCount = to; + if (to - m_nextResultToHandle >= 100) { + handleHighlighterResults(); + m_nextResultToHandle = to; + } +} + +void SemanticHighlighter::handleHighlighterResults() +{ + int from = m_nextResultToHandle; + const int to = m_resultCount; + if (from >= to) + return; + QElapsedTimer t; t.start(); @@ -172,6 +193,8 @@ void SemanticHighlighter::onHighlighterFinished() { QTC_ASSERT(m_watcher, return); + handleHighlighterResults(); + QElapsedTimer t; t.start(); diff --git a/src/plugins/cppeditor/semantichighlighter.h b/src/plugins/cppeditor/semantichighlighter.h index b34c49ce87a..ea49f289a01 100644 --- a/src/plugins/cppeditor/semantichighlighter.h +++ b/src/plugins/cppeditor/semantichighlighter.h @@ -8,14 +8,21 @@ #include #include #include +#include #include +#include namespace TextEditor { class HighlightingResult; +class Parenthesis; class TextDocument; } +QT_BEGIN_NAMESPACE +class QTextBlock; +QT_END_NAMESPACE + namespace CppEditor { class CPPEDITOR_EXPORT SemanticHighlighter : public QObject @@ -60,12 +67,14 @@ public: private: void onHighlighterResultAvailable(int from, int to); + void handleHighlighterResults(); void onHighlighterFinished(); void connectWatcher(); void disconnectWatcher(); unsigned documentRevision() const; + QVector getClearedParentheses(const QTextBlock &block); private: TextEditor::TextDocument *m_baseTextDocument; @@ -73,6 +82,9 @@ private: unsigned m_revision = 0; QScopedPointer> m_watcher; QHash m_formatMap; + std::set m_seenBlocks; + int m_nextResultToHandle = 0; + int m_resultCount = 0; HighlightingRunner m_highlightingRunner; }; diff --git a/src/plugins/cppeditor/senddocumenttracker.cpp b/src/plugins/cppeditor/senddocumenttracker.cpp deleted file mode 100644 index f7044a65a2b..00000000000 --- a/src/plugins/cppeditor/senddocumenttracker.cpp +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "senddocumenttracker.h" - -#include - -#ifdef WITH_TESTS -#include -#endif - -namespace CppEditor { - -void SendDocumentTracker::setLastSentRevision(int revision) -{ - m_lastSentRevision = revision; - m_contentChangeStartPosition = std::numeric_limits::max(); -} - -int SendDocumentTracker::lastSentRevision() const -{ - return m_lastSentRevision; -} - -void SendDocumentTracker::setLastCompletionPosition(int lastCompletionPosition) -{ - m_lastCompletionPosition = lastCompletionPosition; -} - -int SendDocumentTracker::lastCompletionPosition() const -{ - return m_lastCompletionPosition; -} - -void SendDocumentTracker::applyContentChange(int startPosition) -{ - if (startPosition < m_lastCompletionPosition) - m_lastCompletionPosition = -1; - - m_contentChangeStartPosition = std::min(startPosition, m_contentChangeStartPosition); -} - -bool SendDocumentTracker::shouldSendCompletion(int newCompletionPosition) const -{ - return m_lastCompletionPosition != newCompletionPosition; -} - -bool SendDocumentTracker::shouldSendRevision(uint newRevision) const -{ - return m_lastSentRevision != int(newRevision); -} - -bool SendDocumentTracker::shouldSendRevisionWithCompletionPosition(int newRevision, int newCompletionPosition) const -{ - if (shouldSendRevision(newRevision)) - return changedBeforeCompletionPosition(newCompletionPosition); - - return false; -} - -bool SendDocumentTracker::changedBeforeCompletionPosition(int newCompletionPosition) const -{ - return m_contentChangeStartPosition < newCompletionPosition; -} - -#ifdef WITH_TESTS -namespace Internal { - -void DocumentTrackerTest::testDefaultLastSentRevision() -{ - SendDocumentTracker tracker; - - QCOMPARE(tracker.lastSentRevision(), -1); - QCOMPARE(tracker.lastCompletionPosition(), -1); -} - -void DocumentTrackerTest::testSetRevision() -{ - SendDocumentTracker tracker; - tracker.setLastSentRevision(46); - - QCOMPARE(tracker.lastSentRevision(), 46); - QCOMPARE(tracker.lastCompletionPosition(), -1); -} - -void DocumentTrackerTest::testSetLastCompletionPosition() -{ - SendDocumentTracker tracker; - tracker.setLastCompletionPosition(33); - - QCOMPARE(tracker.lastSentRevision(), -1); - QCOMPARE(tracker.lastCompletionPosition(), 33); -} - -void DocumentTrackerTest::testApplyContentChange() -{ - SendDocumentTracker tracker; - tracker.setLastSentRevision(46); - tracker.setLastCompletionPosition(33); - tracker.applyContentChange(10); - - QCOMPARE(tracker.lastSentRevision(), 46); - QCOMPARE(tracker.lastCompletionPosition(), -1); -} - -void DocumentTrackerTest::testDontSendCompletionIfPositionIsEqual() -{ - SendDocumentTracker tracker; - tracker.setLastCompletionPosition(33); - - QVERIFY(!tracker.shouldSendCompletion(33)); -} - -void DocumentTrackerTest::testSendCompletionIfPositionIsDifferent() -{ - SendDocumentTracker tracker; - tracker.setLastSentRevision(46); - tracker.setLastCompletionPosition(33); - - QVERIFY(tracker.shouldSendCompletion(22)); -} - -void DocumentTrackerTest::testSendCompletionIfChangeIsBeforeCompletionPositionAndPositionIsEqual() -{ - SendDocumentTracker tracker; - tracker.setLastSentRevision(46); - tracker.setLastCompletionPosition(33); - tracker.applyContentChange(10); - - QVERIFY(tracker.shouldSendCompletion(33)); -} - -void DocumentTrackerTest::testDontSendCompletionIfChangeIsAfterCompletionPositionAndPositionIsEqual() -{ - SendDocumentTracker tracker; - tracker.setLastSentRevision(46); - tracker.setLastCompletionPosition(33); - tracker.applyContentChange(40); - - QVERIFY(!tracker.shouldSendCompletion(33)); -} - -void DocumentTrackerTest::testDontSendRevisionIfRevisionIsEqual() -{ - SendDocumentTracker tracker; - tracker.setLastSentRevision(46); - - QVERIFY(!tracker.shouldSendRevision(46)); -} - -void DocumentTrackerTest::testSendRevisionIfRevisionIsDifferent() -{ - SendDocumentTracker tracker; - tracker.setLastSentRevision(46); - - QVERIFY(tracker.shouldSendRevision(21)); -} - -void DocumentTrackerTest::testDontSendRevisionWithDefaults() -{ - SendDocumentTracker tracker; - QVERIFY(!tracker.shouldSendRevisionWithCompletionPosition(21, 33)); -} - -void DocumentTrackerTest::testDontSendIfRevisionIsDifferentAndCompletionPositionIsEqualAndNoContentChange() -{ - SendDocumentTracker tracker; - tracker.setLastSentRevision(46); - tracker.setLastCompletionPosition(33); - - QVERIFY(!tracker.shouldSendRevisionWithCompletionPosition(21, 33)); -} - -void DocumentTrackerTest::testDontSendIfRevisionIsDifferentAndCompletionPositionIsDifferentAndNoContentChange() -{ - SendDocumentTracker tracker; - tracker.setLastSentRevision(46); - tracker.setLastCompletionPosition(33); - - QVERIFY(!tracker.shouldSendRevisionWithCompletionPosition(21, 44)); -} - -void DocumentTrackerTest::testDontSendIfRevisionIsEqualAndCompletionPositionIsDifferentAndNoContentChange() -{ - SendDocumentTracker tracker; - tracker.setLastSentRevision(46); - tracker.setLastCompletionPosition(33); - - QVERIFY(!tracker.shouldSendRevisionWithCompletionPosition(46,44)); -} - -void DocumentTrackerTest::testSendIfChangeIsBeforeCompletionAndPositionIsEqualAndRevisionIsDifferent() -{ - SendDocumentTracker tracker; - tracker.setLastSentRevision(46); - tracker.setLastCompletionPosition(33); - tracker.applyContentChange(10); - - QVERIFY(tracker.shouldSendRevisionWithCompletionPosition(45, 33)); -} - -void DocumentTrackerTest::testDontSendIfChangeIsAfterCompletionPositionAndRevisionIsDifferent() -{ - SendDocumentTracker tracker; - tracker.setLastSentRevision(46); - tracker.setLastCompletionPosition(50); - tracker.applyContentChange(40); - - QVERIFY(!tracker.shouldSendRevisionWithCompletionPosition(45, 36)); -} - -void DocumentTrackerTest::testSendIfChangeIsBeforeCompletionPositionAndRevisionIsDifferent() -{ - SendDocumentTracker tracker; - tracker.setLastSentRevision(46); - tracker.setLastCompletionPosition(50); - tracker.applyContentChange(30); - - QVERIFY(tracker.shouldSendRevisionWithCompletionPosition(45, 36)); -} - -void DocumentTrackerTest::testResetChangedContentStartPositionIfLastRevisionIsSet() -{ - SendDocumentTracker tracker; - tracker.setLastSentRevision(46); - tracker.setLastCompletionPosition(50); - tracker.applyContentChange(30); - tracker.setLastSentRevision(47); - - QVERIFY(!tracker.shouldSendRevisionWithCompletionPosition(45, 36)); -} - -} // namespace Internal -#endif - -} // namespace CppEditor diff --git a/src/plugins/cppeditor/senddocumenttracker.h b/src/plugins/cppeditor/senddocumenttracker.h deleted file mode 100644 index f2736efdd99..00000000000 --- a/src/plugins/cppeditor/senddocumenttracker.h +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "cppeditor_global.h" - -#include - -#include - -namespace CppEditor { - -class CPPEDITOR_EXPORT SendDocumentTracker -{ -public: - void setLastSentRevision(int lastSentRevision); - int lastSentRevision() const; - - void setLastCompletionPosition(int lastCompletionPosition); - int lastCompletionPosition() const; - - void applyContentChange(int startPosition); - - bool shouldSendCompletion(int newCompletionPosition) const; - bool shouldSendRevision(uint newRevision) const; - bool shouldSendRevisionWithCompletionPosition(int newRevision, int newCompletionPosition) const; - -private: - bool changedBeforeCompletionPosition(int newCompletionPosition) const; - -private: - int m_lastSentRevision = -1; - int m_lastCompletionPosition = -1; - int m_contentChangeStartPosition = std::numeric_limits::max(); -}; - -#ifdef WITH_TESTS -namespace Internal { -class DocumentTrackerTest : public QObject -{ - Q_OBJECT - -private slots: - void testDefaultLastSentRevision(); - void testSetRevision(); - void testSetLastCompletionPosition(); - void testApplyContentChange(); - void testDontSendCompletionIfPositionIsEqual(); - void testSendCompletionIfPositionIsDifferent(); - void testSendCompletionIfChangeIsBeforeCompletionPositionAndPositionIsEqual(); - void testDontSendCompletionIfChangeIsAfterCompletionPositionAndPositionIsEqual(); - void testDontSendRevisionIfRevisionIsEqual(); - void testSendRevisionIfRevisionIsDifferent(); - void testDontSendRevisionWithDefaults(); - void testDontSendIfRevisionIsDifferentAndCompletionPositionIsEqualAndNoContentChange(); - void testDontSendIfRevisionIsDifferentAndCompletionPositionIsDifferentAndNoContentChange(); - void testDontSendIfRevisionIsEqualAndCompletionPositionIsDifferentAndNoContentChange(); - void testSendIfChangeIsBeforeCompletionAndPositionIsEqualAndRevisionIsDifferent(); - void testDontSendIfChangeIsAfterCompletionPositionAndRevisionIsDifferent(); - void testSendIfChangeIsBeforeCompletionPositionAndRevisionIsDifferent(); - void testResetChangedContentStartPositionIfLastRevisionIsSet(); -}; -} // namespace Internal -#endif // WITH_TESTS - -} // namespace CppEditor diff --git a/src/plugins/cppeditor/symbolsearcher_test.cpp b/src/plugins/cppeditor/symbolsearcher_test.cpp index 9f700d99726..688df3d924f 100644 --- a/src/plugins/cppeditor/symbolsearcher_test.cpp +++ b/src/plugins/cppeditor/symbolsearcher_test.cpp @@ -4,13 +4,13 @@ #include "symbolsearcher_test.h" #include "cppindexingsupport.h" -#include "cppmodelmanager.h" #include "cpptoolstestcase.h" #include "searchsymbols.h" #include #include -#include + +#include #include @@ -35,10 +35,10 @@ public: return m_symbolName == other.m_symbolName && m_scope == other.m_scope; } - static ResultDataList fromSearchResultList(const QList &entries) + static ResultDataList fromSearchResultList(const Utils::SearchResultItems &entries) { ResultDataList result; - for (const Core::SearchResultItem &entry : entries) + for (const Utils::SearchResultItem &entry : entries) result << ResultData(entry.lineText(), entry.path().join(QLatin1String("::"))); return result; } @@ -77,8 +77,8 @@ public: const QScopedPointer symbolSearcher( new SymbolSearcher(searchParameters, QSet{testFile})); - QFuture search - = Utils::runAsync(&SymbolSearcher::runSearch, symbolSearcher.data()); + QFuture search + = Utils::asyncRun(&SymbolSearcher::runSearch, symbolSearcher.data()); search.waitForFinished(); ResultDataList results = ResultData::fromSearchResultList(search.results()); QCOMPARE(results, expectedResults); diff --git a/src/plugins/cppeditor/symbolsfindfilter.cpp b/src/plugins/cppeditor/symbolsfindfilter.cpp index 3b901715b2c..eb6a4c81dab 100644 --- a/src/plugins/cppeditor/symbolsfindfilter.cpp +++ b/src/plugins/cppeditor/symbolsfindfilter.cpp @@ -12,18 +12,19 @@ #include #include #include + #include #include -#include +#include #include -#include +#include #include -#include +#include #include #include -#include +#include using namespace Core; using namespace Utils; @@ -108,7 +109,7 @@ void SymbolsFindFilter::startSearch(SearchResult *search) SymbolSearcher::Parameters parameters = search->userData().value(); QSet projectFileNames; if (parameters.scope == SymbolSearcher::SearchProjectsOnly) { - for (ProjectExplorer::Project *project : ProjectExplorer::SessionManager::projects()) + for (ProjectExplorer::Project *project : ProjectExplorer::ProjectManager::projects()) projectFileNames += Utils::transform(project->files(ProjectExplorer::Project::AllFiles), &Utils::FilePath::toString); } @@ -120,7 +121,7 @@ void SymbolsFindFilter::startSearch(SearchResult *search) SymbolSearcher *symbolSearcher = new SymbolSearcher(parameters, projectFileNames); connect(watcher, &QFutureWatcherBase::finished, symbolSearcher, &QObject::deleteLater); - watcher->setFuture(Utils::runAsync(m_manager->sharedThreadPool(), + watcher->setFuture(Utils::asyncRun(m_manager->sharedThreadPool(), &SymbolSearcher::runSearch, symbolSearcher)); FutureProgress *progress = ProgressManager::addTask(watcher->future(), Tr::tr("Searching for Symbol"), Core::Constants::TASK_SEARCH); @@ -135,7 +136,7 @@ void SymbolsFindFilter::addResults(QFutureWatcher *watcher, in watcher->cancel(); return; } - QList items; + SearchResultItems items; for (int i = begin; i < end; ++i) items << watcher->resultAt(i); search->addResults(items, SearchResult::AddSorted); diff --git a/src/plugins/cppeditor/symbolsfindfilter.h b/src/plugins/cppeditor/symbolsfindfilter.h index 61866aae4db..a77f5873606 100644 --- a/src/plugins/cppeditor/symbolsfindfilter.h +++ b/src/plugins/cppeditor/symbolsfindfilter.h @@ -6,7 +6,6 @@ #include "searchsymbols.h" #include -#include #include #include @@ -16,6 +15,7 @@ #include namespace Core { class SearchResult; } +namespace Utils { class SearchResultItem; } namespace CppEditor { class CppModelManager; @@ -52,10 +52,10 @@ signals: void symbolsToSearchChanged(); private: - void openEditor(const Core::SearchResultItem &item); + void openEditor(const Utils::SearchResultItem &item); - void addResults(QFutureWatcher *watcher, int begin, int end); - void finish(QFutureWatcher *watcher); + void addResults(QFutureWatcher *watcher, int begin, int end); + void finish(QFutureWatcher *watcher); void cancel(Core::SearchResult *search); void setPaused(Core::SearchResult *search, bool paused); void onTaskStarted(Utils::Id type); @@ -67,7 +67,7 @@ private: CppModelManager *m_manager; bool m_enabled; - QMap *, QPointer > m_watchers; + QMap *, QPointer > m_watchers; QPointer m_currentSearch; SearchSymbols::SymbolTypes m_symbolsToSearch; SearchScope m_scope; diff --git a/src/plugins/cppeditor/testcases/highlightingtestcase.cpp b/src/plugins/cppeditor/testcases/highlightingtestcase.cpp index 20fa2526467..53ffcc1b55e 100644 --- a/src/plugins/cppeditor/testcases/highlightingtestcase.cpp +++ b/src/plugins/cppeditor/testcases/highlightingtestcase.cpp @@ -1,7 +1,7 @@ auto func() { return R"(foo - foobar + R"notaprefix!( barfoobar)" R"(second)" /* comment */ R"(third)"; } @@ -25,3 +25,15 @@ template class C; struct ConversionFunction { operator int(); }; + +template concept NoConstraint = true; + +const char16_t *operator ""_w(const char16_t *s, size_t) { return s; } +const auto s = u"one"_w; +const auto s2 = L"hello"; +const auto s3 = u8"hello"; +const auto s4 = U"hello"; +const auto s5 = uR"("o + ne")"_w; +const auto s6 = u"o\ +ne"_w; diff --git a/src/plugins/cppeditor/typehierarchybuilder.cpp b/src/plugins/cppeditor/typehierarchybuilder.cpp index 5f3aae00e79..889d0eacfc0 100644 --- a/src/plugins/cppeditor/typehierarchybuilder.cpp +++ b/src/plugins/cppeditor/typehierarchybuilder.cpp @@ -108,20 +108,12 @@ const QList &TypeHierarchy::hierarchy() const } TypeHierarchy TypeHierarchyBuilder::buildDerivedTypeHierarchy(Symbol *symbol, - const Snapshot &snapshot) -{ - QFutureInterfaceBase dummy; - return TypeHierarchyBuilder::buildDerivedTypeHierarchy(dummy, symbol, snapshot); -} - -TypeHierarchy TypeHierarchyBuilder::buildDerivedTypeHierarchy(QFutureInterfaceBase &futureInterface, - Symbol *symbol, - const Snapshot &snapshot) + const Snapshot &snapshot, const std::optional> &future) { TypeHierarchy hierarchy(symbol); TypeHierarchyBuilder builder; QHash> cache; - builder.buildDerived(futureInterface, &hierarchy, snapshot, cache); + builder.buildDerived(future, &hierarchy, snapshot, cache); return hierarchy; } @@ -172,11 +164,10 @@ static FilePaths filesDependingOn(const Snapshot &snapshot, Symbol *symbol) return FilePaths{file} + snapshot.filesDependingOn(file); } -void TypeHierarchyBuilder::buildDerived(QFutureInterfaceBase &futureInterface, +void TypeHierarchyBuilder::buildDerived(const std::optional> &future, TypeHierarchy *typeHierarchy, const Snapshot &snapshot, - QHash> &cache, - int depth) + QHash> &cache) { Symbol *symbol = typeHierarchy->_symbol; if (_visited.contains(symbol)) @@ -188,15 +179,10 @@ void TypeHierarchyBuilder::buildDerived(QFutureInterfaceBase &futureInterface, DerivedHierarchyVisitor visitor(symbolName, cache); const FilePaths dependingFiles = filesDependingOn(snapshot, symbol); - if (depth == 0) - futureInterface.setProgressRange(0, dependingFiles.size()); - int i = -1; for (const FilePath &fileName : dependingFiles) { - if (futureInterface.isCanceled()) + if (future && future->isCanceled()) return; - if (depth == 0) - futureInterface.setProgressValue(++i); Document::Ptr doc = snapshot.document(fileName); if ((_candidates.contains(fileName) && !_candidates.value(fileName).contains(symbolName)) || !doc->control()->findIdentifier(symbol->identifier()->chars(), @@ -210,8 +196,8 @@ void TypeHierarchyBuilder::buildDerived(QFutureInterfaceBase &futureInterface, const QList &derived = visitor.derived(); for (Symbol *s : derived) { TypeHierarchy derivedHierarchy(s); - buildDerived(futureInterface, &derivedHierarchy, snapshot, cache, depth + 1); - if (futureInterface.isCanceled()) + buildDerived(future, &derivedHierarchy, snapshot, cache); + if (future && future->isCanceled()) return; typeHierarchy->_hierarchy.append(derivedHierarchy); } diff --git a/src/plugins/cppeditor/typehierarchybuilder.h b/src/plugins/cppeditor/typehierarchybuilder.h index 07556126dd8..c6ee8a56bf6 100644 --- a/src/plugins/cppeditor/typehierarchybuilder.h +++ b/src/plugins/cppeditor/typehierarchybuilder.h @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include @@ -44,19 +44,17 @@ class TypeHierarchyBuilder { public: static TypeHierarchy buildDerivedTypeHierarchy(CPlusPlus::Symbol *symbol, - const CPlusPlus::Snapshot &snapshot); - static TypeHierarchy buildDerivedTypeHierarchy(QFutureInterfaceBase &futureInterface, - CPlusPlus::Symbol *symbol, - const CPlusPlus::Snapshot &snapshot); + const CPlusPlus::Snapshot &snapshot, + const std::optional> &future = {}); static CPlusPlus::LookupItem followTypedef(const CPlusPlus::LookupContext &context, const CPlusPlus::Name *symbolName, CPlusPlus::Scope *enclosingScope, std::set typedefs = {}); private: TypeHierarchyBuilder() = default; - void buildDerived(QFutureInterfaceBase &futureInterface, TypeHierarchy *typeHierarchy, + void buildDerived(const std::optional> &future, TypeHierarchy *typeHierarchy, const CPlusPlus::Snapshot &snapshot, - QHash > &cache, int depth = 0); + QHash > &cache); QSet _visited; QHash > _candidates; diff --git a/src/plugins/ctfvisualizer/ctfvisualizertool.cpp b/src/plugins/ctfvisualizer/ctfvisualizertool.cpp index 0fbc5cac650..05cf55d729f 100644 --- a/src/plugins/ctfvisualizer/ctfvisualizertool.cpp +++ b/src/plugins/ctfvisualizer/ctfvisualizertool.cpp @@ -15,6 +15,8 @@ #include #include #include + +#include #include #include @@ -64,7 +66,7 @@ CtfVisualizerTool::CtfVisualizerTool() m_restrictToThreadsButton->setIcon(Utils::Icons::FILTER.icon()); m_restrictToThreadsButton->setToolTip(Tr::tr("Restrict to Threads")); m_restrictToThreadsButton->setPopupMode(QToolButton::InstantPopup); - m_restrictToThreadsButton->setProperty("noArrow", true); + m_restrictToThreadsButton->setProperty(Utils::StyleHelper::C_NO_ARROW, true); m_restrictToThreadsButton->setMenu(m_restrictToThreadsMenu); connect(m_restrictToThreadsMenu, &QMenu::triggered, this, &CtfVisualizerTool::toggleThreadRestriction); diff --git a/src/plugins/cvs/cvsplugin.cpp b/src/plugins/cvs/cvsplugin.cpp index 85e98c1e4ce..d9980328fc9 100644 --- a/src/plugins/cvs/cvsplugin.cpp +++ b/src/plugins/cvs/cvsplugin.cpp @@ -137,34 +137,28 @@ static inline bool messageBoxQuestion(const QString &title, const QString &quest class CvsDiffConfig : public VcsBaseEditorConfig { public: - CvsDiffConfig(CvsSettings &settings, QToolBar *toolBar) : - VcsBaseEditorConfig(toolBar), - m_settings(settings) + CvsDiffConfig(QToolBar *toolBar) + : VcsBaseEditorConfig(toolBar) { mapSetting(addToggleButton("-w", Tr::tr("Ignore Whitespace")), - &settings.diffIgnoreWhiteSpace); + &settings().diffIgnoreWhiteSpace); mapSetting(addToggleButton("-B", Tr::tr("Ignore Blank Lines")), - &settings.diffIgnoreBlankLines); + &settings().diffIgnoreBlankLines); } QStringList arguments() const override { - return m_settings.diffOptions.value().split(' ', Qt::SkipEmptyParts) + return settings().diffOptions.value().split(' ', Qt::SkipEmptyParts) + VcsBaseEditorConfig::arguments(); } - -private: - CvsSettings &m_settings; }; class CvsClient : public VcsBaseClient { public: - explicit CvsClient(CvsSettings *settings) : VcsBaseClient(settings) + explicit CvsClient() : VcsBaseClient(&Internal::settings()) { - setDiffConfigCreator([settings](QToolBar *toolBar) { - return new CvsDiffConfig(*settings, toolBar); - }); + setDiffConfigCreator([](QToolBar *toolBar) { return new CvsDiffConfig(toolBar); }); } ExitCodeInterpreter exitCodeInterpreter(VcsCommandTag cmd) const override @@ -294,7 +288,7 @@ private: bool commit(const QString &messageFile, const QStringList &subVersionFileList); void cleanCommitMessageFile(); - CvsSettings m_settings; + CvsSettings m_setting; CvsClient *m_client = nullptr; QString m_commitMessageFileName; @@ -327,8 +321,6 @@ private: QAction *m_menuAction = nullptr; - CvsSettingsPage m_settingsPage{&m_settings}; - public: VcsSubmitEditorFactory submitEditorFactory { submitParameters, @@ -374,7 +366,7 @@ bool CvsPluginPrivate::isVcsFileOrDirectory(const Utils::FilePath &filePath) con bool CvsPluginPrivate::isConfigured() const { - const Utils::FilePath binary = m_settings.binaryPath.filePath(); + const FilePath binary = settings().binaryPath(); if (binary.isEmpty()) return false; QFileInfo fi = binary.toFileInfo(); @@ -447,7 +439,7 @@ VcsCommand *CvsPluginPrivate::createInitialCheckoutCommand(const QString &url, auto command = VcsBaseClient::createVcsCommand(baseDirectory, Environment::systemEnvironment()); command->setDisplayName(Tr::tr("CVS Checkout")); - command->addJob({m_settings.binaryPath.filePath(), m_settings.addOptions(args)}, -1); + command->addJob({settings().binaryPath(), settings().addOptions(args)}, -1); return command; } @@ -497,7 +489,7 @@ CvsPluginPrivate::CvsPluginPrivate() dd = this; Context context(CVS_CONTEXT); - m_client = new CvsClient(&m_settings); + m_client = new CvsClient; const QString prefix = QLatin1String("cvs"); m_commandLocator = new CommandLocator("CVS", prefix, prefix, this); @@ -692,7 +684,7 @@ CvsPluginPrivate::CvsPluginPrivate() cvsMenu->addAction(command); m_commandLocator->appendCommand(command); - connect(&m_settings, &AspectContainer::applied, this, &IVersionControl::configurationChanged); + connect(&settings(), &AspectContainer::applied, this, &IVersionControl::configurationChanged); } void CvsPluginPrivate::vcsDescribe(const FilePath &source, const QString &changeNr) @@ -1230,7 +1222,7 @@ bool CvsPluginPrivate::describe(const FilePath &toplevel, const QString &file, *errorMessage = Tr::tr("Parsing of the log output failed."); return false; } - if (m_settings.describeByCommitId.value()) { + if (settings().describeByCommitId()) { // Run a log command over the repo, filtering by the commit date // and commit id, collecting all files touched by the commit. const QString commitId = fileLog.front().revisions.front().commitId; @@ -1286,7 +1278,7 @@ bool CvsPluginPrivate::describe(const FilePath &repositoryPath, for (QList::iterator it = entries.begin(); it != lend; ++it) { const QString &revision = it->revisions.front().revision; if (!isFirstRevision(revision)) { - const QStringList args{"diff", m_settings.diffOptions.value(), + const QStringList args{"diff", settings().diffOptions(), "-r", previousRevision(revision), "-r", it->revisions.front().revision, it->file}; const auto diffResponse = runCvs(repositoryPath, args, RunFlags::None, codec); @@ -1329,13 +1321,13 @@ CommandResult CvsPluginPrivate::runCvs(const FilePath &workingDirectory, const QStringList &arguments, RunFlags flags, QTextCodec *outputCodec, int timeoutMultiplier) const { - const FilePath executable = m_settings.binaryPath.filePath(); + const FilePath executable = settings().binaryPath(); if (executable.isEmpty()) return CommandResult(ProcessResult::StartFailed, Tr::tr("No CVS executable specified.")); - const int timeoutS = m_settings.timeout.value() * timeoutMultiplier; + const int timeoutS = settings().timeout() * timeoutMultiplier; return m_client->vcsSynchronousExec(workingDirectory, - {executable, m_settings.addOptions(arguments)}, + {executable, settings().addOptions(arguments)}, flags, timeoutS, outputCodec); } diff --git a/src/plugins/cvs/cvssettings.cpp b/src/plugins/cvs/cvssettings.cpp index 917db719211..9e1523486fa 100644 --- a/src/plugins/cvs/cvssettings.cpp +++ b/src/plugins/cvs/cvssettings.cpp @@ -17,32 +17,37 @@ using namespace Utils; namespace Cvs::Internal { -// CvsSettings +static CvsSettings *theSettings; + +CvsSettings &settings() +{ + return *theSettings; +} CvsSettings::CvsSettings() { + theSettings = this; setSettingsGroup("CVS"); - registerAspect(&binaryPath); + setId(VcsBase::Constants::VCS_ID_CVS); + setDisplayName(Tr::tr("CVS")); + setCategory(VcsBase::Constants::VCS_SETTINGS_CATEGORY); + binaryPath.setDefaultValue("cvs" QTC_HOST_EXE_SUFFIX); - binaryPath.setDisplayStyle(StringAspect::PathChooserDisplay); binaryPath.setExpectedKind(PathChooser::ExistingCommand); binaryPath.setHistoryCompleter(QLatin1String("Cvs.Command.History")); binaryPath.setDisplayName(Tr::tr("CVS Command")); binaryPath.setLabelText(Tr::tr("CVS command:")); - registerAspect(&cvsRoot); cvsRoot.setDisplayStyle(StringAspect::LineEditDisplay); cvsRoot.setSettingsKey("Root"); cvsRoot.setLabelText(Tr::tr("CVS root:")); - registerAspect(&diffOptions); diffOptions.setDisplayStyle(StringAspect::LineEditDisplay); diffOptions.setSettingsKey("DiffOptions"); diffOptions.setDefaultValue("-du"); diffOptions.setLabelText("Diff options:"); - registerAspect(&describeByCommitId); describeByCommitId.setSettingsKey("DescribeByCommitId"); describeByCommitId.setDefaultValue(true); describeByCommitId.setLabelText(Tr::tr("Describe all files matching commit id")); @@ -50,11 +55,33 @@ CvsSettings::CvsSettings() "displayed when clicking on a revision number in the annotation view " "(retrieved via commit ID). Otherwise, only the respective file will be displayed.")); - registerAspect(&diffIgnoreWhiteSpace); diffIgnoreWhiteSpace.setSettingsKey("DiffIgnoreWhiteSpace"); - registerAspect(&diffIgnoreBlankLines); diffIgnoreBlankLines.setSettingsKey("DiffIgnoreBlankLines"); + + setLayouter([this] { + using namespace Layouting; + return Column { + Group { + title(Tr::tr("Configuration")), + Form { + binaryPath, br, + cvsRoot + } + }, + Group { + title(Tr::tr("Miscellaneous")), + Column { + Form { + timeout, br, + diffOptions, + }, + describeByCommitId, + } + }, + st + }; + }); } QStringList CvsSettings::addOptions(const QStringList &args) const @@ -70,38 +97,4 @@ QStringList CvsSettings::addOptions(const QStringList &args) const return rc; } -CvsSettingsPage::CvsSettingsPage(CvsSettings *settings) -{ - setId(VcsBase::Constants::VCS_ID_CVS); - setDisplayName(Tr::tr("CVS")); - setCategory(VcsBase::Constants::VCS_SETTINGS_CATEGORY); - setSettings(settings); - - setLayouter([settings](QWidget *widget) { - CvsSettings &s = *settings; - using namespace Layouting; - - Column { - Group { - title(Tr::tr("Configuration")), - Form { - s.binaryPath, - s.cvsRoot - } - }, - Group { - title(Tr::tr("Miscellaneous")), - Column { - Form { - s.timeout, - s.diffOptions, - }, - s.describeByCommitId, - } - }, - st - }.attachTo(widget); - }); -} - } // Cvs::Internal diff --git a/src/plugins/cvs/cvssettings.h b/src/plugins/cvs/cvssettings.h index ac52ce9402f..ade9920b2ad 100644 --- a/src/plugins/cvs/cvssettings.h +++ b/src/plugins/cvs/cvssettings.h @@ -3,8 +3,6 @@ #pragma once -#include - #include namespace Cvs::Internal { @@ -12,21 +10,17 @@ namespace Cvs::Internal { class CvsSettings : public VcsBase::VcsBaseSettings { public: - Utils::StringAspect cvsRoot; - Utils::StringAspect diffOptions; - Utils::BoolAspect diffIgnoreWhiteSpace; - Utils::BoolAspect diffIgnoreBlankLines; - Utils::BoolAspect describeByCommitId; - CvsSettings(); + Utils::StringAspect cvsRoot{this}; + Utils::StringAspect diffOptions{this}; + Utils::BoolAspect diffIgnoreWhiteSpace{this}; + Utils::BoolAspect diffIgnoreBlankLines{this}; + Utils::BoolAspect describeByCommitId{this}; + QStringList addOptions(const QStringList &args) const; }; -class CvsSettingsPage final : public Core::IOptionsPage -{ -public: - explicit CvsSettingsPage(CvsSettings *settings); -}; +CvsSettings &settings(); } // Cvs::Internal diff --git a/src/plugins/debugger/CMakeLists.txt b/src/plugins/debugger/CMakeLists.txt index f5f45deb4af..d06e82a27e1 100644 --- a/src/plugins/debugger/CMakeLists.txt +++ b/src/plugins/debugger/CMakeLists.txt @@ -27,6 +27,7 @@ add_qtc_plugin(Debugger console/consoleitemmodel.cpp console/consoleitemmodel.h console/consoleproxymodel.cpp console/consoleproxymodel.h console/consoleview.cpp console/consoleview.h + dap/dapengine.cpp dap/dapengine.h debugger.qrc debugger_global.h debuggeractions.cpp debuggeractions.h diff --git a/src/plugins/debugger/analyzer/analyzerutils.cpp b/src/plugins/debugger/analyzer/analyzerutils.cpp index dcfda572cb5..dc87b2ad614 100644 --- a/src/plugins/debugger/analyzer/analyzerutils.cpp +++ b/src/plugins/debugger/analyzer/analyzerutils.cpp @@ -52,7 +52,7 @@ CPlusPlus::Symbol *AnalyzerUtils::findSymbolUnderCursor() CPlusPlus::ExpressionUnderCursor expressionUnderCursor(doc->languageFeatures()); moveCursorToEndOfName(&tc); const QString &expression = expressionUnderCursor(tc); - CPlusPlus::Scope *scope = doc->scopeAt(line, column - 1); + CPlusPlus::Scope *scope = doc->scopeAt(line, column); CPlusPlus::TypeOfExpression typeOfExpression; typeOfExpression.init(doc, snapshot); diff --git a/src/plugins/debugger/breakhandler.cpp b/src/plugins/debugger/breakhandler.cpp index bcc50a4b0d0..35915f9755f 100644 --- a/src/plugins/debugger/breakhandler.cpp +++ b/src/plugins/debugger/breakhandler.cpp @@ -18,8 +18,7 @@ #include #include #include - -#include +#include #include #include @@ -41,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -1126,9 +1126,9 @@ void BreakpointItem::addToCommand(DebuggerCommand *cmd) const cmd->arg("expression", requested.expression); } -void BreakpointItem::updateFromGdbOutput(const GdbMi &bkpt) +void BreakpointItem::updateFromGdbOutput(const GdbMi &bkpt, const FilePath &fileRoot) { - m_parameters.updateFromGdbOutput(bkpt); + m_parameters.updateFromGdbOutput(bkpt, fileRoot); adjustMarker(); } @@ -2691,14 +2691,13 @@ void BreakpointManager::gotoLocation(const GlobalBreakpoint &gbp) const void BreakpointManager::executeDeleteAllBreakpointsDialog() { - QDialogButtonBox::StandardButton pressed = - CheckableMessageBox::doNotAskAgainQuestion(ICore::dialogParent(), - Tr::tr("Remove All Breakpoints"), - Tr::tr("Are you sure you want to remove all breakpoints " - "from all files in the current session?"), - ICore::settings(), - "RemoveAllBreakpoints"); - if (pressed != QDialogButtonBox::Yes) + QMessageBox::StandardButton pressed + = CheckableMessageBox::question(ICore::dialogParent(), + Tr::tr("Remove All Breakpoints"), + Tr::tr("Are you sure you want to remove all breakpoints " + "from all files in the current session?"), + QString("RemoveAllBreakpoints")); + if (pressed != QMessageBox::Yes) return; for (GlobalBreakpoint gbp : globalBreakpoints()) diff --git a/src/plugins/debugger/breakhandler.h b/src/plugins/debugger/breakhandler.h index 1024783db38..0b939a011fc 100644 --- a/src/plugins/debugger/breakhandler.h +++ b/src/plugins/debugger/breakhandler.h @@ -107,7 +107,7 @@ public: const BreakpointParameters &requestedParameters() const; void addToCommand(DebuggerCommand *cmd) const; - void updateFromGdbOutput(const GdbMi &bkpt); + void updateFromGdbOutput(const GdbMi &bkpt, const Utils::FilePath &fileRoot); int modelId() const; QString responseId() const { return m_responseId; } diff --git a/src/plugins/debugger/breakpoint.cpp b/src/plugins/debugger/breakpoint.cpp index 8d456ef67f6..79e2badf7b5 100644 --- a/src/plugins/debugger/breakpoint.cpp +++ b/src/plugins/debugger/breakpoint.cpp @@ -265,7 +265,8 @@ static QString cleanupFullName(const QString &fileName) return cleanFilePath; } -void BreakpointParameters::updateFromGdbOutput(const GdbMi &bkpt) + +void BreakpointParameters::updateFromGdbOutput(const GdbMi &bkpt, const Utils::FilePath &fileRoot) { QTC_ASSERT(bkpt.isValid(), return); @@ -358,7 +359,7 @@ void BreakpointParameters::updateFromGdbOutput(const GdbMi &bkpt) QString name; if (!fullName.isEmpty()) { name = cleanupFullName(fullName); - fileName = Utils::FilePath::fromString(name); + fileName = fileRoot.withNewPath(name); //if (data->markerFileName().isEmpty()) // data->setMarkerFileName(name); } else { @@ -367,7 +368,7 @@ void BreakpointParameters::updateFromGdbOutput(const GdbMi &bkpt) // gdb's own. No point in assigning markerFileName for now. } if (!name.isEmpty()) - fileName = Utils::FilePath::fromString(name); + fileName = fileRoot.withNewPath(name); if (fileName.isEmpty()) updateLocation(originalLocation); diff --git a/src/plugins/debugger/breakpoint.h b/src/plugins/debugger/breakpoint.h index 52a150f15e2..b5ea0628da7 100644 --- a/src/plugins/debugger/breakpoint.h +++ b/src/plugins/debugger/breakpoint.h @@ -128,7 +128,7 @@ public: bool isQmlFileAndLineBreakpoint() const; QString toString() const; void updateLocation(const QString &location); // file.cpp:42 - void updateFromGdbOutput(const GdbMi &bkpt); + void updateFromGdbOutput(const GdbMi &bkpt, const Utils::FilePath &fileRoot); bool operator==(const BreakpointParameters &p) const { return equals(p); } bool operator!=(const BreakpointParameters &p) const { return !equals(p); } diff --git a/src/plugins/debugger/cdb/cdbengine.cpp b/src/plugins/debugger/cdb/cdbengine.cpp index b95d1c93d52..7c70a3c31b0 100644 --- a/src/plugins/debugger/cdb/cdbengine.cpp +++ b/src/plugins/debugger/cdb/cdbengine.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -44,9 +45,9 @@ #include #include #include +#include #include #include -#include #include #include @@ -181,8 +182,8 @@ CdbEngine::CdbEngine() : DebuggerSettings *s = debuggerSettings(); connect(s->createFullBacktrace.action(), &QAction::triggered, this, &CdbEngine::createFullBacktrace); - connect(&m_process, &QtcProcess::started, this, &CdbEngine::processStarted); - connect(&m_process, &QtcProcess::done, this, &CdbEngine::processDone); + connect(&m_process, &Process::started, this, &CdbEngine::processStarted); + connect(&m_process, &Process::done, this, &CdbEngine::processDone); m_process.setStdOutLineCallback([this](const QString &line) { parseOutputLine(line); }); m_process.setStdErrLineCallback([this](const QString &line) { parseOutputLine(line); }); connect(&s->useDebuggingHelpers, &BaseAspect::changed, @@ -224,7 +225,8 @@ void CdbEngine::init() } } - const SourcePathMap &sourcePathMap = debuggerSettings()->sourcePathMap.value(); + const SourcePathMap &sourcePathMap + = mergePlatformQtPath(runParameters(), debuggerSettings()->sourcePathMap.value()); if (!sourcePathMap.isEmpty()) { for (auto it = sourcePathMap.constBegin(), cend = sourcePathMap.constEnd(); it != cend; ++it) { m_sourcePathMappings.push_back({QDir::toNativeSeparators(it.key()), @@ -419,10 +421,11 @@ void CdbEngine::setupEngine() inferiorEnvironment.set(qtLoggingToConsoleKey, "0"); static const char cdbExtensionPathVariableC[] = "_NT_DEBUGGER_EXTENSION_PATH"; - inferiorEnvironment.prependOrSet(cdbExtensionPathVariableC, extensionFi.absolutePath(), {";"}); + const QString pSep = OsSpecificAspects::pathListSeparator(Utils::OsTypeWindows); + inferiorEnvironment.prependOrSet(cdbExtensionPathVariableC, extensionFi.absolutePath(), pSep); const QString oldCdbExtensionPath = qtcEnvironmentVariable(cdbExtensionPathVariableC); if (!oldCdbExtensionPath.isEmpty()) - inferiorEnvironment.appendOrSet(cdbExtensionPathVariableC, oldCdbExtensionPath, {";"}); + inferiorEnvironment.appendOrSet(cdbExtensionPathVariableC, oldCdbExtensionPath, pSep); m_process.setEnvironment(inferiorEnvironment); if (!sp.inferior.workingDirectory.isEmpty()) @@ -485,10 +488,10 @@ void CdbEngine::handleInitialSessionIdle() runCommand({"sxn ibp", NoFlags}); // Do not break on initial breakpoints. runCommand({".asm source_line", NoFlags}); // Source line in assembly runCommand({m_extensionCommandPrefix - + "setparameter maxStringLength=" + QString::number(s.maximalStringLength.value()) - + " maxStackDepth=" + QString::number(s.maximalStackDepth.value()) - + " firstChance=" + (s.firstChanceExceptionTaskEntry.value() ? "1" : "0") - + " secondChance=" + (s.secondChanceExceptionTaskEntry.value() ? "1" : "0") + + "setparameter maxStringLength=" + QString::number(s.maximalStringLength()) + + " maxStackDepth=" + QString::number(s.maximalStackDepth()) + + " firstChance=" + (s.firstChanceExceptionTaskEntry() ? "1" : "0") + + " secondChance=" + (s.secondChanceExceptionTaskEntry() ? "1" : "0") , NoFlags}); if (s.cdbUsePythonDumper.value()) @@ -954,9 +957,11 @@ void CdbEngine::runCommand(const DebuggerCommand &dbgCmd) return; } - if (dbgCmd.flags == ScriptCommand) { + if (dbgCmd.flags & ScriptCommand) { // repack script command into an extension command DebuggerCommand newCmd("script", ExtensionCommand, dbgCmd.callback); + if (dbgCmd.flags & DebuggerCommand::Silent) + newCmd.flags |= DebuggerCommand::Silent; if (!dbgCmd.args.isNull()) newCmd.args = QString{dbgCmd.function + '(' + dbgCmd.argsToPython() + ')'}; else @@ -975,7 +980,7 @@ void CdbEngine::runCommand(const DebuggerCommand &dbgCmd) } else { const int token = ++m_nextCommandToken; StringInputStream str(fullCmd); - if (dbgCmd.flags == BuiltinCommand) { + if (dbgCmd.flags & BuiltinCommand) { // Post a built-in-command producing free-format output with a callback. // In order to catch the output, it is enclosed in 'echo' commands // printing a specially formatted token to be identifiable in the output. @@ -986,7 +991,7 @@ void CdbEngine::runCommand(const DebuggerCommand &dbgCmd) showMessage("Command is longer than 4096 characters execution will likely fail.", LogWarning); } - } else if (dbgCmd.flags == ExtensionCommand) { + } else if (dbgCmd.flags & ExtensionCommand) { // Post an extension command producing one-line output with a callback, // pass along token for identification in hash. @@ -1020,7 +1025,8 @@ void CdbEngine::runCommand(const DebuggerCommand &dbgCmd) if (debug) { qDebug("CdbEngine::postCommand: resulting command '%s'\n", qPrintable(fullCmd)); } - showMessage(cmd, LogInput); + if (!(dbgCmd.flags & DebuggerCommand::Silent)) + showMessage(cmd, LogInput); m_process.write(fullCmd); } @@ -1441,15 +1447,16 @@ void CdbEngine::reloadModules() runCommand({"modules", ExtensionCommand, CB(handleModules)}); } -void CdbEngine::loadSymbols(const QString & /* moduleName */) +void CdbEngine::loadSymbols(const FilePath &moduleName) { + Q_UNUSED(moduleName) } void CdbEngine::loadAllSymbols() { } -void CdbEngine::requestModuleSymbols(const QString &moduleName) +void CdbEngine::requestModuleSymbols(const FilePath &moduleName) { Q_UNUSED(moduleName) } @@ -1487,12 +1494,13 @@ void CdbEngine::handleModules(const DebuggerResponse &response) { if (response.resultClass == ResultDone) { if (response.data.type() == GdbMi::List) { + const FilePath inferior = runParameters().inferior.command.executable(); ModulesHandler *handler = modulesHandler(); handler->beginUpdateAll(); for (const GdbMi &gdbmiModule : response.data) { Module module; module.moduleName = gdbmiModule["name"].data(); - module.modulePath = gdbmiModule["image"].data(); + module.modulePath = inferior.withNewPath(gdbmiModule["image"].data()); module.startAddress = gdbmiModule["start"].data().toULongLong(nullptr, 0); module.endAddress = gdbmiModule["end"].data().toULongLong(nullptr, 0); if (gdbmiModule["deferred"].type() == GdbMi::Invalid) @@ -2254,12 +2262,10 @@ void CdbEngine::checkQtSdkPdbFiles(const QString &module) "symbols for the debugger.") .arg(qtName); - CheckableMessageBox::doNotShowAgainInformation( - Core::ICore::dialogParent(), - Tr::tr("Missing Qt Debug Information"), - message, - Core::ICore::settings(), - "CdbQtSdkPdbHint"); + CheckableMessageBox::information(Core::ICore::dialogParent(), + Tr::tr("Missing Qt Debug Information"), + message, + QString("CdbQtSdkPdbHint")); showMessage("Missing Qt Debug Information Files package for " + qtName, LogMisc); }; @@ -2280,15 +2286,14 @@ void CdbEngine::parseOutputLine(QString line) // An extension notification (potentially consisting of several chunks) if (!m_initialSessionIdleHandled && line.startsWith("SECURE: File not allowed to be loaded") && line.endsWith("qtcreatorcdbext.dll")) { - CheckableMessageBox::doNotShowAgainInformation( + CheckableMessageBox::information( Core::ICore::dialogParent(), Tr::tr("Debugger Start Failed"), Tr::tr("The system prevents loading of %1, which is required for debugging. " "Make sure that your antivirus solution is up to date and if that does not work " "consider adding an exception for %1.") .arg(m_extensionFileName), - Core::ICore::settings(), - "SecureInfoCdbextCannotBeLoaded"); + QString("SecureInfoCdbextCannotBeLoaded")); notifyEngineSetupFailed(); } static const QString creatorExtPrefix = "|"; @@ -2459,8 +2464,8 @@ static CPlusPlus::Document::Ptr getParsedDocument(const FilePath &filePath, const CPlusPlus::Snapshot &snapshot) { QByteArray src; - if (workingCopy.contains(filePath)) - src = workingCopy.source(filePath); + if (const auto source = workingCopy.source(filePath)) + src = *source; else src = QString::fromLocal8Bit(filePath.fileContents().value_or(QByteArray())).toUtf8(); @@ -2764,17 +2769,65 @@ void CdbEngine::setupScripting(const DebuggerResponse &response) return; } - QString dumperPath = runParameters().dumperPath.toUserOutput(); - dumperPath.replace('\\', "\\\\"); - runCommand({"sys.path.insert(1, '" + dumperPath + "')", ScriptCommand}); - runCommand({"from cdbbridge import Dumper", ScriptCommand}); - runCommand({"print(dir())", ScriptCommand}); - runCommand({"theDumper = Dumper()", ScriptCommand}); - const QString path = debuggerSettings()->extraDumperFile.value(); - if (!path.isEmpty() && QFileInfo(path).isReadable()) { + if (runParameters().startMode == AttachToRemoteServer) { + FilePath dumperPath = Core::ICore::resourcePath("debugger"); + const FilePath loadOrderFile = dumperPath / "loadorder.txt"; + const expected_str toLoad = loadOrderFile.fileContents(); + if (!toLoad) { + Core::AsynchronousMessageBox::critical( + Tr::tr("Cannot Find Debugger Initialization Script"), + Tr::tr("Cannot read %1: %2").arg(loadOrderFile.toUserOutput(), toLoad.error())); + notifyEngineSetupFailed(); + return; + } + + runCommand({"import sys, types", ScriptCommand}); + QStringList moduleList; + for (const QByteArray &rawModuleName : toLoad->split('\n')) { + QString module = QString::fromUtf8(rawModuleName).trimmed(); + if (module.startsWith('#') || module.isEmpty()) + continue; + if (module == "***bridge***") + module = "cdbbridge"; + + const FilePath codeFile = dumperPath / (module + ".py"); + const expected_str code = codeFile.fileContents(); + if (!code) { + qDebug() << Tr::tr("Cannot read %1: %2").arg(codeFile.toUserOutput(), code.error()); + continue; + } + + showMessage("Reading " + codeFile.toUserOutput(), LogInput); + runCommand({QString("module = types.ModuleType('%1')").arg(module), ScriptCommand}); + runCommand({QString("code = bytes.fromhex('%1').decode('utf-8')") + .arg(QString::fromUtf8(code->toHex())), ScriptCommand | DebuggerCommand::Silent}); + runCommand({QString("exec(code, module.__dict__)"), ScriptCommand}); + runCommand({QString("sys.modules['%1'] = module").arg(module), ScriptCommand}); + runCommand({QString("import %1").arg(module), ScriptCommand}); + + if (module.endsWith("types")) + moduleList.append('"' + module + '"'); + } + + runCommand({"from cdbbridge import Dumper", ScriptCommand}); + runCommand({"print(dir())", ScriptCommand}); + runCommand({"theDumper = Dumper()", ScriptCommand}); + runCommand({QString("theDumper.dumpermodules = [%1]").arg(moduleList.join(',')), ScriptCommand}); + + } else { + QString dumperPath = Core::ICore::resourcePath("debugger").toUserOutput(); + dumperPath.replace('\\', "\\\\"); + runCommand({"sys.path.insert(1, '" + dumperPath + "')", ScriptCommand}); + runCommand({"from cdbbridge import Dumper", ScriptCommand}); + runCommand({"print(dir())", ScriptCommand}); + runCommand({"theDumper = Dumper()", ScriptCommand}); + } + + const FilePath path = debuggerSettings()->extraDumperFile(); + if (!path.isEmpty() && path.isReadableFile()) { DebuggerCommand cmd("theDumper.addDumperModule", ScriptCommand); - cmd.arg("path", path); + cmd.arg("path", path.path()); runCommand(cmd); } const QString commands = debuggerSettings()->extraDumperCommands.value(); diff --git a/src/plugins/debugger/cdb/cdbengine.h b/src/plugins/debugger/cdb/cdbengine.h index 3bea692d5b5..84a0dded1a7 100644 --- a/src/plugins/debugger/cdb/cdbengine.h +++ b/src/plugins/debugger/cdb/cdbengine.h @@ -10,7 +10,7 @@ #include -#include +#include #include @@ -65,9 +65,9 @@ public: void changeMemory(MemoryAgent *, quint64 addr, const QByteArray &data) override; void reloadModules() override; - void loadSymbols(const QString &moduleName) override; + void loadSymbols(const Utils::FilePath &moduleName) override; void loadAllSymbols() override; - void requestModuleSymbols(const QString &moduleName) override; + void requestModuleSymbols(const Utils::FilePath &moduleName) override; void reloadRegisters() override; void reloadSourceFiles() override; @@ -110,9 +110,9 @@ private: }; enum CommandFlags { NoFlags = 0, - BuiltinCommand, - ExtensionCommand, - ScriptCommand + BuiltinCommand = DebuggerCommand::Silent << 1, + ExtensionCommand = DebuggerCommand::Silent << 2, + ScriptCommand = DebuggerCommand::Silent << 3 }; void init(); @@ -174,7 +174,7 @@ private: const QString m_tokenPrefix; void handleSetupFailure(const QString &errorMessage); - Utils::QtcProcess m_process; + Utils::Process m_process; DebuggerStartMode m_effectiveStartMode = NoStartMode; //! Debugger accessible (expecting commands) bool m_accessible = false; diff --git a/src/plugins/debugger/commonoptionspage.cpp b/src/plugins/debugger/commonoptionspage.cpp index dc47fb79e08..97e9442266e 100644 --- a/src/plugins/debugger/commonoptionspage.cpp +++ b/src/plugins/debugger/commonoptionspage.cpp @@ -15,8 +15,7 @@ using namespace Core; using namespace Debugger::Constants; using namespace Utils; -namespace Debugger { -namespace Internal { +namespace Debugger::Internal { /////////////////////////////////////////////////////////////////////// // @@ -30,6 +29,19 @@ public: explicit CommonOptionsPageWidget() { DebuggerSettings &s = *debuggerSettings(); + + setOnApply([&s] { + const bool originalPostMortem = s.registerForPostMortem->value(); + const bool currentPostMortem = s.registerForPostMortem->volatileValue().toBool(); + // explicitly trigger setValue() to override the setValueSilently() and trigger the registration + if (originalPostMortem != currentPostMortem) + s.registerForPostMortem->setValue(currentPostMortem); + s.page1.apply(); + s.page1.writeSettings(ICore::settings()); + }); + + setOnFinish([&s] { s.page1.finish(); }); + using namespace Layouting; Column col1 { @@ -60,27 +72,8 @@ public: st }.attachTo(this); } - - void apply() final; - void finish() final { m_group.finish(); } - -private: - AspectContainer &m_group = debuggerSettings()->page1; }; -void CommonOptionsPageWidget::apply() -{ - const DebuggerSettings *s = debuggerSettings(); - const bool originalPostMortem = s->registerForPostMortem->value(); - const bool currentPostMortem = s->registerForPostMortem->volatileValue().toBool(); - // explicitly trigger setValue() to override the setValueSilently() and trigger the registration - if (originalPostMortem != currentPostMortem) - s->registerForPostMortem->setValue(currentPostMortem); - - m_group.apply(); - m_group.writeSettings(ICore::settings()); -} - CommonOptionsPage::CommonOptionsPage() { setId(DEBUGGER_COMMON_SETTINGS_ID); @@ -122,8 +115,9 @@ class LocalsAndExpressionsOptionsPageWidget : public IOptionsPageWidget public: LocalsAndExpressionsOptionsPageWidget() { - using namespace Layouting; DebuggerSettings &s = *debuggerSettings(); + setOnApply([&s] { s.page4.apply(); s.page4.writeSettings(ICore::settings()); }); + setOnFinish([&s] { s.page4.finish(); }); auto label = new QLabel; //(useHelperGroup); label->setTextFormat(Qt::AutoText); @@ -134,6 +128,7 @@ public: "std::map in the "Locals" and "Expressions" views.") + "

"); + using namespace Layouting; Column left { label, s.useCodeModel, @@ -153,7 +148,8 @@ public: Grid limits { s.maximalStringLength, br, - s.displayStringLimit + s.displayStringLimit, br, + s.defaultArraySize }; Column { @@ -168,12 +164,6 @@ public: st }.attachTo(this); } - - void apply() final { m_group.apply(); m_group.writeSettings(ICore::settings()); } - void finish() final { m_group.finish(); } - -private: - AspectContainer &m_group = debuggerSettings()->page4; }; LocalsAndExpressionsOptionsPage::LocalsAndExpressionsOptionsPage() @@ -185,5 +175,4 @@ LocalsAndExpressionsOptionsPage::LocalsAndExpressionsOptionsPage() setWidgetCreator([] { return new LocalsAndExpressionsOptionsPageWidget; }); } -} // namespace Internal -} // namespace Debugger +} // Debugger::Internal diff --git a/src/plugins/debugger/dap/dapengine.cpp b/src/plugins/debugger/dap/dapengine.cpp new file mode 100644 index 00000000000..9db7c0f7e5e --- /dev/null +++ b/src/plugins/debugger/dap/dapengine.cpp @@ -0,0 +1,799 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "dapengine.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Core; +using namespace Utils; + +namespace Debugger::Internal { + +DapEngine::DapEngine() +{ + setObjectName("DapEngine"); + setDebuggerName("DAP"); +} + +void DapEngine::executeDebuggerCommand(const QString &/*command*/) +{ + QTC_ASSERT(state() == InferiorStopOk, qDebug() << state()); +// if (state() == DebuggerNotReady) { +// showMessage("DAP PROCESS NOT RUNNING, PLAIN CMD IGNORED: " + command); +// return; +// } +// QTC_ASSERT(m_proc.isRunning(), notifyEngineIll()); +// postDirectCommand(command); +} + +void DapEngine::postDirectCommand(const QJsonObject &ob) +{ + static int seq = 1; + QJsonObject obseq = ob; + obseq.insert("seq", seq++); + + const QByteArray data = QJsonDocument(obseq).toJson(QJsonDocument::Compact); + const QByteArray msg = "Content-Length: " + QByteArray::number(data.size()) + "\r\n\r\n" + data; + qDebug() << msg; + + m_proc.writeRaw(msg); + + showMessage(QString::fromUtf8(msg), LogInput); +} + +void DapEngine::runCommand(const DebuggerCommand &cmd) +{ + if (state() == EngineSetupRequested) { // cmd has been triggered too early + showMessage("IGNORED COMMAND: " + cmd.function); + return; + } + QTC_ASSERT(m_proc.isRunning(), notifyEngineIll()); +// postDirectCommand(cmd.args.toObject()); +// const QByteArray data = QJsonDocument(cmd.args.toObject()).toJson(QJsonDocument::Compact); +// m_proc.writeRaw("Content-Length: " + QByteArray::number(data.size()) + "\r\n" + data + "\r\n"); + +// showMessage(QString::fromUtf8(data), LogInput); +} + +void DapEngine::shutdownInferior() +{ + QTC_ASSERT(state() == InferiorShutdownRequested, qDebug() << state()); + postDirectCommand({{"command", "terminate"}, + {"type", "request"}}); + + qDebug() << "DapEngine::shutdownInferior()"; + notifyInferiorShutdownFinished(); +} + +void DapEngine::shutdownEngine() +{ + QTC_ASSERT(state() == EngineShutdownRequested, qDebug() << state()); + + qDebug() << "DapEngine::shutdownEngine()"; + m_proc.kill(); +} + +void DapEngine::setupEngine() +{ + QTC_ASSERT(state() == EngineSetupRequested, qDebug() << state()); + + connect(&m_proc, &Process::started, this, &DapEngine::handleDabStarted); + connect(&m_proc, &Process::done, this, &DapEngine::handleDapDone); + connect(&m_proc, &Process::readyReadStandardOutput, this, &DapEngine::readDapStandardOutput); + connect(&m_proc, &Process::readyReadStandardError, this, &DapEngine::readDapStandardError); + + const DebuggerRunParameters &rp = runParameters(); + const CommandLine cmd{rp.debugger.command.executable(), {"-i", "dap"}}; + showMessage("STARTING " + cmd.toUserOutput()); + m_proc.setProcessMode(ProcessMode::Writer); + m_proc.setEnvironment(rp.debugger.environment); + m_proc.setCommand(cmd); + m_proc.start(); + notifyEngineRunAndInferiorRunOk(); +} + +// From the docs: +// The sequence of events/requests is as follows: +// * adapters sends initialized event (after the initialize request has returned) +// * client sends zero or more setBreakpoints requests +// * client sends one setFunctionBreakpoints request +// (if corresponding capability supportsFunctionBreakpoints is true) +// * client sends a setExceptionBreakpoints request if one or more exceptionBreakpointFilters +// have been defined (or if supportsConfigurationDoneRequest is not true) +// * client sends other future configuration requests +// * client sends one configurationDone request to indicate the end of the configuration. + +void DapEngine::handleDabStarted() +{ + notifyEngineSetupOk(); + QTC_ASSERT(state() == EngineRunRequested, qDebug() << state()); + +// CHECK_STATE(EngineRunRequested); + + postDirectCommand({ + {"command", "initialize"}, + {"type", "request"}, + {"arguments", QJsonObject { + {"clientID", "QtCreator"}, // The ID of the client using this adapter. + {"clientName", "QtCreator"} // The human-readable name of the client using this adapter. + }} + }); + + qDebug() << "handleDabStarted"; +} + +void DapEngine::handleDabConfigurationDone() +{ + QTC_ASSERT(state() == EngineRunRequested, qDebug() << state()); + + // CHECK_STATE(EngineRunRequested); + + postDirectCommand({{"command", "configurationDone"}, {"type", "request"}}); + + qDebug() << "handleDabConfigurationDone"; +} + + +void DapEngine::handleDabLaunch() +{ + QTC_ASSERT(state() == EngineRunRequested, qDebug() << state()); + + // CHECK_STATE(EngineRunRequested); + + postDirectCommand( + {{"command", "launch"}, + {"type", "request"}, +// {"program", runParameters().inferior.command.executable().path()}, + {"arguments", + QJsonObject{ + {"noDebug", false}, + {"program", runParameters().inferior.command.executable().path()}, + {"__restart", ""} + }}}); + qDebug() << "handleDabLaunch"; +} + +void DapEngine::interruptInferior() +{ + postDirectCommand({{"command", "pause"}, + {"type", "request"}}); +} + +void DapEngine::executeStepIn(bool) +{ + notifyInferiorRunRequested(); + +// postDirectCommand({{"command", "stepIn"}, +// {"type", "request"}, +// {"arguments", +// QJsonObject{ +// {"threadId", 1}, // The ID of the client using this adapter. +// }}}); + + notifyInferiorRunOk(); +} + +void DapEngine::executeStepOut() +{ + notifyInferiorRunRequested(); + notifyInferiorRunOk(); +// postDirectCommand("return"); +} + +void DapEngine::executeStepOver(bool) +{ + notifyInferiorRunRequested(); + notifyInferiorRunOk(); +// postDirectCommand("next"); +} + +void DapEngine::continueInferior() +{ + notifyInferiorRunRequested(); + postDirectCommand({{"command", "continue"}, + {"type", "request"}, + {"arguments", + QJsonObject{ + {"threadId", 1}, // The ID of the client using this adapter. + }}}); +} + +void DapEngine::executeRunToLine(const ContextData &data) +{ + Q_UNUSED(data) + QTC_CHECK("FIXME: DapEngine::runToLineExec()" && false); +} + +void DapEngine::executeRunToFunction(const QString &functionName) +{ + Q_UNUSED(functionName) + QTC_CHECK("FIXME: DapEngine::runToFunctionExec()" && false); +} + +void DapEngine::executeJumpToLine(const ContextData &data) +{ + Q_UNUSED(data) + QTC_CHECK("FIXME: DapEngine::jumpToLineExec()" && false); +} + +void DapEngine::activateFrame(int frameIndex) +{ + if (state() != InferiorStopOk && state() != InferiorUnrunnable) + return; + + StackHandler *handler = stackHandler(); + QTC_ASSERT(frameIndex < handler->stackSize(), return); + handler->setCurrentIndex(frameIndex); + gotoLocation(handler->currentFrame()); + updateLocals(); +} + +void DapEngine::selectThread(const Thread &thread) +{ + Q_UNUSED(thread) +} + +bool DapEngine::acceptsBreakpoint(const BreakpointParameters &) const +{ + return true; // FIXME: Too bold. +} + +static QJsonObject createBreakpoint(const Breakpoint &breakpoint) +{ + const BreakpointParameters ¶ms = breakpoint->requestedParameters(); + + if (params.fileName.isEmpty()) + return QJsonObject(); + + QJsonObject bp; + bp["line"] = params.lineNumber; + bp["source"] = QJsonObject{{"name", params.fileName.fileName()}, + {"path", params.fileName.path()}}; + return bp; +} + + +void DapEngine::insertBreakpoint(const Breakpoint &bp) +{ + QTC_ASSERT(bp, return); + QTC_CHECK(bp->state() == BreakpointInsertionRequested); + notifyBreakpointInsertProceeding(bp); + + const BreakpointParameters ¶ms = bp->requestedParameters(); + bp->setResponseId(QString::number(m_nextBreakpointId++)); + + QJsonArray breakpoints; + for (const auto &breakpoint : breakHandler()->breakpoints()) { + QJsonObject jsonBp = createBreakpoint(breakpoint); + if (!jsonBp.isEmpty() + && params.fileName.path() == jsonBp["source"].toObject()["path"].toString()) { + breakpoints.append(jsonBp); + } + } + + postDirectCommand( + {{"command", "setBreakpoints"}, + {"type", "request"}, + {"arguments", + QJsonObject{{"source", QJsonObject{{"path", params.fileName.path()}}}, + {"breakpoints", breakpoints} + + }}}); + + qDebug() << "insertBreakpoint" << bp->modelId() << bp->responseId(); +} + +void DapEngine::updateBreakpoint(const Breakpoint &bp) +{ + notifyBreakpointChangeProceeding(bp); +// QTC_ASSERT(bp, return); +// const BreakpointState state = bp->state(); +// if (QTC_GUARD(state == BreakpointUpdateRequested)) +// if (bp->responseId().isEmpty()) // FIXME postpone update somehow (QTimer::singleShot?) +// return; + +// // FIXME figure out what needs to be changed (there might be more than enabled state) +// const BreakpointParameters &requested = bp->requestedParameters(); +// if (requested.enabled != bp->isEnabled()) { +// if (bp->isEnabled()) +// postDirectCommand("disable " + bp->responseId()); +// else +// postDirectCommand("enable " + bp->responseId()); +// bp->setEnabled(!bp->isEnabled()); +// } +// // Pretend it succeeds without waiting for response. + notifyBreakpointChangeOk(bp); +} + +void DapEngine::removeBreakpoint(const Breakpoint &bp) +{ + QTC_ASSERT(bp, return); + QTC_CHECK(bp->state() == BreakpointRemoveRequested); + notifyBreakpointRemoveProceeding(bp); + + const BreakpointParameters ¶ms = bp->requestedParameters(); + + QJsonArray breakpoints; + for (const auto &breakpoint : breakHandler()->breakpoints()) + if (breakpoint->responseId() != bp->responseId() + && params.fileName == breakpoint->requestedParameters().fileName) { + QJsonObject jsonBp = createBreakpoint(breakpoint); + breakpoints.append(jsonBp); + } + + postDirectCommand({{"command", "setBreakpoints"}, + {"type", "request"}, + {"arguments", + QJsonObject{{"source", QJsonObject{{"path", params.fileName.path()}}}, + {"breakpoints", breakpoints}}}}); + + qDebug() << "removeBreakpoint" << bp->modelId() << bp->responseId(); +} + +void DapEngine::loadSymbols(const Utils::FilePath &/*moduleName*/) +{ +} + +void DapEngine::loadAllSymbols() +{ +} + +void DapEngine::reloadModules() +{ + runCommand({"listModules"}); +} + +void DapEngine::refreshModules(const GdbMi &modules) +{ + ModulesHandler *handler = modulesHandler(); + handler->beginUpdateAll(); + for (const GdbMi &item : modules) { + Module module; + module.moduleName = item["name"].data(); + QString path = item["value"].data(); + int pos = path.indexOf("' from '"); + if (pos != -1) { + path = path.mid(pos + 8); + if (path.size() >= 2) + path.chop(2); + } else if (path.startsWith("")) { + path = "(builtin)"; + } + module.modulePath = FilePath::fromString(path); + handler->updateModule(module); + } + handler->endUpdateAll(); +} + +void DapEngine::requestModuleSymbols(const Utils::FilePath &/*moduleName*/) +{ +// DebuggerCommand cmd("listSymbols"); +// cmd.arg("module", moduleName); +// runCommand(cmd); +} + +void DapEngine::refreshState(const GdbMi &reportedState) +{ + QString newState = reportedState.data(); + if (newState == "stopped") { + notifyInferiorSpontaneousStop(); + updateAll(); + } else if (newState == "inferiorexited") { + notifyInferiorExited(); + } +} + +void DapEngine::refreshLocation(const GdbMi &reportedLocation) +{ + StackFrame frame; + frame.file = FilePath::fromString(reportedLocation["file"].data()); + frame.line = reportedLocation["line"].toInt(); + frame.usable = frame.file.isReadableFile(); + if (state() == InferiorRunOk) { + showMessage(QString("STOPPED AT: %1:%2").arg(frame.file.toUserOutput()).arg(frame.line)); + gotoLocation(frame); + notifyInferiorSpontaneousStop(); + updateAll(); + } +} + +void DapEngine::refreshSymbols(const GdbMi &symbols) +{ + QString moduleName = symbols["module"].data(); + Symbols syms; + for (const GdbMi &item : symbols["symbols"]) { + Symbol symbol; + symbol.name = item["name"].data(); + syms.append(symbol); + } + showModuleSymbols(FilePath::fromString(moduleName), syms); +} + +bool DapEngine::canHandleToolTip(const DebuggerToolTipContext &) const +{ + return state() == InferiorStopOk; +} + +void DapEngine::assignValueInDebugger(WatchItem *, const QString &/*expression*/, const QVariant &/*value*/) +{ + //DebuggerCommand cmd("assignValue"); + //cmd.arg("expression", expression); + //cmd.arg("value", value.toString()); + //runCommand(cmd); +// postDirectCommand("global " + expression + ';' + expression + "=" + value.toString()); + updateLocals(); +} + +void DapEngine::updateItem(const QString &iname) +{ + Q_UNUSED(iname) + updateAll(); +} + +QString DapEngine::errorMessage(QProcess::ProcessError error) const +{ + switch (error) { + case QProcess::FailedToStart: + return Tr::tr("The DAP process failed to start. Either the " + "invoked program \"%1\" is missing, or you may have insufficient " + "permissions to invoke the program.") + .arg(m_proc.commandLine().executable().toUserOutput()); + case QProcess::Crashed: + return Tr::tr("The DAP process crashed some time after starting " + "successfully."); + case QProcess::Timedout: + return Tr::tr("The last waitFor...() function timed out. " + "The state of QProcess is unchanged, and you can try calling " + "waitFor...() again."); + case QProcess::WriteError: + return Tr::tr("An error occurred when attempting to write " + "to the DAP process. For example, the process may not be running, " + "or it may have closed its input channel."); + case QProcess::ReadError: + return Tr::tr("An error occurred when attempting to read from " + "the DAP process. For example, the process may not be running."); + default: + return Tr::tr("An unknown error in the DAP process occurred.") + ' '; + } +} + +void DapEngine::handleDapDone() +{ + if (m_proc.result() == ProcessResult::StartFailed) { + notifyEngineSetupFailed(); + showMessage("ADAPTER START FAILED"); + ICore::showWarningWithOptions(Tr::tr("Adapter start failed"), m_proc.exitMessage()); + return; + } + + const QProcess::ProcessError error = m_proc.error(); + if (error != QProcess::UnknownError) { + showMessage("HANDLE DAP ERROR"); + if (error != QProcess::Crashed) + AsynchronousMessageBox::critical(Tr::tr("DAP I/O Error"), errorMessage(error)); + if (error == QProcess::FailedToStart) + return; + } + showMessage(QString("DAP PROCESS FINISHED, status %1, code %2") + .arg(m_proc.exitStatus()).arg(m_proc.exitCode())); + notifyEngineSpontaneousShutdown(); +} + +void DapEngine::readDapStandardError() +{ + QString err = m_proc.readAllStandardError(); + //qWarning() << "Unexpected DAP stderr:" << err; + showMessage("Unexpected DAP stderr: " + err); + //handleOutput(err); +} + +void DapEngine::readDapStandardOutput() +{ + m_inbuffer.append(m_proc.readAllStandardOutput().toUtf8()); +// qDebug() << m_inbuffer; + + while (true) { + // Something like + // Content-Length: 128\r\n + // {"type": "event", "event": "output", "body": {"category": "stdout", "output": "...\n"}, "seq": 1}\r\n + // FIXME: There coud be more than one header line. + int pos1 = m_inbuffer.indexOf("Content-Length:"); + if (pos1 == -1) + break; + pos1 += 15; + + int pos2 = m_inbuffer.indexOf('\n', pos1); + if (pos2 == -1) + break; + + const int len = m_inbuffer.mid(pos1, pos2 - pos1).trimmed().toInt(); + if (len < 4) + break; + + pos2 += 3; // Skip \r\n\r + + if (pos2 + len > m_inbuffer.size()) + break; + + QJsonParseError error; + const auto doc = QJsonDocument::fromJson(m_inbuffer.mid(pos2, len), &error); + + m_inbuffer = m_inbuffer.mid(pos2 + len); + + handleOutput(doc); + } +} + +void DapEngine::handleOutput(const QJsonDocument &data) +{ + QJsonObject ob = data.object(); + + const QJsonValue t = ob.value("type"); + const QString type = t.toString(); + qDebug() << "response" << ob; + + if (type == "response") { + const QString command = ob.value("command").toString(); + if (command == "configurationDone") { + showMessage("configurationDone", LogDebug); + qDebug() << "configurationDone success"; + notifyInferiorRunOk(); + return; + } + + if (command == "continue") { + showMessage("continue", LogDebug); + qDebug() << "continue success"; + notifyInferiorRunOk(); + return; + } + + } + + if (type == "event") { + const QString event = ob.value("event").toString(); + const QJsonObject body = ob.value("body").toObject(); + + if (event == "output") { + const QString category = body.value("category").toString(); + const QString output = body.value("output").toString(); + if (category == "stdout") + showMessage(output, AppOutput); + else if (category == "stderr") + showMessage(output, AppError); + else + showMessage(output, LogDebug); + return; + } + qDebug() << data; + + if (event == "initialized") { + showMessage(event, LogDebug); + qDebug() << "initialize success"; + handleDabLaunch(); + handleDabConfigurationDone(); + return; + } + + if (event == "initialized") { + showMessage(event, LogDebug); + return; + } + + if (event == "stopped") { + showMessage(event, LogDebug); + if (body.value("reason").toString() == "breakpoint") { + QString id = QString::number(body.value("hitBreakpointIds").toArray().first().toInteger()); + const BreakpointParameters ¶ms + = breakHandler()->findBreakpointByResponseId(id)->requestedParameters(); + gotoLocation(Location(params.fileName, params.lineNumber)); + } + + if (state() == InferiorStopRequested) + notifyInferiorStopOk(); + else + notifyInferiorSpontaneousStop(); + return; + } + + if (event == "thread") { + showMessage(event, LogDebug); + if (body.value("reason").toString() == "started" && body.value("threadId").toInt() == 1) + claimInitialBreakpoints(); + return; + } + + if (event == "breakpoint") { + showMessage(event, LogDebug); + QJsonObject breakpoint = body.value("breakpoint").toObject(); + Breakpoint bp = breakHandler()->findBreakpointByResponseId( + QString::number(breakpoint.value("id").toInt())); + qDebug() << "breakpoint id :" << breakpoint.value("id").toInt(); + + if (body.value("reason").toString() == "new") { + if (breakpoint.value("verified").toBool()) { +// bp->setPending(false); + notifyBreakpointInsertOk(bp); + qDebug() << "breakpoint inserted"; + } else { + notifyBreakpointInsertFailed(bp); + qDebug() << "breakpoint insertion failed"; + } + return; + } + + if (body.value("reason").toString() == "removed") { + if (breakpoint.value("verified").toBool()) { + notifyBreakpointRemoveOk(bp); + qDebug() << "breakpoint removed"; + } else { + notifyBreakpointRemoveFailed(bp); + qDebug() << "breakpoint remove failed"; + } + return; + } + return; + } + + + showMessage("UNKNOWN EVENT:" + event); + return; + } + + showMessage("UNKNOWN TYPE:" + type); +} + +void DapEngine::refreshLocals(const GdbMi &vars) +{ + WatchHandler *handler = watchHandler(); + handler->resetValueCache(); + handler->insertItems(vars); + handler->notifyUpdateFinished(); + + updateToolTips(); +} + +void DapEngine::refreshStack(const GdbMi &stack) +{ + StackHandler *handler = stackHandler(); + StackFrames frames; + for (const GdbMi &item : stack["frames"]) { + StackFrame frame; + frame.level = item["level"].data(); + frame.file = FilePath::fromString(item["file"].data()); + frame.function = item["function"].data(); + frame.module = item["function"].data(); + frame.line = item["line"].toInt(); + frame.address = item["address"].toAddress(); + GdbMi usable = item["usable"]; + if (usable.isValid()) + frame.usable = usable.data().toInt(); + else + frame.usable = frame.file.isReadableFile(); + frames.append(frame); + } + bool canExpand = stack["hasmore"].toInt(); + //action(ExpandStack)->setEnabled(canExpand); + handler->setFrames(frames, canExpand); + + int index = stackHandler()->firstUsableIndex(); + handler->setCurrentIndex(index); + if (index >= 0 && index < handler->stackSize()) + gotoLocation(handler->frameAt(index)); +} + +void DapEngine::updateAll() +{ + runCommand({"stackListFrames"}); + updateLocals(); +} + +void DapEngine::updateLocals() +{ +// DebuggerCommand cmd("updateData"); +// cmd.arg("nativeMixed", isNativeMixedActive()); +// watchHandler()->appendFormatRequests(&cmd); +// watchHandler()->appendWatchersAndTooltipRequests(&cmd); + +// const bool alwaysVerbose = qtcEnvironmentVariableIsSet("QTC_DEBUGGER_PYTHON_VERBOSE"); +// cmd.arg("passexceptions", alwaysVerbose); +// cmd.arg("fancy", debuggerSettings()->useDebuggingHelpers.value()); + +// //cmd.arg("resultvarname", m_resultVarName); +// //m_lastDebuggableCommand = cmd; +// //m_lastDebuggableCommand.args.replace("\"passexceptions\":0", "\"passexceptions\":1"); +// cmd.arg("frame", stackHandler()->currentIndex()); + +// watchHandler()->notifyUpdateStarted(); +// runCommand(cmd); +} + +bool DapEngine::hasCapability(unsigned cap) const +{ + return cap & (ReloadModuleCapability + | BreakConditionCapability + | ShowModuleSymbolsCapability); +} + +void DapEngine::claimInitialBreakpoints() +{ + BreakpointManager::claimBreakpointsForEngine(this); + qDebug() << "claimInitialBreakpoints"; +// const Breakpoints bps = breakHandler()->breakpoints(); +// for (const Breakpoint &bp : bps) +// qDebug() << "breakpoit: " << bp->fileName() << bp->lineNumber(); +// qDebug() << "claimInitialBreakpoints end"; + +// const DebuggerRunParameters &rp = runParameters(); +// if (rp.startMode != AttachToCore) { +// showStatusMessage(Tr::tr("Setting breakpoints...")); +// showMessage(Tr::tr("Setting breakpoints...")); +// BreakpointManager::claimBreakpointsForEngine(this); + +// const DebuggerSettings &s = *debuggerSettings(); +// const bool onAbort = s.breakOnAbort.value(); +// const bool onWarning = s.breakOnWarning.value(); +// const bool onFatal = s.breakOnFatal.value(); +// if (onAbort || onWarning || onFatal) { +// DebuggerCommand cmd("createSpecialBreakpoints"); +// cmd.arg("breakonabort", onAbort); +// cmd.arg("breakonwarning", onWarning); +// cmd.arg("breakonfatal", onFatal); +// runCommand(cmd); +// } +// } + +// // It is ok to cut corners here and not wait for createSpecialBreakpoints()'s +// // response, as the command is synchronous from Creator's point of view, +// // and even if it fails (e.g. due to stripped binaries), continuing with +// // the start up is the best we can do. + +// if (!rp.commandsAfterConnect.isEmpty()) { +// const QString commands = expand(rp.commandsAfterConnect); +// for (const QString &command : commands.split('\n')) +// runCommand({command, NativeCommand}); +// } +} + +DebuggerEngine *createDapEngine() +{ + return new DapEngine; +} + +} // Debugger::Internal diff --git a/src/plugins/debugger/dap/dapengine.h b/src/plugins/debugger/dap/dapengine.h new file mode 100644 index 00000000000..2fa3019cad9 --- /dev/null +++ b/src/plugins/debugger/dap/dapengine.h @@ -0,0 +1,98 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include +#include + +#include + +namespace Debugger::Internal { + +class DebuggerCommand; +class GdbMi; + +/* + * A debugger engine for the debugger adapter protocol. + */ + +class DapEngine : public DebuggerEngine +{ +public: + DapEngine(); + +private: + void executeStepIn(bool) override; + void executeStepOut() override; + void executeStepOver(bool) override; + + void setupEngine() override; + void shutdownInferior() override; + void shutdownEngine() override; + + bool canHandleToolTip(const DebuggerToolTipContext &) const override; + + void continueInferior() override; + void interruptInferior() override; + + void executeRunToLine(const ContextData &data) override; + void executeRunToFunction(const QString &functionName) override; + void executeJumpToLine(const ContextData &data) override; + + void activateFrame(int index) override; + void selectThread(const Thread &thread) override; + + bool acceptsBreakpoint(const BreakpointParameters &bp) const override; + void insertBreakpoint(const Breakpoint &bp) override; + void updateBreakpoint(const Breakpoint &bp) override; + void removeBreakpoint(const Breakpoint &bp) override; + + void assignValueInDebugger(WatchItem *item, + const QString &expr, const QVariant &value) override; + void executeDebuggerCommand(const QString &command) override; + + void loadSymbols(const Utils::FilePath &moduleName) override; + void loadAllSymbols() override; + void requestModuleSymbols(const Utils::FilePath &moduleName) override; + void reloadModules() override; + void reloadRegisters() override {} + void reloadSourceFiles() override {} + void reloadFullStack() override {} + + bool supportsThreads() const { return true; } + void updateItem(const QString &iname) override; + + void runCommand(const DebuggerCommand &cmd) override; + void postDirectCommand(const QJsonObject &ob); + + void refreshLocation(const GdbMi &reportedLocation); + void refreshStack(const GdbMi &stack); + void refreshLocals(const GdbMi &vars); + void refreshModules(const GdbMi &modules); + void refreshState(const GdbMi &reportedState); + void refreshSymbols(const GdbMi &symbols); + + QString errorMessage(QProcess::ProcessError error) const; + bool hasCapability(unsigned cap) const override; + + void claimInitialBreakpoints(); + + void handleDabStarted(); + void handleDabLaunch(); + void handleDabConfigurationDone(); + + void handleDapDone(); + void readDapStandardOutput(); + void readDapStandardError(); + void handleOutput(const QJsonDocument &data); + void handleResponse(const QString &ba); + void updateAll() override; + void updateLocals() override; + + QByteArray m_inbuffer; + Utils::Process m_proc; + int m_nextBreakpointId = 1; +}; + +} // Debugger::Internal diff --git a/src/plugins/debugger/debugger.qbs b/src/plugins/debugger/debugger.qbs index 50f1eba24f9..be2adfc04d9 100644 --- a/src/plugins/debugger/debugger.qbs +++ b/src/plugins/debugger/debugger.qbs @@ -122,6 +122,12 @@ Project { files: ["pdbengine.cpp", "pdbengine.h"] } + Group { + name: "dap" + prefix: "dap/" + files: ["dapengine.cpp", "dapengine.h"] + } + Group { name: "uvsc" prefix: "uvsc/" @@ -175,7 +181,7 @@ Project { Group { name: "Images" prefix: "images/" - files: ["*.png", "*.xpm"] + files: ["*.png"] } Group { @@ -239,9 +245,7 @@ Project { ] } - Group { - name: "Unit tests" - condition: qtc.testsEnabled + QtcTestFiles { files: [ "debuggerunittests.qrc", ] diff --git a/src/plugins/debugger/debugger.qrc b/src/plugins/debugger/debugger.qrc index 86a05a73ff1..6fc31bb4fbb 100644 --- a/src/plugins/debugger/debugger.qrc +++ b/src/plugins/debugger/debugger.qrc @@ -42,7 +42,6 @@ images/mode_debug@2x.png images/mode_debug_mask.png images/mode_debug_mask@2x.png - images/pin.xpm images/debugger_restart_small.png images/debugger_restart_small@2x.png images/recordfill.png diff --git a/src/plugins/debugger/debuggeractions.cpp b/src/plugins/debugger/debuggeractions.cpp index a3a6187a417..907e626f4dc 100644 --- a/src/plugins/debugger/debuggeractions.cpp +++ b/src/plugins/debugger/debuggeractions.cpp @@ -242,7 +242,7 @@ DebuggerSettings::DebuggerSettings() adjustBreakpointLocations.setDisplayName(Tr::tr("Adjust Breakpoint Locations")); adjustBreakpointLocations.setToolTip( "

" - + Tr::tr("

Not all source code lines generate " + + Tr::tr("Not all source code lines generate " "executable code. Putting a breakpoint on such a line acts as " "if the breakpoint was set on the next line that generated code. " "Selecting 'Adjust Breakpoint Locations' shifts the red " @@ -364,7 +364,6 @@ DebuggerSettings::DebuggerSettings() + "

"); extraDumperFile.setSettingsKey(debugModeGroup, "ExtraDumperFile"); - extraDumperFile.setDisplayStyle(StringAspect::PathChooserDisplay); extraDumperFile.setDisplayName(Tr::tr("Extra Debugging Helpers")); // Label text is intentional empty in the GUI. extraDumperFile.setToolTip(Tr::tr("Path to a Python file containing additional data dumpers.")); @@ -531,6 +530,15 @@ DebuggerSettings::DebuggerSettings() + Tr::tr("The maximum length for strings in separated windows. " "Longer strings are cut off and displayed with an ellipsis attached.")); + defaultArraySize.setSettingsKey(debugModeGroup, "DefaultArraySize"); + defaultArraySize.setDefaultValue(100); + defaultArraySize.setRange(10, 1000000000); + defaultArraySize.setSingleStep(100); + defaultArraySize.setLabelText(Tr::tr("Default array size:")); + defaultArraySize.setToolTip("

" + + Tr::tr("The number of array elements requested when expanding " + "entries in the Locals and Expressions views.")); + expandStack.setLabelText(Tr::tr("Reload Full Stack")); createFullBacktrace.setLabelText(Tr::tr("Create Full Backtrace")); @@ -610,6 +618,7 @@ DebuggerSettings::DebuggerSettings() page4.registerAspect(&showQObjectNames); page4.registerAspect(&displayStringLimit); page4.registerAspect(&maximalStringLength); + page4.registerAspect(&defaultArraySize); // Page 5 page5.registerAspect(&cdbAdditionalArguments); @@ -650,7 +659,7 @@ DebuggerSettings::DebuggerSettings() aspect->setAutoApply(false); // FIXME: Make the positioning part of the LayoutBuilder later if (auto boolAspect = dynamic_cast(aspect)) - boolAspect->setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel); + boolAspect->setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBox); }); } diff --git a/src/plugins/debugger/debuggeractions.h b/src/plugins/debugger/debuggeractions.h index 69cc76a1f1b..a6fb497815f 100644 --- a/src/plugins/debugger/debuggeractions.h +++ b/src/plugins/debugger/debuggeractions.h @@ -30,7 +30,7 @@ public: void fromMap(const QVariantMap &map) override; void toMap(QVariantMap &map) const override; - void addToLayout(Utils::Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; QVariant volatileValue() const override; void setVolatileValue(const QVariant &val) override; @@ -106,7 +106,7 @@ public: Utils::BoolAspect useDebuggingHelpers; Utils::BoolAspect useCodeModel; Utils::BoolAspect showThreadNames; - Utils::StringAspect extraDumperFile; // For loading a file. Recommended. + Utils::FilePathAspect extraDumperFile; // For loading a file. Recommended. Utils::StringAspect extraDumperCommands; // To modify an existing setup. Utils::BoolAspect showStdNamespace; @@ -146,6 +146,7 @@ public: Utils::BoolAspect autoDerefPointers; Utils::IntegerAspect maximalStringLength; Utils::IntegerAspect displayStringLimit; + Utils::IntegerAspect defaultArraySize; Utils::BoolAspect sortStructMembers; Utils::BoolAspect useToolTipsInLocalsView; diff --git a/src/plugins/debugger/debuggerconstants.h b/src/plugins/debugger/debuggerconstants.h index b071713a844..06ccb94f1b9 100644 --- a/src/plugins/debugger/debuggerconstants.h +++ b/src/plugins/debugger/debuggerconstants.h @@ -99,6 +99,7 @@ enum DebuggerEngineType CdbEngineType = 0x004, PdbEngineType = 0x008, LldbEngineType = 0x100, + DapEngineType = 0x200, UvscEngineType = 0x1000 }; diff --git a/src/plugins/debugger/debuggerdialogs.cpp b/src/plugins/debugger/debuggerdialogs.cpp index ece2c226692..c382e91c175 100644 --- a/src/plugins/debugger/debuggerdialogs.cpp +++ b/src/plugins/debugger/debuggerdialogs.cpp @@ -212,6 +212,7 @@ StartApplicationDialog::StartApplicationDialog(QWidget *parent) d->localExecutablePathChooser->setHistoryCompleter("LocalExecutable"); d->arguments = new FancyLineEdit(this); + d->arguments->setClearButtonEnabled(true); d->arguments->setHistoryCompleter("CommandlineArguments"); d->workingDirectory = new PathChooser(this); diff --git a/src/plugins/debugger/debuggerengine.cpp b/src/plugins/debugger/debuggerengine.cpp index 14cab127508..27c1373c99e 100644 --- a/src/plugins/debugger/debuggerengine.cpp +++ b/src/plugins/debugger/debuggerengine.cpp @@ -13,15 +13,16 @@ #include "debuggertr.h" #include "breakhandler.h" +#include "debuggermainwindow.h" #include "disassembleragent.h" +#include "enginemanager.h" #include "localsandexpressionswindow.h" #include "logwindow.h" -#include "debuggermainwindow.h" -#include "enginemanager.h" #include "memoryagent.h" #include "moduleshandler.h" #include "registerhandler.h" #include "peripheralregisterhandler.h" +#include "shared/peutils.h" #include "sourcefileshandler.h" #include "sourceutils.h" #include "stackhandler.h" @@ -31,7 +32,6 @@ #include "watchhandler.h" #include "watchutils.h" #include "watchwindow.h" -#include "debugger/shared/peutils.h" #include #include @@ -54,10 +54,10 @@ #include #include #include +#include #include #include #include -#include #include #include @@ -471,7 +471,7 @@ public: QString m_qtNamespace; // Safety net to avoid infinite lookups. - QSet m_lookupRequests; // FIXME: Integrate properly. + QHash m_lookupRequests; // FIXME: Integrate properly. QPointer m_alertBox; QPointer m_breakView; @@ -1036,11 +1036,6 @@ void DebuggerEngine::setRunTool(DebuggerRunTool *runTool) { d->m_device = runTool->device(); - IDevice::ConstPtr debuggerDevice = - DeviceManager::deviceForPath(d->m_runParameters.debugger.command.executable()); - if (QTC_GUARD(debuggerDevice)) - d->m_runParameters.dumperPath = debuggerDevice->debugDumperPath(); - d->m_terminalRunner = runTool->terminalRunner(); validateRunParameters(d->m_runParameters); @@ -2083,7 +2078,7 @@ void DebuggerEngine::examineModules() { } -void DebuggerEngine::loadSymbols(const QString &) +void DebuggerEngine::loadSymbols(const FilePath &) { } @@ -2095,11 +2090,11 @@ void DebuggerEngine::loadSymbolsForStack() { } -void DebuggerEngine::requestModuleSymbols(const QString &) +void DebuggerEngine::requestModuleSymbols(const FilePath &) { } -void DebuggerEngine::requestModuleSections(const QString &) +void DebuggerEngine::requestModuleSections(const FilePath &) { } @@ -2360,9 +2355,10 @@ bool DebuggerEngine::canHandleToolTip(const DebuggerToolTipContext &context) con void DebuggerEngine::updateItem(const QString &iname) { - if (d->m_lookupRequests.contains(iname)) { + WatchHandler *handler = watchHandler(); + const int maxArrayCount = handler->maxArrayCount(iname); + if (d->m_lookupRequests.value(iname, -1) == maxArrayCount) { showMessage(QString("IGNORING REPEATED REQUEST TO EXPAND " + iname)); - WatchHandler *handler = watchHandler(); WatchItem *item = handler->findItem(iname); QTC_CHECK(item); WatchModelBase *model = handler->model(); @@ -2382,7 +2378,7 @@ void DebuggerEngine::updateItem(const QString &iname) } // We could legitimately end up here after expanding + closing + re-expaning an item. } - d->m_lookupRequests.insert(iname); + d->m_lookupRequests[iname] = maxArrayCount; UpdateParameters params; params.partialVariable = iname; @@ -2591,6 +2587,7 @@ bool DebuggerRunParameters::isCppDebugging() const return cppEngineType == GdbEngineType || cppEngineType == LldbEngineType || cppEngineType == CdbEngineType + || cppEngineType == DapEngineType || cppEngineType == UvscEngineType; } @@ -2651,7 +2648,7 @@ static void createNewDock(QWidget *widget) dockWidget->show(); } -void DebuggerEngine::showModuleSymbols(const QString &moduleName, const Symbols &symbols) +void DebuggerEngine::showModuleSymbols(const FilePath &moduleName, const Symbols &symbols) { auto w = new QTreeWidget; w->setUniformRowHeights(true); @@ -2659,7 +2656,7 @@ void DebuggerEngine::showModuleSymbols(const QString &moduleName, const Symbols w->setRootIsDecorated(false); w->setAlternatingRowColors(true); w->setSortingEnabled(true); - w->setObjectName("Symbols." + moduleName); + w->setObjectName("Symbols." + moduleName.toFSPathString()); QStringList header; header.append(Tr::tr("Symbol")); header.append(Tr::tr("Address")); @@ -2667,7 +2664,7 @@ void DebuggerEngine::showModuleSymbols(const QString &moduleName, const Symbols header.append(Tr::tr("Section")); header.append(Tr::tr("Name")); w->setHeaderLabels(header); - w->setWindowTitle(Tr::tr("Symbols in \"%1\"").arg(moduleName)); + w->setWindowTitle(Tr::tr("Symbols in \"%1\"").arg(moduleName.toUserOutput())); for (const Symbol &s : symbols) { auto it = new QTreeWidgetItem; it->setData(0, Qt::DisplayRole, s.name); @@ -2680,7 +2677,7 @@ void DebuggerEngine::showModuleSymbols(const QString &moduleName, const Symbols createNewDock(w); } -void DebuggerEngine::showModuleSections(const QString &moduleName, const Sections §ions) +void DebuggerEngine::showModuleSections(const FilePath &moduleName, const Sections §ions) { auto w = new QTreeWidget; w->setUniformRowHeights(true); @@ -2688,7 +2685,7 @@ void DebuggerEngine::showModuleSections(const QString &moduleName, const Section w->setRootIsDecorated(false); w->setAlternatingRowColors(true); w->setSortingEnabled(true); - w->setObjectName("Sections." + moduleName); + w->setObjectName("Sections." + moduleName.toFSPathString()); QStringList header; header.append(Tr::tr("Name")); header.append(Tr::tr("From")); @@ -2696,7 +2693,7 @@ void DebuggerEngine::showModuleSections(const QString &moduleName, const Section header.append(Tr::tr("Address")); header.append(Tr::tr("Flags")); w->setHeaderLabels(header); - w->setWindowTitle(Tr::tr("Sections in \"%1\"").arg(moduleName)); + w->setWindowTitle(Tr::tr("Sections in \"%1\"").arg(moduleName.toUserOutput())); for (const Section &s : sections) { auto it = new QTreeWidgetItem; it->setData(0, Qt::DisplayRole, s.name); @@ -2719,7 +2716,6 @@ Context CppDebuggerEngine::languageContext() const void CppDebuggerEngine::validateRunParameters(DebuggerRunParameters &rp) { static const QString warnOnInappropriateDebuggerKey = "DebuggerWarnOnInappropriateDebugger"; - QtcSettings *coreSettings = Core::ICore::settings(); const bool warnOnRelease = debuggerSettings()->warnOnReleaseBuilds.value() && rp.toolChainAbi.osFlavor() != Abi::AndroidLinuxFlavor; @@ -2727,7 +2723,7 @@ void CppDebuggerEngine::validateRunParameters(DebuggerRunParameters &rp) QString detailedWarning; switch (rp.toolChainAbi.binaryFormat()) { case Abi::PEFormat: { - if (CheckableMessageBox::shouldAskAgain(coreSettings, warnOnInappropriateDebuggerKey)) { + if (CheckableDecider(warnOnInappropriateDebuggerKey).shouldAskAgain()) { QString preferredDebugger; if (rp.toolChainAbi.osFlavor() == Abi::WindowsMSysFlavor) { if (rp.cppEngineType == CdbEngineType) @@ -2767,7 +2763,7 @@ void CppDebuggerEngine::validateRunParameters(DebuggerRunParameters &rp) break; } case Abi::ElfFormat: { - if (CheckableMessageBox::shouldAskAgain(coreSettings, warnOnInappropriateDebuggerKey)) { + if (CheckableDecider(warnOnInappropriateDebuggerKey).shouldAskAgain()) { if (rp.cppEngineType == CdbEngineType) { warnOnInappropriateDebugger = true; detailedWarning = Tr::tr( @@ -2873,15 +2869,13 @@ void CppDebuggerEngine::validateRunParameters(DebuggerRunParameters &rp) return; } if (warnOnInappropriateDebugger) { - CheckableMessageBox::doNotShowAgainInformation( + CheckableMessageBox::information( Core::ICore::dialogParent(), Tr::tr("Warning"), - Tr::tr( - "The selected debugger may be inappropriate for the inferior.\n" - "Examining symbols and setting breakpoints by file name and line number " - "may fail.\n") + Tr::tr("The selected debugger may be inappropriate for the inferior.\n" + "Examining symbols and setting breakpoints by file name and line number " + "may fail.\n") + '\n' + detailedWarning, - Core::ICore::settings(), warnOnInappropriateDebuggerKey); } else if (warnOnRelease) { AsynchronousMessageBox::information(Tr::tr("Warning"), diff --git a/src/plugins/debugger/debuggerengine.h b/src/plugins/debugger/debuggerengine.h index 422d4eb788c..ac91ecb6440 100644 --- a/src/plugins/debugger/debuggerengine.h +++ b/src/plugins/debugger/debuggerengine.h @@ -7,12 +7,14 @@ #include "debuggerconstants.h" #include "debuggerprotocol.h" #include "breakhandler.h" -#include "projectexplorer/abi.h" #include "threadshandler.h" #include + +#include #include #include + #include #include @@ -182,7 +184,6 @@ public: QStringList validationErrors; - Utils::FilePath dumperPath; int fallbackQtVersion = 0x50200; // Common debugger constants. @@ -303,11 +304,11 @@ public: virtual void reloadModules(); virtual void examineModules(); - virtual void loadSymbols(const QString &moduleName); + virtual void loadSymbols(const Utils::FilePath &moduleName); virtual void loadSymbolsForStack(); virtual void loadAllSymbols(); - virtual void requestModuleSymbols(const QString &moduleName); - virtual void requestModuleSections(const QString &moduleName); + virtual void requestModuleSymbols(const Utils::FilePath &moduleName); + virtual void requestModuleSections(const Utils::FilePath &moduleName); virtual void reloadRegisters(); virtual void reloadPeripheralRegisters(); @@ -452,8 +453,8 @@ public: void openMemoryEditor(); - static void showModuleSymbols(const QString &moduleName, const QVector &symbols); - static void showModuleSections(const QString &moduleName, const QVector

§ions); + static void showModuleSymbols(const Utils::FilePath &moduleName, const QVector &symbols); + static void showModuleSections(const Utils::FilePath &moduleName, const QVector
§ions); void handleExecDetach(); void handleExecContinue(); diff --git a/src/plugins/debugger/debuggeritem.cpp b/src/plugins/debugger/debuggeritem.cpp index a7205d34a1c..241ac32ecdd 100644 --- a/src/plugins/debugger/debuggeritem.cpp +++ b/src/plugins/debugger/debuggeritem.cpp @@ -12,8 +12,8 @@ #include #include #include +#include #include -#include #include #include #include @@ -43,7 +43,7 @@ const char DEBUGGER_INFORMATION_WORKINGDIRECTORY[] = "WorkingDirectory"; static QString getGdbConfiguration(const FilePath &command, const Environment &sysEnv) { // run gdb with the --configuration opion - QtcProcess proc; + Process proc; proc.setEnvironment(sysEnv); proc.setCommand({command, {"--configuration"}}); proc.runBlocking(); @@ -116,6 +116,9 @@ void DebuggerItem::createId() void DebuggerItem::reinitializeFromFile(QString *error, Utils::Environment *customEnv) { + if (isGeneric()) + return; + // CDB only understands the single-dash -version, whereas GDB and LLDB are // happy with both -version and --version. So use the "working" -version // except for the experimental LLDB-MI which insists on --version. @@ -159,7 +162,7 @@ void DebuggerItem::reinitializeFromFile(QString *error, Utils::Environment *cust // hack below tricks it into giving us the information we want. env.set("QNX_TARGET", QString()); - QtcProcess proc; + Process proc; proc.setEnvironment(env); proc.setCommand({m_command, {version}}); proc.runBlocking(); @@ -171,8 +174,12 @@ void DebuggerItem::reinitializeFromFile(QString *error, Utils::Environment *cust return; } m_abis.clear(); + if (output.contains("gdb")) { m_engineType = GdbEngineType; + // FIXME: HACK while introducing DAP support + if (m_command.fileName().endsWith("-dap")) + m_engineType = DapEngineType; // Version bool isMacGdb, isQnxGdb; @@ -208,6 +215,7 @@ void DebuggerItem::reinitializeFromFile(QString *error, Utils::Environment *cust //! \note If unable to determine the GDB ABI, no ABI is appended to m_abis here. return; } + if (output.contains("lldb") || output.startsWith("LLDB")) { m_engineType = LldbEngineType; m_abis = Abi::abisOfBinary(m_command); @@ -275,6 +283,8 @@ QString DebuggerItem::engineTypeName() const return QLatin1String("CDB"); case LldbEngineType: return QLatin1String("LLDB"); + case DapEngineType: + return QLatin1String("DAP"); case UvscEngineType: return QLatin1String("UVSC"); default: @@ -282,6 +292,16 @@ QString DebuggerItem::engineTypeName() const } } +void DebuggerItem::setGeneric(bool on) +{ + m_detectionSource = on ? QLatin1String("Generic") : QLatin1String(); +} + +bool DebuggerItem::isGeneric() const +{ + return m_detectionSource == "Generic"; +} + QStringList DebuggerItem::abiNames() const { QStringList list; @@ -297,13 +317,15 @@ QDateTime DebuggerItem::lastModified() const QIcon DebuggerItem::decoration() const { + if (isGeneric()) + return {}; if (m_engineType == NoEngineType) return Icons::CRITICAL.icon(); if (!m_command.isExecutableFile()) return Icons::WARNING.icon(); if (!m_workingDirectory.isEmpty() && !m_workingDirectory.isDir()) return Icons::WARNING.icon(); - return QIcon(); + return {}; } QString DebuggerItem::validityMessage() const diff --git a/src/plugins/debugger/debuggeritem.h b/src/plugins/debugger/debuggeritem.h index 1edaf283ca4..95993665b67 100644 --- a/src/plugins/debugger/debuggeritem.h +++ b/src/plugins/debugger/debuggeritem.h @@ -8,7 +8,7 @@ #include -#include +#include #include #include @@ -84,6 +84,9 @@ public: QString detectionSource() const { return m_detectionSource; } void setDetectionSource(const QString &source) { m_detectionSource = source; } + bool isGeneric() const; + void setGeneric(bool on); + static bool addAndroidLldbPythonEnv(const Utils::FilePath &lldbCmd, Utils::Environment &env); private: diff --git a/src/plugins/debugger/debuggeritemmanager.cpp b/src/plugins/debugger/debuggeritemmanager.cpp index b3792554168..aca5a976359 100644 --- a/src/plugins/debugger/debuggeritemmanager.cpp +++ b/src/plugins/debugger/debuggeritemmanager.cpp @@ -9,8 +9,6 @@ #include #include -#include - #include #include #include @@ -22,8 +20,8 @@ #include #include #include +#include #include -#include #include #include @@ -92,7 +90,8 @@ static DebuggerItemManagerPrivate *d = nullptr; class DebuggerItemConfigWidget : public QWidget { public: - explicit DebuggerItemConfigWidget(); + DebuggerItemConfigWidget(); + void load(const DebuggerItem *item); void store() const; @@ -104,13 +103,18 @@ private: QLineEdit *m_displayNameLineEdit; QLineEdit *m_typeLineEdit; QLabel *m_cdbLabel; - QLineEdit *m_versionLabel; PathChooser *m_binaryChooser; - PathChooser *m_workingDirectoryChooser; - QLineEdit *m_abis; bool m_autodetected = false; + bool m_generic = false; DebuggerEngineType m_engineType = NoEngineType; QVariant m_id; + + QLabel *m_abisLabel; + QLineEdit *m_abis; + QLabel *m_versionLabel; + QLineEdit *m_version; + QLabel *m_workingDirectoryLabel; + PathChooser *m_workingDirectoryChooser; }; // -------------------------------------------------------------------------- @@ -170,10 +174,11 @@ class DebuggerItemModel : public TreeModelappendChild( - new StaticTreeItem({ProjectExplorer::Constants::msgAutoDetected()}, - {ProjectExplorer::Constants::msgAutoDetectedToolTip()})); + + auto generic = new StaticTreeItem(Tr::tr("Generic")); + auto autoDetected = new StaticTreeItem({ProjectExplorer::Constants::msgAutoDetected()}, + {ProjectExplorer::Constants::msgAutoDetectedToolTip()}); + rootItem()->appendChild(generic); + rootItem()->appendChild(autoDetected); rootItem()->appendChild(new StaticTreeItem(ProjectExplorer::Constants::msgManual())); + + DebuggerItem genericGdb(QVariant("gdb")); + genericGdb.setAutoDetected(true); + genericGdb.setGeneric(true); + genericGdb.setEngineType(GdbEngineType); + genericGdb.setAbi(Abi()); + genericGdb.setCommand("gdb"); + genericGdb.setUnexpandedDisplayName(Tr::tr("%1 from PATH on Build Device").arg("GDB")); + generic->appendChild(new DebuggerTreeItem(genericGdb, false)); + + DebuggerItem genericLldb(QVariant("lldb")); + genericLldb.setAutoDetected(true); + genericLldb.setEngineType(LldbEngineType); + genericLldb.setGeneric(true); + genericLldb.setAbi(Abi()); + genericLldb.setCommand("lldb"); + genericLldb.setUnexpandedDisplayName(Tr::tr("%1 from PATH on Build Device").arg("LLDB")); + generic->appendChild(new DebuggerTreeItem(genericLldb, false)); } -void DebuggerItemModel::addDebugger(const DebuggerItem &item, bool changed) +DebuggerTreeItem *DebuggerItemModel::addDebugger(const DebuggerItem &item, bool changed) { - QTC_ASSERT(item.id().isValid(), return); - int group = item.isAutoDetected() ? 0 : 1; - rootItem()->childAt(group)->appendChild(new DebuggerTreeItem(item, changed)); + QTC_ASSERT(item.id().isValid(), return {}); + int group = item.isGeneric() ? Generic : (item.isAutoDetected() ? AutoDetected : Manual); + auto treeItem = new DebuggerTreeItem(item, changed); + rootItem()->childAt(group)->appendChild(treeItem); + return treeItem; } void DebuggerItemModel::updateDebugger(const DebuggerItem &item) @@ -301,6 +329,7 @@ DebuggerItemConfigWidget::DebuggerItemConfigWidget() }); m_binaryChooser->setAllowPathFromDevice(true); + m_workingDirectoryLabel = new QLabel(Tr::tr("ABIs:")); m_workingDirectoryChooser = new PathChooser(this); m_workingDirectoryChooser->setExpectedKind(PathChooser::Directory); m_workingDirectoryChooser->setMinimumWidth(400); @@ -310,10 +339,12 @@ DebuggerItemConfigWidget::DebuggerItemConfigWidget() m_cdbLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); m_cdbLabel->setOpenExternalLinks(true); - m_versionLabel = new QLineEdit(this); - m_versionLabel->setPlaceholderText(Tr::tr("Unknown")); - m_versionLabel->setEnabled(false); + m_versionLabel = new QLabel(Tr::tr("Version:")); + m_version = new QLineEdit(this); + m_version->setPlaceholderText(Tr::tr("Unknown")); + m_version->setEnabled(false); + m_abisLabel = new QLabel(Tr::tr("Working directory:")); m_abis = new QLineEdit(this); m_abis->setEnabled(false); @@ -323,9 +354,9 @@ DebuggerItemConfigWidget::DebuggerItemConfigWidget() formLayout->addRow(m_cdbLabel); formLayout->addRow(new QLabel(Tr::tr("Path:")), m_binaryChooser); formLayout->addRow(new QLabel(Tr::tr("Type:")), m_typeLineEdit); - formLayout->addRow(new QLabel(Tr::tr("ABIs:")), m_abis); - formLayout->addRow(new QLabel(Tr::tr("Version:")), m_versionLabel); - formLayout->addRow(new QLabel(Tr::tr("Working directory:")), m_workingDirectoryChooser); + formLayout->addRow(m_abisLabel, m_abis); + formLayout->addRow(m_versionLabel, m_version); + formLayout->addRow(m_workingDirectoryLabel, m_workingDirectoryChooser); connect(m_binaryChooser, &PathChooser::textChanged, this, &DebuggerItemConfigWidget::binaryPathHasChanged); @@ -337,21 +368,24 @@ DebuggerItemConfigWidget::DebuggerItemConfigWidget() DebuggerItem DebuggerItemConfigWidget::item() const { + static const QRegularExpression noAbi("[^A-Za-z0-9-_]+"); + DebuggerItem item(m_id); item.setUnexpandedDisplayName(m_displayNameLineEdit->text()); item.setCommand(m_binaryChooser->filePath()); item.setWorkingDirectory(m_workingDirectoryChooser->filePath()); item.setAutoDetected(m_autodetected); Abis abiList; - const QStringList abis = m_abis->text().split(QRegularExpression("[^A-Za-z0-9-_]+")); + const QStringList abis = m_abis->text().split(noAbi); for (const QString &a : abis) { if (a.isNull()) continue; abiList << Abi::fromString(a); } item.setAbis(abiList); - item.setVersion(m_versionLabel->text()); + item.setVersion(m_version->text()); item.setEngineType(m_engineType); + item.setGeneric(m_generic); return item; } @@ -373,6 +407,7 @@ void DebuggerItemConfigWidget::load(const DebuggerItem *item) return; // Set values: + m_generic = item->isGeneric(); m_autodetected = item->isAutoDetected(); m_displayNameLineEdit->setEnabled(!item->isAutoDetected()); @@ -382,6 +417,15 @@ void DebuggerItemConfigWidget::load(const DebuggerItem *item) m_binaryChooser->setReadOnly(item->isAutoDetected()); m_binaryChooser->setFilePath(item->command()); + m_binaryChooser->setExpectedKind(m_generic ? PathChooser::Any : PathChooser::ExistingCommand); + + m_abisLabel->setVisible(!m_generic); + m_abis->setVisible(!m_generic); + m_versionLabel->setVisible(!m_generic); + m_version->setVisible(!m_generic); + m_workingDirectoryLabel->setVisible(!m_generic); + m_workingDirectoryChooser->setVisible(!m_generic); + m_workingDirectoryChooser->setReadOnly(item->isAutoDetected()); m_workingDirectoryChooser->setFilePath(item->workingDirectory()); @@ -405,7 +449,7 @@ void DebuggerItemConfigWidget::load(const DebuggerItem *item) m_cdbLabel->setText(text); m_cdbLabel->setVisible(!text.isEmpty()); m_binaryChooser->setCommandVersionArguments(QStringList(versionCommand)); - m_versionLabel->setText(item->version()); + m_version->setText(item->version()); setAbis(item->abiNames()); m_engineType = item->engineType(); m_id = item->id(); @@ -417,16 +461,18 @@ void DebuggerItemConfigWidget::binaryPathHasChanged() if (!m_id.isValid()) return; - DebuggerItem tmp; - if (m_binaryChooser->filePath().isExecutableFile()) { - tmp = item(); - tmp.reinitializeFromFile(); - } + if (!m_generic) { + DebuggerItem tmp; + if (m_binaryChooser->filePath().isExecutableFile()) { + tmp = item(); + tmp.reinitializeFromFile(); + } - setAbis(tmp.abiNames()); - m_versionLabel->setText(tmp.version()); - m_engineType = tmp.engineType(); - m_typeLineEdit->setText(tmp.engineTypeName()); + setAbis(tmp.abiNames()); + m_version->setText(tmp.version()); + m_engineType = tmp.engineType(); + m_typeLineEdit->setText(tmp.engineTypeName()); + } store(); } @@ -534,8 +580,10 @@ void DebuggerConfigWidget::cloneDebugger() newItem.setUnexpandedDisplayName(d->uniqueDisplayName(Tr::tr("Clone of %1").arg(item->displayName()))); newItem.reinitializeFromFile(); newItem.setAutoDetected(false); - d->m_model->addDebugger(newItem, true); - m_debuggerView->setCurrentIndex(d->m_model->lastIndex()); + newItem.setGeneric(item->isGeneric()); + newItem.setEngineType(item->engineType()); + auto addedItem = d->m_model->addDebugger(newItem, true); + m_debuggerView->setCurrentIndex(d->m_model->indexForItem(addedItem)); } void DebuggerConfigWidget::addDebugger() @@ -545,8 +593,8 @@ void DebuggerConfigWidget::addDebugger() item.setEngineType(NoEngineType); item.setUnexpandedDisplayName(d->uniqueDisplayName(Tr::tr("New Debugger"))); item.setAutoDetected(false); - d->m_model->addDebugger(item, true); - m_debuggerView->setCurrentIndex(d->m_model->lastIndex()); + auto addedItem = d->m_model->addDebugger(item, true); + m_debuggerView->setCurrentIndex(d->m_model->indexForItem(addedItem)); } void DebuggerConfigWidget::removeDebugger() @@ -712,7 +760,7 @@ void DebuggerItemManagerPrivate::autoDetectGdbOrLldbDebuggers(const FilePaths &s FilePaths suspects; if (searchPaths.front().osType() == OsTypeMac) { - QtcProcess proc; + Process proc; proc.setTimeoutS(2); proc.setCommand({"xcrun", {"--find", "lldb"}}); proc.runBlocking(); @@ -763,6 +811,14 @@ void DebuggerItemManagerPrivate::autoDetectGdbOrLldbDebuggers(const FilePaths &s item.setUnexpandedDisplayName(name.arg(item.engineTypeName()).arg(command.toUserOutput())); m_model->addDebugger(item); logMessages.append(Tr::tr("Found: \"%1\"").arg(command.toUserOutput())); + + if (item.engineType() == GdbEngineType) { + if (item.version().startsWith("GNU gdb (GDB) 14.0.50.2023")) { + // FIXME: Use something more robust + item.setEngineType(DapEngineType); + m_model->addDebugger(item); + } + } } if (logMessage) *logMessage = logMessages.join('\n'); @@ -820,7 +876,6 @@ DebuggerItemManagerPrivate::DebuggerItemManagerPrivate() d = this; m_model = new DebuggerItemModel; m_optionsPage = new DebuggerOptionsPage; - ExtensionSystem::PluginManager::addObject(m_optionsPage); } void DebuggerItemManagerPrivate::extensionsInitialized() @@ -830,7 +885,6 @@ void DebuggerItemManagerPrivate::extensionsInitialized() DebuggerItemManagerPrivate::~DebuggerItemManagerPrivate() { - ExtensionSystem::PluginManager::removeObject(m_optionsPage); delete m_optionsPage; delete m_model; } @@ -932,6 +986,8 @@ void DebuggerItemManagerPrivate::saveDebuggers() int count = 0; forAllDebuggers([&count, &data](DebuggerItem &item) { + if (item.isGeneric()) // do not store generic debuggers, these get added automatically + return; if (item.isValid() && item.engineType() != NoEngineType) { QVariantMap tmp = item.toMap(); if (!tmp.isEmpty()) { diff --git a/src/plugins/debugger/debuggerkitinformation.cpp b/src/plugins/debugger/debuggerkitinformation.cpp index cac42d5a425..b1ba6bf1d59 100644 --- a/src/plugins/debugger/debuggerkitinformation.cpp +++ b/src/plugins/debugger/debuggerkitinformation.cpp @@ -60,11 +60,11 @@ public: } private: - void addToLayout(Utils::Layouting::LayoutBuilder &builder) override + void addToLayout(Layouting::LayoutItem &parent) override { addMutableAction(m_comboBox); - builder.addItem(m_comboBox); - builder.addItem(m_manageButton); + parent.addItem(m_comboBox); + parent.addItem(m_manageButton); } void makeReadOnly() override @@ -228,66 +228,6 @@ void DebuggerKitAspect::setup(Kit *k) k->setValue(DebuggerKitAspect::id(), bestLevel != DebuggerItem::DoesNotMatch ? bestItem.id() : QVariant()); } - -// This handles the upgrade path from 2.8 to 3.0 -void DebuggerKitAspect::fix(Kit *k) -{ - QTC_ASSERT(k, return); - - // This can be Id, binary path, but not "auto" anymore. - const QVariant rawId = k->value(DebuggerKitAspect::id()); - - if (rawId.toString().isEmpty()) // No debugger set, that is fine. - return; - - if (rawId.type() == QVariant::String) { - const DebuggerItem * const item = DebuggerItemManager::findById(rawId); - if (!item) { - qWarning("Unknown debugger id %s in kit %s", - qPrintable(rawId.toString()), qPrintable(k->displayName())); - k->setValue(DebuggerKitAspect::id(), QVariant()); - setup(k); - return; - } - - Abi kitAbi; - if (ToolChainKitAspect::toolChains(k).isEmpty()) { - if (DeviceTypeKitAspect::deviceTypeId(k) - != ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE) { - return; - } - kitAbi = Abi(Abi::UnknownArchitecture, Abi::hostAbi().os()); - } else { - kitAbi = ToolChainKitAspect::targetAbi(k); - } - if (item->matchTarget(kitAbi) != DebuggerItem::DoesNotMatch) - return; - k->setValue(DebuggerKitAspect::id(), QVariant()); - setup(k); - return; // All fine (now). - } - - QMap map = rawId.toMap(); - QString binary = map.value("Binary").toString(); - if (binary == "auto") { - // This should not happen as "auto" is handled by setup() already. - QTC_CHECK(false); - k->setValue(DebuggerKitAspect::id(), QVariant()); - return; - } - - FilePath fileName = FilePath::fromUserInput(binary); - const DebuggerItem *item = DebuggerItemManager::findByCommand(fileName); - if (!item) { - qWarning("Debugger command %s invalid in kit %s", - qPrintable(binary), qPrintable(k->displayName())); - k->setValue(DebuggerKitAspect::id(), QVariant()); - return; - } - - k->setValue(DebuggerKitAspect::id(), item->id()); -} - // Check the configuration errors and return a flag mask. Provide a quick check and // a verbose one with a list of errors. @@ -299,15 +239,15 @@ DebuggerKitAspect::ConfigurationErrors DebuggerKitAspect::configurationErrors(co if (!item) return NoDebugger; - if (item->command().isEmpty()) + const FilePath debugger = item->command(); + if (debugger.isEmpty()) return NoDebugger; + if (debugger.isRelativePath()) + return NoConfigurationError; + ConfigurationErrors result = NoConfigurationError; - const FilePath debugger = item->command(); - const bool found = debugger.exists() && !debugger.isDir(); - if (!found) - result |= DebuggerNotFound; - else if (!debugger.isExecutableFile()) + if (!debugger.isExecutableFile()) result |= DebuggerNotExecutable; const Abi tcAbi = ToolChainKitAspect::targetAbi(k); @@ -318,16 +258,15 @@ DebuggerKitAspect::ConfigurationErrors DebuggerKitAspect::configurationErrors(co result |= DebuggerDoesNotMatch; } - if (!found) { - if (item->engineType() == NoEngineType) - return NoDebugger; + if (item->engineType() == NoEngineType) + return NoDebugger; - // We need an absolute path to be able to locate Python on Windows. - if (item->engineType() == GdbEngineType) { - if (tcAbi.os() == Abi::WindowsOS && !debugger.isAbsolutePath()) - result |= DebuggerNeedsAbsolutePath; - } + // We need an absolute path to be able to locate Python on Windows. + if (item->engineType() == GdbEngineType) { + if (tcAbi.os() == Abi::WindowsOS && !debugger.isAbsolutePath()) + result |= DebuggerNeedsAbsolutePath; } + return result; } @@ -342,7 +281,12 @@ Runnable DebuggerKitAspect::runnable(const Kit *kit) { Runnable runnable; if (const DebuggerItem *item = debugger(kit)) { - runnable.command = CommandLine{item->command()}; + FilePath cmd = item->command(); + if (cmd.isRelativePath()) { + if (const IDeviceConstPtr buildDevice = BuildDeviceKitAspect::device(kit)) + cmd = buildDevice->searchExecutableInPath(cmd.path()); + } + runnable.command.setExecutable(cmd); runnable.workingDirectory = item->workingDirectory(); runnable.environment = kit->runEnvironment(); runnable.environment.set("LC_NUMERIC", "C"); diff --git a/src/plugins/debugger/debuggerkitinformation.h b/src/plugins/debugger/debuggerkitinformation.h index eb6ad1d58aa..548f76793bb 100644 --- a/src/plugins/debugger/debuggerkitinformation.h +++ b/src/plugins/debugger/debuggerkitinformation.h @@ -21,7 +21,6 @@ public: { return DebuggerKitAspect::validateDebugger(k); } void setup(ProjectExplorer::Kit *k) override; - void fix(ProjectExplorer::Kit *k) override; static const DebuggerItem *debugger(const ProjectExplorer::Kit *kit); static ProjectExplorer::Runnable runnable(const ProjectExplorer::Kit *kit); diff --git a/src/plugins/debugger/debuggermainwindow.cpp b/src/plugins/debugger/debuggermainwindow.cpp index 047fc3ec89b..2a5ec866edb 100644 --- a/src/plugins/debugger/debuggermainwindow.cpp +++ b/src/plugins/debugger/debuggermainwindow.cpp @@ -162,13 +162,13 @@ DebuggerMainWindowPrivate::DebuggerMainWindowPrivate(DebuggerMainWindow *parent) { m_centralWidgetStack = new QStackedWidget; m_statusLabel = new Utils::StatusLabel; - m_statusLabel->setProperty("panelwidget", true); + StyleHelper::setPanelWidget(m_statusLabel); m_statusLabel->setIndent(2 * QFontMetrics(q->font()).horizontalAdvance(QChar('x'))); m_editorPlaceHolder = new EditorManagerPlaceHolder; m_perspectiveChooser = new QComboBox; m_perspectiveChooser->setObjectName("PerspectiveChooser"); - m_perspectiveChooser->setProperty("panelwidget", true); + StyleHelper::setPanelWidget(m_perspectiveChooser); m_perspectiveChooser->setSizeAdjustPolicy(QComboBox::AdjustToContents); connect(m_perspectiveChooser, &QComboBox::activated, this, [this](int item) { Perspective *perspective = Perspective::findPerspective(m_perspectiveChooser->itemData(item).toString()); @@ -201,7 +201,7 @@ DebuggerMainWindowPrivate::DebuggerMainWindowPrivate(DebuggerMainWindow *parent) closeButton->setToolTip(Tr::tr("Leave Debug Mode")); auto toolbar = new Utils::StyledBar; - toolbar->setProperty("topBorder", true); + toolbar->setProperty(StyleHelper::C_TOP_BORDER, true); // "Engine switcher" style comboboxes auto subPerspectiveSwitcher = new QWidget; @@ -233,8 +233,8 @@ DebuggerMainWindowPrivate::DebuggerMainWindowPrivate(DebuggerMainWindow *parent) scrolledToolbar->setFrameStyle(QFrame::NoFrame); scrolledToolbar->setWidgetResizable(true); scrolledToolbar->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scrolledToolbar->setFixedHeight(toolbar->height()); scrolledToolbar->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + StyleHelper::setPanelWidgetSingleRow(scrolledToolbar); auto dock = new QDockWidget(Tr::tr("Toolbar"), q); dock->setObjectName("Toolbar"); @@ -507,7 +507,7 @@ QWidget *DebuggerMainWindow::centralWidgetStack() void DebuggerMainWindow::addSubPerspectiveSwitcher(QWidget *widget) { widget->setVisible(false); - widget->setProperty("panelwidget", true); + StyleHelper::setPanelWidget(widget); d->m_subPerspectiveSwitcherLayout->addWidget(widget); } @@ -810,7 +810,7 @@ QToolButton *PerspectivePrivate::setupToolButton(QAction *action) { QTC_ASSERT(action, return nullptr); auto toolButton = new QToolButton(m_innerToolBar); - toolButton->setProperty("panelwidget", true); + StyleHelper::setPanelWidget(toolButton); toolButton->setDefaultAction(action); toolButton->setToolTip(action->toolTip()); m_innerToolBarLayout->addWidget(toolButton); @@ -833,7 +833,7 @@ void Perspective::addToolBarWidget(QWidget *widget) { QTC_ASSERT(widget, return); // QStyle::polish is called before it is added to the toolbar, explicitly make it a panel widget - widget->setProperty("panelwidget", true); + StyleHelper::setPanelWidget(widget); widget->setParent(d->m_innerToolBar); d->m_innerToolBarLayout->addWidget(widget); } diff --git a/src/plugins/debugger/debuggerplugin.cpp b/src/plugins/debugger/debuggerplugin.cpp index 8ab67c0bc54..7ccc808b08f 100644 --- a/src/plugins/debugger/debuggerplugin.cpp +++ b/src/plugins/debugger/debuggerplugin.cpp @@ -67,9 +67,10 @@ #include #include #include +#include #include #include -#include +#include #include #include #include @@ -685,7 +686,6 @@ public: EngineManager m_engineManager; QTimer m_shutdownTimer; - bool m_shuttingDown = false; Console m_console; // ensure Debugger Console is created before settings are taken into account DebuggerSettings m_debuggerSettings; @@ -1187,7 +1187,7 @@ DebuggerPluginPrivate::DebuggerPluginPrivate(const QStringList &arguments) setInitialState(); - connect(SessionManager::instance(), &SessionManager::startupProjectChanged, + connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, this, &DebuggerPluginPrivate::onStartupProjectChanged); connect(EngineManager::instance(), &EngineManager::engineStateChanged, this, &DebuggerPluginPrivate::updatePresetState); @@ -1391,11 +1391,11 @@ static QVariant configValue(const QString &name) void DebuggerPluginPrivate::updatePresetState() { - if (m_shuttingDown) + if (PluginManager::isShuttingDown()) return; - Project *startupProject = SessionManager::startupProject(); - RunConfiguration *startupRunConfig = SessionManager::startupRunConfiguration(); + Project *startupProject = ProjectManager::startupProject(); + RunConfiguration *startupRunConfig = ProjectManager::startupRunConfiguration(); DebuggerEngine *currentEngine = EngineManager::currentEngine(); QString whyNot; @@ -1995,9 +1995,7 @@ void DebuggerPluginPrivate::dumpLog() void DebuggerPluginPrivate::aboutToShutdown() { - m_shuttingDown = true; - - disconnect(SessionManager::instance(), &SessionManager::startupProjectChanged, this, nullptr); + disconnect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, this, nullptr); m_shutdownTimer.setInterval(0); m_shutdownTimer.setSingleShot(true); @@ -2080,7 +2078,7 @@ QWidget *addSearch(BaseTreeView *treeView) void openTextEditor(const QString &titlePattern0, const QString &contents) { - if (dd->m_shuttingDown) + if (PluginManager::isShuttingDown()) return; QString titlePattern = titlePattern0; IEditor *editor = EditorManager::openEditorWithContents( @@ -2165,7 +2163,7 @@ static bool buildTypeAccepted(QFlags toolMode, BuildConfiguration::Bui static BuildConfiguration::BuildType startupBuildType() { BuildConfiguration::BuildType buildType = BuildConfiguration::Unknown; - if (RunConfiguration *runConfig = SessionManager::startupRunConfiguration()) { + if (RunConfiguration *runConfig = ProjectManager::startupRunConfiguration()) { if (const BuildConfiguration *buildConfig = runConfig->target()->activeBuildConfiguration()) buildType = buildConfig->buildType(); } @@ -2239,10 +2237,12 @@ bool wantRunTool(ToolMode toolMode, const QString &toolName) "or otherwise insufficient output.

" "Do you want to continue and run the tool in %2 mode?

") .arg(toolName).arg(currentMode).arg(toolModeString); - if (Utils::CheckableMessageBox::doNotAskAgainQuestion(ICore::dialogParent(), - title, message, ICore::settings(), "AnalyzerCorrectModeWarning") - != QDialogButtonBox::Yes) - return false; + if (Utils::CheckableMessageBox::question(ICore::dialogParent(), + title, + message, + QString("AnalyzerCorrectModeWarning")) + != QMessageBox::Yes) + return false; } return true; @@ -2335,12 +2335,12 @@ void DebuggerUnitTests::testStateMachine() QEventLoop loop; connect(BuildManager::instance(), &BuildManager::buildQueueFinished, &loop, &QEventLoop::quit); - BuildManager::buildProjectWithDependencies(SessionManager::startupProject()); + BuildManager::buildProjectWithDependencies(ProjectManager::startupProject()); loop.exec(); ExecuteOnDestruction guard([] { EditorManager::closeAllEditors(false); }); - RunConfiguration *rc = SessionManager::startupRunConfiguration(); + RunConfiguration *rc = ProjectManager::startupRunConfiguration(); QVERIFY(rc); auto runControl = new RunControl(ProjectExplorer::Constants::DEBUG_RUN_MODE); diff --git a/src/plugins/debugger/debuggerrunconfigurationaspect.cpp b/src/plugins/debugger/debuggerrunconfigurationaspect.cpp index ef80d58a334..8c118f553b6 100644 --- a/src/plugins/debugger/debuggerrunconfigurationaspect.cpp +++ b/src/plugins/debugger/debuggerrunconfigurationaspect.cpp @@ -5,6 +5,8 @@ #include "debuggertr.h" +#include + #include #include #include @@ -20,6 +22,7 @@ #include +#include #include #include @@ -28,117 +31,10 @@ #include #include -using namespace Debugger::Internal; using namespace ProjectExplorer; using namespace Utils; namespace Debugger { -namespace Internal { - -enum DebuggerLanguageStatus { - DisabledLanguage = 0, - EnabledLanguage, - AutoEnabledLanguage -}; - -class DebuggerLanguageAspect : public BaseAspect -{ -public: - DebuggerLanguageAspect() = default; - - void addToLayout(Layouting::LayoutBuilder &builder) override; - - bool value() const; - void setValue(bool val); - - void setAutoSettingsKey(const QString &settingsKey); - void setLabel(const QString &label); - void setInfoLabelText(const QString &text) { m_infoLabelText = text; } - - void setClickCallBack(const std::function &clickCallBack) - { - m_clickCallBack = clickCallBack; - } - - void fromMap(const QVariantMap &map) override; - void toMap(QVariantMap &map) const override; - -public: - DebuggerLanguageStatus m_value = AutoEnabledLanguage; - bool m_defaultValue = false; - QString m_label; - QString m_infoLabelText; - QPointer m_checkBox; // Owned by configuration widget - QPointer m_infoLabel; // Owned by configuration widget - QString m_autoSettingsKey; - - std::function m_clickCallBack; -}; - -void DebuggerLanguageAspect::addToLayout(Layouting::LayoutBuilder &builder) -{ - QTC_CHECK(!m_checkBox); - m_checkBox = new QCheckBox(m_label); - m_checkBox->setChecked(m_value); - m_checkBox->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); - - QTC_CHECK(m_clickCallBack); - connect(m_checkBox, &QAbstractButton::clicked, this, m_clickCallBack, Qt::QueuedConnection); - - connect(m_checkBox.data(), &QAbstractButton::clicked, this, [this] { - m_value = m_checkBox->isChecked() ? EnabledLanguage : DisabledLanguage; - emit changed(); - }); - builder.addItem(QString()); - builder.addItem(m_checkBox.data()); - - if (!m_infoLabelText.isEmpty()) { - QTC_CHECK(!m_infoLabel); - m_infoLabel = new QLabel(m_infoLabelText); - connect(m_infoLabel, &QLabel::linkActivated, [](const QString &link) { - Core::HelpManager::showHelpUrl(link); - }); - builder.addItem(m_infoLabel.data()); - } -} - -void DebuggerLanguageAspect::setAutoSettingsKey(const QString &settingsKey) -{ - m_autoSettingsKey = settingsKey; -} - -void DebuggerLanguageAspect::fromMap(const QVariantMap &map) -{ - const bool val = map.value(settingsKey(), false).toBool(); - const bool autoVal = map.value(m_autoSettingsKey, false).toBool(); - m_value = autoVal ? AutoEnabledLanguage : val ? EnabledLanguage : DisabledLanguage; -} - -void DebuggerLanguageAspect::toMap(QVariantMap &data) const -{ - data.insert(settingsKey(), m_value == EnabledLanguage); - data.insert(m_autoSettingsKey, m_value == AutoEnabledLanguage); -} - - -bool DebuggerLanguageAspect::value() const -{ - return m_value; -} - -void DebuggerLanguageAspect::setValue(bool value) -{ - m_value = value ? EnabledLanguage : DisabledLanguage; - if (m_checkBox) - m_checkBox->setChecked(m_value); -} - -void DebuggerLanguageAspect::setLabel(const QString &label) -{ - m_label = label; -} - -} // Internal /*! \class Debugger::DebuggerRunConfigurationAspect @@ -151,15 +47,53 @@ DebuggerRunConfigurationAspect::DebuggerRunConfigurationAspect(Target *target) setDisplayName(Tr::tr("Debugger settings")); setConfigWidgetCreator([this] { - Layouting::Form builder; - builder.addRow(m_cppAspect); - builder.addRow(m_qmlAspect); - builder.addRow(m_overrideStartupAspect); + Layouting::Grid builder; + builder.addRow({m_cppAspect}); + auto info = new QLabel( + Tr::tr("What are the prerequisites?")); + builder.addRow({m_qmlAspect, info}); + connect(info, &QLabel::linkActivated, [](const QString &link) { + Core::HelpManager::showHelpUrl(link); + }); + builder.addRow({m_overrideStartupAspect}); static const QString env = qtcEnvironmentVariable("QTC_DEBUGGER_MULTIPROCESS"); if (env.toInt()) - builder.addRow(m_multiProcessAspect); - return builder.emerge(Layouting::WithoutMargins); + builder.addRow({m_multiProcessAspect}); + + auto details = new DetailsWidget; + details->setState(DetailsWidget::Expanded); + auto innerPane = new QWidget; + details->setWidget(innerPane); + builder.addItem(Layouting::noMargin); + builder.attachTo(innerPane); + + const auto setSummaryText = [this, details] { + QStringList items; + if (m_cppAspect->value() == TriState::Enabled) + items.append(Tr::tr("Enable C++ debugger")); + else if (m_cppAspect->value() == TriState::Default) + items.append(Tr::tr("Try to determine need for C++ debugger")); + + if (m_qmlAspect->value() == TriState::Enabled) + items.append(Tr::tr("Enable QML debugger")); + else if (m_qmlAspect->value() == TriState::Default) + items.append(Tr::tr("Try to determine need for QML debugger")); + + items.append(m_overrideStartupAspect->value().isEmpty() + ? Tr::tr("Without additional startup commands") + : Tr::tr("With additional startup commands")); + details->setSummaryText(items.join(". ")); + }; + setSummaryText(); + + connect(m_cppAspect, &BaseAspect::changed, this, setSummaryText); + connect(m_qmlAspect, &BaseAspect::changed, this, setSummaryText); + connect(m_overrideStartupAspect, &BaseAspect::changed, this, setSummaryText); + + return details; }); addDataExtractor(this, &DebuggerRunConfigurationAspect::useCppDebugger, &Data::useCppDebugger); @@ -167,29 +101,25 @@ DebuggerRunConfigurationAspect::DebuggerRunConfigurationAspect(Target *target) addDataExtractor(this, &DebuggerRunConfigurationAspect::useMultiProcess, &Data::useMultiProcess); addDataExtractor(this, &DebuggerRunConfigurationAspect::overrideStartup, &Data::overrideStartup); - m_cppAspect = new DebuggerLanguageAspect; - m_cppAspect->setLabel(Tr::tr("Enable C++")); + m_cppAspect = new TriStateAspect(nullptr, Tr::tr("Enabled"), Tr::tr("Disabled"), Tr::tr("Automatic")); + m_cppAspect->setLabelText(Tr::tr("C++ debugger:")); m_cppAspect->setSettingsKey("RunConfiguration.UseCppDebugger"); - m_cppAspect->setAutoSettingsKey("RunConfiguration.UseCppDebuggerAuto"); - m_qmlAspect = new DebuggerLanguageAspect; - m_qmlAspect->setLabel(Tr::tr("Enable QML")); + m_qmlAspect = new TriStateAspect(nullptr, Tr::tr("Enabled"), Tr::tr("Disabled"), Tr::tr("Automatic")); + m_qmlAspect->setLabelText(Tr::tr("QML debugger:")); m_qmlAspect->setSettingsKey("RunConfiguration.UseQmlDebugger"); - m_qmlAspect->setAutoSettingsKey("RunConfiguration.UseQmlDebuggerAuto"); - m_qmlAspect->setInfoLabelText(Tr::tr("What are the prerequisites?")); // Make sure at least one of the debuggers is set to be active. - m_cppAspect->setClickCallBack([this](bool on) { - if (!on && !m_qmlAspect->value()) - m_qmlAspect->setValue(true); + connect(m_cppAspect, &TriStateAspect::changed, this, [this]{ + if (m_cppAspect->value() == TriState::Disabled && m_qmlAspect->value() == TriState::Disabled) + m_qmlAspect->setValue(TriState::Default); }); - m_qmlAspect->setClickCallBack([this](bool on) { - if (!on && !m_cppAspect->value()) - m_cppAspect->setValue(true); + connect(m_qmlAspect, &TriStateAspect::changed, this, [this]{ + if (m_qmlAspect->value() == TriState::Disabled && m_cppAspect->value() == TriState::Disabled) + m_cppAspect->setValue(TriState::Default); }); + m_multiProcessAspect = new BoolAspect; m_multiProcessAspect->setSettingsKey("RunConfiguration.UseMultiProcess"); m_multiProcessAspect->setLabel(Tr::tr("Enable Debugging of Subprocesses"), @@ -211,23 +141,37 @@ DebuggerRunConfigurationAspect::~DebuggerRunConfigurationAspect() void DebuggerRunConfigurationAspect::setUseQmlDebugger(bool value) { - m_qmlAspect->setValue(value); + m_qmlAspect->setValue(value ? TriState::Enabled : TriState::Disabled); } bool DebuggerRunConfigurationAspect::useCppDebugger() const { - if (m_cppAspect->m_value == AutoEnabledLanguage) + if (m_cppAspect->value() == TriState::Default) return m_target->project()->projectLanguages().contains( ProjectExplorer::Constants::CXX_LANGUAGE_ID); - return m_cppAspect->m_value == EnabledLanguage; + return m_cppAspect->value() == TriState::Enabled; +} + +static bool projectHasQmlDefines(ProjectExplorer::Project *project) +{ + auto projectInfo = CppEditor::CppModelManager::instance()->projectInfo(project); + QTC_ASSERT(projectInfo, return false); + return Utils::anyOf(projectInfo->projectParts(), + [](const CppEditor::ProjectPart::ConstPtr &part){ + return Utils::anyOf(part->projectMacros, [](const Macro ¯o){ + return macro.key == "QT_DECLARATIVE_LIB" + || macro.key == "QT_QUICK_LIB" + || macro.key == "QT_QML_LIB"; + }); + }); } bool DebuggerRunConfigurationAspect::useQmlDebugger() const { - if (m_qmlAspect->m_value == AutoEnabledLanguage) { + if (m_qmlAspect->value() == TriState::Default) { const Core::Context languages = m_target->project()->projectLanguages(); if (!languages.contains(ProjectExplorer::Constants::QMLJS_LANGUAGE_ID)) - return false; + return projectHasQmlDefines(m_target->project()); // // Try to find a build configuration to check whether qml debugging is enabled there @@ -238,7 +182,7 @@ bool DebuggerRunConfigurationAspect::useQmlDebugger() const return !languages.contains(ProjectExplorer::Constants::CXX_LANGUAGE_ID); } - return m_qmlAspect->m_value == EnabledLanguage; + return m_qmlAspect->value() == TriState::Enabled; } bool DebuggerRunConfigurationAspect::useMultiProcess() const @@ -272,12 +216,23 @@ void DebuggerRunConfigurationAspect::toMap(QVariantMap &map) const m_qmlAspect->toMap(map); m_multiProcessAspect->toMap(map); m_overrideStartupAspect->toMap(map); + + // compatibility to old settings + map.insert("RunConfiguration.UseCppDebuggerAuto", m_cppAspect->value() == TriState::Default); + map.insert("RunConfiguration.UseQmlDebuggerAuto", m_qmlAspect->value() == TriState::Default); } void DebuggerRunConfigurationAspect::fromMap(const QVariantMap &map) { m_cppAspect->fromMap(map); m_qmlAspect->fromMap(map); + + // respect old project settings + if (map.value("RunConfiguration.UseCppDebuggerAuto", false).toBool()) + m_cppAspect->setValue(TriState::Default); + if (map.value("RunConfiguration.UseQmlDebuggerAuto", false).toBool()) + m_qmlAspect->setValue(TriState::Default); + m_multiProcessAspect->fromMap(map); m_overrideStartupAspect->fromMap(map); } diff --git a/src/plugins/debugger/debuggerrunconfigurationaspect.h b/src/plugins/debugger/debuggerrunconfigurationaspect.h index 2534d4f81ca..53b9c1f54d2 100644 --- a/src/plugins/debugger/debuggerrunconfigurationaspect.h +++ b/src/plugins/debugger/debuggerrunconfigurationaspect.h @@ -10,8 +10,6 @@ namespace Debugger { -namespace Internal { class DebuggerLanguageAspect; } - class DEBUGGER_EXPORT DebuggerRunConfigurationAspect : public ProjectExplorer::GlobalOrProjectAspect { @@ -40,8 +38,8 @@ public: }; private: - Internal::DebuggerLanguageAspect *m_cppAspect; - Internal::DebuggerLanguageAspect *m_qmlAspect; + Utils::TriStateAspect *m_cppAspect; + Utils::TriStateAspect *m_qmlAspect; Utils::BoolAspect *m_multiProcessAspect; Utils::StringAspect *m_overrideStartupAspect; ProjectExplorer::Target *m_target; diff --git a/src/plugins/debugger/debuggerruncontrol.cpp b/src/plugins/debugger/debuggerruncontrol.cpp index 8b6ac29c995..e3feb04e393 100644 --- a/src/plugins/debugger/debuggerruncontrol.cpp +++ b/src/plugins/debugger/debuggerruncontrol.cpp @@ -23,8 +23,9 @@ #include #include #include +#include #include -#include +#include #include #include #include @@ -34,8 +35,8 @@ #include #include #include +#include #include -#include #include #include #include @@ -69,6 +70,7 @@ DebuggerEngine *createPdbEngine(); DebuggerEngine *createQmlEngine(); DebuggerEngine *createLldbEngine(); DebuggerEngine *createUvscEngine(); +DebuggerEngine *createDapEngine(); static QString noEngineMessage() { @@ -107,7 +109,7 @@ private: } m_coreUnpackProcess.setWorkingDirectory(TemporaryDirectory::masterDirectoryFilePath()); - connect(&m_coreUnpackProcess, &QtcProcess::done, this, [this] { + connect(&m_coreUnpackProcess, &Process::done, this, [this] { if (m_coreUnpackProcess.error() == QProcess::UnknownError) { reportStopped(); return; @@ -130,7 +132,7 @@ private: appendMessage(msg.arg(m_tempCoreFilePath.toUserOutput()), LogMessageFormat); m_tempCoreFile.setFileName(m_tempCoreFilePath.path()); m_tempCoreFile.open(QFile::WriteOnly); - connect(&m_coreUnpackProcess, &QtcProcess::readyReadStandardOutput, this, [this] { + connect(&m_coreUnpackProcess, &Process::readyReadStandardOutput, this, [this] { m_tempCoreFile.write(m_coreUnpackProcess.readAllRawStandardOutput()); }); m_coreUnpackProcess.setCommand({"gzip", {"-c", "-d", m_coreFilePath.path()}}); @@ -146,7 +148,7 @@ private: QFile m_tempCoreFile; FilePath m_coreFilePath; FilePath m_tempCoreFilePath; - QtcProcess m_coreUnpackProcess; + Process m_coreUnpackProcess; }; class DebuggerRunToolPrivate @@ -182,8 +184,8 @@ void DebuggerRunTool::setStartMode(DebuggerStartMode startMode) // FIXME: This is horribly wrong. // get files from all the projects in the session - QList projects = SessionManager::projects(); - if (Project *startupProject = SessionManager::startupProject()) { + QList projects = ProjectManager::projects(); + if (Project *startupProject = ProjectManager::startupProject()) { // startup project first projects.removeOne(startupProject); projects.insert(0, startupProject); @@ -508,6 +510,9 @@ void DebuggerRunTool::start() case UvscEngineType: m_engine = createUvscEngine(); break; + case DapEngineType: + m_engine = createDapEngine(); + break; default: if (!m_runParameters.isQmlDebugging) { reportFailure(noEngineMessage() + '\n' + @@ -611,14 +616,12 @@ void DebuggerRunTool::start() showMessage(warningMessage, LogWarning); - static bool checked = true; - if (checked) - CheckableMessageBox::information(Core::ICore::dialogParent(), - Tr::tr("Debugger"), - warningMessage, - Tr::tr("&Show this message again."), - &checked, - QDialogButtonBox::Ok); + static bool doNotShowAgain = false; + CheckableMessageBox::information(Core::ICore::dialogParent(), + Tr::tr("Debugger"), + warningMessage, + &doNotShowAgain, + QMessageBox::Ok); } } @@ -885,8 +888,8 @@ DebuggerRunTool::DebuggerRunTool(RunControl *runControl, AllowTerminal allowTerm Runnable inferior = runControl->runnable(); const FilePath &debuggerExecutable = m_runParameters.debugger.command.executable(); - inferior.command.setExecutable(inferior.command.executable().onDevice(debuggerExecutable)); - inferior.workingDirectory = inferior.workingDirectory.onDevice(debuggerExecutable); + inferior.command.setExecutable(debuggerExecutable.withNewMappedPath(inferior.command.executable())); + inferior.workingDirectory = debuggerExecutable.withNewMappedPath(inferior.workingDirectory); // Normalize to work around QTBUG-17529 (QtDeclarative fails with 'File name case mismatch'...) inferior.workingDirectory = inferior.workingDirectory.normalizedPathName(); m_runParameters.inferior = inferior; @@ -900,6 +903,9 @@ DebuggerRunTool::DebuggerRunTool(RunControl *runControl, AllowTerminal allowTerm if (Project *project = runControl->project()) { m_runParameters.projectSourceDirectory = project->projectDirectory(); m_runParameters.projectSourceFiles = project->files(Project::SourceFiles); + } else { + m_runParameters.projectSourceDirectory = m_runParameters.debugger.command.executable().parentDir(); + m_runParameters.projectSourceFiles.clear(); } m_runParameters.toolChainAbi = ToolChainKitAspect::targetAbi(kit); @@ -924,7 +930,6 @@ DebuggerRunTool::DebuggerRunTool(RunControl *runControl, AllowTerminal allowTerm } } - m_runParameters.dumperPath = Core::ICore::resourcePath("debugger/"); if (QtSupport::QtVersion *baseQtVersion = QtSupport::QtKitAspect::qtVersion(kit)) { const QVersionNumber qtVersion = baseQtVersion->qtVersion(); m_runParameters.fallbackQtVersion = 0x10000 * qtVersion.majorVersion() @@ -1036,14 +1041,37 @@ DebugServerRunner::DebugServerRunner(RunControl *runControl, DebugServerPortsGat cmd.setExecutable(commandLine().executable()); // FIXME: Case should not happen? } else { cmd.setExecutable(runControl->device()->debugServerPath()); - if (cmd.isEmpty()) - cmd.setExecutable(runControl->device()->filePath("gdbserver")); + + if (cmd.isEmpty()) { + if (runControl->device()->osType() == Utils::OsTypeMac) { + const FilePath debugServerLocation = runControl->device()->filePath( + "/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/" + "Resources/debugserver"); + + if (debugServerLocation.isExecutableFile()) { + cmd.setExecutable(debugServerLocation); + } else { + // TODO: In the future it is expected that the debugserver will be + // replaced by lldb-server. Remove the check for debug server at that point. + const FilePath lldbserver + = runControl->device()->filePath("lldb-server").searchInPath(); + if (lldbserver.isExecutableFile()) + cmd.setExecutable(lldbserver); + } + } else { + cmd.setExecutable(runControl->device()->filePath("gdbserver")); + } + } args.clear(); - if (cmd.executable().toString().contains("lldb-server")) { + if (cmd.executable().baseName().contains("lldb-server")) { args.append("platform"); args.append("--listen"); args.append(QString("*:%1").arg(portsGatherer->gdbServer().port())); args.append("--server"); + } else if (cmd.executable().baseName() == "debugserver") { + args.append(QString("*:%1").arg(portsGatherer->gdbServer().port())); + args.append("--attach"); + args.append(QString::number(m_pid.pid())); } else { // Something resembling gdbserver if (m_useMulti) diff --git a/src/plugins/debugger/debuggersourcepathmappingwidget.cpp b/src/plugins/debugger/debuggersourcepathmappingwidget.cpp index 33417b55a01..9d3af533bcc 100644 --- a/src/plugins/debugger/debuggersourcepathmappingwidget.cpp +++ b/src/plugins/debugger/debuggersourcepathmappingwidget.cpp @@ -12,8 +12,8 @@ #include #include #include +#include #include -#include #include #include @@ -420,7 +420,7 @@ static QString findQtInstallPath(const FilePath &qmakePath) { if (qmakePath.isEmpty()) return QString(); - QtcProcess proc; + Process proc; proc.setCommand({qmakePath, {"-query", "QT_INSTALL_HEADERS"}}); proc.start(); if (!proc.waitForFinished()) { @@ -491,12 +491,12 @@ void SourcePathMapAspect::toMap(QVariantMap &) const QTC_CHECK(false); } -void SourcePathMapAspect::addToLayout(Layouting::LayoutBuilder &builder) +void SourcePathMapAspect::addToLayout(Layouting::LayoutItem &parent) { QTC_CHECK(!d->m_widget); d->m_widget = createSubWidget(); d->m_widget->setSourcePathMap(value()); - builder.addRow(d->m_widget.data()); + parent.addItem(d->m_widget.data()); } QVariant SourcePathMapAspect::volatileValue() const diff --git a/src/plugins/debugger/debuggertooltipmanager.cpp b/src/plugins/debugger/debuggertooltipmanager.cpp index f5e9933fbef..8efea0c487a 100644 --- a/src/plugins/debugger/debuggertooltipmanager.cpp +++ b/src/plugins/debugger/debuggertooltipmanager.cpp @@ -13,16 +13,15 @@ #include "stackhandler.h" #include "watchhandler.h" -#include #include #include #include +#include #include +#include #include -#include - #include #include @@ -266,9 +265,20 @@ public: void expandNode(const QModelIndex &idx) { + if (!m_engine) + return; + m_expandedINames.insert(idx.data(LocalsINameRole).toString()); - if (canFetchMore(idx)) - fetchMore(idx); + if (canFetchMore(idx)) { + if (!idx.isValid()) + return; + + if (auto item = dynamic_cast(itemForIndex(idx))) { + WatchItem *it = m_engine->watchHandler()->findItem(item->iname); + if (QTC_GUARD(it)) + it->model()->fetchMore(it->index()); + } + } } void collapseNode(const QModelIndex &idx) @@ -276,22 +286,6 @@ public: m_expandedINames.remove(idx.data(LocalsINameRole).toString()); } - void fetchMore(const QModelIndex &idx) override - { - if (!idx.isValid()) - return; - auto item = dynamic_cast(itemForIndex(idx)); - if (!item) - return; - QString iname = item->iname; - if (!m_engine) - return; - - WatchItem *it = m_engine->watchHandler()->findItem(iname); - QTC_ASSERT(it, return); - it->model()->fetchMore(it->index()); - } - void restoreTreeModel(QXmlStreamReader &r); QPointer m_engine; @@ -519,7 +513,7 @@ DebuggerToolTipWidget::DebuggerToolTipWidget() setAttribute(Qt::WA_DeleteOnClose); isPinned = false; - const QIcon pinIcon(":/debugger/images/pin.xpm"); + const QIcon pinIcon = Utils::Icons::PINNED_SMALL.icon(); pinButton = new QToolButton; pinButton->setIcon(pinIcon); @@ -534,9 +528,7 @@ DebuggerToolTipWidget::DebuggerToolTipWidget() auto toolBar = new QToolBar(this); toolBar->setProperty("_q_custom_style_disabled", QVariant(true)); - const QList pinIconSizes = pinIcon.availableSizes(); - if (!pinIconSizes.isEmpty()) - toolBar->setIconSize(pinIconSizes.front()); + toolBar->setIconSize({12, 12}); toolBar->addWidget(pinButton); toolBar->addWidget(copyButton); toolBar->addWidget(titleLabel); @@ -1171,6 +1163,7 @@ void DebuggerToolTipManagerPrivate::slotTooltipOverrideRequested context.fileName = document->filePath(); context.position = pos; editorWidget->convertPosition(pos, &context.line, &context.column); + ++context.column; QString raw = cppExpressionAt(editorWidget, context.position, &context.line, &context.column, &context.function, &context.scopeFromLine, &context.scopeToLine); context.expression = fixCppExpression(raw); diff --git a/src/plugins/debugger/gdb/gdbengine.cpp b/src/plugins/debugger/gdb/gdbengine.cpp index 9f8dbac6619..f69c9f72d7b 100644 --- a/src/plugins/debugger/gdb/gdbengine.cpp +++ b/src/plugins/debugger/gdb/gdbengine.cpp @@ -38,9 +38,9 @@ #include #include #include +#include #include #include -#include #include #include @@ -146,13 +146,13 @@ GdbEngine::GdbEngine() connect(&s.useDynamicType, &BaseAspect::changed, this, &GdbEngine::reloadLocals); - connect(&m_gdbProc, &QtcProcess::started, + connect(&m_gdbProc, &Process::started, this, &GdbEngine::handleGdbStarted); - connect(&m_gdbProc, &QtcProcess::done, + connect(&m_gdbProc, &Process::done, this, &GdbEngine::handleGdbDone); - connect(&m_gdbProc, &QtcProcess::readyReadStandardOutput, + connect(&m_gdbProc, &Process::readyReadStandardOutput, this, &GdbEngine::readGdbStandardOutput); - connect(&m_gdbProc, &QtcProcess::readyReadStandardError, + connect(&m_gdbProc, &Process::readyReadStandardError, this, &GdbEngine::readGdbStandardError); // Output @@ -470,7 +470,8 @@ void GdbEngine::handleAsyncOutput(const QStringView asyncClass, const GdbMi &res module.startAddress = 0; module.endAddress = 0; module.hostPath = result["host-name"].data(); - module.modulePath = result["target-name"].data(); + const QString target = result["target-name"].data(); + module.modulePath = runParameters().inferior.command.executable().withNewPath(target); module.moduleName = QFileInfo(module.hostPath).baseName(); modulesHandler()->updateModule(module); } else if (asyncClass == u"library-unloaded") { @@ -478,7 +479,8 @@ void GdbEngine::handleAsyncOutput(const QStringView asyncClass, const GdbMi &res // target-name="/usr/lib/libdrm.so.2", // host-name="/usr/lib/libdrm.so.2" QString id = result["id"].data(); - modulesHandler()->removeModule(result["target-name"].data()); + const QString target = result["target-name"].data(); + modulesHandler()->removeModule(runParameters().inferior.command.executable().withNewPath(target)); progressPing(); showStatusMessage(Tr::tr("Library %1 unloaded.").arg(id), 1000); } else if (asyncClass == u"thread-group-added") { @@ -537,6 +539,7 @@ void GdbEngine::handleAsyncOutput(const QStringView asyncClass, const GdbMi &res ba.remove(pos1, pos3 - pos1 + 1); GdbMi res; res.fromString(ba); + const FilePath &fileRoot = runParameters().projectSourceDirectory; BreakHandler *handler = breakHandler(); Breakpoint bp; for (const GdbMi &bkpt : res) { @@ -545,13 +548,13 @@ void GdbEngine::handleAsyncOutput(const QStringView asyncClass, const GdbMi &res // A sub-breakpoint. QTC_ASSERT(bp, continue); SubBreakpoint loc = bp->findOrCreateSubBreakpoint(nr); - loc->params.updateFromGdbOutput(bkpt); + loc->params.updateFromGdbOutput(bkpt, fileRoot); loc->params.type = bp->type(); } else { // A primary breakpoint. bp = handler->findBreakpointByResponseId(nr); if (bp) - bp->updateFromGdbOutput(bkpt); + bp->updateFromGdbOutput(bkpt, fileRoot); } } if (bp) @@ -567,7 +570,7 @@ void GdbEngine::handleAsyncOutput(const QStringView asyncClass, const GdbMi &res const QString nr = bkpt["number"].data(); BreakpointParameters br; br.type = BreakpointByFileAndLine; - br.updateFromGdbOutput(bkpt); + br.updateFromGdbOutput(bkpt, runParameters().projectSourceDirectory); handler->handleAlienBreakpoint(nr, br); } } else if (asyncClass == u"breakpoint-deleted") { @@ -762,7 +765,8 @@ void GdbEngine::runCommand(const DebuggerCommand &command) if (cmd.flags & ConsoleCommand) cmd.function = "-interpreter-exec console \"" + cmd.function + '"'; cmd.function = QString::number(token) + cmd.function; - showMessage(cmd.function, LogInput); + + showMessage(cmd.function.left(100), LogInput); if (m_scheduledTestResponses.contains(token)) { // Fake response for test cases. @@ -797,7 +801,7 @@ void GdbEngine::runCommand(const DebuggerCommand &command) int GdbEngine::commandTimeoutTime() const { - const int time = debuggerSettings()->gdbWatchdogTimeout.value(); + const int time = debuggerSettings()->gdbWatchdogTimeout(); return 1000 * qMax(20, time); } @@ -1012,7 +1016,7 @@ void GdbEngine::updateAll() { //PENDING_DEBUG("UPDATING ALL\n"); QTC_CHECK(state() == InferiorUnrunnable || state() == InferiorStopOk); - DebuggerCommand cmd(stackCommand(debuggerSettings()->maximalStackDepth.value())); + DebuggerCommand cmd(stackCommand(debuggerSettings()->maximalStackDepth())); cmd.callback = [this](const DebuggerResponse &r) { handleStackListFrames(r, false); }; runCommand(cmd); stackHandler()->setCurrentIndex(0); @@ -1026,7 +1030,7 @@ void GdbEngine::handleQuerySources(const DebuggerResponse &response) { m_sourcesListUpdating = false; if (response.resultClass == ResultDone) { - QMap oldShortToFull = m_shortToFullName; + QMap oldShortToFull = m_shortToFullName; m_shortToFullName.clear(); m_fullToShortName.clear(); // "^done,files=[{file="../../../../bin/dumper/dumper.cpp", @@ -1037,7 +1041,7 @@ void GdbEngine::handleQuerySources(const DebuggerResponse &response) continue; GdbMi fullName = item["fullname"]; QString file = fileName.data(); - QString full; + FilePath full; if (fullName.isValid()) { full = cleanupFullName(fullName.data()); m_fullToShortName[full] = file; @@ -1102,9 +1106,9 @@ void GdbEngine::handleStopResponse(const GdbMi &data) // Ignore trap on Windows terminals, which results in // spurious "* stopped" message. if (m_expectTerminalTrap) { - m_expectTerminalTrap = false; if ((!data.isValid() || !data["reason"].isValid()) && Abi::hostAbi().os() == Abi::WindowsOS) { + m_expectTerminalTrap = false; showMessage("IGNORING TERMINAL SIGTRAP", LogMisc); return; } @@ -1181,7 +1185,7 @@ void GdbEngine::handleStopResponse(const GdbMi &data) const QString nr = data["bkptno"].data(); int lineNumber = 0; - QString fullName; + FilePath fullName; QString function; QString language; if (frame.isValid()) { @@ -1194,15 +1198,13 @@ void GdbEngine::handleStopResponse(const GdbMi &data) lineNumber = lineNumberG.toInt(); fullName = cleanupFullName(frame["fullname"].data()); if (fullName.isEmpty()) - fullName = frame["file"].data(); + fullName = runParameters().projectSourceDirectory.withNewPath(frame["file"].data()); } // found line number } else { showMessage("INVALID STOPPED REASON", LogWarning); } - const FilePath onDevicePath = FilePath::fromString(fullName).onDevice( - runParameters().debugger.command.executable()); - const FilePath fileName = onDevicePath.localSource().value_or(onDevicePath); + const FilePath fileName = fullName.localSource().value_or(fullName); if (!nr.isEmpty() && frame.isValid()) { // Use opportunity to update the breakpoint marker position. @@ -1420,14 +1422,19 @@ void GdbEngine::handleStop2(const GdbMi &data) } else if (m_isQnxGdb && name == "0" && meaning == "Signal 0") { showMessage("SIGNAL 0 CONSIDERED BOGUS."); } else { - showMessage("HANDLING SIGNAL " + name); - if (debuggerSettings()->useMessageBoxForSignals.value() && !isStopperThread) - if (!showStoppedBySignalMessageBox(meaning, name)) { - showMessage("SIGNAL RECEIVED WHILE SHOWING SIGNAL MESSAGE"); - return; - } - if (!name.isEmpty() && !meaning.isEmpty()) - reasontr = msgStoppedBySignal(meaning, name); + if (terminal() && name == "SIGCONT" && m_expectTerminalTrap) { + continueInferior(); + m_expectTerminalTrap = false; + } else { + showMessage("HANDLING SIGNAL " + name); + if (debuggerSettings()->useMessageBoxForSignals.value() && !isStopperThread) + if (!showStoppedBySignalMessageBox(meaning, name)) { + showMessage("SIGNAL RECEIVED WHILE SHOWING SIGNAL MESSAGE"); + return; + } + if (!name.isEmpty() && !meaning.isEmpty()) + reasontr = msgStoppedBySignal(meaning, name); + } } } if (reason.isEmpty()) @@ -1558,50 +1565,47 @@ void GdbEngine::handleExecuteContinue(const DebuggerResponse &response) } } -QString GdbEngine::fullName(const QString &fileName) +FilePath GdbEngine::fullName(const QString &fileName) { if (fileName.isEmpty()) - return QString(); - QTC_ASSERT(!m_sourcesListUpdating, /* */); - return m_shortToFullName.value(fileName, QString()); + return {}; + QTC_CHECK(!m_sourcesListUpdating); + return m_shortToFullName.value(fileName, {}); } -QString GdbEngine::cleanupFullName(const QString &fileName) +FilePath GdbEngine::cleanupFullName(const QString &fileName) { - QString cleanFilePath = fileName; + FilePath cleanFilePath = + runParameters().projectSourceDirectory.withNewPath(fileName).cleanPath(); // Gdb running on windows often delivers "fullnames" which // (a) have no drive letter and (b) are not normalized. if (Abi::hostAbi().os() == Abi::WindowsOS) { if (fileName.isEmpty()) - return QString(); - QFileInfo fi(fileName); - if (fi.isReadable()) - cleanFilePath = QDir::cleanPath(fi.absoluteFilePath()); + return {}; } if (!debuggerSettings()->autoEnrichParameters.value()) return cleanFilePath; - const QString sysroot = runParameters().sysRoot.toString(); - if (QFileInfo(cleanFilePath).isReadable()) + if (cleanFilePath.isReadableFile()) return cleanFilePath; + + const FilePath sysroot = runParameters().sysRoot; if (!sysroot.isEmpty() && fileName.startsWith('/')) { - cleanFilePath = sysroot + fileName; - if (QFileInfo(cleanFilePath).isReadable()) + cleanFilePath = sysroot.pathAppended(fileName.mid(1)); + if (cleanFilePath.isReadableFile()) return cleanFilePath; } if (m_baseNameToFullName.isEmpty()) { - FilePath filePath = FilePath::fromString(sysroot + "/usr/src/debug"); + FilePath filePath = sysroot.pathAppended("/usr/src/debug"); if (filePath.isDir()) { filePath.iterateDirectory( [this](const FilePath &filePath) { QString name = filePath.fileName(); - if (!name.startsWith('.')) { - QString path = filePath.path(); - m_baseNameToFullName.insert(name, path); - } + if (!name.startsWith('.')) + m_baseNameToFullName.insert(name, filePath); return IterationPolicy::Continue; }, {{"*"}, QDir::NoFilter, QDirIterator::Subdirectories}); @@ -1611,7 +1615,7 @@ QString GdbEngine::cleanupFullName(const QString &fileName) cleanFilePath.clear(); const QString base = FilePath::fromUserInput(fileName).fileName(); - QMultiMap::const_iterator jt = m_baseNameToFullName.constFind(base); + auto jt = m_baseNameToFullName.constFind(base); while (jt != m_baseNameToFullName.constEnd() && jt.key() == base) { // FIXME: Use some heuristics to find the "best" match. return jt.value(); @@ -1948,7 +1952,7 @@ void GdbEngine::executeRunToLine(const ContextData &data) if (data.address) loc = addressSpec(data.address); else - loc = '"' + breakLocation(data.fileName.toString()) + '"' + ':' + QString::number(data.lineNumber); + loc = '"' + breakLocation(data.fileName) + '"' + ':' + QString::number(data.lineNumber); runCommand({"tbreak " + loc}); runCommand({"continue", NativeCommand|RunRequest, CB(handleExecuteRunToLine)}); @@ -1976,7 +1980,7 @@ void GdbEngine::executeJumpToLine(const ContextData &data) if (data.address) loc = addressSpec(data.address); else - loc = '"' + breakLocation(data.fileName.toString()) + '"' + ':' + QString::number(data.lineNumber); + loc = '"' + breakLocation(data.fileName) + '"' + ':' + QString::number(data.lineNumber); runCommand({"tbreak " + loc}); notifyInferiorRunRequested(); @@ -2050,11 +2054,11 @@ void GdbEngine::setTokenBarrier() // ////////////////////////////////////////////////////////////////////// -QString GdbEngine::breakLocation(const QString &file) const +QString GdbEngine::breakLocation(const FilePath &file) const { QString where = m_fullToShortName.value(file); if (where.isEmpty()) - return FilePath::fromString(file).fileName(); + return file.fileName(); return where; } @@ -2078,7 +2082,7 @@ QString GdbEngine::breakpointLocation(const BreakpointParameters &data) usage = BreakpointUseShortPath; const QString fileName = usage == BreakpointUseFullPath - ? data.fileName.toString() : breakLocation(data.fileName.toString()); + ? data.fileName.path() : breakLocation(data.fileName); // The argument is simply a C-quoted version of the argument to the // non-MI "break" command, including the "original" quoting it wants. return "\"\\\"" + GdbMi::escapeCString(fileName) + "\\\":" @@ -2092,7 +2096,7 @@ QString GdbEngine::breakpointLocation2(const BreakpointParameters &data) usage = BreakpointUseShortPath; const QString fileName = usage == BreakpointUseFullPath - ? data.fileName.toString() : breakLocation(data.fileName.toString()); + ? data.fileName.path() : breakLocation(data.fileName); return GdbMi::escapeCString(fileName) + ':' + QString::number(data.lineNumber); } @@ -2105,7 +2109,7 @@ void GdbEngine::handleInsertInterpreterBreakpoint(const DebuggerResponse &respon notifyBreakpointInsertOk(bp); } else { bp->setResponseId(response.data["number"].data()); - bp->updateFromGdbOutput(response.data); + bp->updateFromGdbOutput(response.data, runParameters().projectSourceDirectory); notifyBreakpointInsertOk(bp); } } @@ -2115,7 +2119,7 @@ void GdbEngine::handleInterpreterBreakpointModified(const GdbMi &data) int modelId = data["modelid"].toInt(); Breakpoint bp = breakHandler()->findBreakpointByModelId(modelId); QTC_ASSERT(bp, return); - bp->updateFromGdbOutput(data); + bp->updateFromGdbOutput(data, runParameters().projectSourceDirectory); } void GdbEngine::handleWatchInsert(const DebuggerResponse &response, const Breakpoint &bp) @@ -2165,7 +2169,7 @@ void GdbEngine::handleBkpt(const GdbMi &bkpt, const Breakpoint &bp) // A sub-breakpoint. SubBreakpoint sub = bp->findOrCreateSubBreakpoint(nr); QTC_ASSERT(sub, return); - sub->params.updateFromGdbOutput(bkpt); + sub->params.updateFromGdbOutput(bkpt, runParameters().projectSourceDirectory); sub->params.type = bp->type(); if (usePseudoTracepoints && bp->isTracepoint()) { sub->params.tracepoint = true; @@ -2183,7 +2187,7 @@ void GdbEngine::handleBkpt(const GdbMi &bkpt, const Breakpoint &bp) const QString subnr = location["number"].data(); SubBreakpoint sub = bp->findOrCreateSubBreakpoint(subnr); QTC_ASSERT(sub, return); - sub->params.updateFromGdbOutput(location); + sub->params.updateFromGdbOutput(location, runParameters().projectSourceDirectory); sub->params.type = bp->type(); if (usePseudoTracepoints && bp->isTracepoint()) { sub->params.tracepoint = true; @@ -2194,7 +2198,7 @@ void GdbEngine::handleBkpt(const GdbMi &bkpt, const Breakpoint &bp) // A (the?) primary breakpoint. bp->setResponseId(nr); - bp->updateFromGdbOutput(bkpt); + bp->updateFromGdbOutput(bkpt, runParameters().projectSourceDirectory); if (usePseudoTracepoints && bp->isTracepoint()) bp->setMessage(bp->requestedParameters().message); } @@ -2501,7 +2505,7 @@ void GdbEngine::handleTracepointModified(const GdbMi &data) // A sub-breakpoint. QTC_ASSERT(bp, continue); SubBreakpoint loc = bp->findOrCreateSubBreakpoint(nr); - loc->params.updateFromGdbOutput(bkpt); + loc->params.updateFromGdbOutput(bkpt, runParameters().projectSourceDirectory); loc->params.type = bp->type(); if (bp->isTracepoint()) { loc->params.tracepoint = true; @@ -2511,7 +2515,7 @@ void GdbEngine::handleTracepointModified(const GdbMi &data) // A primary breakpoint. bp = handler->findBreakpointByResponseId(nr); if (bp) - bp->updateFromGdbOutput(bkpt); + bp->updateFromGdbOutput(bkpt, runParameters().projectSourceDirectory); } } QTC_ASSERT(bp, return); @@ -2612,13 +2616,13 @@ void GdbEngine::insertBreakpoint(const Breakpoint &bp) "QTC_DEBUGGER_PYTHON_VERBOSE"); const DebuggerSettings &s = *debuggerSettings(); cmd.arg("passexceptions", alwaysVerbose); - cmd.arg("fancy", s.useDebuggingHelpers.value()); - cmd.arg("autoderef", s.autoDerefPointers.value()); - cmd.arg("dyntype", s.useDynamicType.value()); - cmd.arg("qobjectnames", s.showQObjectNames.value()); + cmd.arg("fancy", s.useDebuggingHelpers()); + cmd.arg("autoderef", s.autoDerefPointers()); + cmd.arg("dyntype", s.useDynamicType()); + cmd.arg("qobjectnames", s.showQObjectNames()); cmd.arg("nativemixed", isNativeMixedActive()); - cmd.arg("stringcutoff", s.maximalStringLength.value()); - cmd.arg("displaystringlimit", s.displayStringLimit.value()); + cmd.arg("stringcutoff", s.maximalStringLength()); + cmd.arg("displaystringlimit", s.displayStringLimit()); cmd.arg("spec", breakpointLocation2(requested)); cmd.callback = [this, bp](const DebuggerResponse &r) { handleTracepointInsert(r, bp); }; @@ -2783,10 +2787,10 @@ static QString dotEscape(QString str) return str; } -void GdbEngine::loadSymbols(const QString &modulePath) +void GdbEngine::loadSymbols(const FilePath &modulePath) { // FIXME: gdb does not understand quoted names here (tested with 6.8) - runCommand({"sharedlibrary " + dotEscape(modulePath)}); + runCommand({"sharedlibrary " + dotEscape(modulePath.path())}); reloadModulesInternal(); reloadStack(); updateLocals(); @@ -2811,7 +2815,7 @@ void GdbEngine::loadSymbolsForStack() for (const Module &module : modules) { if (module.startAddress <= frame.address && frame.address < module.endAddress) { - runCommand({"sharedlibrary " + dotEscape(module.modulePath)}); + runCommand({"sharedlibrary " + dotEscape(module.modulePath.path())}); needUpdate = true; } } @@ -2824,7 +2828,7 @@ void GdbEngine::loadSymbolsForStack() } static void handleShowModuleSymbols(const DebuggerResponse &response, - const QString &modulePath, const QString &fileName) + const FilePath &modulePath, const QString &fileName) { if (response.resultClass == ResultDone) { Symbols symbols; @@ -2883,21 +2887,21 @@ static void handleShowModuleSymbols(const DebuggerResponse &response, } } -void GdbEngine::requestModuleSymbols(const QString &modulePath) +void GdbEngine::requestModuleSymbols(const FilePath &modulePath) { - Utils::TemporaryFile tf("gdbsymbols"); + TemporaryFile tf("gdbsymbols"); if (!tf.open()) return; QString fileName = tf.fileName(); tf.close(); - DebuggerCommand cmd("maint print msymbols \"" + fileName + "\" " + modulePath, NeedsTemporaryStop); + DebuggerCommand cmd("maint print msymbols \"" + fileName + "\" " + modulePath.path(), NeedsTemporaryStop); cmd.callback = [modulePath, fileName](const DebuggerResponse &r) { handleShowModuleSymbols(r, modulePath, fileName); }; runCommand(cmd); } -void GdbEngine::requestModuleSections(const QString &moduleName) +void GdbEngine::requestModuleSections(const FilePath &moduleName) { // There seems to be no way to get the symbols from a single .so. DebuggerCommand cmd("maint info section ALLOBJ", NeedsTemporaryStop); @@ -2908,14 +2912,14 @@ void GdbEngine::requestModuleSections(const QString &moduleName) } void GdbEngine::handleShowModuleSections(const DebuggerResponse &response, - const QString &moduleName) + const FilePath &moduleName) { // ~" Object file: /usr/lib/i386-linux-gnu/libffi.so.6\n" // ~" 0xb44a6114->0xb44a6138 at 0x00000114: .note.gnu.build-id ALLOC LOAD READONLY DATA HAS_CONTENTS\n" if (response.resultClass == ResultDone) { const QStringList lines = response.consoleStreamOutput.split('\n'); const QString prefix = " Object file: "; - const QString needle = prefix + moduleName; + const QString needle = prefix + moduleName.path(); Sections sections; bool active = false; for (const QString &line : std::as_const(lines)) { @@ -2956,11 +2960,6 @@ void GdbEngine::reloadModulesInternal() runCommand({"info shared", NeedsTemporaryStop, CB(handleModulesList)}); } -static QString nameFromPath(const QString &path) -{ - return QFileInfo(path).baseName(); -} - void GdbEngine::handleModulesList(const DebuggerResponse &response) { if (response.resultClass == ResultDone) { @@ -2971,14 +2970,15 @@ void GdbEngine::handleModulesList(const DebuggerResponse &response) QString data = response.consoleStreamOutput; QTextStream ts(&data, QIODevice::ReadOnly); bool found = false; + const FilePath inferior = runParameters().inferior.command.executable(); while (!ts.atEnd()) { QString line = ts.readLine(); QString symbolsRead; QTextStream ts(&line, QIODevice::ReadOnly); if (line.startsWith("0x")) { ts >> module.startAddress >> module.endAddress >> symbolsRead; - module.modulePath = ts.readLine().trimmed(); - module.moduleName = nameFromPath(module.modulePath); + module.modulePath = inferior.withNewPath(ts.readLine().trimmed()); + module.moduleName = module.modulePath.baseName(); module.symbolsRead = (symbolsRead == "Yes" ? Module::ReadOk : Module::ReadFailed); handler->updateModule(module); @@ -2989,8 +2989,8 @@ void GdbEngine::handleModulesList(const DebuggerResponse &response) QTC_ASSERT(symbolsRead == "No", continue); module.startAddress = 0; module.endAddress = 0; - module.modulePath = ts.readLine().trimmed(); - module.moduleName = nameFromPath(module.modulePath); + module.modulePath = inferior.withNewPath(ts.readLine().trimmed()); + module.moduleName = module.modulePath.baseName(); handler->updateModule(module); found = true; } @@ -3002,8 +3002,8 @@ void GdbEngine::handleModulesList(const DebuggerResponse &response) // loaded_addr="0x8fe00000",slide="0x0",prefix="__dyld_"}, // shlib-info={...}... for (const GdbMi &item : response.data) { - module.modulePath = item["path"].data(); - module.moduleName = nameFromPath(module.modulePath); + module.modulePath = inferior.withNewPath(item["path"].data()); + module.moduleName = module.modulePath.baseName(); module.symbolsRead = (item["state"].data() == "Y") ? Module::ReadOk : Module::ReadFailed; module.startAddress = @@ -3038,7 +3038,7 @@ void GdbEngine::reloadSourceFiles() cmd.callback = [this](const DebuggerResponse &response) { m_sourcesListUpdating = false; if (response.resultClass == ResultDone) { - QMap oldShortToFull = m_shortToFullName; + QMap oldShortToFull = m_shortToFullName; m_shortToFullName.clear(); m_fullToShortName.clear(); // "^done,files=[{file="../../../../bin/dumper/dumper.cpp", @@ -3049,7 +3049,7 @@ void GdbEngine::reloadSourceFiles() continue; GdbMi fullName = item["fullname"]; QString file = fileName.data(); - QString full; + FilePath full; if (fullName.isValid()) { full = cleanupFullName(fullName.data()); m_fullToShortName[full] = file; @@ -3922,7 +3922,7 @@ void GdbEngine::handleGdbStarted() Module module; module.startAddress = 0; module.endAddress = 0; - module.modulePath = rp.inferior.command.executable().toString(); + module.modulePath = rp.inferior.command.executable(); module.moduleName = ""; modulesHandler()->updateModule(module); @@ -3970,17 +3970,66 @@ void GdbEngine::handleGdbStarted() //if (terminal()->isUsable()) // runCommand({"set inferior-tty " + QString::fromUtf8(terminal()->slaveDevice())}); - const QString uninstalledData = rp.debugger.command.executable().parentDir() - .pathAppended("data-directory/python").path(); + const FilePath dumperPath = ICore::resourcePath("debugger"); + if (rp.debugger.command.executable().needsDevice()) { + // Gdb itself running remotely. + const FilePath loadOrderFile = dumperPath / "loadorder.txt"; + const expected_str toLoad = loadOrderFile.fileContents(); + if (!toLoad) { + AsynchronousMessageBox::critical( + Tr::tr("Cannot Find Debugger Initialization Script"), + Tr::tr("Cannot read %1: %2").arg(loadOrderFile.toUserOutput(), toLoad.error())); + notifyEngineSetupFailed(); + return; + } - runCommand({"python sys.path.insert(1, '" + rp.dumperPath.path() + "')"}); - runCommand({"python sys.path.append('" + uninstalledData + "')"}); - runCommand({"python from gdbbridge import *"}); + runCommand({"python import sys, types"}); + QStringList moduleList; + for (const QByteArray &rawModuleName : toLoad->split('\n')) { + QString module = QString::fromUtf8(rawModuleName).trimmed(); + if (module.startsWith('#') || module.isEmpty()) + continue; + if (module == "***bridge***") + module = "gdbbridge"; - const QString path = debuggerSettings()->extraDumperFile.value(); - if (!path.isEmpty() && QFileInfo(path).isReadable()) { + const FilePath codeFile = dumperPath / (module + ".py"); + const expected_str code = codeFile.fileContents(); + if (!code) { + qDebug() << Tr::tr("Cannot read %1: %2").arg(codeFile.toUserOutput(), code.error()); + continue; + } + + showMessage("Reading " + codeFile.toUserOutput(), LogInput); + runCommand({QString("python module = types.ModuleType('%1')").arg(module)}); + runCommand({QString("python code = bytes.fromhex('%1').decode('utf-8')") + .arg(QString::fromUtf8(code->toHex()))}); + runCommand({QString("python exec(code, module.__dict__)")}); + runCommand({QString("python sys.modules['%1'] = module").arg(module)}); + runCommand({QString("python import %1").arg(module)}); + + if (module.endsWith("types")) + moduleList.append('"' + module + '"'); + } + + runCommand({"python from gdbbridge import *"}); + runCommand(QString("python theDumper.dumpermodules = [%1]").arg(moduleList.join(','))); + + } else { + // Gdb on local host + // This is useful (only) in custom gdb builds that did not run 'make install' + const FilePath uninstalledData = rp.debugger.command.executable().parentDir() + / "data-directory/python"; + if (uninstalledData.exists()) + runCommand({"python sys.path.append('" + uninstalledData.path() + "')"}); + + runCommand({"python sys.path.insert(1, '" + dumperPath.path() + "')"}); + runCommand({"python from gdbbridge import *"}); + } + + const FilePath path = debuggerSettings()->extraDumperFile(); + if (!path.isEmpty() && path.isReadableFile()) { DebuggerCommand cmd("addDumperModule"); - cmd.arg("path", path); + cmd.arg("path", path.path()); runCommand(cmd); } @@ -4256,7 +4305,7 @@ void GdbEngine::interruptLocalInferior(qint64 pid) if (runParameters().runAsRoot) { Environment env = Environment::systemEnvironment(); RunControl::provideAskPassEntry(env); - QtcProcess proc; + Process proc; proc.setCommand(CommandLine{"sudo", {"-A", "kill", "-s", "SIGINT", QString::number(pid)}}); proc.setEnvironment(env); proc.start(); @@ -4369,7 +4418,7 @@ void GdbEngine::setupInferior() setLinuxOsAbi(); QString symbolFile; if (!rp.symbolFile.isEmpty()) - symbolFile = rp.symbolFile.toFileInfo().absoluteFilePath(); + symbolFile = rp.symbolFile.absoluteFilePath().path(); //const QByteArray sysroot = sp.sysroot.toLocal8Bit(); //const QByteArray remoteArch = sp.remoteArchitecture.toLatin1(); @@ -4558,7 +4607,13 @@ void GdbEngine::handleLocalAttach(const DebuggerResponse &response) switch (response.resultClass) { case ResultDone: case ResultRunning: + { showMessage("INFERIOR ATTACHED"); + + QString commands = expand(debuggerSettings()->gdbPostAttachCommands.value()); + if (!commands.isEmpty()) + runCommand({commands, NativeCommand}); + if (state() == EngineRunRequested) { // Happens e.g. for "Attach to unstarted application" // We will get a '*stopped' later that we'll interpret as 'spontaneous' @@ -4578,6 +4633,7 @@ void GdbEngine::handleLocalAttach(const DebuggerResponse &response) updateAll(); } break; + } case ResultError: if (response.data["msg"].data() == "ptrace: Operation not permitted.") { QString msg = msgPtraceError(runParameters().startMode); @@ -4732,6 +4788,13 @@ void GdbEngine::handleExecRun(const DebuggerResponse &response) CHECK_STATE(EngineRunRequested); if (response.resultClass == ResultRunning) { + + if (isLocalRunEngine()) { + QString commands = expand(debuggerSettings()->gdbPostAttachCommands.value()); + if (!commands.isEmpty()) + runCommand({commands, NativeCommand}); + } + notifyEngineRunAndInferiorRunOk(); showMessage("INFERIOR STARTED"); showMessage(msgInferiorSetupOk(), StatusBar); @@ -4983,7 +5046,7 @@ CoreInfo CoreInfo::readExecutableNameFromCore(const Runnable &debugger, const Fi args += {"-ex", "set osabi GNU/Linux"}; args += {"-ex", "core " + coreFile.toUserOutput()}; - QtcProcess proc; + Process proc; Environment envLang(Environment::systemEnvironment()); envLang.setupEnglishOutput(); proc.setEnvironment(envLang); diff --git a/src/plugins/debugger/gdb/gdbengine.h b/src/plugins/debugger/gdb/gdbengine.h index cd3a21ffbde..64a2eddd4eb 100644 --- a/src/plugins/debugger/gdb/gdbengine.h +++ b/src/plugins/debugger/gdb/gdbengine.h @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include @@ -210,23 +210,23 @@ private: ////////// General Interface ////////// void handleBkpt(const GdbMi &bkpt, const Breakpoint &bp); QString breakpointLocation(const BreakpointParameters &data); // For gdb/MI. QString breakpointLocation2(const BreakpointParameters &data); // For gdb/CLI fallback. - QString breakLocation(const QString &file) const; + QString breakLocation(const Utils::FilePath &file) const; void updateTracepointCaptures(const Breakpoint &bp); // // Modules specific stuff // - void loadSymbols(const QString &moduleName) final; + void loadSymbols(const Utils::FilePath &moduleName) final; void loadAllSymbols() final; void loadSymbolsForStack() final; - void requestModuleSymbols(const QString &moduleName) final; - void requestModuleSections(const QString &moduleName) final; + void requestModuleSymbols(const Utils::FilePath &moduleName) final; + void requestModuleSections(const Utils::FilePath &moduleName) final; void reloadModules() final; void examineModules() final; void reloadModulesInternal(); void handleModulesList(const DebuggerResponse &response); - void handleShowModuleSections(const DebuggerResponse &response, const QString &moduleName); + void handleShowModuleSections(const DebuggerResponse &response, const Utils::FilePath &moduleName); // // Snapshot specific stuff @@ -265,13 +265,13 @@ private: ////////// General Interface ////////// void reloadSourceFilesInternal(); void handleQuerySources(const DebuggerResponse &response); - QString fullName(const QString &fileName); - QString cleanupFullName(const QString &fileName); + Utils::FilePath fullName(const QString &fileName); + Utils::FilePath cleanupFullName(const QString &fileName); // awful hack to keep track of used files - QMap m_shortToFullName; - QMap m_fullToShortName; - QMultiMap m_baseNameToFullName; + QMap m_shortToFullName; + QMap m_fullToShortName; + QMultiMap m_baseNameToFullName; bool m_sourcesListUpdating = false; @@ -405,7 +405,7 @@ private: ////////// General Interface ////////// bool usesOutputCollector() const; - Utils::QtcProcess m_gdbProc; + Utils::Process m_gdbProc; OutputCollector m_outputCollector; QString m_errorString; }; diff --git a/src/plugins/debugger/gdb/gdboptionspage.cpp b/src/plugins/debugger/gdb/gdboptionspage.cpp index 3d24dd6c9cf..37008cabcd1 100644 --- a/src/plugins/debugger/gdb/gdboptionspage.cpp +++ b/src/plugins/debugger/gdb/gdboptionspage.cpp @@ -32,7 +32,7 @@ public: setCategory(Constants::DEBUGGER_SETTINGS_CATEGORY); setSettings(&debuggerSettings()->page2); - setLayouter([](QWidget *w) { + setLayouter([] { using namespace Layouting; DebuggerSettings &s = *debuggerSettings(); @@ -84,7 +84,7 @@ public: Column { s.gdbPostAttachCommands }, }; - Grid { general, extended, br, startup, attach }.attachTo(w); + return Grid { general, extended, br, startup, attach }; }); } }; diff --git a/src/plugins/debugger/images/pin.xpm b/src/plugins/debugger/images/pin.xpm deleted file mode 100644 index 0759becb67f..00000000000 --- a/src/plugins/debugger/images/pin.xpm +++ /dev/null @@ -1,19 +0,0 @@ -/* XPM */ -static const char * pin_xpm[] = { -"12 9 7 1", -" c None", -". c #000000", -"+ c #515151", -"@ c #A8A8A8", -"# c #A9A9A9", -"$ c #999999", -"% c #696969", -" . ", -" ......+", -" .@@@@@.", -" .#####.", -"+.....$$$$$.", -" .%%%%%.", -" .......", -" ......+", -" . "}; diff --git a/src/plugins/debugger/lldb/lldbengine.cpp b/src/plugins/debugger/lldb/lldbengine.cpp index 3283d5ff7d5..09b5d6eaec9 100644 --- a/src/plugins/debugger/lldb/lldbengine.cpp +++ b/src/plugins/debugger/lldb/lldbengine.cpp @@ -29,9 +29,9 @@ #include #include +#include #include #include -#include #include #include @@ -77,11 +77,11 @@ LldbEngine::LldbEngine() connect(&ds.useDynamicType, &BaseAspect::changed, this, &LldbEngine::updateLocals); connect(&ds.intelFlavor, &BaseAspect::changed, this, &LldbEngine::updateAll); - connect(&m_lldbProc, &QtcProcess::started, this, &LldbEngine::handleLldbStarted); - connect(&m_lldbProc, &QtcProcess::done, this, &LldbEngine::handleLldbDone); - connect(&m_lldbProc, &QtcProcess::readyReadStandardOutput, + connect(&m_lldbProc, &Process::started, this, &LldbEngine::handleLldbStarted); + connect(&m_lldbProc, &Process::done, this, &LldbEngine::handleLldbDone); + connect(&m_lldbProc, &Process::readyReadStandardOutput, this, &LldbEngine::readLldbStandardOutput); - connect(&m_lldbProc, &QtcProcess::readyReadStandardError, + connect(&m_lldbProc, &Process::readyReadStandardError, this, &LldbEngine::readLldbStandardError); connect(this, &LldbEngine::outputReady, @@ -187,7 +187,7 @@ void LldbEngine::setupEngine() // LLDB 14 installation on Ubuntu 22.04 is broken: // https://bugs.launchpad.net/ubuntu/+source/llvm-defaults/+bug/1972855 // Brush over it: - QtcProcess lldbPythonPathFinder; + Process lldbPythonPathFinder; lldbPythonPathFinder.setCommand({lldbCmd, {"-P"}}); lldbPythonPathFinder.start(); lldbPythonPathFinder.waitForFinished(); @@ -219,7 +219,8 @@ void LldbEngine::handleLldbStarted() const DebuggerRunParameters &rp = runParameters(); - executeCommand("script sys.path.insert(1, '" + rp.dumperPath.path() + "')"); + QString dumperPath = ICore::resourcePath("debugger").path(); + executeCommand("script sys.path.insert(1, '" + dumperPath + "')"); // This triggers reportState("enginesetupok") or "enginesetupfailed": executeCommand("script from lldbbridge import *"); @@ -227,10 +228,10 @@ void LldbEngine::handleLldbStarted() if (!commands.isEmpty()) executeCommand(commands); - const QString path = debuggerSettings()->extraDumperFile.value(); - if (!path.isEmpty() && QFileInfo(path).isReadable()) { + const FilePath path = debuggerSettings()->extraDumperFile(); + if (!path.isEmpty() && path.isReadableFile()) { DebuggerCommand cmd("addDumperModule"); - cmd.arg("path", path); + cmd.arg("path", path.path()); runCommand(cmd); } @@ -267,7 +268,8 @@ void LldbEngine::handleLldbStarted() cmd2.arg("nativemixed", isNativeMixedActive()); cmd2.arg("workingdirectory", rp.inferior.workingDirectory.path()); cmd2.arg("environment", rp.inferior.environment.toStringList()); - cmd2.arg("processargs", toHex(ProcessArgs::splitArgs(rp.inferior.command.arguments()).join(QChar(0)))); + cmd2.arg("processargs", toHex(ProcessArgs::splitArgs(rp.inferior.command.arguments(), + HostOsInfo::hostOs()).join(QChar(0)))); cmd2.arg("platform", rp.platform); cmd2.arg("symbolfile", rp.symbolFile.path()); @@ -278,8 +280,8 @@ void LldbEngine::handleLldbStarted() ? QString::fromLatin1("Attaching to %1 (%2)").arg(attachedPID).arg(attachedMainThreadID) : QString::fromLatin1("Attaching to %1").arg(attachedPID); showMessage(msg, LogMisc); + cmd2.arg("startmode", DebuggerStartMode::AttachToLocalProcess); cmd2.arg("attachpid", attachedPID); - } else { cmd2.arg("startmode", rp.startMode); // it is better not to check the start mode on the python sid (as we would have to duplicate the @@ -467,7 +469,7 @@ void LldbEngine::selectThread(const Thread &thread) DebuggerCommand cmd("selectThread"); cmd.arg("id", thread->id()); cmd.callback = [this](const DebuggerResponse &) { - fetchStack(debuggerSettings()->maximalStackDepth.value()); + fetchStack(debuggerSettings()->maximalStackDepth()); }; runCommand(cmd); } @@ -629,7 +631,7 @@ void LldbEngine::handleInterpreterBreakpointModified(const GdbMi &bpItem) updateBreakpointData(bp, bpItem, false); } -void LldbEngine::loadSymbols(const QString &moduleName) +void LldbEngine::loadSymbols(const FilePath &moduleName) { Q_UNUSED(moduleName) } @@ -642,12 +644,13 @@ void LldbEngine::reloadModules() { DebuggerCommand cmd("fetchModules"); cmd.callback = [this](const DebuggerResponse &response) { + const FilePath inferior = runParameters().inferior.command.executable(); const GdbMi &modules = response.data["modules"]; ModulesHandler *handler = modulesHandler(); handler->beginUpdateAll(); for (const GdbMi &item : modules) { Module module; - module.modulePath = item["file"].data(); + module.modulePath = inferior.withNewPath(item["file"].data()); module.moduleName = item["name"].data(); module.symbolsRead = Module::UnknownReadState; module.startAddress = item["loaded_addr"].toAddress(); @@ -659,13 +662,13 @@ void LldbEngine::reloadModules() runCommand(cmd); } -void LldbEngine::requestModuleSymbols(const QString &moduleName) +void LldbEngine::requestModuleSymbols(const FilePath &moduleName) { DebuggerCommand cmd("fetchSymbols"); - cmd.arg("module", moduleName); + cmd.arg("module", moduleName.path()); cmd.callback = [moduleName](const DebuggerResponse &response) { const GdbMi &symbols = response.data["symbols"]; - QString moduleName = response.data["module"].data(); + const QString module = response.data["module"].data(); Symbols syms; for (const GdbMi &item : symbols) { Symbol symbol; @@ -676,7 +679,7 @@ void LldbEngine::requestModuleSymbols(const QString &moduleName) symbol.demangled = item["demangled"].data(); syms.append(symbol); } - showModuleSymbols(moduleName, syms); + showModuleSymbols(moduleName.withNewPath(module), syms); }; runCommand(cmd); } @@ -698,7 +701,7 @@ void LldbEngine::updateAll() DebuggerCommand cmd("fetchThreads"); cmd.callback = [this](const DebuggerResponse &response) { threadsHandler()->setThreads(response.data); - fetchStack(debuggerSettings()->maximalStackDepth.value()); + fetchStack(debuggerSettings()->maximalStackDepth()); reloadRegisters(); }; runCommand(cmd); diff --git a/src/plugins/debugger/lldb/lldbengine.h b/src/plugins/debugger/lldb/lldbengine.h index 0a0d9adfb55..db11af9c104 100644 --- a/src/plugins/debugger/lldb/lldbengine.h +++ b/src/plugins/debugger/lldb/lldbengine.h @@ -11,7 +11,7 @@ #include #include -#include +#include #include #include @@ -68,9 +68,9 @@ private: void assignValueInDebugger(WatchItem *item, const QString &expr, const QVariant &value) override; void executeDebuggerCommand(const QString &command) override; - void loadSymbols(const QString &moduleName) override; + void loadSymbols(const Utils::FilePath &moduleName) override; void loadAllSymbols() override; - void requestModuleSymbols(const QString &moduleName) override; + void requestModuleSymbols(const Utils::FilePath &moduleName) override; void reloadModules() override; void reloadRegisters() override; void reloadSourceFiles() override {} @@ -113,7 +113,7 @@ private: QString m_inbuffer; QString m_scriptFileName; - Utils::QtcProcess m_lldbProc; + Utils::Process m_lldbProc; // FIXME: Make generic. int m_lastAgentId = 0; diff --git a/src/plugins/debugger/loadcoredialog.cpp b/src/plugins/debugger/loadcoredialog.cpp index 86c9c738dc3..4a0fea3f165 100644 --- a/src/plugins/debugger/loadcoredialog.cpp +++ b/src/plugins/debugger/loadcoredialog.cpp @@ -11,13 +11,12 @@ #include #include -#include +#include #include #include #include #include #include -#include #include #include @@ -34,6 +33,7 @@ using namespace Core; using namespace ProjectExplorer; +using namespace Tasking; using namespace Utils; namespace Debugger::Internal { @@ -217,8 +217,6 @@ void AttachCoreDialog::accepted() const DebuggerItem *debuggerItem = Debugger::DebuggerKitAspect::debugger(kit()); const FilePath debuggerCommand = debuggerItem->command(); - using namespace Tasking; - const auto copyFile = [debuggerCommand](const FilePath &srcPath) -> expected_str { if (!srcPath.isSameDevice(debuggerCommand)) { const expected_str tmpPath = debuggerCommand.tmpDir(); @@ -243,18 +241,18 @@ void AttachCoreDialog::accepted() using ResultType = expected_str; - const auto copyFileAsync = [=](QFutureInterface &fi, const FilePath &srcPath) { - fi.reportResult(copyFile(srcPath)); + const auto copyFileAsync = [=](QPromise &promise, const FilePath &srcPath) { + promise.addResult(copyFile(srcPath)); }; const Group root = { parallel, - Async{[=](auto &task) { - task.setAsyncCallData(copyFileAsync, this->coreFile()); + AsyncTask{[=](auto &task) { + task.setConcurrentCallData(copyFileAsync, this->coreFile()); }, [=](const auto &task) { d->coreFileResult = task.result(); }}, - Async{[=](auto &task) { - task.setAsyncCallData(copyFileAsync, this->symbolFile()); + AsyncTask{[=](auto &task) { + task.setConcurrentCallData(copyFileAsync, this->symbolFile()); }, [=](const auto &task) { d->symbolFileResult = task.result(); }}, }; diff --git a/src/plugins/debugger/moduleshandler.cpp b/src/plugins/debugger/moduleshandler.cpp index 3451b9b5725..4f063b57288 100644 --- a/src/plugins/debugger/moduleshandler.cpp +++ b/src/plugins/debugger/moduleshandler.cpp @@ -11,8 +11,8 @@ #include #include +#include #include -#include #include #include @@ -48,7 +48,7 @@ QVariant ModuleItem::data(int column, int role) const break; case 1: if (role == Qt::DisplayRole) - return module.modulePath; + return module.modulePath.toUserOutput(); if (role == Qt::ToolTipRole) { QString msg; if (!module.elfData.buildId.isEmpty()) @@ -140,6 +140,12 @@ public: DebuggerEngine *engine; }; +static bool dependsCanBeFound() +{ + static bool dependsInPath = Environment::systemEnvironment().searchInPath("depends").isEmpty(); + return dependsInPath; +} + bool ModulesModel::contextMenuEvent(const ItemViewEvent &ev) { ModuleItem *item = itemForIndexAtLevel<1>(ev.sourceModelIndex()); @@ -150,7 +156,7 @@ bool ModulesModel::contextMenuEvent(const ItemViewEvent &ev) const bool canShowSymbols = engine->hasCapability(ShowModuleSymbolsCapability); const bool moduleNameValid = item && !item->module.moduleName.isEmpty(); const QString moduleName = item ? item->module.moduleName : QString(); - const QString modulePath = item ? item->module.modulePath : QString(); + const FilePath modulePath = item ? item->module.modulePath : FilePath(); auto menu = new QMenu; @@ -163,11 +169,13 @@ bool ModulesModel::contextMenuEvent(const ItemViewEvent &ev) moduleNameValid && enabled && canReload, [this, modulePath] { engine->loadSymbols(modulePath); }); - // FIXME: Dependencies only available on Windows, when "depends" is installed. addAction(this, menu, Tr::tr("Show Dependencies of \"%1\"").arg(moduleName), Tr::tr("Show Dependencies"), - moduleNameValid && !moduleName.isEmpty() && HostOsInfo::isWindowsHost(), - [modulePath] { QtcProcess::startDetached({{"depends"}, {modulePath}}); }); + moduleNameValid && !modulePath.needsDevice() && modulePath.exists() + && dependsCanBeFound(), + [modulePath] { + Process::startDetached({{"depends"}, {modulePath.toString()}}); + }); addAction(this, menu, Tr::tr("Load Symbols for All Modules"), enabled && canLoadSymbols, @@ -185,7 +193,7 @@ bool ModulesModel::contextMenuEvent(const ItemViewEvent &ev) addAction(this, menu, Tr::tr("Edit File \"%1\"").arg(moduleName), Tr::tr("Edit File"), moduleNameValid, - [this, modulePath] { engine->gotoLocation(FilePath::fromString(modulePath)); }); + [this, modulePath] { engine->gotoLocation(modulePath); }); addAction(this, menu, Tr::tr("Show Symbols in File \"%1\"").arg(moduleName), Tr::tr("Show Symbols"), @@ -239,7 +247,7 @@ QAbstractItemModel *ModulesHandler::model() const return m_proxyModel; } -ModuleItem *ModulesHandler::moduleFromPath(const QString &modulePath) const +ModuleItem *ModulesHandler::moduleFromPath(const FilePath &modulePath) const { // Recent modules are more likely to be unloaded first. return m_model->findItemAtLevel<1>([modulePath](ModuleItem *item) { @@ -259,7 +267,7 @@ const Modules ModulesHandler::modules() const return mods; } -void ModulesHandler::removeModule(const QString &modulePath) +void ModulesHandler::removeModule(const FilePath &modulePath) { if (ModuleItem *item = moduleFromPath(modulePath)) m_model->destroyItem(item); @@ -267,7 +275,7 @@ void ModulesHandler::removeModule(const QString &modulePath) void ModulesHandler::updateModule(const Module &module) { - const QString path = module.modulePath; + const FilePath path = module.modulePath; if (path.isEmpty()) return; @@ -281,12 +289,12 @@ void ModulesHandler::updateModule(const Module &module) } try { // MinGW occasionallly throws std::bad_alloc. - ElfReader reader(FilePath::fromUserInput(path)); + ElfReader reader(path); item->module.elfData = reader.readHeaders(); item->update(); } catch(...) { qWarning("%s: An exception occurred while reading module '%s'", - Q_FUNC_INFO, qPrintable(module.modulePath)); + Q_FUNC_INFO, qPrintable(module.modulePath.toUserOutput())); } item->updated = true; } diff --git a/src/plugins/debugger/moduleshandler.h b/src/plugins/debugger/moduleshandler.h index 082a66f2ca0..03434edab28 100644 --- a/src/plugins/debugger/moduleshandler.h +++ b/src/plugins/debugger/moduleshandler.h @@ -70,7 +70,7 @@ public: ReadOk // Dwarf index available. }; QString moduleName; - QString modulePath; + Utils::FilePath modulePath; QString hostPath; SymbolReadState symbolsRead = UnknownReadState; quint64 startAddress = 0; @@ -99,7 +99,7 @@ public: QAbstractItemModel *model() const; - void removeModule(const QString &modulePath); + void removeModule(const Utils::FilePath &modulePath); void updateModule(const Module &module); void beginUpdateAll(); @@ -109,7 +109,7 @@ public: const Modules modules() const; private: - ModuleItem *moduleFromPath(const QString &modulePath) const; + ModuleItem *moduleFromPath(const Utils::FilePath &modulePath) const; ModulesModel *m_model; QSortFilterProxyModel *m_proxyModel; diff --git a/src/plugins/debugger/pdb/pdbengine.cpp b/src/plugins/debugger/pdb/pdbengine.cpp index fd749a2d0ce..accf32d0e6e 100644 --- a/src/plugins/debugger/pdb/pdbengine.cpp +++ b/src/plugins/debugger/pdb/pdbengine.cpp @@ -22,8 +22,8 @@ #include #include +#include #include -#include #include #include @@ -99,10 +99,10 @@ void PdbEngine::setupEngine() m_interpreter = runParameters().interpreter; QString bridge = ICore::resourcePath("debugger/pdbbridge.py").toString(); - connect(&m_proc, &QtcProcess::started, this, &PdbEngine::handlePdbStarted); - connect(&m_proc, &QtcProcess::done, this, &PdbEngine::handlePdbDone); - connect(&m_proc, &QtcProcess::readyReadStandardOutput, this, &PdbEngine::readPdbStandardOutput); - connect(&m_proc, &QtcProcess::readyReadStandardError, this, &PdbEngine::readPdbStandardError); + connect(&m_proc, &Process::started, this, &PdbEngine::handlePdbStarted); + connect(&m_proc, &Process::done, this, &PdbEngine::handlePdbDone); + connect(&m_proc, &Process::readyReadStandardOutput, this, &PdbEngine::readPdbStandardOutput); + connect(&m_proc, &Process::readyReadStandardError, this, &PdbEngine::readPdbStandardError); const FilePath scriptFile = runParameters().mainScript; if (!scriptFile.isReadableFile()) { @@ -263,7 +263,7 @@ void PdbEngine::removeBreakpoint(const Breakpoint &bp) notifyBreakpointRemoveOk(bp); } -void PdbEngine::loadSymbols(const QString &moduleName) +void PdbEngine::loadSymbols(const FilePath &moduleName) { Q_UNUSED(moduleName) } @@ -294,16 +294,16 @@ void PdbEngine::refreshModules(const GdbMi &modules) && path.endsWith("' (built-in)>")) { path = "(builtin)"; } - module.modulePath = path; + module.modulePath = FilePath::fromString(path); handler->updateModule(module); } handler->endUpdateAll(); } -void PdbEngine::requestModuleSymbols(const QString &moduleName) +void PdbEngine::requestModuleSymbols(const FilePath &moduleName) { DebuggerCommand cmd("listSymbols"); - cmd.arg("module", moduleName); + cmd.arg("module", moduleName.path()); runCommand(cmd); } @@ -341,7 +341,7 @@ void PdbEngine::refreshSymbols(const GdbMi &symbols) symbol.name = item["name"].data(); syms.append(symbol); } - showModuleSymbols(moduleName, syms); + showModuleSymbols(runParameters().inferior.command.executable().withNewPath(moduleName), syms); } bool PdbEngine::canHandleToolTip(const DebuggerToolTipContext &) const diff --git a/src/plugins/debugger/pdb/pdbengine.h b/src/plugins/debugger/pdb/pdbengine.h index d65f0e33b1c..1e365b36a7f 100644 --- a/src/plugins/debugger/pdb/pdbengine.h +++ b/src/plugins/debugger/pdb/pdbengine.h @@ -4,7 +4,7 @@ #pragma once #include -#include +#include #include @@ -52,9 +52,9 @@ private: const QString &expr, const QVariant &value) override; void executeDebuggerCommand(const QString &command) override; - void loadSymbols(const QString &moduleName) override; + void loadSymbols(const Utils::FilePath &moduleName) override; void loadAllSymbols() override; - void requestModuleSymbols(const QString &moduleName) override; + void requestModuleSymbols(const Utils::FilePath &moduleName) override; void reloadModules() override; void reloadRegisters() override {} void reloadSourceFiles() override {} @@ -87,7 +87,7 @@ private: void updateLocals() override; QString m_inbuffer; - Utils::QtcProcess m_proc; + Utils::Process m_proc; Utils::FilePath m_interpreter; }; diff --git a/src/plugins/debugger/qml/qmlengine.cpp b/src/plugins/debugger/qml/qmlengine.cpp index 204d6c4437c..033069c1165 100644 --- a/src/plugins/debugger/qml/qmlengine.cpp +++ b/src/plugins/debugger/qml/qmlengine.cpp @@ -40,8 +40,8 @@ #include #include +#include #include -#include #include #include @@ -200,7 +200,7 @@ public: QHash sourceDocuments; InteractiveInterpreter interpreter; - QtcProcess process; + Process process; QmlInspectorAgent inspectorAgent; QList queryIds; @@ -249,17 +249,17 @@ QmlEngine::QmlEngine() connect(stackHandler(), &StackHandler::currentIndexChanged, this, &QmlEngine::updateCurrentContext); - connect(&d->process, &QtcProcess::readyReadStandardOutput, this, [this] { + connect(&d->process, &Process::readyReadStandardOutput, this, [this] { // FIXME: Redirect to RunControl showMessage(d->process.readAllStandardOutput(), AppOutput); }); - connect(&d->process, &QtcProcess::readyReadStandardError, this, [this] { + connect(&d->process, &Process::readyReadStandardError, this, [this] { // FIXME: Redirect to RunControl showMessage(d->process.readAllStandardError(), AppOutput); }); - connect(&d->process, &QtcProcess::done, this, &QmlEngine::disconnected); - connect(&d->process, &QtcProcess::started, this, &QmlEngine::handleLauncherStarted); + connect(&d->process, &Process::done, this, &QmlEngine::disconnected); + connect(&d->process, &Process::started, this, &QmlEngine::handleLauncherStarted); debuggerConsole()->populateFileFinder(); debuggerConsole()->setScriptEvaluator([this](const QString &expr) { @@ -736,7 +736,7 @@ bool QmlEngine::acceptsBreakpoint(const BreakpointParameters &bp) const return bp.isQmlFileAndLineBreakpoint(); } -void QmlEngine::loadSymbols(const QString &moduleName) +void QmlEngine::loadSymbols(const FilePath &moduleName) { Q_UNUSED(moduleName) } @@ -759,7 +759,7 @@ void QmlEngine::updateAll() d->updateLocals(); } -void QmlEngine::requestModuleSymbols(const QString &moduleName) +void QmlEngine::requestModuleSymbols(const FilePath &moduleName) { Q_UNUSED(moduleName) } @@ -1802,10 +1802,10 @@ void QmlEnginePrivate::messageReceived(const QByteArray &data) updateScriptSource(name, lineOffset, columnOffset, source); } - QMap files; + QMap files; for (const QString &file : std::as_const(sourceFiles)) { QString shortName = file; - QString fullName = engine->toFileInProject(file); + FilePath fullName = engine->toFileInProject(file); files.insert(shortName, fullName); } @@ -1915,7 +1915,7 @@ void QmlEnginePrivate::messageReceived(const QByteArray &data) const QVariantMap script = body.value("script").toMap(); QUrl fileUrl(script.value(NAME).toString()); - QString filePath = engine->toFileInProject(fileUrl); + FilePath filePath = engine->toFileInProject(fileUrl); const QVariantMap exception = body.value("exception").toMap(); QString errorMessage = exception.value("text").toString(); @@ -2045,8 +2045,7 @@ StackFrame QmlEnginePrivate::extractStackFrame(const QVariant &bodyVal) stackFrame.function = extractString(body.value("func")); if (stackFrame.function.isEmpty()) stackFrame.function = Tr::tr("Anonymous Function"); - stackFrame.file = FilePath::fromString( - engine->toFileInProject(extractString(body.value("script")))); + stackFrame.file = engine->toFileInProject(extractString(body.value("script"))); stackFrame.usable = stackFrame.file.isReadableFile(); stackFrame.receiver = extractString(body.value("receiver")); stackFrame.line = body.value("line").toInt() + 1; @@ -2321,7 +2320,6 @@ void QmlEnginePrivate::insertSubItems(WatchItem *parent, const QVariantList &pro QTC_ASSERT(parent, return); LookupItems itemsToLookup; - const QSet expandedINames = engine->watchHandler()->expandedINames(); for (const QVariant &property : properties) { QmlV8ObjectData propertyData = extractData(property); std::unique_ptr item(new WatchItem); @@ -2343,7 +2341,7 @@ void QmlEnginePrivate::insertSubItems(WatchItem *parent, const QVariantList &pro item->id = propertyData.handle; item->type = propertyData.type; item->value = propertyData.value.toString(); - if (item->type.isEmpty() || expandedINames.contains(item->iname)) + if (item->type.isEmpty() || engine->watchHandler()->isExpandedIName(item->iname)) itemsToLookup.insert(propertyData.handle, {item->iname, item->name, item->exp}); setWatchItemHasChildren(item.get(), propertyData.hasChildren()); parent->appendChild(item.release()); @@ -2445,7 +2443,7 @@ void QmlEnginePrivate::flushSendBuffer() sendBuffer.clear(); } -QString QmlEngine::toFileInProject(const QUrl &fileUrl) +FilePath QmlEngine::toFileInProject(const QUrl &fileUrl) { // make sure file finder is properly initialized const DebuggerRunParameters &rp = runParameters(); @@ -2454,7 +2452,7 @@ QString QmlEngine::toFileInProject(const QUrl &fileUrl) d->fileFinder.setAdditionalSearchDirectories(rp.additionalSearchDirectories); d->fileFinder.setSysroot(rp.sysRoot); - return d->fileFinder.findFile(fileUrl).constFirst().toString(); + return d->fileFinder.findFile(fileUrl).constFirst(); } DebuggerEngine *createQmlEngine() diff --git a/src/plugins/debugger/qml/qmlengine.h b/src/plugins/debugger/qml/qmlengine.h index 331176d4269..2006ce081ff 100644 --- a/src/plugins/debugger/qml/qmlengine.h +++ b/src/plugins/debugger/qml/qmlengine.h @@ -25,7 +25,7 @@ public: void logServiceActivity(const QString &service, const QString &logMessage); void expressionEvaluated(quint32 queryId, const QVariant &result); - QString toFileInProject(const QUrl &fileUrl); + Utils::FilePath toFileInProject(const QUrl &fileUrl); private: void disconnected(); @@ -75,9 +75,9 @@ private: void assignValueInDebugger(WatchItem *item, const QString &expr, const QVariant &value) override; - void loadSymbols(const QString &moduleName) override; + void loadSymbols(const Utils::FilePath &moduleName) override; void loadAllSymbols() override; - void requestModuleSymbols(const QString &moduleName) override; + void requestModuleSymbols(const Utils::FilePath &moduleName) override; void reloadModules() override; void reloadRegisters() override {} void reloadSourceFiles() override; diff --git a/src/plugins/debugger/qml/qmlengineutils.cpp b/src/plugins/debugger/qml/qmlengineutils.cpp index 7e304d3c921..80e0ac98f17 100644 --- a/src/plugins/debugger/qml/qmlengineutils.cpp +++ b/src/plugins/debugger/qml/qmlengineutils.cpp @@ -20,6 +20,7 @@ using namespace QmlDebug; using namespace QmlJS; using namespace QmlJS::AST; using namespace TextEditor; +using namespace Utils; namespace Debugger::Internal { @@ -218,11 +219,10 @@ void clearExceptionSelection() } } -QStringList highlightExceptionCode(int lineNumber, const QString &filePath, const QString &errorMessage) +QStringList highlightExceptionCode(int lineNumber, const FilePath &filePath, const QString &errorMessage) { QStringList messages; - const QList editors = DocumentModel::editorsForFilePath( - Utils::FilePath::fromString(filePath)); + const QList editors = DocumentModel::editorsForFilePath(filePath); const TextEditor::FontSettings &fontSettings = TextEditor::TextEditorSettings::fontSettings(); QTextCharFormat errorFormat = fontSettings.toTextCharFormat(TextEditor::C_ERROR); @@ -251,7 +251,7 @@ QStringList highlightExceptionCode(int lineNumber, const QString &filePath, cons selections.append(sel); ed->setExtraSelections(TextEditorWidget::DebuggerExceptionSelection, selections); - messages.append(QString::fromLatin1("%1: %2: %3").arg(filePath).arg(lineNumber).arg(errorMessage)); + messages.append(QString::fromLatin1("%1: %2: %3").arg(filePath.toUserOutput()).arg(lineNumber).arg(errorMessage)); } return messages; } diff --git a/src/plugins/debugger/qml/qmlengineutils.h b/src/plugins/debugger/qml/qmlengineutils.h index 7cf55067b66..325c8f28e93 100644 --- a/src/plugins/debugger/qml/qmlengineutils.h +++ b/src/plugins/debugger/qml/qmlengineutils.h @@ -6,11 +6,13 @@ #include #include +namespace Utils { class FilePath; } + namespace Debugger::Internal { void appendDebugOutput(QtMsgType type, const QString &message, const QmlDebug::QDebugContextInfo &info); void clearExceptionSelection(); -QStringList highlightExceptionCode(int lineNumber, const QString &filePath, const QString &errorMessage); +QStringList highlightExceptionCode(int lineNumber, const Utils::FilePath &filePath, const QString &errorMessage); } // Debugger::Internal diff --git a/src/plugins/debugger/qml/qmlinspectoragent.cpp b/src/plugins/debugger/qml/qmlinspectoragent.cpp index 983e38adc0d..4b632c0f0db 100644 --- a/src/plugins/debugger/qml/qmlinspectoragent.cpp +++ b/src/plugins/debugger/qml/qmlinspectoragent.cpp @@ -32,6 +32,7 @@ using namespace QmlDebug; using namespace QmlDebug::Constants; +using namespace Utils; namespace Debugger::Internal { @@ -541,8 +542,8 @@ void QmlInspectorAgent::buildDebugIdHashRecursive(const ObjectReference &ref) lineNum += match.captured(3).toInt() - 1; } - const QString filePath = m_qmlEngine->toFileInProject(fileUrl); - m_debugIdLocations.insert(ref.debugId(), FileReference(filePath, lineNum, colNum)); + const FilePath filePath = m_qmlEngine->toFileInProject(fileUrl); + m_debugIdLocations.insert(ref.debugId(), FileReference(filePath.toFSPathString(), lineNum, colNum)); const auto children = ref.children(); for (const ObjectReference &it : children) @@ -735,7 +736,7 @@ void QmlInspectorAgent::onShowAppOnTopChanged(bool checked) void QmlInspectorAgent::jumpToObjectDefinitionInEditor(const FileReference &objSource) { - const auto filePath = Utils::FilePath::fromString(m_qmlEngine->toFileInProject(objSource.url())); + const FilePath filePath = m_qmlEngine->toFileInProject(objSource.url()); Core::EditorManager::openEditorAt({filePath, objSource.lineNumber()}); } diff --git a/src/plugins/debugger/shared/cdbsymbolpathlisteditor.cpp b/src/plugins/debugger/shared/cdbsymbolpathlisteditor.cpp index eb17cba3dec..9f3824ffce5 100644 --- a/src/plugins/debugger/shared/cdbsymbolpathlisteditor.cpp +++ b/src/plugins/debugger/shared/cdbsymbolpathlisteditor.cpp @@ -9,13 +9,13 @@ #include #include -#include #include #include #include #include #include +#include #include #include #include diff --git a/src/plugins/debugger/sourcefileshandler.cpp b/src/plugins/debugger/sourcefileshandler.cpp index 412f550482a..bec03054173 100644 --- a/src/plugins/debugger/sourcefileshandler.cpp +++ b/src/plugins/debugger/sourcefileshandler.cpp @@ -55,8 +55,8 @@ Qt::ItemFlags SourceFilesHandler::flags(const QModelIndex &index) const { if (index.row() >= m_fullNames.size()) return {}; - QFileInfo fi(m_fullNames.at(index.row())); - return fi.isReadable() ? QAbstractItemModel::flags(index) : Qt::ItemFlags({}); + FilePath filePath = m_fullNames.at(index.row()); + return filePath.isReadableFile() ? QAbstractItemModel::flags(index) : Qt::ItemFlags({}); } QVariant SourceFilesHandler::data(const QModelIndex &index, int role) const @@ -75,7 +75,7 @@ QVariant SourceFilesHandler::data(const QModelIndex &index, int role) const break; case 1: if (role == Qt::DisplayRole) - return m_fullNames.at(row); + return m_fullNames.at(row).toUserOutput(); //if (role == Qt::DecorationRole) // return module.symbolsRead ? icon2 : icon; break; @@ -123,13 +123,13 @@ bool SourceFilesHandler::setData(const QModelIndex &idx, const QVariant &data, i return false; } -void SourceFilesHandler::setSourceFiles(const QMap &sourceFiles) +void SourceFilesHandler::setSourceFiles(const QMap &sourceFiles) { beginResetModel(); m_shortNames.clear(); m_fullNames.clear(); - QMap::ConstIterator it = sourceFiles.begin(); - QMap::ConstIterator et = sourceFiles.end(); + auto it = sourceFiles.begin(); + const auto et = sourceFiles.end(); for (; it != et; ++it) { m_shortNames.append(it.key()); m_fullNames.append(it.value()); @@ -139,7 +139,7 @@ void SourceFilesHandler::setSourceFiles(const QMap &sourceFile void SourceFilesHandler::removeAll() { - setSourceFiles(QMap()); + setSourceFiles({}); //header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); } diff --git a/src/plugins/debugger/sourcefileshandler.h b/src/plugins/debugger/sourcefileshandler.h index dabbdbdad0d..d714eda2e36 100644 --- a/src/plugins/debugger/sourcefileshandler.h +++ b/src/plugins/debugger/sourcefileshandler.h @@ -3,6 +3,8 @@ #pragma once +#include + #include #include @@ -29,7 +31,7 @@ public: void clearModel(); - void setSourceFiles(const QMap &sourceFiles); + void setSourceFiles(const QMap &sourceFiles); void removeAll(); QAbstractItemModel *model() { return m_proxyModel; } @@ -37,7 +39,7 @@ public: private: DebuggerEngine *m_engine; QStringList m_shortNames; - QStringList m_fullNames; + Utils::FilePaths m_fullNames; QAbstractItemModel *m_proxyModel; }; diff --git a/src/plugins/debugger/stackframe.cpp b/src/plugins/debugger/stackframe.cpp index 7008900aab2..7ba504d5caf 100644 --- a/src/plugins/debugger/stackframe.cpp +++ b/src/plugins/debugger/stackframe.cpp @@ -74,7 +74,7 @@ StackFrame StackFrame::parseFrame(const GdbMi &frameMi, const DebuggerRunParamet frame.function = frameMi["function"].data(); frame.module = frameMi["module"].data(); const FilePath debugger = rp.debugger.command.executable(); - const FilePath onDevicePath = FilePath::fromUserInput(frameMi["file"].data()).onDevice(debugger); + const FilePath onDevicePath = debugger.withNewPath(frameMi["file"].data()).cleanPath(); frame.file = onDevicePath.localSource().value_or(onDevicePath); frame.line = frameMi["line"].toInt(); frame.address = frameMi["address"].toAddress(); diff --git a/src/plugins/debugger/stackhandler.cpp b/src/plugins/debugger/stackhandler.cpp index 6a2390fcadd..1fc6f04582f 100644 --- a/src/plugins/debugger/stackhandler.cpp +++ b/src/plugins/debugger/stackhandler.cpp @@ -234,7 +234,7 @@ void StackHandler::setFramesAndCurrentIndex(const GdbMi &frames, bool isFull) targetFrame = i; } - bool canExpand = !isFull && (n >= debuggerSettings()->maximalStackDepth.value()); + bool canExpand = !isFull && (n >= debuggerSettings()->maximalStackDepth()); debuggerSettings()->expandStack.setEnabled(canExpand); setFrames(stackFrames, canExpand); diff --git a/src/plugins/debugger/terminal.cpp b/src/plugins/debugger/terminal.cpp index 6707137e853..ac8a043df86 100644 --- a/src/plugins/debugger/terminal.cpp +++ b/src/plugins/debugger/terminal.cpp @@ -11,8 +11,8 @@ #include #include +#include #include -#include #include #include @@ -38,12 +38,6 @@ using namespace Utils; namespace Debugger::Internal { -static QString currentError() -{ - int err = errno; - return QString::fromLatin1(strerror(err)); -} - Terminal::Terminal(QObject *parent) : QObject(parent) { @@ -52,6 +46,10 @@ Terminal::Terminal(QObject *parent) void Terminal::setup() { #ifdef DEBUGGER_USE_TERMINAL + const auto currentError = [] { + int err = errno; + return QString::fromLatin1(strerror(err)); + }; if (!qtcEnvironmentVariableIsSet("QTC_USE_PTY")) return; @@ -174,13 +172,12 @@ void TerminalRunner::start() QTC_ASSERT(!m_stubProc, reportFailure({}); return); Runnable stub = m_stubRunnable(); - m_stubProc = new QtcProcess(this); - m_stubProc->setTerminalMode(HostOsInfo::isWindowsHost() - ? TerminalMode::Suspend : TerminalMode::Debug); + m_stubProc = new Process(this); + m_stubProc->setTerminalMode(TerminalMode::Debug); - connect(m_stubProc, &QtcProcess::started, + connect(m_stubProc, &Process::started, this, &TerminalRunner::stubStarted); - connect(m_stubProc, &QtcProcess::done, + connect(m_stubProc, &Process::done, this, &TerminalRunner::stubDone); m_stubProc->setEnvironment(stub.environment); diff --git a/src/plugins/debugger/terminal.h b/src/plugins/debugger/terminal.h index b4fa1317c59..cd57e9eea21 100644 --- a/src/plugins/debugger/terminal.h +++ b/src/plugins/debugger/terminal.h @@ -8,7 +8,7 @@ #include -namespace Utils { class QtcProcess; } +namespace Utils { class Process; } namespace Debugger { @@ -65,7 +65,7 @@ private: void stubStarted(); void stubDone(); - Utils::QtcProcess *m_stubProc = nullptr; + Utils::Process *m_stubProc = nullptr; std::function m_stubRunnable; qint64 m_applicationPid = 0; qint64 m_applicationMainThreadId = 0; diff --git a/src/plugins/debugger/uvsc/uvscengine.cpp b/src/plugins/debugger/uvsc/uvscengine.cpp index 2bb0bef8e76..06450651de2 100644 --- a/src/plugins/debugger/uvsc/uvscengine.cpp +++ b/src/plugins/debugger/uvsc/uvscengine.cpp @@ -600,7 +600,7 @@ void UvscEngine::handleProjectClosed() Module module; module.startAddress = 0; module.endAddress = 0; - module.modulePath = rp.inferior.command.executable().toString(); + module.modulePath = rp.inferior.command.executable(); module.moduleName = ""; modulesHandler()->updateModule(module); diff --git a/src/plugins/debugger/watchdata.cpp b/src/plugins/debugger/watchdata.cpp index 329e58b88b7..8d0af0e4d31 100644 --- a/src/plugins/debugger/watchdata.cpp +++ b/src/plugins/debugger/watchdata.cpp @@ -216,6 +216,13 @@ public: child->valueEditable = true; item->appendChild(child); } + if (childrenElided) { + auto child = new WatchItem; + child->name = WatchItem::loadMoreName; + child->iname = item->iname + "." + WatchItem::loadMoreName; + child->wantsChildren = true; + item->appendChild(child); + } } void decode() @@ -264,6 +271,7 @@ public: QString rawData; QString childType; DebuggerEncoding encoding; + int childrenElided; quint64 addrbase; quint64 addrstep; Endian endian; @@ -379,6 +387,7 @@ void WatchItem::parseHelper(const GdbMi &input, bool maySort) decoder.item = this; decoder.rawData = mi.data(); decoder.childType = input["childtype"].data(); + decoder.childrenElided = input["childrenelided"].toInt(); decoder.addrbase = input["addrbase"].toAddress(); decoder.addrstep = input["addrstep"].toAddress(); decoder.endian = input["endian"].data() == ">" ? Endian::Big : Endian::Little; @@ -504,6 +513,11 @@ QString WatchItem::toToolTip() const return res; } +bool WatchItem::isLoadMore() const +{ + return name == loadMoreName; +} + bool WatchItem::isLocal() const { if (arrayIndex >= 0) diff --git a/src/plugins/debugger/watchdata.h b/src/plugins/debugger/watchdata.h index 4ef57eacf18..42d046f2a13 100644 --- a/src/plugins/debugger/watchdata.h +++ b/src/plugins/debugger/watchdata.h @@ -36,8 +36,10 @@ public: int editType() const; static const qint64 InvalidId = -1; + constexpr static char loadMoreName[] = ""; void setHasChildren(bool c) { wantsChildren = c; } + bool isLoadMore() const; bool isValid() const { return !iname.isEmpty(); } bool isVTablePointer() const; diff --git a/src/plugins/debugger/watchhandler.cpp b/src/plugins/debugger/watchhandler.cpp index 17ce07bee04..5324c7a8dcb 100644 --- a/src/plugins/debugger/watchhandler.cpp +++ b/src/plugins/debugger/watchhandler.cpp @@ -21,11 +21,10 @@ #include "watchdelegatewidgets.h" #include "watchutils.h" -#include #include +#include #include - -#include +#include #include @@ -40,6 +39,7 @@ #include #include +#include #include #include #include @@ -52,8 +52,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -410,6 +410,7 @@ public: bool hasChildren(const QModelIndex &idx) const override; bool canFetchMore(const QModelIndex &idx) const override; void fetchMore(const QModelIndex &idx) override; + void expand(WatchItem *item, bool requestEngineUpdate); QString displayForAutoTest(const QByteArray &iname) const; void reinitialize(bool includeInspectData = false); @@ -468,6 +469,7 @@ public: SeparatedView *m_separatedView; // Not owned. QSet m_expandedINames; + QHash m_maxArrayCount; QTimer m_requestUpdateTimer; QTimer m_localsWindowsTimer; @@ -1226,7 +1228,8 @@ bool WatchModel::setData(const QModelIndex &idx, const QVariant &value, int role if (value.toBool()) { // Should already have been triggered by fetchMore() //QTC_CHECK(m_expandedINames.contains(item->iname)); - m_expandedINames.insert(item->iname); + if (!item->isLoadMore()) + m_expandedINames.insert(item->iname); } else { m_expandedINames.remove(item->iname); } @@ -1336,13 +1339,23 @@ bool WatchModel::canFetchMore(const QModelIndex &idx) const void WatchModel::fetchMore(const QModelIndex &idx) { - if (!idx.isValid()) - return; + if (idx.isValid()) + expand(nonRootItemForIndex(idx), true); +} - WatchItem *item = nonRootItemForIndex(idx); - if (item) { +void WatchModel::expand(WatchItem *item, bool requestEngineUpdate) +{ + if (!item) + return; + if (item->isLoadMore()) { + item = item->parent(); + m_maxArrayCount[item->iname] + = m_maxArrayCount.value(item->iname, debuggerSettings()->defaultArraySize.value()) * 10; + if (requestEngineUpdate) + m_engine->updateItem(item->iname); + } else { m_expandedINames.insert(item->iname); - if (item->childCount() == 0) + if (requestEngineUpdate && item->childCount() == 0) m_engine->expandItem(item->iname); } } @@ -1765,10 +1778,14 @@ bool WatchModel::contextMenuEvent(const ItemViewEvent &ev) menu->addSeparator(); addAction(this, menu, Tr::tr("Expand All Children"), item, [this, name = item ? item->iname : QString()] { - m_expandedINames.insert(name); - if (auto item = findItem(name)) { - item->forFirstLevelChildren( - [this](WatchItem *child) { m_expandedINames.insert(child->iname); }); + if (name.isEmpty()) + return; + if (WatchItem *item = findItem(name)) { + expand(item, false); + item->forFirstLevelChildren([this](WatchItem *child) { + if (!child->isLoadMore()) + expand(child, false); + }); m_engine->updateLocals(); } }); @@ -2226,7 +2243,7 @@ bool WatchHandler::insertItem(WatchItem *item) void WatchModel::reexpandItems() { - for (const QString &iname : std::as_const(m_expandedINames)) { + for (const QString &iname: m_expandedINames) { if (WatchItem *item = findItem(iname)) { emit itemIsExpanded(indexForItem(item)); emit inameIsExpanded(iname); @@ -2308,7 +2325,8 @@ void WatchHandler::notifyUpdateFinished() m_model->destroyItem(item); m_model->forAllItems([this](WatchItem *item) { - if (item->wantsChildren && isExpandedIName(item->iname)) { + if (item->wantsChildren && isExpandedIName(item->iname) + && item->name != WatchItem::loadMoreName) { // m_model->m_engine->showMessage(QString("ADJUSTING CHILD EXPECTATION FOR " + item->iname)); item->wantsChildren = false; } @@ -2553,11 +2571,12 @@ void WatchModel::clearWatches() if (theWatcherNames.isEmpty()) return; - const QDialogButtonBox::StandardButton ret = CheckableMessageBox::doNotAskAgainQuestion( - ICore::dialogParent(), Tr::tr("Remove All Expression Evaluators"), - Tr::tr("Are you sure you want to remove all expression evaluators?"), - ICore::settings(), "RemoveAllWatchers"); - if (ret != QDialogButtonBox::Yes) + const QMessageBox::StandardButton ret = CheckableMessageBox::question( + ICore::dialogParent(), + Tr::tr("Remove All Expression Evaluators"), + Tr::tr("Are you sure you want to remove all expression evaluators?"), + QString("RemoveAllWatchers")); + if (ret != QMessageBox::Yes) return; m_watchRoot->removeChildren(); @@ -2607,11 +2626,8 @@ const WatchItem *WatchHandler::watchItem(const QModelIndex &idx) const void WatchHandler::fetchMore(const QString &iname) const { - if (WatchItem *item = m_model->findItem(iname)) { - m_model->m_expandedINames.insert(iname); - if (item->childCount() == 0) - m_model->m_engine->expandItem(iname); - } + if (WatchItem *item = m_model->findItem(iname)) + m_model->expand(item, true); } WatchItem *WatchHandler::findItem(const QString &iname) const @@ -2725,9 +2741,9 @@ QString WatchHandler::individualFormatRequests() const void WatchHandler::appendFormatRequests(DebuggerCommand *cmd) const { - QJsonArray expanded; - for (const QString &name : std::as_const(m_model->m_expandedINames)) - expanded.append(name); + QJsonObject expanded; + for (const QString &iname : std::as_const(m_model->m_expandedINames)) + expanded.insert(iname, maxArrayCount(iname)); cmd->arg("expanded", expanded); @@ -2831,6 +2847,11 @@ QSet WatchHandler::expandedINames() const return m_model->m_expandedINames; } +int WatchHandler::maxArrayCount(const QString &iname) const +{ + return m_model->m_maxArrayCount.value(iname, debuggerSettings()->defaultArraySize()); +} + void WatchHandler::recordTypeInfo(const GdbMi &typeInfo) { if (typeInfo.type() == GdbMi::List) { diff --git a/src/plugins/debugger/watchhandler.h b/src/plugins/debugger/watchhandler.h index 0683d73ce94..58a64cea4a4 100644 --- a/src/plugins/debugger/watchhandler.h +++ b/src/plugins/debugger/watchhandler.h @@ -60,6 +60,7 @@ public: bool isExpandedIName(const QString &iname) const; QSet expandedINames() const; + int maxArrayCount(const QString &iname) const; static QStringList watchedExpressions(); static QMap watcherNames(); diff --git a/src/plugins/designer/CMakeLists.txt b/src/plugins/designer/CMakeLists.txt index f6304e4a67d..18b1cf97692 100644 --- a/src/plugins/designer/CMakeLists.txt +++ b/src/plugins/designer/CMakeLists.txt @@ -22,7 +22,7 @@ add_qtc_plugin(Designer formeditorfactory.cpp formeditorfactory.h formeditorplugin.cpp formeditorplugin.h formeditorstack.cpp formeditorstack.h - formeditorw.cpp formeditorw.h + formeditor.cpp formeditor.h formtemplatewizardpage.cpp formtemplatewizardpage.h formwindoweditor.cpp formwindoweditor.h formwindowfile.cpp formwindowfile.h diff --git a/src/plugins/designer/codemodelhelpers.cpp b/src/plugins/designer/codemodelhelpers.cpp index beddb3714e6..8428fd98171 100644 --- a/src/plugins/designer/codemodelhelpers.cpp +++ b/src/plugins/designer/codemodelhelpers.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include @@ -30,7 +30,7 @@ static const char setupUiC[] = "setupUi"; // Find the generated "ui_form.h" header of the form via project. static FilePath generatedHeaderOf(const FilePath &uiFileName) { - if (const Project *uiProject = SessionManager::projectForFile(uiFileName)) { + if (const Project *uiProject = ProjectManager::projectForFile(uiFileName)) { if (Target *t = uiProject->activeTarget()) { if (BuildSystem *bs = t->buildSystem()) { FilePaths files = bs->filesGeneratedFrom(uiFileName); diff --git a/src/plugins/designer/cpp/newclasswidget.cpp b/src/plugins/designer/cpp/newclasswidget.cpp index b659854777b..924700b59c0 100644 --- a/src/plugins/designer/cpp/newclasswidget.cpp +++ b/src/plugins/designer/cpp/newclasswidget.cpp @@ -66,14 +66,15 @@ NewClassWidget::NewClassWidget(QWidget *parent) : setNamesDelimiter(QLatin1String("::")); - using namespace Utils::Layouting; + using namespace Layouting; Form { Tr::tr("&Class name:"), d->m_classLineEdit, br, Tr::tr("&Header file:"), d->m_headerFileLineEdit, br, Tr::tr("&Source file:"), d->m_sourceFileLineEdit, br, Tr::tr("&Form file:"), d->m_formFileLineEdit, br, Tr::tr("&Path:"), d->m_pathChooser, br, - }.attachTo(this, WithoutMargins); + noMargin + }.attachTo(this); connect(d->m_classLineEdit, &ClassNameValidatingLineEdit::updateFileName, this, &NewClassWidget::slotUpdateFileNames); diff --git a/src/plugins/designer/designer.qbs b/src/plugins/designer/designer.qbs index 411be43283b..8b97a494a7c 100644 --- a/src/plugins/designer/designer.qbs +++ b/src/plugins/designer/designer.qbs @@ -41,7 +41,7 @@ QtcPlugin { "formeditorfactory.cpp", "formeditorfactory.h", "formeditorplugin.cpp", "formeditorplugin.h", "formeditorstack.cpp", "formeditorstack.h", - "formeditorw.cpp", "formeditorw.h", + "formeditor.cpp", "formeditor.h", "formtemplatewizardpage.cpp", "formtemplatewizardpage.h", "formwindoweditor.cpp", "formwindoweditor.h", "formwindowfile.cpp", "formwindowfile.h", @@ -77,9 +77,7 @@ QtcPlugin { ] } - Group { - name: "Tests" - condition: qtc.testsEnabled + QtcTestFiles { files: [ "gotoslot_test.cpp" ] cpp.defines: outer.concat(['SRCDIR="' + FileInfo.path(filePath) + '"']) diff --git a/src/plugins/designer/designercontext.cpp b/src/plugins/designer/designercontext.cpp index de65df8a207..266a347bbb2 100644 --- a/src/plugins/designer/designercontext.cpp +++ b/src/plugins/designer/designercontext.cpp @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "designercontext.h" -#include "formeditorw.h" +#include "formeditor.h" #include #include @@ -25,7 +25,7 @@ DesignerContext::DesignerContext(const Core::Context &context, void DesignerContext::contextHelp(const HelpCallback &callback) const { - const QDesignerFormEditorInterface *core = FormEditorW::designerEditor(); + const QDesignerFormEditorInterface *core = designerEditor(); callback(core->integration()->contextHelpId()); } diff --git a/src/plugins/designer/editorwidget.cpp b/src/plugins/designer/editorwidget.cpp index f1b1772fc2f..664e953b3bc 100644 --- a/src/plugins/designer/editorwidget.cpp +++ b/src/plugins/designer/editorwidget.cpp @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "editorwidget.h" -#include "formeditorw.h" +#include "formeditor.h" #include "formeditorstack.h" #include @@ -28,7 +28,7 @@ EditorWidget::EditorWidget(QWidget *parent) : setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); - QWidget * const*subs = FormEditorW::designerSubWindows(); + QWidget * const * subs = designerSubWindows(); for (int i = 0; i < DesignerSubWindowCount; i++) { QWidget *subWindow = subs[i]; subWindow->setWindowTitle(subs[i]->windowTitle()); diff --git a/src/plugins/designer/formeditorw.cpp b/src/plugins/designer/formeditor.cpp similarity index 96% rename from src/plugins/designer/formeditorw.cpp rename to src/plugins/designer/formeditor.cpp index a55b93d71fe..96e65796500 100644 --- a/src/plugins/designer/formeditorw.cpp +++ b/src/plugins/designer/formeditor.cpp @@ -5,7 +5,7 @@ #include "designertr.h" #include "editordata.h" #include "editorwidget.h" -#include "formeditorw.h" +#include "formeditor.h" #include "formwindoweditor.h" #include "formwindowfile.h" #include "qtcreatorintegration.h" @@ -123,9 +123,9 @@ public: } }; -// --------- FormEditorW +// FormEditorData -class FormEditorData +class FormEditorData : public QObject { public: FormEditorData(); @@ -173,7 +173,7 @@ public: QDesignerFormEditorInterface *m_formeditor = nullptr; QtCreatorIntegration *m_integration = nullptr; QDesignerFormWindowManagerInterface *m_fwm = nullptr; - FormEditorW::InitializationStage m_initStage = FormEditorW::RegisterPlugins; + InitializationStage m_initStage = RegisterPlugins; QWidget *m_designerSubWindows[DesignerSubWindowCount]; @@ -202,7 +202,6 @@ public: }; static FormEditorData *d = nullptr; -static FormEditorW *m_instance = nullptr; FormEditorData::FormEditorData() : m_formeditor(QDesignerComponents::createFormEditor(nullptr)) @@ -238,7 +237,7 @@ FormEditorData::FormEditorData() : if (editor && editor->document()->id() == Constants::K_DESIGNER_XML_EDITOR_ID) { FormWindowEditor *xmlEditor = qobject_cast(editor); QTC_ASSERT(xmlEditor, return); - FormEditorW::ensureInitStage(FormEditorW::FullyInitialized); + ensureInitStage(FullyInitialized); SharedTools::WidgetHost *fw = m_editorWidget->formWindowEditorForXmlEditor(xmlEditor); QTC_ASSERT(fw, return); m_editorWidget->setVisibleEditor(xmlEditor); @@ -251,7 +250,7 @@ FormEditorData::FormEditorData() : FormEditorData::~FormEditorData() { - if (m_initStage == FormEditorW::FullyInitialized) { + if (m_initStage == FullyInitialized) { QSettings *s = ICore::settings(); s->beginGroup(settingsGroupC); m_editorWidget->saveSettings(s); @@ -324,18 +323,18 @@ void FormEditorData::setupViewActions() void FormEditorData::fullInit() { - QTC_ASSERT(m_initStage == FormEditorW::RegisterPlugins, return); + QTC_ASSERT(m_initStage == RegisterPlugins, return); QElapsedTimer *initTime = nullptr; if (Designer::Constants::Internal::debug) { initTime = new QElapsedTimer; initTime->start(); } - QDesignerComponents::createTaskMenu(m_formeditor, m_instance); + QDesignerComponents::createTaskMenu(m_formeditor, this); QDesignerComponents::initializePlugins(m_formeditor); QDesignerComponents::initializeResources(); initDesignerSubWindows(); - m_integration = new QtCreatorIntegration(m_formeditor, m_instance); + m_integration = new QtCreatorIntegration(m_formeditor, this); m_formeditor->setIntegration(m_integration); // Connect Qt Designer help request to HelpManager. QObject::connect(m_integration, &QtCreatorIntegration::creatorHelpRequested, @@ -397,13 +396,13 @@ void FormEditorData::fullInit() Context designerContexts = m_contexts; designerContexts.add(Core::Constants::C_EDITORMANAGER); - ICore::addContextObject(new DesignerContext(designerContexts, m_modeWidget, m_instance)); + ICore::addContextObject(new DesignerContext(designerContexts, m_modeWidget, this)); DesignMode::registerDesignWidget(m_modeWidget, QStringList(FORM_MIMETYPE), m_contexts); setupViewActions(); - m_initStage = FormEditorW::FullyInitialized; + m_initStage = FullyInitialized; } void FormEditorData::initDesignerSubWindows() @@ -438,22 +437,21 @@ void FormEditorData::initDesignerSubWindows() ae->setObjectName("ActionEditor"); m_formeditor->setActionEditor(ae); m_designerSubWindows[ActionEditorSubWindow] = ae; - m_initStage = FormEditorW::SubwindowsInitialized; + m_initStage = SubwindowsInitialized; } -QList FormEditorW::optionsPages() +QList optionsPages() { return d->m_settingsPages; } -void FormEditorW::ensureInitStage(InitializationStage s) +void ensureInitStage(InitializationStage s) { if (Designer::Constants::Internal::debug) qDebug() << Q_FUNC_INFO << s; - if (!d) { - m_instance = new FormEditorW; + if (!d) d = new FormEditorData; - } + if (d->m_initStage >= s) return; QApplication::setOverrideCursor(Qt::WaitCursor); @@ -461,15 +459,13 @@ void FormEditorW::ensureInitStage(InitializationStage s) QApplication::restoreOverrideCursor(); } -void FormEditorW::deleteInstance() +void deleteInstance() { delete d; d = nullptr; - delete m_instance; - m_instance = nullptr; } -IEditor *FormEditorW::createEditor() +IEditor *createEditor() { ensureInitStage(FullyInitialized); return d->createEditor(); @@ -489,7 +485,7 @@ void FormEditorData::setupActions() bindShortcut(ActionManager::registerAction(m_fwm->actionPaste(), Core::Constants::PASTE, m_contexts), m_fwm->actionPaste()); bindShortcut(ActionManager::registerAction(m_fwm->actionSelectAll(), Core::Constants::SELECTALL, m_contexts), m_fwm->actionSelectAll()); - m_actionPrint = new QAction(m_instance); + m_actionPrint = new QAction(this); bindShortcut(ActionManager::registerAction(m_actionPrint, Core::Constants::PRINT, m_contexts), m_actionPrint); QObject::connect(m_actionPrint, &QAction::triggered, [this]() { print(); }); @@ -502,7 +498,7 @@ void FormEditorData::setupActions() command->setAttribute(Command::CA_Hide); medit->addAction(command, Core::Constants::G_EDIT_COPYPASTE); - m_actionGroupEditMode = new QActionGroup(m_instance); + m_actionGroupEditMode = new QActionGroup(this); m_actionGroupEditMode->setExclusive(true); QObject::connect(m_actionGroupEditMode, &QActionGroup::triggered, [this](QAction *a) { activateEditMode(a->data().toInt()); }); @@ -601,7 +597,7 @@ void FormEditorData::setupActions() QString(), Core::Constants::G_DEFAULT_THREE); mformtools->addSeparator(m_contexts, Core::Constants::G_DEFAULT_THREE); - m_actionAboutPlugins = new QAction(Tr::tr("About Qt Designer Plugins..."), m_instance); + m_actionAboutPlugins = new QAction(Tr::tr("About Qt Designer Plugins..."), d); addToolAction(m_actionAboutPlugins, m_contexts, "FormEditor.AboutPlugins", mformtools, QString(), Core::Constants::G_DEFAULT_THREE); QObject::connect(m_actionAboutPlugins, &QAction::triggered, @@ -760,19 +756,19 @@ IEditor *FormEditorData::createEditor() return formWindowEditor; } -QDesignerFormEditorInterface *FormEditorW::designerEditor() +QDesignerFormEditorInterface *designerEditor() { ensureInitStage(FullyInitialized); return d->m_formeditor; } -QWidget * const *FormEditorW::designerSubWindows() +QWidget * const *designerSubWindows() { ensureInitStage(SubwindowsInitialized); return d->m_designerSubWindows; } -SharedTools::WidgetHost *FormEditorW::activeWidgetHost() +SharedTools::WidgetHost *activeWidgetHost() { ensureInitStage(FullyInitialized); if (d->m_editorWidget) @@ -780,7 +776,7 @@ SharedTools::WidgetHost *FormEditorW::activeWidgetHost() return nullptr; } -FormWindowEditor *FormEditorW::activeEditor() +FormWindowEditor *activeEditor() { ensureInitStage(FullyInitialized); if (d->m_editorWidget) diff --git a/src/plugins/designer/formeditor.h b/src/plugins/designer/formeditor.h new file mode 100644 index 00000000000..0d069e2322c --- /dev/null +++ b/src/plugins/designer/formeditor.h @@ -0,0 +1,64 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +QT_BEGIN_NAMESPACE +class QDesignerFormEditorInterface; +class QWidget; +QT_END_NAMESPACE + +namespace Core { +class IEditor; +class IOptionsPage; +} + +namespace SharedTools { class WidgetHost; } + +namespace Designer { + +class FormWindowEditor; + +namespace Internal { + +/** This is an interface to the Designer CoreInterface to + * performs centralized operations. + * Since fully initializing Designer at startup is expensive, the + * setup has an internal partial initialization stage "RegisterPlugins" + * which is there to register the Creator plugin objects + * that must be present at startup (settings pages, actions). + * The plugin uses this stage at first by calling ensureInitStage(). + * Requesting an editor via instance() will fully initialize the class. + * This is based on the assumption that the Designer settings work with + * no plugins loaded. + * + * The form editor shows a read-only XML editor in edit mode and Qt Designer + * in Design mode. */ + +enum InitializationStage { + // Register Creator plugins (settings pages, actions) + RegisterPlugins, + // Subwindows of the designer are initialized + SubwindowsInitialized, + // Fully initialized for handling editor requests + FullyInitialized +}; + +// Create an instance and initialize up to stage s +void ensureInitStage(InitializationStage s); +// Deletes an existing instance if there is one. +void deleteInstance(); + +Core::IEditor *createEditor(); + +QDesignerFormEditorInterface *designerEditor(); +QWidget * const *designerSubWindows(); + +SharedTools::WidgetHost *activeWidgetHost(); +FormWindowEditor *activeEditor(); +QList optionsPages(); + +} // namespace Internal +} // namespace Designer diff --git a/src/plugins/designer/formeditorfactory.cpp b/src/plugins/designer/formeditorfactory.cpp index beace0702ed..278cb065f40 100644 --- a/src/plugins/designer/formeditorfactory.cpp +++ b/src/plugins/designer/formeditorfactory.cpp @@ -1,18 +1,16 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "designertr.h" #include "formeditorfactory.h" -#include "formeditorw.h" -#include "formwindoweditor.h" + +#include "designerconstants.h" +#include "designertr.h" +#include "formeditor.h" #include #include #include -#include -#include - using namespace Core; using namespace Designer::Constants; using namespace Utils; @@ -25,7 +23,7 @@ FormEditorFactory::FormEditorFactory() setId(K_DESIGNER_XML_EDITOR_ID); setDisplayName(Tr::tr(C_DESIGNER_XML_DISPLAY_NAME)); addMimeType(FORM_MIMETYPE); - setEditorCreator([] { return FormEditorW::createEditor(); }); + setEditorCreator([] { return Designer::Internal::createEditor(); }); FileIconProvider::registerIconOverlayForSuffix(ProjectExplorer::Constants::FILEOVERLAY_UI, "ui"); } diff --git a/src/plugins/designer/formeditorplugin.cpp b/src/plugins/designer/formeditorplugin.cpp index 8d26f27f0fb..881e6a30eb5 100644 --- a/src/plugins/designer/formeditorplugin.cpp +++ b/src/plugins/designer/formeditorplugin.cpp @@ -1,10 +1,12 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "formeditorplugin.h" + +#include "designerconstants.h" #include "designertr.h" #include "formeditorfactory.h" -#include "formeditorplugin.h" -#include "formeditorw.h" +#include "formeditor.h" #include "formtemplatewizardpage.h" #ifdef CPP_ENABLED @@ -54,7 +56,7 @@ public: FormEditorPlugin::~FormEditorPlugin() { - FormEditorW::deleteInstance(); + deleteInstance(); delete d; } diff --git a/src/plugins/designer/formeditorstack.cpp b/src/plugins/designer/formeditorstack.cpp index 9325827f878..1bccd341784 100644 --- a/src/plugins/designer/formeditorstack.cpp +++ b/src/plugins/designer/formeditorstack.cpp @@ -2,8 +2,10 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "formeditorstack.h" + +#include "designerconstants.h" #include "formwindoweditor.h" -#include "formeditorw.h" +#include "formeditor.h" #include "formwindowfile.h" #include diff --git a/src/plugins/designer/formeditorw.h b/src/plugins/designer/formeditorw.h deleted file mode 100644 index 50462c2aa18..00000000000 --- a/src/plugins/designer/formeditorw.h +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "designerconstants.h" - -#include - -QT_BEGIN_NAMESPACE -class QDesignerFormEditorInterface; -QT_END_NAMESPACE - -namespace Core { -class IEditor; -class IOptionsPage; -} - -namespace SharedTools { class WidgetHost; } - -namespace Designer { - -class FormWindowEditor; - -namespace Internal { - -/** FormEditorW is a singleton that stores the Designer CoreInterface and - * performs centralized operations. The instance() function will return an - * instance. However, it must be manually deleted when unloading the - * plugin. Since fully initializing Designer at startup is expensive, the - * class has an internal partial initialisation stage "RegisterPlugins" - * which is there to register the Creator plugin objects - * that must be present at startup (settings pages, actions). - * The plugin uses this stage at first by calling ensureInitStage(). - * Requesting an editor via instance() will fully initialize the class. - * This is based on the assumption that the Designer settings work with - * no plugins loaded. - * - * The form editor shows a read-only XML editor in edit mode and Qt Designer - * in Design mode. */ -class FormEditorW : public QObject -{ -public: - enum InitializationStage { - // Register Creator plugins (settings pages, actions) - RegisterPlugins, - // Subwindows of the designer are initialized - SubwindowsInitialized, - // Fully initialized for handling editor requests - FullyInitialized - }; - - // Create an instance and initialize up to stage s - static void ensureInitStage(InitializationStage s); - // Deletes an existing instance if there is one. - static void deleteInstance(); - - static Core::IEditor *createEditor(); - - static QDesignerFormEditorInterface *designerEditor(); - static QWidget * const *designerSubWindows(); - - static SharedTools::WidgetHost *activeWidgetHost(); - static FormWindowEditor *activeEditor(); - static QList optionsPages(); -}; - -} // namespace Internal -} // namespace Designer diff --git a/src/plugins/designer/formtemplatewizardpage.cpp b/src/plugins/designer/formtemplatewizardpage.cpp index c6417522f44..68e3fb17111 100644 --- a/src/plugins/designer/formtemplatewizardpage.cpp +++ b/src/plugins/designer/formtemplatewizardpage.cpp @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "designertr.h" -#include "formeditorw.h" +#include "formeditor.h" #include "formtemplatewizardpage.h" #include @@ -58,7 +58,7 @@ bool FormPageFactory::validateData(Utils::Id typeId, const QVariant &data, QStri FormTemplateWizardPage::FormTemplateWizardPage(QWidget * parent) : Utils::WizardPage(parent), - m_newFormWidget(QDesignerNewFormWidgetInterface::createNewFormWidget(FormEditorW::designerEditor())), + m_newFormWidget(QDesignerNewFormWidgetInterface::createNewFormWidget(designerEditor())), m_templateSelected(m_newFormWidget->hasCurrentTemplate()) { setTitle(Tr::tr("Choose a Form Template")); diff --git a/src/plugins/designer/gotoslot_test.cpp b/src/plugins/designer/gotoslot_test.cpp index f4562a41e79..f804c2259e3 100644 --- a/src/plugins/designer/gotoslot_test.cpp +++ b/src/plugins/designer/gotoslot_test.cpp @@ -3,7 +3,7 @@ #include "formeditorplugin.h" -#include "formeditorw.h" +#include "formeditor.h" #include #include @@ -147,7 +147,7 @@ public: waitForFilesInGlobalSnapshot({cppFile, hFile}); // Execute "Go To Slot" - QDesignerIntegrationInterface *integration = FormEditorW::designerEditor()->integration(); + QDesignerIntegrationInterface *integration = designerEditor()->integration(); QVERIFY(integration); integration->emitNavigateToSlot("pushButton", "clicked()", QStringList()); diff --git a/src/plugins/designer/qtcreatorintegration.cpp b/src/plugins/designer/qtcreatorintegration.cpp index db96523c360..010ab85a82b 100644 --- a/src/plugins/designer/qtcreatorintegration.cpp +++ b/src/plugins/designer/qtcreatorintegration.cpp @@ -1,10 +1,13 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "designertr.h" -#include "formeditorw.h" -#include "formwindoweditor.h" #include "qtcreatorintegration.h" + +#include "designerconstants.h" +#include "designertr.h" +#include "formeditor.h" +#include "formwindoweditor.h" + #include #include @@ -16,20 +19,24 @@ #include #include #include + #include #include + #include #include #include -#include -#include + #include #include #include +#include #include -#include #include +#include +#include + #include #include #include @@ -152,14 +159,14 @@ void QtCreatorIntegration::slotDesignerHelpRequested(const QString &manual, cons void QtCreatorIntegration::updateSelection() { - if (SharedTools::WidgetHost *host = FormEditorW::activeWidgetHost()) + if (SharedTools::WidgetHost *host = activeWidgetHost()) host->updateFormWindowSelectionHandles(true); QDesignerIntegration::updateSelection(); } QWidget *QtCreatorIntegration::containerWindow(QWidget * /*widget*/) const { - if (SharedTools::WidgetHost *host = FormEditorW::activeWidgetHost()) + if (SharedTools::WidgetHost *host = activeWidgetHost()) return host->integrationContainer(); return nullptr; } @@ -431,7 +438,7 @@ void QtCreatorIntegration::slotNavigateToSlot(const QString &objectName, const Q { QString errorMessage; if (!navigateToSlot(objectName, signalSignature, parameterNames, &errorMessage) && !errorMessage.isEmpty()) - QMessageBox::warning(FormEditorW::designerEditor()->topLevel(), Tr::tr("Error finding/adding a slot."), errorMessage); + QMessageBox::warning(designerEditor()->topLevel(), Tr::tr("Error finding/adding a slot."), errorMessage); } // Build name of the class as generated by uic, insert Ui namespace @@ -452,8 +459,8 @@ static Document::Ptr getParsedDocument(const FilePath &filePath, Snapshot &snapshot) { QByteArray src; - if (workingCopy.contains(filePath)) { - src = workingCopy.source(filePath); + if (const auto source = workingCopy.source(filePath)) { + src = *source; } else { Utils::FileReader reader; if (reader.fetch(filePath)) // ### FIXME error reporting @@ -476,7 +483,7 @@ bool QtCreatorIntegration::navigateToSlot(const QString &objectName, { using DocumentMap = QMap; - const Utils::FilePath currentUiFile = FormEditorW::activeEditor()->document()->filePath(); + const Utils::FilePath currentUiFile = activeEditor()->document()->filePath(); #if 0 return Designer::Internal::navigateToSlot(currentUiFile.toString(), objectName, signalSignature, parameterNames, errorMessage); @@ -493,10 +500,10 @@ bool QtCreatorIntegration::navigateToSlot(const QString &objectName, // Retrieve code model snapshot restricted to project of ui file or the working copy. Snapshot docTable = CppEditor::CppModelManager::instance()->snapshot(); Snapshot newDocTable; - const Project *uiProject = SessionManager::projectForFile(currentUiFile); + const Project *uiProject = ProjectManager::projectForFile(currentUiFile); if (uiProject) { for (Snapshot::const_iterator i = docTable.begin(), ei = docTable.end(); i != ei; ++i) { - const Project *project = SessionManager::projectForFile(i.key()); + const Project *project = ProjectManager::projectForFile(i.key()); if (project == uiProject) newDocTable.insert(i.value()); } @@ -529,7 +536,7 @@ bool QtCreatorIntegration::navigateToSlot(const QString &objectName, return false; } - QDesignerFormWindowInterface *fwi = FormEditorW::activeWidgetHost()->formWindow(); + QDesignerFormWindowInterface *fwi = activeWidgetHost()->formWindow(); QString uiClass; const Class *cl = nullptr; @@ -639,7 +646,7 @@ void QtCreatorIntegration::handleSymbolRenameStage1( return; // Get ExtraCompiler. - const Project * const project = SessionManager::projectForFile(uiFile); + const Project * const project = ProjectManager::projectForFile(uiFile); if (!project) { return reportRenamingError(oldName, Designer::Tr::tr("File \"%1\" not found in project.") .arg(uiFile.toUserOutput())); diff --git a/src/plugins/designer/resourcehandler.cpp b/src/plugins/designer/resourcehandler.cpp index 29838283d5c..fe2cdd28c60 100644 --- a/src/plugins/designer/resourcehandler.cpp +++ b/src/plugins/designer/resourcehandler.cpp @@ -2,13 +2,16 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "resourcehandler.h" + #include "designerconstants.h" -#include -#include #include -#include +#include +#include +#include + #include + #include #include @@ -42,10 +45,10 @@ void ResourceHandler::ensureInitialized() Qt::QueuedConnection); }; - for (Project *p : SessionManager::projects()) + for (Project *p : ProjectManager::projects()) connector(p); - connect(SessionManager::instance(), &SessionManager::projectAdded, this, connector); + connect(ProjectManager::instance(), &ProjectManager::projectAdded, this, connector); m_originalUiQrcPaths = m_form->activeResourceFilePaths(); if (Designer::Constants::Internal::debug) @@ -68,7 +71,7 @@ void ResourceHandler::updateResourcesHelper(bool updateProjectResources) qDebug() << "ResourceHandler::updateResources()" << fileName; // Filename could change in the meantime. - Project *project = SessionManager::projectForFile(Utils::FilePath::fromUserInput(fileName)); + Project *project = ProjectManager::projectForFile(Utils::FilePath::fromUserInput(fileName)); const bool dirty = m_form->property("_q_resourcepathchanged").toBool(); if (dirty) m_form->setDirty(true); diff --git a/src/plugins/designer/settingspage.cpp b/src/plugins/designer/settingspage.cpp index a3e3ce3808f..e1998ffd5e3 100644 --- a/src/plugins/designer/settingspage.cpp +++ b/src/plugins/designer/settingspage.cpp @@ -1,48 +1,46 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "designertr.h" -#include "formeditorw.h" #include "settingspage.h" +#include "designerconstants.h" +#include "designertr.h" +#include "formeditor.h" + #include #include #include #include +#include -using namespace Designer::Internal; +namespace Designer::Internal { -SettingsPage::SettingsPage(QDesignerOptionsPageInterface *designerPage) : - Core::IOptionsPage(nullptr, false), - m_designerPage(designerPage) +class SettingsPageWidget : public Core::IOptionsPageWidget { - setId(Utils::Id::fromString(m_designerPage->name())); - setDisplayName(m_designerPage->name()); +public: + explicit SettingsPageWidget(QDesignerOptionsPageInterface *designerPage) + : m_designerPage(designerPage) + { + auto vbox = new QVBoxLayout(this); + vbox->addWidget(m_designerPage->createPage(nullptr)); + } + + void apply() { m_designerPage->apply(); } + void finish() { m_designerPage->finish(); } + + QDesignerOptionsPageInterface *m_designerPage; +}; + + +SettingsPage::SettingsPage(QDesignerOptionsPageInterface *designerPage) + : Core::IOptionsPage(false) +{ + setId(Utils::Id::fromString(designerPage->name())); + setDisplayName(designerPage->name()); setCategory(Designer::Constants::SETTINGS_CATEGORY); -} - -QWidget *SettingsPage::widget() -{ - m_initialized = true; - if (!m_widget) - m_widget = m_designerPage->createPage(nullptr); - return m_widget; - -} - -void SettingsPage::apply() -{ - if (m_initialized) - m_designerPage->apply(); -} - -void SettingsPage::finish() -{ - if (m_initialized) - m_designerPage->finish(); - delete m_widget; + setWidgetCreator([designerPage] { return new SettingsPageWidget(designerPage); }); } SettingsPageProvider::SettingsPageProvider() @@ -58,9 +56,9 @@ QList SettingsPageProvider::pages() const if (!m_initialized) { // get options pages from designer m_initialized = true; - FormEditorW::ensureInitStage(FormEditorW::RegisterPlugins); + ensureInitStage(RegisterPlugins); } - return FormEditorW::optionsPages(); + return optionsPages(); } bool SettingsPageProvider::matches(const QRegularExpression ®ex) const @@ -102,3 +100,5 @@ bool SettingsPageProvider::matches(const QRegularExpression ®ex) const } return false; } + +} // Designer::Internal diff --git a/src/plugins/designer/settingspage.h b/src/plugins/designer/settingspage.h index cdfb32c003c..03206109dba 100644 --- a/src/plugins/designer/settingspage.h +++ b/src/plugins/designer/settingspage.h @@ -5,38 +5,20 @@ #include -#include - QT_BEGIN_NAMESPACE class QDesignerOptionsPageInterface; QT_END_NAMESPACE -namespace Designer { -namespace Internal { - -class SettingsPageWidget; +namespace Designer::Internal { class SettingsPage : public Core::IOptionsPage { - Q_OBJECT - public: explicit SettingsPage(QDesignerOptionsPageInterface *designerPage); - - QWidget *widget() override; - void apply() override; - void finish() override; - -private: - QDesignerOptionsPageInterface *m_designerPage; - bool m_initialized = false; - QPointer m_widget; }; class SettingsPageProvider : public Core::IOptionsPageProvider { - Q_OBJECT - public: SettingsPageProvider(); @@ -48,5 +30,4 @@ private: mutable QStringList m_keywords; }; -} // namespace Internal -} // namespace Designer +} // Designer::Internal diff --git a/src/plugins/diffeditor/diffeditor.cpp b/src/plugins/diffeditor/diffeditor.cpp index 5274214f92b..929c30c3398 100644 --- a/src/plugins/diffeditor/diffeditor.cpp +++ b/src/plugins/diffeditor/diffeditor.cpp @@ -185,7 +185,7 @@ DiffEditor::DiffEditor() policy.setHorizontalPolicy(QSizePolicy::Expanding); m_entriesComboBox->setSizePolicy(policy); connect(m_entriesComboBox, &QComboBox::currentIndexChanged, - this, &DiffEditor::setCurrentDiffFileIndex); + this, &DiffEditor::currentIndexChanged); m_toolBar->addWidget(m_entriesComboBox); QLabel *contextLabel = new QLabel(m_toolBar); @@ -309,7 +309,6 @@ TextEditorWidget *DiffEditor::sideEditorWidget(DiffSide side) const return m_sideBySideView->sideEditorWidget(side); } - void DiffEditor::documentHasChanged() { GuardLocker guard(m_ignoreChanges); @@ -319,7 +318,10 @@ void DiffEditor::documentHasChanged() currentView()->setDiff(diffFileList); m_entriesComboBox->clear(); - for (const FileData &diffFile : diffFileList) { + const QString startupFile = m_document->startupFile(); + int startupFileIndex = -1; + for (int i = 0, total = diffFileList.count(); i < total; ++i) { + const FileData &diffFile = diffFileList.at(i); const DiffFileInfo &leftEntry = diffFile.fileInfo[LeftSide]; const DiffFileInfo &rightEntry = diffFile.fileInfo[RightSide]; const QString leftShortFileName = FilePath::fromString(leftEntry.fileName).fileName(); @@ -332,30 +334,20 @@ void DiffEditor::documentHasChanged() if (leftEntry.typeInfo.isEmpty() && rightEntry.typeInfo.isEmpty()) { itemToolTip = leftEntry.fileName; } else { - itemToolTip = Tr::tr("[%1] vs. [%2] %3") - .arg(leftEntry.typeInfo, - rightEntry.typeInfo, - leftEntry.fileName); + itemToolTip = Tr::tr("[%1] vs. [%2] %3").arg( + leftEntry.typeInfo, rightEntry.typeInfo, leftEntry.fileName); } } else { - if (leftShortFileName == rightShortFileName) { + if (leftShortFileName == rightShortFileName) itemText = leftShortFileName; - } else { - itemText = Tr::tr("%1 vs. %2") - .arg(leftShortFileName, - rightShortFileName); - } + else + itemText = Tr::tr("%1 vs. %2").arg(leftShortFileName, rightShortFileName); if (leftEntry.typeInfo.isEmpty() && rightEntry.typeInfo.isEmpty()) { - itemToolTip = Tr::tr("%1 vs. %2") - .arg(leftEntry.fileName, - rightEntry.fileName); + itemToolTip = Tr::tr("%1 vs. %2").arg(leftEntry.fileName, rightEntry.fileName); } else { - itemToolTip = Tr::tr("[%1] %2 vs. [%3] %4") - .arg(leftEntry.typeInfo, - leftEntry.fileName, - rightEntry.typeInfo, - rightEntry.fileName); + itemToolTip = Tr::tr("[%1] %2 vs. [%3] %4").arg(leftEntry.typeInfo, + leftEntry.fileName, rightEntry.typeInfo, rightEntry.fileName); } } m_entriesComboBox->addItem(itemText); @@ -365,7 +357,21 @@ void DiffEditor::documentHasChanged() rightEntry.fileName, Qt::UserRole + 1); m_entriesComboBox->setItemData(m_entriesComboBox->count() - 1, itemToolTip, Qt::ToolTipRole); + if (startupFileIndex < 0) { + const bool isStartup = m_currentFileChunk.first.isEmpty() + && m_currentFileChunk.second.isEmpty() + && startupFile.endsWith(rightEntry.fileName); + const bool isSame = m_currentFileChunk.first == leftEntry.fileName + && m_currentFileChunk.second == rightEntry.fileName; + if (isStartup || isSame) + startupFileIndex = i; + } } + + currentView()->endOperation(); + m_currentFileChunk = {}; + if (startupFileIndex >= 0) + setCurrentDiffFileIndex(startupFileIndex); } void DiffEditor::toggleDescription() @@ -439,6 +445,7 @@ void DiffEditor::prepareForReload() m_whitespaceButtonAction->setChecked(m_document->ignoreWhitespace()); } currentView()->beginOperation(); + currentView()->setMessage(Tr::tr("Waiting for data...")); } void DiffEditor::reloadHasFinished(bool success) @@ -446,29 +453,8 @@ void DiffEditor::reloadHasFinished(bool success) if (!currentView()) return; - currentView()->endOperation(success); - - int index = -1; - const QString startupFile = m_document->startupFile(); - const QList &diffFileList = m_document->diffFiles(); - const int count = diffFileList.count(); - for (int i = 0; i < count; i++) { - const FileData &diffFile = diffFileList.at(i); - const DiffFileInfo &leftEntry = diffFile.fileInfo[LeftSide]; - const DiffFileInfo &rightEntry = diffFile.fileInfo[RightSide]; - if ((m_currentFileChunk.first.isEmpty() - && m_currentFileChunk.second.isEmpty() - && startupFile.endsWith(rightEntry.fileName)) - || (m_currentFileChunk.first == leftEntry.fileName - && m_currentFileChunk.second == rightEntry.fileName)) { - index = i; - break; - } - } - - m_currentFileChunk = {}; - if (index >= 0) - setCurrentDiffFileIndex(index); + if (!success) + currentView()->setMessage(Tr::tr("Retrieving data failed.")); } void DiffEditor::updateEntryToolTip() @@ -478,14 +464,19 @@ void DiffEditor::updateEntryToolTip() m_entriesComboBox->setToolTip(toolTip); } -void DiffEditor::setCurrentDiffFileIndex(int index) +void DiffEditor::currentIndexChanged(int index) { if (m_ignoreChanges.isLocked()) return; + GuardLocker guard(m_ignoreChanges); + setCurrentDiffFileIndex(index); +} + +void DiffEditor::setCurrentDiffFileIndex(int index) +{ QTC_ASSERT((index < 0) != (m_entriesComboBox->count() > 0), return); - GuardLocker guard(m_ignoreChanges); m_currentDiffFileIndex = index; currentView()->setCurrentDiffFileIndex(index); @@ -563,7 +554,7 @@ void DiffEditor::addView(IDiffView *view) if (m_views.count() == 1) setCurrentView(view); - connect(view, &IDiffView::currentDiffFileIndexChanged, this, &DiffEditor::setCurrentDiffFileIndex); + connect(view, &IDiffView::currentDiffFileIndexChanged, this, &DiffEditor::currentIndexChanged); } IDiffView *DiffEditor::currentView() const diff --git a/src/plugins/diffeditor/diffeditor.h b/src/plugins/diffeditor/diffeditor.h index a801ee953b2..49922d323eb 100644 --- a/src/plugins/diffeditor/diffeditor.h +++ b/src/plugins/diffeditor/diffeditor.h @@ -53,6 +53,7 @@ private: void ignoreWhitespaceHasChanged(); void prepareForReload(); void reloadHasFinished(bool success); + void currentIndexChanged(int index); void setCurrentDiffFileIndex(int index); void documentStateChanged(); diff --git a/src/plugins/diffeditor/diffeditorcontroller.cpp b/src/plugins/diffeditor/diffeditorcontroller.cpp index dcce09f0b2b..1ab7bfccb2d 100644 --- a/src/plugins/diffeditor/diffeditorcontroller.cpp +++ b/src/plugins/diffeditor/diffeditorcontroller.cpp @@ -12,6 +12,7 @@ #include using namespace Core; +using namespace Tasking; using namespace Utils; namespace DiffEditor { diff --git a/src/plugins/diffeditor/diffeditorcontroller.h b/src/plugins/diffeditor/diffeditorcontroller.h index f52f2af1a0a..1f791b2dcb3 100644 --- a/src/plugins/diffeditor/diffeditorcontroller.h +++ b/src/plugins/diffeditor/diffeditorcontroller.h @@ -6,7 +6,7 @@ #include "diffeditor_global.h" #include "diffutils.h" -#include +#include #include @@ -61,7 +61,7 @@ signals: protected: // Core functions: - void setReloadRecipe(const Utils::Tasking::Group &recipe) { m_reloadRecipe = recipe; } + void setReloadRecipe(const Tasking::Group &recipe) { m_reloadRecipe = recipe; } void setDiffFiles(const QList &diffFileList); // Optional: void setDisplayName(const QString &name) { m_displayName = name; } @@ -74,8 +74,8 @@ private: Internal::DiffEditorDocument *const m_document; QString m_displayName; - std::unique_ptr m_taskTree; - Utils::Tasking::Group m_reloadRecipe; + std::unique_ptr m_taskTree; + Tasking::Group m_reloadRecipe; }; Q_DECLARE_OPERATORS_FOR_FLAGS(DiffEditorController::PatchOptions) diff --git a/src/plugins/diffeditor/diffeditordocument.cpp b/src/plugins/diffeditor/diffeditordocument.cpp index 05b1684c8a3..1d43064d60f 100644 --- a/src/plugins/diffeditor/diffeditordocument.cpp +++ b/src/plugins/diffeditor/diffeditordocument.cpp @@ -291,8 +291,8 @@ Core::IDocument::OpenResult DiffEditorDocument::open(QString *errorString, const return OpenResult::ReadError; } - bool ok = false; - QList fileDataList = DiffUtils::readPatch(patch, &ok); + const std::optional> fileDataList = DiffUtils::readPatch(patch); + bool ok = fileDataList.has_value(); if (!ok) { *errorString = Tr::tr("Could not parse patch file \"%1\". " "The content is not of unified diff format.") @@ -302,7 +302,7 @@ Core::IDocument::OpenResult DiffEditorDocument::open(QString *errorString, const emit temporaryStateChanged(); setFilePath(filePath.absoluteFilePath()); setWorkingDirectory(filePath.absoluteFilePath()); - setDiffFiles(fileDataList); + setDiffFiles(*fileDataList); } endReload(ok); if (!ok && readResult == TextFileFormat::ReadEncodingError) diff --git a/src/plugins/diffeditor/diffeditorplugin.cpp b/src/plugins/diffeditor/diffeditorplugin.cpp index 2a813206657..99ceef2f0ab 100644 --- a/src/plugins/diffeditor/diffeditorplugin.cpp +++ b/src/plugins/diffeditor/diffeditorplugin.cpp @@ -13,12 +13,13 @@ #include #include +#include + #include -#include +#include #include #include -#include #include #include @@ -47,13 +48,12 @@ public: m_ignoreWhitespace(ignoreWhitespace) {} - void operator()(QFutureInterface &futureInterface, - const ReloadInput &reloadInput) const + void operator()(QPromise &promise, const ReloadInput &reloadInput) const { if (reloadInput.text[LeftSide] == reloadInput.text[RightSide]) return; // We show "No difference" in this case, regardless if it's binary or not - Differ differ(&futureInterface); + Differ differ(QFuture(promise.future())); FileData fileData; if (!reloadInput.binaryFiles) { @@ -85,7 +85,7 @@ public: fileData.fileInfo = reloadInput.fileInfo; fileData.fileOperation = reloadInput.fileOperation; fileData.binaryFiles = reloadInput.binaryFiles; - futureInterface.reportResult(fileData); + promise.addResult(fileData); } private: @@ -114,11 +114,12 @@ DiffFilesController::DiffFilesController(IDocument *document) const auto setupTree = [this, storage](TaskTree &taskTree) { QList> *outputList = storage.activeStorage(); - const auto setupDiff = [this](AsyncTask &async, const ReloadInput &reloadInput) { - async.setAsyncCallData(DiffFile(ignoreWhitespace(), contextLineCount()), reloadInput); - async.setFutureSynchronizer(Internal::DiffEditorPlugin::futureSynchronizer()); + const auto setupDiff = [this](Async &async, const ReloadInput &reloadInput) { + async.setConcurrentCallData( + DiffFile(ignoreWhitespace(), contextLineCount()), reloadInput); + async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer()); }; - const auto onDiffDone = [outputList](const AsyncTask &async, int i) { + const auto onDiffDone = [outputList](const Async &async, int i) { if (async.isResultAvailable()) (*outputList)[i] = async.result(); }; @@ -127,15 +128,15 @@ DiffFilesController::DiffFilesController(IDocument *document) outputList->resize(inputList.size()); using namespace std::placeholders; - QList tasks {parallel, optional}; + QList tasks {parallel, finishAllAndDone}; for (int i = 0; i < inputList.size(); ++i) { - tasks.append(Async(std::bind(setupDiff, _1, inputList.at(i)), + tasks.append(AsyncTask(std::bind(setupDiff, _1, inputList.at(i)), std::bind(onDiffDone, _1, i))); } taskTree.setupRoot(tasks); }; const auto onTreeDone = [this, storage] { - const QList> &results = *storage.activeStorage(); + const QList> &results = *storage; QList finalList; for (const std::optional &result : results) { if (result.has_value()) @@ -149,9 +150,9 @@ DiffFilesController::DiffFilesController(IDocument *document) const Group root = { Storage(storage), - Tree(setupTree), - OnGroupDone(onTreeDone), - OnGroupError(onTreeError) + TaskTreeTask(setupTree), + onGroupDone(onTreeDone), + onGroupError(onTreeError) }; setReloadRecipe(root); } @@ -416,12 +417,10 @@ public: DiffEditorFactory m_editorFactory; DiffEditorServiceImpl m_service; - FutureSynchronizer m_futureSynchronizer; }; DiffEditorPluginPrivate::DiffEditorPluginPrivate() { - m_futureSynchronizer.setCancelOnWait(true); //register actions ActionContainer *toolsContainer = ActionManager::actionContainer(Core::Constants::M_TOOLS); toolsContainer->insertGroup(Core::Constants::G_TOOLS_DEBUG, Constants::G_TOOLS_DIFF); @@ -536,12 +535,6 @@ void DiffEditorPlugin::initialize() d = new DiffEditorPluginPrivate; } -FutureSynchronizer *DiffEditorPlugin::futureSynchronizer() -{ - QTC_ASSERT(s_instance, return nullptr); - return &s_instance->d->m_futureSynchronizer; -} - } // namespace Internal } // namespace DiffEditor @@ -771,13 +764,13 @@ void DiffEditor::Internal::DiffEditorPlugin::testMakePatch() QCOMPARE(result, patchText); - bool ok; - QList resultList = DiffUtils::readPatch(result, &ok); + const std::optional> resultList = DiffUtils::readPatch(result); + const bool ok = resultList.has_value(); QVERIFY(ok); - QCOMPARE(resultList.count(), 1); - for (int i = 0; i < resultList.count(); i++) { - const FileData &resultFileData = resultList.at(i); + QCOMPARE(resultList->count(), 1); + for (int i = 0; i < resultList->count(); i++) { + const FileData &resultFileData = resultList->at(i); QCOMPARE(resultFileData.fileInfo[LeftSide].fileName, fileName); QCOMPARE(resultFileData.fileInfo[RightSide].fileName, fileName); QCOMPARE(resultFileData.chunks.count(), 1); @@ -1335,14 +1328,14 @@ void DiffEditor::Internal::DiffEditorPlugin::testReadPatch() QFETCH(QString, sourcePatch); QFETCH(QList, fileDataList); - bool ok; - const QList &result = DiffUtils::readPatch(sourcePatch, &ok); + const std::optional> result = DiffUtils::readPatch(sourcePatch); + const bool ok = result.has_value(); QVERIFY(ok); - QCOMPARE(result.count(), fileDataList.count()); + QCOMPARE(result->count(), fileDataList.count()); for (int i = 0; i < fileDataList.count(); i++) { const FileData &origFileData = fileDataList.at(i); - const FileData &resultFileData = result.at(i); + const FileData &resultFileData = result->at(i); QCOMPARE(resultFileData.fileInfo[LeftSide].fileName, origFileData.fileInfo[LeftSide].fileName); QCOMPARE(resultFileData.fileInfo[LeftSide].typeInfo, origFileData.fileInfo[LeftSide].typeInfo); QCOMPARE(resultFileData.fileInfo[RightSide].fileName, origFileData.fileInfo[RightSide].fileName); diff --git a/src/plugins/diffeditor/diffeditorplugin.h b/src/plugins/diffeditor/diffeditorplugin.h index 66849a7387e..17b6a2d430d 100644 --- a/src/plugins/diffeditor/diffeditorplugin.h +++ b/src/plugins/diffeditor/diffeditorplugin.h @@ -6,8 +6,6 @@ #include #include -namespace Utils { class FutureSynchronizer; } - namespace DiffEditor { namespace Internal { @@ -34,8 +32,6 @@ public: void initialize() final; - static Utils::FutureSynchronizer *futureSynchronizer(); - private: class DiffEditorPluginPrivate *d = nullptr; diff --git a/src/plugins/diffeditor/diffutils.cpp b/src/plugins/diffeditor/diffutils.cpp index f9d7d6b264a..afb75203037 100644 --- a/src/plugins/diffeditor/diffutils.cpp +++ b/src/plugins/diffeditor/diffutils.cpp @@ -6,7 +6,8 @@ #include #include -#include +#include +#include #include #include #include @@ -556,7 +557,7 @@ static QList readLines(QStringView patch, bool lastChunk, bool *lastChu int noNewLineInDelete = -1; int noNewLineInInsert = -1; - const QVector lines = patch.split(newLine); + const QList lines = patch.split(newLine); int i; for (i = 0; i < lines.size(); i++) { QStringView line = lines.at(i); @@ -794,7 +795,7 @@ static QList readChunks(QStringView patch, bool *lastChunkAtTheEndOfF QList chunkDataList; int position = -1; - QVector startingPositions; // store starting positions of @@ + QList startingPositions; // store starting positions of @@ if (patch.startsWith(QStringLiteral("@@ -"))) startingPositions.append(position + 1); @@ -892,7 +893,7 @@ static FileData readDiffHeaderAndChunks(QStringView headerAndChunks, bool *ok) } -static QList readDiffPatch(QStringView patch, bool *ok, QFutureInterfaceBase *jobController) +static void readDiffPatch(QPromise> &promise, QStringView patch) { const QRegularExpression diffRegExp("(?:\\n|^)" // new line of the beginning of a patch "(" // either @@ -911,23 +912,20 @@ static QList readDiffPatch(QStringView patch, bool *ok, QFutureInterfa ")"); // end of or bool readOk = false; - QList fileDataList; - QRegularExpressionMatch diffMatch = diffRegExp.match(patch); if (diffMatch.hasMatch()) { readOk = true; int lastPos = -1; do { - if (jobController && jobController->isCanceled()) - return {}; + if (promise.isCanceled()) + return; int pos = diffMatch.capturedStart(); if (lastPos >= 0) { QStringView headerAndChunks = patch.mid(lastPos, pos - lastPos); - const FileData fileData = readDiffHeaderAndChunks(headerAndChunks, - &readOk); + const FileData fileData = readDiffHeaderAndChunks(headerAndChunks, &readOk); if (!readOk) break; @@ -942,21 +940,15 @@ static QList readDiffPatch(QStringView patch, bool *ok, QFutureInterfa if (readOk) { QStringView headerAndChunks = patch.mid(lastPos, patch.size() - lastPos - 1); - const FileData fileData = readDiffHeaderAndChunks(headerAndChunks, - &readOk); + const FileData fileData = readDiffHeaderAndChunks(headerAndChunks, &readOk); if (readOk) fileDataList.append(fileData); } } - - if (ok) - *ok = readOk; - if (!readOk) - return {}; - - return fileDataList; + return; + promise.addResult(fileDataList); } // The git diff patch format (ChangeFile, NewFile, DeleteFile) @@ -1203,11 +1195,11 @@ static bool detectFileData(QStringView patch, FileData *fileData, QStringView *r return detectIndexAndBinary(*remainingPatch, fileData, remainingPatch); } -static QList readGitPatch(QStringView patch, bool *ok, QFutureInterfaceBase *jobController) +static void readGitPatch(QPromise> &promise, QStringView patch) { int position = -1; - QVector startingPositions; // store starting positions of git headers + QList startingPositions; // store starting positions of git headers if (patch.startsWith(QStringLiteral("diff --git "))) startingPositions.append(position + 1); @@ -1221,13 +1213,12 @@ static QList readGitPatch(QStringView patch, bool *ok, QFutureInterfac }; const QChar newLine('\n'); - bool readOk = true; - QVector patches; + QList patches; const int count = startingPositions.size(); for (int i = 0; i < count; i++) { - if (jobController && jobController->isCanceled()) - return {}; + if (promise.isCanceled()) + return; const int diffStart = startingPositions.at(i); const int diffEnd = (i < count - 1) @@ -1242,65 +1233,53 @@ static QList readGitPatch(QStringView patch, bool *ok, QFutureInterfac FileData fileData; QStringView remainingFileDiff; - readOk = detectFileData(fileDiff, &fileData, &remainingFileDiff); - - if (!readOk) - break; + if (!detectFileData(fileDiff, &fileData, &remainingFileDiff)) + return; patches.append(PatchInfo { remainingFileDiff, fileData }); } - if (!readOk) { - if (ok) - *ok = readOk; - return {}; - } + if (patches.isEmpty()) + return; - if (jobController) - jobController->setProgressRange(0, patches.size()); + promise.setProgressRange(0, patches.size()); QList fileDataList; - readOk = false; int i = 0; for (const auto &patchInfo : std::as_const(patches)) { - if (jobController) { - if (jobController->isCanceled()) - return {}; - jobController->setProgressValue(i++); - } + if (promise.isCanceled()) + return; + promise.setProgressValue(i++); FileData fileData = patchInfo.fileData; + bool readOk = false; if (!patchInfo.patch.isEmpty() || fileData.fileOperation == FileData::ChangeFile) fileData.chunks = readChunks(patchInfo.patch, &fileData.lastChunkAtTheEndOfFile, &readOk); else readOk = true; if (!readOk) - break; + return; fileDataList.append(fileData); } - - if (ok) - *ok = readOk; - - if (!readOk) - return {}; - - return fileDataList; + promise.addResult(fileDataList); } -QList DiffUtils::readPatch(const QString &patch, bool *ok, - QFutureInterfaceBase *jobController) +std::optional> DiffUtils::readPatch(const QString &patch) { - bool readOk = false; + QPromise> promise; + promise.start(); + readPatchWithPromise(promise, patch); + if (promise.future().resultCount() == 0) + return {}; + return promise.future().result(); +} - QList fileDataList; - - if (jobController) { - jobController->setProgressRange(0, 1); - jobController->setProgressValue(0); - } +void DiffUtils::readPatchWithPromise(QPromise> &promise, const QString &patch) +{ + promise.setProgressRange(0, 1); + promise.setProgressValue(0); QStringView croppedPatch = QStringView(patch); // Crop e.g. "-- \n2.10.2.windows.1\n\n" at end of file const QRegularExpression formatPatchEndingRegExp("(\\n-- \\n\\S*\\n\\n$)"); @@ -1308,14 +1287,9 @@ QList DiffUtils::readPatch(const QString &patch, bool *ok, if (match.hasMatch()) croppedPatch = croppedPatch.left(match.capturedStart() + 1); - fileDataList = readGitPatch(croppedPatch, &readOk, jobController); - if (!readOk) - fileDataList = readDiffPatch(croppedPatch, &readOk, jobController); - - if (ok) - *ok = readOk; - - return fileDataList; + readGitPatch(promise, croppedPatch); + if (promise.future().resultCount() == 0) + readDiffPatch(promise, croppedPatch); } } // namespace DiffEditor diff --git a/src/plugins/diffeditor/diffutils.h b/src/plugins/diffeditor/diffutils.h index 65fcda46786..e128f01012c 100644 --- a/src/plugins/diffeditor/diffutils.h +++ b/src/plugins/diffeditor/diffutils.h @@ -14,7 +14,8 @@ #include QT_BEGIN_NAMESPACE -class QFutureInterfaceBase; +template +class QPromise; QT_END_NAMESPACE namespace Utils { class Diff; } @@ -142,9 +143,8 @@ public: const QString &rightFileName, bool lastChunk = false); static QString makePatch(const QList &fileDataList); - static QList readPatch(const QString &patch, - bool *ok = nullptr, - QFutureInterfaceBase *jobController = nullptr); + static std::optional> readPatch(const QString &patch); + static void readPatchWithPromise(QPromise> &promise, const QString &patch); }; } // namespace DiffEditor diff --git a/src/plugins/diffeditor/diffview.cpp b/src/plugins/diffeditor/diffview.cpp index 8ff444066ac..686f9798b23 100644 --- a/src/plugins/diffeditor/diffview.cpp +++ b/src/plugins/diffeditor/diffview.cpp @@ -117,7 +117,6 @@ void UnifiedView::beginOperation() DiffEditorDocument *document = m_widget->diffDocument(); if (document && document->state() == DiffEditorDocument::LoadOK) m_widget->saveState(); - m_widget->clear(Tr::tr("Waiting for data...")); } void UnifiedView::setDiff(const QList &diffFileList) @@ -126,13 +125,15 @@ void UnifiedView::setDiff(const QList &diffFileList) m_widget->setDiff(diffFileList); } -void UnifiedView::endOperation(bool success) +void UnifiedView::setMessage(const QString &message) +{ + m_widget->clear(message); +} + +void UnifiedView::endOperation() { QTC_ASSERT(m_widget, return); - if (success) - m_widget->restoreState(); - else - m_widget->clear(Tr::tr("Retrieving data failed.")); + m_widget->restoreState(); } void UnifiedView::setCurrentDiffFileIndex(int index) @@ -197,7 +198,6 @@ void SideBySideView::beginOperation() DiffEditorDocument *document = m_widget->diffDocument(); if (document && document->state() == DiffEditorDocument::LoadOK) m_widget->saveState(); - m_widget->clear(Tr::tr("Waiting for data...")); } void SideBySideView::setCurrentDiffFileIndex(int index) @@ -212,13 +212,16 @@ void SideBySideView::setDiff(const QList &diffFileList) m_widget->setDiff(diffFileList); } -void SideBySideView::endOperation(bool success) +void SideBySideView::setMessage(const QString &message) { QTC_ASSERT(m_widget, return); - if (success) - m_widget->restoreState(); - else - m_widget->clear(Tr::tr("Retrieving data failed.")); + m_widget->clear(message); +} + +void SideBySideView::endOperation() +{ + QTC_ASSERT(m_widget, return); + m_widget->restoreState(); } void SideBySideView::setSync(bool sync) diff --git a/src/plugins/diffeditor/diffview.h b/src/plugins/diffeditor/diffview.h index b1e4f21227f..9028f83c636 100644 --- a/src/plugins/diffeditor/diffview.h +++ b/src/plugins/diffeditor/diffview.h @@ -44,7 +44,8 @@ public: virtual void beginOperation() = 0; virtual void setCurrentDiffFileIndex(int index) = 0; virtual void setDiff(const QList &diffFileList) = 0; - virtual void endOperation(bool success) = 0; + virtual void setMessage(const QString &message) = 0; + virtual void endOperation() = 0; virtual void setSync(bool) = 0; @@ -81,7 +82,8 @@ public: void beginOperation() override; void setCurrentDiffFileIndex(int index) override; void setDiff(const QList &diffFileList) override; - void endOperation(bool success) override; + void setMessage(const QString &message) override; + void endOperation() override; void setSync(bool sync) override; @@ -104,7 +106,8 @@ public: void beginOperation() override; void setCurrentDiffFileIndex(int index) override; void setDiff(const QList &diffFileList) override; - void endOperation(bool success) override; + void setMessage(const QString &message) override; + void endOperation() override; void setSync(bool sync) override; diff --git a/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp b/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp index da69e607840..d2f58f83ac4 100644 --- a/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp +++ b/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp @@ -5,30 +5,30 @@ #include "diffeditorconstants.h" #include "diffeditordocument.h" -#include "diffeditorplugin.h" #include "diffeditortr.h" -#include -#include -#include -#include -#include - -#include #include +#include #include #include +#include + #include #include #include #include #include -#include +#include #include #include +#include +#include +#include +#include + using namespace Core; using namespace TextEditor; using namespace Utils; @@ -245,8 +245,8 @@ QString SideDiffEditorWidget::plainTextFromSelection(const QTextCursor &cursor) return TextDocument::convertToPlainText(text); } -SideBySideDiffOutput SideDiffData::diffOutput(QFutureInterface &fi, int progressMin, - int progressMax, const DiffEditorInput &input) +static SideBySideDiffOutput diffOutput(QPromise &promise, int progressMin, + int progressMax, const DiffEditorInput &input) { SideBySideDiffOutput output; @@ -366,8 +366,8 @@ SideBySideDiffOutput SideDiffData::diffOutput(QFutureInterface &fi, int pr diffText[RightSide].replace('\r', ' '); output.side[LeftSide].diffText += diffText[LeftSide]; output.side[RightSide].diffText += diffText[RightSide]; - fi.setProgressValue(MathUtils::interpolateLinear(++i, 0, count, progressMin, progressMax)); - if (fi.isCanceled()) + promise.setProgressValue(MathUtils::interpolateLinear(++i, 0, count, progressMin, progressMax)); + if (promise.isCanceled()) return {}; } output.side[LeftSide].selections = SelectableTextEditorWidget::polishedSelections( @@ -869,16 +869,16 @@ void SideBySideDiffEditorWidget::restoreState() void SideBySideDiffEditorWidget::showDiff() { - m_asyncTask.reset(new AsyncTask()); - m_asyncTask->setFutureSynchronizer(DiffEditorPlugin::futureSynchronizer()); + m_asyncTask.reset(new Async()); + m_asyncTask->setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer()); m_controller.setBusyShowing(true); - connect(m_asyncTask.get(), &AsyncTaskBase::done, this, [this] { + connect(m_asyncTask.get(), &AsyncBase::done, this, [this] { if (m_asyncTask->isCanceled() || !m_asyncTask->isResultAvailable()) { for (SideDiffEditorWidget *editor : m_editor) editor->clearAll(Tr::tr("Retrieving data failed.")); } else { - const ShowResults results = m_asyncTask->result(); + const SideBySideShowResults results = m_asyncTask->result(); m_editor[LeftSide]->setDiffData(results[LeftSide].diffData); m_editor[RightSide]->setDiffData(results[RightSide].diffData); TextDocumentPtr leftDoc(results[LeftSide].textDocument); @@ -914,28 +914,23 @@ void SideBySideDiffEditorWidget::showDiff() const DiffEditorInput input(&m_controller); - auto getDocument = [input](QFutureInterface &futureInterface) { - auto cleanup = qScopeGuard([&futureInterface] { - if (futureInterface.isCanceled()) - futureInterface.reportCanceled(); - }); + auto getDocument = [input](QPromise &promise) { const int firstPartMax = 20; // diffOutput is about 4 times quicker than filling document const int leftPartMax = 60; const int rightPartMax = 100; - futureInterface.setProgressRange(0, rightPartMax); - futureInterface.setProgressValue(0); - QFutureInterface fi = futureInterface; - const SideBySideDiffOutput output = SideDiffData::diffOutput(fi, 0, firstPartMax, input); - if (futureInterface.isCanceled()) + promise.setProgressRange(0, rightPartMax); + promise.setProgressValue(0); + const SideBySideDiffOutput output = diffOutput(promise, 0, firstPartMax, input); + if (promise.isCanceled()) return; - const ShowResult leftResult{TextDocumentPtr(new TextDocument("DiffEditor.SideDiffEditor")), + const SideBySideShowResult leftResult{TextDocumentPtr(new TextDocument("DiffEditor.SideDiffEditor")), output.side[LeftSide].diffData, output.side[LeftSide].selections}; - const ShowResult rightResult{TextDocumentPtr(new TextDocument("DiffEditor.SideDiffEditor")), + const SideBySideShowResult rightResult{TextDocumentPtr(new TextDocument("DiffEditor.SideDiffEditor")), output.side[RightSide].diffData, output.side[RightSide].selections}; - const ShowResults result{leftResult, rightResult}; + const SideBySideShowResults result{leftResult, rightResult}; - auto propagateDocument = [&output, &fi](DiffSide side, const ShowResult &result, + auto propagateDocument = [&output, &promise](DiffSide side, const SideBySideShowResult &result, int progressMin, int progressMax) { // No need to store the change history result.textDocument->document()->setUndoRedoEnabled(false); @@ -952,8 +947,9 @@ void SideBySideDiffEditorWidget::showDiff() const QString package = output.side[side].diffText.mid(currentPos, packageSize); cursor.insertText(package); currentPos += package.size(); - fi.setProgressValue(MathUtils::interpolateLinear(currentPos, 0, diffSize, progressMin, progressMax)); - if (fi.isCanceled()) + promise.setProgressValue(MathUtils::interpolateLinear(currentPos, 0, diffSize, + progressMin, progressMax)); + if (promise.isCanceled()) return; } @@ -968,16 +964,16 @@ void SideBySideDiffEditorWidget::showDiff() }; propagateDocument(LeftSide, leftResult, firstPartMax, leftPartMax); - if (fi.isCanceled()) + if (promise.isCanceled()) return; propagateDocument(RightSide, rightResult, leftPartMax, rightPartMax); - if (fi.isCanceled()) + if (promise.isCanceled()) return; - futureInterface.reportResult(result); + promise.addResult(result); }; - m_asyncTask->setAsyncCallData(getDocument); + m_asyncTask->setConcurrentCallData(getDocument); m_asyncTask->start(); ProgressManager::addTask(m_asyncTask->future(), Tr::tr("Rendering diff"), "DiffEditor"); } diff --git a/src/plugins/diffeditor/sidebysidediffeditorwidget.h b/src/plugins/diffeditor/sidebysidediffeditorwidget.h index 21225fa5580..b56fea7da02 100644 --- a/src/plugins/diffeditor/sidebysidediffeditorwidget.h +++ b/src/plugins/diffeditor/sidebysidediffeditorwidget.h @@ -7,7 +7,6 @@ #include "diffeditorwidgetcontroller.h" #include "selectabletexteditorwidget.h" // TODO: we need DiffSelections here only -#include #include namespace Core { class IContext; } @@ -19,7 +18,7 @@ class TextEditorWidget; namespace Utils { template -class AsyncTask; +class Async; } QT_BEGIN_NAMESPACE @@ -40,9 +39,6 @@ class SideBySideDiffOutput; class SideDiffData { public: - static SideBySideDiffOutput diffOutput(QFutureInterface &fi, int progressMin, - int progressMax, const DiffEditorInput &input); - DiffChunkInfo m_chunkInfo; // block number, fileInfo. Set for file lines only. QMap m_fileInfo; @@ -60,7 +56,6 @@ public: int blockNumberForFileIndex(int fileIndex) const; int fileIndexForBlockNumber(int blockNumber) const; -private: void setLineNumber(int blockNumber, int lineNumber); void setFileInfo(int blockNumber, const DiffFileInfo &fileInfo); void setSkippedLines(int blockNumber, int skippedLines, const QString &contextInfo = {}) { @@ -88,6 +83,16 @@ public: QHash foldingIndent; }; +class SideBySideShowResult +{ +public: + QSharedPointer textDocument{}; + SideDiffData diffData; + DiffSelections selections; +}; + +using SideBySideShowResults = std::array; + class SideBySideDiffEditorWidget : public QWidget { Q_OBJECT @@ -135,15 +140,7 @@ private: bool m_horizontalSync = false; - struct ShowResult - { - QSharedPointer textDocument{}; - SideDiffData diffData; - DiffSelections selections; - }; - using ShowResults = std::array; - - std::unique_ptr> m_asyncTask; + std::unique_ptr> m_asyncTask; }; } // namespace Internal diff --git a/src/plugins/diffeditor/unifieddiffeditorwidget.cpp b/src/plugins/diffeditor/unifieddiffeditorwidget.cpp index b937b919f7c..bb64e61d5b0 100644 --- a/src/plugins/diffeditor/unifieddiffeditorwidget.cpp +++ b/src/plugins/diffeditor/unifieddiffeditorwidget.cpp @@ -5,26 +5,24 @@ #include "diffeditorconstants.h" #include "diffeditordocument.h" -#include "diffeditorplugin.h" #include "diffeditortr.h" -#include -#include -#include -#include - #include #include +#include + #include #include -#include #include -#include +#include #include #include -#include + +#include +#include +#include using namespace Core; using namespace TextEditor; @@ -48,10 +46,10 @@ UnifiedDiffEditorWidget::UnifiedDiffEditorWidget(QWidget *parent) connect(this, &QPlainTextEdit::cursorPositionChanged, this, &UnifiedDiffEditorWidget::slotCursorPositionChangedInEditor); - auto context = new Core::IContext(this); + auto context = new IContext(this); context->setWidget(this); - context->setContext(Core::Context(Constants::UNIFIED_VIEW_ID)); - Core::ICore::addContextObject(context); + context->setContext(Context(Constants::UNIFIED_VIEW_ID)); + ICore::addContextObject(context); } UnifiedDiffEditorWidget::~UnifiedDiffEditorWidget() = default; @@ -68,6 +66,14 @@ DiffEditorDocument *UnifiedDiffEditorWidget::diffDocument() const return m_controller.document(); } +void UnifiedDiffEditorWidget::setDiff(const QList &diffFileList) +{ + const GuardLocker locker(m_controller.m_ignoreChanges); + clear(Tr::tr("Waiting for data...")); + m_controller.m_contextFileData = diffFileList; + showDiff(); +} + void UnifiedDiffEditorWidget::saveState() { if (!m_state.isNull()) @@ -260,14 +266,6 @@ void UnifiedDiffData::setLineNumber(DiffSide side, int blockNumber, int lineNumb m_lineNumberDigits[side] = qMax(m_lineNumberDigits[side], lineNumberString.count()); } -void UnifiedDiffEditorWidget::setDiff(const QList &diffFileList) -{ - const GuardLocker locker(m_controller.m_ignoreChanges); - clear(Tr::tr("Waiting for data...")); - m_controller.m_contextFileData = diffFileList; - showDiff(); -} - QString UnifiedDiffData::setChunk(const DiffEditorInput &input, const ChunkData &chunkData, bool lastChunk, int *blockNumber, DiffSelections *selections) { @@ -391,8 +389,8 @@ QString UnifiedDiffData::setChunk(const DiffEditorInput &input, const ChunkData return diffText; } -UnifiedDiffOutput UnifiedDiffData::diffOutput(QFutureInterface &fi, int progressMin, - int progressMax, const DiffEditorInput &input) +static UnifiedDiffOutput diffOutput(QPromise &promise, int progressMin, + int progressMax, const DiffEditorInput &input) { UnifiedDiffOutput output; @@ -437,8 +435,8 @@ UnifiedDiffOutput UnifiedDiffData::diffOutput(QFutureInterface &fi, int pr output.diffData.m_chunkInfo.setChunkIndex(oldBlock, blockNumber - oldBlock, j); } } - fi.setProgressValue(MathUtils::interpolateLinear(++i, 0, count, progressMin, progressMax)); - if (fi.isCanceled()) + promise.setProgressValue(MathUtils::interpolateLinear(++i, 0, count, progressMin, progressMax)); + if (promise.isCanceled()) return {}; } @@ -454,14 +452,14 @@ void UnifiedDiffEditorWidget::showDiff() return; } - m_asyncTask.reset(new AsyncTask()); - m_asyncTask->setFutureSynchronizer(DiffEditorPlugin::futureSynchronizer()); + m_asyncTask.reset(new Async()); + m_asyncTask->setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer()); m_controller.setBusyShowing(true); - connect(m_asyncTask.get(), &AsyncTaskBase::done, this, [this] { + connect(m_asyncTask.get(), &AsyncBase::done, this, [this] { if (m_asyncTask->isCanceled() || !m_asyncTask->isResultAvailable()) { setPlainText(Tr::tr("Retrieving data failed.")); } else { - const ShowResult result = m_asyncTask->result(); + const UnifiedShowResult result = m_asyncTask->result(); m_data = result.diffData; TextDocumentPtr doc(result.textDocument); { @@ -481,21 +479,16 @@ void UnifiedDiffEditorWidget::showDiff() const DiffEditorInput input(&m_controller); - auto getDocument = [input](QFutureInterface &futureInterface) { - auto cleanup = qScopeGuard([&futureInterface] { - if (futureInterface.isCanceled()) - futureInterface.reportCanceled(); - }); + auto getDocument = [input](QPromise &promise) { const int progressMax = 100; const int firstPartMax = 20; // diffOutput is about 4 times quicker than filling document - futureInterface.setProgressRange(0, progressMax); - futureInterface.setProgressValue(0); - QFutureInterface fi = futureInterface; - const UnifiedDiffOutput output = UnifiedDiffData::diffOutput(fi, 0, firstPartMax, input); - if (futureInterface.isCanceled()) + promise.setProgressRange(0, progressMax); + promise.setProgressValue(0); + const UnifiedDiffOutput output = diffOutput(promise, 0, firstPartMax, input); + if (promise.isCanceled()) return; - const ShowResult result = {TextDocumentPtr(new TextDocument("DiffEditor.UnifiedDiffEditor")), + const UnifiedShowResult result = {TextDocumentPtr(new TextDocument("DiffEditor.UnifiedDiffEditor")), output.diffData, output.selections}; // No need to store the change history result.textDocument->document()->setUndoRedoEnabled(false); @@ -512,8 +505,9 @@ void UnifiedDiffEditorWidget::showDiff() const QString package = output.diffText.mid(currentPos, packageSize); cursor.insertText(package); currentPos += package.size(); - fi.setProgressValue(MathUtils::interpolateLinear(currentPos, 0, diffSize, firstPartMax, progressMax)); - if (futureInterface.isCanceled()) + promise.setProgressValue(MathUtils::interpolateLinear(currentPos, 0, diffSize, + firstPartMax, progressMax)); + if (promise.isCanceled()) return; } @@ -525,10 +519,10 @@ void UnifiedDiffEditorWidget::showDiff() // to caller's thread. We push it to no thread (make object to have no thread affinity), // and later, in the caller's thread, we pull it back to the caller's thread. result.textDocument->moveToThread(nullptr); - futureInterface.reportResult(result); + promise.addResult(result); }; - m_asyncTask->setAsyncCallData(getDocument); + m_asyncTask->setConcurrentCallData(getDocument); m_asyncTask->start(); ProgressManager::addTask(m_asyncTask->future(), Tr::tr("Rendering diff"), "DiffEditor"); } diff --git a/src/plugins/diffeditor/unifieddiffeditorwidget.h b/src/plugins/diffeditor/unifieddiffeditorwidget.h index 842eec9ea36..e303a757172 100644 --- a/src/plugins/diffeditor/unifieddiffeditorwidget.h +++ b/src/plugins/diffeditor/unifieddiffeditorwidget.h @@ -6,15 +6,13 @@ #include "diffeditorwidgetcontroller.h" #include "selectabletexteditorwidget.h" -#include - namespace Core { class IContext; } namespace TextEditor { class FontSettings; } namespace Utils { template -class AsyncTask; +class Async; } namespace DiffEditor { @@ -31,9 +29,6 @@ class UnifiedDiffOutput; class UnifiedDiffData { public: - static UnifiedDiffOutput diffOutput(QFutureInterface &fi, int progressMin, int progressMax, - const DiffEditorInput &input); - DiffChunkInfo m_chunkInfo; // block number, visual line number. QMap m_fileInfo; @@ -45,10 +40,10 @@ public: int blockNumberForFileIndex(int fileIndex) const; int fileIndexForBlockNumber(int blockNumber) const; -private: - void setLineNumber(DiffSide side, int blockNumber, int lineNumber, int rowNumberInChunk); QString setChunk(const DiffEditorInput &input, const ChunkData &chunkData, bool lastChunk, int *blockNumber, DiffSelections *selections); +private: + void setLineNumber(DiffSide side, int blockNumber, int lineNumber, int rowNumberInChunk); }; class UnifiedDiffOutput @@ -63,6 +58,14 @@ public: DiffSelections selections; }; +class UnifiedShowResult +{ +public: + QSharedPointer textDocument; + UnifiedDiffData diffData; + DiffSelections selections; +}; + class UnifiedDiffEditorWidget final : public SelectableTextEditorWidget { Q_OBJECT @@ -105,14 +108,7 @@ private: DiffEditorWidgetController m_controller; QByteArray m_state; - struct ShowResult - { - QSharedPointer textDocument; - UnifiedDiffData diffData; - DiffSelections selections; - }; - - std::unique_ptr> m_asyncTask; + std::unique_ptr> m_asyncTask; }; } // namespace Internal diff --git a/src/plugins/docker/dockerapi.cpp b/src/plugins/docker/dockerapi.cpp index 75256f9473f..baa9ed6be9f 100644 --- a/src/plugins/docker/dockerapi.cpp +++ b/src/plugins/docker/dockerapi.cpp @@ -6,9 +6,9 @@ #include "dockertr.h" #include +#include +#include #include -#include -#include #include @@ -35,7 +35,7 @@ DockerApi *DockerApi::instance() bool DockerApi::canConnect() { - QtcProcess process; + Process process; FilePath dockerExe = dockerClient(); if (dockerExe.isEmpty() || !dockerExe.isExecutableFile()) return false; @@ -43,7 +43,7 @@ bool DockerApi::canConnect() bool result = false; process.setCommand(CommandLine(dockerExe, QStringList{"info"})); - connect(&process, &QtcProcess::done, [&process, &result] { + connect(&process, &Process::done, [&process, &result] { qCInfo(dockerApiLog) << "'docker info' result:\n" << qPrintable(process.allOutput()); if (process.result() == ProcessResult::FinishedWithSuccess) result = true; @@ -65,7 +65,7 @@ void DockerApi::checkCanConnect(bool async) m_dockerDaemonAvailable = std::nullopt; emit dockerDaemonAvailableChanged(); - auto future = Utils::runAsync([lk = std::move(lk), this] { + auto future = Utils::asyncRun([lk = std::move(lk), this] { m_dockerDaemonAvailable = canConnect(); emit dockerDaemonAvailableChanged(); }); @@ -103,7 +103,7 @@ std::optional DockerApi::isDockerDaemonAvailable(bool async) FilePath DockerApi::dockerClient() { - return FilePath::fromString(m_settings->dockerBinaryPath.value()); + return m_settings->dockerBinaryPath(); } } // Docker::Internal diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 2a955f131fe..2dd2a589f72 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -43,11 +44,12 @@ #include #include #include +#include #include #include -#include #include #include +#include #include #include @@ -95,16 +97,16 @@ public: {} private: - void setupShellProcess(QtcProcess *shellProcess) final + void setupShellProcess(Process *shellProcess) final { - shellProcess->setCommand({m_settings->dockerBinaryPath.filePath(), + shellProcess->setCommand({m_settings->dockerBinaryPath(), {"container", "start", "-i", "-a", m_containerId}}); } CommandLine createFallbackCommand(const CommandLine &cmdLine) { CommandLine result = cmdLine; - result.setExecutable(cmdLine.executable().onDevice(m_devicePath)); + result.setExecutable(m_devicePath.withNewPath(cmdLine.executable().path())); return result; } @@ -124,7 +126,6 @@ public: RunResult runInShell(const CommandLine &cmdLine, const QByteArray &stdInData) const override; QString mapToDevicePath(const QString &hostPath) const override; - OsType osType(const FilePath &filePath) const override; DockerDevicePrivate *m_dev = nullptr; }; @@ -142,7 +143,7 @@ public: RunResult runInShell(const CommandLine &cmd, const QByteArray &stdInData = {}); - void updateContainerAccess(); + bool updateContainerAccess(); void changeMounts(QStringList newMounts); bool ensureReachable(const FilePath &other); void shutdown(); @@ -160,15 +161,17 @@ public: Environment environment(); CommandLine withDockerExecCmd(const CommandLine &cmd, - Environment *env = nullptr, - FilePath *workDir = nullptr, - bool interactive = false); + const std::optional &env = std::nullopt, + const std::optional &workDir = std::nullopt, + bool interactive = false, + bool withPty = false, + bool withMarker = true); bool prepareForBuild(const Target *target); Tasks validateMounts() const; bool createContainer(); - void startContainer(); + bool startContainer(); void stopCurrentContainer(); void fetchSystemEnviroment(); @@ -183,6 +186,8 @@ public: QStringList createMountArgs() const; + bool isImageAvailable() const; + DockerDevice *const q; DockerDeviceData m_data; DockerSettings *m_settings; @@ -221,7 +226,7 @@ private: // as this object is alive. IDevice::ConstPtr m_device; - QtcProcess m_process; + Process m_process; qint64 m_remotePID = 0; bool m_hasReceivedFirstOutput = false; }; @@ -231,43 +236,66 @@ DockerProcessImpl::DockerProcessImpl(IDevice::ConstPtr device, DockerDevicePriva , m_device(std::move(device)) , m_process(this) { - connect(&m_process, &QtcProcess::started, this, [this] { + connect(&m_process, &Process::started, this, [this] { qCDebug(dockerDeviceLog) << "Process started:" << m_process.commandLine(); - }); - connect(&m_process, &QtcProcess::readyReadStandardOutput, this, [this] { - if (!m_hasReceivedFirstOutput) { - QByteArray output = m_process.readAllRawStandardOutput(); - qsizetype idx = output.indexOf('\n'); - QByteArray firstLine = output.left(idx); - QByteArray rest = output.mid(idx + 1); - qCDebug(dockerDeviceLog) - << "Process first line received:" << m_process.commandLine() << firstLine; - if (firstLine.startsWith("__qtc")) { - bool ok = false; - m_remotePID = firstLine.mid(5, firstLine.size() - 5 - 5).toLongLong(&ok); - - if (ok) - emit started(m_remotePID); - - if (rest.size() > 0) - emit readyRead(rest, {}); - - m_hasReceivedFirstOutput = true; - return; - } + if (m_setup.m_ptyData.has_value()) { + m_hasReceivedFirstOutput = true; + emit started(m_process.processId(), m_process.applicationMainThreadId()); } - emit readyRead(m_process.readAllRawStandardOutput(), {}); }); - connect(&m_process, &QtcProcess::readyReadStandardError, this, [this] { - emit readyRead({}, m_process.readAllRawStandardError()); + connect(&m_process, &Process::readyReadStandardOutput, this, [this] { + if (m_hasReceivedFirstOutput) + emit readyRead(m_process.readAllRawStandardOutput(), {}); + + QByteArray output = m_process.readAllRawStandardOutput(); + qsizetype idx = output.indexOf('\n'); + QByteArray firstLine = output.left(idx).trimmed(); + QByteArray rest = output.mid(idx + 1); + qCDebug(dockerDeviceLog) << "Process first line received:" << m_process.commandLine() + << firstLine; + + if (!firstLine.startsWith("__qtc")) + return; + + bool ok = false; + m_remotePID = firstLine.mid(5, firstLine.size() - 5 - 5).toLongLong(&ok); + + if (ok) + emit started(m_remotePID); + + // In case we already received some error output, send it now. + const QByteArray stdErr = m_process.readAllRawStandardError(); + if (rest.size() > 0 || stdErr.size() > 0) + emit readyRead(rest, stdErr); + + m_hasReceivedFirstOutput = true; }); - connect(&m_process, &QtcProcess::done, this, [this] { + connect(&m_process, &Process::readyReadStandardError, this, [this] { + if (m_remotePID) + emit readyRead({}, m_process.readAllRawStandardError()); + }); + + connect(&m_process, &Process::done, this, [this] { qCDebug(dockerDeviceLog) << "Process exited:" << m_process.commandLine() << "with code:" << m_process.resultData().m_exitCode; - emit done(m_process.resultData()); + + Utils::ProcessResultData resultData = m_process.resultData(); + + if (m_remotePID == 0 && !m_hasReceivedFirstOutput) { + resultData.m_error = QProcess::FailedToStart; + qCWarning(dockerDeviceLog) << "Process failed to start:" << m_process.commandLine(); + QByteArray stdOut = m_process.readAllRawStandardOutput(); + QByteArray stdErr = m_process.readAllRawStandardError(); + if (!stdOut.isEmpty()) + qCWarning(dockerDeviceLog) << "stdout:" << stdOut; + if (!stdErr.isEmpty()) + qCWarning(dockerDeviceLog) << "stderr:" << stdErr; + } + + emit done(resultData); }); } @@ -282,23 +310,30 @@ void DockerProcessImpl::start() m_process.setProcessImpl(m_setup.m_processImpl); m_process.setProcessMode(m_setup.m_processMode); m_process.setTerminalMode(m_setup.m_terminalMode); + m_process.setPtyData(m_setup.m_ptyData); m_process.setReaperTimeout(m_setup.m_reaperTimeout); m_process.setWriteData(m_setup.m_writeData); m_process.setProcessChannelMode(m_setup.m_processChannelMode); m_process.setExtraData(m_setup.m_extraData); m_process.setStandardInputFile(m_setup.m_standardInputFile); m_process.setAbortOnMetaChars(m_setup.m_abortOnMetaChars); + m_process.setCreateConsoleOnWindows(m_setup.m_createConsoleOnWindows); if (m_setup.m_lowPriority) m_process.setLowPriority(); - const bool interactive = m_setup.m_processMode == ProcessMode::Writer - || !m_setup.m_writeData.isEmpty(); + const bool inTerminal = m_setup.m_terminalMode != TerminalMode::Off + || m_setup.m_ptyData.has_value(); - const CommandLine fullCommandLine = m_devicePrivate - ->withDockerExecCmd(m_setup.m_commandLine, - &m_setup.m_environment, - &m_setup.m_workingDirectory, - interactive); + const bool interactive = m_setup.m_processMode == ProcessMode::Writer + || !m_setup.m_writeData.isEmpty() || inTerminal; + + const CommandLine fullCommandLine + = m_devicePrivate->withDockerExecCmd(m_setup.m_commandLine, + m_setup.m_environment, + m_setup.m_workingDirectory, + interactive, + inTerminal, + !m_process.ptyData().has_value()); m_process.setCommand(fullCommandLine); m_process.start(); @@ -311,14 +346,26 @@ qint64 DockerProcessImpl::write(const QByteArray &data) void DockerProcessImpl::sendControlSignal(ControlSignal controlSignal) { - QTC_ASSERT(m_remotePID, return); - if (controlSignal == ControlSignal::CloseWriteChannel) { - m_process.closeWriteChannel(); - return; + if (!m_setup.m_ptyData.has_value()) { + QTC_ASSERT(m_remotePID, return); + if (controlSignal == ControlSignal::CloseWriteChannel) { + m_process.closeWriteChannel(); + return; + } + const int signal = controlSignalToInt(controlSignal); + m_devicePrivate->runInShell( + {"kill", {QString("-%1").arg(signal), QString("%2").arg(m_remotePID)}}); + } else { + // clang-format off + switch (controlSignal) { + case ControlSignal::Terminate: m_process.terminate(); break; + case ControlSignal::Kill: m_process.kill(); break; + case ControlSignal::Interrupt: m_process.interrupt(); break; + case ControlSignal::KickOff: m_process.kickoffProcess(); break; + case ControlSignal::CloseWriteChannel: break; + } + // clang-format on } - const int signal = controlSignalToInt(controlSignal); - m_devicePrivate->runInShell( - {"kill", {QString("-%1").arg(signal), QString("%2").arg(m_remotePID)}}); } IDeviceWidget *DockerDevice::createWidget() @@ -367,47 +414,40 @@ QString DockerDeviceFileAccess::mapToDevicePath(const QString &hostPath) const return newPath; } -OsType DockerDeviceFileAccess::osType(const FilePath &filePath) const -{ - QTC_ASSERT(m_dev, return UnixDeviceFileAccess::osType(filePath)); - return m_dev->q->osType(); -} - DockerDevice::DockerDevice(DockerSettings *settings, const DockerDeviceData &data) : d(new DockerDevicePrivate(this, settings, data)) { setFileAccess(&d->m_fileAccess); setDisplayType(Tr::tr("Docker")); - setOsType(OsTypeOtherUnix); + setOsType(OsTypeLinux); setDefaultDisplayName(Tr::tr("Docker Image")); - + setupId(IDevice::ManuallyAdded); + setType(Constants::DOCKER_DEVICE_TYPE); + setMachineType(IDevice::Hardware); setDisplayName(Tr::tr("Docker Image \"%1\" (%2)").arg(data.repoAndTag()).arg(data.imageId)); setAllowEmptyCommand(true); - setOpenTerminal([this, settings](const Environment &env, const FilePath &workingDir) { + setOpenTerminal([this](const Environment &env, const FilePath &workingDir) { Q_UNUSED(env); // TODO: That's the runnable's environment in general. Use it via -e below. - updateContainerAccess(); + if (!updateContainerAccess()) + return; + if (d->containerId().isEmpty()) { MessageManager::writeDisrupting(Tr::tr("Error starting remote shell. No container.")); return; } - QtcProcess *proc = new QtcProcess(d); - proc->setTerminalMode(TerminalMode::On); + Process proc; + proc.setTerminalMode(TerminalMode::Detached); + proc.setEnvironment(env); + proc.setWorkingDirectory(workingDir); + proc.setCommand({Terminal::defaultShellForDevice(rootPath()), {}}); + proc.start(); - QObject::connect(proc, &QtcProcess::done, [proc] { - if (proc->error() != QProcess::UnknownError && MessageManager::instance()) { - MessageManager::writeDisrupting( - Tr::tr("Error starting remote shell: %1").arg(proc->errorString())); - } - proc->deleteLater(); - }); - - const QString wd = workingDir.isEmpty() ? "/" : workingDir.path(); - proc->setCommand({settings->dockerBinaryPath.filePath(), - {"exec", "-it", "-w", wd, d->containerId(), "/bin/sh"}}); - proc->setEnvironment(Environment::systemEnvironment()); // The host system env. Intentional. - proc->start(); + if (proc.error() != QProcess::UnknownError && MessageManager::instance()) { + MessageManager::writeDisrupting( + Tr::tr("Error starting remote shell: %1").arg(proc.errorString())); + } }); addDeviceAction({Tr::tr("Open Shell in Container"), [](const IDevice::Ptr &device, QWidget *) { @@ -440,47 +480,58 @@ void DockerDevice::setData(const DockerDeviceData &data) d->setData(data); } -void DockerDevice::updateContainerAccess() const +bool DockerDevice::updateContainerAccess() const { - d->updateContainerAccess(); + return d->updateContainerAccess(); } CommandLine DockerDevicePrivate::withDockerExecCmd(const CommandLine &cmd, - Environment *env, - FilePath *workDir, - bool interactive) + const std::optional &env, + const std::optional &workDir, + bool interactive, + bool withPty, + bool withMarker) { if (!m_settings) return {}; - updateContainerAccess(); + if (!updateContainerAccess()) + return {}; - CommandLine dockerCmd{m_settings->dockerBinaryPath.filePath(), {"exec"}}; + CommandLine dockerCmd{m_settings->dockerBinaryPath(), {"exec"}}; if (interactive) dockerCmd.addArg("-i"); + if (withPty) + dockerCmd.addArg("-t"); + if (env) { - for (auto it = env->constBegin(); it != env->constEnd(); ++it) { + env->forEachEntry([&](const QString &key, const QString &value, bool) { dockerCmd.addArg("-e"); - dockerCmd.addArg(env->key(it) + "=" + env->expandedValueForKey(env->key(it))); - } + dockerCmd.addArg(key + "=" + env->expandVariables(value)); + }); } if (workDir && !workDir->isEmpty()) - dockerCmd.addArgs({"-w", workDir->path()}); + dockerCmd.addArgs({"-w", q->rootPath().withNewMappedPath(*workDir).nativePath()}); dockerCmd.addArg(m_container); + dockerCmd.addArgs({"/bin/sh", "-c"}); CommandLine exec("exec"); - exec.addCommandLineAsArgs(cmd); + exec.addCommandLineAsArgs(cmd, CommandLine::Raw); - CommandLine echo("echo"); - echo.addArgs("__qtc$$qtc__", CommandLine::Raw); - echo.addCommandLineWithAnd(exec); + if (withMarker) { + CommandLine echo("echo"); + echo.addArgs("__qtc$$qtc__", CommandLine::Raw); + echo.addCommandLineWithAnd(exec); - dockerCmd.addCommandLineAsSingleArg(echo); + dockerCmd.addCommandLineAsSingleArg(echo); + } else { + dockerCmd.addCommandLineAsSingleArg(exec); + } return dockerCmd; } @@ -494,10 +545,17 @@ void DockerDevicePrivate::stopCurrentContainer() if (!DockerApi::isDockerDaemonAvailable(false).value_or(false)) return; - m_shell.reset(); + if (m_shell) { + // We have to disconnect the shell from the device, otherwise it will try to + // tell us about the container being stopped. Since that signal is emitted in a different + // thread, it would be delayed received by us when we might already have started + // a new shell. + m_shell->disconnect(this); + m_shell.reset(); + } - QtcProcess proc; - proc.setCommand({m_settings->dockerBinaryPath.filePath(), {"container", "stop", m_container}}); + Process proc; + proc.setCommand({m_settings->dockerBinaryPath(), {"container", "stop", m_container}}); m_container.clear(); @@ -598,14 +656,33 @@ QStringList DockerDevicePrivate::createMountArgs() const return cmds; } +bool DockerDevicePrivate::isImageAvailable() const +{ + Process proc; + proc.setCommand( + {m_settings->dockerBinaryPath(), + {"image", "list", m_data.repoAndTag(), "--format", "{{.Repository}}:{{.Tag}}"}}); + proc.runBlocking(); + if (proc.result() != ProcessResult::FinishedWithSuccess) + return false; + + if (proc.stdOut().trimmed() == m_data.repoAndTag()) + return true; + + return false; +} + bool DockerDevicePrivate::createContainer() { if (!m_settings) return false; + if (!isImageAvailable()) + return false; + const QString display = HostOsInfo::isLinuxHost() ? QString(":0") : QString("host.docker.internal:0"); - CommandLine dockerCreate{m_settings->dockerBinaryPath.filePath(), + CommandLine dockerCreate{m_settings->dockerBinaryPath(), {"create", "-i", "--rm", @@ -621,9 +698,6 @@ bool DockerDevicePrivate::createContainer() if (m_data.useLocalUidGid) dockerCreate.addArgs({"-u", QString("%1:%2").arg(getuid()).arg(getgid())}); #endif - FilePath dumperPath = FilePath::fromString("/tmp/qtcreator/debugger"); - addTemporaryMount(Core::ICore::resourcePath("debugger/"), dumperPath); - q->setDebugDumperPath(dumperPath); dockerCreate.addArgs(createMountArgs()); @@ -636,7 +710,7 @@ bool DockerDevicePrivate::createContainer() dockerCreate.addArg(m_data.repoAndTag()); qCDebug(dockerDeviceLog).noquote() << "RUNNING: " << dockerCreate.toUserOutput(); - QtcProcess createProcess; + Process createProcess; createProcess.setCommand(dockerCreate); createProcess.runBlocking(); @@ -655,20 +729,22 @@ bool DockerDevicePrivate::createContainer() return true; } -void DockerDevicePrivate::startContainer() +bool DockerDevicePrivate::startContainer() { if (!createContainer()) - return; + return false; m_shell = std::make_unique(m_settings, m_container, q->rootPath()); connect(m_shell.get(), &DeviceShell::done, this, [this](const ProcessResultData &resultData) { + if (m_shell) + m_shell.release()->deleteLater(); + if (resultData.m_error != QProcess::UnknownError || resultData.m_exitStatus == QProcess::NormalExit) return; qCWarning(dockerDeviceLog) << "Container shell encountered error:" << resultData.m_error; - m_shell.release()->deleteLater(); DockerApi::recheckDockerDaemon(); MessageManager::writeFlashing(Tr::tr("Docker daemon appears to be not running. " @@ -677,23 +753,25 @@ void DockerDevicePrivate::startContainer() "or restart Qt Creator.")); }); - if (!m_shell->start()) { - qCWarning(dockerDeviceLog) << "Container shell failed to start"; - } + if (m_shell->start()) + return true; + + qCWarning(dockerDeviceLog) << "Container shell failed to start"; + return false; } -void DockerDevicePrivate::updateContainerAccess() +bool DockerDevicePrivate::updateContainerAccess() { if (m_isShutdown) - return; + return false; if (DockerApi::isDockerDaemonAvailable(false).value_or(false) == false) - return; + return false; if (m_shell) - return; + return true; - startContainer(); + return startContainer(); } void DockerDevice::setMounts(const QStringList &mounts) const @@ -750,36 +828,9 @@ ProcessInterface *DockerDevice::createProcessInterface() const return new DockerProcessImpl(this->sharedFromThis(), d); } -bool DockerDevice::canAutoDetectPorts() const +DeviceProcessList *DockerDevice::createProcessListModel(QObject *parent) const { - return true; -} - -PortsGatheringMethod DockerDevice::portsGatheringMethod() const -{ - return {[this](QAbstractSocket::NetworkLayerProtocol protocol) -> CommandLine { - // We might encounter the situation that protocol is given IPv6 - // but the consumer of the free port information decides to open - // an IPv4(only) port. As a result the next IPv6 scan will - // report the port again as open (in IPv6 namespace), while the - // same port in IPv4 namespace might still be blocked, and - // re-use of this port fails. - // GDBserver behaves exactly like this. - - Q_UNUSED(protocol) - - // /proc/net/tcp* covers /proc/net/tcp and /proc/net/tcp6 - return {filePath("sed"), - "-e 's/.*: [[:xdigit:]]*:\\([[:xdigit:]]\\{4\\}\\).*/\\1/g' /proc/net/tcp*", - CommandLine::Raw}; - }, - - &Port::parseFromSedOutput}; -}; - -DeviceProcessList *DockerDevice::createProcessListModel(QObject *) const -{ - return nullptr; + return new ProcessList(sharedFromThis(), parent); } DeviceTester *DockerDevice::createDeviceTester() const @@ -865,7 +916,8 @@ void DockerDevice::aboutToBeRemoved() const void DockerDevicePrivate::fetchSystemEnviroment() { - updateContainerAccess(); + if (!updateContainerAccess()) + return; if (m_shell && m_shell->state() == DeviceShell::State::Succeeded) { const RunResult result = runInShell({"env", {}}); @@ -874,7 +926,7 @@ void DockerDevicePrivate::fetchSystemEnviroment() return; } - QtcProcess proc; + Process proc; proc.setCommand(withDockerExecCmd({"env", {}})); proc.start(); proc.waitForFinished(); @@ -889,7 +941,8 @@ void DockerDevicePrivate::fetchSystemEnviroment() RunResult DockerDevicePrivate::runInShell(const CommandLine &cmd, const QByteArray &stdInData) { - updateContainerAccess(); + if (!updateContainerAccess()) + return {}; QTC_ASSERT(m_shell, return {}); return m_shell->runInShell(cmd, stdInData); } @@ -985,6 +1038,7 @@ public: using namespace Layouting; + // clang-format off Column { Stack { statusLabel, @@ -993,21 +1047,20 @@ public: m_log, errorLabel, Row{showUnnamedContainers, m_buttons}, - } - .attachTo(this); - + }.attachTo(this); + // clang-format on connect(m_buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(m_buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); m_buttons->button(QDialogButtonBox::Ok)->setEnabled(false); - CommandLine cmd{m_settings->dockerBinaryPath.filePath(), + CommandLine cmd{m_settings->dockerBinaryPath(), {"images", "--format", "{{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.Size}}"}}; m_log->append(Tr::tr("Running \"%1\"\n").arg(cmd.toUserOutput())); - m_process = new QtcProcess(this); + m_process = new Process(this); m_process->setCommand(cmd); - connect(m_process, &QtcProcess::readyReadStandardOutput, this, [this] { + connect(m_process, &Process::readyReadStandardOutput, this, [this] { const QString out = m_process->readAllStandardOutput().trimmed(); m_log->append(out); for (const QString &line : out.split('\n')) { @@ -1026,12 +1079,12 @@ public: m_log->append(Tr::tr("Done.")); }); - connect(m_process, &Utils::QtcProcess::readyReadStandardError, this, [this] { + connect(m_process, &Utils::Process::readyReadStandardError, this, [this] { const QString out = Tr::tr("Error: %1").arg(m_process->cleanedStdErr()); m_log->append(Tr::tr("Error: %1").arg(out)); }); - connect(m_process, &QtcProcess::done, errorLabel, [errorLabel, this, statusLabel] { + connect(m_process, &Process::done, errorLabel, [errorLabel, this, statusLabel] { delete statusLabel; if (m_process->result() == ProcessResult::FinishedWithSuccess) m_view->setEnabled(true); @@ -1057,9 +1110,6 @@ public: QTC_ASSERT(item, return {}); auto device = DockerDevice::create(m_settings, *item); - device->setupId(IDevice::ManuallyAdded); - device->setType(Constants::DOCKER_DEVICE_TYPE); - device->setMachineType(IDevice::Hardware); return device; } @@ -1072,7 +1122,7 @@ public: QDialogButtonBox *m_buttons; DockerSettings *m_settings; - QtcProcess *m_process = nullptr; + Process *m_process = nullptr; QString m_selectedId; }; @@ -1186,16 +1236,25 @@ bool DockerDevicePrivate::ensureReachable(const FilePath &other) const FilePath fMount = FilePath::fromString(mount); if (other.isChildOf(fMount)) return true; + + if (fMount == other) + return true; } for (const auto &[path, containerPath] : m_temporaryMounts) { if (path.path() != containerPath.path()) continue; + if (path == other) + return true; + if (other.isChildOf(path)) return true; } + if (q->filePath(other.path()).exists()) + return false; + addTemporaryMount(other, other); return true; } diff --git a/src/plugins/docker/dockerdevice.h b/src/plugins/docker/dockerdevice.h index 3ecc0118d49..bd0ce29d9d4 100644 --- a/src/plugins/docker/dockerdevice.h +++ b/src/plugins/docker/dockerdevice.h @@ -73,9 +73,7 @@ public: Utils::ProcessInterface *createProcessInterface() const override; - bool canAutoDetectPorts() const override; - ProjectExplorer::PortsGatheringMethod portsGatheringMethod() const override; - bool canCreateProcessModel() const override { return false; } + bool canCreateProcessModel() const override { return true; } ProjectExplorer::DeviceProcessList *createProcessListModel(QObject *parent) const override; bool hasDeviceTester() const override { return false; } ProjectExplorer::DeviceTester *createDeviceTester() const override; @@ -95,7 +93,7 @@ public: void setData(const DockerDeviceData &data); - void updateContainerAccess() const; + bool updateContainerAccess() const; void setMounts(const QStringList &mounts) const; bool prepareForBuild(const ProjectExplorer::Target *target) override; diff --git a/src/plugins/docker/dockerdevicewidget.cpp b/src/plugins/docker/dockerdevicewidget.cpp index bc9c76f5b25..cd32fead6fb 100644 --- a/src/plugins/docker/dockerdevicewidget.cpp +++ b/src/plugins/docker/dockerdevicewidget.cpp @@ -198,6 +198,8 @@ DockerDeviceWidget::DockerDeviceWidget(const IDevice::Ptr &device) logView->append(Tr::tr("Docker daemon appears to be not running.")); else logView->append(Tr::tr("Docker daemon appears to be running.")); + + logView->append(Tr::tr("Detection complete.")); updateDaemonStateTexts(); }); @@ -245,7 +247,8 @@ DockerDeviceWidget::DockerDeviceWidget(const IDevice::Ptr &device) m_pathsListEdit, }, br, (dockerDevice->isAutoDetected() ? Column {} : std::move(detectionControls)), - }.attachTo(this, WithoutMargins); + noMargin, + }.attachTo(this); // clang-format on searchDirsLineEdit->setVisible(false); diff --git a/src/plugins/docker/dockerplugin.cpp b/src/plugins/docker/dockerplugin.cpp index d7addc894df..7e47abd7b1f 100644 --- a/src/plugins/docker/dockerplugin.cpp +++ b/src/plugins/docker/dockerplugin.cpp @@ -28,28 +28,17 @@ public: DockerSettings m_settings; DockerDeviceFactory m_deviceFactory{&m_settings}; - DockerSettingsPage m_settingPage{&m_settings}; DockerApi m_dockerApi{&m_settings}; }; -static DockerPlugin *s_instance = nullptr; - DockerPlugin::DockerPlugin() { - s_instance = this; FSEngine::registerDeviceScheme(Constants::DOCKER_DEVICE_SCHEME); } -DockerApi *DockerPlugin::dockerApi() -{ - QTC_ASSERT(s_instance, return nullptr); - return &s_instance->d->m_dockerApi; -} - DockerPlugin::~DockerPlugin() { FSEngine::unregisterDeviceScheme(Constants::DOCKER_DEVICE_SCHEME); - s_instance = nullptr; delete d; } diff --git a/src/plugins/docker/dockerplugin.h b/src/plugins/docker/dockerplugin.h index c4ee04ac5af..267244709d8 100644 --- a/src/plugins/docker/dockerplugin.h +++ b/src/plugins/docker/dockerplugin.h @@ -3,12 +3,8 @@ #pragma once -#include "dockerapi.h" - #include -#include - namespace Docker::Internal { class DockerPlugin final : public ExtensionSystem::IPlugin @@ -19,8 +15,6 @@ class DockerPlugin final : public ExtensionSystem::IPlugin public: DockerPlugin(); - static DockerApi *dockerApi(); - private: ~DockerPlugin() final; diff --git a/src/plugins/docker/dockersettings.cpp b/src/plugins/docker/dockersettings.cpp index bfec44f2433..0643cb3a31a 100644 --- a/src/plugins/docker/dockersettings.cpp +++ b/src/plugins/docker/dockersettings.cpp @@ -5,13 +5,10 @@ #include "dockerconstants.h" #include "dockertr.h" -#include "utils/hostosinfo.h" - -#include #include -#include +#include #include using namespace Utils; @@ -21,7 +18,22 @@ namespace Docker::Internal { DockerSettings::DockerSettings() { setSettingsGroup(Constants::DOCKER); - setAutoApply(false); + setId(Docker::Constants::DOCKER_SETTINGS_ID); + setDisplayName(Tr::tr("Docker")); + setCategory(ProjectExplorer::Constants::DEVICE_SETTINGS_CATEGORY); + + setLayouter([this] { + using namespace Layouting; + // clang-format off + return Column { + Group { + title(Tr::tr("Configuration")), + Row { dockerBinaryPath } + }, + st + }; + // clang-format on + }); FilePaths additionalPaths; if (HostOsInfo::isWindowsHost()) @@ -29,8 +41,6 @@ DockerSettings::DockerSettings() else additionalPaths.append("/usr/local/bin"); - registerAspect(&dockerBinaryPath); - dockerBinaryPath.setDisplayStyle(StringAspect::PathChooserDisplay); dockerBinaryPath.setExpectedKind(PathChooser::ExistingCommand); dockerBinaryPath.setDefaultFilePath( FilePath::fromString("docker").searchInPath(additionalPaths)); @@ -39,32 +49,7 @@ DockerSettings::DockerSettings() dockerBinaryPath.setLabelText(Tr::tr("Command:")); dockerBinaryPath.setSettingsKey("cli"); - readSettings(Core::ICore::settings()); -} - -// DockerSettingsPage - -DockerSettingsPage::DockerSettingsPage(DockerSettings *settings) -{ - setId(Docker::Constants::DOCKER_SETTINGS_ID); - setDisplayName(Tr::tr("Docker")); - setCategory(ProjectExplorer::Constants::DEVICE_SETTINGS_CATEGORY); - setSettings(settings); - - setLayouter([settings](QWidget *widget) { - DockerSettings &s = *settings; - using namespace Layouting; - - // clang-format off - Column { - Group { - title(Tr::tr("Configuration")), - Row { s.dockerBinaryPath } - }, - st - }.attachTo(widget); - // clang-format on - }); + readSettings(); } } // Docker::Internal diff --git a/src/plugins/docker/dockersettings.h b/src/plugins/docker/dockersettings.h index 4980a0a6b04..076acf6fa8c 100644 --- a/src/plugins/docker/dockersettings.h +++ b/src/plugins/docker/dockersettings.h @@ -5,22 +5,14 @@ #include -#include - namespace Docker::Internal { -class DockerSettings final : public Utils::AspectContainer +class DockerSettings final : public Core::PagedSettings { public: DockerSettings(); - Utils::StringAspect dockerBinaryPath; -}; - -class DockerSettingsPage final : public Core::IOptionsPage -{ -public: - explicit DockerSettingsPage(DockerSettings *settings); + Utils::FilePathAspect dockerBinaryPath{this}; }; } // Docker::Internal diff --git a/src/plugins/emacskeys/emacskeysplugin.cpp b/src/plugins/emacskeys/emacskeysplugin.cpp index acb2be5b892..4f1fa8c2c82 100644 --- a/src/plugins/emacskeys/emacskeysplugin.cpp +++ b/src/plugins/emacskeys/emacskeysplugin.cpp @@ -135,11 +135,6 @@ void EmacsKeysPlugin::extensionsInitialized() { } -ExtensionSystem::IPlugin::ShutdownFlag EmacsKeysPlugin::aboutToShutdown() -{ - return SynchronousShutdown; -} - void EmacsKeysPlugin::editorAboutToClose(IEditor *editor) { auto w = qobject_cast(editor->widget()); diff --git a/src/plugins/emacskeys/emacskeysplugin.h b/src/plugins/emacskeys/emacskeysplugin.h index b0acb078cc0..ab124b47ce3 100644 --- a/src/plugins/emacskeys/emacskeysplugin.h +++ b/src/plugins/emacskeys/emacskeysplugin.h @@ -57,7 +57,6 @@ public: void initialize() override; void extensionsInitialized() override; - ShutdownFlag aboutToShutdown() override; private: void editorAboutToClose(Core::IEditor *editor); diff --git a/src/plugins/fakevim/fakevim.qbs b/src/plugins/fakevim/fakevim.qbs index cdfb9e24ddf..a4dad38428f 100644 --- a/src/plugins/fakevim/fakevim.qbs +++ b/src/plugins/fakevim/fakevim.qbs @@ -25,9 +25,7 @@ QtcPlugin { "fakevimtr.h", ] - Group { - name: "Tests" - condition: qtc.testsEnabled + QtcTestFiles { files: ["fakevim_test.cpp"] } } diff --git a/src/plugins/fakevim/fakevimactions.cpp b/src/plugins/fakevim/fakevimactions.cpp index 7f7c44baf40..916fea16097 100644 --- a/src/plugins/fakevim/fakevimactions.cpp +++ b/src/plugins/fakevim/fakevimactions.cpp @@ -10,6 +10,13 @@ // Qt Creator. The idea is to keep this file here in a "clean" state that // allows easy reuse with any QTextEdit or QPlainTextEdit derived class. +#ifndef FAKEVIM_STANDALONE +#include +#include +#include +#include +#endif + #include #include #include @@ -18,8 +25,7 @@ using namespace Utils; -namespace FakeVim { -namespace Internal { +namespace FakeVim::Internal { #ifdef FAKEVIM_STANDALONE FvBaseAspect::FvBaseAspect() @@ -62,13 +68,31 @@ QString FvBaseAspect::settingsKey() const void setAutoApply(bool ) {} #endif + +static FakeVimSettings *s_settings; + +FakeVimSettings &settings() +{ + return *s_settings; +} + FakeVimSettings::FakeVimSettings() { - setAutoApply(false); + s_settings = this; #ifndef FAKEVIM_STANDALONE + const char SETTINGS_CATEGORY[] = "D.FakeVim"; + const char SETTINGS_ID[] = "A.FakeVim.General"; + + setId(SETTINGS_ID); + setDisplayName(Tr::tr("General")); + setCategory(SETTINGS_CATEGORY); + setDisplayCategory(Tr::tr("FakeVim")); + setCategoryIconPath(":/fakevim/images/settingscategory_fakevim.png"); + setup(&useFakeVim, false, "UseFakeVim", {}, Tr::tr("Use FakeVim")); #endif + // Specific FakeVim settings setup(&readVimRc, false, "ReadVimRc", {}, Tr::tr("Read .vimrc from location:")); setup(&vimRcPath, QString(), "VimRcPath", {}, {}); // Tr::tr("Path to .vimrc") @@ -135,6 +159,121 @@ FakeVimSettings::FakeVimSettings() "%USERPROFILE%\\_vimrc on Windows, ~/.vimrc otherwise.")); vimRcPath.setPlaceHolderText(Tr::tr("Default: %1").arg(vimrcDefault)); vimRcPath.setDisplayStyle(FvStringAspect::PathChooserDisplay); + + setLayouter([this] { + using namespace Layouting; + using namespace TextEditor; + + Row bools { + Column { + autoIndent, + smartIndent, + expandTab, + smartTab, + hlSearch, + showCmd, + startOfLine, + passKeys, + blinkingCursor + }, + Column { + incSearch, + useCoreSearch, + ignoreCase, + smartCase, + wrapScan, + showMarks, + passControlKey, + relativeNumber, + tildeOp + } + }; + + Row ints { shiftWidth, tabStop, scrollOff, st }; + + vimRcPath.setEnabler(&readVimRc); + + Column strings { + backspace, + isKeyword, + Row {readVimRc, vimRcPath} + }; + + return Column { + useFakeVim, + + Group { + title(Tr::tr("Vim Behavior")), + Column { + bools, + ints, + strings + } + }, + + Group { + title(Tr::tr("Plugin Emulation")), + Column { + emulateVimCommentary, + emulateReplaceWithRegister, + emulateArgTextObj, + emulateExchange, + emulateSurround + } + }, + + Row { + PushButton { + text(Tr::tr("Copy Text Editor Settings")), + onClicked([this] { + TabSettings ts = TextEditorSettings::codeStyle()->tabSettings(); + TypingSettings tps = TextEditorSettings::typingSettings(); + expandTab.setValue(ts.m_tabPolicy != TabSettings::TabsOnlyTabPolicy); + tabStop.setValue(ts.m_tabSize); + shiftWidth.setValue(ts.m_indentSize); + smartTab.setValue(tps.m_smartBackspaceBehavior + == TypingSettings::BackspaceFollowsPreviousIndents); + autoIndent.setValue(true); + smartIndent.setValue(tps.m_autoIndent); + incSearch.setValue(true); + }), + }, + PushButton { + text(Tr::tr("Set Qt Style")), + onClicked([this] { + expandTab.setVolatileValue(true); + tabStop.setVolatileValue(4); + shiftWidth.setVolatileValue(4); + smartTab.setVolatileValue(true); + autoIndent.setVolatileValue(true); + smartIndent.setVolatileValue(true); + incSearch.setVolatileValue(true); + backspace.setVolatileValue(QString("indent,eol,start")); + passKeys.setVolatileValue(true); + }), + }, + PushButton { + text(Tr::tr("Set Plain Style")), + onClicked([this] { + expandTab.setVolatileValue(false); + tabStop.setVolatileValue(8); + shiftWidth.setVolatileValue(8); + smartTab.setVolatileValue(false); + autoIndent.setVolatileValue(false); + smartIndent.setVolatileValue(false); + incSearch.setVolatileValue(false); + backspace.setVolatileValue(QString()); + passKeys.setVolatileValue(false); + }), + }, + st + }, + st + }; + }); + + readSettings(); + #endif } @@ -173,7 +312,7 @@ void FakeVimSettings::setup(FvBaseAspect *aspect, registerAspect(aspect); if (auto boolAspect = dynamic_cast(aspect)) - boolAspect->setLabelPlacement(FvBoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel); + boolAspect->setLabelPlacement(FvBoolAspect::LabelPlacement::AtCheckBox); #else Q_UNUSED(labelText) #endif @@ -187,11 +326,4 @@ void FakeVimSettings::setup(FvBaseAspect *aspect, m_nameToAspect[shortName] = aspect; } -FakeVimSettings *fakeVimSettings() -{ - static FakeVimSettings s; - return &s; -} - -} // namespace Internal -} // namespace FakeVim +} // FakeVim::Internal diff --git a/src/plugins/fakevim/fakevimactions.h b/src/plugins/fakevim/fakevimactions.h index 6f7e303f0f4..e3b14b0d47a 100644 --- a/src/plugins/fakevim/fakevimactions.h +++ b/src/plugins/fakevim/fakevimactions.h @@ -4,7 +4,7 @@ #pragma once #ifndef FAKEVIM_STANDALONE -# include +# include #endif #include @@ -13,8 +13,7 @@ #include #include -namespace FakeVim { -namespace Internal { +namespace FakeVim::Internal { #ifdef FAKEVIM_STANDALONE class FvBaseAspect @@ -44,18 +43,21 @@ class FvBoolAspect : public FvBaseAspect { public: bool value() const { return FvBaseAspect::value().toBool(); } + bool operator()() const { return value(); } }; class FvIntegerAspect : public FvBaseAspect { public: qint64 value() const { return FvBaseAspect::value().toLongLong(); } + qint64 operator()() const { return value(); } }; class FvStringAspect : public FvBaseAspect { public: QString value() const { return FvBaseAspect::value().toString(); } + QString operator()() const { return value(); } }; class FvAspectContainer : public FvBaseAspect @@ -65,7 +67,7 @@ public: #else -using FvAspectContainer = Utils::AspectContainer; +using FvAspectContainer = Core::PagedSettings; using FvBaseAspect = Utils::BaseAspect; using FvBoolAspect = Utils::BoolAspect; using FvIntegerAspect = Utils::IntegerAspect; @@ -142,7 +144,6 @@ private: QHash m_aspectToName; }; -FakeVimSettings *fakeVimSettings(); +FakeVimSettings &settings(); -} // namespace Internal -} // namespace FakeVim +} // FakeVim::Internal diff --git a/src/plugins/fakevim/fakevimhandler.cpp b/src/plugins/fakevim/fakevimhandler.cpp index 68239650654..edfa315af98 100644 --- a/src/plugins/fakevim/fakevimhandler.cpp +++ b/src/plugins/fakevim/fakevimhandler.cpp @@ -36,8 +36,6 @@ #include "fakevimactions.h" #include "fakevimtr.h" -#include - #include #include #include @@ -410,9 +408,8 @@ static QRegularExpression vimPatternToQtPattern(const QString &needle) */ // FIXME: Option smartcase should be used only if search was typed by user. - const bool ignoreCaseOption = fakeVimSettings()->ignoreCase.value(); - const bool smartCaseOption = fakeVimSettings()->smartCase.value(); - const bool initialIgnoreCase = ignoreCaseOption + const bool smartCaseOption = settings().smartCase(); + const bool initialIgnoreCase = settings().ignoreCase() && !(smartCaseOption && needle.contains(QRegularExpression("[A-Z]"))); bool ignorecase = initialIgnoreCase; @@ -833,29 +830,6 @@ static void setClipboardData(const QString &content, RangeMode mode, clipboard->setMimeData(data, clipboardMode); } -static QByteArray toLocalEncoding(const QString &text) -{ -#if defined(Q_OS_WIN) - return QString(text).replace("\n", "\r\n").toLocal8Bit(); -#else - return text.toLocal8Bit(); -#endif -} - -static QString getProcessOutput(const QString &command, const QString &input) -{ - Utils::QtcProcess proc; - proc.setCommand(Utils::CommandLine::fromUserInput(command)); - proc.setWriteData(toLocalEncoding(input)); - proc.start(); - - // FIXME: Process should be interruptable by user. - // Solution is to create a QObject for each process and emit finished state. - proc.waitForFinished(); - - return proc.cleanedStdOut(); -} - static const QMap &vimKeyNames() { static const QMap k = { @@ -1042,14 +1016,6 @@ inline QString msgMarkNotSet(const QString &text) return Tr::tr("Mark \"%1\" not set.").arg(text); } -static void initSingleShotTimer(QTimer *timer, int interval, FakeVimHandler::Private *receiver, - void (FakeVimHandler::Private::*slot)()) -{ - timer->setSingleShot(true); - timer->setInterval(interval); - QObject::connect(timer, &QTimer::timeout, receiver, slot); -} - class Input { public: @@ -2406,9 +2372,19 @@ public: QString surroundFunction; // Used for storing the function name provided to ys{motion}f } g; - FakeVimSettings &s = *fakeVimSettings(); + FakeVimSettings &s = settings(); }; +static void initSingleShotTimer(QTimer *timer, + int interval, + FakeVimHandler::Private *receiver, + void (FakeVimHandler::Private::*slot)()) +{ + timer->setSingleShot(true); + timer->setInterval(interval); + QObject::connect(timer, &QTimer::timeout, receiver, slot); +} + FakeVimHandler::Private::GlobalData FakeVimHandler::Private::g; FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget) @@ -2550,7 +2526,7 @@ void FakeVimHandler::Private::leaveFakeVim(bool needUpdate) // The command might have destroyed the editor. if (m_textedit || m_plaintextedit) { - if (s.showMarks.value()) + if (s.showMarks()) updateSelection(); updateMiniBuffer(); @@ -2599,7 +2575,7 @@ bool FakeVimHandler::Private::wantsOverride(QKeyEvent *ev) // We are interested in overriding most Ctrl key combinations. if (isOnlyControlModifier(mods) - && !s.passControlKey.value() + && !s.passControlKey() && ((key >= Key_A && key <= Key_Z && key != Key_K) || key == Key_BracketLeft || key == Key_BracketRight)) { // Ctrl-K is special as it is the Core's default notion of Locator @@ -2809,7 +2785,7 @@ void FakeVimHandler::Private::ensureCursorVisible() void FakeVimHandler::Private::updateEditor() { - setTabSize(s.tabStop.value()); + setTabSize(s.tabStop()); setupCharClass(); } @@ -3082,7 +3058,7 @@ void FakeVimHandler::Private::stopIncrementalFind() void FakeVimHandler::Private::updateFind(bool isComplete) { - if (!isComplete && !s.incSearch.value()) + if (!isComplete && !s.incSearch()) return; g.currentMessage.clear(); @@ -3184,7 +3160,7 @@ void FakeVimHandler::Private::pushUndoState(bool overwrite) pos = firstPositionInLine(lineForPosition(pos)); else if (isVisualBlockMode()) pos = blockAt(pos).position() + qMin(columnAt(anchor()), columnAt(position())); - } else if (g.movetype == MoveLineWise && s.startOfLine.value()) { + } else if (g.movetype == MoveLineWise && s.startOfLine()) { QTextCursor tc = m_cursor; if (g.submode == ShiftLeftSubMode || g.submode == ShiftRightSubMode || g.submode == IndentSubMode) { @@ -3644,7 +3620,7 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommandMovement) return; } else if (g.submode == ExchangeSubMode) { exchangeRange(currentRange()); - } else if (g.submode == ReplaceWithRegisterSubMode && s.emulateReplaceWithRegister.value()) { + } else if (g.submode == ReplaceWithRegisterSubMode && s.emulateReplaceWithRegister()) { pushUndoState(false); beginEditBlock(); replaceWithRegister(currentRange()); @@ -3757,7 +3733,7 @@ void FakeVimHandler::Private::clearCurrentMode() void FakeVimHandler::Private::updateSelection() { QList selections = m_extraSelections; - if (s.showMarks.value()) { + if (s.showMarks()) { for (auto it = m_buffer->marks.cbegin(), end = m_buffer->marks.cend(); it != end; ++it) { QTextEdit::ExtraSelection sel; sel.cursor = m_cursor; @@ -3776,7 +3752,7 @@ void FakeVimHandler::Private::updateSelection() void FakeVimHandler::Private::updateHighlights() { - if (s.useCoreSearch.value() || !s.hlSearch.value() || g.highlightsCleared) { + if (s.useCoreSearch() || !s.hlSearch() || g.highlightsCleared) { if (m_highlighted.isEmpty()) return; m_highlighted.clear(); @@ -3823,7 +3799,7 @@ void FakeVimHandler::Private::updateMiniBuffer() } else if (!g.mapStates.isEmpty() && !g.mapStates.last().silent) { // Do not reset previous message when after running a mapped command. return; - } else if (g.mode == CommandMode && !g.currentCommand.isEmpty() && s.showCmd.value()) { + } else if (g.mode == CommandMode && !g.currentCommand.isEmpty() && s.showCmd()) { msg = g.currentCommand; messageLevel = MessageShowCmd; } else if (g.mode == CommandMode && isVisualMode()) { @@ -3930,7 +3906,7 @@ bool FakeVimHandler::Private::handleCommandSubSubMode(const Input &input) handled = selectBlockTextObject(g.subsubdata.is('i'), '{', '}'); else if (input.is('"') || input.is('\'') || input.is('`')) handled = selectQuotedStringTextObject(g.subsubdata.is('i'), input.asChar()); - else if (input.is('a') && s.emulateArgTextObj.value()) + else if (input.is('a') && s.emulateArgTextObj()) handled = selectArgumentTextObject(g.subsubdata.is('i')); else handled = false; @@ -4073,7 +4049,7 @@ bool FakeVimHandler::Private::handleMovement(const Input &input) g.subsubmode = NoSubSubMode; } else if (input.is('/') || input.is('?')) { g.lastSearchForward = input.is('/'); - if (s.useCoreSearch.value()) { + if (s.useCoreSearch()) { // re-use the core dialog. g.findPending = true; m_findStartPosition = position(); @@ -4248,7 +4224,7 @@ bool FakeVimHandler::Private::handleMovement(const Input &input) m_cursor = EDITOR(cursorForPosition(QPoint(0, EDITOR(height()) / 2))); handleStartOfLine(); } else if (input.is('n') || input.is('N')) { - if (s.useCoreSearch.value()) { + if (s.useCoreSearch()) { bool forward = (input.is('n')) ? g.lastSearchForward : !g.lastSearchForward; int pos = position(); q->findNextRequested(!forward); @@ -4337,7 +4313,7 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) handled = handleNoSubMode(input); } else if (g.submode == ExchangeSubMode) { handled = handleExchangeSubMode(input); - } else if (g.submode == ChangeSubMode && input.is('x') && s.emulateExchange.value()) { + } else if (g.submode == ChangeSubMode && input.is('x') && s.emulateExchange()) { // Exchange submode is "cx", so we need to switch over from ChangeSubMode here g.submode = ExchangeSubMode; handled = true; @@ -4347,15 +4323,15 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) } else if (g.submode == AddSurroundingSubMode) { handled = handleAddSurroundingSubMode(input); } else if (g.submode == ChangeSubMode && (input.is('s') || input.is('S')) - && s.emulateSurround.value()) { + && s.emulateSurround()) { g.submode = ChangeSurroundingSubMode; g.surroundUpperCaseS = input.is('S'); handled = true; - } else if (g.submode == DeleteSubMode && input.is('s') && s.emulateSurround.value()) { + } else if (g.submode == DeleteSubMode && input.is('s') && s.emulateSurround()) { g.submode = DeleteSurroundingSubMode; handled = true; } else if (g.submode == YankSubMode && (input.is('s') || input.is('S')) - && s.emulateSurround.value()) { + && s.emulateSurround()) { g.submode = AddSurroundingSubMode; g.movetype = MoveInclusive; g.surroundUpperCaseS = input.is('S'); @@ -4364,10 +4340,9 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) || g.submode == DeleteSubMode || g.submode == YankSubMode) { handled = handleChangeDeleteYankSubModes(input); - } else if (g.submode == CommentSubMode && s.emulateVimCommentary.value()) { + } else if (g.submode == CommentSubMode && s.emulateVimCommentary()) { handled = handleCommentSubMode(input); - } else if (g.submode == ReplaceWithRegisterSubMode - && s.emulateReplaceWithRegister.value()) { + } else if (g.submode == ReplaceWithRegisterSubMode && s.emulateReplaceWithRegister()) { handled = handleReplaceWithRegisterSubMode(input); } else if (g.submode == ReplaceSubMode) { handled = handleReplaceSubMode(input); @@ -4510,7 +4485,7 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input) setTargetColumn(); } else if (input.isControl('a')) { changeNumberTextObject(count()); - } else if (g.gflag && input.is('c') && s.emulateVimCommentary.value()) { + } else if (g.gflag && input.is('c') && s.emulateVimCommentary()) { if (isVisualMode()) { pushUndoState(); @@ -4535,7 +4510,7 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input) pushUndoState(); setAnchor(); } - } else if (g.gflag && input.is('r') && s.emulateReplaceWithRegister.value()) { + } else if (g.gflag && input.is('r') && s.emulateReplaceWithRegister()) { g.submode = ReplaceWithRegisterSubMode; if (isVisualMode()) { dotCommand = visualDotCommand() + QString::number(count()) + "gr"; @@ -4694,7 +4669,7 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input) int repeat = count(); while (--repeat >= 0) redo(); - } else if (input.is('S') && isVisualMode() && s.emulateSurround.value()) { + } else if (input.is('S') && isVisualMode() && s.emulateSurround()) { g.submode = AddSurroundingSubMode; g.subsubmode = SurroundSubSubMode; } else if (input.is('s')) { @@ -4779,7 +4754,7 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input) if (isVisualMode()) { leaveVisualMode(); finishMovement(); - } else if (g.gflag || (g.submode == InvertCaseSubMode && s.tildeOp.value())) { + } else if (g.gflag || (g.submode == InvertCaseSubMode && s.tildeOp())) { if (atEndOfLine()) moveLeft(); setAnchor(); @@ -5197,6 +5172,7 @@ void FakeVimHandler::Private::handleReplaceMode(const Input &input) moveDown(); } else if (input.isKey(Key_Insert)) { g.mode = InsertMode; + q->modeChanged(isInsertMode()); } else if (input.isControl('o')) { enterCommandMode(ReplaceMode); } else { @@ -5394,6 +5370,7 @@ void FakeVimHandler::Private::handleInsertMode(const Input &input) removeText(range); } else if (input.isKey(Key_Insert)) { g.mode = ReplaceMode; + q->modeChanged(isInsertMode()); } else if (input.isKey(Key_Left)) { moveLeft(); } else if (input.isShift(Key_Left) || input.isControl(Key_Left)) { @@ -5425,15 +5402,15 @@ void FakeVimHandler::Private::handleInsertMode(const Input &input) if (!handleInsertInEditor(Input(Qt::Key_Backspace, Qt::NoModifier))) { joinPreviousEditBlock(); if (!m_buffer->lastInsertion.isEmpty() - || s.backspace.value().contains("start") - || s.backspace.value().contains("2")) { + || s.backspace().contains("start") + || s.backspace().contains("2")) { const int line = cursorLine() + 1; const Column col = cursorColumn(); QString data = lineContents(line); const Column ind = indentation(data); if (col.logical <= ind.logical && col.logical && startsWithWhitespace(data, col.physical)) { - const int ts = s.tabStop.value(); + const int ts = s.tabStop(); const int newl = col.logical - 1 - (col.logical - 1) % ts; const QString prefix = tabExpand(newl); setLineContents(line, prefix + data.mid(col.physical)); @@ -5457,20 +5434,22 @@ void FakeVimHandler::Private::handleInsertMode(const Input &input) } else if (input.isKey(Key_PageUp) || input.isControl('b')) { movePageUp(); } else if (input.isKey(Key_Tab)) { - m_buffer->insertState.insertingSpaces = true; - if (s.expandTab.value()) { - const int ts = s.tabStop.value(); - const int col = logicalCursorColumn(); - QString str = QString(ts - col % ts, ' '); - insertText(str); - } else { - insertInInsertMode(input.raw()); + if (q->tabPressedInInsertMode()) { + m_buffer->insertState.insertingSpaces = true; + if (s.expandTab()) { + const int ts = s.tabStop(); + const int col = logicalCursorColumn(); + QString str = QString(ts - col % ts, ' '); + insertText(str); + } else { + insertInInsertMode(input.raw()); + } + m_buffer->insertState.insertingSpaces = false; } - m_buffer->insertState.insertingSpaces = false; } else if (input.isControl('d')) { // remove one level of indentation from the current line - const int shift = s.shiftWidth.value(); - const int tab = s.tabStop.value(); + const int shift = s.shiftWidth(); + const int tab = s.tabStop(); int line = cursorLine() + 1; int pos = firstPositionInLine(line); QString text = lineContents(line); @@ -5513,7 +5492,7 @@ void FakeVimHandler::Private::insertInInsertMode(const QString &text) { joinPreviousEditBlock(); insertText(text); - if (s.smartIndent.value() && isElectricCharacter(text.at(0))) { + if (s.smartIndent() && isElectricCharacter(text.at(0))) { const QString leftText = block().text() .left(position() - 1 - block().position()); if (leftText.simplified().isEmpty()) { @@ -6284,7 +6263,7 @@ bool FakeVimHandler::Private::handleExMoveCommand(const ExCommand &cmd) if (!insertAtEnd) moveUp(1); - if (s.startOfLine.value()) + if (s.startOfLine()) moveToFirstNonBlankOnLine(); if (lastAnchor.line >= startLine && lastAnchor.line <= endLine) @@ -6428,7 +6407,8 @@ bool FakeVimHandler::Private::handleExBangCommand(const ExCommand &cmd) // :! const QString command = QString(cmd.cmd.mid(1) + ' ' + cmd.args).trimmed(); const QString input = replaceText ? selectText(cmd.range) : QString(); - const QString result = getProcessOutput(command, input); + QString result; + q->processOutput(command, input, &result); if (replaceText) { setCurrentRange(cmd.range); @@ -6812,7 +6792,7 @@ QTextCursor FakeVimHandler::Private::search(const SearchData &sd, int startPos, } if (tc.isNull()) { - if (s.wrapScan.value()) { + if (s.wrapScan()) { tc = QTextCursor(document()); tc.movePosition(sd.forward ? StartOfDocument : EndOfDocument); if (sd.forward) @@ -6972,10 +6952,10 @@ void FakeVimHandler::Private::shiftRegionRight(int repeat) std::swap(beginLine, endLine); targetPos = position(); } - if (s.startOfLine.value()) + if (s.startOfLine()) targetPos = firstPositionInLine(beginLine); - const int sw = s.shiftWidth.value(); + const int sw = s.shiftWidth(); g.movetype = MoveLineWise; beginEditBlock(); QTextBlock block = document()->findBlockByLineNumber(beginLine - 1); @@ -7124,7 +7104,7 @@ void FakeVimHandler::Private::setupCharClass() const QChar c = QLatin1Char(i); m_charClass[i] = c.isSpace() ? 0 : 1; } - const QString conf = s.isKeyword.value(); + const QString conf = s.isKeyword(); for (const QString &part : conf.split(',')) { if (part.contains('-')) { const int from = someInt(part.section('-', 0, 0)); @@ -7313,7 +7293,7 @@ int FakeVimHandler::Private::physicalCursorColumn() const int FakeVimHandler::Private::physicalToLogicalColumn (const int physical, const QString &line) const { - const int ts = s.tabStop.value(); + const int ts = s.tabStop(); int p = 0; int logical = 0; while (p < physical) { @@ -7334,7 +7314,7 @@ int FakeVimHandler::Private::physicalToLogicalColumn int FakeVimHandler::Private::logicalToPhysicalColumn (const int logical, const QString &line) const { - const int ts = s.tabStop.value(); + const int ts = s.tabStop(); int physical = 0; for (int l = 0; l < logical && physical < line.size(); ++physical) { QChar c = line.at(physical); @@ -7348,7 +7328,7 @@ int FakeVimHandler::Private::logicalToPhysicalColumn int FakeVimHandler::Private::windowScrollOffset() const { - return qMin(static_cast(s.scrollOff.value()), linesOnScreen() / 2); + return qMin(static_cast(s.scrollOff()), linesOnScreen() / 2); } int FakeVimHandler::Private::logicalCursorColumn() const @@ -7612,7 +7592,7 @@ void FakeVimHandler::Private::transformText(const Range &range, const Transforma void FakeVimHandler::Private::insertText(QTextCursor &tc, const QString &text) { - if (s.passKeys.value()) { + if (s.passKeys()) { if (tc.hasSelection() && text.isEmpty()) { QKeyEvent event(QEvent::KeyPress, Qt::Key_Delete, Qt::NoModifier, QString()); passEventToEditor(event, tc); @@ -7955,7 +7935,7 @@ void FakeVimHandler::Private::joinLines(int count, bool preserveSpace) moveRight(); // If the line we started from is a comment, remove the comment string from the next line - if (startingLineIsComment && s.formatOptions.value().contains('f')) { + if (startingLineIsComment && s.formatOptions().contains('f')) { if (characterAtCursor() == '/' && characterAt(position() + 1) == '/') moveRight(2); else if (characterAtCursor() == '*' || characterAtCursor() == '#') @@ -7973,7 +7953,7 @@ void FakeVimHandler::Private::joinLines(int count, bool preserveSpace) void FakeVimHandler::Private::insertNewLine() { - if (m_buffer->editBlockLevel <= 1 && s.passKeys.value()) { + if (m_buffer->editBlockLevel <= 1 && s.passKeys()) { QKeyEvent event(QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier, "\n"); if (passEventToEditor(event, m_cursor)) return; @@ -7985,7 +7965,7 @@ void FakeVimHandler::Private::insertNewLine() bool FakeVimHandler::Private::handleInsertInEditor(const Input &input) { - if (m_buffer->editBlockLevel > 0 || !s.passKeys.value()) + if (m_buffer->editBlockLevel > 0 || !s.passKeys()) return false; joinPreviousEditBlock(); @@ -8575,6 +8555,8 @@ void FakeVimHandler::Private::enterInsertOrReplaceMode(Mode mode) g.returnToMode = mode; clearLastInsertion(); } + + q->modeChanged(isInsertMode()); } void FakeVimHandler::Private::enterVisualInsertMode(QChar command) @@ -8650,6 +8632,8 @@ void FakeVimHandler::Private::enterCommandMode(Mode returnToMode) g.returnToMode = returnToMode; m_positionPastEnd = false; m_anchorPastEnd = false; + + q->modeChanged(isInsertMode()); } void FakeVimHandler::Private::enterExMode(const QString &contents) @@ -8664,6 +8648,8 @@ void FakeVimHandler::Private::enterExMode(const QString &contents) g.submode = NoSubMode; g.subsubmode = NoSubSubMode; unfocus(); + + q->modeChanged(isInsertMode()); } void FakeVimHandler::Private::recordJump(int position) @@ -8696,7 +8682,7 @@ void FakeVimHandler::Private::jump(int distance) Column FakeVimHandler::Private::indentation(const QString &line) const { - int ts = s.tabStop.value(); + int ts = s.tabStop(); int physical = 0; int logical = 0; int n = line.size(); @@ -8715,8 +8701,8 @@ Column FakeVimHandler::Private::indentation(const QString &line) const QString FakeVimHandler::Private::tabExpand(int n) const { - int ts = s.tabStop.value(); - if (s.expandTab.value() || ts < 1) + int ts = s.tabStop(); + if (s.expandTab() || ts < 1) return QString(n, ' '); return QString(n / ts, '\t') + QString(n % ts, ' '); @@ -8724,10 +8710,10 @@ QString FakeVimHandler::Private::tabExpand(int n) const void FakeVimHandler::Private::insertAutomaticIndentation(bool goingDown, bool forceAutoIndent) { - if (!forceAutoIndent && !s.autoIndent.value() && !s.smartIndent.value()) + if (!forceAutoIndent && !s.autoIndent() && !s.smartIndent()) return; - if (s.smartIndent.value()) { + if (s.smartIndent()) { QTextBlock bl = block(); Range range(bl.position(), bl.position()); indentText(range, '\n'); @@ -8746,7 +8732,7 @@ void FakeVimHandler::Private::insertAutomaticIndentation(bool goingDown, bool fo void FakeVimHandler::Private::handleStartOfLine() { - if (s.startOfLine.value()) + if (s.startOfLine()) moveToFirstNonBlankOnLine(); } @@ -9343,7 +9329,7 @@ void FakeVimHandler::Private::getRegisterType(int *reg, bool *isClipboard, bool *reg = c.toLower().unicode(); if (c == '"') { - QStringList list = s.clipboard.value().split(','); + QStringList list = s.clipboard().split(','); clipboard = list.contains("unnamedplus"); selection = list.contains("unnamed"); } else if (c == '+') { @@ -9397,7 +9383,7 @@ void FakeVimHandler::updateGlobalMarksFilenames(const QString &oldFileName, cons bool FakeVimHandler::eventFilter(QObject *ob, QEvent *ev) { #ifndef FAKEVIM_STANDALONE - if (!fakeVimSettings()->useFakeVim.value()) + if (!settings().useFakeVim()) return QObject::eventFilter(ob, ev); #endif @@ -9558,6 +9544,11 @@ bool FakeVimHandler::jumpToLocalMark(QChar mark, bool backTickMode) return d->jumpToMark(mark, backTickMode); } +bool FakeVimHandler::inFakeVimMode() +{ + return d->m_inFakeVim; +} + } // namespace Internal } // namespace FakeVim diff --git a/src/plugins/fakevim/fakevimhandler.h b/src/plugins/fakevim/fakevimhandler.h index 57955cd2cbd..0b615e1657e 100644 --- a/src/plugins/fakevim/fakevimhandler.h +++ b/src/plugins/fakevim/fakevimhandler.h @@ -61,23 +61,30 @@ enum MessageLevel MessageShowCmd // partial command }; -template -class Signal +template +class Callback; + +template +class Callback { public: - using Callable = std::function; + static constexpr auto IsVoidReturnType = std::is_same_v; + using Function = std::function; + void set(const Function &callable) { m_callable = callable; } - void connect(const Callable &callable) { m_callables.push_back(callable); } - - template - void operator()(Args ...args) const + R operator()(Params... params) { - for (const Callable &callable : m_callables) - callable(args...); - } + if (!m_callable) + return R(); + + if constexpr (IsVoidReturnType) + m_callable(std::forward(params)...); + else + return m_callable(std::forward(params)...); + } private: - std::vector m_callables; + Function m_callable; }; class FakeVimHandler : public QObject @@ -129,34 +136,40 @@ public: bool jumpToLocalMark(QChar mark, bool backTickMode); + bool inFakeVimMode(); + bool eventFilter(QObject *ob, QEvent *ev) override; - Signal commandBufferChanged; - Signal statusDataChanged; - Signal extraInformationChanged; - Signal &selection)> selectionChanged; - Signal highlightMatches; - Signal moveToMatchingParenthesis; - Signal checkForElectricCharacter; - Signal indentRegion; - Signal simpleCompletionRequested; - Signal windowCommandRequested; - Signal findRequested; - Signal findNextRequested; - Signal handleExCommandRequested; - Signal requestDisableBlockSelection; - Signal requestSetBlockSelection; - Signal requestBlockSelection; - Signal requestHasBlockSelection; - Signal foldToggle; - Signal foldAll; - Signal fold; - Signal foldGoTo; - Signal requestJumpToLocalMark; - Signal requestJumpToGlobalMark; - Signal completionRequested; - Signal tabPreviousRequested; - Signal tabNextRequested; + Callback + commandBufferChanged; + Callback statusDataChanged; + Callback extraInformationChanged; + Callback &selection)> selectionChanged; + Callback highlightMatches; + Callback moveToMatchingParenthesis; + Callback checkForElectricCharacter; + Callback indentRegion; + Callback simpleCompletionRequested; + Callback windowCommandRequested; + Callback findRequested; + Callback findNextRequested; + Callback handleExCommandRequested; + Callback requestDisableBlockSelection; + Callback requestSetBlockSelection; + Callback requestBlockSelection; + Callback requestHasBlockSelection; + Callback foldToggle; + Callback foldAll; + Callback fold; + Callback foldGoTo; + Callback requestJumpToLocalMark; + Callback requestJumpToGlobalMark; + Callback completionRequested; + Callback tabPreviousRequested; + Callback tabNextRequested; + Callback modeChanged; + Callback tabPressedInInsertMode; + Callback processOutput; public: class Private; diff --git a/src/plugins/fakevim/fakevimplugin.cpp b/src/plugins/fakevim/fakevimplugin.cpp index 02207de8728..54ae4ca93f8 100644 --- a/src/plugins/fakevim/fakevimplugin.cpp +++ b/src/plugins/fakevim/fakevimplugin.cpp @@ -50,7 +50,7 @@ #include #include #include -#include +#include #include #include @@ -340,149 +340,6 @@ private: using ExCommandMap = QMap; using UserCommandMap = QMap; -class FakeVimOptionPage : public IOptionsPage -{ -public: - FakeVimOptionPage() - { - setId(SETTINGS_ID); - setDisplayName(Tr::tr("General")); - setCategory(SETTINGS_CATEGORY); - setDisplayCategory(Tr::tr("FakeVim")); - setCategoryIconPath(":/fakevim/images/settingscategory_fakevim.png"); - setLayouter([this](QWidget *widget) { return layoutPage(widget); }); - setSettings(fakeVimSettings()); - } - -private: - void layoutPage(QWidget *); - void copyTextEditorSettings(); - void setQtStyle(); - void setPlainStyle(); -}; - -void FakeVimOptionPage::layoutPage(QWidget *widget) -{ - auto copyTextEditorSettings = new QPushButton(Tr::tr("Copy Text Editor Settings")); - auto setQtStyle = new QPushButton(Tr::tr("Set Qt Style")); - auto setPlainStyle = new QPushButton(Tr::tr("Set Plain Style")); - - using namespace Layouting; - FakeVimSettings &s = *fakeVimSettings(); - - Row bools { - Column { - s.autoIndent, - s.smartIndent, - s.expandTab, - s.smartTab, - s.hlSearch, - s.showCmd, - s.startOfLine, - s.passKeys, - s.blinkingCursor - }, - Column { - s.incSearch, - s.useCoreSearch, - s.ignoreCase, - s.smartCase, - s.wrapScan, - s.showMarks, - s.passControlKey, - s.relativeNumber, - s.tildeOp - } - }; - - Row ints { s.shiftWidth, s.tabStop, s.scrollOff, st }; - - Column strings { - s.backspace, - s.isKeyword, - Row {s.readVimRc, s.vimRcPath} - }; - - Column { - s.useFakeVim, - - Group { - title(Tr::tr("Vim Behavior")), - Column { - bools, - ints, - strings - } - }, - - Group { - title(Tr::tr("Plugin Emulation")), - Column { - s.emulateVimCommentary, - s.emulateReplaceWithRegister, - s.emulateArgTextObj, - s.emulateExchange, - s.emulateSurround - } - }, - - Row { copyTextEditorSettings, setQtStyle, setPlainStyle, st }, - st - - }.attachTo(widget); - - s.vimRcPath.setEnabler(&s.readVimRc); - - connect(copyTextEditorSettings, &QAbstractButton::clicked, - this, &FakeVimOptionPage::copyTextEditorSettings); - connect(setQtStyle, &QAbstractButton::clicked, - this, &FakeVimOptionPage::setQtStyle); - connect(setPlainStyle, &QAbstractButton::clicked, - this, &FakeVimOptionPage::setPlainStyle); -} - -void FakeVimOptionPage::copyTextEditorSettings() -{ - FakeVimSettings &s = *fakeVimSettings(); - TabSettings ts = TextEditorSettings::codeStyle()->tabSettings(); - TypingSettings tps = TextEditorSettings::typingSettings(); - s.expandTab.setValue(ts.m_tabPolicy != TabSettings::TabsOnlyTabPolicy); - s.tabStop.setValue(ts.m_tabSize); - s.shiftWidth.setValue(ts.m_indentSize); - s.smartTab.setValue(tps.m_smartBackspaceBehavior - == TypingSettings::BackspaceFollowsPreviousIndents); - s.autoIndent.setValue(true); - s.smartIndent.setValue(tps.m_autoIndent); - s.incSearch.setValue(true); -} - -void FakeVimOptionPage::setQtStyle() -{ - FakeVimSettings &s = *fakeVimSettings(); - s.expandTab.setVolatileValue(true); - s.tabStop.setVolatileValue(4); - s.shiftWidth.setVolatileValue(4); - s.smartTab.setVolatileValue(true); - s.autoIndent.setVolatileValue(true); - s.smartIndent.setVolatileValue(true); - s.incSearch.setVolatileValue(true); - s.backspace.setVolatileValue(QString("indent,eol,start")); - s.passKeys.setVolatileValue(true); -} - -void FakeVimOptionPage::setPlainStyle() -{ - FakeVimSettings &s = *fakeVimSettings(); - s.expandTab.setVolatileValue(false); - s.tabStop.setVolatileValue(8); - s.shiftWidth.setVolatileValue(8); - s.smartTab.setVolatileValue(false); - s.autoIndent.setVolatileValue(false); - s.smartIndent.setVolatileValue(false); - s.incSearch.setVolatileValue(false); - s.backspace.setVolatileValue(QString()); - s.passKeys.setVolatileValue(false); -} /////////////////////////////////////////////////////////////////////// // @@ -522,13 +379,14 @@ public: int cursorPos, int anchorPos, int messageLevel); void handleExCommand(FakeVimHandler *handler, bool *handled, const ExCommand &cmd); - void writeSettings(); void readSettings(); void handleDelayedQuitAll(bool forced); void handleDelayedQuit(bool forced, Core::IEditor *editor); void userActionTriggered(int key); + void updateAllHightLights(); + void switchToFile(int n); int currentFile() const; @@ -539,7 +397,22 @@ signals: void delayedQuitAllRequested(bool forced); public: - QHash m_editorToHandler; + struct HandlerAndData + { +#ifdef Q_OS_WIN + // We need to declare a constructor here, otherwise the MSVC 17.5.x compiler fails to parse + // the "{nullptr}" initializer in the definition below. + // This seems to be a compiler bug, + // see: https://developercommunity.visualstudio.com/t/10351118 + HandlerAndData() + : handler(nullptr) + {} +#endif + FakeVimHandler *handler{nullptr}; + TextEditorWidget::SuggestionBlocker suggestionBlocker; + }; + + QHash m_editorToHandler; void setActionChecked(Id id, bool check); @@ -557,6 +430,8 @@ public: MiniBuffer *m_miniBuffer = nullptr; FakeVimPluginRunData *runData = nullptr; + QString m_lastHighlight; + int m_savedCursorFlashTime = 0; }; @@ -568,12 +443,21 @@ public: enum { CommandRole = Qt::UserRole }; -class FakeVimExCommandsWidget : public CommandMappings +const char exCommandMapGroup[] = "FakeVimExCommand"; +const char userCommandMapGroup[] = "FakeVimUserCommand"; +const char reKey[] = "RegEx"; +const char cmdKey[] = "Cmd"; +const char idKey[] = "Command"; + +class FakeVimExCommandsMappings : public CommandMappings { public: - FakeVimExCommandsWidget(); + FakeVimExCommandsMappings(); + void apply(); protected: + ExCommandMap exCommandMapFromWidget(); + void commandChanged(); void resetToDefault(); void defaultAction() override; @@ -581,113 +465,48 @@ protected: void handleCurrentCommandChanged(QTreeWidgetItem *current); private: - void initialize(); - - ExCommandMap exCommandMapFromWidget(); - QGroupBox *m_commandBox; FancyLineEdit *m_commandEdit; - - friend class FakeVimExCommandsPage; // allow the page accessing the ExCommandMaps }; -FakeVimExCommandsWidget::FakeVimExCommandsWidget() +FakeVimExCommandsMappings::FakeVimExCommandsMappings() { setPageTitle(Tr::tr("Ex Command Mapping")); setTargetHeader(Tr::tr("Ex Trigger Expression")); setImportExportEnabled(false); - connect(this, &FakeVimExCommandsWidget::currentCommandChanged, - this, &FakeVimExCommandsWidget::handleCurrentCommandChanged); + connect(this, &FakeVimExCommandsMappings::currentCommandChanged, + this, &FakeVimExCommandsMappings::handleCurrentCommandChanged); m_commandBox = new QGroupBox(Tr::tr("Ex Command"), this); m_commandBox->setEnabled(false); - auto boxLayout = new QHBoxLayout(m_commandBox); + auto commandBoxLayout = new QVBoxLayout(m_commandBox); + auto boxLayout = new QHBoxLayout; + commandBoxLayout->addLayout(boxLayout); m_commandEdit = new FancyLineEdit(m_commandBox); m_commandEdit->setFiltering(true); m_commandEdit->setPlaceholderText(QString()); connect(m_commandEdit, &FancyLineEdit::textChanged, - this, &FakeVimExCommandsWidget::commandChanged); + this, &FakeVimExCommandsMappings::commandChanged); + m_commandEdit->setValidationFunction([](FancyLineEdit *e, QString *){ + return QRegularExpression(e->text()).isValid(); + }); auto resetButton = new QPushButton(Tr::tr("Reset"), m_commandBox); resetButton->setToolTip(Tr::tr("Reset to default.")); connect(resetButton, &QPushButton::clicked, - this, &FakeVimExCommandsWidget::resetToDefault); + this, &FakeVimExCommandsMappings::resetToDefault); boxLayout->addWidget(new QLabel(Tr::tr("Regular expression:"))); boxLayout->addWidget(m_commandEdit); boxLayout->addWidget(resetButton); + auto infoLabel = new InfoLabel(Tr::tr("Invalid regular expression."), InfoLabel::Error); + infoLabel->setVisible(false); + connect(m_commandEdit, &FancyLineEdit::validChanged, [infoLabel](bool valid){ + infoLabel->setVisible(!valid); + }); + commandBoxLayout->addWidget(infoLabel); layout()->addWidget(m_commandBox); - initialize(); -} - -class FakeVimExCommandsPage : public IOptionsPage -{ -public: - FakeVimExCommandsPage() - { - setId(SETTINGS_EX_CMDS_ID); - setDisplayName(Tr::tr("Ex Command Mapping")); - setCategory(SETTINGS_CATEGORY); - } - - QWidget *widget() override - { - if (!m_widget) - m_widget = new FakeVimExCommandsWidget; - return m_widget; - } - - void apply() override; - void finish() override {} - -private: - QPointer m_widget; -}; - - -const char exCommandMapGroup[] = "FakeVimExCommand"; -const char userCommandMapGroup[] = "FakeVimUserCommand"; -const char reKey[] = "RegEx"; -const char cmdKey[] = "Cmd"; -const char idKey[] = "Command"; - -void FakeVimExCommandsPage::apply() -{ - if (!m_widget) // page has not been shown at all - return; - // now save the mappings if necessary - const ExCommandMap &newMapping = m_widget->exCommandMapFromWidget(); - ExCommandMap &globalCommandMapping = dd->m_exCommandMap; - - if (newMapping != globalCommandMapping) { - const ExCommandMap &defaultMap = dd->m_defaultExCommandMap; - QSettings *settings = ICore::settings(); - settings->beginWriteArray(exCommandMapGroup); - int count = 0; - using Iterator = ExCommandMap::const_iterator; - const Iterator end = newMapping.constEnd(); - for (Iterator it = newMapping.constBegin(); it != end; ++it) { - const QString id = it.key(); - const QRegularExpression re = it.value(); - - if ((defaultMap.contains(id) && defaultMap[id] != re) - || (!defaultMap.contains(id) && !re.pattern().isEmpty())) { - settings->setArrayIndex(count); - settings->setValue(idKey, id); - settings->setValue(reKey, re.pattern()); - ++count; - } - } - settings->endArray(); - globalCommandMapping.clear(); - globalCommandMapping.insert(defaultMap); - globalCommandMapping.insert(newMapping); - } -} - -void FakeVimExCommandsWidget::initialize() -{ QMap sections; const QList commands = ActionManager::commands(); @@ -727,7 +546,30 @@ void FakeVimExCommandsWidget::initialize() handleCurrentCommandChanged(nullptr); } -void FakeVimExCommandsWidget::handleCurrentCommandChanged(QTreeWidgetItem *current) +ExCommandMap FakeVimExCommandsMappings::exCommandMapFromWidget() +{ + ExCommandMap map; + int n = commandList()->topLevelItemCount(); + for (int i = 0; i != n; ++i) { + QTreeWidgetItem *section = commandList()->topLevelItem(i); + int m = section->childCount(); + for (int j = 0; j != m; ++j) { + QTreeWidgetItem *item = section->child(j); + const QString name = item->data(0, CommandRole).toString(); + const QString regex = item->data(2, Qt::DisplayRole).toString(); + const QString pattern = dd->m_defaultExCommandMap.value(name).pattern(); + if ((regex.isEmpty() && pattern.isEmpty()) + || (!regex.isEmpty() && pattern == regex)) + continue; + const QRegularExpression expression(regex); + if (expression.isValid()) + map[name] = expression; + } + } + return map; +} + +void FakeVimExCommandsMappings::handleCurrentCommandChanged(QTreeWidgetItem *current) { if (current) { m_commandEdit->setText(current->text(2)); @@ -738,7 +580,7 @@ void FakeVimExCommandsWidget::handleCurrentCommandChanged(QTreeWidgetItem *curre } } -void FakeVimExCommandsWidget::commandChanged() +void FakeVimExCommandsMappings::commandChanged() { QTreeWidgetItem *current = commandList()->currentItem(); if (!current) @@ -753,7 +595,7 @@ void FakeVimExCommandsWidget::commandChanged() setModified(current, regex != dd->m_defaultExCommandMap[name].pattern()); } -void FakeVimExCommandsWidget::resetToDefault() +void FakeVimExCommandsMappings::resetToDefault() { QTreeWidgetItem *current = commandList()->currentItem(); if (!current) @@ -765,12 +607,12 @@ void FakeVimExCommandsWidget::resetToDefault() m_commandEdit->setText(regex); } -void FakeVimExCommandsWidget::defaultAction() +void FakeVimExCommandsMappings::defaultAction() { - int n = commandList()->topLevelItemCount(); + const int n = commandList()->topLevelItemCount(); for (int i = 0; i != n; ++i) { QTreeWidgetItem *section = commandList()->topLevelItem(i); - int m = section->childCount(); + const int m = section->childCount(); for (int j = 0; j != m; ++j) { QTreeWidgetItem *item = section->child(j); const QString name = item->data(0, CommandRole).toString(); @@ -785,6 +627,61 @@ void FakeVimExCommandsWidget::defaultAction() } } +void FakeVimExCommandsMappings::apply() +{ + // now save the mappings if necessary + const ExCommandMap &newMapping = exCommandMapFromWidget(); + ExCommandMap &globalCommandMapping = dd->m_exCommandMap; + + if (newMapping != globalCommandMapping) { + const ExCommandMap &defaultMap = dd->m_defaultExCommandMap; + QSettings *settings = ICore::settings(); + settings->beginWriteArray(exCommandMapGroup); + int count = 0; + using Iterator = ExCommandMap::const_iterator; + const Iterator end = newMapping.constEnd(); + for (Iterator it = newMapping.constBegin(); it != end; ++it) { + const QString id = it.key(); + const QRegularExpression re = it.value(); + + if ((defaultMap.contains(id) && defaultMap[id] != re) + || (!defaultMap.contains(id) && !re.pattern().isEmpty())) { + settings->setArrayIndex(count); + settings->setValue(idKey, id); + settings->setValue(reKey, re.pattern()); + ++count; + } + } + settings->endArray(); + globalCommandMapping.clear(); + globalCommandMapping.insert(defaultMap); + globalCommandMapping.insert(newMapping); + } +} + +class FakeVimExCommandsPageWidget : public IOptionsPageWidget +{ +public: + FakeVimExCommandsPageWidget() + { + auto exCommands = new FakeVimExCommandsMappings; + setOnApply([exCommands] { exCommands->apply(); }); + Layouting::Column { exCommands, Layouting::noMargin }.attachTo(this); + } +}; + +class FakeVimExCommandsPage : public IOptionsPage +{ +public: + FakeVimExCommandsPage() + { + setId(SETTINGS_EX_CMDS_ID); + setDisplayName(Tr::tr("Ex Command Mapping")); + setCategory(SETTINGS_CATEGORY); + setWidgetCreator([] { return new FakeVimExCommandsPageWidget; }); + } +}; + /////////////////////////////////////////////////////////////////////// // // FakeVimUserCommandsPage @@ -797,28 +694,17 @@ public: FakeVimUserCommandsModel() { m_commandMap = dd->m_userCommandMap; } UserCommandMap commandMap() const { return m_commandMap; } - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex &parent) const override; - QVariant data(const QModelIndex &index, int role) const override; - bool setData(const QModelIndex &index, const QVariant &data, int role) override; - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; + int rowCount(const QModelIndex &parent) const final { return parent.isValid() ? 0 : 9; } + int columnCount(const QModelIndex &parent) const final { return parent.isValid() ? 0 : 2; } + QVariant data(const QModelIndex &index, int role) const final; + bool setData(const QModelIndex &index, const QVariant &data, int role) final; + QVariant headerData(int section, Qt::Orientation orientation, int role) const final; + Qt::ItemFlags flags(const QModelIndex &index) const final; private: UserCommandMap m_commandMap; }; -int FakeVimUserCommandsModel::rowCount(const QModelIndex &parent) const -{ - return parent.isValid() ? 0 : 9; -} - -int FakeVimUserCommandsModel::columnCount(const QModelIndex &parent) const -{ - return parent.isValid() ? 0 : 2; -} - - QVariant FakeVimUserCommandsModel::headerData(int section, Qt::Orientation orient, int role) const { @@ -862,6 +748,60 @@ public: } }; +class FakeVimUserCommandsPageWidget : public IOptionsPageWidget +{ +public: + FakeVimUserCommandsPageWidget(FakeVimUserCommandsModel *model) + : m_model(model) + { + auto widget = new QTreeView; + widget->setModel(m_model); + widget->resizeColumnToContents(0); + + auto delegate = new FakeVimUserCommandsDelegate(widget); + widget->setItemDelegateForColumn(1, delegate); + + auto layout = new QGridLayout(this); + layout->addWidget(widget, 0, 0); + setLayout(layout); + } + +private: + void apply() final + { + // now save the mappings if necessary + const UserCommandMap ¤t = m_model->commandMap(); + UserCommandMap &userMap = dd->m_userCommandMap; + + if (current != userMap) { + QSettings *settings = ICore::settings(); + settings->beginWriteArray(userCommandMapGroup); + int count = 0; + using Iterator = UserCommandMap::const_iterator; + const Iterator end = current.constEnd(); + for (Iterator it = current.constBegin(); it != end; ++it) { + const int key = it.key(); + const QString cmd = it.value(); + + if ((dd->m_defaultUserCommandMap.contains(key) + && dd->m_defaultUserCommandMap[key] != cmd) + || (!dd->m_defaultUserCommandMap.contains(key) && !cmd.isEmpty())) { + settings->setArrayIndex(count); + settings->setValue(idKey, key); + settings->setValue(cmdKey, cmd); + ++count; + } + } + settings->endArray(); + userMap.clear(); + userMap.insert(dd->m_defaultUserCommandMap); + userMap.insert(current); + } + } + + FakeVimUserCommandsModel *m_model; +}; + class FakeVimUserCommandsPage : public IOptionsPage { public: @@ -870,76 +810,13 @@ public: setId(SETTINGS_USER_CMDS_ID); setDisplayName(Tr::tr("User Command Mapping")); setCategory(SETTINGS_CATEGORY); + setWidgetCreator([this] { return new FakeVimUserCommandsPageWidget(&m_model); }); } - void apply() override; - void finish() override {} - - QWidget *widget() override; - void initialize() {} - UserCommandMap currentCommandMap() { return m_model->commandMap(); } - private: - QPointer m_widget; - FakeVimUserCommandsModel *m_model = nullptr; + FakeVimUserCommandsModel m_model; }; -QWidget *FakeVimUserCommandsPage::widget() -{ - if (!m_widget) { - m_widget = new QWidget; - - m_model = new FakeVimUserCommandsModel; - auto widget = new QTreeView; - m_model->setParent(widget); - widget->setModel(m_model); - widget->resizeColumnToContents(0); - - auto delegate = new FakeVimUserCommandsDelegate(widget); - widget->setItemDelegateForColumn(1, delegate); - - auto layout = new QGridLayout(m_widget); - layout->addWidget(widget, 0, 0); - m_widget->setLayout(layout); - } - return m_widget; -} - -void FakeVimUserCommandsPage::apply() -{ - if (!m_widget) // page has not been shown at all - return; - - // now save the mappings if necessary - const UserCommandMap ¤t = currentCommandMap(); - UserCommandMap &userMap = dd->m_userCommandMap; - - if (current != userMap) { - QSettings *settings = ICore::settings(); - settings->beginWriteArray(userCommandMapGroup); - int count = 0; - using Iterator = UserCommandMap::const_iterator; - const Iterator end = current.constEnd(); - for (Iterator it = current.constBegin(); it != end; ++it) { - const int key = it.key(); - const QString cmd = it.value(); - - if ((dd->m_defaultUserCommandMap.contains(key) - && dd->m_defaultUserCommandMap[key] != cmd) - || (!dd->m_defaultUserCommandMap.contains(key) && !cmd.isEmpty())) { - settings->setArrayIndex(count); - settings->setValue(idKey, key); - settings->setValue(cmdKey, cmd); - ++count; - } - } - settings->endArray(); - userMap.clear(); - userMap.insert(dd->m_defaultUserCommandMap); - userMap.insert(current); - } -} - /////////////////////////////////////////////////////////////////////// // @@ -1101,7 +978,7 @@ IAssistProcessor *FakeVimCompletionAssistProvider::createProcessor(const AssistI class FakeVimPluginRunData { public: - FakeVimOptionPage optionsPage; + FakeVimSettings settings; FakeVimExCommandsPage exCommandsPage; FakeVimUserCommandsPage userCommandsPage; @@ -1173,7 +1050,7 @@ void FakeVimPluginPrivate::initialize() Command *cmd = nullptr; - cmd = ActionManager::registerAction(fakeVimSettings()->useFakeVim.action(), + cmd = ActionManager::registerAction(settings().useFakeVim.action(), INSTALL_HANDLER, Context(Core::Constants::C_GLOBAL), true); cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? Tr::tr("Meta+Shift+Y,Meta+Shift+Y") : Tr::tr("Alt+Y,Alt+Y"))); @@ -1211,7 +1088,7 @@ void FakeVimPluginPrivate::initialize() connect(DocumentManager::instance(), &DocumentManager::documentRenamed, this, &FakeVimPluginPrivate::documentRenamed); - FakeVimSettings &s = *fakeVimSettings(); + FakeVimSettings &s = settings(); connect(&s.useFakeVim, &FvBoolAspect::valueChanged, this, &FakeVimPluginPrivate::setUseFakeVim); connect(&s.readVimRc, &FvBaseAspect::changed, @@ -1229,16 +1106,16 @@ void FakeVimPluginPrivate::initialize() connect(this, &FakeVimPluginPrivate::delayedQuitAllRequested, this, &FakeVimPluginPrivate::handleDelayedQuitAll, Qt::QueuedConnection); - setCursorBlinking(s.blinkingCursor.value()); + setCursorBlinking(s.blinkingCursor()); } void FakeVimPluginPrivate::userActionTriggered(int key) { IEditor *editor = EditorManager::currentEditor(); - FakeVimHandler *handler = m_editorToHandler[editor]; + FakeVimHandler *handler = m_editorToHandler[editor].handler; if (handler) { // If disabled, enable FakeVim mode just for single user command. - bool enableFakeVim = !fakeVimSettings()->useFakeVim.value(); + bool enableFakeVim = !settings().useFakeVim(); if (enableFakeVim) setUseFakeVimInternal(true); @@ -1250,37 +1127,41 @@ void FakeVimPluginPrivate::userActionTriggered(int key) } } +void FakeVimPluginPrivate::updateAllHightLights() +{ + const QList editors = EditorManager::visibleEditors(); + for (IEditor *editor : editors) { + QWidget *w = editor->widget(); + if (auto find = Aggregation::query(w)) + find->highlightAll(m_lastHighlight, FindRegularExpression | FindCaseSensitively); + } +} + void FakeVimPluginPrivate::createRelativeNumberWidget(IEditor *editor) { if (auto textEditor = TextEditorWidget::fromEditor(editor)) { auto relativeNumbers = new RelativeNumbersColumn(textEditor); - connect(&fakeVimSettings()->relativeNumber, &FvBaseAspect::changed, + connect(&settings().relativeNumber, &FvBaseAspect::changed, relativeNumbers, &QObject::deleteLater); - connect(&fakeVimSettings()->useFakeVim, &FvBaseAspect::changed, + connect(&settings().useFakeVim, &FvBaseAspect::changed, relativeNumbers, &QObject::deleteLater); relativeNumbers->show(); } } -void FakeVimPluginPrivate::writeSettings() -{ - QSettings *settings = ICore::settings(); - fakeVimSettings()->writeSettings(settings); -} - void FakeVimPluginPrivate::readSettings() { QSettings *settings = ICore::settings(); - fakeVimSettings()->readSettings(settings); - m_exCommandMap = m_defaultExCommandMap; int size = settings->beginReadArray(exCommandMapGroup); for (int i = 0; i < size; ++i) { settings->setArrayIndex(i); const QString id = settings->value(idKey).toString(); const QString re = settings->value(reKey).toString(); - m_exCommandMap[id] = QRegularExpression(re); + const QRegularExpression regEx(re); + if (regEx.isValid()) + m_exCommandMap[id] = regEx; } settings->endArray(); @@ -1300,9 +1181,9 @@ void FakeVimPluginPrivate::maybeReadVimRc() //qDebug() << theFakeVimSetting(ConfigReadVimRc) // << theFakeVimSetting(ConfigReadVimRc)->value(); //qDebug() << theFakeVimSetting(ConfigShiftWidth)->value(); - if (!fakeVimSettings()->readVimRc.value()) + if (!settings().readVimRc()) return; - QString fileName = fakeVimSettings()->vimRcPath.value(); + QString fileName = settings().vimRcPath(); if (fileName.isEmpty()) { fileName = QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + QLatin1String(HostOsInfo::isWindowsHost() ? "/_vimrc" : "/.vimrc"); @@ -1312,7 +1193,6 @@ void FakeVimPluginPrivate::maybeReadVimRc() QPlainTextEdit editor; FakeVimHandler handler(&editor); handler.handleCommand("source " + fileName); - //writeSettings(); //qDebug() << theFakeVimSetting(ConfigShiftWidth)->value(); } @@ -1549,29 +1429,53 @@ void FakeVimPluginPrivate::editorOpened(IEditor *editor) // the handler might have triggered the deletion of the editor: // make sure that it can return before being deleted itself new DeferredDeleter(widget, handler); - m_editorToHandler[editor] = handler; + m_editorToHandler[editor].handler = handler; - handler->extraInformationChanged.connect([this](const QString &text) { + handler->extraInformationChanged.set([this](const QString &text) { EditorManager::splitSideBySide(); QString title = "stdout.txt"; IEditor *iedit = EditorManager::openEditorWithContents(Id(), &title, text.toUtf8()); EditorManager::activateEditor(iedit); - FakeVimHandler *handler = m_editorToHandler.value(iedit, nullptr); + FakeVimHandler *handler = m_editorToHandler.value(iedit, {}).handler; QTC_ASSERT(handler, return); handler->handleCommand("0"); }); - handler->commandBufferChanged - .connect([this, handler](const QString &contents, int cursorPos, int anchorPos, int messageLevel) { - showCommandBuffer(handler, contents, cursorPos, anchorPos, messageLevel); - }); + handler->commandBufferChanged.set( + [this, handler](const QString &contents, int cursorPos, int anchorPos, int messageLevel) { + showCommandBuffer(handler, contents, cursorPos, anchorPos, messageLevel); + }); - handler->selectionChanged.connect([tew](const QList &selection) { + handler->selectionChanged.set([tew](const QList &selection) { if (tew) tew->setExtraSelections(TextEditorWidget::FakeVimSelection, selection); }); - handler->highlightMatches.connect([](const QString &needle) { + handler->tabPressedInInsertMode.set([tew]() { + auto suggestion = tew->currentSuggestion(); + if (suggestion) { + suggestion->apply(); + return false; + } + + return true; + }); + + handler->modeChanged.set([tew, this, editor](bool insertMode) { + HandlerAndData &handlerAndData = m_editorToHandler[editor]; + if (!handlerAndData.handler->inFakeVimMode()) + return; + + // We don't want to show suggestions unless we are in insert mode. + if (insertMode != (handlerAndData.suggestionBlocker == nullptr)) + handlerAndData.suggestionBlocker = insertMode ? nullptr : tew->blockSuggestions(); + + if (tew) + tew->clearSuggestion(); + }); + + handler->highlightMatches.set([this](const QString &needle) { + m_lastHighlight = needle; for (IEditor *editor : EditorManager::visibleEditors()) { QWidget *w = editor->widget(); if (auto find = Aggregation::query(w)) @@ -1579,7 +1483,7 @@ void FakeVimPluginPrivate::editorOpened(IEditor *editor) } }); - handler->moveToMatchingParenthesis.connect([](bool *moved, bool *forward, QTextCursor *cursor) { + handler->moveToMatchingParenthesis.set([](bool *moved, bool *forward, QTextCursor *cursor) { *moved = false; bool undoFakeEOL = false; @@ -1612,14 +1516,14 @@ void FakeVimPluginPrivate::editorOpened(IEditor *editor) } }); - handler->indentRegion.connect([tew](int beginBlock, int endBlock, QChar typedChar) { + handler->indentRegion.set([tew](int beginBlock, int endBlock, QChar typedChar) { if (!tew) return; TabSettings tabSettings; - tabSettings.m_indentSize = fakeVimSettings()->shiftWidth.value(); - tabSettings.m_tabSize = fakeVimSettings()->tabStop.value(); - tabSettings.m_tabPolicy = fakeVimSettings()->expandTab.value() + tabSettings.m_indentSize = settings().shiftWidth(); + tabSettings.m_tabSize = settings().tabStop(); + tabSettings.m_tabPolicy = settings().expandTab() ? TabSettings::SpacesOnlyTabPolicy : TabSettings::TabsOnlyTabPolicy; tabSettings.m_continuationAlignBehavior = tew->textDocument()->tabSettings().m_continuationAlignBehavior; @@ -1645,17 +1549,17 @@ void FakeVimPluginPrivate::editorOpened(IEditor *editor) } }); - handler->checkForElectricCharacter.connect([tew](bool *result, QChar c) { + handler->checkForElectricCharacter.set([tew](bool *result, QChar c) { if (tew) *result = tew->textDocument()->indenter()->isElectricCharacter(c); }); - handler->requestDisableBlockSelection.connect([tew] { + handler->requestDisableBlockSelection.set([tew] { if (tew) tew->setTextCursor(tew->textCursor()); }); - handler->requestSetBlockSelection.connect([tew](const QTextCursor &cursor) { + handler->requestSetBlockSelection.set([tew](const QTextCursor &cursor) { if (tew) { const TabSettings &tabs = tew->textDocument()->tabSettings(); MultiTextCursor mtc; @@ -1679,7 +1583,7 @@ void FakeVimPluginPrivate::editorOpened(IEditor *editor) } }); - handler->requestBlockSelection.connect([tew](QTextCursor *cursor) { + handler->requestBlockSelection.set([tew](QTextCursor *cursor) { if (tew && cursor) { MultiTextCursor mtc = tew->multiTextCursor(); *cursor = mtc.cursors().first(); @@ -1687,16 +1591,16 @@ void FakeVimPluginPrivate::editorOpened(IEditor *editor) } }); - handler->requestHasBlockSelection.connect([tew](bool *on) { + handler->requestHasBlockSelection.set([tew](bool *on) { if (tew && on) *on = tew->multiTextCursor().hasMultipleCursors(); }); - handler->simpleCompletionRequested.connect([this, handler](const QString &needle, bool forward) { + handler->simpleCompletionRequested.set([this, handler](const QString &needle, bool forward) { runData->wordProvider.setActive(needle, forward, handler); }); - handler->windowCommandRequested.connect([this, handler](const QString &map, int count) { + handler->windowCommandRequested.set([this, handler](const QString &map, int count) { // normalize mapping const QString key = map.toUpper(); @@ -1726,22 +1630,22 @@ void FakeVimPluginPrivate::editorOpened(IEditor *editor) qDebug() << "UNKNOWN WINDOW COMMAND: " << map; }); - handler->findRequested.connect([](bool reverse) { + handler->findRequested.set([](bool reverse) { Find::setUseFakeVim(true); Find::openFindToolBar(reverse ? Find::FindBackwardDirection : Find::FindForwardDirection); }); - handler->findNextRequested.connect([](bool reverse) { + handler->findNextRequested.set([](bool reverse) { triggerAction(reverse ? Core::Constants::FIND_PREVIOUS : Core::Constants::FIND_NEXT); }); - handler->foldToggle.connect([this, handler](int depth) { + handler->foldToggle.set([this, handler](int depth) { QTextBlock block = handler->textCursor().block(); fold(handler, depth, !TextDocumentLayout::isFolded(block)); }); - handler->foldAll.connect([handler](bool fold) { + handler->foldAll.set([handler](bool fold) { QTextDocument *document = handler->textCursor().document(); auto documentLayout = qobject_cast(document->documentLayout()); QTC_ASSERT(documentLayout, return); @@ -1756,11 +1660,9 @@ void FakeVimPluginPrivate::editorOpened(IEditor *editor) documentLayout->emitDocumentSizeChanged(); }); - handler->fold.connect([this, handler](int depth, bool dofold) { - fold(handler, depth, dofold); - }); + handler->fold.set([this, handler](int depth, bool dofold) { fold(handler, depth, dofold); }); - handler->foldGoTo.connect([handler](int count, bool current) { + handler->foldGoTo.set([handler](int count, bool current) { QTextCursor tc = handler->textCursor(); QTextBlock block = tc.block(); @@ -1812,44 +1714,48 @@ void FakeVimPluginPrivate::editorOpened(IEditor *editor) } }); - handler->requestJumpToGlobalMark.connect( + handler->requestJumpToGlobalMark.set( [this](QChar mark, bool backTickMode, const QString &fileName) { if (IEditor *iedit = EditorManager::openEditor(FilePath::fromString(fileName))) { - if (FakeVimHandler *handler = m_editorToHandler.value(iedit, nullptr)) + if (FakeVimHandler *handler = m_editorToHandler.value(iedit, {}).handler) handler->jumpToLocalMark(mark, backTickMode); } }); - handler->handleExCommandRequested.connect([this, handler](bool *handled, const ExCommand &cmd) { + handler->handleExCommandRequested.set([this, handler](bool *handled, const ExCommand &cmd) { handleExCommand(handler, handled, cmd); }); - handler->tabNextRequested.connect([] { - triggerAction(Core::Constants::GOTONEXTINHISTORY); - }); + handler->tabNextRequested.set([] { triggerAction(Core::Constants::GOTONEXTINHISTORY); }); - handler->tabPreviousRequested.connect([] { - triggerAction(Core::Constants::GOTOPREVINHISTORY); - }); + handler->tabPreviousRequested.set([] { triggerAction(Core::Constants::GOTOPREVINHISTORY); }); - handler->completionRequested.connect([this, tew] { + handler->completionRequested.set([this, tew] { if (tew) tew->invokeAssist(Completion, &runData->wordProvider); }); - connect(ICore::instance(), &ICore::saveSettingsRequested, - this, &FakeVimPluginPrivate::writeSettings); + handler->processOutput.set([](const QString &command, const QString &input, QString *output) { + Process proc; + proc.setCommand(Utils::CommandLine::fromUserInput(command)); + proc.setWriteData(input.toLocal8Bit()); + proc.start(); + // FIXME: Process should be interruptable by user. + // Solution is to create a QObject for each process and emit finished state. + proc.waitForFinished(); + *output = proc.cleanedStdOut(); + }); handler->setCurrentFileName(editor->document()->filePath().toString()); handler->installEventFilter(); // pop up the bar - if (fakeVimSettings()->useFakeVim.value()) { + if (settings().useFakeVim()) { resetCommandBuffer(); handler->setupWidget(); - if (fakeVimSettings()->relativeNumber.value()) + if (settings().relativeNumber()) createRelativeNumberWidget(editor); } } @@ -1862,7 +1768,7 @@ void FakeVimPluginPrivate::editorAboutToClose(IEditor *editor) void FakeVimPluginPrivate::currentEditorAboutToChange(IEditor *editor) { - if (FakeVimHandler *handler = m_editorToHandler.value(editor, 0)) + if (FakeVimHandler *handler = m_editorToHandler.value(editor, {}).handler) handler->enterCommandMode(); } @@ -1880,9 +1786,9 @@ void FakeVimPluginPrivate::documentRenamed( void FakeVimPluginPrivate::renameFileNameInEditors(const FilePath &oldPath, const FilePath &newPath) { - for (FakeVimHandler *handler : m_editorToHandler) { - if (handler->currentFileName() == oldPath.toString()) - handler->setCurrentFileName(newPath.toString()); + for (const HandlerAndData &handlerAndData : m_editorToHandler) { + if (handlerAndData.handler->currentFileName() == oldPath.toString()) + handlerAndData.handler->setCurrentFileName(newPath.toString()); } } @@ -1891,8 +1797,8 @@ void FakeVimPluginPrivate::setUseFakeVim(bool on) //qDebug() << "SET USE FAKEVIM" << on; Find::setUseFakeVim(on); setUseFakeVimInternal(on); - setShowRelativeLineNumbers(fakeVimSettings()->relativeNumber.value()); - setCursorBlinking(fakeVimSettings()->blinkingCursor.value()); + setShowRelativeLineNumbers(settings().relativeNumber()); + setCursorBlinking(settings().blinkingCursor()); } void FakeVimPluginPrivate::setUseFakeVimInternal(bool on) @@ -1901,23 +1807,26 @@ void FakeVimPluginPrivate::setUseFakeVimInternal(bool on) //ICore *core = ICore::instance(); //core->updateAdditionalContexts(Context(FAKEVIM_CONTEXT), // Context()); - for (FakeVimHandler *handler : m_editorToHandler) - handler->setupWidget(); + for (const HandlerAndData &handlerAndData : m_editorToHandler) + handlerAndData.handler->setupWidget(); } else { //ICore *core = ICore::instance(); //core->updateAdditionalContexts(Context(), // Context(FAKEVIM_CONTEXT)); resetCommandBuffer(); - for (auto it = m_editorToHandler.constBegin(); it != m_editorToHandler.constEnd(); ++it) { - if (auto textDocument = qobject_cast(it.key()->document())) - it.value()->restoreWidget(textDocument->tabSettings().m_tabSize); + for (auto it = m_editorToHandler.begin(); it != m_editorToHandler.end(); ++it) { + if (auto textDocument = qobject_cast(it.key()->document())) { + HandlerAndData &handlerAndData = it.value(); + handlerAndData.handler->restoreWidget(textDocument->tabSettings().m_tabSize); + handlerAndData.suggestionBlocker.reset(); + } } } } void FakeVimPluginPrivate::setShowRelativeLineNumbers(bool on) { - if (on && fakeVimSettings()->useFakeVim.value()) { + if (on && settings().useFakeVim()) { for (auto it = m_editorToHandler.constBegin(); it != m_editorToHandler.constEnd(); ++it) createRelativeNumberWidget(it.key()); } @@ -1928,7 +1837,7 @@ void FakeVimPluginPrivate::setCursorBlinking(bool on) if (m_savedCursorFlashTime == 0) m_savedCursorFlashTime = QGuiApplication::styleHints()->cursorFlashTime(); - const bool blink = on || !fakeVimSettings()->useFakeVim.value(); + const bool blink = on || !settings().useFakeVim(); QGuiApplication::styleHints()->setCursorFlashTime(blink ? m_savedCursorFlashTime : 0); } @@ -1945,11 +1854,22 @@ void FakeVimPluginPrivate::handleExCommand(FakeVimHandler *handler, bool *handle if (editor) editor->setFocus(); + auto editorFromHandler = [this, handler]() -> Core::IEditor * { + auto itEditor = std::find_if(m_editorToHandler.cbegin(), + m_editorToHandler.cend(), + [handler](const HandlerAndData &handlerAndData) { + return handlerAndData.handler == handler; + }); + if (itEditor != m_editorToHandler.cend()) + return itEditor.key(); + return nullptr; + }; + *handled = true; if ((cmd.matches("w", "write") || cmd.cmd == "wq") && cmd.args.isEmpty()) { // :w[rite] bool saved = false; - IEditor *editor = m_editorToHandler.key(handler); + IEditor *editor = editorFromHandler(); const QString fileName = handler->currentFileName(); if (editor && editor->document()->filePath().toString() == fileName) { triggerAction(Core::Constants::SAVE); @@ -1961,7 +1881,7 @@ void FakeVimPluginPrivate::handleExCommand(FakeVimHandler *handler, bool *handle handler->showMessage(MessageInfo, Tr::tr("\"%1\" %2 %3L, %4C written") .arg(fileName).arg(' ').arg(ba.count('\n')).arg(ba.size())); if (cmd.cmd == "wq") - emit delayedQuitRequested(cmd.hasBang, m_editorToHandler.key(handler)); + emit delayedQuitRequested(cmd.hasBang, editor); } } } @@ -1980,16 +1900,18 @@ void FakeVimPluginPrivate::handleExCommand(FakeVimHandler *handler, bool *handle emit delayedQuitAllRequested(cmd.hasBang); } else if (cmd.matches("q", "quit")) { // :q[uit] - emit delayedQuitRequested(cmd.hasBang, m_editorToHandler.key(handler)); + emit delayedQuitRequested(cmd.hasBang, editorFromHandler()); } else if (cmd.matches("qa", "qall")) { // :qa[ll] emit delayedQuitAllRequested(cmd.hasBang); } else if (cmd.matches("sp", "split")) { // :sp[lit] triggerAction(Core::Constants::SPLIT); + updateAllHightLights(); } else if (cmd.matches("vs", "vsplit")) { // :vs[plit] triggerAction(Core::Constants::SPLIT_SIDE_BY_SIDE); + updateAllHightLights(); } else if (cmd.matches("mak", "make")) { // :mak[e][!] [arguments] triggerAction(ProjectExplorer::Constants::BUILD); @@ -2059,7 +1981,7 @@ void FakeVimPluginPrivate::handleDelayedQuitAll(bool forced) void FakeVimPluginPrivate::quitFakeVim() { - fakeVimSettings()->useFakeVim.setValue(false); + settings().useFakeVim.setValue(false); } void FakeVimPluginPrivate::resetCommandBuffer() @@ -2096,28 +2018,6 @@ void FakeVimPluginPrivate::switchToFile(int n) EditorManager::activateEditorForEntry(DocumentModel::entries().at(n)); } -ExCommandMap FakeVimExCommandsWidget::exCommandMapFromWidget() -{ - ExCommandMap map; - int n = commandList()->topLevelItemCount(); - for (int i = 0; i != n; ++i) { - QTreeWidgetItem *section = commandList()->topLevelItem(i); - int m = section->childCount(); - for (int j = 0; j != m; ++j) { - QTreeWidgetItem *item = section->child(j); - const QString name = item->data(0, CommandRole).toString(); - const QString regex = item->data(2, Qt::DisplayRole).toString(); - const QString pattern = dd->m_defaultExCommandMap.value(name).pattern(); - if ((regex.isEmpty() && pattern.isEmpty()) - || (!regex.isEmpty() && pattern == regex)) - continue; - map[name] = QRegularExpression(regex); - } - } - return map; -} - - /////////////////////////////////////////////////////////////////////// // @@ -2164,7 +2064,7 @@ void FakeVimPlugin::setupTest(QString *title, FakeVimHandler **handler, QWidget IEditor *iedit = EditorManager::openEditorWithContents(Id(), title); EditorManager::activateEditor(iedit); *edit = iedit->widget(); - *handler = dd->m_editorToHandler.value(iedit, 0); + *handler = dd->m_editorToHandler.value(iedit, {}).handler; (*handler)->setupWidget(); (*handler)->handleCommand("set startofline"); diff --git a/src/plugins/fossil/configuredialog.cpp b/src/plugins/fossil/configuredialog.cpp index 69c74ad8c84..e5b6c8f3e77 100644 --- a/src/plugins/fossil/configuredialog.cpp +++ b/src/plugins/fossil/configuredialog.cpp @@ -67,7 +67,7 @@ ConfigureDialog::ConfigureDialog(QWidget *parent) : QDialog(parent), connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - using namespace Utils::Layouting; + using namespace Layouting; Column { Group { title(Tr::tr("Repository User")), diff --git a/src/plugins/fossil/fossilclient.cpp b/src/plugins/fossil/fossilclient.cpp index 16038150cb9..f7bb29f2aea 100644 --- a/src/plugins/fossil/fossilclient.cpp +++ b/src/plugins/fossil/fossilclient.cpp @@ -16,9 +16,9 @@ #include #include #include +#include #include #include -#include #include #include @@ -55,9 +55,9 @@ public: addReloadButton(); if (features.testFlag(FossilClient::DiffIgnoreWhiteSpaceFeature)) { mapSetting(addToggleButton("-w", Tr::tr("Ignore All Whitespace")), - &client->settings().diffIgnoreAllWhiteSpace); + &settings().diffIgnoreAllWhiteSpace); mapSetting(addToggleButton("--strip-trailing-cr", Tr::tr("Strip Trailing CR")), - &client->settings().diffStripTrailingCR); + &settings().diffStripTrailingCR); } } }; @@ -73,20 +73,19 @@ public: { QTC_ASSERT(client, return); - FossilSettings &settings = client->settings(); FossilClient::SupportedFeatures features = client->supportedFeatures(); if (features.testFlag(FossilClient::AnnotateBlameFeature)) { mapSetting(addToggleButton("|BLAME|", Tr::tr("Show Committers")), - &settings.annotateShowCommitters); + &settings().annotateShowCommitters); } // Force listVersions setting to false by default. // This way the annotated line number would not get offset by the version list. - settings.annotateListVersions.setValue(false); + settings().annotateListVersions.setValue(false); mapSetting(addToggleButton("--log", Tr::tr("List Versions")), - &settings.annotateListVersions); + &settings().annotateListVersions); } }; @@ -108,12 +107,9 @@ class FossilLogConfig : public VcsBaseEditorConfig Q_OBJECT public: - FossilLogConfig(FossilClient *client, QToolBar *toolBar) : - VcsBaseEditorConfig(toolBar), - m_client(client) + FossilLogConfig(QToolBar *toolBar) + : VcsBaseEditorConfig(toolBar) { - QTC_ASSERT(client, return); - addReloadButton(); addLineageComboBox(); addVerboseToggleButton(); @@ -122,8 +118,6 @@ public: void addLineageComboBox() { - FossilSettings &settings = m_client->settings(); - // ancestors/descendants filter // This is a positional argument not an option. // Normally it takes the checkin/branch/tag as an additional parameter @@ -137,23 +131,19 @@ public: ChoiceItem(Tr::tr("Unfiltered"), "") }; mapSetting(addChoices(Tr::tr("Lineage"), QStringList("|LINEAGE|%1|current"), lineageFilterChoices), - &settings.timelineLineageFilter); + &settings().timelineLineageFilter); } void addVerboseToggleButton() { - FossilSettings &settings = m_client->settings(); - // show files mapSetting(addToggleButton("-showfiles", Tr::tr("Verbose"), Tr::tr("Show files changed in each revision")), - &settings.timelineVerbose); + &settings().timelineVerbose); } void addItemTypeComboBox() { - FossilSettings &settings = m_client->settings(); - // option: -t const QList itemTypeChoices = { ChoiceItem(Tr::tr("All Items"), "all"), @@ -169,7 +159,7 @@ public: // Fossil expects separate arguments for option and value ( i.e. "-t" "all") // so we need to handle the splitting explicitly in arguments(). mapSetting(addChoices(Tr::tr("Item Types"), QStringList("-t %1"), itemTypeChoices), - &settings.timelineItemType); + &settings().timelineItemType); } QStringList arguments() const final @@ -199,9 +189,6 @@ public: } return args; } - -private: - FossilClient *m_client; }; unsigned FossilClient::makeVersionNumber(int major, int minor, int patch) @@ -224,22 +211,22 @@ QString FossilClient::makeVersionString(unsigned version) .arg(versionPart(version)); } -FossilClient::FossilClient(FossilSettings *settings) - : VcsBaseClient(settings), m_settings(settings) +FossilSettings &FossilClient::settings() const +{ + return Internal::settings(); +} + +FossilClient::FossilClient() + : VcsBaseClient(&Internal::settings()) { setDiffConfigCreator([this](QToolBar *toolBar) { return new FossilDiffConfig(this, toolBar); }); } -FossilSettings &FossilClient::settings() const -{ - return *m_settings; -} - unsigned int FossilClient::synchronousBinaryVersion() const { - if (settings().binaryPath.value().isEmpty()) + if (settings().binaryPath().isEmpty()) return 0; const CommandResult result = vcsSynchronousExec({}, QStringList{"version"}); @@ -599,7 +586,7 @@ bool FossilClient::synchronousCreateRepository(const FilePath &workingDirectory, // use the configured default user for admin const QString repoName = workingDirectory.fileName().simplified(); - const QString repoPath = settings().defaultRepoPath.value(); + const FilePath repoPath = settings().defaultRepoPath(); const QString adminUser = settings().userName.value(); if (repoName.isEmpty() || repoPath.isEmpty()) @@ -609,8 +596,7 @@ bool FossilClient::synchronousCreateRepository(const FilePath &workingDirectory, // @TODO: what about --template options? const FilePath fullRepoName = FilePath::fromStringWithExtension(repoName, Constants::FOSSIL_FILE_SUFFIX); - const FilePath repoFilePath = FilePath::fromString(repoPath) - .pathAppended(fullRepoName.toString()); + const FilePath repoFilePath = repoPath.pathAppended(fullRepoName.toString()); QStringList args(vcsCommandString(CreateRepositoryCommand)); if (!adminUser.isEmpty()) args << "--admin-user" << adminUser; @@ -1177,7 +1163,7 @@ VcsBaseEditorConfig *FossilClient::createLogCurrentFileEditor(VcsBaseEditorWidge VcsBaseEditorConfig *FossilClient::createLogEditor(VcsBaseEditorWidget *editor) { - return new FossilLogConfig(this, editor->toolBar()); + return new FossilLogConfig(editor->toolBar()); } } // namespace Internal diff --git a/src/plugins/fossil/fossilclient.h b/src/plugins/fossil/fossilclient.h index 21dfeaae046..b3016378461 100644 --- a/src/plugins/fossil/fossilclient.h +++ b/src/plugins/fossil/fossilclient.h @@ -41,7 +41,7 @@ public: static unsigned makeVersionNumber(int major, int minor, int patch); static QString makeVersionString(unsigned version); - explicit FossilClient(FossilSettings *settings); + FossilClient(); FossilSettings &settings() const; unsigned int synchronousBinaryVersion() const; @@ -107,7 +107,6 @@ private: VcsBase::VcsBaseEditorConfig *createLogEditor(VcsBase::VcsBaseEditorWidget *editor); friend class FossilPluginPrivate; - FossilSettings *m_settings = nullptr; }; Q_DECLARE_OPERATORS_FOR_FLAGS(FossilClient::SupportedFeatures) diff --git a/src/plugins/fossil/fossilcommitwidget.cpp b/src/plugins/fossil/fossilcommitwidget.cpp index 5658ed9f760..72fba5ef86f 100644 --- a/src/plugins/fossil/fossilcommitwidget.cpp +++ b/src/plugins/fossil/fossilcommitwidget.cpp @@ -93,8 +93,8 @@ FossilCommitWidget::FossilCommitWidget() : m_commitPanel(new QWidget) m_invalidBranchLabel->setType(InfoLabel::Error); m_isPrivateCheckBox = new QCheckBox(Tr::tr("Private")); - m_isPrivateCheckBox->setToolTip(Tr::tr("Create a private check-in that is never synced.\n" - "Children of private check-ins are automatically private.\n" + m_isPrivateCheckBox->setToolTip("" + Tr::tr("Create a private check-in that is never synced. " + "Children of private check-ins are automatically private. " "Private check-ins are not pushed to the remote repository by default.")); m_tagsLineEdit = new QLineEdit; @@ -120,8 +120,9 @@ FossilCommitWidget::FossilCommitWidget() : m_commitPanel(new QWidget) Tr::tr("Tags:"), m_tagsLineEdit, br, Tr::tr("Author:"), m_authorLineEdit, st, } - } - }.attachTo(m_commitPanel, WithoutMargins); + }, + noMargin + }.attachTo(m_commitPanel); insertTopWidget(m_commitPanel); new FossilSubmitHighlighter(descriptionEdit()); diff --git a/src/plugins/fossil/fossilplugin.cpp b/src/plugins/fossil/fossilplugin.cpp index 94f800e1ea6..1f6f7d93064 100644 --- a/src/plugins/fossil/fossilplugin.cpp +++ b/src/plugins/fossil/fossilplugin.cpp @@ -187,10 +187,8 @@ public: bool pullOrPush(SyncMode mode); // Variables - FossilSettings m_fossilSettings; - FossilClient m_client{&m_fossilSettings}; - - OptionsPage optionPage{[this] { configurationChanged(); }, &m_fossilSettings}; + FossilSettings m_settings; + FossilClient m_client; VcsSubmitEditorFactory submitEditorFactory { submitEditorParameters, @@ -274,11 +272,6 @@ void FossilPlugin::extensionsInitialized() dd->extensionsInitialized(); } -const FossilSettings &FossilPlugin::settings() -{ - return dd->m_fossilSettings; -} - FossilClient *FossilPlugin::client() { return &dd->m_client; @@ -293,11 +286,13 @@ FossilPluginPrivate::FossilPluginPrivate() connect(&m_client, &VcsBase::VcsBaseClient::changed, this, &FossilPluginPrivate::changed); m_commandLocator = new Core::CommandLocator("Fossil", "fossil", "fossil", this); + m_commandLocator->setDescription(Tr::tr("Triggers a Fossil version control operation.")); ProjectExplorer::JsonWizardFactory::addWizardPath(Utils::FilePath::fromString(Constants::WIZARD_PATH)); - Core::JsExpander::registerGlobalObject("Fossil", [this] { - return new FossilJsExtension(&m_fossilSettings); - }); + Core::JsExpander::registerGlobalObject("Fossil", [] { return new FossilJsExtension; }); + + connect(&settings(), &AspectContainer::changed, + this, &IVersionControl::configurationChanged); createMenu(context); } @@ -337,7 +332,7 @@ void FossilPluginPrivate::createFileActions(const Core::Context &context) command = Core::ActionManager::registerAction(m_diffFile, Constants::DIFF, context); command->setAttribute(Core::Command::CA_UpdateText); command->setDefaultKeySequence(QKeySequence(Core::useMacShortcuts ? Tr::tr("Meta+I,Meta+D") - : Tr::tr("ALT+I,Alt+D"))); + : Tr::tr("Alt+I,Alt+D"))); connect(m_diffFile, &QAction::triggered, this, &FossilPluginPrivate::diffCurrentFile); m_fossilContainer->addAction(command); m_commandLocator->appendCommand(command); @@ -346,7 +341,7 @@ void FossilPluginPrivate::createFileActions(const Core::Context &context) command = Core::ActionManager::registerAction(m_logFile, Constants::LOG, context); command->setAttribute(Core::Command::CA_UpdateText); command->setDefaultKeySequence(QKeySequence(Core::useMacShortcuts ? Tr::tr("Meta+I,Meta+L") - : Tr::tr("ALT+I,Alt+L"))); + : Tr::tr("Alt+I,Alt+L"))); connect(m_logFile, &QAction::triggered, this, &FossilPluginPrivate::logCurrentFile); m_fossilContainer->addAction(command); m_commandLocator->appendCommand(command); @@ -355,7 +350,7 @@ void FossilPluginPrivate::createFileActions(const Core::Context &context) command = Core::ActionManager::registerAction(m_statusFile, Constants::STATUS, context); command->setAttribute(Core::Command::CA_UpdateText); command->setDefaultKeySequence(QKeySequence(Core::useMacShortcuts ? Tr::tr("Meta+I,Meta+S") - : Tr::tr("ALT+I,Alt+S"))); + : Tr::tr("Alt+I,Alt+S"))); connect(m_statusFile, &QAction::triggered, this, &FossilPluginPrivate::statusCurrentFile); m_fossilContainer->addAction(command); m_commandLocator->appendCommand(command); @@ -417,10 +412,10 @@ void FossilPluginPrivate::logCurrentFile() QTC_ASSERT(state.hasFile(), return); FossilClient::SupportedFeatures features = m_client.supportedFeatures(); QStringList extraOptions; - extraOptions << "-n" << QString::number(m_client.settings().logCount.value()); + extraOptions << "-n" << QString::number(m_client.settings().logCount()); if (features.testFlag(FossilClient::TimelineWidthFeature)) - extraOptions << "-W" << QString::number(m_client.settings().timelineWidth.value()); + extraOptions << "-W" << QString::number(m_client.settings().timelineWidth()); // disable annotate context menu for older client versions, used to be supported for current revision only bool enableAnnotationContextMenu = features.testFlag(FossilClient::AnnotateRevisionFeature); @@ -468,7 +463,7 @@ void FossilPluginPrivate::createDirectoryActions(const Core::Context &context) m_repositoryActionList.append(action); command = Core::ActionManager::registerAction(action, Constants::LOGMULTI, context); command->setDefaultKeySequence(QKeySequence(Core::useMacShortcuts ? Tr::tr("Meta+I,Meta+T") - : Tr::tr("ALT+I,Alt+T"))); + : Tr::tr("Alt+I,Alt+T"))); connect(action, &QAction::triggered, this, &FossilPluginPrivate::logRepository); m_fossilContainer->addAction(command); m_commandLocator->appendCommand(command); @@ -502,10 +497,10 @@ void FossilPluginPrivate::logRepository() QTC_ASSERT(state.hasTopLevel(), return); FossilClient::SupportedFeatures features = m_client.supportedFeatures(); QStringList extraOptions; - extraOptions << "-n" << QString::number(m_client.settings().logCount.value()); + extraOptions << "-n" << QString::number(m_client.settings().logCount()); if (features.testFlag(FossilClient::TimelineWidthFeature)) - extraOptions << "-W" << QString::number(m_client.settings().timelineWidth.value()); + extraOptions << "-W" << QString::number(m_client.settings().timelineWidth()); m_client.log(state.topLevel(), {}, extraOptions); } @@ -550,7 +545,7 @@ void FossilPluginPrivate::createRepositoryActions(const Core::Context &context) m_repositoryActionList.append(action); command = Core::ActionManager::registerAction(action, Constants::UPDATE, context); command->setDefaultKeySequence(QKeySequence(Core::useMacShortcuts ? Tr::tr("Meta+I,Meta+U") - : Tr::tr("ALT+I,Alt+U"))); + : Tr::tr("Alt+I,Alt+U"))); connect(action, &QAction::triggered, this, &FossilPluginPrivate::update); m_fossilContainer->addAction(command); m_commandLocator->appendCommand(command); @@ -559,12 +554,12 @@ void FossilPluginPrivate::createRepositoryActions(const Core::Context &context) m_repositoryActionList.append(action); command = Core::ActionManager::registerAction(action, Constants::COMMIT, context); command->setDefaultKeySequence(QKeySequence(Core::useMacShortcuts ? Tr::tr("Meta+I,Meta+C") - : Tr::tr("ALT+I,Alt+C"))); + : Tr::tr("Alt+I,Alt+C"))); connect(action, &QAction::triggered, this, &FossilPluginPrivate::commit); m_fossilContainer->addAction(command); m_commandLocator->appendCommand(command); - action = new QAction(Tr::tr("Settings ..."), this); + action = new QAction(Tr::tr("Settings..."), this); m_repositoryActionList.append(action); command = Core::ActionManager::registerAction(action, Constants::CONFIGURE_REPOSITORY, context); connect(action, &QAction::triggered, this, &FossilPluginPrivate::configureRepository); @@ -597,7 +592,7 @@ bool FossilPluginPrivate::pullOrPush(FossilPluginPrivate::SyncMode mode) QTC_ASSERT(state.hasTopLevel(), return false); PullOrPushDialog dialog(pullOrPushMode, Core::ICore::dialogParent()); - dialog.setLocalBaseDirectory(m_client.settings().defaultRepoPath.value()); + dialog.setLocalBaseDirectory(m_client.settings().defaultRepoPath()); const QString defaultURL(m_client.synchronousGetRepositoryURL(state.topLevel())); dialog.setDefaultRemoteLocation(defaultURL); if (dialog.exec() != QDialog::Accepted) @@ -839,7 +834,7 @@ void FossilPluginPrivate::updateActions(VcsBase::VcsBasePluginPrivate::ActionSta m_revertFile->setParameter(filename); m_statusFile->setParameter(filename); - for (QAction *repoAction : qAsConst(m_repositoryActionList)) + for (QAction *repoAction : std::as_const(m_repositoryActionList)) repoAction->setEnabled(repoEnabled); } @@ -873,24 +868,19 @@ bool FossilPluginPrivate::managesFile(const FilePath &workingDirectory, const QS bool FossilPluginPrivate::isConfigured() const { - const Utils::FilePath binary = m_client.vcsBinary(); + const FilePath binary = m_client.vcsBinary(); if (binary.isEmpty()) return false; - const QFileInfo fi = binary.toFileInfo(); - if ( !(fi.exists() && fi.isFile() && fi.isExecutable()) ) + if (!binary.isExecutableFile()) return false; // Local repositories default path must be set and exist - const QString repoPath = m_client.settings().defaultRepoPath.value(); + const FilePath repoPath = m_client.settings().defaultRepoPath(); if (repoPath.isEmpty()) return false; - const QDir dir(repoPath); - if (!dir.exists()) - return false; - - return true; + return repoPath.isReadableDir(); } bool FossilPluginPrivate::supportsOperation(Operation operation) const @@ -1093,7 +1083,7 @@ RevertDialog::RevertDialog(const QString &title, QWidget *parent) connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - using namespace Utils::Layouting; + using namespace Layouting; Form { Tr::tr("Revision"), m_revisionLineEdit, br, }.attachTo(groupBox); diff --git a/src/plugins/fossil/fossilplugin.h b/src/plugins/fossil/fossilplugin.h index b3a45f62dbb..7297cd05ae9 100644 --- a/src/plugins/fossil/fossilplugin.h +++ b/src/plugins/fossil/fossilplugin.h @@ -3,8 +3,6 @@ #pragma once -#include "fossilsettings.h" - #include #include #include @@ -25,7 +23,6 @@ class FossilPlugin final : public ExtensionSystem::IPlugin void extensionsInitialized() final; public: - static const FossilSettings &settings(); static FossilClient *client(); #ifdef WITH_TESTS diff --git a/src/plugins/fossil/fossilsettings.cpp b/src/plugins/fossil/fossilsettings.cpp index 130ff5c2c56..0662445b131 100644 --- a/src/plugins/fossil/fossilsettings.cpp +++ b/src/plugins/fossil/fossilsettings.cpp @@ -15,160 +15,110 @@ using namespace Utils; -namespace Fossil { -namespace Internal { +namespace Fossil::Internal { + +static FossilSettings *theSettings; + +FossilSettings &settings() +{ + return *theSettings; +} FossilSettings::FossilSettings() { - setSettingsGroup(Constants::FOSSIL); - setAutoApply(false); + theSettings = this; + + setSettingsGroup(Constants::FOSSIL); + setId(Constants::VCS_ID_FOSSIL); + setDisplayName(Tr::tr("Fossil")); + setCategory(VcsBase::Constants::VCS_SETTINGS_CATEGORY); - registerAspect(&binaryPath); - binaryPath.setDisplayStyle(StringAspect::PathChooserDisplay); binaryPath.setExpectedKind(PathChooser::ExistingCommand); binaryPath.setDefaultValue(Constants::FOSSILDEFAULT); binaryPath.setDisplayName(Tr::tr("Fossil Command")); binaryPath.setHistoryCompleter("Fossil.Command.History"); binaryPath.setLabelText(Tr::tr("Command:")); - registerAspect(&defaultRepoPath); defaultRepoPath.setSettingsKey("defaultRepoPath"); - defaultRepoPath.setDisplayStyle(StringAspect::PathChooserDisplay); defaultRepoPath.setExpectedKind(PathChooser::Directory); defaultRepoPath.setDisplayName(Tr::tr("Fossil Repositories")); defaultRepoPath.setLabelText(Tr::tr("Default path:")); defaultRepoPath.setToolTip(Tr::tr("Directory to store local repositories by default.")); - registerAspect(&userName); userName.setDisplayStyle(StringAspect::LineEditDisplay); userName.setLabelText(Tr::tr("Default user:")); userName.setToolTip(Tr::tr("Existing user to become an author of changes made to the repository.")); - registerAspect(&sslIdentityFile); sslIdentityFile.setSettingsKey("sslIdentityFile"); - sslIdentityFile.setDisplayStyle(StringAspect::PathChooserDisplay); sslIdentityFile.setExpectedKind(PathChooser::File); sslIdentityFile.setDisplayName(Tr::tr("SSL/TLS Identity Key")); sslIdentityFile.setLabelText(Tr::tr("SSL/TLS identity:")); sslIdentityFile.setToolTip(Tr::tr("SSL/TLS client identity key to use if requested by the server.")); - registerAspect(&diffIgnoreAllWhiteSpace); diffIgnoreAllWhiteSpace.setSettingsKey("diffIgnoreAllWhiteSpace"); - registerAspect(&diffStripTrailingCR); diffStripTrailingCR.setSettingsKey("diffStripTrailingCR"); - registerAspect(&annotateShowCommitters); annotateShowCommitters.setSettingsKey("annotateShowCommitters"); - registerAspect(&annotateListVersions); annotateListVersions.setSettingsKey("annotateListVersions"); - registerAspect(&timelineWidth); timelineWidth.setSettingsKey("timelineWidth"); timelineWidth.setLabelText(Tr::tr("Log width:")); timelineWidth.setToolTip(Tr::tr("The width of log entry line (>20). " "Choose 0 to see a single line per entry.")); - registerAspect(&timelineLineageFilter); timelineLineageFilter.setSettingsKey("timelineLineageFilter"); - registerAspect(&timelineVerbose); timelineVerbose.setSettingsKey("timelineVerbose"); - registerAspect(&timelineItemType); timelineItemType.setDefaultValue("all"); timelineItemType.setSettingsKey("timelineItemType"); - registerAspect(&disableAutosync); disableAutosync.setSettingsKey("disableAutosync"); disableAutosync.setDefaultValue(true); disableAutosync.setLabelText(Tr::tr("Disable auto-sync")); disableAutosync.setToolTip(Tr::tr("Disable automatic pull prior to commit or update and " "automatic push after commit or tag or branch creation.")); - registerAspect(&timeout); timeout.setLabelText(Tr::tr("Timeout:")); timeout.setSuffix(Tr::tr("s")); - registerAspect(&logCount); logCount.setLabelText(Tr::tr("Log count:")); logCount.setToolTip(Tr::tr("The number of recent commit log entries to show. " "Choose 0 to see all entries.")); -}; -// OptionsPage - -class OptionsPageWidget final : public Core::IOptionsPageWidget -{ -public: - OptionsPageWidget(const std::function &onApply, FossilSettings *settings); - void apply() final; - -private: - const std::function m_onApply; - FossilSettings *m_settings; -}; - -void OptionsPageWidget::apply() -{ - if (!m_settings->isDirty()) - return; - - m_settings->apply(); - m_onApply(); -} - -OptionsPageWidget::OptionsPageWidget(const std::function &onApply, FossilSettings *settings) : - m_onApply(onApply), - m_settings(settings) -{ - FossilSettings &s = *m_settings; - - using namespace Layouting; - - Column { - Group { - title(Tr::tr("Configuration")), - Row { s.binaryPath } - }, - - Group { - title(Tr::tr("Local Repositories")), - Row { s.defaultRepoPath } - }, - Group { - title(Tr::tr("User")), - Form { - s.userName, br, - s.sslIdentityFile - } - }, - - Group { - title(Tr::tr("Miscellaneous")), - Column { - Row { - s.logCount, - s.timelineWidth, - s.timeout, - st - }, - s.disableAutosync + setLayouter([this] { + using namespace Layouting; + return Column { + Group { + title(Tr::tr("Configuration")), + Row { binaryPath } }, - }, - st - }.attachTo(this); + Group { + title(Tr::tr("Local Repositories")), + Row { defaultRepoPath } + }, + + Group { + title(Tr::tr("User")), + Form { + userName, br, + sslIdentityFile + } + }, + + Group { + title(Tr::tr("Miscellaneous")), + Column { + Row { logCount, timelineWidth, timeout, st }, + disableAutosync + }, + }, + st + }; + }); } -OptionsPage::OptionsPage(const std::function &onApply, FossilSettings *settings) -{ - setId(Constants::VCS_ID_FOSSIL); - setDisplayName(Tr::tr("Fossil")); - setWidgetCreator([onApply, settings]() { return new OptionsPageWidget(onApply, settings); }); - setCategory(VcsBase::Constants::VCS_SETTINGS_CATEGORY); -} - -} // Internal -} // Fossil +} // Fossil::Internal diff --git a/src/plugins/fossil/fossilsettings.h b/src/plugins/fossil/fossilsettings.h index e4133393f66..8e56b1985cc 100644 --- a/src/plugins/fossil/fossilsettings.h +++ b/src/plugins/fossil/fossilsettings.h @@ -3,30 +3,30 @@ #pragma once -#include #include -namespace Fossil { -namespace Internal { +namespace Fossil::Internal { class FossilSettings : public VcsBase::VcsBaseSettings { public: - Utils::StringAspect defaultRepoPath; - Utils::StringAspect sslIdentityFile; - Utils::BoolAspect diffIgnoreAllWhiteSpace; - Utils::BoolAspect diffStripTrailingCR; - Utils::BoolAspect annotateShowCommitters; - Utils::BoolAspect annotateListVersions; - Utils::IntegerAspect timelineWidth; - Utils::StringAspect timelineLineageFilter; - Utils::BoolAspect timelineVerbose; - Utils::StringAspect timelineItemType; - Utils::BoolAspect disableAutosync; - FossilSettings(); + + Utils::FilePathAspect defaultRepoPath{this}; + Utils::FilePathAspect sslIdentityFile{this}; + Utils::BoolAspect diffIgnoreAllWhiteSpace{this}; + Utils::BoolAspect diffStripTrailingCR{this}; + Utils::BoolAspect annotateShowCommitters{this}; + Utils::BoolAspect annotateListVersions{this}; + Utils::IntegerAspect timelineWidth{this}; + Utils::StringAspect timelineLineageFilter{this}; + Utils::BoolAspect timelineVerbose{this}; + Utils::StringAspect timelineItemType{this}; + Utils::BoolAspect disableAutosync{this}; }; +FossilSettings &settings(); + struct RepositorySettings { enum AutosyncMode {AutosyncOff, AutosyncOn, AutosyncPullOnly}; @@ -34,20 +34,13 @@ struct RepositorySettings QString user; QString sslIdentityFile; AutosyncMode autosync = AutosyncOn; + + friend bool operator==(const RepositorySettings &lh, const RepositorySettings &rh) + { + return lh.user == rh.user + && lh.sslIdentityFile == rh.sslIdentityFile + && lh.autosync == rh.autosync; + } }; -inline bool operator==(const RepositorySettings &lh, const RepositorySettings &rh) -{ - return (lh.user == rh.user && - lh.sslIdentityFile == rh.sslIdentityFile && - lh.autosync == rh.autosync); -} - -class OptionsPage : public Core::IOptionsPage -{ -public: - OptionsPage(const std::function &onApply, FossilSettings *settings); -}; - -} // namespace Internal -} // namespace Fossil +} // Fossil::Internal diff --git a/src/plugins/fossil/pullorpushdialog.cpp b/src/plugins/fossil/pullorpushdialog.cpp index 05bdf136f18..b6473a68b41 100644 --- a/src/plugins/fossil/pullorpushdialog.cpp +++ b/src/plugins/fossil/pullorpushdialog.cpp @@ -15,8 +15,7 @@ #include #include -namespace Fossil { -namespace Internal { +namespace Fossil::Internal { PullOrPushDialog::PullOrPushDialog(Mode mode, QWidget *parent) : QDialog(parent) @@ -35,7 +34,7 @@ PullOrPushDialog::PullOrPushDialog(Mode mode, QWidget *parent) m_localPathChooser->setPromptDialogFilter(Tr::tr(Constants::FOSSIL_FILE_FILTER)); m_urlButton = new QRadioButton(Tr::tr("Specify URL:")); - m_urlButton->setToolTip(Tr::tr("For example: https://[user[:pass]@]host[:port]/[path]")); + m_urlButton->setToolTip(Tr::tr("For example: \"https://[user[:pass]@]host[:port]/[path]\".")); m_urlLineEdit = new QLineEdit; m_urlLineEdit->setEnabled(false); @@ -52,7 +51,7 @@ PullOrPushDialog::PullOrPushDialog(Mode mode, QWidget *parent) connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - using namespace Utils::Layouting; + using namespace Layouting; Column { Group { title(Tr::tr("Remote Location")), @@ -106,10 +105,9 @@ void PullOrPushDialog::setDefaultRemoteLocation(const QString &url) m_urlLineEdit->setText(url); } -void PullOrPushDialog::setLocalBaseDirectory(const QString &dir) +void PullOrPushDialog::setLocalBaseDirectory(const Utils::FilePath &dir) { - m_localPathChooser->setBaseDirectory(Utils::FilePath::fromString(dir)); + m_localPathChooser->setBaseDirectory(dir); } -} // namespace Internal -} // namespace Fossil +} // Fossil::Internal diff --git a/src/plugins/fossil/pullorpushdialog.h b/src/plugins/fossil/pullorpushdialog.h index 1d921733b69..5594947cb0d 100644 --- a/src/plugins/fossil/pullorpushdialog.h +++ b/src/plugins/fossil/pullorpushdialog.h @@ -3,6 +3,8 @@ #pragma once +#include + #include QT_BEGIN_NAMESPACE @@ -13,13 +15,10 @@ QT_END_NAMESPACE namespace Utils { class PathChooser; } -namespace Fossil { -namespace Internal { +namespace Fossil::Internal { class PullOrPushDialog : public QDialog { - Q_OBJECT - public: enum Mode { PullMode, @@ -33,7 +32,7 @@ public: bool isRememberOptionEnabled() const; bool isPrivateOptionEnabled() const; void setDefaultRemoteLocation(const QString &url); - void setLocalBaseDirectory(const QString &dir); + void setLocalBaseDirectory(const Utils::FilePath &dir); // Pull-specific options // Push-specific options @@ -47,5 +46,4 @@ private: QCheckBox *m_privateCheckBox; }; -} // namespace Internal -} // namespace Fossil +} // Fossil::Internal diff --git a/src/plugins/fossil/wizard/fossiljsextension.cpp b/src/plugins/fossil/wizard/fossiljsextension.cpp index 66747db5674..4c013b503a8 100644 --- a/src/plugins/fossil/wizard/fossiljsextension.cpp +++ b/src/plugins/fossil/wizard/fossiljsextension.cpp @@ -17,19 +17,6 @@ using namespace Core; namespace Fossil { namespace Internal { -class FossilJsExtensionPrivate { -public: - FossilJsExtensionPrivate(FossilSettings *settings) : - m_vscId(Constants::VCS_ID_FOSSIL), - m_settings(settings) - { - } - - Utils::Id m_vscId; - FossilSettings *m_settings; -}; - - QMap FossilJsExtension::parseArgOptions(const QStringList &args) { QMap options; @@ -42,24 +29,19 @@ QMap FossilJsExtension::parseArgOptions(const QStringList &arg return options; } -FossilJsExtension::FossilJsExtension(FossilSettings *settings) : - d(new FossilJsExtensionPrivate(settings)) -{ } +FossilJsExtension::FossilJsExtension() = default; -FossilJsExtension::~FossilJsExtension() -{ - delete d; -} +FossilJsExtension::~FossilJsExtension() = default; bool FossilJsExtension::isConfigured() const { - IVersionControl *vc = VcsManager::versionControl(d->m_vscId); + IVersionControl *vc = VcsManager::versionControl(Constants::VCS_ID_FOSSIL); return vc && vc->isConfigured(); } QString FossilJsExtension::displayName() const { - IVersionControl *vc = VcsManager::versionControl(d->m_vscId); + IVersionControl *vc = VcsManager::versionControl(Constants::VCS_ID_FOSSIL); return vc ? vc->displayName() : QString(); } @@ -68,7 +50,7 @@ QString FossilJsExtension::defaultAdminUser() const if (!isConfigured()) return QString(); - return d->m_settings->userName.value(); + return settings().userName.value(); } QString FossilJsExtension::defaultSslIdentityFile() const @@ -76,7 +58,7 @@ QString FossilJsExtension::defaultSslIdentityFile() const if (!isConfigured()) return QString(); - return d->m_settings->sslIdentityFile.value(); + return settings().sslIdentityFile.value(); } QString FossilJsExtension::defaultLocalRepoPath() const @@ -84,7 +66,7 @@ QString FossilJsExtension::defaultLocalRepoPath() const if (!isConfigured()) return QString(); - return d->m_settings->defaultRepoPath.value(); + return settings().defaultRepoPath.value(); } bool FossilJsExtension::defaultDisableAutosync() const @@ -92,7 +74,7 @@ bool FossilJsExtension::defaultDisableAutosync() const if (!isConfigured()) return false; - return d->m_settings->disableAutosync.value(); + return settings().disableAutosync.value(); } } // namespace Internal diff --git a/src/plugins/fossil/wizard/fossiljsextension.h b/src/plugins/fossil/wizard/fossiljsextension.h index 3a37fc5280a..0947693d581 100644 --- a/src/plugins/fossil/wizard/fossiljsextension.h +++ b/src/plugins/fossil/wizard/fossiljsextension.h @@ -12,9 +12,6 @@ namespace Fossil { namespace Internal { -class FossilJsExtensionPrivate; -class FossilSettings; - class FossilJsExtension : public QObject { Q_OBJECT @@ -22,7 +19,7 @@ class FossilJsExtension : public QObject public: static QMap parseArgOptions(const QStringList &args); - FossilJsExtension(FossilSettings *settings); + FossilJsExtension(); ~FossilJsExtension(); Q_INVOKABLE bool isConfigured() const; @@ -31,9 +28,6 @@ public: Q_INVOKABLE QString defaultSslIdentityFile() const; Q_INVOKABLE QString defaultLocalRepoPath() const; Q_INVOKABLE bool defaultDisableAutosync() const; - -private: - FossilJsExtensionPrivate *d = nullptr; }; } // namespace Internal diff --git a/src/plugins/fossil/wizard/projects/vcs/wizard.json b/src/plugins/fossil/wizard/projects/vcs/wizard.json index 56e08b63774..7f5fb174520 100644 --- a/src/plugins/fossil/wizard/projects/vcs/wizard.json +++ b/src/plugins/fossil/wizard/projects/vcs/wizard.json @@ -86,7 +86,7 @@ { "name": "Repo", "trDisplayName": "Remote repository:", - "trToolTip": "For example: https://[user[:pass]@]host[:port]/[path]", + "trToolTip": "For example: \"https://[user[:pass]@]host[:port]/[path]\".", "type": "LineEdit", "enabled": "%{isCloneRepo}", "mandatory": false diff --git a/src/plugins/genericprojectmanager/genericproject.cpp b/src/plugins/genericprojectmanager/genericproject.cpp index fbe77e74a9f..ef83f906a95 100644 --- a/src/plugins/genericprojectmanager/genericproject.cpp +++ b/src/plugins/genericprojectmanager/genericproject.cpp @@ -38,8 +38,8 @@ #include #include #include +#include #include -#include #include #include @@ -96,7 +96,7 @@ private: //////////////////////////////////////////////////////////////////////////////////// // -// GenericProjectNode +// GenericBuildSystem // //////////////////////////////////////////////////////////////////////////////////// @@ -401,7 +401,7 @@ static QStringList readFlags(const QString &filePath) return QStringList(); QStringList flags; for (const auto &line : lines) - flags.append(ProcessArgs::splitArgs(line)); + flags.append(ProcessArgs::splitArgs(line, HostOsInfo::hostOs())); return flags; } @@ -570,8 +570,8 @@ void GenericBuildSystem::refreshCppCodeModel() rpp.setQtVersion(kitInfo.projectPartQtVersion); rpp.setHeaderPaths(m_projectIncludePaths); rpp.setConfigFileName(m_configFileName); - rpp.setFlagsForCxx({nullptr, m_cxxflags, projectDirectory().toString()}); - rpp.setFlagsForC({nullptr, m_cflags, projectDirectory().toString()}); + rpp.setFlagsForCxx({nullptr, m_cxxflags, projectDirectory()}); + rpp.setFlagsForC({nullptr, m_cflags, projectDirectory()}); static const auto sourceFilesToStringList = [](const SourceFiles &sourceFiles) { return Utils::transform(sourceFiles, [](const SourceFile &f) { diff --git a/src/plugins/git/branchadddialog.cpp b/src/plugins/git/branchadddialog.cpp index 3dfd7ad946d..011a01ea1e1 100644 --- a/src/plugins/git/branchadddialog.cpp +++ b/src/plugins/git/branchadddialog.cpp @@ -125,7 +125,7 @@ BranchAddDialog::BranchAddDialog(const QStringList &localBranches, Type type, QW break; } - using namespace Utils::Layouting; + using namespace Layouting; Column { Row { branchNameLabel, m_branchNameEdit }, diff --git a/src/plugins/git/branchcheckoutdialog.cpp b/src/plugins/git/branchcheckoutdialog.cpp index 9fa2e83a0b4..9615c28ff68 100644 --- a/src/plugins/git/branchcheckoutdialog.cpp +++ b/src/plugins/git/branchcheckoutdialog.cpp @@ -45,7 +45,7 @@ BranchCheckoutDialog::BranchCheckoutDialog(QWidget *parent, auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); - using namespace Utils::Layouting; + using namespace Layouting; Column { m_makeStashRadioButton, diff --git a/src/plugins/git/branchmodel.cpp b/src/plugins/git/branchmodel.cpp index 3c4971fa734..3bf65cf5b38 100644 --- a/src/plugins/git/branchmodel.cpp +++ b/src/plugins/git/branchmodel.cpp @@ -11,8 +11,8 @@ #include #include +#include #include -#include #include #include @@ -20,6 +20,7 @@ #include +using namespace Tasking; using namespace Utils; using namespace VcsBase; @@ -229,6 +230,7 @@ public: QString currentSha; QDateTime currentDateTime; QStringList obsoleteLocalBranches; + std::unique_ptr refreshTask; bool oldBranchesIncluded = false; struct OldEntry @@ -399,50 +401,82 @@ void BranchModel::clear() d->obsoleteLocalBranches.clear(); } -bool BranchModel::refresh(const FilePath &workingDirectory, QString *errorMessage) +void BranchModel::refresh(const FilePath &workingDirectory, ShowError showError) { + if (d->refreshTask) { + endResetModel(); // for the running task tree. + d->refreshTask.reset(); // old running tree is reset, no handlers are being called + } beginResetModel(); clear(); if (workingDirectory.isEmpty()) { endResetModel(); - return true; + return; } - d->currentSha = d->client->synchronousTopRevision(workingDirectory, &d->currentDateTime); - QStringList args = {"--format=%(objectname)\t%(refname)\t%(upstream:short)\t" - "%(*objectname)\t%(committerdate:raw)\t%(*committerdate:raw)", - "refs/heads/**", - "refs/remotes/**"}; - if (d->client->settings().showTags.value()) - args << "refs/tags/**"; - QString output; - if (!d->client->synchronousForEachRefCmd(workingDirectory, args, &output, errorMessage)) { + const ProcessTask topRevisionProc = + d->client->topRevision(workingDirectory, + [=](const QString &ref, const QDateTime &dateTime) { + d->currentSha = ref; + d->currentDateTime = dateTime; + }); + + const auto setupForEachRef = [=](Process &process) { + d->workingDirectory = workingDirectory; + QStringList args = {"for-each-ref", + "--format=%(objectname)\t%(refname)\t%(upstream:short)\t" + "%(*objectname)\t%(committerdate:raw)\t%(*committerdate:raw)", + "refs/heads/**", + "refs/remotes/**"}; + if (settings().showTags()) + args << "refs/tags/**"; + d->client->setupCommand(process, workingDirectory, args); + }; + + const auto forEachRefDone = [=](const Process &process) { + const QString output = process.stdOut(); + const QStringList lines = output.split('\n'); + for (const QString &l : lines) + d->parseOutputLine(l); + d->flushOldEntries(); + + d->updateAllUpstreamStatus(d->rootNode->children.at(LocalBranches)); + if (d->currentBranch) { + if (d->currentBranch->isLocal()) + d->currentBranch = nullptr; + setCurrentBranch(); + } + if (!d->currentBranch) { + BranchNode *local = d->rootNode->children.at(LocalBranches); + d->currentBranch = d->headNode = new BranchNode( + Tr::tr("Detached HEAD"), "HEAD", {}, d->currentDateTime); + local->prepend(d->headNode); + } + }; + + const auto forEachRefError = [=](const Process &process) { + if (showError == ShowError::No) + return; + const QString message = Tr::tr("Cannot run \"%1\" in \"%2\": %3") + .arg("git for-each-ref") + .arg(workingDirectory.toUserOutput()) + .arg(process.cleanedStdErr()); + VcsBase::VcsOutputWindow::appendError(message); + }; + + const auto finalize = [this] { endResetModel(); - return false; - } + d->refreshTask.release()->deleteLater(); + }; - d->workingDirectory = workingDirectory; - const QStringList lines = output.split('\n'); - for (const QString &l : lines) - d->parseOutputLine(l); - d->flushOldEntries(); - - d->updateAllUpstreamStatus(d->rootNode->children.at(LocalBranches)); - if (d->currentBranch) { - if (d->currentBranch->isLocal()) - d->currentBranch = nullptr; - setCurrentBranch(); - } - if (!d->currentBranch) { - BranchNode *local = d->rootNode->children.at(LocalBranches); - d->currentBranch = d->headNode = new BranchNode(Tr::tr("Detached HEAD"), "HEAD", QString(), - d->currentDateTime); - local->prepend(d->headNode); - } - - endResetModel(); - - return true; + const Group root { + topRevisionProc, + ProcessTask(setupForEachRef, forEachRefDone, forEachRefError), + onGroupDone(finalize), + onGroupError(finalize) + }; + d->refreshTask.reset(new TaskTree(root)); + d->refreshTask->start(); } void BranchModel::setCurrentBranch() @@ -469,7 +503,7 @@ void BranchModel::renameBranch(const QString &oldName, const QString &newName) &output, &errorMessage)) VcsOutputWindow::appendError(errorMessage); else - refresh(d->workingDirectory, &errorMessage); + refresh(d->workingDirectory); } void BranchModel::renameTag(const QString &oldName, const QString &newName) @@ -482,7 +516,7 @@ void BranchModel::renameTag(const QString &oldName, const QString &newName) &output, &errorMessage)) { VcsOutputWindow::appendError(errorMessage); } else { - refresh(d->workingDirectory, &errorMessage); + refresh(d->workingDirectory); } } @@ -771,7 +805,7 @@ void BranchModel::Private::parseOutputLine(const QString &line, bool force) const qint64 age = dateTime.daysTo(QDateTime::currentDateTime()); isOld = age > Constants::OBSOLETE_COMMIT_AGE_IN_DAYS; } - const bool showTags = client->settings().showTags.value(); + const bool showTags = settings().showTags(); // insert node into tree: QStringList nameParts = fullName.split('/'); @@ -885,12 +919,12 @@ void BranchModel::updateUpstreamStatus(BranchNode *node) if (node->tracking.isEmpty()) return; - QtcProcess *process = new QtcProcess(node); + Process *process = new Process(node); process->setEnvironment(d->client->processEnvironment()); process->setCommand({d->client->vcsBinary(), {"rev-list", "--no-color", "--left-right", "--count", node->fullRef() + "..." + node->tracking}}); process->setWorkingDirectory(d->workingDirectory); - connect(process, &QtcProcess::done, this, [this, process, node] { + connect(process, &Process::done, this, [this, process, node] { process->deleteLater(); if (process->result() != ProcessResult::FinishedWithSuccess) return; diff --git a/src/plugins/git/branchmodel.h b/src/plugins/git/branchmodel.h index 580af612985..de126e9a5eb 100644 --- a/src/plugins/git/branchmodel.h +++ b/src/plugins/git/branchmodel.h @@ -34,7 +34,8 @@ public: Qt::ItemFlags flags(const QModelIndex &index) const override; void clear(); - bool refresh(const Utils::FilePath &workingDirectory, QString *errorMessage); + enum class ShowError { No, Yes }; + void refresh(const Utils::FilePath &workingDirectory, ShowError showError = ShowError::No); void renameBranch(const QString &oldName, const QString &newName); void renameTag(const QString &oldName, const QString &newName); diff --git a/src/plugins/git/branchview.cpp b/src/plugins/git/branchview.cpp index 528ec288f97..c3daf9d5a88 100644 --- a/src/plugins/git/branchview.cpp +++ b/src/plugins/git/branchview.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -36,6 +37,7 @@ #include using namespace Core; +using namespace Tasking; using namespace Utils; using namespace VcsBase; @@ -113,7 +115,7 @@ BranchView::BranchView() connect(m_includeOldEntriesAction, &QAction::toggled, this, &BranchView::setIncludeOldEntries); m_includeTagsAction->setCheckable(true); - m_includeTagsAction->setChecked(GitClient::settings().showTags.value()); + m_includeTagsAction->setChecked(settings().showTags.value()); connect(m_includeTagsAction, &QAction::toggled, this, &BranchView::setIncludeTags); @@ -160,9 +162,7 @@ void BranchView::refresh(const FilePath &repository, bool force) if (!isVisible()) return; - QString errorMessage; - if (!m_model->refresh(m_repository, &errorMessage)) - VcsBase::VcsOutputWindow::appendError(errorMessage); + m_model->refresh(m_repository, BranchModel::ShowError::Yes); } void BranchView::refreshCurrentBranch() @@ -181,7 +181,7 @@ QList BranchView::createToolButtons() filter->setIcon(Utils::Icons::FILTER.icon()); filter->setToolTip(Tr::tr("Filter")); filter->setPopupMode(QToolButton::InstantPopup); - filter->setProperty("noArrow", true); + filter->setProperty(StyleHelper::C_NO_ARROW, true); auto filterMenu = new QMenu(filter); filterMenu->addAction(m_includeOldEntriesAction); @@ -190,11 +190,11 @@ QList BranchView::createToolButtons() auto addButton = new QToolButton; addButton->setDefaultAction(m_addAction); - addButton->setProperty("noArrow", true); + addButton->setProperty(StyleHelper::C_NO_ARROW, true); auto refreshButton = new QToolButton; refreshButton->setDefaultAction(m_refreshAction); - refreshButton->setProperty("noArrow", true); + refreshButton->setProperty(StyleHelper::C_NO_ARROW, true); return {filter, addButton, refreshButton}; } @@ -225,6 +225,8 @@ void BranchView::slotCustomContextMenu(const QPoint &point) const bool isTag = m_model->isTag(index); const bool hasActions = m_model->isLeaf(index); const bool currentLocal = m_model->isLocal(currentBranch); + std::unique_ptr taskTree; + QAction *mergeAction = nullptr; QMenu contextMenu; contextMenu.addAction(Tr::tr("&Add..."), this, &BranchView::add); @@ -268,19 +270,20 @@ void BranchView::slotCustomContextMenu(const QPoint &point) resetMenu->addAction(Tr::tr("&Mixed"), this, [this] { reset("mixed"); }); resetMenu->addAction(Tr::tr("&Soft"), this, [this] { reset("soft"); }); contextMenu.addMenu(resetMenu); - QString mergeTitle; - if (isFastForwardMerge()) { - contextMenu.addAction(Tr::tr("&Merge \"%1\" into \"%2\" (Fast-Forward)") - .arg(indexName, currentName), - this, [this] { merge(true); }); - mergeTitle = Tr::tr("Merge \"%1\" into \"%2\" (No &Fast-Forward)") - .arg(indexName, currentName); - } else { - mergeTitle = Tr::tr("&Merge \"%1\" into \"%2\"") - .arg(indexName, currentName); - } + mergeAction = contextMenu.addAction(Tr::tr("&Merge \"%1\" into \"%2\"") + .arg(indexName, currentName), + this, + [this] { merge(false); }); + taskTree.reset(onFastForwardMerge([&] { + auto ffMerge = new QAction( + Tr::tr("&Merge \"%1\" into \"%2\" (Fast-Forward)").arg(indexName, currentName)); + connect(ffMerge, &QAction::triggered, this, [this] { merge(true); }); + contextMenu.insertAction(mergeAction, ffMerge); + mergeAction->setText(Tr::tr("Merge \"%1\" into \"%2\" (No &Fast-Forward)") + .arg(indexName, currentName)); + })); + connect(mergeAction, &QObject::destroyed, taskTree.get(), &TaskTree::stop); - contextMenu.addAction(mergeTitle, this, [this] { merge(false); }); contextMenu.addAction(Tr::tr("&Rebase \"%1\" on \"%2\"") .arg(currentName, indexName), this, &BranchView::rebase); @@ -316,7 +319,7 @@ void BranchView::setIncludeOldEntries(bool filter) void BranchView::setIncludeTags(bool includeTags) { - GitClient::settings().showTags.setValue(includeTags); + settings().showTags.setValue(includeTags); refreshCurrentRepository(); } @@ -523,13 +526,48 @@ bool BranchView::reset(const QByteArray &resetType) return false; } -bool BranchView::isFastForwardMerge() +TaskTree *BranchView::onFastForwardMerge(const std::function &callback) { const QModelIndex selected = selectedIndex(); QTC_CHECK(selected != m_model->currentBranch()); const QString branch = m_model->fullName(selected, true); - return GitClient::instance()->isFastForwardMerge(m_repository, branch); + + struct FastForwardStorage + { + QString mergeBase; + QString topRevision; + }; + + const TreeStorage storage; + + GitClient *client = GitClient::instance(); + const auto setupMergeBase = [=](Process &process) { + client->setupCommand(process, m_repository, {"merge-base", "HEAD", branch}); + }; + const auto onMergeBaseDone = [storage](const Process &process) { + storage->mergeBase = process.cleanedStdOut().trimmed(); + }; + + const ProcessTask topRevisionProc = client->topRevision( + m_repository, + [storage](const QString &revision, const QDateTime &) { + storage->topRevision = revision; + }); + + const Group root { + Storage(storage), + parallel, + ProcessTask(setupMergeBase, onMergeBaseDone), + topRevisionProc, + onGroupDone([storage, callback] { + if (storage->mergeBase == storage->topRevision) + callback(); + }) + }; + auto taskTree = new TaskTree(root); + taskTree->start(); + return taskTree; } bool BranchView::merge(bool allowFastForward) diff --git a/src/plugins/git/branchview.h b/src/plugins/git/branchview.h index 9a1a3c1e455..d272a5e2194 100644 --- a/src/plugins/git/branchview.h +++ b/src/plugins/git/branchview.h @@ -17,6 +17,8 @@ class QToolButton; class QTreeView; QT_END_NAMESPACE; +namespace Tasking { class TaskTree; } + namespace Utils { class ElidingLabel; class NavigationTreeView; @@ -54,7 +56,7 @@ private: bool remove(); bool rename(); bool reset(const QByteArray &resetType); - bool isFastForwardMerge(); + Tasking::TaskTree *onFastForwardMerge(const std::function &callback); bool merge(bool allowFastForward); void rebase(); bool cherryPick(); diff --git a/src/plugins/git/changeselectiondialog.cpp b/src/plugins/git/changeselectiondialog.cpp index aba7be1ad2c..8e4a6bdc997 100644 --- a/src/plugins/git/changeselectiondialog.cpp +++ b/src/plugins/git/changeselectiondialog.cpp @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include @@ -209,12 +209,12 @@ void ChangeSelectionDialog::recalculateCompletion() return; GitClient *client = GitClient::instance(); - QtcProcess *process = new QtcProcess(this); + Process *process = new Process(this); process->setEnvironment(client->processEnvironment()); process->setCommand({client->vcsBinary(), {"for-each-ref", "--format=%(refname:short)"}}); process->setWorkingDirectory(workingDir); process->setUseCtrlCStub(true); - connect(process, &QtcProcess::done, this, [this, process] { + connect(process, &Process::done, this, [this, process] { if (process->result() == ProcessResult::FinishedWithSuccess) m_changeModel->setStringList(process->cleanedStdOut().split('\n')); process->deleteLater(); @@ -238,8 +238,8 @@ void ChangeSelectionDialog::recalculateDetails() return; } - m_process.reset(new QtcProcess); - connect(m_process.get(), &QtcProcess::done, this, &ChangeSelectionDialog::setDetails); + m_process.reset(new Process); + connect(m_process.get(), &Process::done, this, &ChangeSelectionDialog::setDetails); m_process->setWorkingDirectory(workingDir); m_process->setEnvironment(m_gitEnvironment); m_process->setCommand({m_gitExecutable, {"show", "--decorate", "--stat=80", ref}}); diff --git a/src/plugins/git/changeselectiondialog.h b/src/plugins/git/changeselectiondialog.h index 69da3b20d43..24bc3155dc9 100644 --- a/src/plugins/git/changeselectiondialog.h +++ b/src/plugins/git/changeselectiondialog.h @@ -18,7 +18,7 @@ QT_END_NAMESPACE namespace Utils { class CompletingLineEdit; class PathChooser; -class QtcProcess; +class Process; } // Utils namespace Git::Internal { @@ -53,7 +53,7 @@ private: void enableButtons(bool b); - std::unique_ptr m_process; + std::unique_ptr m_process; Utils::FilePath m_gitExecutable; Utils::Environment m_gitEnvironment; ChangeCommand m_command = NoCommand; diff --git a/src/plugins/git/gerrit/gerritmodel.cpp b/src/plugins/git/gerrit/gerritmodel.cpp index 8273fe0f096..6457117b322 100644 --- a/src/plugins/git/gerrit/gerritmodel.cpp +++ b/src/plugins/git/gerrit/gerritmodel.cpp @@ -10,8 +10,8 @@ #include #include +#include #include -#include #include #include @@ -227,7 +227,7 @@ private: void errorTermination(const QString &msg); - QtcProcess m_process; + Process m_process; QTimer m_timer; FilePath m_binary; QByteArray m_output; @@ -259,15 +259,15 @@ QueryContext::QueryContext(const QString &query, + "&o=CURRENT_REVISION&o=DETAILED_LABELS&o=DETAILED_ACCOUNTS"; m_arguments = server.curlArguments() << url; } - connect(&m_process, &QtcProcess::readyReadStandardError, this, [this] { + connect(&m_process, &Process::readyReadStandardError, this, [this] { const QString text = QString::fromLocal8Bit(m_process.readAllRawStandardError()); VcsOutputWindow::appendError(text); m_error.append(text); }); - connect(&m_process, &QtcProcess::readyReadStandardOutput, this, [this] { + connect(&m_process, &Process::readyReadStandardOutput, this, [this] { m_output.append(m_process.readAllRawStandardOutput()); }); - connect(&m_process, &QtcProcess::done, this, &QueryContext::processDone); + connect(&m_process, &Process::done, this, &QueryContext::processDone); m_process.setEnvironment(Git::Internal::GitClient::instance()->processEnvironment()); m_timer.setInterval(timeOutMS); @@ -340,7 +340,7 @@ void QueryContext::timeout() arg(timeOutMS / 1000), QMessageBox::NoButton, parent); QPushButton *terminateButton = box.addButton(Git::Tr::tr("Terminate"), QMessageBox::YesRole); box.addButton(Git::Tr::tr("Keep Running"), QMessageBox::NoRole); - connect(&m_process, &QtcProcess::done, &box, &QDialog::reject); + connect(&m_process, &Process::done, &box, &QDialog::reject); box.exec(); if (m_process.state() != QProcess::Running) return; diff --git a/src/plugins/git/gerrit/gerritoptionspage.cpp b/src/plugins/git/gerrit/gerritoptionspage.cpp index 61aa7047b7a..3d77c26aa46 100644 --- a/src/plugins/git/gerrit/gerritoptionspage.cpp +++ b/src/plugins/git/gerrit/gerritoptionspage.cpp @@ -7,6 +7,8 @@ #include "../gittr.h" #include + +#include #include #include @@ -16,108 +18,87 @@ #include #include -namespace Gerrit { -namespace Internal { +namespace Gerrit::Internal { + +class GerritOptionsWidget : public Core::IOptionsPageWidget +{ +public: + GerritOptionsWidget(const QSharedPointer &p, + const std::function &onChanged) + : m_parameters(p) + { + auto hostLineEdit = new QLineEdit(p->server.host); + + auto userLineEdit = new QLineEdit(p->server.user.userName); + + auto sshChooser = new Utils::PathChooser; + sshChooser->setFilePath(p->ssh); + sshChooser->setExpectedKind(Utils::PathChooser::ExistingCommand); + sshChooser->setCommandVersionArguments({"-V"}); + sshChooser->setHistoryCompleter("Git.SshCommand.History"); + + auto curlChooser = new Utils::PathChooser; + curlChooser->setFilePath(p->curl); + curlChooser->setExpectedKind(Utils::PathChooser::ExistingCommand); + curlChooser->setCommandVersionArguments({"-V"}); + + auto portSpinBox = new QSpinBox(this); + portSpinBox->setRange(1, 65535); + portSpinBox->setValue(p->server.port); + + auto httpsCheckBox = new QCheckBox(Git::Tr::tr("HTTPS")); + httpsCheckBox->setChecked(p->https); + httpsCheckBox->setToolTip(Git::Tr::tr( + "Determines the protocol used to form a URL in case\n" + "\"canonicalWebUrl\" is not configured in the file\n" + "\"gerrit.config\".")); + + using namespace Layouting; + Form { + Git::Tr::tr("&Host:"), hostLineEdit, br, + Git::Tr::tr("&User:"), userLineEdit, br, + Git::Tr::tr("&ssh:"), sshChooser, br, + Git::Tr::tr("cur&l:"), curlChooser, br, + Git::Tr::tr("SSH &Port:"), portSpinBox, br, + Git::Tr::tr("P&rotocol:"), httpsCheckBox + }.attachTo(this); + + setOnApply([this, hostLineEdit, userLineEdit, sshChooser, + curlChooser, portSpinBox, httpsCheckBox, onChanged] { + GerritParameters newParameters; + newParameters.server = GerritServer(hostLineEdit->text().trimmed(), + static_cast(portSpinBox->value()), + userLineEdit->text().trimmed(), + GerritServer::Ssh); + newParameters.ssh = sshChooser->filePath(); + newParameters.curl = curlChooser->filePath(); + newParameters.https = httpsCheckBox->isChecked(); + + if (newParameters != *m_parameters) { + if (m_parameters->ssh == newParameters.ssh) + newParameters.portFlag = m_parameters->portFlag; + else + newParameters.setPortFlagBySshType(); + *m_parameters = newParameters; + m_parameters->toSettings(Core::ICore::settings()); + emit onChanged(); + } + }); + } + +private: + const QSharedPointer &m_parameters; +}; + +// GerritOptionsPage GerritOptionsPage::GerritOptionsPage(const QSharedPointer &p, - QObject *parent) - : Core::IOptionsPage(parent) - , m_parameters(p) + const std::function &onChanged) { setId("Gerrit"); setDisplayName(Git::Tr::tr("Gerrit")); setCategory(VcsBase::Constants::VCS_SETTINGS_CATEGORY); + setWidgetCreator([p, onChanged] { return new GerritOptionsWidget(p, onChanged); }); } -GerritOptionsPage::~GerritOptionsPage() -{ - delete m_widget; -} - -QWidget *GerritOptionsPage::widget() -{ - if (!m_widget) { - m_widget = new GerritOptionsWidget; - m_widget->setParameters(*m_parameters); - } - return m_widget; -} - -void GerritOptionsPage::apply() -{ - if (GerritOptionsWidget *w = m_widget.data()) { - GerritParameters newParameters = w->parameters(); - if (newParameters != *m_parameters) { - if (m_parameters->ssh == newParameters.ssh) - newParameters.portFlag = m_parameters->portFlag; - else - newParameters.setPortFlagBySshType(); - *m_parameters = newParameters; - m_parameters->toSettings(Core::ICore::settings()); - emit settingsChanged(); - } - } -} - -void GerritOptionsPage::finish() -{ - delete m_widget; -} - -GerritOptionsWidget::GerritOptionsWidget(QWidget *parent) - : QWidget(parent) - , m_hostLineEdit(new QLineEdit(this)) - , m_userLineEdit(new QLineEdit(this)) - , m_sshChooser(new Utils::PathChooser) - , m_curlChooser(new Utils::PathChooser) - , m_portSpinBox(new QSpinBox(this)) - , m_httpsCheckBox(new QCheckBox(Git::Tr::tr("HTTPS"))) -{ - auto formLayout = new QFormLayout(this); - formLayout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow); - formLayout->addRow(Git::Tr::tr("&Host:"), m_hostLineEdit); - formLayout->addRow(Git::Tr::tr("&User:"), m_userLineEdit); - m_sshChooser->setExpectedKind(Utils::PathChooser::ExistingCommand); - m_sshChooser->setCommandVersionArguments({"-V"}); - m_sshChooser->setHistoryCompleter("Git.SshCommand.History"); - formLayout->addRow(Git::Tr::tr("&ssh:"), m_sshChooser); - m_curlChooser->setExpectedKind(Utils::PathChooser::ExistingCommand); - m_curlChooser->setCommandVersionArguments({"-V"}); - formLayout->addRow(Git::Tr::tr("cur&l:"), m_curlChooser); - m_portSpinBox->setMinimum(1); - m_portSpinBox->setMaximum(65535); - formLayout->addRow(Git::Tr::tr("SSH &Port:"), m_portSpinBox); - formLayout->addRow(Git::Tr::tr("P&rotocol:"), m_httpsCheckBox); - m_httpsCheckBox->setToolTip(Git::Tr::tr( - "Determines the protocol used to form a URL in case\n" - "\"canonicalWebUrl\" is not configured in the file\n" - "\"gerrit.config\".")); - setTabOrder(m_sshChooser, m_curlChooser); - setTabOrder(m_curlChooser, m_portSpinBox); -} - -GerritParameters GerritOptionsWidget::parameters() const -{ - GerritParameters result; - result.server = GerritServer(m_hostLineEdit->text().trimmed(), - static_cast(m_portSpinBox->value()), - m_userLineEdit->text().trimmed(), - GerritServer::Ssh); - result.ssh = m_sshChooser->filePath(); - result.curl = m_curlChooser->filePath(); - result.https = m_httpsCheckBox->isChecked(); - return result; -} - -void GerritOptionsWidget::setParameters(const GerritParameters &p) -{ - m_hostLineEdit->setText(p.server.host); - m_userLineEdit->setText(p.server.user.userName); - m_sshChooser->setFilePath(p.ssh); - m_curlChooser->setFilePath(p.curl); - m_portSpinBox->setValue(p.server.port); - m_httpsCheckBox->setChecked(p.https); -} - -} // namespace Internal -} // namespace Gerrit +} // Gerrit::Internal diff --git a/src/plugins/git/gerrit/gerritoptionspage.h b/src/plugins/git/gerrit/gerritoptionspage.h index d5b220eb335..96caab06092 100644 --- a/src/plugins/git/gerrit/gerritoptionspage.h +++ b/src/plugins/git/gerrit/gerritoptionspage.h @@ -6,57 +6,16 @@ #include #include -#include -QT_BEGIN_NAMESPACE -class QLineEdit; -class QSpinBox; -class QCheckBox; -QT_END_NAMESPACE - -namespace Utils { class PathChooser; } -namespace Gerrit { -namespace Internal { +namespace Gerrit::Internal { class GerritParameters; -class GerritOptionsWidget : public QWidget -{ - Q_OBJECT -public: - explicit GerritOptionsWidget(QWidget *parent = nullptr); - - GerritParameters parameters() const; - void setParameters(const GerritParameters &); - -private: - QLineEdit *m_hostLineEdit; - QLineEdit *m_userLineEdit; - Utils::PathChooser *m_sshChooser; - Utils::PathChooser *m_curlChooser; - QSpinBox *m_portSpinBox; - QCheckBox *m_httpsCheckBox; -}; - class GerritOptionsPage : public Core::IOptionsPage { - Q_OBJECT - public: - GerritOptionsPage(const QSharedPointer &p, QObject *parent = nullptr); - ~GerritOptionsPage() override; - - QWidget *widget() override; - void apply() override; - void finish() override; - -signals: - void settingsChanged(); - -private: - const QSharedPointer &m_parameters; - QPointer m_widget; + GerritOptionsPage(const QSharedPointer &p, + const std::function &onChanged); }; -} // namespace Internal -} // namespace Gerrit +} // Gerrit::Internal diff --git a/src/plugins/git/gerrit/gerritplugin.cpp b/src/plugins/git/gerrit/gerritplugin.cpp index 41fa83731f9..87ce5bfdaca 100644 --- a/src/plugins/git/gerrit/gerritplugin.cpp +++ b/src/plugins/git/gerrit/gerritplugin.cpp @@ -22,8 +22,8 @@ #include #include +#include #include -#include #include @@ -78,7 +78,7 @@ private: const FetchMode m_fetchMode; const Utils::FilePath m_git; const GerritServer m_server; - QtcProcess m_process; + Process m_process; }; FetchContext::FetchContext(const QSharedPointer &change, @@ -93,11 +93,11 @@ FetchContext::FetchContext(const QSharedPointer &change, , m_server(server) { m_process.setUseCtrlCStub(true); - connect(&m_process, &QtcProcess::done, this, &FetchContext::processDone); - connect(&m_process, &QtcProcess::readyReadStandardError, this, [this] { + connect(&m_process, &Process::done, this, &FetchContext::processDone); + connect(&m_process, &Process::readyReadStandardError, this, [this] { VcsBase::VcsOutputWindow::append(QString::fromLocal8Bit(m_process.readAllRawStandardError())); }); - connect(&m_process, &QtcProcess::readyReadStandardOutput, this, [this] { + connect(&m_process, &Process::readyReadStandardOutput, this, [this] { VcsBase::VcsOutputWindow::append(QString::fromLocal8Bit(m_process.readAllRawStandardOutput())); }); m_process.setWorkingDirectory(repository); @@ -152,18 +152,26 @@ void FetchContext::checkout() GitClient::instance()->checkout(m_repository, "FETCH_HEAD"); } -GerritPlugin::GerritPlugin(QObject *parent) - : QObject(parent) - , m_parameters(new GerritParameters) +GerritPlugin::GerritPlugin() + : m_parameters(new GerritParameters) , m_server(new GerritServer) { + m_parameters->fromSettings(ICore::settings()); + + m_gerritOptionsPage = new GerritOptionsPage(m_parameters, + [this] { + if (m_dialog) + m_dialog->scheduleUpdateRemotes(); + }); } -GerritPlugin::~GerritPlugin() = default; - -void GerritPlugin::initialize(ActionContainer *ac) +GerritPlugin::~GerritPlugin() +{ + delete m_gerritOptionsPage; +} + +void GerritPlugin::addToMenu(ActionContainer *ac) { - m_parameters->fromSettings(ICore::settings()); QAction *openViewAction = new QAction(Git::Tr::tr("Gerrit..."), this); @@ -178,13 +186,6 @@ void GerritPlugin::initialize(ActionContainer *ac) ActionManager::registerAction(pushAction, Constants::GERRIT_PUSH); connect(pushAction, &QAction::triggered, this, [this] { push(); }); ac->addAction(m_pushToGerritCommand); - - auto options = new GerritOptionsPage(m_parameters, this); - connect(options, &GerritOptionsPage::settingsChanged, - this, [this] { - if (m_dialog) - m_dialog->scheduleUpdateRemotes(); - }); } void GerritPlugin::updateActions(const VcsBase::VcsBasePluginState &state) diff --git a/src/plugins/git/gerrit/gerritplugin.h b/src/plugins/git/gerrit/gerritplugin.h index 77d96d9f74a..b7e0de29d81 100644 --- a/src/plugins/git/gerrit/gerritplugin.h +++ b/src/plugins/git/gerrit/gerritplugin.h @@ -3,7 +3,7 @@ #pragma once -#include +#include #include #include @@ -25,15 +25,17 @@ class GerritChange; class GerritDialog; class GerritParameters; class GerritServer; +class GerritOptionsPage; class GerritPlugin : public QObject { Q_OBJECT + public: - explicit GerritPlugin(QObject *parent = nullptr); + GerritPlugin(); ~GerritPlugin() override; - void initialize(Core::ActionContainer *ac); + void addToMenu(Core::ActionContainer *ac); static Utils::FilePath gitBinDirectory(); static QString branch(const Utils::FilePath &repository); @@ -59,6 +61,7 @@ private: Core::Command *m_gerritCommand = nullptr; Core::Command *m_pushToGerritCommand = nullptr; QString m_reviewers; + GerritOptionsPage *m_gerritOptionsPage = nullptr; }; } // namespace Internal diff --git a/src/plugins/git/gerrit/gerritpushdialog.cpp b/src/plugins/git/gerrit/gerritpushdialog.cpp index 057d4cd6448..87961c5945a 100644 --- a/src/plugins/git/gerrit/gerritpushdialog.cpp +++ b/src/plugins/git/gerrit/gerritpushdialog.cpp @@ -125,7 +125,7 @@ GerritPushDialog::GerritPushDialog(const Utils::FilePath &workingDir, const QStr "Unchecked - Remove mark.\n" "Partially checked - Do not change current state.")); m_commitView->setToolTip(::Git::Tr::tr( - "Pushes the selected commit and all dependent commits.")); + "Pushes the selected commit and all commits it depends on.")); m_reviewersLineEdit->setToolTip(::Git::Tr::tr("Comma-separated list of reviewers.\n" "\n" "Reviewers can be specified by nickname or email address. Spaces not allowed.\n" @@ -136,7 +136,7 @@ GerritPushDialog::GerritPushDialog(const Utils::FilePath &workingDir, const QStr connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - using namespace Utils::Layouting; + using namespace Layouting; Grid { ::Git::Tr::tr("Push:"), workingDir.toUserOutput(), m_localBranchComboBox, br, diff --git a/src/plugins/git/gitclient.cpp b/src/plugins/git/gitclient.cpp index e419449fe02..935b56291ff 100644 --- a/src/plugins/git/gitclient.cpp +++ b/src/plugins/git/gitclient.cpp @@ -19,7 +19,7 @@ #include #include -#include +#include #include #include #include @@ -27,8 +27,8 @@ #include #include #include +#include #include -#include #include #include @@ -60,7 +60,7 @@ const char GIT_DIRECTORY[] = ".git"; const char HEAD[] = "HEAD"; const char CHERRY_PICK_HEAD[] = "CHERRY_PICK_HEAD"; -const char BRANCHES_PREFIX[] = "Branches: "; +[[maybe_unused]] const char BRANCHES_PREFIX[] = "Branches: "; const char stashNamePrefix[] = "stash@{"; const char noColorOption[] = "--no-color"; const char colorOption[] = "--color=always"; @@ -76,6 +76,7 @@ const char showFormatC[] = using namespace Core; using namespace DiffEditor; +using namespace Tasking; using namespace Utils; using namespace VcsBase; @@ -92,7 +93,7 @@ static QString branchesDisplay(const QString &prefix, QStringList *branches, boo if (*first) *first = false; else - output += QString(sizeof(BRANCHES_PREFIX) - 1, ' '); // Align + output += QString(sizeof(BRANCHES_PREFIX) - 1 /* the \0 */, ' '); // Align output += prefix + ": "; // If there are more than 'limit' branches, list limit/2 (first limit/4 and last limit/4) if (count > limit) { @@ -165,18 +166,18 @@ GitDiffEditorController::GitDiffEditorController(IDocument *document, const TreeStorage diffInputStorage = inputStorage(); - const auto setupDiff = [=](QtcProcess &process) { + const auto setupDiff = [=](Process &process) { process.setCodec(VcsBaseEditor::getCodec(workingDirectory(), {})); setupCommand(process, {addConfigurationArguments(diffArgs(leftCommit, rightCommit, extraArgs))}); VcsOutputWindow::appendCommand(process.workingDirectory(), process.commandLine()); }; - const auto onDiffDone = [diffInputStorage](const QtcProcess &process) { - *diffInputStorage.activeStorage() = process.cleanedStdOut(); + const auto onDiffDone = [diffInputStorage](const Process &process) { + *diffInputStorage = process.cleanedStdOut(); }; const Group root { Storage(diffInputStorage), - Process(setupDiff, onDiffDone), + ProcessTask(setupDiff, onDiffDone), postProcessTask() }; setReloadRecipe(root); @@ -231,7 +232,7 @@ FileListDiffController::FileListDiffController(IDocument *document, const QStrin const TreeStorage storage; const TreeStorage diffInputStorage = inputStorage(); - const auto setupStaged = [this, stagedFiles](QtcProcess &process) { + const auto setupStaged = [this, stagedFiles](Process &process) { if (stagedFiles.isEmpty()) return TaskAction::StopWithError; process.setCodec(VcsBaseEditor::getCodec(workingDirectory(), stagedFiles)); @@ -240,11 +241,11 @@ FileListDiffController::FileListDiffController(IDocument *document, const QStrin VcsOutputWindow::appendCommand(process.workingDirectory(), process.commandLine()); return TaskAction::Continue; }; - const auto onStagedDone = [storage](const QtcProcess &process) { + const auto onStagedDone = [storage](const Process &process) { storage->m_stagedOutput = process.cleanedStdOut(); }; - const auto setupUnstaged = [this, unstagedFiles](QtcProcess &process) { + const auto setupUnstaged = [this, unstagedFiles](Process &process) { if (unstagedFiles.isEmpty()) return TaskAction::StopWithError; process.setCodec(VcsBaseEditor::getCodec(workingDirectory(), unstagedFiles)); @@ -253,12 +254,12 @@ FileListDiffController::FileListDiffController(IDocument *document, const QStrin VcsOutputWindow::appendCommand(process.workingDirectory(), process.commandLine()); return TaskAction::Continue; }; - const auto onUnstagedDone = [storage](const QtcProcess &process) { + const auto onUnstagedDone = [storage](const Process &process) { storage->m_unstagedOutput = process.cleanedStdOut(); }; const auto onStagingDone = [storage, diffInputStorage] { - *diffInputStorage.activeStorage() = storage->m_stagedOutput + storage->m_unstagedOutput; + *diffInputStorage = storage->m_stagedOutput + storage->m_unstagedOutput; }; const Group root { @@ -267,9 +268,9 @@ FileListDiffController::FileListDiffController(IDocument *document, const QStrin Group { parallel, continueOnDone, - Process(setupStaged, onStagedDone), - Process(setupUnstaged, onUnstagedDone), - OnGroupDone(onStagingDone) + ProcessTask(setupStaged, onStagedDone), + ProcessTask(setupUnstaged, onUnstagedDone), + onGroupDone(onStagingDone) }, postProcessTask() }; @@ -321,13 +322,13 @@ ShowController::ShowController(IDocument *document, const QString &id) setDescription(desc); }; - const auto setupDescription = [this, id](QtcProcess &process) { + const auto setupDescription = [this, id](Process &process) { process.setCodec(m_instance->encoding(GitClient::EncodingCommit, workingDirectory())); setupCommand(process, {"show", "-s", noColorOption, showFormatC, id}); VcsOutputWindow::appendCommand(process.workingDirectory(), process.commandLine()); setDescription(Tr::tr("Waiting for data...")); }; - const auto onDescriptionDone = [this, storage, updateDescription](const QtcProcess &process) { + const auto onDescriptionDone = [this, storage, updateDescription](const Process &process) { ReloadStorage *data = storage.activeStorage(); const QString output = process.cleanedStdOut(); data->m_postProcessDescription = output.startsWith("commit "); @@ -348,12 +349,12 @@ ShowController::ShowController(IDocument *document, const QString &id) return TaskAction::Continue; }; - const auto setupBranches = [this, storage](QtcProcess &process) { + const auto setupBranches = [this, storage](Process &process) { storage->m_branches = busyMessage; setupCommand(process, {"branch", noColorOption, "-a", "--contains", storage->m_commit}); VcsOutputWindow::appendCommand(process.workingDirectory(), process.commandLine()); }; - const auto onBranchesDone = [storage, updateDescription](const QtcProcess &process) { + const auto onBranchesDone = [storage, updateDescription](const Process &process) { ReloadStorage *data = storage.activeStorage(); data->m_branches.clear(); const QString remotePrefix = "remotes/"; @@ -391,17 +392,17 @@ ShowController::ShowController(IDocument *document, const QString &id) data->m_branches = data->m_branches.trimmed(); updateDescription(*data); }; - const auto onBranchesError = [storage, updateDescription](const QtcProcess &) { + const auto onBranchesError = [storage, updateDescription](const Process &) { ReloadStorage *data = storage.activeStorage(); data->m_branches.clear(); updateDescription(*data); }; - const auto setupPrecedes = [this, storage](QtcProcess &process) { + const auto setupPrecedes = [this, storage](Process &process) { storage->m_precedes = busyMessage; setupCommand(process, {"describe", "--contains", storage->m_commit}); }; - const auto onPrecedesDone = [storage, updateDescription](const QtcProcess &process) { + const auto onPrecedesDone = [storage, updateDescription](const Process &process) { ReloadStorage *data = storage.activeStorage(); data->m_precedes = process.cleanedStdOut().trimmed(); const int tilde = data->m_precedes.indexOf('~'); @@ -411,7 +412,7 @@ ShowController::ShowController(IDocument *document, const QString &id) data->m_precedes.chop(2); updateDescription(*data); }; - const auto onPrecedesError = [storage, updateDescription](const QtcProcess &) { + const auto onPrecedesError = [storage, updateDescription](const Process &) { ReloadStorage *data = storage.activeStorage(); data->m_precedes.clear(); updateDescription(*data); @@ -427,10 +428,10 @@ ShowController::ShowController(IDocument *document, const QString &id) data->m_follows = {busyMessage}; data->m_follows.resize(parents.size()); - const auto setupFollow = [this](QtcProcess &process, const QString &parent) { + const auto setupFollow = [this](Process &process, const QString &parent) { setupCommand(process, {"describe", "--tags", "--abbrev=0", parent}); }; - const auto onFollowDone = [data, updateDescription](const QtcProcess &process, int index) { + const auto onFollowDone = [data, updateDescription](const Process &process, int index) { data->m_follows[index] = process.cleanedStdOut().trimmed(); updateDescription(*data); }; @@ -440,43 +441,43 @@ ShowController::ShowController(IDocument *document, const QString &id) }; using namespace std::placeholders; - QList tasks {parallel, continueOnDone, OnGroupError(onFollowsError)}; + QList tasks {parallel, continueOnDone, onGroupError(onFollowsError)}; for (int i = 0, total = parents.size(); i < total; ++i) { - tasks.append(Process(std::bind(setupFollow, _1, parents.at(i)), + tasks.append(ProcessTask(std::bind(setupFollow, _1, parents.at(i)), std::bind(onFollowDone, _1, i))); } taskTree.setupRoot(tasks); }; - const auto setupDiff = [this, id](QtcProcess &process) { + const auto setupDiff = [this, id](Process &process) { setupCommand(process, addConfigurationArguments( {"show", "--format=format:", // omit header, already generated noColorOption, decorateOption, id})); VcsOutputWindow::appendCommand(process.workingDirectory(), process.commandLine()); }; - const auto onDiffDone = [diffInputStorage](const QtcProcess &process) { - *diffInputStorage.activeStorage() = process.cleanedStdOut(); + const auto onDiffDone = [diffInputStorage](const Process &process) { + *diffInputStorage = process.cleanedStdOut(); }; const Group root { Storage(storage), Storage(diffInputStorage), parallel, - OnGroupSetup([this] { setStartupFile(VcsBase::source(this->document()).toString()); }), + onGroupSetup([this] { setStartupFile(VcsBase::source(this->document()).toString()); }), Group { - optional, - Process(setupDescription, onDescriptionDone), + finishAllAndDone, + ProcessTask(setupDescription, onDescriptionDone), Group { parallel, - optional, - OnGroupSetup(desciptionDetailsSetup), - Process(setupBranches, onBranchesDone, onBranchesError), - Process(setupPrecedes, onPrecedesDone, onPrecedesError), - Tree(setupFollows) + finishAllAndDone, + onGroupSetup(desciptionDetailsSetup), + ProcessTask(setupBranches, onBranchesDone, onBranchesError), + ProcessTask(setupPrecedes, onPrecedesDone, onPrecedesError), + TaskTreeTask(setupFollows) } }, Group { - Process(setupDiff, onDiffDone), + ProcessTask(setupDiff, onDiffDone), postProcessTask() } }; @@ -490,16 +491,16 @@ class BaseGitDiffArgumentsWidget : public VcsBaseEditorConfig Q_OBJECT public: - BaseGitDiffArgumentsWidget(GitSettings &settings, QToolBar *toolBar) : - VcsBaseEditorConfig(toolBar) + explicit BaseGitDiffArgumentsWidget(QToolBar *toolBar) + : VcsBaseEditorConfig(toolBar) { m_patienceButton = addToggleButton("--patience", Tr::tr("Patience"), Tr::tr("Use the patience algorithm for calculating the differences.")); - mapSetting(m_patienceButton, &settings.diffPatience); + mapSetting(m_patienceButton, &settings().diffPatience); m_ignoreWSButton = addToggleButton("--ignore-space-change", Tr::tr("Ignore Whitespace"), Tr::tr("Ignore whitespace only changes.")); - mapSetting(m_ignoreWSButton, &settings.ignoreSpaceChangesInDiff); + mapSetting(m_ignoreWSButton, &settings().ignoreSpaceChangesInDiff); } protected: @@ -512,15 +513,15 @@ class GitBlameArgumentsWidget : public VcsBaseEditorConfig Q_OBJECT public: - GitBlameArgumentsWidget(GitSettings &settings, QToolBar *toolBar) : - VcsBaseEditorConfig(toolBar) + explicit GitBlameArgumentsWidget(QToolBar *toolBar) + : VcsBaseEditorConfig(toolBar) { mapSetting(addToggleButton(QString(), Tr::tr("Omit Date"), Tr::tr("Hide the date of a change from the output.")), - &settings.omitAnnotationDate); + &settings().omitAnnotationDate); mapSetting(addToggleButton("-w", Tr::tr("Ignore Whitespace"), Tr::tr("Ignore whitespace only changes.")), - &settings.ignoreSpaceChangesInBlame); + &settings().ignoreSpaceChangesInBlame); const QList logChoices = { ChoiceItem(Tr::tr("No Move Detection"), ""), @@ -529,7 +530,7 @@ public: ChoiceItem(Tr::tr("Detect Moves and Copies Between Files"), "-M -C -C") }; mapSetting(addChoices(Tr::tr("Move detection"), {}, logChoices), - &settings.blameMoveDetection); + &settings().blameMoveDetection); addReloadButton(); } @@ -540,13 +541,13 @@ class BaseGitLogArgumentsWidget : public BaseGitDiffArgumentsWidget Q_OBJECT public: - BaseGitLogArgumentsWidget(GitSettings &settings, GitEditorWidget *editor) : - BaseGitDiffArgumentsWidget(settings, editor->toolBar()) + BaseGitLogArgumentsWidget(GitEditorWidget *editor) + : BaseGitDiffArgumentsWidget(editor->toolBar()) { QToolBar *toolBar = editor->toolBar(); QAction *diffButton = addToggleButton(patchOption, Tr::tr("Diff"), Tr::tr("Show difference.")); - mapSetting(diffButton, &settings.logDiff); + mapSetting(diffButton, &settings().logDiff); connect(diffButton, &QAction::toggled, m_patienceButton, &QAction::setVisible); connect(diffButton, &QAction::toggled, m_ignoreWSButton, &QAction::setVisible); m_patienceButton->setVisible(diffButton->isChecked()); @@ -581,27 +582,27 @@ class GitLogArgumentsWidget : public BaseGitLogArgumentsWidget Q_OBJECT public: - GitLogArgumentsWidget(GitSettings &settings, bool fileRelated, GitEditorWidget *editor) : - BaseGitLogArgumentsWidget(settings, editor) + GitLogArgumentsWidget(bool fileRelated, GitEditorWidget *editor) + : BaseGitLogArgumentsWidget(editor) { QAction *firstParentButton = addToggleButton({"-m", "--first-parent"}, Tr::tr("First Parent"), Tr::tr("Follow only the first parent on merge commits.")); - mapSetting(firstParentButton, &settings.firstParent); + mapSetting(firstParentButton, &settings().firstParent); QAction *graphButton = addToggleButton(graphArguments(), Tr::tr("Graph"), Tr::tr("Show textual graph log.")); - mapSetting(graphButton, &settings.graphLog); + mapSetting(graphButton, &settings().graphLog); QAction *colorButton = addToggleButton(QStringList{colorOption}, Tr::tr("Color"), Tr::tr("Use colors in log.")); - mapSetting(colorButton, &settings.colorLog); + mapSetting(colorButton, &settings().colorLog); if (fileRelated) { QAction *followButton = addToggleButton( "--follow", Tr::tr("Follow"), Tr::tr("Show log also for previous names of the file.")); - mapSetting(followButton, &settings.followRenames); + mapSetting(followButton, &settings().followRenames); } addReloadButton(); @@ -640,14 +641,14 @@ class GitRefLogArgumentsWidget : public BaseGitLogArgumentsWidget Q_OBJECT public: - GitRefLogArgumentsWidget(GitSettings &settings, GitEditorWidget *editor) : - BaseGitLogArgumentsWidget(settings, editor) + explicit GitRefLogArgumentsWidget(GitEditorWidget *editor) + : BaseGitLogArgumentsWidget(editor) { QAction *showDateButton = addToggleButton("--date=iso", Tr::tr("Show Date"), Tr::tr("Show date instead of sequence.")); - mapSetting(showDateButton, &settings.refLogShowDate); + mapSetting(showDateButton, &settings().refLogShowDate); addReloadButton(); } @@ -735,8 +736,8 @@ static inline void msgCannotRun(const QStringList &args, const FilePath &working // ---------------- GitClient -GitClient::GitClient(GitSettings *settings) - : VcsBase::VcsBaseClientImpl(settings) +GitClient::GitClient() + : VcsBase::VcsBaseClientImpl(&Internal::settings()) { m_instance = this; m_gitQtcEditor = QString::fromLatin1("\"%1\" -client -block -pid %2") @@ -751,7 +752,7 @@ GitClient *GitClient::instance() GitSettings &GitClient::settings() { - return static_cast(m_instance->VcsBaseClientImpl::settings()); + return Internal::settings(); } FilePath GitClient::findRepositoryForDirectory(const FilePath &directory) const @@ -821,14 +822,19 @@ FilePaths GitClient::unmanagedFiles(const FilePaths &filePaths) const return res; } +QTextCodec *GitClient::defaultCommitEncoding() const +{ + // Set default commit encoding to 'UTF-8', when it's not set, + // to solve displaying error of commit log with non-latin characters. + return QTextCodec::codecForName("UTF-8"); +} + QTextCodec *GitClient::encoding(GitClient::EncodingType encodingType, const FilePath &source) const { auto codec = [this](const FilePath &workingDirectory, const QString &configVar) { const QString codecName = readConfigValue(workingDirectory, configVar).trimmed(); - // Set default commit encoding to 'UTF-8', when it's not set, - // to solve displaying error of commit log with non-latin characters. if (codecName.isEmpty()) - return QTextCodec::codecForName("UTF-8"); + return defaultCommitEncoding(); return QTextCodec::codecForName(codecName.toUtf8()); }; @@ -1074,7 +1080,7 @@ void GitClient::log(const FilePath &workingDirectory, const QString &fileName, encoding(EncodingLogOutput), "logTitle", msgArg)); VcsBaseEditorConfig *argWidget = editor->editorConfig(); if (!argWidget) { - argWidget = new GitLogArgumentsWidget(settings(), !fileName.isEmpty(), editor); + argWidget = new GitLogArgumentsWidget(!fileName.isEmpty(), editor); argWidget->setBaseArguments(args); connect(argWidget, &VcsBaseEditorConfig::commandExecutionRequested, this, [=] { this->log(workingDir, fileName, enableAnnotationContextMenu, args); }); @@ -1084,7 +1090,7 @@ void GitClient::log(const FilePath &workingDirectory, const QString &fileName, editor->setWorkingDirectory(workingDir); QStringList arguments = {"log", decorateOption}; - int logCount = settings().logCount.value(); + int logCount = settings().logCount(); if (logCount > 0) arguments << "-n" << QString::number(logCount); @@ -1130,7 +1136,7 @@ void GitClient::reflog(const FilePath &workingDirectory, const QString &ref) "reflogRepository", workingDir.toString())); VcsBaseEditorConfig *argWidget = editor->editorConfig(); if (!argWidget) { - argWidget = new GitRefLogArgumentsWidget(settings(), editor); + argWidget = new GitRefLogArgumentsWidget(editor); if (!ref.isEmpty()) argWidget->setBaseArguments({ref}); connect(argWidget, &VcsBaseEditorConfig::commandExecutionRequested, this, @@ -1141,7 +1147,7 @@ void GitClient::reflog(const FilePath &workingDirectory, const QString &ref) QStringList arguments = {"reflog", noColorOption, decorateOption}; arguments << argWidget->arguments(); - int logCount = settings().logCount.value(); + int logCount = settings().logCount(); if (logCount > 0) arguments << "-n" << QString::number(logCount); @@ -1242,7 +1248,7 @@ void GitClient::annotate(const Utils::FilePath &workingDir, const QString &file, encoding(EncodingSource, sourceFile), "blameFileName", id); VcsBaseEditorConfig *argWidget = editor->editorConfig(); if (!argWidget) { - argWidget = new GitBlameArgumentsWidget(settings(), editor->toolBar()); + argWidget = new GitBlameArgumentsWidget(editor->toolBar()); argWidget->setBaseArguments(extraOptions); connect(argWidget, &VcsBaseEditorConfig::commandExecutionRequested, this, [=] { const int line = VcsBaseEditor::lineNumberOfCurrentEditor(); @@ -1292,14 +1298,15 @@ QStringList GitClient::setupCheckoutArguments(const FilePath &workingDirectory, if (localBranches.contains(ref)) return arguments; - if (Utils::CheckableMessageBox::doNotAskAgainQuestion( - ICore::dialogParent() /*parent*/, - Tr::tr("Create Local Branch") /*title*/, - Tr::tr("Would you like to create a local branch?") /*message*/, - ICore::settings(), "Git.CreateLocalBranchOnCheckout" /*setting*/, - QDialogButtonBox::Yes | QDialogButtonBox::No /*buttons*/, - QDialogButtonBox::No /*default button*/, - QDialogButtonBox::No /*button to save*/) != QDialogButtonBox::Yes) { + if (Utils::CheckableMessageBox::question( + ICore::dialogParent() /*parent*/, + Tr::tr("Create Local Branch") /*title*/, + Tr::tr("Would you like to create a local branch?") /*message*/, + QString("Git.CreateLocalBranchOnCheckout"), /* decider */ + QMessageBox::Yes | QMessageBox::No /*buttons*/, + QMessageBox::No /*default button*/, + QMessageBox::No /*button to save*/) + != QMessageBox::Yes) { return arguments; } @@ -1730,19 +1737,25 @@ bool GitClient::synchronousRevParseCmd(const FilePath &workingDirectory, const Q } // Retrieve head revision -QString GitClient::synchronousTopRevision(const FilePath &workingDirectory, QDateTime *dateTime) +ProcessTask GitClient::topRevision(const FilePath &workingDirectory, + const std::function &callback) { - const QStringList arguments = {"show", "-s", "--pretty=format:%H:%ct", HEAD}; - const CommandResult result = vcsSynchronousExec(workingDirectory, arguments, RunFlags::NoOutput); - if (result.result() != ProcessResult::FinishedWithSuccess) - return QString(); - const QStringList output = result.cleanedStdOut().trimmed().split(':'); - if (dateTime && output.size() > 1) { - bool ok = false; - const qint64 timeT = output.at(1).toLongLong(&ok); - *dateTime = ok ? QDateTime::fromSecsSinceEpoch(timeT) : QDateTime(); - } - return output.first(); + const auto setupProcess = [=](Process &process) { + setupCommand(process, workingDirectory, {"show", "-s", "--pretty=format:%H:%ct", HEAD}); + }; + const auto onProcessDone = [=](const Process &process) { + const QStringList output = process.cleanedStdOut().trimmed().split(':'); + QDateTime dateTime; + if (output.size() > 1) { + bool ok = false; + const qint64 timeT = output.at(1).toLongLong(&ok); + if (ok) + dateTime = QDateTime::fromSecsSinceEpoch(timeT); + } + callback(output.first(), dateTime); + }; + + return ProcessTask(setupProcess, onProcessDone); } bool GitClient::isRemoteCommit(const FilePath &workingDirectory, const QString &commit) @@ -1752,13 +1765,6 @@ bool GitClient::isRemoteCommit(const FilePath &workingDirectory, const QString & return !result.rawStdOut().isEmpty(); } -bool GitClient::isFastForwardMerge(const FilePath &workingDirectory, const QString &branch) -{ - const CommandResult result = vcsSynchronousExec(workingDirectory, - {"merge-base", HEAD, branch}, RunFlags::NoOutput); - return result.cleanedStdOut().trimmed() == synchronousTopRevision(workingDirectory); -} - // Format an entry in a one-liner for selection list using git log. QString GitClient::synchronousShortDescription(const FilePath &workingDirectory, const QString &revision, const QString &format) const @@ -2420,9 +2426,9 @@ void GitClient::launchGitK(const FilePath &workingDirectory, const QString &file void GitClient::launchRepositoryBrowser(const FilePath &workingDirectory) const { - const FilePath repBrowserBinary = settings().repositoryBrowserCmd.filePath(); + const FilePath repBrowserBinary = settings().repositoryBrowserCmd(); if (!repBrowserBinary.isEmpty()) - QtcProcess::startDetached({repBrowserBinary, {workingDirectory.toString()}}, workingDirectory); + Process::startDetached({repBrowserBinary, {workingDirectory.toString()}}, workingDirectory); } static FilePath gitBinDir(const GitClient::GitKLaunchTrial trial, const FilePath &parentDir) @@ -2466,21 +2472,21 @@ void GitClient::tryLaunchingGitK(const Environment &env, arguments << "--" << fileName; VcsOutputWindow::appendCommand(workingDirectory, {binary, arguments}); - // This should always use QtcProcess::startDetached (as not to kill + // This should always use Process::startDetached (as not to kill // the child), but that does not have an environment parameter. if (!settings().path.value().isEmpty()) { - auto process = new QtcProcess(const_cast(this)); + auto process = new Process(const_cast(this)); process->setWorkingDirectory(workingDirectory); process->setEnvironment(env); process->setCommand({binary, arguments}); - connect(process, &QtcProcess::done, this, [=] { + connect(process, &Process::done, this, [=] { if (process->result() == ProcessResult::StartFailed) handleGitKFailedToStart(env, workingDirectory, fileName, trial, gitBinDirectory); process->deleteLater(); }); process->start(); } else { - if (!QtcProcess::startDetached({binary, arguments}, workingDirectory)) + if (!Process::startDetached({binary, arguments}, workingDirectory)) handleGitKFailedToStart(env, workingDirectory, fileName, trial, gitBinDirectory); } } @@ -2517,7 +2523,7 @@ bool GitClient::launchGitGui(const FilePath &workingDirectory) { if (gitBinary.isEmpty()) { success = false; } else { - success = QtcProcess::startDetached({gitBinary, {"gui"}}, workingDirectory); + success = Process::startDetached({gitBinary, {"gui"}}, workingDirectory); } if (!success) @@ -2562,7 +2568,7 @@ bool GitClient::launchGitBash(const FilePath &workingDirectory) success = false; } else { const FilePath gitBash = git.absolutePath().parentDir() / "git-bash.exe"; - success = QtcProcess::startDetached({gitBash, {}}, workingDirectory); + success = Process::startDetached({gitBash, {}}, workingDirectory); } if (!success) @@ -2574,7 +2580,7 @@ bool GitClient::launchGitBash(const FilePath &workingDirectory) FilePath GitClient::vcsBinary() const { bool ok; - Utils::FilePath binary = static_cast(settings()).gitExecutable(&ok); + Utils::FilePath binary = settings().gitExecutable(&ok); if (!ok) return Utils::FilePath(); return binary; @@ -2617,11 +2623,10 @@ bool GitClient::readDataFromCommit(const FilePath &repoDirectory, const QString return true; } -Author GitClient::getAuthor(const Utils::FilePath &workingDirectory) +Author GitClient::parseAuthor(const QString &authorInfo) { // The format is: // Joe Developer unixtimestamp +HHMM - const QString authorInfo = readGitVar(workingDirectory, "GIT_AUTHOR_IDENT"); int lt = authorInfo.lastIndexOf('<'); int gt = authorInfo.lastIndexOf('>'); if (gt == -1 || uint(lt) > uint(gt)) { @@ -2633,6 +2638,12 @@ Author GitClient::getAuthor(const Utils::FilePath &workingDirectory) return result; } +Author GitClient::getAuthor(const Utils::FilePath &workingDirectory) +{ + const QString authorInfo = readGitVar(workingDirectory, "GIT_AUTHOR_IDENT"); + return parseAuthor(authorInfo); +} + bool GitClient::getCommitData(const FilePath &workingDirectory, QString *commitTemplate, CommitData &commitData, @@ -2862,7 +2873,7 @@ bool GitClient::addAndCommit(const FilePath &repositoryDirectory, GitPlugin::updateCurrentBranch(); return true; } - VcsOutputWindow::appendError(Tr::tr("Cannot commit %n files", nullptr, commitCount) + "\n"); + VcsOutputWindow::appendError(Tr::tr("Cannot commit %n file(s)", nullptr, commitCount) + "\n"); return false; } @@ -3120,7 +3131,7 @@ void GitClient::synchronousSubversionFetch(const FilePath &workingDirectory) con void GitClient::subversionLog(const FilePath &workingDirectory) const { QStringList arguments = {"svn", "log"}; - int logCount = settings().logCount.value(); + int logCount = settings().logCount(); if (logCount > 0) arguments << ("--limit=" + QString::number(logCount)); @@ -3424,21 +3435,33 @@ QString GitClient::readGitVar(const FilePath &workingDirectory, const QString &c return readOneLine(workingDirectory, {"var", configVar}); } -QString GitClient::readOneLine(const FilePath &workingDirectory, const QStringList &arguments) const +static QTextCodec *configFileCodec() { // Git for Windows always uses UTF-8 for configuration: // https://github.com/msysgit/msysgit/wiki/Git-for-Windows-Unicode-Support#convert-config-files static QTextCodec *codec = HostOsInfo::isWindowsHost() ? QTextCodec::codecForName("UTF-8") : QTextCodec::codecForLocale(); + return codec; +} +QString GitClient::readOneLine(const FilePath &workingDirectory, const QStringList &arguments) const +{ const CommandResult result = vcsSynchronousExec(workingDirectory, arguments, - RunFlags::NoOutput, vcsTimeoutS(), codec); + RunFlags::NoOutput, vcsTimeoutS(), + configFileCodec()); if (result.result() == ProcessResult::FinishedWithSuccess) return result.cleanedStdOut().trimmed(); return {}; } +void GitClient::readConfigAsync(const FilePath &workingDirectory, const QStringList &arguments, + const CommandHandler &handler) const +{ + vcsExecWithHandler(workingDirectory, arguments, this, handler, RunFlags::NoOutput, + configFileCodec()); +} + static unsigned parseGitVersion(const QString &output) { // cut 'git version 1.6.5.1.sha' @@ -3464,8 +3487,8 @@ QFuture GitClient::gitVersion() const const FilePath newGitBinary = vcsBinary(); const bool needToRunGit = m_gitVersionForBinary != newGitBinary && !newGitBinary.isEmpty(); if (needToRunGit) { - auto proc = new QtcProcess(const_cast(this)); - connect(proc, &QtcProcess::done, this, [this, proc, fi, newGitBinary]() mutable { + auto proc = new Process(const_cast(this)); + connect(proc, &Process::done, this, [this, proc, fi, newGitBinary]() mutable { if (proc->result() == ProcessResult::FinishedWithSuccess) { m_cachedGitVersion = parseGitVersion(proc->cleanedStdOut()); m_gitVersionForBinary = newGitBinary; diff --git a/src/plugins/git/gitclient.h b/src/plugins/git/gitclient.h index 2dfd6ddaaa1..398b1c1cc86 100644 --- a/src/plugins/git/gitclient.h +++ b/src/plugins/git/gitclient.h @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -78,6 +79,12 @@ public: }; struct Author { + bool operator==(const Author &other) const { + return name == other.name && email == other.email; + } + bool operator!=(const Author &other) const { + return !operator==(other); + } QString name; QString email; }; @@ -114,9 +121,8 @@ public: PushAction m_pushAction = NoPush; }; - explicit GitClient(GitSettings *settings); + GitClient(); static GitClient *instance(); - static GitSettings &settings(); Utils::FilePath vcsBinary() const override; QFuture gitVersion() const; @@ -241,9 +247,9 @@ public: QString synchronousTopic(const Utils::FilePath &workingDirectory) const; bool synchronousRevParseCmd(const Utils::FilePath &workingDirectory, const QString &ref, QString *output, QString *errorMessage = nullptr) const; - QString synchronousTopRevision(const Utils::FilePath &workingDirectory, QDateTime *dateTime = nullptr); + Tasking::ProcessTask topRevision(const Utils::FilePath &workingDirectory, + const std::function &callback); bool isRemoteCommit(const Utils::FilePath &workingDirectory, const QString &commit); - bool isFastForwardMerge(const Utils::FilePath &workingDirectory, const QString &branch); void fetch(const Utils::FilePath &workingDirectory, const QString &remote); void pull(const Utils::FilePath &workingDirectory, bool rebase); @@ -343,12 +349,19 @@ public: Core::IEditor *openShowEditor(const Utils::FilePath &workingDirectory, const QString &ref, const Utils::FilePath &path, ShowEditor showSetting = ShowEditor::Always); + Author parseAuthor(const QString &authorInfo); Author getAuthor(const Utils::FilePath &workingDirectory); + QTextCodec *defaultCommitEncoding() const; enum EncodingType { EncodingSource, EncodingLogOutput, EncodingCommit, EncodingDefault }; QTextCodec *encoding(EncodingType encodingType, const Utils::FilePath &source = {}) const; + void readConfigAsync(const Utils::FilePath &workingDirectory, const QStringList &arguments, + const VcsBase::CommandHandler &handler) const; + private: + static GitSettings &settings(); + void finishSubmoduleUpdate(); void chunkActionsRequested(DiffEditor::DiffEditorController *controller, QMenu *menu, int fileIndex, int chunkIndex, @@ -400,6 +413,7 @@ private: QString m_diffCommit; Utils::FilePaths m_updatedSubmodules; bool m_disableEditor = false; + // The synchronizer has cancelOnWait set to true by default. Utils::FutureSynchronizer m_synchronizer; // for commit updates }; diff --git a/src/plugins/git/giteditor.cpp b/src/plugins/git/giteditor.cpp index 7f73842cb02..c7078dc2ba1 100644 --- a/src/plugins/git/giteditor.cpp +++ b/src/plugins/git/giteditor.cpp @@ -132,7 +132,7 @@ static QString sanitizeBlameOutput(const QString &b) if (b.isEmpty()) return b; - const bool omitDate = GitClient::instance()->settings().omitAnnotationDate.value(); + const bool omitDate = settings().omitAnnotationDate.value(); const QChar space(' '); const int parenPos = b.indexOf(')'); if (parenPos == -1) diff --git a/src/plugins/git/gitgrep.cpp b/src/plugins/git/gitgrep.cpp index 6baa635080d..f6324f152de 100644 --- a/src/plugins/git/gitgrep.cpp +++ b/src/plugins/git/gitgrep.cpp @@ -13,21 +13,20 @@ #include #include +#include #include #include #include +#include #include -#include -#include #include -#include #include #include #include -#include using namespace Core; +using namespace TextEditor; using namespace Utils; using namespace VcsBase; @@ -43,92 +42,106 @@ public: QString id() const { return recurseSubmodules ? ref + ".Rec" : ref; } }; -class GitGrepRunner +static QStringView nextLine(QStringView *remainingInput) { - using FutureInterfaceType = QFutureInterface; - -public: - GitGrepRunner(const TextEditor::FileFindParameters ¶meters) - : m_parameters(parameters) - { - m_directory = FilePath::fromString(parameters.additionalParameters.toString()); - m_vcsBinary = GitClient::instance()->vcsBinary(); - m_environment = GitClient::instance()->processEnvironment(); + const int newLinePos = remainingInput->indexOf('\n'); + if (newLinePos < 0) { + QStringView ret = *remainingInput; + *remainingInput = QStringView(); + return ret; } + QStringView ret = remainingInput->left(newLinePos); + *remainingInput = remainingInput->mid(newLinePos + 1); + return ret; +} - struct Match - { - Match() = default; - Match(int start, int length) : - matchStart(start), matchLength(length) {} +struct Match +{ + Match() = default; + Match(int start, int length) : + matchStart(start), matchLength(length) {} - int matchStart = 0; - int matchLength = 0; - QStringList regexpCapturedTexts; - }; + int matchStart = 0; + int matchLength = 0; + QStringList regexpCapturedTexts; +}; - void processLine(const QString &line, FileSearchResultList *resultList) const - { +static void processLine(QStringView line, SearchResultItems *resultList, + const std::optional ®Exp, const QString &ref, + const FilePath &directory) +{ + if (line.isEmpty()) + return; + static const QLatin1String boldRed("\x1b[1;31m"); + static const QLatin1String resetColor("\x1b[m"); + SearchResultItem result; + const int lineSeparator = line.indexOf(QChar::Null); + QStringView filePath = line.left(lineSeparator); + if (!ref.isEmpty() && filePath.startsWith(ref)) + filePath = filePath.mid(ref.length()); + result.setFilePath(directory.pathAppended(filePath.toString())); + const int textSeparator = line.indexOf(QChar::Null, lineSeparator + 1); + const int lineNumber = line.mid(lineSeparator + 1, textSeparator - lineSeparator - 1).toInt(); + QString text = line.mid(textSeparator + 1).toString(); + QList matches; + while (true) { + const int matchStart = text.indexOf(boldRed); + if (matchStart == -1) + break; + const int matchTextStart = matchStart + boldRed.size(); + const int matchEnd = text.indexOf(resetColor, matchTextStart); + QTC_ASSERT(matchEnd != -1, break); + const int matchLength = matchEnd - matchTextStart; + Match match(matchStart, matchLength); + const QString matchText = text.mid(matchTextStart, matchLength); + if (regExp) + match.regexpCapturedTexts = regExp->match(matchText).capturedTexts(); + matches.append(match); + text = text.left(matchStart) + matchText + text.mid(matchEnd + resetColor.size()); + } + result.setDisplayText(text); + + for (const auto &match : std::as_const(matches)) { + result.setMainRange(lineNumber, match.matchStart, match.matchLength); + result.setUserData(match.regexpCapturedTexts); + result.setUseTextEditorFont(true); + resultList->append(result); + } +} + +static SearchResultItems parse(const QFuture &future, const QString &input, + const std::optional ®Exp, const QString &ref, + const FilePath &directory) +{ + SearchResultItems items; + QStringView remainingInput(input); + while (true) { + if (future.isCanceled()) + return {}; + + if (remainingInput.isEmpty()) + break; + + const QStringView line = nextLine(&remainingInput); if (line.isEmpty()) - return; - static const QLatin1String boldRed("\x1b[1;31m"); - static const QLatin1String resetColor("\x1b[m"); - FileSearchResult single; - const int lineSeparator = line.indexOf(QChar::Null); - QString filePath = line.left(lineSeparator); - if (!m_ref.isEmpty() && filePath.startsWith(m_ref)) - filePath.remove(0, m_ref.length()); - single.fileName = m_directory.pathAppended(filePath); - const int textSeparator = line.indexOf(QChar::Null, lineSeparator + 1); - single.lineNumber = line.mid(lineSeparator + 1, textSeparator - lineSeparator - 1).toInt(); - QString text = line.mid(textSeparator + 1); - QRegularExpression regexp; - QVector matches; - if (m_parameters.flags & FindRegularExpression) { - const QRegularExpression::PatternOptions patternOptions = - (m_parameters.flags & FindCaseSensitively) - ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption; - regexp.setPattern(m_parameters.text); - regexp.setPatternOptions(patternOptions); - } - for (;;) { - const int matchStart = text.indexOf(boldRed); - if (matchStart == -1) - break; - const int matchTextStart = matchStart + boldRed.size(); - const int matchEnd = text.indexOf(resetColor, matchTextStart); - QTC_ASSERT(matchEnd != -1, break); - const int matchLength = matchEnd - matchTextStart; - Match match(matchStart, matchLength); - const QString matchText = text.mid(matchTextStart, matchLength); - if (m_parameters.flags & FindRegularExpression) - match.regexpCapturedTexts = regexp.match(matchText).capturedTexts(); - matches.append(match); - text = text.left(matchStart) + matchText + text.mid(matchEnd + resetColor.size()); - } - single.matchingLine = text; + continue; - for (const auto &match : std::as_const(matches)) { - single.matchStart = match.matchStart; - single.matchLength = match.matchLength; - single.regexpCapturedTexts = match.regexpCapturedTexts; - resultList->append(single); - } + processLine(line, &items, regExp, ref, directory); } + return items; +} - void read(FutureInterfaceType &fi, const QString &text) - { - FileSearchResultList resultList; - QString t = text; - QTextStream stream(&t); - while (!stream.atEnd() && !fi.isCanceled()) - processLine(stream.readLine(), &resultList); - if (!resultList.isEmpty() && !fi.isCanceled()) - fi.reportResult(resultList); - } +static void runGitGrep(QPromise &promise, const FileFindParameters ¶meters) +{ + const FilePath directory = FilePath::fromString(parameters.additionalParameters.toString()); + const GitGrepParameters gitParameters + = parameters.searchEngineParameters.value(); + const QString ref = gitParameters.ref.isEmpty() ? QString() : gitParameters.ref + ':'; + + const auto setupProcess = [&](Process &process) { + const FilePath vcsBinary = GitClient::instance()->vcsBinary(); + const Environment environment = GitClient::instance()->processEnvironment(); - void operator()(FutureInterfaceType &fi) - { QStringList arguments = { "-c", "color.grep.match=bold red", "-c", "color.grep=always", @@ -136,60 +149,41 @@ public: "-c", "color.grep.lineNumber=", "grep", "-zn", "--no-full-name" }; - if (!(m_parameters.flags & FindCaseSensitively)) + if (!(parameters.flags & FindCaseSensitively)) arguments << "-i"; - if (m_parameters.flags & FindWholeWords) + if (parameters.flags & FindWholeWords) arguments << "-w"; - if (m_parameters.flags & FindRegularExpression) + if (parameters.flags & FindRegularExpression) arguments << "-P"; else arguments << "-F"; - arguments << "-e" << m_parameters.text; - GitGrepParameters params = m_parameters.searchEngineParameters.value(); - if (params.recurseSubmodules) + arguments << "-e" << parameters.text; + if (gitParameters.recurseSubmodules) arguments << "--recurse-submodules"; - if (!params.ref.isEmpty()) { - arguments << params.ref; - m_ref = params.ref + ':'; + if (!gitParameters.ref.isEmpty()) { + arguments << gitParameters.ref; } const QStringList filterArgs = - m_parameters.nameFilters.isEmpty() ? QStringList("*") // needed for exclusion filters - : m_parameters.nameFilters; + parameters.nameFilters.isEmpty() ? QStringList("*") // needed for exclusion filters + : parameters.nameFilters; const QStringList exclusionArgs = - Utils::transform(m_parameters.exclusionFilters, [](const QString &filter) { - return QString(":!" + filter); - }); + Utils::transform(parameters.exclusionFilters, [](const QString &filter) { + return QString(":!" + filter); + }); arguments << "--" << filterArgs << exclusionArgs; - QtcProcess process; - process.setEnvironment(m_environment); - process.setCommand({m_vcsBinary, arguments}); - process.setWorkingDirectory(m_directory); - process.setStdOutCallback([this, &fi](const QString &text) { read(fi, text); }); - process.start(); - process.waitForFinished(); + process.setEnvironment(environment); + process.setCommand({vcsBinary, arguments}); + process.setWorkingDirectory(directory); + }; - switch (process.result()) { - case ProcessResult::TerminatedAbnormally: - case ProcessResult::StartFailed: - case ProcessResult::Hang: - fi.reportCanceled(); - break; - case ProcessResult::FinishedWithSuccess: - case ProcessResult::FinishedWithError: - // When no results are found, git-grep exits with non-zero status. - // Do not consider this as an error. - break; - } - } + const auto outputParser = [&ref, &directory](const QFuture &future, const QString &input, + const std::optional ®Exp) { + return parse(future, input, regExp, ref, directory); + }; -private: - FilePath m_vcsBinary; - FilePath m_directory; - QString m_ref; - TextEditor::FileFindParameters m_parameters; - Environment m_environment; -}; + TextEditor::searchInProcessOutput(promise, parameters, setupProcess, outputParser); +} static bool isGitDirectory(const FilePath &path) { @@ -212,18 +206,16 @@ GitGrep::GitGrep(GitClient *client) m_treeLineEdit->setValidator(new QRegularExpressionValidator(refExpression, this)); layout->addWidget(m_treeLineEdit); // asynchronously check git version, add "recurse submodules" option if available - Utils::onResultReady(client->gitVersion(), - this, + Utils::onResultReady(client->gitVersion(), this, [this, pLayout = QPointer(layout)](unsigned version) { - if (version >= 0x021300 && pLayout) { - m_recurseSubmodules = new QCheckBox(Tr::tr("Recurse submodules")); - pLayout->addWidget(m_recurseSubmodules); - } - }); - TextEditor::FindInFiles *findInFiles = TextEditor::FindInFiles::instance(); + if (version >= 0x021300 && pLayout) { + m_recurseSubmodules = new QCheckBox(Tr::tr("Recurse submodules")); + pLayout->addWidget(m_recurseSubmodules); + } + }); + FindInFiles *findInFiles = FindInFiles::instance(); QTC_ASSERT(findInFiles, return); - connect(findInFiles, &TextEditor::FindInFiles::pathChanged, - m_widget, [this](const FilePath &path) { + connect(findInFiles, &FindInFiles::pathChanged, m_widget, [this](const FilePath &path) { setEnabled(isGitDirectory(path)); }); connect(this, &SearchEngine::enabledChanged, m_widget, &QWidget::setEnabled); @@ -272,14 +264,14 @@ void GitGrep::writeSettings(QSettings *settings) const settings->setValue(GitGrepRef, m_treeLineEdit->text()); } -QFuture GitGrep::executeSearch(const TextEditor::FileFindParameters ¶meters, - TextEditor::BaseFileFind * /*baseFileFind*/) +QFuture GitGrep::executeSearch(const FileFindParameters ¶meters, + BaseFileFind *) { - return Utils::runAsync(GitGrepRunner(parameters)); + return Utils::asyncRun(runGitGrep, parameters); } IEditor *GitGrep::openEditor(const SearchResultItem &item, - const TextEditor::FileFindParameters ¶meters) + const FileFindParameters ¶meters) { const GitGrepParameters params = parameters.searchEngineParameters.value(); const QStringList &itemPath = item.path(); diff --git a/src/plugins/git/gitgrep.h b/src/plugins/git/gitgrep.h index 119751102c5..fda6fe56ebd 100644 --- a/src/plugins/git/gitgrep.h +++ b/src/plugins/git/gitgrep.h @@ -5,7 +5,9 @@ #include -QT_FORWARD_DECLARE_CLASS(QCheckBox); +QT_BEGIN_NAMESPACE +class QCheckBox; +QT_END_NAMESPACE namespace Utils { class FancyLineEdit; } @@ -25,10 +27,10 @@ public: QVariant parameters() const override; void readSettings(QSettings *settings) override; void writeSettings(QSettings *settings) const override; - QFuture executeSearch( + QFuture executeSearch( const TextEditor::FileFindParameters ¶meters, TextEditor::BaseFileFind *baseFileFind) override; - Core::IEditor *openEditor(const Core::SearchResultItem &item, + Core::IEditor *openEditor(const Utils::SearchResultItem &item, const TextEditor::FileFindParameters ¶meters) override; private: diff --git a/src/plugins/git/gitplugin.cpp b/src/plugins/git/gitplugin.cpp index 9729dbe4240..c5f30fa036f 100644 --- a/src/plugins/git/gitplugin.cpp +++ b/src/plugins/git/gitplugin.cpp @@ -42,12 +42,12 @@ #include #include +#include #include #include #include #include #include -#include #include #include @@ -392,11 +392,14 @@ public: void setupInstantBlame(); void instantBlameOnce(); + void forceInstantBlame(); void instantBlame(); void stopInstantBlame(); + bool refreshWorkingDirectory(const FilePath &workingDirectory); void onApplySettings(); + GitSettings setting; CommandLocator *m_commandLocator = nullptr; QAction *m_menuAction = nullptr; @@ -418,24 +421,23 @@ public: QVector m_projectActions; QVector m_repositoryActions; ParameterAction *m_applyCurrentFilePatchAction = nullptr; - Gerrit::Internal::GerritPlugin *m_gerritPlugin = nullptr; + Gerrit::Internal::GerritPlugin m_gerritPlugin; - GitSettings m_settings; - GitClient m_gitClient{&m_settings}; + GitClient m_gitClient; QPointer m_stashDialog; BranchViewFactory m_branchViewFactory; QPointer m_remoteDialog; FilePath m_submitRepository; QString m_commitMessageFileName; + FilePath m_workingDirectory; + QTextCodec *m_codec = nullptr; Author m_author; int m_lastVisitedEditorLine = -1; QTimer *m_cursorPositionChangedTimer = nullptr; std::unique_ptr m_blameMark; QMetaObject::Connection m_blameCursorPosConn; - GitSettingsPage settingPage{&m_settings}; - GitGrep gitGrep{&m_gitClient}; VcsEditorFactory svnLogEditorFactory { @@ -524,7 +526,7 @@ void GitPluginPrivate::onApplySettings() updateRepositoryBrowserAction(); bool gitFoundOk; QString errorMessage; - m_settings.gitExecutable(&gitFoundOk, &errorMessage); + settings().gitExecutable(&gitFoundOk, &errorMessage); if (!gitFoundOk) { QTimer::singleShot(0, this, [errorMessage] { AsynchronousMessageBox::warning(Tr::tr("Git Settings"), errorMessage); @@ -555,11 +557,6 @@ IVersionControl *GitPlugin::versionControl() return dd; } -const GitSettings &GitPlugin::settings() -{ - return dd->m_settings; -} - const VcsBasePluginState &GitPlugin::currentState() { return dd->currentState(); @@ -709,6 +706,7 @@ GitPluginPrivate::GitPluginPrivate() m_fileActions.reserve(10); m_projectActions.reserve(10); m_repositoryActions.reserve(50); + m_codec = GitClient::instance()->defaultCommitEncoding(); Context context(Constants::GIT_CONTEXT); @@ -776,13 +774,11 @@ GitPluginPrivate::GitPluginPrivate() createProjectAction(currentProjectMenu, Tr::tr("Diff Current Project", "Avoid translating \"Diff\""), Tr::tr("Diff Project \"%1\"", "Avoid translating \"Diff\""), - "Git.DiffProject", context, true, &GitPluginPrivate::diffCurrentProject, - QKeySequence(useMacShortcuts ? Tr::tr("Meta+G,Meta+Shift+D") : Tr::tr("Alt+G,Alt+Shift+D"))); + "Git.DiffProject", context, true, &GitPluginPrivate::diffCurrentProject); createProjectAction(currentProjectMenu, Tr::tr("Log Project", "Avoid translating \"Log\""), Tr::tr("Log Project \"%1\"", "Avoid translating \"Log\""), - "Git.LogProject", context, true, &GitPluginPrivate::logProject, - QKeySequence(useMacShortcuts ? Tr::tr("Meta+G,Meta+K") : Tr::tr("Alt+G,Alt+K"))); + "Git.LogProject", context, true, &GitPluginPrivate::logProject); createProjectAction(currentProjectMenu, Tr::tr("Clean Project...", "Avoid translating \"Clean\""), Tr::tr("Clean Project \"%1\"...", "Avoid translating \"Clean\""), @@ -795,10 +791,12 @@ GitPluginPrivate::GitPluginPrivate() gitContainer->addMenu(localRepositoryMenu); createRepositoryAction(localRepositoryMenu, "Diff", "Git.DiffRepository", - context, true, &GitClient::diffRepository); + context, true, &GitClient::diffRepository, + QKeySequence(useMacShortcuts ? Tr::tr("Meta+G,Meta+Shift+D") : Tr::tr("Alt+G,Alt+Shift+D"))); createRepositoryAction(localRepositoryMenu, "Log", "Git.LogRepository", - context, true, std::bind(&GitPluginPrivate::logRepository, this)); + context, true, std::bind(&GitPluginPrivate::logRepository, this), + QKeySequence(useMacShortcuts ? Tr::tr("Meta+G,Meta+K") : Tr::tr("Alt+G,Alt+K"))); createRepositoryAction(localRepositoryMenu, "Reflog", "Git.ReflogRepository", context, true, std::bind(&GitPluginPrivate::reflogRepository, this)); @@ -1065,12 +1063,11 @@ GitPluginPrivate::GitPluginPrivate() this, &GitPluginPrivate::updateBranches, Qt::QueuedConnection); /* "Gerrit" */ - m_gerritPlugin = new Gerrit::Internal::GerritPlugin(this); - m_gerritPlugin->initialize(remoteRepositoryMenu); - m_gerritPlugin->updateActions(currentState()); - m_gerritPlugin->addToLocator(m_commandLocator); + m_gerritPlugin.addToMenu(remoteRepositoryMenu); + m_gerritPlugin.updateActions(currentState()); + m_gerritPlugin.addToLocator(m_commandLocator); - connect(&m_settings, &AspectContainer::applied, this, &GitPluginPrivate::onApplySettings); + connect(&settings(), &AspectContainer::applied, this, &GitPluginPrivate::onApplySettings); setupInstantBlame(); } @@ -1440,17 +1437,12 @@ void GitPluginPrivate::setupInstantBlame() return; } - if (!GitClient::instance()->settings().instantBlame.value()) { + if (!settings().instantBlame.value()) { m_lastVisitedEditorLine = -1; stopInstantBlame(); return; } - const Utils::FilePath workingDirectory = GitPlugin::currentState().currentFileTopLevel(); - if (workingDirectory.isEmpty()) - return; - m_author = GitClient::instance()->getAuthor(workingDirectory); - const TextEditorWidget *widget = TextEditorWidget::fromEditor(editor); if (!widget) return; @@ -1458,21 +1450,24 @@ void GitPluginPrivate::setupInstantBlame() if (qobject_cast(widget)) return; // Skip in VCS editors like log or blame + const Utils::FilePath workingDirectory = GitPlugin::currentState().currentFileTopLevel(); + if (!refreshWorkingDirectory(workingDirectory)) + return; + m_blameCursorPosConn = connect(widget, &QPlainTextEdit::cursorPositionChanged, this, [this] { - if (!GitClient::instance()->settings().instantBlame.value()) { + if (!settings().instantBlame.value()) { disconnect(m_blameCursorPosConn); return; } m_cursorPositionChangedTimer->start(500); }); - m_lastVisitedEditorLine = -1; - instantBlame(); + forceInstantBlame(); }; - connect(&GitClient::instance()->settings().instantBlame, - &BoolAspect::valueChanged, this, [this, setupBlameForEditor](bool enabled) { + connect(&settings().instantBlame, &BoolAspect::valueChanged, this, + [this, setupBlameForEditor](bool enabled) { if (enabled) setupBlameForEditor(EditorManager::currentEditor()); else @@ -1521,7 +1516,7 @@ CommitInfo parseBlameOutput(const QStringList &blame, const Utils::FilePath &fil void GitPluginPrivate::instantBlameOnce() { - if (!GitClient::instance()->settings().instantBlame.value()) { + if (!settings().instantBlame.value()) { const TextEditorWidget *widget = TextEditorWidget::currentTextEditorWidget(); if (!widget) return; @@ -1532,11 +1527,15 @@ void GitPluginPrivate::instantBlameOnce() this, [this] { m_blameMark.reset(); }, Qt::SingleShotConnection); const Utils::FilePath workingDirectory = GitPlugin::currentState().topLevel(); - if (workingDirectory.isEmpty()) + if (!refreshWorkingDirectory(workingDirectory)) return; - m_author = GitClient::instance()->getAuthor(workingDirectory); } + forceInstantBlame(); +} + +void GitPluginPrivate::forceInstantBlame() +{ m_lastVisitedEditorLine = -1; instantBlame(); } @@ -1556,7 +1555,7 @@ void GitPluginPrivate::instantBlame() const QTextCursor cursor = widget->textCursor(); const QTextBlock block = cursor.block(); const int line = block.blockNumber() + 1; - const int lines = widget->document()->lineCount(); + const int lines = widget->document()->blockCount(); if (line >= lines) { m_blameMark.reset(); @@ -1582,10 +1581,9 @@ void GitPluginPrivate::instantBlame() const CommitInfo info = parseBlameOutput(output.split('\n'), filePath, m_author); m_blameMark.reset(new BlameMark(filePath, line, info)); }; - QTextCodec *codec = GitClient::instance()->encoding(GitClient::EncodingCommit, workingDirectory); GitClient::instance()->vcsExecWithHandler(workingDirectory, {"blame", "-p", "-L", lineString, "--", filePath.toString()}, - this, commandHandler, RunFlags::NoOutput, codec); + this, commandHandler, RunFlags::NoOutput, m_codec); } void GitPluginPrivate::stopInstantBlame() @@ -1595,6 +1593,51 @@ void GitPluginPrivate::stopInstantBlame() disconnect(m_blameCursorPosConn); } +bool GitPluginPrivate::refreshWorkingDirectory(const FilePath &workingDirectory) +{ + if (workingDirectory.isEmpty()) + return false; + + if (m_workingDirectory == workingDirectory) + return true; + + m_workingDirectory = workingDirectory; + + const auto commitCodecHandler = [this, workingDirectory](const CommandResult &result) { + QTextCodec *codec = nullptr; + + if (result.result() == ProcessResult::FinishedWithSuccess) { + const QString codecName = result.cleanedStdOut().trimmed(); + codec = QTextCodec::codecForName(codecName.toUtf8()); + } else { + codec = GitClient::instance()->defaultCommitEncoding(); + } + + if (m_codec != codec) { + m_codec = codec; + forceInstantBlame(); + } + }; + GitClient::instance()->readConfigAsync(workingDirectory, {"config", "i18n.commitEncoding"}, + commitCodecHandler); + + const auto authorHandler = [this, workingDirectory](const CommandResult &result) { + if (result.result() == ProcessResult::FinishedWithSuccess) { + const QString authorInfo = result.cleanedStdOut().trimmed(); + const Author author = GitClient::instance()->parseAuthor(authorInfo); + + if (m_author != author) { + m_author = author; + forceInstantBlame(); + } + } + }; + GitClient::instance()->readConfigAsync(workingDirectory, {"var", "GIT_AUTHOR_IDENT"}, + authorHandler); + + return true; +} + IEditor *GitPluginPrivate::openSubmitEditor(const QString &fileName, const CommitData &cd) { IEditor *editor = EditorManager::openEditor(FilePath::fromString(fileName), @@ -1683,7 +1726,7 @@ void GitPluginPrivate::pull() const VcsBasePluginState state = currentState(); QTC_ASSERT(state.hasTopLevel(), return); FilePath topLevel = state.topLevel(); - bool rebase = m_settings.pullRebase.value(); + bool rebase = settings().pullRebase.value(); if (!rebase) { QString currentBranch = m_gitClient.synchronousCurrentLocalBranch(topLevel); @@ -1927,7 +1970,7 @@ void GitPluginPrivate::updateActions(VcsBasePluginPrivate::ActionState as) updateContinueAndAbortCommands(); updateRepositoryBrowserAction(); - m_gerritPlugin->updateActions(state); + m_gerritPlugin.updateActions(state); } void GitPluginPrivate::updateContinueAndAbortCommands() @@ -1965,7 +2008,7 @@ void GitPluginPrivate::updateContinueAndAbortCommands() void GitPluginPrivate::delayedPushToGerrit() { - m_gerritPlugin->push(m_submitRepository); + m_gerritPlugin.push(m_submitRepository); } void GitPluginPrivate::updateBranches(const FilePath &repository) @@ -1994,7 +2037,7 @@ QObject *GitPlugin::remoteCommand(const QStringList &options, const QString &wor void GitPluginPrivate::updateRepositoryBrowserAction() { const bool repositoryEnabled = currentState().hasTopLevel(); - const bool hasRepositoryBrowserCmd = !m_settings.repositoryBrowserCmd.value().isEmpty(); + const bool hasRepositoryBrowserCmd = !settings().repositoryBrowserCmd().isEmpty(); m_repositoryBrowserAction->setEnabled(repositoryEnabled && hasRepositoryBrowserCmd); } @@ -2100,7 +2143,7 @@ GitPluginPrivate::RepoUrl GitPluginPrivate::getRepoUrl(const QString &location) FilePaths GitPluginPrivate::additionalToolsPath() const { - FilePaths res = m_gitClient.settings().searchPathList(); + FilePaths res = settings().searchPathList(); const FilePath binaryPath = m_gitClient.gitBinDirectory(); if (!binaryPath.isEmpty() && !res.contains(binaryPath)) res << binaryPath; @@ -2172,7 +2215,7 @@ void GitPlugin::updateBranches(const FilePath &repository) void GitPlugin::gerritPush(const FilePath &topLevel) { - dd->m_gerritPlugin->push(topLevel); + dd->m_gerritPlugin.push(topLevel); } bool GitPlugin::isCommitEditorOpen() diff --git a/src/plugins/git/gitplugin.h b/src/plugins/git/gitplugin.h index 0b0e7518012..59d9a87fdec 100644 --- a/src/plugins/git/gitplugin.h +++ b/src/plugins/git/gitplugin.h @@ -3,7 +3,6 @@ #pragma once -#include "gitsettings.h" #include "git_global.h" #include @@ -36,7 +35,6 @@ public: static GitClient *client(); static Core::IVersionControl *versionControl(); - static const GitSettings &settings(); static const VcsBase::VcsBasePluginState ¤tState(); static QString msgRepositoryLabel(const Utils::FilePath &repository); @@ -63,7 +61,6 @@ private slots: void testGitRemote_data(); void testGitRemote(); #endif - }; } // Git::Internal diff --git a/src/plugins/git/gitsettings.cpp b/src/plugins/git/gitsettings.cpp index 25a8f77988e..4ebf7debeed 100644 --- a/src/plugins/git/gitsettings.cpp +++ b/src/plugins/git/gitsettings.cpp @@ -17,43 +17,46 @@ using namespace VcsBase; namespace Git::Internal { +static GitSettings *theSettings; + +GitSettings &settings() +{ + return *theSettings; +} + GitSettings::GitSettings() { + theSettings = this; + + setId(VcsBase::Constants::VCS_ID_GIT); + setDisplayName(Tr::tr("Git")); + setCategory(VcsBase::Constants::VCS_SETTINGS_CATEGORY); setSettingsGroup("Git"); path.setDisplayStyle(StringAspect::LineEditDisplay); path.setLabelText(Tr::tr("Prepend to PATH:")); - registerAspect(&binaryPath); binaryPath.setDefaultValue("git"); - registerAspect(&pullRebase); pullRebase.setSettingsKey("PullRebase"); pullRebase.setLabelText(Tr::tr("Pull with rebase")); - registerAspect(&showTags); showTags.setSettingsKey("ShowTags"); - registerAspect(&omitAnnotationDate); omitAnnotationDate.setSettingsKey("OmitAnnotationDate"); - registerAspect(&ignoreSpaceChangesInDiff); ignoreSpaceChangesInDiff.setSettingsKey("SpaceIgnorantDiff"); ignoreSpaceChangesInDiff.setDefaultValue(true); - registerAspect(&ignoreSpaceChangesInBlame); ignoreSpaceChangesInBlame.setSettingsKey("SpaceIgnorantBlame"); ignoreSpaceChangesInBlame.setDefaultValue(true); - registerAspect(&blameMoveDetection); blameMoveDetection.setSettingsKey("BlameDetectMove"); blameMoveDetection.setDefaultValue(0); - registerAspect(&diffPatience); diffPatience.setSettingsKey("DiffPatience"); diffPatience.setDefaultValue(true); - registerAspect(&winSetHomeEnvironment); winSetHomeEnvironment.setSettingsKey("WinSetHomeEnvironment"); winSetHomeEnvironment.setDefaultValue(true); winSetHomeEnvironment.setLabelText(Tr::tr("Set \"HOME\" environment variable")); @@ -71,52 +74,78 @@ GitSettings::GitSettings() winSetHomeEnvironment.setVisible(false); } - registerAspect(&gitkOptions); gitkOptions.setDisplayStyle(StringAspect::LineEditDisplay); gitkOptions.setSettingsKey("GitKOptions"); gitkOptions.setLabelText(Tr::tr("Arguments:")); - registerAspect(&logDiff); logDiff.setSettingsKey("LogDiff"); logDiff.setToolTip(Tr::tr("Note that huge amount of commits might take some time.")); - registerAspect(&repositoryBrowserCmd); - repositoryBrowserCmd.setDisplayStyle(StringAspect::PathChooserDisplay); repositoryBrowserCmd.setSettingsKey("RepositoryBrowserCmd"); repositoryBrowserCmd.setExpectedKind(PathChooser::ExistingCommand); repositoryBrowserCmd.setHistoryCompleter("Git.RepoCommand.History"); repositoryBrowserCmd.setDisplayName(Tr::tr("Git Repository Browser Command")); repositoryBrowserCmd.setLabelText(Tr::tr("Command:")); - registerAspect(&instantBlame); instantBlame.setSettingsKey("Git Instant"); instantBlame.setDefaultValue(true); instantBlame.setLabelText(Tr::tr("Add instant blame annotations to editor")); - instantBlame.setToolTip(Tr::tr("Directly annotate each line in the editor " - "when scrolling through the document.")); + instantBlame.setToolTip( + Tr::tr("Annotate the current line in the editor with Git \"blame\" output.")); - registerAspect(&graphLog); graphLog.setSettingsKey("GraphLog"); - registerAspect(&colorLog); colorLog.setSettingsKey("ColorLog"); colorLog.setDefaultValue(true); - registerAspect(&firstParent); firstParent.setSettingsKey("FirstParent"); - registerAspect(&followRenames); followRenames.setSettingsKey("FollowRenames"); followRenames.setDefaultValue(true); - registerAspect(&lastResetIndex); lastResetIndex.setSettingsKey("LastResetIndex"); - registerAspect(&refLogShowDate); refLogShowDate.setSettingsKey("RefLogShowDate"); timeout.setDefaultValue(Utils::HostOsInfo::isWindowsHost() ? 60 : 30); + setLayouter([this] { + using namespace Layouting; + return Column { + Group { + title(Tr::tr("Configuration")), + Column { + Row { path }, + winSetHomeEnvironment, + } + }, + + Group { + title(Tr::tr("Miscellaneous")), + Column { + Row { logCount, timeout, st }, + pullRebase + } + }, + + Group { + title(Tr::tr("Gitk")), + Row { gitkOptions } + }, + + Group { + title(Tr::tr("Repository Browser")), + Row { repositoryBrowserCmd } + }, + + Group { + title(Tr::tr("Instant Blame")), + Row { instantBlame } + }, + + st + }; + }); connect(&binaryPath, &StringAspect::valueChanged, this, [this] { tryResolve = true; }); connect(&path, &StringAspect::valueChanged, this, [this] { tryResolve = true; }); } @@ -130,7 +159,7 @@ FilePath GitSettings::gitExecutable(bool *ok, QString *errorMessage) const errorMessage->clear(); if (tryResolve) { - resolvedBinPath = binaryPath.filePath(); + resolvedBinPath = binaryPath(); if (!resolvedBinPath.isAbsolutePath()) resolvedBinPath = resolvedBinPath.searchInPath({path.filePath()}, FilePath::PrependToPath); tryResolve = false; @@ -146,54 +175,4 @@ FilePath GitSettings::gitExecutable(bool *ok, QString *errorMessage) const return resolvedBinPath; } -// GitSettingsPage - -GitSettingsPage::GitSettingsPage(GitSettings *settings) -{ - setId(VcsBase::Constants::VCS_ID_GIT); - setDisplayName(Tr::tr("Git")); - setCategory(VcsBase::Constants::VCS_SETTINGS_CATEGORY); - setSettings(settings); - - setLayouter([settings](QWidget *widget) { - GitSettings &s = *settings; - using namespace Layouting; - - Column { - Group { - title(Tr::tr("Configuration")), - Column { - Row { s.path }, - s.winSetHomeEnvironment, - } - }, - - Group { - title(Tr::tr("Miscellaneous")), - Column { - Row { s.logCount, s.timeout, st }, - s.pullRebase - } - }, - - Group { - title(Tr::tr("Gitk")), - Row { s.gitkOptions } - }, - - Group { - title(Tr::tr("Repository Browser")), - Row { s.repositoryBrowserCmd } - }, - - Group { - title(Tr::tr("Instant Blame")), - Row { s.instantBlame } - }, - - st - }.attachTo(widget); - }); -} - } // Git::Internal diff --git a/src/plugins/git/gitsettings.h b/src/plugins/git/gitsettings.h index c03deadeb9b..df6cf56e4e8 100644 --- a/src/plugins/git/gitsettings.h +++ b/src/plugins/git/gitsettings.h @@ -3,7 +3,6 @@ #pragma once -#include #include namespace Git::Internal { @@ -21,24 +20,24 @@ class GitSettings : public VcsBase::VcsBaseSettings public: GitSettings(); - Utils::BoolAspect pullRebase; - Utils::BoolAspect showTags; - Utils::BoolAspect omitAnnotationDate; - Utils::BoolAspect ignoreSpaceChangesInDiff; - Utils::BoolAspect ignoreSpaceChangesInBlame; - Utils::IntegerAspect blameMoveDetection; - Utils::BoolAspect diffPatience; - Utils::BoolAspect winSetHomeEnvironment; - Utils::StringAspect gitkOptions; - Utils::BoolAspect logDiff; - Utils::StringAspect repositoryBrowserCmd; - Utils::BoolAspect graphLog; - Utils::BoolAspect colorLog; - Utils::BoolAspect firstParent; - Utils::BoolAspect followRenames; - Utils::IntegerAspect lastResetIndex; - Utils::BoolAspect refLogShowDate; - Utils::BoolAspect instantBlame; + Utils::BoolAspect pullRebase{this}; + Utils::BoolAspect showTags{this}; + Utils::BoolAspect omitAnnotationDate{this}; + Utils::BoolAspect ignoreSpaceChangesInDiff{this}; + Utils::BoolAspect ignoreSpaceChangesInBlame{this}; + Utils::IntegerAspect blameMoveDetection{this}; + Utils::BoolAspect diffPatience{this}; + Utils::BoolAspect winSetHomeEnvironment{this}; + Utils::StringAspect gitkOptions{this}; + Utils::BoolAspect logDiff{this}; + Utils::FilePathAspect repositoryBrowserCmd{this}; + Utils::BoolAspect graphLog{this}; + Utils::BoolAspect colorLog{this}; + Utils::BoolAspect firstParent{this}; + Utils::BoolAspect followRenames{this}; + Utils::IntegerAspect lastResetIndex{this}; + Utils::BoolAspect refLogShowDate{this}; + Utils::BoolAspect instantBlame{this}; mutable Utils::FilePath resolvedBinPath; mutable bool tryResolve = true; @@ -46,10 +45,6 @@ public: Utils::FilePath gitExecutable(bool *ok = nullptr, QString *errorMessage = nullptr) const; }; -class GitSettingsPage final : public Core::IOptionsPage -{ -public: - explicit GitSettingsPage(GitSettings *settings); -}; +GitSettings &settings(); } // Git::Internal diff --git a/src/plugins/git/gitsubmiteditor.cpp b/src/plugins/git/gitsubmiteditor.cpp index 23755d98751..9ff83c8c124 100644 --- a/src/plugins/git/gitsubmiteditor.cpp +++ b/src/plugins/git/gitsubmiteditor.cpp @@ -11,8 +11,8 @@ #include #include #include +#include #include -#include #include #include @@ -204,7 +204,7 @@ void GitSubmitEditor::updateFileModel() return; w->setUpdateInProgress(true); // TODO: Check if fetch works OK from separate thread, refactor otherwise - m_fetchWatcher.setFuture(Utils::runAsync(&CommitDataFetchResult::fetch, + m_fetchWatcher.setFuture(Utils::asyncRun(&CommitDataFetchResult::fetch, m_commitType, m_workingDirectory)); Core::ProgressManager::addTask(m_fetchWatcher.future(), Tr::tr("Refreshing Commit Data"), TASK_UPDATE_COMMIT); diff --git a/src/plugins/git/gitsubmiteditorwidget.cpp b/src/plugins/git/gitsubmiteditorwidget.cpp index 65bc5beab41..ec09437e266 100644 --- a/src/plugins/git/gitsubmiteditorwidget.cpp +++ b/src/plugins/git/gitsubmiteditorwidget.cpp @@ -36,8 +36,6 @@ class GitSubmitPanel : public QWidget public: GitSubmitPanel() { - resize(364, 269); - repositoryLabel = new QLabel(Tr::tr("repository")); branchLabel = new QLabel(Tr::tr("branch")); // FIXME: Isn't this overwritten soon? showHeadLabel = new QLabel("" + Tr::tr("Show HEAD") + ""); @@ -81,7 +79,8 @@ public: } }, editGroup, - }.attachTo(this, WithoutMargins); + noMargin, + }.attachTo(this); } QLabel *repositoryLabel; diff --git a/src/plugins/git/logchangedialog.cpp b/src/plugins/git/logchangedialog.cpp index 55055cbf768..75660c11897 100644 --- a/src/plugins/git/logchangedialog.cpp +++ b/src/plugins/git/logchangedialog.cpp @@ -224,7 +224,7 @@ LogChangeDialog::LogChangeDialog(bool isReset, QWidget *parent) : m_resetTypeComboBox->addItem(Tr::tr("Hard"), "--hard"); m_resetTypeComboBox->addItem(Tr::tr("Mixed"), "--mixed"); m_resetTypeComboBox->addItem(Tr::tr("Soft"), "--soft"); - m_resetTypeComboBox->setCurrentIndex(GitClient::settings().lastResetIndex.value()); + m_resetTypeComboBox->setCurrentIndex(settings().lastResetIndex()); popUpLayout->addWidget(m_resetTypeComboBox); popUpLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored)); } @@ -250,7 +250,7 @@ bool LogChangeDialog::runDialog(const FilePath &repository, if (QDialog::exec() == QDialog::Accepted) { if (m_resetTypeComboBox) - GitClient::settings().lastResetIndex.setValue(m_resetTypeComboBox->currentIndex()); + settings().lastResetIndex.setValue(m_resetTypeComboBox->currentIndex()); return true; } return false; diff --git a/src/plugins/git/mergetool.cpp b/src/plugins/git/mergetool.cpp index f696bef124c..6fbc9d5ea83 100644 --- a/src/plugins/git/mergetool.cpp +++ b/src/plugins/git/mergetool.cpp @@ -23,8 +23,8 @@ namespace Git::Internal { MergeTool::MergeTool(QObject *parent) : QObject(parent) { - connect(&m_process, &QtcProcess::done, this, &MergeTool::done); - connect(&m_process, &QtcProcess::readyReadStandardOutput, this, &MergeTool::readData); + connect(&m_process, &Process::done, this, &MergeTool::done); + connect(&m_process, &Process::readyReadStandardOutput, this, &MergeTool::readData); Environment env = Environment::systemEnvironment(); env.set("LANG", "C"); env.set("LANGUAGE", "C"); diff --git a/src/plugins/git/mergetool.h b/src/plugins/git/mergetool.h index 290b50f10c3..727f9a85df0 100644 --- a/src/plugins/git/mergetool.h +++ b/src/plugins/git/mergetool.h @@ -3,7 +3,7 @@ #pragma once -#include +#include #include #include @@ -49,7 +49,7 @@ private: void chooseAction(); void addButton(QMessageBox *msgBox, const QString &text, char key); - Utils::QtcProcess m_process; + Utils::Process m_process; MergeType m_mergeType = NormalMerge; QString m_fileName; FileState m_localState = UnknownState; diff --git a/src/plugins/gitlab/gitlabclonedialog.cpp b/src/plugins/gitlab/gitlabclonedialog.cpp index 4d54105e447..3a061b9cc57 100644 --- a/src/plugins/gitlab/gitlabclonedialog.cpp +++ b/src/plugins/gitlab/gitlabclonedialog.cpp @@ -23,8 +23,8 @@ #include #include #include +#include #include -#include #include #include diff --git a/src/plugins/gitlab/gitlabdialog.cpp b/src/plugins/gitlab/gitlabdialog.cpp index 4a10888c944..9375c057f57 100644 --- a/src/plugins/gitlab/gitlabdialog.cpp +++ b/src/plugins/gitlab/gitlabdialog.cpp @@ -9,7 +9,7 @@ #include "gitlabprojectsettings.h" #include "gitlabtr.h" -#include +#include #include #include @@ -188,7 +188,7 @@ void GitLabDialog::requestMainViewUpdate() bool linked = false; m_currentServerId = Id(); - if (auto project = ProjectExplorer::SessionManager::startupProject()) { + if (auto project = ProjectExplorer::ProjectManager::startupProject()) { GitLabProjectSettings *projSettings = GitLabPlugin::projectSettings(project); if (projSettings->isLinked()) { m_currentServerId = projSettings->currentServer(); diff --git a/src/plugins/gitlab/gitlaboptionspage.cpp b/src/plugins/gitlab/gitlaboptionspage.cpp index 4f519e77886..4660cc92bb5 100644 --- a/src/plugins/gitlab/gitlaboptionspage.cpp +++ b/src/plugins/gitlab/gitlaboptionspage.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -44,6 +45,25 @@ static bool hostValid(const QString &host) return (host == "localhost") || dn.match(host).hasMatch(); } +class GitLabServerWidget : public QWidget +{ +public: + enum Mode { Display, Edit }; + explicit GitLabServerWidget(Mode m, QWidget *parent = nullptr); + + GitLabServer gitLabServer() const; + void setGitLabServer(const GitLabServer &server); + +private: + Mode m_mode = Display; + Id m_id; + StringAspect m_host; + StringAspect m_description; + StringAspect m_token; + IntegerAspect m_port; + BoolAspect m_secure; +}; + GitLabServerWidget::GitLabServerWidget(Mode m, QWidget *parent) : QWidget(parent) , m_mode(m) @@ -77,13 +97,14 @@ GitLabServerWidget::GitLabServerWidget(Mode m, QWidget *parent) Row { Form { - m_host, - m_description, - m_token, - m_port, - m_secure + m_host, br, + m_description, br, + m_token, br, + m_port, br, + m_secure, + m == Edit ? normalMargin : noMargin }, - }.attachTo(this, m == Edit ? WithMargins : WithoutMargins); + }.attachTo(this); } GitLabServer GitLabServerWidget::gitLabServer() const @@ -108,14 +129,35 @@ void GitLabServerWidget::setGitLabServer(const GitLabServer &server) m_secure.setValue(server.secure); } -GitLabOptionsWidget::GitLabOptionsWidget(QWidget *parent) - : QWidget(parent) +class GitLabOptionsWidget : public Core::IOptionsPageWidget +{ +public: + explicit GitLabOptionsWidget(GitLabParameters *parameters); + +private: + void showEditServerDialog(); + void showAddServerDialog(); + void removeCurrentTriggered(); + void addServer(const GitLabServer &newServer); + void modifyCurrentServer(const GitLabServer &newServer); + void updateButtonsState(); + + GitLabParameters *m_parameters = nullptr; + GitLabServerWidget *m_gitLabServerWidget = nullptr; + QPushButton *m_edit = nullptr; + QPushButton *m_remove = nullptr; + QPushButton *m_add = nullptr; + QComboBox *m_defaultGitLabServer = nullptr; + FilePathAspect m_curl; +}; + +GitLabOptionsWidget::GitLabOptionsWidget(GitLabParameters *params) + : m_parameters(params) { auto defaultLabel = new QLabel(Tr::tr("Default:"), this); m_defaultGitLabServer = new QComboBox(this); - m_curl.setDisplayStyle(Utils::StringAspect::DisplayStyle::PathChooserDisplay); m_curl.setLabelText(Tr::tr("curl:")); - m_curl.setExpectedKind(Utils::PathChooser::ExistingCommand); + m_curl.setExpectedKind(PathChooser::ExistingCommand); m_gitLabServerWidget = new GitLabServerWidget(GitLabServerWidget::Display, this); @@ -126,7 +168,7 @@ GitLabOptionsWidget::GitLabOptionsWidget(QWidget *parent) m_add = new QPushButton(Tr::tr("Add..."), this); m_add->setToolTip(Tr::tr("Add new GitLab server configuration.")); - using namespace Utils::Layouting; + using namespace Layouting; Grid { Form { @@ -136,6 +178,20 @@ GitLabOptionsWidget::GitLabOptionsWidget(QWidget *parent) }, Column { m_add, m_edit, m_remove, st }, }.attachTo(this); + m_curl.setFilePath(params->curl); + + for (const auto &gitLabServer : params->gitLabServers) { + m_defaultGitLabServer->addItem(gitLabServer.displayString(), + QVariant::fromValue(gitLabServer)); + } + + const GitLabServer found = params->currentDefaultServer(); + if (found.id.isValid()) { + m_defaultGitLabServer->setCurrentIndex(m_defaultGitLabServer->findData( + QVariant::fromValue(found))); + } + updateButtonsState(); + connect(m_edit, &QPushButton::clicked, this, &GitLabOptionsWidget::showEditServerDialog); connect(m_remove, &QPushButton::clicked, this, &GitLabOptionsWidget::removeCurrentTriggered); connect(m_add, &QPushButton::clicked, this, &GitLabOptionsWidget::showAddServerDialog); @@ -143,35 +199,22 @@ GitLabOptionsWidget::GitLabOptionsWidget(QWidget *parent) m_gitLabServerWidget->setGitLabServer( m_defaultGitLabServer->currentData().value()); }); -} -GitLabParameters GitLabOptionsWidget::parameters() const -{ - GitLabParameters result; - // get all configured gitlabservers - for (int i = 0, end = m_defaultGitLabServer->count(); i < end; ++i) - result.gitLabServers.append(m_defaultGitLabServer->itemData(i).value()); - if (m_defaultGitLabServer->count()) - result.defaultGitLabServer = m_defaultGitLabServer->currentData().value().id; - result.curl = m_curl.filePath(); - return result; -} + setOnApply([this] { + GitLabParameters result; + // get all configured gitlabservers + for (int i = 0, end = m_defaultGitLabServer->count(); i < end; ++i) + result.gitLabServers.append(m_defaultGitLabServer->itemData(i).value()); + if (m_defaultGitLabServer->count()) + result.defaultGitLabServer = m_defaultGitLabServer->currentData().value().id; + result.curl = m_curl(); -void GitLabOptionsWidget::setParameters(const GitLabParameters ¶ms) -{ - m_curl.setFilePath(params.curl); - - for (const auto &gitLabServer : params.gitLabServers) { - m_defaultGitLabServer->addItem(gitLabServer.displayString(), - QVariant::fromValue(gitLabServer)); - } - - const GitLabServer found = params.currentDefaultServer(); - if (found.id.isValid()) { - m_defaultGitLabServer->setCurrentIndex(m_defaultGitLabServer->findData( - QVariant::fromValue(found))); - } - updateButtonsState(); + if (result != *m_parameters) { + m_parameters->assign(result); + m_parameters->toSettings(Core::ICore::settings()); + emit m_parameters->changed(); + } + }); } void GitLabOptionsWidget::showEditServerDialog() @@ -253,39 +296,14 @@ void GitLabOptionsWidget::updateButtonsState() m_remove->setEnabled(hasItems); } -GitLabOptionsPage::GitLabOptionsPage(GitLabParameters *p, QObject *parent) - : Core::IOptionsPage{parent} - , m_parameters(p) +// GitLabOptionsPage + +GitLabOptionsPage::GitLabOptionsPage(GitLabParameters *p) { setId(Constants::GITLAB_SETTINGS); setDisplayName(Tr::tr("GitLab")); setCategory(VcsBase::Constants::VCS_SETTINGS_CATEGORY); -} - -QWidget *GitLabOptionsPage::widget() -{ - if (!m_widget) { - m_widget = new GitLabOptionsWidget; - m_widget->setParameters(*m_parameters); - } - return m_widget; -} - -void GitLabOptionsPage::apply() -{ - if (GitLabOptionsWidget *w = m_widget.data()) { - GitLabParameters newParameters = w->parameters(); - if (newParameters != *m_parameters) { - *m_parameters = newParameters; - m_parameters->toSettings(Core::ICore::settings()); - emit settingsChanged(); - } - } -} - -void GitLabOptionsPage::finish() -{ - delete m_widget; + setWidgetCreator([p] { return new GitLabOptionsWidget(p); }); } } // namespace GitLab diff --git a/src/plugins/gitlab/gitlaboptionspage.h b/src/plugins/gitlab/gitlaboptionspage.h index 327cc1128a7..664f4a26fc1 100644 --- a/src/plugins/gitlab/gitlaboptionspage.h +++ b/src/plugins/gitlab/gitlaboptionspage.h @@ -6,84 +6,15 @@ #include "gitlabparameters.h" #include -#include - -#include - -QT_BEGIN_NAMESPACE -class QComboBox; -class QPushButton; -QT_END_NAMESPACE namespace GitLab { -namespace Constants { -const char GITLAB_SETTINGS[] = "GitLab"; -} // namespace Constants - -class GitLabServerWidget : public QWidget -{ -public: - enum Mode { Display, Edit }; - explicit GitLabServerWidget(Mode m, QWidget *parent = nullptr); - - GitLabServer gitLabServer() const; - void setGitLabServer(const GitLabServer &server); - - bool isValid() const; -private: - Mode m_mode = Display; - Utils::Id m_id; - Utils::StringAspect m_host; - Utils::StringAspect m_description; - Utils::StringAspect m_token; - Utils::IntegerAspect m_port; - Utils::BoolAspect m_secure; -}; - -class GitLabOptionsWidget : public QWidget -{ - Q_OBJECT -public: - explicit GitLabOptionsWidget(QWidget *parent = nullptr); - - GitLabParameters parameters() const; - void setParameters(const GitLabParameters ¶ms); - -private: - void showEditServerDialog(); - void showAddServerDialog(); - void removeCurrentTriggered(); - void addServer(const GitLabServer &newServer); - void modifyCurrentServer(const GitLabServer &newServer); - void updateButtonsState(); - - GitLabServerWidget *m_gitLabServerWidget = nullptr; - QPushButton *m_edit = nullptr; - QPushButton *m_remove = nullptr; - QPushButton *m_add = nullptr; - QComboBox *m_defaultGitLabServer = nullptr; - Utils::StringAspect m_curl; -}; +namespace Constants { const char GITLAB_SETTINGS[] = "GitLab"; } class GitLabOptionsPage : public Core::IOptionsPage { - Q_OBJECT public: - explicit GitLabOptionsPage(GitLabParameters *p, QObject *parent = nullptr); - - QWidget *widget() final; - void apply() final; - void finish() final; - -signals: - void settingsChanged(); - -private: - void addServer(); - - GitLabParameters *m_parameters; - QPointer m_widget; + explicit GitLabOptionsPage(GitLabParameters *p); }; -} // namespace GitLab +} // GitLab diff --git a/src/plugins/gitlab/gitlabparameters.cpp b/src/plugins/gitlab/gitlabparameters.cpp index 1a3f970edb0..79a6ce7da67 100644 --- a/src/plugins/gitlab/gitlabparameters.cpp +++ b/src/plugins/gitlab/gitlabparameters.cpp @@ -102,6 +102,13 @@ GitLabParameters::GitLabParameters() { } +void GitLabParameters::assign(const GitLabParameters &other) +{ + curl = other.curl; + defaultGitLabServer = other.defaultGitLabServer; + gitLabServers = other.gitLabServers; +} + bool GitLabParameters::equals(const GitLabParameters &other) const { return curl == other.curl && defaultGitLabServer == other.defaultGitLabServer diff --git a/src/plugins/gitlab/gitlabparameters.h b/src/plugins/gitlab/gitlabparameters.h index 57dda3667d9..7a21976dc24 100644 --- a/src/plugins/gitlab/gitlabparameters.h +++ b/src/plugins/gitlab/gitlabparameters.h @@ -38,11 +38,14 @@ public: bool validateCert = true; }; -class GitLabParameters +class GitLabParameters : public QObject { + Q_OBJECT + public: GitLabParameters(); + void assign(const GitLabParameters &other); bool equals(const GitLabParameters &other) const; bool isValid() const; @@ -52,6 +55,10 @@ public: GitLabServer currentDefaultServer() const; GitLabServer serverForId(const Utils::Id &id) const; +signals: + void changed(); + +public: friend bool operator==(const GitLabParameters &p1, const GitLabParameters &p2) { return p1.equals(p2); diff --git a/src/plugins/gitlab/gitlabplugin.cpp b/src/plugins/gitlab/gitlabplugin.cpp index 2059ae4d84b..5ae4df682b6 100644 --- a/src/plugins/gitlab/gitlabplugin.cpp +++ b/src/plugins/gitlab/gitlabplugin.cpp @@ -14,11 +14,15 @@ #include #include #include + #include + #include #include -#include +#include + #include + #include #include @@ -34,6 +38,18 @@ const char GITLAB_OPEN_VIEW[] = "GitLab.OpenView"; class GitLabPluginPrivate : public QObject { public: + void setupNotificationTimer(); + void fetchEvents(); + void fetchUser(); + void createAndSendEventsRequest(const QDateTime timeStamp, int page = -1); + void handleUser(const User &user); + void handleEvents(const Events &events, const QDateTime &timeStamp); + + void onSettingsChanged() { + if (dialog) + dialog->updateRemotes(); + } + GitLabParameters parameters; GitLabOptionsPage optionsPage{¶meters}; QHash projectSettings; @@ -43,13 +59,6 @@ public: QString projectName; Utils::Id serverId; bool runningQuery = false; - - void setupNotificationTimer(); - void fetchEvents(); - void fetchUser(); - void createAndSendEventsRequest(const QDateTime timeStamp, int page = -1); - void handleUser(const User &user); - void handleEvents(const Events &events, const QDateTime &timeStamp); }; static GitLabPluginPrivate *dd = nullptr; @@ -85,12 +94,8 @@ void GitLabPlugin::initialize() connect(openViewAction, &QAction::triggered, this, &GitLabPlugin::openView); Core::ActionContainer *ac = Core::ActionManager::actionContainer(Core::Constants::M_TOOLS); ac->addAction(gitlabCommand); - connect(&dd->optionsPage, &GitLabOptionsPage::settingsChanged, this, [] { - if (dd->dialog) - dd->dialog->updateRemotes(); - }); - connect(ProjectExplorer::SessionManager::instance(), - &ProjectExplorer::SessionManager::startupProjectChanged, + connect(ProjectExplorer::ProjectManager::instance(), + &ProjectExplorer::ProjectManager::startupProjectChanged, this, &GitLabPlugin::onStartupProjectChanged); } @@ -121,7 +126,7 @@ void GitLabPlugin::onStartupProjectChanged() { QTC_ASSERT(dd, return); disconnect(&dd->notificationTimer); - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); if (!project) { dd->notificationTimer.stop(); return; @@ -147,7 +152,7 @@ void GitLabPluginPrivate::setupNotificationTimer() void GitLabPluginPrivate::fetchEvents() { - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); QTC_ASSERT(project, return); if (runningQuery) @@ -218,7 +223,7 @@ void GitLabPluginPrivate::handleEvents(const Events &events, const QDateTime &ti { runningQuery = false; - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); QTC_ASSERT(project, return); GitLabProjectSettings *projSettings = GitLabPlugin::projectSettings(project); @@ -301,7 +306,7 @@ bool GitLabPlugin::handleCertificateIssue(const Utils::Id &serverId) int index = dd->parameters.gitLabServers.indexOf(server); server.validateCert = false; dd->parameters.gitLabServers.replace(index, server); - emit dd->optionsPage.settingsChanged(); + dd->onSettingsChanged(); return true; } return false; @@ -311,7 +316,7 @@ void GitLabPlugin::linkedStateChanged(bool enabled) { QTC_ASSERT(dd, return); - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); if (project) { const GitLabProjectSettings *pSettings = projectSettings(project); dd->serverId = pSettings->currentServer(); diff --git a/src/plugins/gitlab/gitlabprojectsettings.cpp b/src/plugins/gitlab/gitlabprojectsettings.cpp index 73532e492f8..240f5292e4f 100644 --- a/src/plugins/gitlab/gitlabprojectsettings.cpp +++ b/src/plugins/gitlab/gitlabprojectsettings.cpp @@ -157,7 +157,7 @@ GitLabProjectSettingsWidget::GitLabProjectSettingsWidget(ProjectExplorer::Projec connect(m_hostCB, &QComboBox::currentIndexChanged, this, [this] { m_infoLabel->setVisible(false); }); - connect(GitLabPlugin::optionsPage(), &GitLabOptionsPage::settingsChanged, + connect(GitLabPlugin::globalParameters(), &GitLabParameters::changed, this, &GitLabProjectSettingsWidget::updateUi); updateUi(); } diff --git a/src/plugins/gitlab/queryrunner.cpp b/src/plugins/gitlab/queryrunner.cpp index 2ddc86cd091..fd56c68c29d 100644 --- a/src/plugins/gitlab/queryrunner.cpp +++ b/src/plugins/gitlab/queryrunner.cpp @@ -96,7 +96,7 @@ QueryRunner::QueryRunner(const Query &query, const Id &id, QObject *parent) url += query.toString(); args << url; m_process.setCommand({p->curl, args}); - connect(&m_process, &QtcProcess::done, this, [this, id] { + connect(&m_process, &Process::done, this, [this, id] { if (m_process.result() != ProcessResult::FinishedWithSuccess) { const int exitCode = m_process.exitCode(); if (m_process.exitStatus() == QProcess::NormalExit diff --git a/src/plugins/gitlab/queryrunner.h b/src/plugins/gitlab/queryrunner.h index ee698e384f0..08afc56e1b6 100644 --- a/src/plugins/gitlab/queryrunner.h +++ b/src/plugins/gitlab/queryrunner.h @@ -4,7 +4,7 @@ #pragma once #include -#include +#include #include @@ -47,7 +47,7 @@ signals: void resultRetrieved(const QByteArray &json); private: - Utils::QtcProcess m_process; + Utils::Process m_process; }; } // namespace GitLab diff --git a/src/plugins/haskell/CMakeLists.txt b/src/plugins/haskell/CMakeLists.txt index 0a62921168a..d45326695cb 100644 --- a/src/plugins/haskell/CMakeLists.txt +++ b/src/plugins/haskell/CMakeLists.txt @@ -1,7 +1,6 @@ add_qtc_plugin(Haskell PLUGIN_DEPENDS QtCreator::Core QtCreator::TextEditor QtCreator::ProjectExplorer - DEPENDS Qt5::Widgets SOURCES haskell.qrc haskell_global.h @@ -13,9 +12,9 @@ add_qtc_plugin(Haskell haskellplugin.cpp haskellplugin.h haskellproject.cpp haskellproject.h haskellrunconfiguration.cpp haskellrunconfiguration.h + haskellsettings.cpp haskellsettings.h haskelltokenizer.cpp haskelltokenizer.h haskelltr.h - optionspage.cpp optionspage.h stackbuildstep.cpp stackbuildstep.h ) diff --git a/src/plugins/haskell/haskell.qbs b/src/plugins/haskell/haskell.qbs index 30eb8328f1a..5c1be115387 100644 --- a/src/plugins/haskell/haskell.qbs +++ b/src/plugins/haskell/haskell.qbs @@ -21,8 +21,8 @@ QtcPlugin { "haskellplugin.cpp", "haskellplugin.h", "haskellproject.cpp", "haskellproject.h", "haskellrunconfiguration.cpp", "haskellrunconfiguration.h", + "haskellsettings.cpp", "haskellsettings.h", "haskelltokenizer.cpp", "haskelltokenizer.h", - "optionspage.cpp", "optionspage.h", "stackbuildstep.cpp", "stackbuildstep.h" ] } diff --git a/src/plugins/haskell/haskell.qrc b/src/plugins/haskell/haskell.qrc index 654f45818c3..3ddf22f76d0 100644 --- a/src/plugins/haskell/haskell.qrc +++ b/src/plugins/haskell/haskell.qrc @@ -1,5 +1,6 @@ - images/category_haskell.png + images/settingscategory_haskell.png + images/settingscategory_haskell@2x.png diff --git a/src/plugins/haskell/haskelleditorfactory.cpp b/src/plugins/haskell/haskelleditorfactory.cpp index 4053efeba2d..ba87f88ef70 100644 --- a/src/plugins/haskell/haskelleditorfactory.cpp +++ b/src/plugins/haskell/haskelleditorfactory.cpp @@ -14,17 +14,14 @@ #include #include -#include +namespace Haskell::Internal { -namespace Haskell { -namespace Internal { - -static QWidget *createEditorWidget() +static QWidget *createEditorWidget(QObject *guard) { auto widget = new TextEditor::TextEditorWidget; auto ghciButton = new Core::CommandButton(Constants::A_RUN_GHCI, widget); ghciButton->setText(Tr::tr("GHCi")); - QObject::connect(ghciButton, &QToolButton::clicked, HaskellManager::instance(), [widget] { + QObject::connect(ghciButton, &QToolButton::clicked, guard, [widget] { HaskellManager::openGhci(widget->textDocument()->filePath()); }); widget->insertExtraToolBarWidget(TextEditor::TextEditorWidget::Left, ghciButton); @@ -40,12 +37,11 @@ HaskellEditorFactory::HaskellEditorFactory() | TextEditor::TextEditorActionHandler::FollowSymbolUnderCursor); setDocumentCreator([] { return new TextEditor::TextDocument(Constants::C_HASKELLEDITOR_ID); }); setIndenterCreator([](QTextDocument *doc) { return new TextEditor::TextIndenter(doc); }); - setEditorWidgetCreator(createEditorWidget); + setEditorWidgetCreator([this] { return createEditorWidget(this); }); setCommentDefinition(Utils::CommentDefinition("--", "{-", "-}")); setParenthesesMatchingEnabled(true); setMarksVisible(true); setSyntaxHighlighterCreator([] { return new HaskellHighlighter(); }); } -} // Internal -} // Haskell +} // Haskell::Internal diff --git a/src/plugins/haskell/haskelleditorfactory.h b/src/plugins/haskell/haskelleditorfactory.h index c91f875df29..090a5b12782 100644 --- a/src/plugins/haskell/haskelleditorfactory.h +++ b/src/plugins/haskell/haskelleditorfactory.h @@ -5,8 +5,7 @@ #include -namespace Haskell { -namespace Internal { +namespace Haskell::Internal { class HaskellEditorFactory : public TextEditor::TextEditorFactory { @@ -14,5 +13,4 @@ public: HaskellEditorFactory(); }; -} // Internal -} // Haskell +} // Haskell::Internal diff --git a/src/plugins/haskell/haskellmanager.cpp b/src/plugins/haskell/haskellmanager.cpp index ac3564f23e0..cc91ae7f816 100644 --- a/src/plugins/haskell/haskellmanager.cpp +++ b/src/plugins/haskell/haskellmanager.cpp @@ -3,43 +3,22 @@ #include "haskellmanager.h" +#include "haskellsettings.h" #include "haskelltr.h" -#include #include #include #include #include +#include #include -#include -#include #include #include -#include - -#include - -static const char kStackExecutableKey[] = "Haskell/StackExecutable"; using namespace Utils; -namespace Haskell { -namespace Internal { - -class HaskellManagerPrivate -{ -public: - FilePath stackExecutable; -}; - -Q_GLOBAL_STATIC(HaskellManagerPrivate, m_d) -Q_GLOBAL_STATIC(HaskellManager, m_instance) - -HaskellManager *HaskellManager::instance() -{ - return m_instance; -} +namespace Haskell::Internal { FilePath HaskellManager::findProjectDirectory(const FilePath &filePath) { @@ -57,28 +36,6 @@ FilePath HaskellManager::findProjectDirectory(const FilePath &filePath) return {}; } -FilePath defaultStackExecutable() -{ - // stack from brew or the installer script from https://docs.haskellstack.org - // install to /usr/local/bin. - if (HostOsInfo::isAnyUnixHost()) - return FilePath::fromString("/usr/local/bin/stack"); - return FilePath::fromString("stack"); -} - -FilePath HaskellManager::stackExecutable() -{ - return m_d->stackExecutable; -} - -void HaskellManager::setStackExecutable(const FilePath &filePath) -{ - if (filePath == m_d->stackExecutable) - return; - m_d->stackExecutable = filePath; - emit m_instance->stackExecutableChanged(m_d->stackExecutable); -} - void HaskellManager::openGhci(const FilePath &haskellFile) { const QList mimeTypes = mimeTypesForFileName(haskellFile.toString()); @@ -87,35 +44,11 @@ void HaskellManager::openGhci(const FilePath &haskellFile) }); const auto args = QStringList{"ghci"} + (isHaskell ? QStringList{haskellFile.fileName()} : QStringList()); - auto p = new QtcProcess(m_instance); - p->setTerminalMode(TerminalMode::On); - p->setCommand({stackExecutable(), args}); - p->setWorkingDirectory(haskellFile.absolutePath()); - connect(p, &QtcProcess::done, p, [p] { - if (p->result() != ProcessResult::FinishedWithSuccess) { - Core::MessageManager::writeDisrupting( - Tr::tr("Failed to run GHCi: \"%1\".").arg(p->errorString())); - } - p->deleteLater(); - }); - p->start(); + Process p; + p.setTerminalMode(TerminalMode::Detached); + p.setCommand({settings().stackPath(), args}); + p.setWorkingDirectory(haskellFile.absolutePath()); + p.start(); } -void HaskellManager::readSettings(QSettings *settings) -{ - m_d->stackExecutable = FilePath::fromString( - settings->value(kStackExecutableKey, - defaultStackExecutable().toString()).toString()); - emit m_instance->stackExecutableChanged(m_d->stackExecutable); -} - -void HaskellManager::writeSettings(QSettings *settings) -{ - if (m_d->stackExecutable == defaultStackExecutable()) - settings->remove(kStackExecutableKey); - else - settings->setValue(kStackExecutableKey, m_d->stackExecutable.toString()); -} - -} // namespace Internal -} // namespace Haskell +} // Haskell::Internal diff --git a/src/plugins/haskell/haskellmanager.h b/src/plugins/haskell/haskellmanager.h index da4018930d1..862f46460f1 100644 --- a/src/plugins/haskell/haskellmanager.h +++ b/src/plugins/haskell/haskellmanager.h @@ -7,30 +7,15 @@ #include -QT_BEGIN_NAMESPACE -class QSettings; -QT_END_NAMESPACE +namespace Haskell::Internal { -namespace Haskell { -namespace Internal { - -class HaskellManager : public QObject +class HaskellManager { - Q_OBJECT - public: static HaskellManager *instance(); static Utils::FilePath findProjectDirectory(const Utils::FilePath &filePath); - static Utils::FilePath stackExecutable(); - static void setStackExecutable(const Utils::FilePath &filePath); static void openGhci(const Utils::FilePath &haskellFile); - static void readSettings(QSettings *settings); - static void writeSettings(QSettings *settings); - -signals: - void stackExecutableChanged(const Utils::FilePath &filePath); }; -} // namespace Internal -} // namespace Haskell +} // Haskell::Internal diff --git a/src/plugins/haskell/haskellplugin.cpp b/src/plugins/haskell/haskellplugin.cpp index 334c1049112..84a86f7db48 100644 --- a/src/plugins/haskell/haskellplugin.cpp +++ b/src/plugins/haskell/haskellplugin.cpp @@ -9,8 +9,8 @@ #include "haskellmanager.h" #include "haskellproject.h" #include "haskellrunconfiguration.h" +#include "haskellsettings.h" #include "haskelltr.h" -#include "optionspage.h" #include "stackbuildstep.h" #include @@ -28,8 +28,8 @@ namespace Internal { class HaskellPluginPrivate { public: + HaskellSettings settings; HaskellEditorFactory editorFactory; - OptionsPage optionsPage; HaskellBuildConfigurationFactory buildConfigFactory; StackBuildStepFactory stackBuildStepFactory; HaskellRunConfigurationFactory runConfigFactory; @@ -41,11 +41,11 @@ HaskellPlugin::~HaskellPlugin() delete d; } -static void registerGhciAction() +static void registerGhciAction(QObject *guard) { - QAction *action = new QAction(Tr::tr("Run GHCi"), HaskellManager::instance()); + QAction *action = new QAction(Tr::tr("Run GHCi"), guard); Core::ActionManager::registerAction(action, Constants::A_RUN_GHCI); - QObject::connect(action, &QAction::triggered, HaskellManager::instance(), [] { + QObject::connect(action, &QAction::triggered, guard, [] { if (Core::IDocument *doc = Core::EditorManager::currentDocument()) HaskellManager::openGhci(doc->filePath()); }); @@ -63,13 +63,7 @@ bool HaskellPlugin::initialize(const QStringList &arguments, QString *errorStrin TextEditor::SnippetProvider::registerGroup(Constants::C_HASKELLSNIPPETSGROUP_ID, Tr::tr("Haskell", "SnippetProvider")); - connect(Core::ICore::instance(), &Core::ICore::saveSettingsRequested, this, [] { - HaskellManager::writeSettings(Core::ICore::settings()); - }); - - registerGhciAction(); - - HaskellManager::readSettings(Core::ICore::settings()); + registerGhciAction(this); ProjectExplorer::JsonWizardFactory::addWizardPath(":/haskell/share/wizards/"); return true; diff --git a/src/plugins/haskell/haskellproject.cpp b/src/plugins/haskell/haskellproject.cpp index 916632c5dc6..f19840a67f5 100644 --- a/src/plugins/haskell/haskellproject.cpp +++ b/src/plugins/haskell/haskellproject.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #include diff --git a/src/plugins/haskell/haskellrunconfiguration.cpp b/src/plugins/haskell/haskellrunconfiguration.cpp index 45b6e684bc4..76d25e557c3 100644 --- a/src/plugins/haskell/haskellrunconfiguration.cpp +++ b/src/plugins/haskell/haskellrunconfiguration.cpp @@ -4,12 +4,11 @@ #include "haskellrunconfiguration.h" #include "haskellconstants.h" -#include "haskellmanager.h" #include "haskellproject.h" #include "haskelltr.h" +#include "haskellsettings.h" #include -#include #include #include #include @@ -35,7 +34,8 @@ HaskellExecutableAspect::HaskellExecutableAspect() HaskellRunConfiguration::HaskellRunConfiguration(Target *target, Utils::Id id) : RunConfiguration(target, id) { - auto envAspect = addAspect(target); + auto envAspect = addAspect(); + envAspect->setSupportForBuildEnvironment(target); addAspect(); addAspect(macroExpander()); @@ -67,8 +67,8 @@ Runnable HaskellRunConfiguration::runnable() const args << "--" << arguments; r.workingDirectory = projectDirectory; - r.environment = aspect()->environment(); - r.command = {r.environment.searchInPath(HaskellManager::stackExecutable().toString()), args}; + r.environment = aspect()->environment(); + r.command = {r.environment.searchInPath(settings().stackPath().path()), args}; return r; } diff --git a/src/plugins/haskell/haskellsettings.cpp b/src/plugins/haskell/haskellsettings.cpp new file mode 100644 index 00000000000..7b2bbc7fd38 --- /dev/null +++ b/src/plugins/haskell/haskellsettings.cpp @@ -0,0 +1,58 @@ +// Copyright (c) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "haskellsettings.h" + +#include "haskellconstants.h" +#include "haskelltr.h" + +#include +#include + +using namespace Utils; + +namespace Haskell::Internal { + +static HaskellSettings *theSettings; + +HaskellSettings &settings() +{ + return *theSettings; +} + +HaskellSettings::HaskellSettings() +{ + theSettings = this; + + setId(Constants::OPTIONS_GENERAL); + setDisplayName(Tr::tr("General")); + setCategory("J.Z.Haskell"); + setDisplayCategory(Tr::tr("Haskell")); + setCategoryIconPath(":/haskell/images/settingscategory_haskell.png"); + + stackPath.setSettingsKey("Haskell/StackExecutable"); + stackPath.setExpectedKind(PathChooser::ExistingCommand); + stackPath.setPromptDialogTitle(Tr::tr("Choose Stack Executable")); + stackPath.setCommandVersionArguments({"--version"}); + + // stack from brew or the installer script from https://docs.haskellstack.org + // install to /usr/local/bin. + stackPath.setDefaultFilePath(HostOsInfo::isAnyUnixHost() + ? FilePath::fromString("/usr/local/bin/stack") + : FilePath::fromString("stack")); + + setLayouter([this] { + using namespace Layouting; + return Column { + Group { + title(Tr::tr("General")), + Row { Tr::tr("Stack executable:"), stackPath } + }, + st, + }; + }); + + readSettings(); +} + +} // Haskell::Internal diff --git a/src/plugins/haskell/haskellsettings.h b/src/plugins/haskell/haskellsettings.h new file mode 100644 index 00000000000..1331e5aa89a --- /dev/null +++ b/src/plugins/haskell/haskellsettings.h @@ -0,0 +1,20 @@ +// Copyright (c) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace Haskell::Internal { + +class HaskellSettings : public Core::PagedSettings +{ +public: + HaskellSettings(); + + Utils::FilePathAspect stackPath{this}; +}; + +HaskellSettings &settings(); + +} // Haskell::Internal diff --git a/src/plugins/haskell/images/category_haskell.png b/src/plugins/haskell/images/category_haskell.png deleted file mode 100644 index 947f948bd91..00000000000 Binary files a/src/plugins/haskell/images/category_haskell.png and /dev/null differ diff --git a/src/plugins/haskell/images/settingscategory_haskell.png b/src/plugins/haskell/images/settingscategory_haskell.png new file mode 100644 index 00000000000..5bd3f69fa8c Binary files /dev/null and b/src/plugins/haskell/images/settingscategory_haskell.png differ diff --git a/src/plugins/haskell/images/settingscategory_haskell@2x.png b/src/plugins/haskell/images/settingscategory_haskell@2x.png new file mode 100644 index 00000000000..1eb20959d5a Binary files /dev/null and b/src/plugins/haskell/images/settingscategory_haskell@2x.png differ diff --git a/src/plugins/haskell/optionspage.cpp b/src/plugins/haskell/optionspage.cpp deleted file mode 100644 index e64a6012345..00000000000 --- a/src/plugins/haskell/optionspage.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "optionspage.h" - -#include "haskellconstants.h" -#include "haskellmanager.h" -#include "haskelltr.h" - -#include -#include -#include -#include -#include - -namespace Haskell { -namespace Internal { - -OptionsPage::OptionsPage() -{ - setId(Constants::OPTIONS_GENERAL); - setDisplayName(Tr::tr("General")); - setCategory("J.Z.Haskell"); - setDisplayCategory(Tr::tr("Haskell")); - setCategoryIcon(Utils::Icon(":/haskell/images/category_haskell.png")); -} - -QWidget *OptionsPage::widget() -{ - using namespace Utils; - if (!m_widget) { - m_widget = new QWidget; - auto topLayout = new QVBoxLayout; - m_widget->setLayout(topLayout); - auto generalBox = new QGroupBox(Tr::tr("General")); - topLayout->addWidget(generalBox); - topLayout->addStretch(10); - auto boxLayout = new QHBoxLayout; - generalBox->setLayout(boxLayout); - boxLayout->addWidget(new QLabel(Tr::tr("Stack executable:"))); - m_stackPath = new PathChooser(); - m_stackPath->setExpectedKind(PathChooser::ExistingCommand); - m_stackPath->setPromptDialogTitle(Tr::tr("Choose Stack Executable")); - m_stackPath->setFilePath(HaskellManager::stackExecutable()); - m_stackPath->setCommandVersionArguments({"--version"}); - boxLayout->addWidget(m_stackPath); - } - return m_widget; -} - -void OptionsPage::apply() -{ - if (!m_widget) - return; - HaskellManager::setStackExecutable(m_stackPath->rawFilePath()); -} - -void OptionsPage::finish() -{ -} - -} // namespace Internal -} // namespace Haskell diff --git a/src/plugins/haskell/optionspage.h b/src/plugins/haskell/optionspage.h deleted file mode 100644 index a477d72dfce..00000000000 --- a/src/plugins/haskell/optionspage.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include -#include - -#include - -namespace Haskell { -namespace Internal { - -class OptionsPage : public Core::IOptionsPage -{ - Q_OBJECT - -public: - OptionsPage(); - - QWidget *widget() override; - void apply() override; - void finish() override; - -private: - QPointer m_widget; - QPointer m_stackPath; -}; - -} // namespace Internal -} // namespace Haskell diff --git a/src/plugins/haskell/stackbuildstep.cpp b/src/plugins/haskell/stackbuildstep.cpp index 084ba8af4a7..4006ae9daad 100644 --- a/src/plugins/haskell/stackbuildstep.cpp +++ b/src/plugins/haskell/stackbuildstep.cpp @@ -4,7 +4,7 @@ #include "stackbuildstep.h" #include "haskellconstants.h" -#include "haskellmanager.h" +#include "haskellsettings.h" #include "haskelltr.h" #include @@ -38,7 +38,7 @@ bool StackBuildStep::init() if (AbstractProcessStep::init()) { const auto projectDir = QDir(project()->projectDirectory().toString()); processParameters()->setCommandLine( - {HaskellManager::stackExecutable(), + {settings().stackPath(), {"build", "--work-dir", projectDir.relativeFilePath(buildDirectory().toString())}}); processParameters()->setEnvironment(buildEnvironment()); } diff --git a/src/plugins/help/CMakeLists.txt b/src/plugins/help/CMakeLists.txt index c37b5b0c313..30347817391 100644 --- a/src/plugins/help/CMakeLists.txt +++ b/src/plugins/help/CMakeLists.txt @@ -12,7 +12,6 @@ add_qtc_plugin(Help helpfindsupport.cpp helpfindsupport.h helpindexfilter.cpp helpindexfilter.h helpmanager.cpp helpmanager.h - helpmode.cpp helpmode.h helpplugin.cpp helpplugin.h helptr.h helpviewer.cpp helpviewer.h @@ -47,7 +46,7 @@ extend_qtc_plugin(Help ) option(BUILD_HELPVIEWERBACKEND_QTWEBENGINE "Build QtWebEngine based help viewer backend." YES) -find_package(Qt5 COMPONENTS WebEngineWidgets QUIET) +find_package(Qt6 COMPONENTS WebEngineWidgets QUIET) extend_qtc_plugin(Help CONDITION BUILD_HELPVIEWERBACKEND_QTWEBENGINE AND TARGET Qt::WebEngineWidgets FEATURE_INFO "QtWebEngine help viewer" diff --git a/src/plugins/help/filtersettingspage.cpp b/src/plugins/help/filtersettingspage.cpp index 771c39fc689..e227b025ef0 100644 --- a/src/plugins/help/filtersettingspage.cpp +++ b/src/plugins/help/filtersettingspage.cpp @@ -7,56 +7,56 @@ #include "helptr.h" #include "localhelpmanager.h" +#include + #include #include #include -using namespace Help::Internal; +namespace Help::Internal { -FilterSettingsPage::FilterSettingsPage() +class FilterSettingsPageWidget : public Core::IOptionsPageWidget +{ +public: + FilterSettingsPageWidget(const std::function &onChanged) + { + LocalHelpManager::setupGuiHelpEngine(); + + auto widget = new QHelpFilterSettingsWidget; + widget->readSettings(LocalHelpManager::filterEngine()); + + Layouting::Column { + Layouting::noMargin, + widget + }.attachTo(this); + + auto updateFilterPage = [widget] { + widget->setAvailableComponents(LocalHelpManager::filterEngine()->availableComponents()); + widget->setAvailableVersions(LocalHelpManager::filterEngine()->availableVersions()); + }; + + auto connection = connect(Core::HelpManager::Signals::instance(), + &Core::HelpManager::Signals::documentationChanged, + this, + updateFilterPage); + updateFilterPage(); + + setOnApply([widget, onChanged] { + if (widget->applySettings(LocalHelpManager::filterEngine())) + onChanged(); + widget->readSettings(LocalHelpManager::filterEngine()); + }); + + setOnFinish([connection] { disconnect(connection); }); + } +}; + +FilterSettingsPage::FilterSettingsPage(const std::function &onChanged) { setId("D.Filters"); setDisplayName(Tr::tr("Filters")); setCategory(Help::Constants::HELP_CATEGORY); + setWidgetCreator([onChanged] { return new FilterSettingsPageWidget(onChanged); }); } -QWidget *FilterSettingsPage::widget() -{ - if (!m_widget) { - LocalHelpManager::setupGuiHelpEngine(); - m_widget = new QHelpFilterSettingsWidget(nullptr); - m_widget->readSettings(LocalHelpManager::filterEngine()); - - connect(Core::HelpManager::Signals::instance(), - &Core::HelpManager::Signals::documentationChanged, - this, - &FilterSettingsPage::updateFilterPage); - - updateFilterPage(); - } - return m_widget; -} - -void FilterSettingsPage::apply() -{ - if (m_widget->applySettings(LocalHelpManager::filterEngine())) - emit filtersChanged(); - - m_widget->readSettings(LocalHelpManager::filterEngine()); -} - -void FilterSettingsPage::finish() -{ - disconnect(Core::HelpManager::Signals::instance(), - &Core::HelpManager::Signals::documentationChanged, - this, - &FilterSettingsPage::updateFilterPage); - delete m_widget; -} - -void FilterSettingsPage::updateFilterPage() -{ - m_widget->setAvailableComponents(LocalHelpManager::filterEngine()->availableComponents()); - m_widget->setAvailableVersions(LocalHelpManager::filterEngine()->availableVersions()); -} - +} // Help::Internal diff --git a/src/plugins/help/filtersettingspage.h b/src/plugins/help/filtersettingspage.h index 308a489f26b..54256b1d7f0 100644 --- a/src/plugins/help/filtersettingspage.h +++ b/src/plugins/help/filtersettingspage.h @@ -5,35 +5,12 @@ #include -#include - -QT_BEGIN_NAMESPACE -class QHelpFilterSettingsWidget; -QT_END_NAMESPACE - -namespace Help { -namespace Internal { +namespace Help::Internal { class FilterSettingsPage : public Core::IOptionsPage { - Q_OBJECT - public: - FilterSettingsPage(); - - QWidget *widget() override; - void apply() override; - void finish() override; - -signals: - void filtersChanged(); - -private: - - void updateFilterPage(); - QPointer m_widget; - + explicit FilterSettingsPage(const std::function &onChanged); }; -} // namespace Help -} // namespace Internal +} // Help::Internal diff --git a/src/plugins/help/generalsettingspage.cpp b/src/plugins/help/generalsettingspage.cpp index a05a0db3eed..d5f5756ed69 100644 --- a/src/plugins/help/generalsettingspage.cpp +++ b/src/plugins/help/generalsettingspage.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -35,19 +36,42 @@ #include #include -#include - using namespace Core; using namespace Utils; -namespace Help { -namespace Internal { +namespace Help::Internal { -class GeneralSettingsPageWidget : public QWidget +class GeneralSettingsPageWidget : public IOptionsPageWidget { public: GeneralSettingsPageWidget(); +private: + void apply() final; + + void setCurrentPage(); + void setBlankPage(); + void setDefaultPage(); + void importBookmarks(); + void exportBookmarks(); + + void updateFontSizeSelector(); + void updateFontStyleSelector(); + void updateFontFamilySelector(); + void updateFont(); + int closestPointSizeIndex(int desiredPointSize) const; + + QFont m_font; + int m_fontZoom = 100; + QFontDatabase m_fontDatabase; + + QString m_homePage; + int m_contextOption; + + int m_startOption; + bool m_returnOnClose; + bool m_scrollWheelZoomingEnabled; + QSpinBox *zoomSpinBox; QFontComboBox *familyComboBox; QComboBox *styleComboBox; @@ -62,7 +86,7 @@ public: QPushButton *importButton; QPushButton *exportButton; QCheckBox *scrollWheelZooming; - QCheckBox *m_returnOnClose; + QCheckBox *m_returnOnCloseCheckBox; QComboBox *viewerBackend; }; @@ -152,8 +176,8 @@ GeneralSettingsPageWidget::GeneralSettingsPageWidget() auto behaviourGroupBox = new QGroupBox(Tr::tr("Behaviour")); scrollWheelZooming = new QCheckBox(Tr::tr("Enable scroll wheel zooming")); - m_returnOnClose = new QCheckBox(Tr::tr("Return to editor on closing the last page")); - m_returnOnClose->setToolTip( + m_returnOnCloseCheckBox = new QCheckBox(Tr::tr("Return to editor on closing the last page")); + m_returnOnCloseCheckBox->setToolTip( Tr::tr("Switches to editor context after last help page is closed.")); auto viewerBackendLabel = new QLabel(Tr::tr("Viewer backend:")); @@ -172,7 +196,7 @@ GeneralSettingsPageWidget::GeneralSettingsPageWidget() auto behaviourGroupBoxLayout = new QVBoxLayout; behaviourGroupBox->setLayout(behaviourGroupBoxLayout); behaviourGroupBoxLayout->addWidget(scrollWheelZooming); - behaviourGroupBoxLayout->addWidget(m_returnOnClose); + behaviourGroupBoxLayout->addWidget(m_returnOnCloseCheckBox); behaviourGroupBoxLayout->addLayout(viewerBackendLayout); // bookmarks @@ -203,167 +227,145 @@ GeneralSettingsPageWidget::GeneralSettingsPageWidget() mainLayout->addWidget(behaviourGroupBox); mainLayout->addLayout(bookmarksLayout); mainLayout->addStretch(1); -} -GeneralSettingsPage::GeneralSettingsPage() -{ - setId("A.General settings"); - setDisplayName(Tr::tr("General")); - setCategory(Help::Constants::HELP_CATEGORY); - setDisplayCategory(Tr::tr("Help")); - setCategoryIconPath(":/help/images/settingscategory_help.png"); -} + m_font = LocalHelpManager::fallbackFont(); + m_fontZoom = LocalHelpManager::fontZoom(); + zoomSpinBox->setValue(m_fontZoom); -QWidget *GeneralSettingsPage::widget() -{ - if (!m_widget) { - m_widget = new GeneralSettingsPageWidget; + updateFontSizeSelector(); + updateFontStyleSelector(); + updateFontFamilySelector(); - m_font = LocalHelpManager::fallbackFont(); - m_fontZoom = LocalHelpManager::fontZoom(); - m_widget->zoomSpinBox->setValue(m_fontZoom); - - updateFontSizeSelector(); + connect(familyComboBox, &QFontComboBox::currentFontChanged, this, [this] { + updateFont(); updateFontStyleSelector(); - updateFontFamilySelector(); + updateFontSizeSelector(); + updateFont(); // changes that might have happened when updating the selectors + }); - connect(m_widget->familyComboBox, &QFontComboBox::currentFontChanged, this, [this] { - updateFont(); - updateFontStyleSelector(); - updateFontSizeSelector(); - updateFont(); // changes that might have happened when updating the selectors - }); + connect(styleComboBox, &QComboBox::currentIndexChanged, this, [this] { + updateFont(); + updateFontSizeSelector(); + updateFont(); // changes that might have happened when updating the selectors + }); - connect(m_widget->styleComboBox, &QComboBox::currentIndexChanged, this, [this] { - updateFont(); - updateFontSizeSelector(); - updateFont(); // changes that might have happened when updating the selectors - }); + connect(sizeComboBox, &QComboBox::currentIndexChanged, + this, &GeneralSettingsPageWidget::updateFont); - connect(m_widget->sizeComboBox, &QComboBox::currentIndexChanged, - this, &GeneralSettingsPage::updateFont); + connect(zoomSpinBox, &QSpinBox::valueChanged, + this, [this](int value) { m_fontZoom = value; }); - connect(m_widget->zoomSpinBox, &QSpinBox::valueChanged, - this, [this](int value) { m_fontZoom = value; }); + m_homePage = LocalHelpManager::homePage(); + homePageLineEdit->setText(m_homePage); - m_homePage = LocalHelpManager::homePage(); - m_widget->homePageLineEdit->setText(m_homePage); + m_startOption = LocalHelpManager::startOption(); + helpStartComboBox->setCurrentIndex(m_startOption); - m_startOption = LocalHelpManager::startOption(); - m_widget->helpStartComboBox->setCurrentIndex(m_startOption); + m_contextOption = LocalHelpManager::contextHelpOption(); + contextHelpComboBox->setCurrentIndex(m_contextOption); - m_contextOption = LocalHelpManager::contextHelpOption(); - m_widget->contextHelpComboBox->setCurrentIndex(m_contextOption); + connect(currentPageButton, &QPushButton::clicked, + this, &GeneralSettingsPageWidget::setCurrentPage); + connect(blankPageButton, &QPushButton::clicked, + this, &GeneralSettingsPageWidget::setBlankPage); + connect(defaultPageButton, &QPushButton::clicked, + this, &GeneralSettingsPageWidget::setDefaultPage); - connect(m_widget->currentPageButton, &QPushButton::clicked, - this, &GeneralSettingsPage::setCurrentPage); - connect(m_widget->blankPageButton, &QPushButton::clicked, - this, &GeneralSettingsPage::setBlankPage); - connect(m_widget->defaultPageButton, - &QPushButton::clicked, - this, - &GeneralSettingsPage::setDefaultPage); + HelpViewer *viewer = HelpPlugin::modeHelpWidget()->currentViewer(); + if (!viewer) + currentPageButton->setEnabled(false); - HelpViewer *viewer = HelpPlugin::modeHelpWidget()->currentViewer(); - if (!viewer) - m_widget->currentPageButton->setEnabled(false); + errorLabel->setVisible(false); + connect(importButton, &QPushButton::clicked, + this, &GeneralSettingsPageWidget::importBookmarks); + connect(exportButton, &QPushButton::clicked, + this, &GeneralSettingsPageWidget::exportBookmarks); - m_widget->errorLabel->setVisible(false); - connect(m_widget->importButton, &QPushButton::clicked, - this, &GeneralSettingsPage::importBookmarks); - connect(m_widget->exportButton, &QPushButton::clicked, - this, &GeneralSettingsPage::exportBookmarks); + m_returnOnClose = LocalHelpManager::returnOnClose(); + m_returnOnCloseCheckBox->setChecked(m_returnOnClose); - m_returnOnClose = LocalHelpManager::returnOnClose(); - m_widget->m_returnOnClose->setChecked(m_returnOnClose); + m_scrollWheelZoomingEnabled = LocalHelpManager::isScrollWheelZoomingEnabled(); + scrollWheelZooming->setChecked(m_scrollWheelZoomingEnabled); - m_scrollWheelZoomingEnabled = LocalHelpManager::isScrollWheelZoomingEnabled(); - m_widget->scrollWheelZooming->setChecked(m_scrollWheelZoomingEnabled); - - m_widget->viewerBackend->addItem( - Tr::tr("Default (%1)", "Default viewer backend") - .arg(LocalHelpManager::defaultViewerBackend().displayName)); - const QByteArray currentBackend = LocalHelpManager::viewerBackendId(); - const QVector backends = LocalHelpManager::viewerBackends(); - for (const HelpViewerFactory &f : backends) { - m_widget->viewerBackend->addItem(f.displayName, f.id); - if (f.id == currentBackend) - m_widget->viewerBackend->setCurrentIndex(m_widget->viewerBackend->count() - 1); - } - if (backends.size() == 1) - m_widget->viewerBackend->setEnabled(false); + viewerBackend->addItem( + Tr::tr("Default (%1)", "Default viewer backend") + .arg(LocalHelpManager::defaultViewerBackend().displayName)); + const QByteArray currentBackend = LocalHelpManager::viewerBackendId(); + const QVector backends = LocalHelpManager::viewerBackends(); + for (const HelpViewerFactory &f : backends) { + viewerBackend->addItem(f.displayName, f.id); + if (f.id == currentBackend) + viewerBackend->setCurrentIndex(viewerBackend->count() - 1); } - return m_widget; + if (backends.size() == 1) + viewerBackend->setEnabled(false); } -void GeneralSettingsPage::apply() +void GeneralSettingsPageWidget::apply() { - if (!m_widget) // page was never shown - return; - if (m_font != LocalHelpManager::fallbackFont()) LocalHelpManager::setFallbackFont(m_font); if (m_fontZoom != LocalHelpManager::fontZoom()) LocalHelpManager::setFontZoom(m_fontZoom); - QString homePage = QUrl::fromUserInput(m_widget->homePageLineEdit->text()).toString(); + QString homePage = QUrl::fromUserInput(homePageLineEdit->text()).toString(); if (homePage.isEmpty()) homePage = Help::Constants::AboutBlank; - m_widget->homePageLineEdit->setText(homePage); + homePageLineEdit->setText(homePage); if (m_homePage != homePage) { m_homePage = homePage; LocalHelpManager::setHomePage(homePage); } - const int startOption = m_widget->helpStartComboBox->currentIndex(); + const int startOption = helpStartComboBox->currentIndex(); if (m_startOption != startOption) { m_startOption = startOption; LocalHelpManager::setStartOption(LocalHelpManager::StartOption(m_startOption)); } - const int helpOption = m_widget->contextHelpComboBox->currentIndex(); + const int helpOption = contextHelpComboBox->currentIndex(); if (m_contextOption != helpOption) { m_contextOption = helpOption; LocalHelpManager::setContextHelpOption(HelpManager::HelpViewerLocation(m_contextOption)); } - const bool close = m_widget->m_returnOnClose->isChecked(); + const bool close = m_returnOnCloseCheckBox->isChecked(); if (m_returnOnClose != close) { m_returnOnClose = close; LocalHelpManager::setReturnOnClose(m_returnOnClose); } - const bool zoom = m_widget->scrollWheelZooming->isChecked(); + const bool zoom = scrollWheelZooming->isChecked(); if (m_scrollWheelZoomingEnabled != zoom) { m_scrollWheelZoomingEnabled = zoom; LocalHelpManager::setScrollWheelZoomingEnabled(m_scrollWheelZoomingEnabled); } - const QByteArray viewerBackendId = m_widget->viewerBackend->currentData().toByteArray(); + const QByteArray viewerBackendId = viewerBackend->currentData().toByteArray(); LocalHelpManager::setViewerBackendId(viewerBackendId); } -void GeneralSettingsPage::setCurrentPage() +void GeneralSettingsPageWidget::setCurrentPage() { HelpViewer *viewer = HelpPlugin::modeHelpWidget()->currentViewer(); if (viewer) - m_widget->homePageLineEdit->setText(viewer->source().toString()); + homePageLineEdit->setText(viewer->source().toString()); } -void GeneralSettingsPage::setBlankPage() +void GeneralSettingsPageWidget::setBlankPage() { - m_widget->homePageLineEdit->setText(Help::Constants::AboutBlank); + homePageLineEdit->setText(Help::Constants::AboutBlank); } -void GeneralSettingsPage::setDefaultPage() +void GeneralSettingsPageWidget::setDefaultPage() { - m_widget->homePageLineEdit->setText(LocalHelpManager::defaultHomePage()); + homePageLineEdit->setText(LocalHelpManager::defaultHomePage()); } -void GeneralSettingsPage::importBookmarks() +void GeneralSettingsPageWidget::importBookmarks() { - m_widget->errorLabel->setVisible(false); + errorLabel->setVisible(false); FilePath filePath = FileUtils::getOpenFilePath(nullptr, Tr::tr("Import Bookmarks"), @@ -381,13 +383,13 @@ void GeneralSettingsPage::importBookmarks() return; } - m_widget->errorLabel->setVisible(true); - m_widget->errorLabel->setText(Tr::tr("Cannot import bookmarks.")); + errorLabel->setVisible(true); + errorLabel->setText(Tr::tr("Cannot import bookmarks.")); } -void GeneralSettingsPage::exportBookmarks() +void GeneralSettingsPageWidget::exportBookmarks() { - m_widget->errorLabel->setVisible(false); + errorLabel->setVisible(false); FilePath filePath = FileUtils::getSaveFilePath(nullptr, Tr::tr("Save File"), @@ -405,12 +407,12 @@ void GeneralSettingsPage::exportBookmarks() saver.setResult(&writer); } if (!saver.finalize()) { - m_widget->errorLabel->setVisible(true); - m_widget->errorLabel->setText(saver.errorString()); + errorLabel->setVisible(true); + errorLabel->setText(saver.errorString()); } } -void GeneralSettingsPage::updateFontSizeSelector() +void GeneralSettingsPageWidget::updateFontSizeSelector() { const QString &family = m_font.family(); const QString &fontStyle = m_fontDatabase.styleString(m_font); @@ -419,81 +421,81 @@ void GeneralSettingsPage::updateFontSizeSelector() if (pointSizes.empty()) pointSizes = QFontDatabase::standardSizes(); - QSignalBlocker blocker(m_widget->sizeComboBox); - m_widget->sizeComboBox->clear(); - m_widget->sizeComboBox->setCurrentIndex(-1); - m_widget->sizeComboBox->setEnabled(!pointSizes.empty()); + QSignalBlocker blocker(sizeComboBox); + sizeComboBox->clear(); + sizeComboBox->setCurrentIndex(-1); + sizeComboBox->setEnabled(!pointSizes.empty()); // try to maintain selection or select closest. if (!pointSizes.empty()) { QString n; for (int pointSize : std::as_const(pointSizes)) - m_widget->sizeComboBox->addItem(n.setNum(pointSize), QVariant(pointSize)); + sizeComboBox->addItem(n.setNum(pointSize), QVariant(pointSize)); const int closestIndex = closestPointSizeIndex(m_font.pointSize()); if (closestIndex != -1) - m_widget->sizeComboBox->setCurrentIndex(closestIndex); + sizeComboBox->setCurrentIndex(closestIndex); } } -void GeneralSettingsPage::updateFontStyleSelector() +void GeneralSettingsPageWidget::updateFontStyleSelector() { const QString &fontStyle = m_fontDatabase.styleString(m_font); const QStringList &styles = m_fontDatabase.styles(m_font.family()); - QSignalBlocker blocker(m_widget->styleComboBox); - m_widget->styleComboBox->clear(); - m_widget->styleComboBox->setCurrentIndex(-1); - m_widget->styleComboBox->setEnabled(!styles.empty()); + QSignalBlocker blocker(styleComboBox); + styleComboBox->clear(); + styleComboBox->setCurrentIndex(-1); + styleComboBox->setEnabled(!styles.empty()); if (!styles.empty()) { int normalIndex = -1; const QString normalStyle = "Normal"; for (const QString &style : styles) { // try to maintain selection or select 'normal' preferably - const int newIndex = m_widget->styleComboBox->count(); - m_widget->styleComboBox->addItem(style); + const int newIndex = styleComboBox->count(); + styleComboBox->addItem(style); if (fontStyle == style) { - m_widget->styleComboBox->setCurrentIndex(newIndex); + styleComboBox->setCurrentIndex(newIndex); } else { if (fontStyle == normalStyle) normalIndex = newIndex; } } - if (m_widget->styleComboBox->currentIndex() == -1 && normalIndex != -1) - m_widget->styleComboBox->setCurrentIndex(normalIndex); + if (styleComboBox->currentIndex() == -1 && normalIndex != -1) + styleComboBox->setCurrentIndex(normalIndex); } } -void GeneralSettingsPage::updateFontFamilySelector() +void GeneralSettingsPageWidget::updateFontFamilySelector() { - m_widget->familyComboBox->setCurrentFont(m_font); + familyComboBox->setCurrentFont(m_font); } -void GeneralSettingsPage::updateFont() +void GeneralSettingsPageWidget::updateFont() { - const QString &family = m_widget->familyComboBox->currentFont().family(); + const QString &family = familyComboBox->currentFont().family(); m_font.setFamily(family); int fontSize = 14; - int currentIndex = m_widget->sizeComboBox->currentIndex(); + int currentIndex = sizeComboBox->currentIndex(); if (currentIndex != -1) - fontSize = m_widget->sizeComboBox->itemData(currentIndex).toInt(); + fontSize = sizeComboBox->itemData(currentIndex).toInt(); m_font.setPointSize(fontSize); - currentIndex = m_widget->styleComboBox->currentIndex(); + currentIndex = styleComboBox->currentIndex(); if (currentIndex != -1) - m_font.setStyleName(m_widget->styleComboBox->itemText(currentIndex)); + m_font.setStyleName(styleComboBox->itemText(currentIndex)); } -int GeneralSettingsPage::closestPointSizeIndex(int desiredPointSize) const +int GeneralSettingsPageWidget::closestPointSizeIndex(int desiredPointSize) const { // try to maintain selection or select closest. int closestIndex = -1; int closestAbsError = 0xFFFF; - const int pointSizeCount = m_widget->sizeComboBox->count(); + const int pointSizeCount = sizeComboBox->count(); for (int i = 0; i < pointSizeCount; i++) { - const int itemPointSize = m_widget->sizeComboBox->itemData(i).toInt(); + const int itemPointSize = sizeComboBox->itemData(i).toInt(); const int absError = qAbs(desiredPointSize - itemPointSize); if (absError < closestAbsError) { closestIndex = i; @@ -508,10 +510,16 @@ int GeneralSettingsPage::closestPointSizeIndex(int desiredPointSize) const return closestIndex; } -void GeneralSettingsPage::finish() +// GeneralSettingPage + +GeneralSettingsPage::GeneralSettingsPage() { - delete m_widget; + setId("A.General settings"); + setDisplayName(Tr::tr("General")); + setCategory(Help::Constants::HELP_CATEGORY); + setDisplayCategory(Tr::tr("Help")); + setCategoryIconPath(":/help/images/settingscategory_help.png"); + setWidgetCreator([] { return new GeneralSettingsPageWidget; }); } -} // Internal -} // Help +} // Help::Interal diff --git a/src/plugins/help/generalsettingspage.h b/src/plugins/help/generalsettingspage.h index 4965cac9ea3..ff5f6313ac8 100644 --- a/src/plugins/help/generalsettingspage.h +++ b/src/plugins/help/generalsettingspage.h @@ -5,51 +5,12 @@ #include -#include -#include - -namespace Help { -namespace Internal { - -class GeneralSettingsPageWidget; +namespace Help::Internal { class GeneralSettingsPage : public Core::IOptionsPage { - Q_OBJECT - public: GeneralSettingsPage(); - - QWidget *widget() override; - void apply() override; - void finish() override; - -private: - void setCurrentPage(); - void setBlankPage(); - void setDefaultPage(); - void importBookmarks(); - void exportBookmarks(); - - void updateFontSizeSelector(); - void updateFontStyleSelector(); - void updateFontFamilySelector(); - void updateFont(); - int closestPointSizeIndex(int desiredPointSize) const; - - QFont m_font; - int m_fontZoom = 100; - QFontDatabase m_fontDatabase; - - QString m_homePage; - int m_contextOption; - - int m_startOption; - bool m_returnOnClose; - bool m_scrollWheelZoomingEnabled; - - QPointer m_widget; }; - } // Internal -} // Help +} // Help::Internal diff --git a/src/plugins/help/help.qbs b/src/plugins/help/help.qbs index 25d85bdc37e..d96cfa154e7 100644 --- a/src/plugins/help/help.qbs +++ b/src/plugins/help/help.qbs @@ -43,7 +43,6 @@ Project { "helpfindsupport.cpp", "helpfindsupport.h", "helpindexfilter.cpp", "helpindexfilter.h", "helpmanager.cpp", "helpmanager.h", - "helpmode.cpp", "helpmode.h", "helpplugin.cpp", "helpplugin.h", "helpviewer.cpp", "helpviewer.h", "helpwidget.cpp", "helpwidget.h", diff --git a/src/plugins/help/helpindexfilter.cpp b/src/plugins/help/helpindexfilter.cpp index fe1c34a48e0..9da19214801 100644 --- a/src/plugins/help/helpindexfilter.cpp +++ b/src/plugins/help/helpindexfilter.cpp @@ -3,30 +3,34 @@ #include "helpindexfilter.h" -#include "helpmanager.h" -#include "helptr.h" #include "localhelpmanager.h" +#include "helpmanager.h" +#include "helpplugin.h" +#include "helptr.h" -#include #include +#include #include +#include #include #include #include #include -#include using namespace Core; using namespace Help; using namespace Help::Internal; +using namespace Tasking; +using namespace Utils; HelpIndexFilter::HelpIndexFilter() { setId("HelpIndexFilter"); setDisplayName(Tr::tr("Help Index")); - setDefaultIncludedByDefault(false); + setDescription(Tr::tr("Locates help topics, for example in the Qt documentation.")); setDefaultShortcutString("?"); + setRefreshRecipe(Sync([this] { invalidateCache(); })); m_icon = Utils::Icons::BOOKMARK.icon(); connect(Core::HelpManager::Signals::instance(), &Core::HelpManager::Signals::setupFinished, @@ -39,82 +43,69 @@ HelpIndexFilter::HelpIndexFilter() this, &HelpIndexFilter::invalidateCache); } -HelpIndexFilter::~HelpIndexFilter() = default; - -bool HelpIndexFilter::updateCache(QFutureInterface &future, - const QStringList &cache, const QString &entry) +static void matches(QPromise &promise, const LocatorStorage &storage, + const QStringList &cache, const QIcon &icon) { - const Qt::CaseSensitivity cs = caseSensitivity(entry); + const QString input = storage.input(); + const Qt::CaseSensitivity cs = ILocatorFilter::caseSensitivity(input); QStringList bestKeywords; QStringList worseKeywords; bestKeywords.reserve(cache.size()); worseKeywords.reserve(cache.size()); for (const QString &keyword : cache) { - if (future.isCanceled()) - return false; - if (keyword.startsWith(entry, cs)) + if (promise.isCanceled()) + return; + if (keyword.startsWith(input, cs)) bestKeywords.append(keyword); - else if (keyword.contains(entry, cs)) + else if (keyword.contains(input, cs)) worseKeywords.append(keyword); } - bestKeywords << worseKeywords; - m_lastIndicesCache = bestKeywords; - m_lastEntry = entry; + const QStringList lastIndicesCache = bestKeywords + worseKeywords; - return true; -} - -QList HelpIndexFilter::matchesFor(QFutureInterface &future, const QString &entry) -{ - if (m_needsUpdate.exchange(false)) { - QStringList indices; - QMetaObject::invokeMethod(this, [this] { return allIndices(); }, - Qt::BlockingQueuedConnection, &indices); - m_allIndicesCache = indices; - // force updating the cache taking the m_allIndicesCache - m_lastIndicesCache = QStringList(); - m_lastEntry = QString(); - } - - const QStringList cacheBase = m_lastEntry.isEmpty() || !entry.contains(m_lastEntry) - ? m_allIndicesCache : m_lastIndicesCache; - - if (!updateCache(future, cacheBase, entry)) - return QList(); - - const Qt::CaseSensitivity cs = caseSensitivity(entry); - QList entries; - for (const QString &keyword : std::as_const(m_lastIndicesCache)) { - const int index = keyword.indexOf(entry, 0, cs); - LocatorFilterEntry filterEntry(this, keyword, {}, m_icon); - filterEntry.highlightInfo = {index, int(entry.length())}; + LocatorFilterEntries entries; + for (const QString &key : lastIndicesCache) { + if (promise.isCanceled()) + return; + const int index = key.indexOf(input, 0, cs); + LocatorFilterEntry filterEntry; + filterEntry.displayName = key; + filterEntry.acceptor = [key] { + HelpPlugin::showLinksInCurrentViewer(LocalHelpManager::linksForKeyword(key), key); + return AcceptResult(); + }; + filterEntry.displayIcon = icon; + filterEntry.highlightInfo = {index, int(input.length())}; entries.append(filterEntry); } - - return entries; + storage.reportOutput(entries); + promise.addResult(lastIndicesCache); } -void HelpIndexFilter::accept(const LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const +LocatorMatcherTasks HelpIndexFilter::matchers() { - Q_UNUSED(newText) - Q_UNUSED(selectionStart) - Q_UNUSED(selectionLength) - const QString &key = selection.displayName; - const QMultiMap links = LocalHelpManager::linksForKeyword(key); - emit linksActivated(links, key); -} + TreeStorage storage; -void HelpIndexFilter::refresh(QFutureInterface &future) -{ - Q_UNUSED(future) - invalidateCache(); -} + const auto onSetup = [this, storage](Async &async) { + if (m_needsUpdate) { + m_needsUpdate = false; + LocalHelpManager::setupGuiHelpEngine(); + m_allIndicesCache = LocalHelpManager::filterEngine()->indices({}); + m_lastIndicesCache.clear(); + m_lastEntry.clear(); + } + const QStringList cache = m_lastEntry.isEmpty() || !storage->input().contains(m_lastEntry) + ? m_allIndicesCache : m_lastIndicesCache; + async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer()); + async.setConcurrentCallData(matches, *storage, cache, m_icon); + }; + const auto onDone = [this, storage](const Async &async) { + if (async.isResultAvailable()) { + m_lastIndicesCache = async.result(); + m_lastEntry = storage->input(); + } + }; -QStringList HelpIndexFilter::allIndices() const -{ - LocalHelpManager::setupGuiHelpEngine(); - return LocalHelpManager::filterEngine()->indices(QString()); + return {{AsyncTask(onSetup, onDone), storage}}; } void HelpIndexFilter::invalidateCache() diff --git a/src/plugins/help/helpindexfilter.h b/src/plugins/help/helpindexfilter.h index 1dfc6f59952..e980f49ab0c 100644 --- a/src/plugins/help/helpindexfilter.h +++ b/src/plugins/help/helpindexfilter.h @@ -5,48 +5,22 @@ #include -#include -#include -#include -#include - -#include - -namespace Help { -namespace Internal { +namespace Help::Internal { class HelpIndexFilter final : public Core::ILocatorFilter { - Q_OBJECT - public: HelpIndexFilter(); - ~HelpIndexFilter() final; - - // ILocatorFilter - QList matchesFor(QFutureInterface &future, - const QString &entry) override; - void accept(const Core::LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const override; - void refresh(QFutureInterface &future) override; - - QStringList allIndices() const; - -signals: - void linksActivated(const QMultiMap &links, const QString &key) const; private: + Core::LocatorMatcherTasks matchers() final; void invalidateCache(); - bool updateCache(QFutureInterface &future, - const QStringList &cache, const QString &entry); - QStringList m_allIndicesCache; QStringList m_lastIndicesCache; QString m_lastEntry; - std::atomic_bool m_needsUpdate = true; + bool m_needsUpdate = true; QIcon m_icon; }; -} // namespace Internal -} // namespace Help +} // namespace Help::Internal diff --git a/src/plugins/help/helpmanager.cpp b/src/plugins/help/helpmanager.cpp index c3e0bd31512..3c053662d54 100644 --- a/src/plugins/help/helpmanager.cpp +++ b/src/plugins/help/helpmanager.cpp @@ -7,16 +7,18 @@ #include #include + #include +#include #include #include -#include #include #include #include #include #include +#include #include #include @@ -99,7 +101,7 @@ void HelpManager::registerDocumentation(const QStringList &files) return; } - QFuture future = Utils::runAsync(&HelpManager::registerDocumentationNow, files); + QFuture future = Utils::asyncRun(&HelpManager::registerDocumentationNow, files); Utils::onResultReady(future, this, [](bool docsChanged){ if (docsChanged) { d->m_helpEngine->setupData(); @@ -122,13 +124,12 @@ void HelpManager::unregisterDocumentation(const QStringList &fileNames) unregisterNamespaces(getNamespaces(fileNames)); } -void HelpManager::registerDocumentationNow(QFutureInterface &futureInterface, - const QStringList &files) +void HelpManager::registerDocumentationNow(QPromise &promise, const QStringList &files) { QMutexLocker locker(&d->m_helpengineMutex); - futureInterface.setProgressRange(0, files.count()); - futureInterface.setProgressValue(0); + promise.setProgressRange(0, files.count()); + promise.setProgressValue(0); QHelpEngineCore helpEngine(collectionFilePath()); helpEngine.setReadOnly(false); @@ -136,9 +137,9 @@ void HelpManager::registerDocumentationNow(QFutureInterface &futureInterfa bool docsChanged = false; QStringList nameSpaces = helpEngine.registeredDocumentations(); for (const QString &file : files) { - if (futureInterface.isCanceled()) + if (promise.isCanceled()) break; - futureInterface.setProgressValue(futureInterface.progressValue() + 1); + promise.setProgressValue(promise.future().progressValue() + 1); const QString &nameSpace = QHelpEngineCore::namespaceName(file); if (nameSpace.isEmpty()) continue; @@ -152,7 +153,7 @@ void HelpManager::registerDocumentationNow(QFutureInterface &futureInterfa } } } - futureInterface.reportResult(docsChanged); + promise.addResult(docsChanged); } void HelpManager::unregisterNamespaces(const QStringList &nameSpaces) diff --git a/src/plugins/help/helpmanager.h b/src/plugins/help/helpmanager.h index d868e96256e..739d5a68767 100644 --- a/src/plugins/help/helpmanager.h +++ b/src/plugins/help/helpmanager.h @@ -5,12 +5,15 @@ #include -QT_FORWARD_DECLARE_CLASS(QUrl) - -#include #include #include +QT_BEGIN_NAMESPACE +template +class QPromise; +class QUrl; +QT_END_NAMESPACE + namespace Help { namespace Internal { @@ -55,10 +58,9 @@ public: const QUrl &url, Core::HelpManager::HelpViewerLocation location = Core::HelpManager::HelpModeAlways) override; - static void setupHelpManager(); - static void registerDocumentationNow(QFutureInterface &futureInterface, - const QStringList &fileNames); + static void registerDocumentationNow(QPromise &promise, const QStringList &fileNames); + signals: void collectionFileChanged(); void helpRequested(const QUrl &url, Core::HelpManager::HelpViewerLocation location); diff --git a/src/plugins/help/helpmode.cpp b/src/plugins/help/helpmode.cpp deleted file mode 100644 index 2e2ee468939..00000000000 --- a/src/plugins/help/helpmode.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "helpmode.h" -#include "helpconstants.h" -#include "helpicons.h" -#include "helptr.h" - -#include - -using namespace Help; -using namespace Help::Internal; - -HelpMode::HelpMode(QObject *parent) - : Core::IMode(parent) -{ - setObjectName("HelpMode"); - setContext(Core::Context(Constants::C_MODE_HELP)); - setIcon(Utils::Icon::modeIcon(Icons::MODE_HELP_CLASSIC, - Icons::MODE_HELP_FLAT, Icons::MODE_HELP_FLAT_ACTIVE)); - setDisplayName(Tr::tr("Help")); - setPriority(Constants::P_MODE_HELP); - setId(Constants::ID_MODE_HELP); -} diff --git a/src/plugins/help/helpmode.h b/src/plugins/help/helpmode.h deleted file mode 100644 index fbe69f08bca..00000000000 --- a/src/plugins/help/helpmode.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -#include -#include - -namespace Help { -namespace Internal { - -class HelpMode : public Core::IMode -{ -public: - explicit HelpMode(QObject *parent = nullptr); -}; - -} // namespace Internal -} // namespace Help diff --git a/src/plugins/help/helpplugin.cpp b/src/plugins/help/helpplugin.cpp index 56126372476..7329b3dad67 100644 --- a/src/plugins/help/helpplugin.cpp +++ b/src/plugins/help/helpplugin.cpp @@ -4,7 +4,6 @@ #include "helpplugin.h" #include "bookmarkmanager.h" -#include "contentwindow.h" #include "docsettingspage.h" #include "filtersettingspage.h" #include "generalsettingspage.h" @@ -13,11 +12,9 @@ #include "helpicons.h" #include "helpindexfilter.h" #include "helpmanager.h" -#include "helpmode.h" #include "helptr.h" #include "helpviewer.h" #include "helpwidget.h" -#include "indexwindow.h" #include "localhelpmanager.h" #include "openpagesmanager.h" #include "searchtaskhandler.h" @@ -35,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -52,45 +50,54 @@ #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - #include +#include #include #include -#include -#include -#include - +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -static const char kExternalWindowStateKey[] = "Help/ExternalWindowState"; -static const char kToolTipHelpContext[] = "Help.ToolTip"; - using namespace Core; using namespace Utils; -namespace Help { -namespace Internal { +namespace Help::Internal { + +const char kExternalWindowStateKey[] = "Help/ExternalWindowState"; +const char kToolTipHelpContext[] = "Help.ToolTip"; + +class HelpMode : public IMode +{ +public: + HelpMode() + { + setObjectName("HelpMode"); + setContext(Core::Context(Constants::C_MODE_HELP)); + setIcon(Icon::modeIcon(Icons::MODE_HELP_CLASSIC, + Icons::MODE_HELP_FLAT, Icons::MODE_HELP_FLAT_ACTIVE)); + setDisplayName(Tr::tr("Help")); + setPriority(Constants::P_MODE_HELP); + setId(Constants::ID_MODE_HELP); + } +}; class HelpPluginPrivate : public QObject { public: HelpPluginPrivate(); - void modeChanged(Utils::Id mode, Utils::Id old); + void modeChanged(Id mode, Id old); void requestContextHelp(); void showContextHelp(const HelpItem &contextHelp); @@ -127,7 +134,7 @@ public: QRect m_externalWindowState; DocSettingsPage m_docSettingsPage; - FilterSettingsPage m_filterSettingsPage; + FilterSettingsPage m_filterSettingsPage{[this] {setupHelpEngineIfNeeded(); }}; SearchTaskHandler m_searchTaskHandler; GeneralSettingsPage m_generalSettingsPage; @@ -158,6 +165,11 @@ void HelpPlugin::showHelpUrl(const QUrl &url, Core::HelpManager::HelpViewerLocat dd->showHelpUrl(url, location); } +void HelpPlugin::showLinksInCurrentViewer(const QMultiMap &links, const QString &key) +{ + dd->showLinksInCurrentViewer(links, key); +} + void HelpPlugin::initialize() { dd = new HelpPluginPrivate; @@ -185,8 +197,6 @@ HelpPluginPrivate::HelpPluginPrivate() connect(&m_searchTaskHandler, &SearchTaskHandler::search, this, &QDesktopServices::openUrl); - connect(&m_filterSettingsPage, &FilterSettingsPage::filtersChanged, - this, &HelpPluginPrivate::setupHelpEngineIfNeeded); connect(Core::HelpManager::Signals::instance(), &Core::HelpManager::Signals::documentationChanged, this, @@ -258,9 +268,6 @@ HelpPluginPrivate::HelpPluginPrivate() ActionManager::actionContainer(Core::Constants::M_HELP)->addAction(cmd, Core::Constants::G_HELP_SUPPORT); connect(action, &QAction::triggered, this, &HelpPluginPrivate::slotSystemInformation); - connect(&helpIndexFilter, &HelpIndexFilter::linksActivated, - this, &HelpPluginPrivate::showLinksInCurrentViewer); - connect(ModeManager::instance(), &ModeManager::currentModeChanged, this, &HelpPluginPrivate::modeChanged); @@ -394,7 +401,7 @@ void HelpPluginPrivate::showLinksInCurrentViewer(const QMultiMap widget->showLinks(links, key); } -void HelpPluginPrivate::modeChanged(Utils::Id mode, Utils::Id old) +void HelpPluginPrivate::modeChanged(Id mode, Id old) { Q_UNUSED(old) if (mode == m_mode.id()) { @@ -494,7 +501,7 @@ HelpViewer *HelpPluginPrivate::viewerForContextHelp() void HelpPluginPrivate::requestContextHelp() { // Find out what to show - const QVariant tipHelpValue = Utils::ToolTip::contextHelp(); + const QVariant tipHelpValue = ToolTip::contextHelp(); const HelpItem tipHelp = tipHelpValue.canConvert() ? tipHelpValue.value() : HelpItem(tipHelpValue.toString()); @@ -641,5 +648,4 @@ void HelpPluginPrivate::doSetupIfNeeded() } } -} // Internal -} // Help +} // Help::Internal diff --git a/src/plugins/help/helpplugin.h b/src/plugins/help/helpplugin.h index d3a3e0b9801..96d14a152b8 100644 --- a/src/plugins/help/helpplugin.h +++ b/src/plugins/help/helpplugin.h @@ -26,6 +26,8 @@ public: ~HelpPlugin() final; static void showHelpUrl(const QUrl &url, Core::HelpManager::HelpViewerLocation location); + static void showLinksInCurrentViewer(const QMultiMap &links, + const QString &key); static HelpViewer *createHelpViewer(); static HelpWidget *modeHelpWidget(); diff --git a/src/plugins/help/helpwidget.cpp b/src/plugins/help/helpwidget.cpp index 571964d0b3e..67b42232c90 100644 --- a/src/plugins/help/helpwidget.cpp +++ b/src/plugins/help/helpwidget.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -319,7 +320,7 @@ HelpWidget::HelpWidget(const Core::Context &context, WidgetStyle style, QWidget cmd = Core::ActionManager::registerAction(helpTargetAction, "Help.OpenContextHelpHere", context); QToolButton *helpTargetButton = Core::Command::toolButtonWithAppendedShortcut(helpTargetAction, cmd); - helpTargetButton->setProperty("noArrow", true); + helpTargetButton->setProperty(Utils::StyleHelper::C_NO_ARROW, true); helpTargetButton->setPopupMode(QToolButton::DelayedPopup); helpTargetButton->setMenu(createHelpTargetMenu(helpTargetButton)); connect(LocalHelpManager::instance(), @@ -416,7 +417,7 @@ HelpWidget::HelpWidget(const Core::Context &context, WidgetStyle style, QWidget auto openButton = new QToolButton; openButton->setIcon(Utils::Icons::SPLIT_HORIZONTAL_TOOLBAR.icon()); openButton->setPopupMode(QToolButton::InstantPopup); - openButton->setProperty("noArrow", true); + openButton->setProperty(Utils::StyleHelper::C_NO_ARROW, true); layout->addWidget(openButton); auto openMenu = new QMenu(openButton); openButton->setMenu(openMenu); diff --git a/src/plugins/imageviewer/CMakeLists.txt b/src/plugins/imageviewer/CMakeLists.txt index b55ef9931e6..38659e8417a 100644 --- a/src/plugins/imageviewer/CMakeLists.txt +++ b/src/plugins/imageviewer/CMakeLists.txt @@ -1,4 +1,4 @@ -find_package(Qt5 COMPONENTS SvgWidgets QUIET) +find_package(Qt6 COMPONENTS SvgWidgets QUIET) if (TARGET Qt::SvgWidgets) set(SVG_WIDGETS Qt::SvgWidgets) endif() diff --git a/src/plugins/imageviewer/imageview.cpp b/src/plugins/imageviewer/imageview.cpp index 34b74d49086..9a2af284f49 100644 --- a/src/plugins/imageviewer/imageview.cpp +++ b/src/plugins/imageviewer/imageview.cpp @@ -8,11 +8,11 @@ #include "imageviewerfile.h" #include "imageviewertr.h" #include "multiexportdialog.h" -#include "utils/mimeutils.h" #include #include +#include #include #include diff --git a/src/plugins/imageviewer/imageviewer.cpp b/src/plugins/imageviewer/imageviewer.cpp index 9c19b0ffbf8..5d4a068e717 100644 --- a/src/plugins/imageviewer/imageviewer.cpp +++ b/src/plugins/imageviewer/imageviewer.cpp @@ -17,8 +17,9 @@ #include #include -#include #include +#include +#include #include #include @@ -112,7 +113,7 @@ void ImageViewer::ctor() d->shareButton->setToolTip(Tr::tr("Export")); d->shareButton->setPopupMode(QToolButton::InstantPopup); d->shareButton->setIcon(Icons::EXPORTFILE_TOOLBAR.icon()); - d->shareButton->setProperty("noArrow", true); + d->shareButton->setProperty(StyleHelper::C_NO_ARROW, true); auto shareMenu = new QMenu(d->shareButton); shareMenu->addAction(d->actionExportImage); shareMenu->addAction(d->actionMultiExportImages); diff --git a/src/plugins/incredibuild/CMakeLists.txt b/src/plugins/incredibuild/CMakeLists.txt index 81e0273b95d..f212046a15c 100644 --- a/src/plugins/incredibuild/CMakeLists.txt +++ b/src/plugins/incredibuild/CMakeLists.txt @@ -13,10 +13,9 @@ add_qtc_plugin(IncrediBuild ibconsolebuildstep.cpp ibconsolebuildstep.h incredibuild_global.h - incredibuildtr.h incredibuildconstants.h incredibuildplugin.cpp - incredibuildplugin.h + incredibuildtr.h makecommandbuilder.cpp makecommandbuilder.h ) diff --git a/src/plugins/incredibuild/buildconsolebuildstep.cpp b/src/plugins/incredibuild/buildconsolebuildstep.cpp index 1238b2c2436..c28c1cd6769 100644 --- a/src/plugins/incredibuild/buildconsolebuildstep.cpp +++ b/src/plugins/incredibuild/buildconsolebuildstep.cpp @@ -76,10 +76,9 @@ BuildConsoleBuildStep::BuildConsoleBuildStep(BuildStepList *buildStepList, Id id addAspect("" + Tr::tr("IncrediBuild Distribution Control")); - auto profileXml = addAspect(); + auto profileXml = addAspect(); profileXml->setSettingsKey("IncrediBuild.BuildConsole.ProfileXml"); profileXml->setLabelText(Tr::tr("Profile.xml:")); - profileXml->setDisplayStyle(StringAspect::PathChooserDisplay); profileXml->setExpectedKind(PathChooser::Kind::File); profileXml->setBaseFileName(PathChooser::homePath()); profileXml->setHistoryCompleter("IncrediBuild.BuildConsole.ProfileXml.History"); @@ -138,10 +137,9 @@ BuildConsoleBuildStep::BuildConsoleBuildStep(BuildStepList *buildStepList, Id id "beginning of the build output text. This title will also be used " "for the Build History and Build Monitor displays.")); - auto monFile = addAspect(); + auto monFile = addAspect(); monFile->setSettingsKey("IncrediBuild.BuildConsole.MonFile"); monFile->setLabelText(Tr::tr("Save IncrediBuild monitor file:")); - monFile->setDisplayStyle(StringAspect::PathChooserDisplay); monFile->setExpectedKind(PathChooser::Kind::Any); monFile->setBaseFileName(PathChooser::homePath()); monFile->setHistoryCompleter(QLatin1String("IncrediBuild.BuildConsole.MonFile.History")); @@ -155,10 +153,9 @@ BuildConsoleBuildStep::BuildConsoleBuildStep(BuildStepList *buildStepList, Id id suppressStdOut->setLabel(Tr::tr("Suppress STDOUT:")); suppressStdOut->setToolTip(Tr::tr("Does not write anything to the standard output.")); - auto logFile = addAspect(); + auto logFile = addAspect(); logFile->setSettingsKey("IncrediBuild.BuildConsole.LogFile"); logFile->setLabelText(Tr::tr("Output Log file:")); - logFile->setDisplayStyle(StringAspect::PathChooserDisplay); logFile->setExpectedKind(PathChooser::Kind::SaveFile); logFile->setBaseFileName(PathChooser::homePath()); logFile->setHistoryCompleter(QLatin1String("IncrediBuild.BuildConsole.LogFile.History")); diff --git a/src/plugins/incredibuild/cmakecommandbuilder.cpp b/src/plugins/incredibuild/cmakecommandbuilder.cpp index 8732601cbc4..2f0dd751ae9 100644 --- a/src/plugins/incredibuild/cmakecommandbuilder.cpp +++ b/src/plugins/incredibuild/cmakecommandbuilder.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include // Compile-time only diff --git a/src/plugins/incredibuild/commandbuilderaspect.cpp b/src/plugins/incredibuild/commandbuilderaspect.cpp index 6a16cabc6d7..5cfbac953b1 100644 --- a/src/plugins/incredibuild/commandbuilderaspect.cpp +++ b/src/plugins/incredibuild/commandbuilderaspect.cpp @@ -110,7 +110,7 @@ void CommandBuilderAspectPrivate::tryToMigrate() } } -void CommandBuilderAspect::addToLayout(Layouting::LayoutBuilder &builder) +void CommandBuilderAspect::addToLayout(Layouting::LayoutItem &parent) { if (!d->commandBuilder) { d->commandBuilder = new QComboBox; @@ -151,9 +151,9 @@ void CommandBuilderAspect::addToLayout(Layouting::LayoutBuilder &builder) if (!d->m_loadedFromMap) d->tryToMigrate(); - builder.addRow({d->label.data(), d->commandBuilder.data()}); - builder.addRow({Tr::tr("Make command:"), d->makePathChooser.data()}); - builder.addRow({Tr::tr("Make arguments:"), d->makeArgumentsLineEdit.data()}); + parent.addRow({d->label.data(), d->commandBuilder.data()}); + parent.addRow({Tr::tr("Make command:"), d->makePathChooser.data()}); + parent.addRow({Tr::tr("Make arguments:"), d->makeArgumentsLineEdit.data()}); updateGui(); } diff --git a/src/plugins/incredibuild/commandbuilderaspect.h b/src/plugins/incredibuild/commandbuilderaspect.h index 7c6d04c93b6..6033e9036e5 100644 --- a/src/plugins/incredibuild/commandbuilderaspect.h +++ b/src/plugins/incredibuild/commandbuilderaspect.h @@ -23,7 +23,7 @@ public: QString fullCommandFlag(bool keepJobNum) const; private: - void addToLayout(Utils::Layouting::LayoutBuilder &builder) final; + void addToLayout(Layouting::LayoutItem &parent) final; void fromMap(const QVariantMap &map) final; void toMap(QVariantMap &map) const final; diff --git a/src/plugins/incredibuild/incredibuild.qbs b/src/plugins/incredibuild/incredibuild.qbs index e08d548691f..9c315ae8c08 100644 --- a/src/plugins/incredibuild/incredibuild.qbs +++ b/src/plugins/incredibuild/incredibuild.qbs @@ -19,10 +19,10 @@ QtcPlugin { "commandbuilderaspect.h", "ibconsolebuildstep.cpp", "ibconsolebuildstep.h", - "incredibuild_global.h", "incredibuildtr.h", + "incredibuild_global.h", "incredibuildconstants.h", "incredibuildplugin.cpp", - "incredibuildplugin.h", + "incredibuildtr.h", "makecommandbuilder.cpp", "makecommandbuilder.h", ] diff --git a/src/plugins/incredibuild/incredibuildplugin.cpp b/src/plugins/incredibuild/incredibuildplugin.cpp index 6e7ea8a4bc1..4e8f572812f 100644 --- a/src/plugins/incredibuild/incredibuildplugin.cpp +++ b/src/plugins/incredibuild/incredibuildplugin.cpp @@ -1,28 +1,26 @@ // Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "incredibuildplugin.h" - #include "buildconsolebuildstep.h" #include "ibconsolebuildstep.h" +#include + namespace IncrediBuild::Internal { -class IncrediBuildPluginPrivate +class IncrediBuildPlugin final : public ExtensionSystem::IPlugin { + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "IncrediBuild.json") + public: - BuildConsoleStepFactory buildConsoleStepFactory; - IBConsoleStepFactory iBConsoleStepFactory; + IncrediBuildPlugin() + { + addManaged(); + addManaged(); + } }; -IncrediBuildPlugin::~IncrediBuildPlugin() -{ - delete d; -} - -void IncrediBuildPlugin::initialize() -{ - d = new IncrediBuildPluginPrivate; -} - } // IncrediBuild::Internal + +#include "incredibuildplugin.moc" diff --git a/src/plugins/incredibuild/incredibuildplugin.h b/src/plugins/incredibuild/incredibuildplugin.h deleted file mode 100644 index 994d65bb777..00000000000 --- a/src/plugins/incredibuild/incredibuildplugin.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -namespace IncrediBuild::Internal { - -class IncrediBuildPlugin final : public ExtensionSystem::IPlugin -{ - Q_OBJECT - Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "IncrediBuild.json") - -public: - IncrediBuildPlugin() = default; - ~IncrediBuildPlugin() final; - - void initialize() final; - -private: - class IncrediBuildPluginPrivate *d = nullptr; -}; - -} // IncrediBuild::Internal diff --git a/src/plugins/insight/insightmodel.cpp b/src/plugins/insight/insightmodel.cpp index 4fe987a5977..b80249d2124 100644 --- a/src/plugins/insight/insightmodel.cpp +++ b/src/plugins/insight/insightmodel.cpp @@ -12,8 +12,8 @@ #include #include +#include #include -#include #include #include @@ -211,8 +211,8 @@ InsightModel::InsightModel(InsightView *view, ExternalDependenciesInterface &ext , m_externalDependencies(externalDependencies) , m_fileSystemWatcher(new Utils::FileSystemWatcher(this)) { - QObject::connect(ProjectExplorer::SessionManager::instance(), - &ProjectExplorer::SessionManager::startupProjectChanged, + QObject::connect(ProjectExplorer::ProjectManager::instance(), + &ProjectExplorer::ProjectManager::startupProjectChanged, this, [&](ProjectExplorer::Project *project) { if (project) diff --git a/src/plugins/ios/createsimulatordialog.cpp b/src/plugins/ios/createsimulatordialog.cpp index 784c33742ff..aff8a673dfc 100644 --- a/src/plugins/ios/createsimulatordialog.cpp +++ b/src/plugins/ios/createsimulatordialog.cpp @@ -7,8 +7,8 @@ #include "simulatorcontrol.h" #include +#include #include -#include #include #include @@ -32,7 +32,7 @@ CreateSimulatorDialog::CreateSimulatorDialog(QWidget *parent) auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - using namespace Utils::Layouting; + using namespace Layouting; Column { Form { @@ -60,11 +60,10 @@ CreateSimulatorDialog::CreateSimulatorDialog(QWidget *parent) enableOk(); }); - m_futureSync.setCancelOnWait(true); - m_futureSync.addFuture(Utils::onResultReady(SimulatorControl::updateDeviceTypes(), this, + m_futureSync.addFuture(Utils::onResultReady(SimulatorControl::updateDeviceTypes(this), this, &CreateSimulatorDialog::populateDeviceTypes)); - QFuture> runtimesfuture = SimulatorControl::updateRuntimes(); + QFuture> runtimesfuture = SimulatorControl::updateRuntimes(this); Utils::onResultReady(runtimesfuture, this, [this](const QList &runtimes) { m_runtimes = runtimes; }); diff --git a/src/plugins/ios/iosbuildstep.cpp b/src/plugins/ios/iosbuildstep.cpp index eb6664c155c..7831d84e239 100644 --- a/src/plugins/ios/iosbuildstep.cpp +++ b/src/plugins/ios/iosbuildstep.cpp @@ -20,8 +20,8 @@ #include #include +#include #include -#include #include #include @@ -101,7 +101,8 @@ QWidget *IosBuildStep::createConfigWidget() updateDetails(); connect(buildArgumentsTextEdit, &QPlainTextEdit::textChanged, this, [=] { - setBaseArguments(ProcessArgs::splitArgs(buildArgumentsTextEdit->toPlainText())); + setBaseArguments(ProcessArgs::splitArgs(buildArgumentsTextEdit->toPlainText(), + HostOsInfo::hostOs())); resetDefaultsButton->setEnabled(!m_useDefaultArguments); updateDetails(); }); @@ -113,7 +114,8 @@ QWidget *IosBuildStep::createConfigWidget() }); connect(extraArgumentsLineEdit, &QLineEdit::editingFinished, this, [=] { - setExtraArguments(ProcessArgs::splitArgs(extraArgumentsLineEdit->text())); + setExtraArguments(ProcessArgs::splitArgs(extraArgumentsLineEdit->text(), + HostOsInfo::hostOs())); }); connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::settingsChanged, diff --git a/src/plugins/ios/iosconfigurations.cpp b/src/plugins/ios/iosconfigurations.cpp index 54674de8ecb..04e4363a2a1 100644 --- a/src/plugins/ios/iosconfigurations.cpp +++ b/src/plugins/ios/iosconfigurations.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -30,8 +31,8 @@ #include #include +#include #include -#include #include #include @@ -211,7 +212,7 @@ static QByteArray decodeProvisioningProfile(const QString &path) { QTC_ASSERT(!path.isEmpty(), return QByteArray()); - QtcProcess p; + Process p; p.setTimeoutS(3); // path is assumed to be valid file path to .mobileprovision p.setCommand({"openssl", {"smime", "-inform", "der", "-verify", "-in", path}}); @@ -404,7 +405,7 @@ void IosConfigurations::updateSimulators() dev = IDevice::ConstPtr(new IosSimulator(devId)); devManager->addDevice(dev); } - SimulatorControl::updateAvailableSimulators(); + SimulatorControl::updateAvailableSimulators(this); } void IosConfigurations::setDeveloperPath(const FilePath &devPath) @@ -573,7 +574,7 @@ IosToolChainFactory::IosToolChainFactory() Toolchains IosToolChainFactory::autoDetect(const ToolchainDetector &detector) const { - if (detector.device) + if (detector.device->type() != ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE) return {}; QList existingClangToolChains = clangToolChains(detector.alreadyKnown); diff --git a/src/plugins/ios/iosdevice.cpp b/src/plugins/ios/iosdevice.cpp index 49a07a2b101..326092fccc3 100644 --- a/src/plugins/ios/iosdevice.cpp +++ b/src/plugins/ios/iosdevice.cpp @@ -185,12 +185,6 @@ Utils::Port IosDevice::nextPort() const return Utils::Port(m_lastPort); } -bool IosDevice::canAutoDetectPorts() const -{ - return true; -} - - // IosDeviceManager IosDeviceManager::TranslationMap IosDeviceManager::translationMap() diff --git a/src/plugins/ios/iosdevice.h b/src/plugins/ios/iosdevice.h index 9f4e66c5025..ada952037d9 100644 --- a/src/plugins/ios/iosdevice.h +++ b/src/plugins/ios/iosdevice.h @@ -36,7 +36,6 @@ public: QString osVersion() const; QString cpuArchitecture() const; Utils::Port nextPort() const; - bool canAutoDetectPorts() const override; static QString name(); diff --git a/src/plugins/ios/iosdsymbuildstep.cpp b/src/plugins/ios/iosdsymbuildstep.cpp index 54481ded9b7..2d0e2b841f0 100644 --- a/src/plugins/ios/iosdsymbuildstep.cpp +++ b/src/plugins/ios/iosdsymbuildstep.cpp @@ -9,21 +9,22 @@ #include "iostr.h" #include -#include -#include + +#include #include #include #include +#include #include -#include #include +#include #include #include -#include +#include #include -#include +#include #include #include @@ -242,7 +243,8 @@ QWidget *IosDsymBuildStep::createConfigWidget() connect(argumentsTextEdit, &QPlainTextEdit::textChanged, this, [this, argumentsTextEdit, resetDefaultsButton, updateDetails] { - setArguments(Utils::ProcessArgs::splitArgs(argumentsTextEdit->toPlainText())); + setArguments(ProcessArgs::splitArgs(argumentsTextEdit->toPlainText(), + HostOsInfo::hostOs())); resetDefaultsButton->setEnabled(!isDefault()); updateDetails(); }); diff --git a/src/plugins/ios/iosprobe.cpp b/src/plugins/ios/iosprobe.cpp index c835e48390e..e6fca977b25 100644 --- a/src/plugins/ios/iosprobe.cpp +++ b/src/plugins/ios/iosprobe.cpp @@ -4,7 +4,7 @@ #include "iosprobe.h" #include -#include +#include #include #include @@ -40,7 +40,7 @@ void XcodeProbe::addDeveloperPath(const QString &path) void XcodeProbe::detectDeveloperPaths() { - Utils::QtcProcess selectedXcode; + Utils::Process selectedXcode; selectedXcode.setTimeoutS(5); selectedXcode.setCommand({"/usr/bin/xcode-select", {"--print-path"}}); selectedXcode.runBlocking(); diff --git a/src/plugins/ios/iosrunconfiguration.cpp b/src/plugins/ios/iosrunconfiguration.cpp index 9a9acedb29e..8a0a3e62dcf 100644 --- a/src/plugins/ios/iosrunconfiguration.cpp +++ b/src/plugins/ios/iosrunconfiguration.cpp @@ -21,9 +21,9 @@ #include #include -#include -#include #include +#include +#include #include #include @@ -324,14 +324,14 @@ IosDeviceTypeAspect::IosDeviceTypeAspect(IosRunConfiguration *runConfiguration) this, &IosDeviceTypeAspect::deviceChanges); } -void IosDeviceTypeAspect::addToLayout(Layouting::LayoutBuilder &builder) +void IosDeviceTypeAspect::addToLayout(Layouting::LayoutItem &parent) { m_deviceTypeComboBox = new QComboBox; m_deviceTypeComboBox->setModel(&m_deviceTypeModel); m_deviceTypeLabel = new QLabel(Tr::tr("Device type:")); - builder.addItems({m_deviceTypeLabel, m_deviceTypeComboBox}); + parent.addItems({m_deviceTypeLabel, m_deviceTypeComboBox}); updateValues(); diff --git a/src/plugins/ios/iosrunconfiguration.h b/src/plugins/ios/iosrunconfiguration.h index 161b0703c54..1ebe79efa56 100644 --- a/src/plugins/ios/iosrunconfiguration.h +++ b/src/plugins/ios/iosrunconfiguration.h @@ -27,7 +27,7 @@ public: void fromMap(const QVariantMap &map) override; void toMap(QVariantMap &map) const override; - void addToLayout(Utils::Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; IosDeviceType deviceType() const; void setDeviceType(const IosDeviceType &deviceType); diff --git a/src/plugins/ios/iosrunner.cpp b/src/plugins/ios/iosrunner.cpp index c01c3c859dc..828bb65c007 100644 --- a/src/plugins/ios/iosrunner.cpp +++ b/src/plugins/ios/iosrunner.cpp @@ -27,7 +27,7 @@ #include #include -#include +#include #include #include diff --git a/src/plugins/ios/iossettingswidget.cpp b/src/plugins/ios/iossettingswidget.cpp index 8c4149a91ba..bb0bc36d61a 100644 --- a/src/plugins/ios/iossettingswidget.cpp +++ b/src/plugins/ios/iossettingswidget.cpp @@ -12,9 +12,9 @@ #include "simulatoroperationdialog.h" #include +#include #include #include -#include #include #include @@ -51,7 +51,6 @@ static void onSimOperation(const SimulatorInfo &simInfo, SimulatorOperationDialo IosSettingsWidget::IosSettingsWidget() { - resize(622, 456); setWindowTitle(Tr::tr("iOS Configuration")); m_deviceAskCheckBox = new QCheckBox(Tr::tr("Ask about devices not in developer mode")); @@ -94,7 +93,7 @@ IosSettingsWidget::IosSettingsWidget() m_pathWidget->addButton(Tr::tr("Screenshot"), this, std::bind(&IosSettingsWidget::onScreenshot, this)); - using namespace Utils::Layouting; + using namespace Layouting; Column { Group { title(Tr::tr("Devices")), @@ -175,7 +174,7 @@ void IosSettingsWidget::onStart() Utils::StdErrFormat); } else { futureList << QFuture(Utils::onResultReady( - SimulatorControl::startSimulator(info.identifier), + SimulatorControl::startSimulator(info.identifier), this, std::bind(onSimOperation, info, statusDialog, Tr::tr("simulator start"), _1))); } } @@ -207,11 +206,9 @@ void IosSettingsWidget::onCreate() CreateSimulatorDialog createDialog(this); if (createDialog.exec() == QDialog::Accepted) { - QFuture f = QFuture( - Utils::onResultReady(SimulatorControl::createSimulator(createDialog.name(), - createDialog.deviceType(), - createDialog.runtime()), - std::bind(onSimulatorCreate, createDialog.name(), _1))); + QFuture f = QFuture(Utils::onResultReady(SimulatorControl::createSimulator( + createDialog.name(), createDialog.deviceType(), createDialog.runtime()), + this, std::bind(onSimulatorCreate, createDialog.name(), _1))); statusDialog->addFutures({ f }); statusDialog->exec(); // Modal dialog returns only when all the operations are done or cancelled. } @@ -242,7 +239,7 @@ void IosSettingsWidget::onReset() QList> futureList; for (const SimulatorInfo &info : simulatorInfoList) { futureList << QFuture(Utils::onResultReady( - SimulatorControl::resetSimulator(info.identifier), + SimulatorControl::resetSimulator(info.identifier), this, std::bind(onSimOperation, info, statusDialog, Tr::tr("simulator reset"), _1))); } @@ -270,7 +267,7 @@ void IosSettingsWidget::onRename() statusDialog->setAttribute(Qt::WA_DeleteOnClose); statusDialog->addMessage(Tr::tr("Renaming simulator device..."), Utils::NormalMessageFormat); QFuture f = QFuture(Utils::onResultReady( - SimulatorControl::renameSimulator(simInfo.identifier, newName), + SimulatorControl::renameSimulator(simInfo.identifier, newName), this, std::bind(onSimOperation, simInfo, statusDialog, Tr::tr("simulator rename"), _1))); statusDialog->addFutures({f}); statusDialog->exec(); // Modal dialog returns only when all the operations are done or cancelled. @@ -300,7 +297,7 @@ void IosSettingsWidget::onDelete() QList> futureList; for (const SimulatorInfo &info : simulatorInfoList) { futureList << QFuture(Utils::onResultReady( - SimulatorControl::deleteSimulator(info.identifier), + SimulatorControl::deleteSimulator(info.identifier), this, std::bind(onSimOperation, info, statusDialog, Tr::tr("simulator delete"), _1))); } @@ -331,7 +328,7 @@ void IosSettingsWidget::onScreenshot() QList> futureList; for (const SimulatorInfo &info : simulatorInfoList) { futureList << QFuture(Utils::onResultReady( - SimulatorControl::takeSceenshot(info.identifier, generatePath(info)), + SimulatorControl::takeSceenshot(info.identifier, generatePath(info)), this, std::bind(onSimOperation, info, statusDialog, Tr::tr("simulator screenshot"), _1))); } diff --git a/src/plugins/ios/iossimulator.cpp b/src/plugins/ios/iossimulator.cpp index b58e451400d..fa68c4031e3 100644 --- a/src/plugins/ios/iossimulator.cpp +++ b/src/plugins/ios/iossimulator.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include @@ -52,7 +52,7 @@ Utils::Port IosSimulator::nextPort() const // use qrand instead? if (++m_lastPort >= Constants::IOS_SIMULATOR_PORT_END) m_lastPort = Constants::IOS_SIMULATOR_PORT_START; - Utils::QtcProcess portVerifier; + Utils::Process portVerifier; // this is a bit too broad (it does not check just listening sockets, but also connections // to that port from this computer) portVerifier.setCommand({"lsof", {"-n", "-P", "-i", QString(":%1").arg(m_lastPort)}}); @@ -66,11 +66,6 @@ Utils::Port IosSimulator::nextPort() const return Utils::Port(m_lastPort); } -bool IosSimulator::canAutoDetectPorts() const -{ - return true; -} - // IosDeviceType IosDeviceType::IosDeviceType(IosDeviceType::Type type, const QString &identifier, const QString &displayName) : diff --git a/src/plugins/ios/iossimulator.h b/src/plugins/ios/iossimulator.h index d2d69f1d0ef..988776e5086 100644 --- a/src/plugins/ios/iossimulator.h +++ b/src/plugins/ios/iossimulator.h @@ -48,7 +48,6 @@ public: ProjectExplorer::IDeviceWidget *createWidget() override; Utils::Port nextPort() const; - bool canAutoDetectPorts() const override; protected: friend class IosSimulatorFactory; diff --git a/src/plugins/ios/iostoolhandler.cpp b/src/plugins/ios/iostoolhandler.cpp index 0331ba2ccab..0e4545bf1da 100644 --- a/src/plugins/ios/iostoolhandler.cpp +++ b/src/plugins/ios/iostoolhandler.cpp @@ -12,11 +12,11 @@ #include +#include #include #include +#include #include -#include -#include #include #include @@ -63,22 +63,22 @@ class LogTailFiles : public QObject Q_OBJECT public: - void exec(QFutureInterface &fi, std::shared_ptr stdoutFile, - std::shared_ptr stderrFile) + void exec(QPromise &promise, std::shared_ptr stdoutFile, + std::shared_ptr stderrFile) { - if (fi.isCanceled()) + if (promise.isCanceled()) return; // The future is canceled when app on simulator is stoped. QEventLoop loop; QFutureWatcher watcher; connect(&watcher, &QFutureWatcher::canceled, &loop, [&] { loop.quit(); }); - watcher.setFuture(fi.future()); + watcher.setFuture(promise.future()); // Process to print the console output while app is running. auto logProcess = [&](QProcess *tailProcess, std::shared_ptr file) { - QObject::connect(tailProcess, &QProcess::readyReadStandardOutput, &loop, [=] { - if (!fi.isCanceled()) + QObject::connect(tailProcess, &QProcess::readyReadStandardOutput, &loop, [&, tailProcess] { + if (!promise.isCanceled()) emit logMessage(QString::fromLocal8Bit(tailProcess->readAll())); }); tailProcess->start(QStringLiteral("tail"), {"-f", file->fileName()}); @@ -787,7 +787,6 @@ IosSimulatorToolHandlerPrivate::IosSimulatorToolHandlerPrivate(const IosDeviceTy { QObject::connect(&outputLogger, &LogTailFiles::logMessage, std::bind(&IosToolHandlerPrivate::appOutput, this, _1)); - futureSynchronizer.setCancelOnWait(true); } void IosSimulatorToolHandlerPrivate::requestTransferApp(const QString &appBundlePath, @@ -814,8 +813,8 @@ void IosSimulatorToolHandlerPrivate::requestTransferApp(const QString &appBundle if (SimulatorControl::isSimulatorRunning(m_deviceId)) installAppOnSimulator(); else - futureSynchronizer.addFuture( - Utils::onResultReady(SimulatorControl::startSimulator(m_deviceId), onSimulatorStart)); + futureSynchronizer.addFuture(Utils::onResultReady( + SimulatorControl::startSimulator(m_deviceId), q, onSimulatorStart)); } void IosSimulatorToolHandlerPrivate::requestRunApp(const QString &appBundlePath, @@ -851,8 +850,8 @@ void IosSimulatorToolHandlerPrivate::requestRunApp(const QString &appBundlePath, if (SimulatorControl::isSimulatorRunning(m_deviceId)) launchAppOnSimulator(extraArgs); else - futureSynchronizer.addFuture( - Utils::onResultReady(SimulatorControl::startSimulator(m_deviceId), onSimulatorStart)); + futureSynchronizer.addFuture(Utils::onResultReady( + SimulatorControl::startSimulator(m_deviceId), q, onSimulatorStart)); } void IosSimulatorToolHandlerPrivate::requestDeviceInfo(const QString &deviceId, int timeout) @@ -904,7 +903,7 @@ void IosSimulatorToolHandlerPrivate::installAppOnSimulator() isTransferringApp(m_bundlePath, m_deviceId, 20, 100, ""); auto installFuture = SimulatorControl::installApp(m_deviceId, Utils::FilePath::fromString(m_bundlePath)); - futureSynchronizer.addFuture(Utils::onResultReady(installFuture, onResponseAppInstall)); + futureSynchronizer.addFuture(Utils::onResultReady(installFuture, q, onResponseAppInstall)); } void IosSimulatorToolHandlerPrivate::launchAppOnSimulator(const QStringList &extraArgs) @@ -931,17 +930,17 @@ void IosSimulatorToolHandlerPrivate::launchAppOnSimulator(const QStringList &ext "Install Xcode 8 or later.").arg(bundleId)); } - auto monitorPid = [this](QFutureInterface &fi, qint64 pid) { + auto monitorPid = [this](QPromise &promise, qint64 pid) { #ifdef Q_OS_UNIX do { // Poll every 1 sec to check whether the app is running. QThread::msleep(1000); - } while (!fi.isCanceled() && kill(pid, 0) == 0); + } while (!promise.isCanceled() && kill(pid, 0) == 0); #else Q_UNUSED(pid) #endif // Future is cancelled if the app is stopped from the qt creator. - if (!fi.isCanceled()) + if (!promise.isCanceled()) stop(0); }; @@ -953,9 +952,9 @@ void IosSimulatorToolHandlerPrivate::launchAppOnSimulator(const QStringList &ext gotInferiorPid(m_bundlePath, m_deviceId, response.pID); didStartApp(m_bundlePath, m_deviceId, Ios::IosToolHandler::Success); // Start monitoring app's life signs. - futureSynchronizer.addFuture(Utils::runAsync(monitorPid, response.pID)); + futureSynchronizer.addFuture(Utils::asyncRun(monitorPid, response.pID)); if (captureConsole) - futureSynchronizer.addFuture(Utils::runAsync(&LogTailFiles::exec, &outputLogger, + futureSynchronizer.addFuture(Utils::asyncRun(&LogTailFiles::exec, &outputLogger, stdoutFile, stderrFile)); } else { m_pid = -1; @@ -967,16 +966,11 @@ void IosSimulatorToolHandlerPrivate::launchAppOnSimulator(const QStringList &ext } }; - futureSynchronizer.addFuture( - Utils::onResultReady(SimulatorControl::launchApp(m_deviceId, - bundleId, - debugRun, - extraArgs, - captureConsole ? stdoutFile->fileName() - : QString(), - captureConsole ? stderrFile->fileName() - : QString()), - onResponseAppLaunch)); + futureSynchronizer.addFuture(Utils::onResultReady(SimulatorControl::launchApp( + m_deviceId, bundleId, debugRun, extraArgs, + captureConsole ? stdoutFile->fileName() : QString(), + captureConsole ? stderrFile->fileName() : QString()), + q, onResponseAppLaunch)); } bool IosSimulatorToolHandlerPrivate::isResponseValid(const SimulatorControl::ResponseData &responseData) diff --git a/src/plugins/ios/simulatorcontrol.cpp b/src/plugins/ios/simulatorcontrol.cpp index 6e090b61886..0832a5faf37 100644 --- a/src/plugins/ios/simulatorcontrol.cpp +++ b/src/plugins/ios/simulatorcontrol.cpp @@ -5,9 +5,9 @@ #include "iosconfigurations.h" #include -#include +#include +#include #include -#include #ifdef Q_OS_MAC #include @@ -57,7 +57,7 @@ static bool checkForTimeout(const chrono::high_resolution_clock::time_point &sta static bool runCommand(const CommandLine &command, QString *stdOutput, QString *allOutput = nullptr) { - QtcProcess p; + Process p; p.setTimeoutS(-1); p.setCommand(command); p.runBlocking(); @@ -98,7 +98,7 @@ static bool launchSimulator(const QString &simUdid) { } } - return QtcProcess::startDetached({simulatorAppPath, {"--args", "-CurrentDeviceUDID", simUdid}}); + return Process::startDetached({simulatorAppPath, {"--args", "-CurrentDeviceUDID", simUdid}}); } static bool isAvailable(const QJsonObject &object) @@ -162,30 +162,30 @@ static SimulatorInfo deviceInfo(const QString &simUdid); static QString bundleIdentifier(const Utils::FilePath &bundlePath); static QString bundleExecutable(const Utils::FilePath &bundlePath); -static void startSimulator(QFutureInterface &fi, +static void startSimulator(QPromise &promise, const QString &simUdid); -static void installApp(QFutureInterface &fi, +static void installApp(QPromise &promise, const QString &simUdid, const Utils::FilePath &bundlePath); -static void launchApp(QFutureInterface &fi, +static void launchApp(QPromise &promise, const QString &simUdid, const QString &bundleIdentifier, bool waitForDebugger, const QStringList &extraArgs, const QString &stdoutPath, const QString &stderrPath); -static void deleteSimulator(QFutureInterface &fi, +static void deleteSimulator(QPromise &promise, const QString &simUdid); -static void resetSimulator(QFutureInterface &fi, +static void resetSimulator(QPromise &promise, const QString &simUdid); -static void renameSimulator(QFutureInterface &fi, +static void renameSimulator(QPromise &promise, const QString &simUdid, const QString &newName); -static void createSimulator(QFutureInterface &fi, +static void createSimulator(QPromise &promise, const QString &name, const DeviceTypeInfo &deviceType, const RuntimeInfo &runtime); -static void takeSceenshot(QFutureInterface &fi, +static void takeSceenshot(QPromise &promise, const QString &simUdid, const QString &filePath); @@ -234,10 +234,10 @@ static QList getAvailableSimulators() return availableDevices; } -QFuture > SimulatorControl::updateDeviceTypes() +QFuture> SimulatorControl::updateDeviceTypes(QObject *context) { - QFuture< QList > future = Utils::runAsync(getAvailableDeviceTypes); - Utils::onResultReady(future, [](const QList &deviceTypes) { + QFuture> future = Utils::asyncRun(getAvailableDeviceTypes); + Utils::onResultReady(future, context, [](const QList &deviceTypes) { s_availableDeviceTypes = deviceTypes; }); return future; @@ -248,20 +248,21 @@ QList SimulatorControl::availableRuntimes() return s_availableRuntimes; } -QFuture > SimulatorControl::updateRuntimes() +QFuture> SimulatorControl::updateRuntimes(QObject *context) { - QFuture< QList > future = Utils::runAsync(getAvailableRuntimes); - Utils::onResultReady(future, [](const QList &runtimes) { + QFuture> future = Utils::asyncRun(getAvailableRuntimes); + Utils::onResultReady(future, context, [](const QList &runtimes) { s_availableRuntimes = runtimes; }); return future; } -QFuture< QList > SimulatorControl::updateAvailableSimulators() +QFuture> SimulatorControl::updateAvailableSimulators(QObject *context) { - QFuture< QList > future = Utils::runAsync(getAvailableSimulators); - Utils::onResultReady(future, - [](const QList &devices) { s_availableDevices = devices; }); + QFuture> future = Utils::asyncRun(getAvailableSimulators); + Utils::onResultReady(future, context, [](const QList &devices) { + s_availableDevices = devices; + }); return future; } @@ -284,13 +285,13 @@ QString SimulatorControl::bundleExecutable(const Utils::FilePath &bundlePath) QFuture SimulatorControl::startSimulator(const QString &simUdid) { - return Utils::runAsync(Internal::startSimulator, simUdid); + return Utils::asyncRun(Internal::startSimulator, simUdid); } QFuture SimulatorControl::installApp( const QString &simUdid, const Utils::FilePath &bundlePath) { - return Utils::runAsync(Internal::installApp, simUdid, bundlePath); + return Utils::asyncRun(Internal::installApp, simUdid, bundlePath); } QFuture SimulatorControl::launchApp(const QString &simUdid, @@ -300,7 +301,7 @@ QFuture SimulatorControl::launchApp(const QStrin const QString &stdoutPath, const QString &stderrPath) { - return Utils::runAsync(Internal::launchApp, + return Utils::asyncRun(Internal::launchApp, simUdid, bundleIdentifier, waitForDebugger, @@ -311,18 +312,18 @@ QFuture SimulatorControl::launchApp(const QStrin QFuture SimulatorControl::deleteSimulator(const QString &simUdid) { - return Utils::runAsync(Internal::deleteSimulator, simUdid); + return Utils::asyncRun(Internal::deleteSimulator, simUdid); } QFuture SimulatorControl::resetSimulator(const QString &simUdid) { - return Utils::runAsync(Internal::resetSimulator, simUdid); + return Utils::asyncRun(Internal::resetSimulator, simUdid); } QFuture SimulatorControl::renameSimulator(const QString &simUdid, const QString &newName) { - return Utils::runAsync(Internal::renameSimulator, simUdid, newName); + return Utils::asyncRun(Internal::renameSimulator, simUdid, newName); } QFuture @@ -330,13 +331,13 @@ SimulatorControl::createSimulator(const QString &name, const DeviceTypeInfo &deviceType, const RuntimeInfo &runtime) { - return Utils::runAsync(Internal::createSimulator, name, deviceType, runtime); + return Utils::asyncRun(Internal::createSimulator, name, deviceType, runtime); } QFuture SimulatorControl::takeSceenshot(const QString &simUdid, const QString &filePath) { - return Utils::runAsync(Internal::takeSceenshot, simUdid, filePath); + return Utils::asyncRun(Internal::takeSceenshot, simUdid, filePath); } // Static members @@ -392,7 +393,7 @@ QString bundleExecutable(const Utils::FilePath &bundlePath) return executable; } -void startSimulator(QFutureInterface &fi, const QString &simUdid) +void startSimulator(QPromise &promise, const QString &simUdid) { SimulatorControl::ResponseData response(simUdid); SimulatorInfo simInfo = deviceInfo(simUdid); @@ -420,7 +421,7 @@ void startSimulator(QFutureInterface &fi, const if (simInfo.isShutdown()) { if (launchSimulator(simUdid)) { - if (fi.isCanceled()) + if (promise.isCanceled()) return; // At this point the sim device exists, available and was not running. // So the simulator is started and we'll wait for it to reach to a state @@ -429,13 +430,11 @@ void startSimulator(QFutureInterface &fi, const SimulatorInfo info; do { info = deviceInfo(simUdid); - if (fi.isCanceled()) + if (promise.isCanceled()) return; - } while (!info.isBooted() - && !checkForTimeout(start, simulatorStartTimeout)); - if (info.isBooted()) { + } while (!info.isBooted() && !checkForTimeout(start, simulatorStartTimeout)); + if (info.isBooted()) response.success = true; - } } else { qCDebug(simulatorLog) << "Error starting simulator."; } @@ -444,14 +443,12 @@ void startSimulator(QFutureInterface &fi, const << simInfo; } - if (!fi.isCanceled()) { - fi.reportResult(response); - } + if (!promise.isCanceled()) + promise.addResult(response); } -void installApp(QFutureInterface &fi, - const QString &simUdid, - const Utils::FilePath &bundlePath) +void installApp(QPromise &promise, + const QString &simUdid, const Utils::FilePath &bundlePath) { QTC_CHECK(bundlePath.exists()); @@ -459,11 +456,11 @@ void installApp(QFutureInterface &fi, response.success = runSimCtlCommand({"install", simUdid, bundlePath.toString()}, nullptr, &response.commandOutput); - if (!fi.isCanceled()) - fi.reportResult(response); + if (!promise.isCanceled()) + promise.addResult(response); } -void launchApp(QFutureInterface &fi, +void launchApp(QPromise &promise, const QString &simUdid, const QString &bundleIdentifier, bool waitForDebugger, @@ -472,7 +469,7 @@ void launchApp(QFutureInterface &fi, const QString &stderrPath) { SimulatorControl::ResponseData response(simUdid); - if (!bundleIdentifier.isEmpty() && !fi.isCanceled()) { + if (!bundleIdentifier.isEmpty() && !promise.isCanceled()) { QStringList args({"launch", simUdid, bundleIdentifier}); // simctl usage documentation : Note: Log output is often directed to stderr, not stdout. @@ -499,30 +496,29 @@ void launchApp(QFutureInterface &fi, } } - if (!fi.isCanceled()) { - fi.reportResult(response); - } + if (!promise.isCanceled()) + promise.addResult(response); } -void deleteSimulator(QFutureInterface &fi, const QString &simUdid) +void deleteSimulator(QPromise &promise, const QString &simUdid) { SimulatorControl::ResponseData response(simUdid); response.success = runSimCtlCommand({"delete", simUdid}, nullptr, &response.commandOutput); - if (!fi.isCanceled()) - fi.reportResult(response); + if (!promise.isCanceled()) + promise.addResult(response); } -void resetSimulator(QFutureInterface &fi, const QString &simUdid) +void resetSimulator(QPromise &promise, const QString &simUdid) { SimulatorControl::ResponseData response(simUdid); response.success = runSimCtlCommand({"erase", simUdid}, nullptr, &response.commandOutput); - if (!fi.isCanceled()) - fi.reportResult(response); + if (!promise.isCanceled()) + promise.addResult(response); } -void renameSimulator(QFutureInterface &fi, +void renameSimulator(QPromise &promise, const QString &simUdid, const QString &newName) { @@ -530,12 +526,11 @@ void renameSimulator(QFutureInterface &fi, response.success = runSimCtlCommand({"rename", simUdid, newName}, nullptr, &response.commandOutput); - - if (!fi.isCanceled()) - fi.reportResult(response); + if (!promise.isCanceled()) + promise.addResult(response); } -void createSimulator(QFutureInterface &fi, +void createSimulator(QPromise &promise, const QString &name, const DeviceTypeInfo &deviceType, const RuntimeInfo &runtime) @@ -550,11 +545,11 @@ void createSimulator(QFutureInterface &fi, response.simUdid = response.success ? stdOutput.trimmed() : QString(); } - if (!fi.isCanceled()) - fi.reportResult(response); + if (!promise.isCanceled()) + promise.addResult(response); } -void takeSceenshot(QFutureInterface &fi, +void takeSceenshot(QPromise &promise, const QString &simUdid, const QString &filePath) { @@ -562,8 +557,8 @@ void takeSceenshot(QFutureInterface &fi, response.success = runSimCtlCommand({"io", simUdid, "screenshot", filePath}, nullptr, &response.commandOutput); - if (!fi.isCanceled()) - fi.reportResult(response); + if (!promise.isCanceled()) + promise.addResult(response); } QDebug &operator<<(QDebug &stream, const SimulatorInfo &info) diff --git a/src/plugins/ios/simulatorcontrol.h b/src/plugins/ios/simulatorcontrol.h index 15fea7b27bd..402100fbeac 100644 --- a/src/plugins/ios/simulatorcontrol.h +++ b/src/plugins/ios/simulatorcontrol.h @@ -65,11 +65,11 @@ public: public: static QList availableDeviceTypes(); - static QFuture > updateDeviceTypes(); + static QFuture> updateDeviceTypes(QObject *context); static QList availableRuntimes(); - static QFuture > updateRuntimes(); + static QFuture> updateRuntimes(QObject *context); static QList availableSimulators(); - static QFuture > updateAvailableSimulators(); + static QFuture> updateAvailableSimulators(QObject *context); static bool isSimulatorRunning(const QString &simUdid); static QString bundleIdentifier(const Utils::FilePath &bundlePath); static QString bundleExecutable(const Utils::FilePath &bundlePath); diff --git a/src/plugins/ios/simulatorinfomodel.cpp b/src/plugins/ios/simulatorinfomodel.cpp index df58dc98cba..dce56467134 100644 --- a/src/plugins/ios/simulatorinfomodel.cpp +++ b/src/plugins/ios/simulatorinfomodel.cpp @@ -6,7 +6,7 @@ #include "iostr.h" #include -#include +#include #include @@ -23,8 +23,6 @@ const int deviceUpdateInterval = 1000; // Update simulator state every 1 sec. SimulatorInfoModel::SimulatorInfoModel(QObject *parent) : QAbstractItemModel(parent) { - m_fetchFuture.setCancelOnWait(true); - requestSimulatorInfo(); auto updateTimer = new QTimer(this); @@ -109,7 +107,7 @@ void SimulatorInfoModel::requestSimulatorInfo() if (!m_fetchFuture.isEmpty()) return; // Ignore the request if the last request is still pending. - m_fetchFuture.addFuture(Utils::onResultReady(SimulatorControl::updateAvailableSimulators(), + m_fetchFuture.addFuture(Utils::onResultReady(SimulatorControl::updateAvailableSimulators(this), this, &SimulatorInfoModel::populateSimulators)); } diff --git a/src/plugins/ios/simulatoroperationdialog.cpp b/src/plugins/ios/simulatoroperationdialog.cpp index 56e2d3b69fe..180b28d76e4 100644 --- a/src/plugins/ios/simulatoroperationdialog.cpp +++ b/src/plugins/ios/simulatoroperationdialog.cpp @@ -40,7 +40,7 @@ SimulatorOperationDialog::SimulatorOperationDialog(QWidget *parent) : m_formatter = new Utils::OutputFormatter; m_formatter->setPlainTextEdit(messageEdit); - using namespace Utils::Layouting; + using namespace Layouting; Column { messageEdit, diff --git a/src/plugins/languageclient/CMakeLists.txt b/src/plugins/languageclient/CMakeLists.txt index 8e1748a04a1..5af221f7f86 100644 --- a/src/plugins/languageclient/CMakeLists.txt +++ b/src/plugins/languageclient/CMakeLists.txt @@ -1,9 +1,17 @@ +if (MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj") +elseif (MINGW) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wa,-mbig-obj") +endif() + add_qtc_plugin(LanguageClient PUBLIC_DEPENDS LanguageServerProtocol Qt::Core app_version PLUGIN_DEPENDS ProjectExplorer Core TextEditor SOURCES callhierarchy.cpp callhierarchy.h client.cpp client.h + clientrequesttask.cpp clientrequesttask.h + currentdocumentsymbolsrequest.cpp currentdocumentsymbolsrequest.h diagnosticmanager.cpp diagnosticmanager.h documentsymbolcache.cpp documentsymbolcache.h dynamiccapabilities.cpp dynamiccapabilities.h diff --git a/src/plugins/languageclient/callhierarchy.cpp b/src/plugins/languageclient/callhierarchy.cpp index 3e0c6589aa7..a05922cbe5e 100644 --- a/src/plugins/languageclient/callhierarchy.cpp +++ b/src/plugins/languageclient/callhierarchy.cpp @@ -23,8 +23,6 @@ using namespace LanguageServerProtocol; namespace LanguageClient { -const char CALL_HIERARCHY_FACTORY_ID[] = "LanguageClient.CallHierarchy"; - namespace { enum Direction { Incoming, Outgoing }; @@ -186,6 +184,9 @@ public: layout()->setSpacing(0); connect(m_view, &NavigationTreeView::activated, this, &CallHierarchy::onItemActivated); + + connect(LanguageClientManager::instance(), &LanguageClientManager::openCallHierarchy, + this, &CallHierarchy::updateHierarchyAtCursorPosition); } void onItemActivated(const QModelIndex &index) @@ -211,26 +212,14 @@ void CallHierarchy::updateHierarchyAtCursorPosition() BaseTextEditor *editor = BaseTextEditor::currentTextEditor(); if (!editor) return; - Client *client = LanguageClientManager::clientForFilePath(editor->document()->filePath()); + + Core::IDocument *document = editor->document(); + + Client *client = LanguageClientManager::clientForFilePath(document->filePath()); if (!client) return; - const QString methodName = PrepareCallHierarchyRequest::methodName; - std::optional registered = client->dynamicCapabilities().isRegistered(methodName); - bool supported = registered.value_or(false); - const Core::IDocument *document = editor->document(); - if (registered) { - if (supported) { - const QJsonValue &options = client->dynamicCapabilities().option(methodName); - const TextDocumentRegistrationOptions docOptions(options); - supported = docOptions.filterApplies(document->filePath(), - Utils::mimeTypeForName(document->mimeType())); - } - } else { - supported = client->capabilities().callHierarchyProvider().has_value(); - } - - if (!supported) + if (!CallHierarchyFactory::supportsCallHierarchy(client, document)) return; TextDocumentPositionParams params; @@ -273,7 +262,25 @@ CallHierarchyFactory::CallHierarchyFactory() { setDisplayName(Tr::tr("Call Hierarchy")); setPriority(650); - setId(CALL_HIERARCHY_FACTORY_ID); + setId(Constants::CALL_HIERARCHY_FACTORY_ID); +} + +bool CallHierarchyFactory::supportsCallHierarchy(Client *client, const Core::IDocument *document) +{ + const QString methodName = PrepareCallHierarchyRequest::methodName; + std::optional registered = client->dynamicCapabilities().isRegistered(methodName); + bool supported = registered.value_or(false); + if (registered) { + if (supported) { + const QJsonValue &options = client->dynamicCapabilities().option(methodName); + const TextDocumentRegistrationOptions docOptions(options); + supported = docOptions.filterApplies(document->filePath(), + Utils::mimeTypeForName(document->mimeType())); + } + } else { + supported = client->capabilities().callHierarchyProvider().has_value(); + } + return supported; } Core::NavigationView CallHierarchyFactory::createWidget() @@ -284,6 +291,7 @@ Core::NavigationView CallHierarchyFactory::createWidget() Icons::RELOAD_TOOLBAR.icon(); auto button = new QToolButton; button->setIcon(Icons::RELOAD_TOOLBAR.icon()); + button->setToolTip(Tr::tr("Reloads the call hierarchy for the symbol under cursor position.")); connect(button, &QToolButton::clicked, [h](){ h->updateHierarchyAtCursorPosition(); }); diff --git a/src/plugins/languageclient/callhierarchy.h b/src/plugins/languageclient/callhierarchy.h index f707c4fbcbb..bbc15b09712 100644 --- a/src/plugins/languageclient/callhierarchy.h +++ b/src/plugins/languageclient/callhierarchy.h @@ -5,8 +5,12 @@ #pragma once +namespace Core { class IDocument; } + namespace LanguageClient { +class Client; + class CallHierarchyFactory : public Core::INavigationWidgetFactory { Q_OBJECT @@ -14,6 +18,8 @@ class CallHierarchyFactory : public Core::INavigationWidgetFactory public: CallHierarchyFactory(); + static bool supportsCallHierarchy(Client *client, const Core::IDocument *document); + Core::NavigationView createWidget() override; }; diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index 0efaacd3500..130961ebd1b 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -3,6 +3,7 @@ #include "client.h" +#include "callhierarchy.h" #include "diagnosticmanager.h" #include "documentsymbolcache.h" #include "languageclientcompletionassist.h" @@ -11,6 +12,7 @@ #include "languageclienthoverhandler.h" #include "languageclientinterface.h" #include "languageclientmanager.h" +#include "languageclientoutline.h" #include "languageclientquickfix.h" #include "languageclientsymbolsupport.h" #include "languageclientutils.h" @@ -26,6 +28,8 @@ #include #include +#include + #include #include #include @@ -38,7 +42,7 @@ #include #include -#include +#include #include #include @@ -51,7 +55,7 @@ #include #include -#include +#include #include #include @@ -155,7 +159,7 @@ public: m_documentUpdateTimer.setInterval(500); connect(&m_documentUpdateTimer, &QTimer::timeout, this, [this] { sendPostponedDocumentUpdates(Schedule::Now); }); - connect(SessionManager::instance(), &SessionManager::projectRemoved, + connect(ProjectManager::instance(), &ProjectManager::projectRemoved, q, &Client::projectClosed); QTC_ASSERT(clientInterface, return); @@ -192,7 +196,7 @@ public: // temporary container needed since m_resetAssistProvider is changed in resetAssistProviders for (TextDocument *document : m_resetAssistProvider.keys()) resetAssistProviders(document); - if (!LanguageClientManager::isShuttingDown()) { + if (!ExtensionSystem::PluginManager::isShuttingDown()) { // prevent accessing deleted editors on Creator shutdown const QList &editors = Core::DocumentModel::editorsForOpenedDocuments(); for (Core::IEditor *editor : editors) { @@ -325,7 +329,6 @@ public: SemanticTokenSupport m_tokenSupport; QString m_serverName; QString m_serverVersion; - LanguageServerProtocol::SymbolStringifier m_symbolStringifier; Client::LogTarget m_logTarget = Client::LogTarget::Ui; bool m_locatorsEnabled = true; bool m_autoRequestCodeActions = true; @@ -353,6 +356,7 @@ void Client::setName(const QString &name) QString Client::name() const { if (d->m_project && !d->m_project->displayName().isEmpty()) + //: for return Tr::tr("%1 for %2").arg(d->m_displayName, d->m_project->displayName()); return d->m_displayName; } @@ -508,11 +512,13 @@ void Client::initialize() if (d->m_project) params.setRootUri(hostPathToServerUri(d->m_project->projectDirectory())); + auto projectFilter = [this](Project *project) { return canOpenProject(project); }; + auto toWorkSpaceFolder = [this](Project *pro) { + return WorkSpaceFolder(hostPathToServerUri(pro->projectDirectory()), pro->displayName()); + }; const QList workspaces - = Utils::transform(SessionManager::projects(), [this](Project *pro) { - return WorkSpaceFolder(hostPathToServerUri(pro->projectDirectory()), - pro->displayName()); - }); + = Utils::transform(Utils::filtered(ProjectManager::projects(), projectFilter), + toWorkSpaceFolder); if (workspaces.isEmpty()) params.setWorkSpaceFolders(nullptr); else @@ -550,11 +556,17 @@ Client::State Client::state() const QString Client::stateString() const { switch (d->m_state){ + //: language client state case Uninitialized: return Tr::tr("uninitialized"); + //: language client state case InitializeRequested: return Tr::tr("initialize requested"); + //: language client state case Initialized: return Tr::tr("initialized"); + //: language client state case ShutdownRequested: return Tr::tr("shutdown requested"); - case Shutdown: return Tr::tr("shutdown"); + //: language client state + case Shutdown: return Tr::tr("shut down"); + //: language client state case Error: return Tr::tr("error"); } return {}; @@ -617,7 +629,7 @@ void Client::openDocument(TextEditor::TextDocument *document) } } - d->m_openedDocument[document].document = document->document()->clone(this); + d->m_openedDocument[document].document = new QTextDocument(document->document()->toPlainText()); d->m_openedDocument[document].contentsChangedConnection = connect(document, &TextDocument::contentsChangedWithPosition, @@ -879,6 +891,8 @@ void Client::activateEditor(Core::IEditor *editor) optionalActions |= TextEditor::TextEditorActionHandler::FindUsage; if (symbolSupport().supportsRename(widget->textDocument())) optionalActions |= TextEditor::TextEditorActionHandler::RenameSymbol; + if (CallHierarchyFactory::supportsCallHierarchy(this, textEditor->document())) + optionalActions |= TextEditor::TextEditorActionHandler::CallHierarchy; widget->setOptionalActions(optionalActions); } } @@ -1348,6 +1362,7 @@ ProjectExplorer::Project *Client::project() const void Client::setCurrentProject(ProjectExplorer::Project *project) { + QTC_ASSERT(canOpenProject(project), return); if (d->m_project == project) return; if (d->m_project) @@ -1364,7 +1379,7 @@ void Client::setCurrentProject(ProjectExplorer::Project *project) void Client::projectOpened(ProjectExplorer::Project *project) { - if (!d->sendWorkspceFolderChanges()) + if (!d->sendWorkspceFolderChanges() || !canOpenProject(project)) return; WorkspaceFoldersChangeEvent event; event.setAdded({WorkSpaceFolder(hostPathToServerUri(project->projectDirectory()), @@ -1377,7 +1392,7 @@ void Client::projectOpened(ProjectExplorer::Project *project) void Client::projectClosed(ProjectExplorer::Project *project) { - if (d->sendWorkspceFolderChanges()) { + if (d->sendWorkspceFolderChanges() && canOpenProject(project)) { WorkspaceFoldersChangeEvent event; event.setRemoved({WorkSpaceFolder(hostPathToServerUri(project->projectDirectory()), project->displayName())}); @@ -1397,6 +1412,12 @@ void Client::projectClosed(ProjectExplorer::Project *project) } } +bool Client::canOpenProject(ProjectExplorer::Project *project) +{ + Q_UNUSED(project); + return true; +} + void Client::updateConfiguration(const QJsonValue &configuration) { d->m_configuration = configuration; @@ -1484,16 +1505,6 @@ void Client::setSemanticTokensHandler(const SemanticTokensHandler &handler) d->m_tokenSupport.setTokensHandler(handler); } -void Client::setSymbolStringifier(const LanguageServerProtocol::SymbolStringifier &stringifier) -{ - d->m_symbolStringifier = stringifier; -} - -SymbolStringifier Client::symbolStringifier() const -{ - return d->m_symbolStringifier; -} - void Client::setSnippetsGroup(const QString &group) { if (const auto provider = qobject_cast( @@ -1679,10 +1690,15 @@ LanguageClientValue ClientPrivate::showMessageBox( } QHash itemForButton; if (const std::optional> actions = message.actions()) { - for (const MessageActionItem &action : *actions) - itemForButton.insert(box->addButton(action.title(), QMessageBox::InvalidRole), action); + auto button = box->addButton(QMessageBox::Close); + connect(button, &QPushButton::clicked, box, &QMessageBox::reject); + for (const MessageActionItem &action : *actions) { + connect(button, &QPushButton::clicked, box, &QMessageBox::accept); + itemForButton.insert(button, action); + } } - box->exec(); + if (box->exec() == QDialog::Rejected) + return {}; const MessageActionItem &item = itemForButton.value(box->clickedButton()); return item.isValid() ? LanguageClientValue(item) : LanguageClientValue(); @@ -1872,7 +1888,7 @@ void ClientPrivate::handleMethod(const QString &method, const MessageId &id, con } else if (method == WorkSpaceFolderRequest::methodName) { WorkSpaceFolderRequest::Response response(id); const QList projects - = ProjectExplorer::SessionManager::projects(); + = ProjectExplorer::ProjectManager::projects(); if (projects.isEmpty()) { response.setResult(nullptr); } else { @@ -1961,7 +1977,7 @@ void ClientPrivate::initializeCallback(const InitializeRequest::Response &initRe if (std::optional> error = initResponse.error()) { if (std::optional data = error->data()) { if (data->retry()) { - const QString title(Tr::tr("Language Server \"%1\" Initialize Error").arg(m_displayName)); + const QString title(Tr::tr("Language Server \"%1\" Initialization Error").arg(m_displayName)); auto result = QMessageBox::warning(Core::ICore::dialogParent(), title, error->message(), @@ -1974,7 +1990,7 @@ void ClientPrivate::initializeCallback(const InitializeRequest::Response &initRe } } } - q->setError(Tr::tr("Initialize error: ") + error->message()); + q->setError(Tr::tr("Initialization error: %1.").arg(error->message())); emit q->finished(); return; } @@ -2089,6 +2105,12 @@ bool Client::fileBelongsToProject(const Utils::FilePath &filePath) const return project() && project()->isKnownFile(filePath); } +LanguageClientOutlineItem *Client::createOutlineItem( + const LanguageServerProtocol::DocumentSymbol &symbol) +{ + return new LanguageClientOutlineItem(this, symbol); +} + FilePath toHostPath(const FilePath serverDeviceTemplate, const FilePath localClientPath) { const FilePath onDevice = serverDeviceTemplate.withNewPath(localClientPath.path()); @@ -2110,7 +2132,7 @@ FilePath Client::serverUriToHostPath(const LanguageServerProtocol::DocumentUri & DocumentUri Client::hostPathToServerUri(const Utils::FilePath &path) const { return DocumentUri::fromFilePath(path, [&](const Utils::FilePath &clientPath) { - return clientPath.onDevice(d->m_serverDeviceTemplate); + return d->m_serverDeviceTemplate.withNewPath(clientPath.path()); }); } diff --git a/src/plugins/languageclient/client.h b/src/plugins/languageclient/client.h index 525739d7a63..2ddd155a9f1 100644 --- a/src/plugins/languageclient/client.h +++ b/src/plugins/languageclient/client.h @@ -31,7 +31,6 @@ class Unregistration; } // namespace LanguageServerProtocol namespace LanguageClient { - class BaseClientInterface; class ClientPrivate; class DiagnosticManager; @@ -40,6 +39,7 @@ class DynamicCapabilities; class HoverHandler; class InterfaceController; class LanguageClientCompletionAssistProvider; +class LanguageClientOutlineItem; class LanguageClientQuickFixProvider; class LanguageFilter; class ProgressManager; @@ -48,16 +48,12 @@ class SymbolSupport; class LANGUAGECLIENT_EXPORT Client : public QObject { Q_OBJECT + Q_DISABLE_COPY_MOVE(Client) public: explicit Client(BaseClientInterface *clientInterface, const Utils::Id &id = {}); // takes ownership ~Client() override; - Client(const Client &) = delete; - Client(Client &&) = delete; - Client &operator=(const Client &) = delete; - Client &operator=(Client &&) = delete; - // basic properties Utils::Id id() const; void setName(const QString &name); @@ -136,6 +132,7 @@ public: ProjectExplorer::Project *project() const; virtual void projectOpened(ProjectExplorer::Project *project); virtual void projectClosed(ProjectExplorer::Project *project); + virtual bool canOpenProject(ProjectExplorer::Project *project); void updateConfiguration(const QJsonValue &configuration); // commands @@ -160,13 +157,13 @@ public: const LanguageServerProtocol::Diagnostic &diag) const; bool hasDiagnostics(const TextEditor::TextDocument *document) const; void setSemanticTokensHandler(const SemanticTokensHandler &handler); - void setSymbolStringifier(const LanguageServerProtocol::SymbolStringifier &stringifier); - LanguageServerProtocol::SymbolStringifier symbolStringifier() const; void setSnippetsGroup(const QString &group); void setCompletionAssistProvider(LanguageClientCompletionAssistProvider *provider); void setQuickFixAssistProvider(LanguageClientQuickFixProvider *provider); virtual bool supportsDocumentSymbols(const TextEditor::TextDocument *doc) const; virtual bool fileBelongsToProject(const Utils::FilePath &filePath) const; + virtual LanguageClientOutlineItem *createOutlineItem( + const LanguageServerProtocol::DocumentSymbol &symbol); LanguageServerProtocol::DocumentUri::PathMapper hostPathMapper() const; Utils::FilePath serverUriToHostPath(const LanguageServerProtocol::DocumentUri &uri) const; diff --git a/src/plugins/languageclient/clientrequesttask.cpp b/src/plugins/languageclient/clientrequesttask.cpp new file mode 100644 index 00000000000..ed3ddaff2c5 --- /dev/null +++ b/src/plugins/languageclient/clientrequesttask.cpp @@ -0,0 +1,39 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "clientrequesttask.h" + +#include + +using namespace LanguageServerProtocol; + +namespace LanguageClient { + +WorkspaceSymbolRequestTaskAdapter::WorkspaceSymbolRequestTaskAdapter() +{ + task()->setResponseCallback([this](const WorkspaceSymbolRequest::Response &response){ + emit done(response.result().has_value()); + }); +} + +void WorkspaceSymbolRequestTaskAdapter::start() +{ + task()->start(); +} + +bool WorkspaceSymbolRequestTask::preStartCheck() +{ + if (!ClientRequestTask::preStartCheck()) + return false; + + const std::optional> capability + = client()->capabilities().workspaceSymbolProvider(); + if (!capability.has_value()) + return false; + if (std::holds_alternative(*capability) && !std::get(*capability)) + return false; + + return true; +} + +} // namespace LanguageClient diff --git a/src/plugins/languageclient/clientrequesttask.h b/src/plugins/languageclient/clientrequesttask.h new file mode 100644 index 00000000000..3e08e1a287f --- /dev/null +++ b/src/plugins/languageclient/clientrequesttask.h @@ -0,0 +1,80 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "languageclient_global.h" + +#include "client.h" + +#include +#include +#include + +#include + +namespace LanguageClient { + +template +class LANGUAGECLIENT_EXPORT ClientRequestTask +{ +public: + virtual ~ClientRequestTask() + { + if (m_id) + m_client->cancelRequest(*m_id); // In order to not to invoke a response callback anymore + } + + void setClient(Client *client) { m_client = client; } + Client *client() const { return m_client; } + void setParams(const typename Request::Parameters ¶ms) { m_params = params; } + + void start() + { + QTC_ASSERT(!isRunning(), return); + if (!preStartCheck()) { + m_callback({}); + return; + } + Request request(m_params); + request.setResponseCallback([this](const typename Request::Response &response) { + m_response = response; + m_id = {}; + m_callback(response); + }); + m_id = request.id(); + m_client->sendMessage(request); + } + + bool isRunning() const { return m_id.has_value(); } + virtual bool preStartCheck() { return m_client && m_client->reachable() && m_params.isValid(); } + + typename Request::Response response() const { return m_response; } + void setResponseCallback(typename Request::ResponseCallback callback) { m_callback = callback; } + +private: + Client *m_client = nullptr; + typename Request::Parameters m_params; + typename Request::ResponseCallback m_callback; + std::optional m_id; + typename Request::Response m_response; +}; + +class LANGUAGECLIENT_EXPORT WorkspaceSymbolRequestTask + : public ClientRequestTask +{ +public: + bool preStartCheck() override; +}; + +class LANGUAGECLIENT_EXPORT WorkspaceSymbolRequestTaskAdapter + : public Tasking::TaskAdapter +{ +public: + WorkspaceSymbolRequestTaskAdapter(); + void start() final; +}; + +} // namespace LanguageClient + +TASKING_DECLARE_TASK(SymbolRequest, LanguageClient::WorkspaceSymbolRequestTaskAdapter); diff --git a/src/plugins/languageclient/currentdocumentsymbolsrequest.cpp b/src/plugins/languageclient/currentdocumentsymbolsrequest.cpp new file mode 100644 index 00000000000..2d272a72161 --- /dev/null +++ b/src/plugins/languageclient/currentdocumentsymbolsrequest.cpp @@ -0,0 +1,83 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "currentdocumentsymbolsrequest.h" + +#include "documentsymbolcache.h" +#include "languageclientmanager.h" + +#include + +using namespace Core; +using namespace LanguageServerProtocol; +using namespace TextEditor; +using namespace Utils; + +namespace LanguageClient { + +void CurrentDocumentSymbolsRequest::start() +{ + QTC_ASSERT(!isRunning(), return); + + m_currentDocumentSymbolsData = {}; + + TextDocument *document = TextDocument::currentTextDocument(); + Client *client = LanguageClientManager::clientForDocument(document); + if (!client) { + emit done(false); + return; + } + + DocumentSymbolCache *symbolCache = client->documentSymbolCache(); + DocumentUri currentUri = client->hostPathToServerUri(document->filePath()); + DocumentUri::PathMapper pathMapper = client->hostPathMapper(); + + const auto reportFailure = [this] { + clearConnections(); + emit done(false); + }; + + const auto updateSymbols = [this, currentUri, pathMapper](const DocumentUri &uri, + const DocumentSymbolsResult &symbols) + { + if (uri != currentUri) // We might get updates for not current editor + return; + + const FilePath filePath = pathMapper ? currentUri.toFilePath(pathMapper) : FilePath(); + m_currentDocumentSymbolsData = {filePath, pathMapper, symbols}; + clearConnections(); + emit done(true); + }; + + m_connections.append(connect(EditorManager::instance(), &EditorManager::currentEditorChanged, + this, reportFailure)); + m_connections.append(connect(client, &Client::finished, this, reportFailure)); + m_connections.append(connect(document, &IDocument::contentsChanged, this, reportFailure)); + m_connections.append(connect(symbolCache, &DocumentSymbolCache::gotSymbols, + this, updateSymbols)); + symbolCache->requestSymbols(currentUri, Schedule::Now); +} + +bool CurrentDocumentSymbolsRequest::isRunning() const +{ + return !m_connections.isEmpty(); +} + +void CurrentDocumentSymbolsRequest::clearConnections() +{ + for (const QMetaObject::Connection &connection : std::as_const(m_connections)) + disconnect(connection); + m_connections.clear(); +} + +CurrentDocumentSymbolsRequestTaskAdapter::CurrentDocumentSymbolsRequestTaskAdapter() +{ + connect(task(), &CurrentDocumentSymbolsRequest::done, this, &TaskInterface::done); +} + +void CurrentDocumentSymbolsRequestTaskAdapter::start() +{ + task()->start(); +} + +} // namespace LanguageClient diff --git a/src/plugins/languageclient/currentdocumentsymbolsrequest.h b/src/plugins/languageclient/currentdocumentsymbolsrequest.h new file mode 100644 index 00000000000..0b9c11a2215 --- /dev/null +++ b/src/plugins/languageclient/currentdocumentsymbolsrequest.h @@ -0,0 +1,53 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "languageclient_global.h" + +#include +#include + +#include + +namespace LanguageClient { + +class LANGUAGECLIENT_EXPORT CurrentDocumentSymbolsData +{ +public: + Utils::FilePath m_filePath; + LanguageServerProtocol::DocumentUri::PathMapper m_pathMapper; + LanguageServerProtocol::DocumentSymbolsResult m_symbols; +}; + +class LANGUAGECLIENT_EXPORT CurrentDocumentSymbolsRequest : public QObject +{ + Q_OBJECT + +public: + void start(); + bool isRunning() const; + CurrentDocumentSymbolsData currentDocumentSymbolsData() const { return m_currentDocumentSymbolsData; } + +signals: + void done(bool success); + +private: + void clearConnections(); + + CurrentDocumentSymbolsData m_currentDocumentSymbolsData; + QList m_connections; +}; + +class LANGUAGECLIENT_EXPORT CurrentDocumentSymbolsRequestTaskAdapter + : public Tasking::TaskAdapter +{ +public: + CurrentDocumentSymbolsRequestTaskAdapter(); + void start() final; +}; + +} // namespace LanguageClient + +TASKING_DECLARE_TASK(CurrentDocumentSymbolsRequestTask, + LanguageClient::CurrentDocumentSymbolsRequestTaskAdapter); diff --git a/src/plugins/languageclient/documentsymbolcache.cpp b/src/plugins/languageclient/documentsymbolcache.cpp index 113b22695a5..b08e70d2911 100644 --- a/src/plugins/languageclient/documentsymbolcache.cpp +++ b/src/plugins/languageclient/documentsymbolcache.cpp @@ -40,6 +40,8 @@ DocumentSymbolCache::DocumentSymbolCache(Client *client) void DocumentSymbolCache::requestSymbols(const DocumentUri &uri, Schedule schedule) { + if (m_runningRequests.contains(uri)) + return; m_compressedUris.insert(uri); switch (schedule) { case Schedule::Now: diff --git a/src/plugins/languageclient/languageclient.qbs b/src/plugins/languageclient/languageclient.qbs index 9a48caf9e3c..286f319a3cc 100644 --- a/src/plugins/languageclient/languageclient.qbs +++ b/src/plugins/languageclient/languageclient.qbs @@ -24,6 +24,10 @@ QtcPlugin { "callhierarchy.h", "client.cpp", "client.h", + "clientrequesttask.cpp", + "clientrequesttask.h", + "currentdocumentsymbolsrequest.cpp", + "currentdocumentsymbolsrequest.h", "diagnosticmanager.cpp", "diagnosticmanager.h", "documentsymbolcache.cpp", diff --git a/src/plugins/languageclient/languageclient_global.h b/src/plugins/languageclient/languageclient_global.h index 0dca3e6bbfc..2abb395d86e 100644 --- a/src/plugins/languageclient/languageclient_global.h +++ b/src/plugins/languageclient/languageclient_global.h @@ -22,12 +22,25 @@ const char LANGUAGECLIENT_STDIO_SETTINGS_ID[] = "LanguageClient::StdIOSettingsID const char LANGUAGECLIENT_SETTINGS_TR[] = QT_TRANSLATE_NOOP("QtC::LanguageClient", "Language Client"); const char LANGUAGECLIENT_DOCUMENT_FILTER_ID[] = "Current Document Symbols"; const char LANGUAGECLIENT_DOCUMENT_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("QtC::LanguageClient", "Symbols in Current Document"); +const char LANGUAGECLIENT_DOCUMENT_FILTER_DESCRIPTION[] + = QT_TRANSLATE_NOOP("QtC::LanguageClient", + "Locates symbols in the current document, based on a language server."); const char LANGUAGECLIENT_WORKSPACE_FILTER_ID[] = "Workspace Symbols"; const char LANGUAGECLIENT_WORKSPACE_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("QtC::LanguageClient", "Symbols in Workspace"); +const char LANGUAGECLIENT_WORKSPACE_FILTER_DESCRIPTION[] + = QT_TRANSLATE_NOOP("QtC::LanguageClient", "Locates symbols in the language server workspace."); const char LANGUAGECLIENT_WORKSPACE_CLASS_FILTER_ID[] = "Workspace Classes and Structs"; const char LANGUAGECLIENT_WORKSPACE_CLASS_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("QtC::LanguageClient", "Classes and Structs in Workspace"); +const char LANGUAGECLIENT_WORKSPACE_CLASS_FILTER_DESCRIPTION[] + = QT_TRANSLATE_NOOP("QtC::LanguageClient", + "Locates classes and structs in the language server workspace."); const char LANGUAGECLIENT_WORKSPACE_METHOD_FILTER_ID[] = "Workspace Functions and Methods"; const char LANGUAGECLIENT_WORKSPACE_METHOD_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("QtC::LanguageClient", "Functions and Methods in Workspace"); +const char LANGUAGECLIENT_WORKSPACE_METHOD_FILTER_DESCRIPTION[] + = QT_TRANSLATE_NOOP("QtC::LanguageClient", + "Locates functions and methods in the language server workspace."); + +const char CALL_HIERARCHY_FACTORY_ID[] = "LanguageClient.CallHierarchy"; } // namespace Constants } // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclientcompletionassist.cpp b/src/plugins/languageclient/languageclientcompletionassist.cpp index f0b66facc18..c5c86122fcd 100644 --- a/src/plugins/languageclient/languageclientcompletionassist.cpp +++ b/src/plugins/languageclient/languageclientcompletionassist.cpp @@ -447,7 +447,6 @@ IAssistProposal *LanguageClientCompletionAssistProcessor::perform() if (!Utils::Text::convertPosition(interface()->textDocument(), m_pos, &line, &column)) return nullptr; --line; // line is 0 based in the protocol - --column; // column is 0 based in the protocol params.setPosition({line, column}); params.setContext(context); params.setTextDocument( diff --git a/src/plugins/languageclient/languageclientinterface.cpp b/src/plugins/languageclient/languageclientinterface.cpp index 541d1c3ab05..d42e74c9b36 100644 --- a/src/plugins/languageclient/languageclientinterface.cpp +++ b/src/plugins/languageclient/languageclientinterface.cpp @@ -71,7 +71,7 @@ void BaseClientInterface::parseCurrentMessage() if (m_currentMessage.mimeType == JsonRpcMessage::jsonRpcMimeType()) { emit messageReceived(JsonRpcMessage(m_currentMessage)); } else { - emit error(Tr::tr("Cannot handle MIME type of message %1") + emit error(Tr::tr("Cannot handle MIME type \"%1\" of message.") .arg(QString::fromUtf8(m_currentMessage.mimeType))); } m_currentMessage = BaseMessage(); @@ -95,14 +95,14 @@ void StdIOClientInterface::startImpl() QTC_CHECK(!m_process->isRunning()); delete m_process; } - m_process = new QtcProcess; + m_process = new Process; m_process->setProcessMode(ProcessMode::Writer); - connect(m_process, &QtcProcess::readyReadStandardError, + connect(m_process, &Process::readyReadStandardError, this, &StdIOClientInterface::readError); - connect(m_process, &QtcProcess::readyReadStandardOutput, + connect(m_process, &Process::readyReadStandardOutput, this, &StdIOClientInterface::readOutput); - connect(m_process, &QtcProcess::started, this, &StdIOClientInterface::started); - connect(m_process, &QtcProcess::done, this, [this] { + connect(m_process, &Process::started, this, &StdIOClientInterface::started); + connect(m_process, &Process::done, this, [this] { m_logFile.flush(); if (m_process->result() != ProcessResult::FinishedWithSuccess) emit error(QString("%1 (see logs in \"%2\")") diff --git a/src/plugins/languageclient/languageclientinterface.h b/src/plugins/languageclient/languageclientinterface.h index 79401f889a1..7577f136913 100644 --- a/src/plugins/languageclient/languageclientinterface.h +++ b/src/plugins/languageclient/languageclientinterface.h @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include @@ -52,15 +52,12 @@ private: class LANGUAGECLIENT_EXPORT StdIOClientInterface : public BaseClientInterface { Q_OBJECT + Q_DISABLE_COPY_MOVE(StdIOClientInterface) + public: StdIOClientInterface(); ~StdIOClientInterface() override; - StdIOClientInterface(const StdIOClientInterface &) = delete; - StdIOClientInterface(StdIOClientInterface &&) = delete; - StdIOClientInterface &operator=(const StdIOClientInterface &) = delete; - StdIOClientInterface &operator=(StdIOClientInterface &&) = delete; - void startImpl() override; // These functions only have an effect if they are called before start @@ -74,7 +71,7 @@ protected: void sendData(const QByteArray &data) final; Utils::CommandLine m_cmd; Utils::FilePath m_workingDirectory; - Utils::QtcProcess *m_process = nullptr; + Utils::Process *m_process = nullptr; Utils::Environment m_env; private: diff --git a/src/plugins/languageclient/languageclientmanager.cpp b/src/plugins/languageclient/languageclientmanager.cpp index a41c4f01d6a..50a38071892 100644 --- a/src/plugins/languageclient/languageclientmanager.cpp +++ b/src/plugins/languageclient/languageclientmanager.cpp @@ -6,18 +6,22 @@ #include "languageclientplugin.h" #include "languageclientsymbolsupport.h" #include "languageclienttr.h" +#include "locatorfilter.h" #include #include #include #include +#include + +#include #include #include #include #include -#include +#include #include #include @@ -28,9 +32,9 @@ #include #include -#include #include +using namespace ExtensionSystem; using namespace LanguageServerProtocol; namespace LanguageClient { @@ -38,11 +42,20 @@ namespace LanguageClient { static Q_LOGGING_CATEGORY(Log, "qtc.languageclient.manager", QtWarningMsg) static LanguageClientManager *managerInstance = nullptr; -static bool g_shuttingDown = false; + +class LanguageClientManagerPrivate +{ + LanguageCurrentDocumentFilter m_currentDocumentFilter; + LanguageAllSymbolsFilter m_allSymbolsFilter; + LanguageClassesFilter m_classFilter; + LanguageFunctionsFilter m_functionFilter; +}; LanguageClientManager::LanguageClientManager(QObject *parent) - : QObject (parent) + : QObject(parent) { + managerInstance = this; + d.reset(new LanguageClientManagerPrivate); using namespace Core; using namespace ProjectExplorer; connect(EditorManager::instance(), &EditorManager::editorOpened, @@ -55,9 +68,9 @@ LanguageClientManager::LanguageClientManager(QObject *parent) this, &LanguageClientManager::documentContentsSaved); connect(EditorManager::instance(), &EditorManager::aboutToSave, this, &LanguageClientManager::documentWillSave); - connect(SessionManager::instance(), &SessionManager::projectAdded, + connect(ProjectManager::instance(), &ProjectManager::projectAdded, this, &LanguageClientManager::projectAdded); - connect(SessionManager::instance(), &SessionManager::projectRemoved, + connect(ProjectManager::instance(), &ProjectManager::projectRemoved, this, [&](Project *project) { project->disconnect(this); }); } @@ -73,7 +86,7 @@ void LanguageClientManager::init() if (managerInstance) return; QTC_ASSERT(LanguageClientPlugin::instance(), return); - managerInstance = new LanguageClientManager(LanguageClientPlugin::instance()); + new LanguageClientManager(LanguageClientPlugin::instance()); } void LanguageClient::LanguageClientManager::addClient(Client *client) @@ -91,7 +104,7 @@ void LanguageClient::LanguageClientManager::addClient(Client *client) &Client::initialized, managerInstance, [client](const LanguageServerProtocol::ServerCapabilities &capabilities) { - managerInstance->m_currentDocumentLocatorFilter.updateCurrentClient(); + emit managerInstance->clientInitialized(client); managerInstance->m_inspector.clientInitialized(client->name(), capabilities); }); connect(client, @@ -128,7 +141,7 @@ void LanguageClientManager::clientStarted(Client *client) QTC_ASSERT(client, return); if (client->state() != Client::Uninitialized) // do not proceed if we already received an error return; - if (g_shuttingDown) { + if (PluginManager::isShuttingDown()) { clientFinished(client); return; } @@ -154,7 +167,7 @@ void LanguageClientManager::clientFinished(Client *client) && client->state() != Client::ShutdownRequested; if (unexpectedFinish) { - if (!g_shuttingDown) { + if (!PluginManager::isShuttingDown()) { const QList &clientDocs = managerInstance->m_clientForDocument.keys(client); if (client->reset()) { @@ -176,7 +189,7 @@ void LanguageClientManager::clientFinished(Client *client) } } deleteClient(client); - if (g_shuttingDown && managerInstance->m_clients.isEmpty()) + if (isShutdownFinished()) emit managerInstance->shutdownFinished(); } @@ -224,18 +237,22 @@ void LanguageClientManager::deleteClient(Client *client) managerInstance->m_clients.removeAll(client); for (QList &clients : managerInstance->m_clientsForSetting) clients.removeAll(client); - client->deleteLater(); - if (!g_shuttingDown) + + // a deleteLater is not sufficient here as it pastes the delete later event at the end + // of the main event loop and when the plugins are shutdown we spawn an additional eventloop + // that will not handle the delete later event. Use invokeMethod with Qt::QueuedConnection + // instead. + QMetaObject::invokeMethod(client, [client] {delete client;}, Qt::QueuedConnection); + managerInstance->trackClientDeletion(client); + + if (!PluginManager::isShuttingDown()) emit instance()->clientRemoved(client); } void LanguageClientManager::shutdown() { QTC_ASSERT(managerInstance, return); - if (g_shuttingDown) - return; qCDebug(Log) << "shutdown manager"; - g_shuttingDown = true; const auto clients = managerInstance->clients(); for (Client *client : clients) shutdownClient(client); @@ -247,11 +264,6 @@ void LanguageClientManager::shutdown() }); } -bool LanguageClientManager::isShuttingDown() -{ - return g_shuttingDown; -} - LanguageClientManager *LanguageClientManager::instance() { return managerInstance; @@ -317,7 +329,7 @@ void LanguageClientManager::applySettings() continue; const Utils::FilePath filePath = textDocument->filePath(); for (ProjectExplorer::Project *project : - ProjectExplorer::SessionManager::projects()) { + ProjectExplorer::ProjectManager::projects()) { if (project->isKnownFile(filePath)) { Client *client = clientForProject[project]; if (!client) { @@ -448,6 +460,8 @@ QList LanguageClientManager::reachableClients() void LanguageClientManager::editorOpened(Core::IEditor *editor) { using namespace TextEditor; + using namespace Core; + if (auto *textEditor = qobject_cast(editor)) { if (TextEditorWidget *widget = textEditor->editorWidget()) { connect(widget, &TextEditorWidget::requestLinkAt, this, @@ -466,6 +480,14 @@ void LanguageClientManager::editorOpened(Core::IEditor *editor) if (auto client = clientForDocument(document)) client->symbolSupport().renameSymbol(document, cursor); }); + connect(widget, &TextEditorWidget::requestCallHierarchy, this, + [this, document = textEditor->textDocument()]() { + if (clientForDocument(document)) { + emit openCallHierarchy(); + NavigationWidget::activateSubWidget(Constants::CALL_HIERARCHY_FACTORY_ID, + Side::Left); + } + }); connect(widget, &TextEditorWidget::cursorPositionChanged, this, [widget]() { if (Client *client = clientForDocument(widget->textDocument())) if (client->reachable()) @@ -494,7 +516,7 @@ void LanguageClientManager::documentOpened(Core::IDocument *document) if (setting->m_startBehavior == BaseSettings::RequiresProject) { const Utils::FilePath &filePath = document->filePath(); for (ProjectExplorer::Project *project : - ProjectExplorer::SessionManager::projects()) { + ProjectExplorer::ProjectManager::projects()) { // check whether file is part of this project if (!project->isKnownFile(filePath)) continue; @@ -587,4 +609,24 @@ void LanguageClientManager::projectAdded(ProjectExplorer::Project *project) client->projectOpened(project); } +void LanguageClientManager::trackClientDeletion(Client *client) +{ + QTC_ASSERT(!m_scheduledForDeletion.contains(client->id()), return); + m_scheduledForDeletion.insert(client->id()); + connect(client, &QObject::destroyed, [this, id = client->id()](){ + m_scheduledForDeletion.remove(id); + if (isShutdownFinished()) + emit shutdownFinished(); + }); +} + +bool LanguageClientManager::isShutdownFinished() +{ + if (!PluginManager::isShuttingDown()) + return false; + QTC_ASSERT(managerInstance, return true); + return managerInstance->m_clients.isEmpty() + && managerInstance->m_scheduledForDeletion.isEmpty(); +} + } // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclientmanager.h b/src/plugins/languageclient/languageclientmanager.h index 6ceca307a61..0a38b3d3872 100644 --- a/src/plugins/languageclient/languageclientmanager.h +++ b/src/plugins/languageclient/languageclientmanager.h @@ -6,7 +6,6 @@ #include "client.h" #include "languageclient_global.h" #include "languageclientsettings.h" -#include "locatorfilter.h" #include "lspinspector.h" #include @@ -25,14 +24,15 @@ namespace ProjectExplorer { class Project; } namespace LanguageClient { +class LanguageClientManagerPrivate; class LanguageClientMark; class LANGUAGECLIENT_EXPORT LanguageClientManager : public QObject { Q_OBJECT + Q_DISABLE_COPY_MOVE(LanguageClientManager) + public: - LanguageClientManager(const LanguageClientManager &other) = delete; - LanguageClientManager(LanguageClientManager &&other) = delete; ~LanguageClientManager() override; static void init(); @@ -48,7 +48,7 @@ public: static void deleteClient(Client *client); static void shutdown(); - static bool isShuttingDown(); + static bool isShutdownFinished(); static LanguageClientManager *instance(); @@ -80,8 +80,10 @@ public: signals: void clientAdded(Client *client); + void clientInitialized(Client *client); void clientRemoved(Client *client); void shutdownFinished(); + void openCallHierarchy(); private: LanguageClientManager(QObject *parent); @@ -95,6 +97,8 @@ private: void updateProject(ProjectExplorer::Project *project); void projectAdded(ProjectExplorer::Project *project); + void trackClientDeletion(Client *client); + QList reachableClients(); QList m_clients; @@ -102,11 +106,9 @@ private: QList m_currentSettings; // owned QMap> m_clientsForSetting; QHash> m_clientForDocument; - DocumentLocatorFilter m_currentDocumentLocatorFilter; - WorkspaceLocatorFilter m_workspaceLocatorFilter; - WorkspaceClassLocatorFilter m_workspaceClassLocatorFilter; - WorkspaceMethodLocatorFilter m_workspaceMethodLocatorFilter; + std::unique_ptr d; LspInspector m_inspector; + QSet m_scheduledForDeletion; }; template bool LanguageClientManager::hasClients() diff --git a/src/plugins/languageclient/languageclientoutline.cpp b/src/plugins/languageclient/languageclientoutline.cpp index 0d7ae8c3715..b065754ec0d 100644 --- a/src/plugins/languageclient/languageclientoutline.cpp +++ b/src/plugins/languageclient/languageclientoutline.cpp @@ -42,67 +42,10 @@ const QList sortedSymbols(const QList &symbols) }); } -class LanguageClientOutlineItem : public Utils::TypedTreeItem -{ -public: - LanguageClientOutlineItem() = default; - LanguageClientOutlineItem(const SymbolInformation &info) - : m_name(info.name()) - , m_range(info.location().range()) - , m_type(info.kind()) - { } - - LanguageClientOutlineItem(const DocumentSymbol &info, const SymbolStringifier &stringifier) - : m_name(info.name()) - , m_detail(info.detail().value_or(QString())) - , m_range(info.range()) - , m_symbolStringifier(stringifier) - , m_type(info.kind()) - { - const QList children = sortedSymbols( - info.children().value_or(QList())); - for (const DocumentSymbol &child : children) - appendChild(new LanguageClientOutlineItem(child, stringifier)); - } - - // TreeItem interface - QVariant data(int column, int role) const override - { - switch (role) { - case Qt::DecorationRole: - return symbolIcon(m_type); - case Qt::DisplayRole: - return m_symbolStringifier - ? m_symbolStringifier(static_cast(m_type), m_name, m_detail) - : m_name; - default: - return Utils::TreeItem::data(column, role); - } - } - - Qt::ItemFlags flags(int column) const override - { - Q_UNUSED(column) - return Utils::TypedTreeItem::flags(column) - | Qt::ItemIsDragEnabled; - } - - Range range() const { return m_range; } - Position pos() const { return m_range.start(); } - bool contains(const Position &pos) const { return m_range.contains(pos); } - -private: - QString m_name; - QString m_detail; - Range m_range; - SymbolStringifier m_symbolStringifier; - int m_type = -1; -}; - class LanguageClientOutlineModel : public Utils::TreeModel { public: - using Utils::TreeModel::TreeModel; + LanguageClientOutlineModel(Client *client) : m_client(client) {} void setFilePath(const Utils::FilePath &filePath) { m_filePath = filePath; } void setInfo(const QList &info) @@ -115,12 +58,7 @@ public: { clear(); for (const DocumentSymbol &symbol : sortedSymbols(info)) - rootItem()->appendChild(new LanguageClientOutlineItem(symbol, m_symbolStringifier)); - } - - void setSymbolStringifier(const SymbolStringifier &stringifier) - { - m_symbolStringifier = stringifier; + rootItem()->appendChild(m_client->createOutlineItem(symbol)); } Qt::DropActions supportedDragActions() const override @@ -146,7 +84,7 @@ public: } private: - SymbolStringifier m_symbolStringifier; + Client * const m_client; Utils::FilePath m_filePath; }; @@ -195,6 +133,7 @@ LanguageClientOutlineWidget::LanguageClientOutlineWidget(Client *client, TextEditor::BaseTextEditor *editor) : m_client(client) , m_editor(editor) + , m_model(client) , m_view(this) , m_uri(m_client->hostPathToServerUri(editor->textDocument()->filePath())) { @@ -214,7 +153,6 @@ LanguageClientOutlineWidget::LanguageClientOutlineWidget(Client *client, layout->setSpacing(0); layout->addWidget(Core::ItemViewFind::createSearchableWrapper(&m_view)); setLayout(layout); - m_model.setSymbolStringifier(m_client->symbolStringifier()); m_model.setFilePath(editor->textDocument()->filePath()); m_proxyModel.setSourceModel(&m_model); m_view.setModel(&m_proxyModel); @@ -373,11 +311,11 @@ Utils::TreeViewComboBox *LanguageClientOutlineWidgetFactory::createComboBox( } OutlineComboBox::OutlineComboBox(Client *client, TextEditor::BaseTextEditor *editor) - : m_client(client) + : m_model(client) + , m_client(client) , m_editorWidget(editor->editorWidget()) , m_uri(m_client->hostPathToServerUri(editor->document()->filePath())) { - m_model.setSymbolStringifier(client->symbolStringifier()); m_proxyModel.setSourceModel(&m_model); const bool sorted = LanguageClientSettings::outlineComboBoxIsSorted(); m_proxyModel.sort(sorted ? 0 : -1); @@ -455,4 +393,40 @@ void OutlineComboBox::setSorted(bool sorted) m_proxyModel.sort(sorted ? 0 : -1); } +LanguageClientOutlineItem::LanguageClientOutlineItem(const SymbolInformation &info) + : m_name(info.name()) + , m_range(info.location().range()) + , m_type(info.kind()) +{ } + +LanguageClientOutlineItem::LanguageClientOutlineItem(Client *client, const DocumentSymbol &info) + : m_client(client) + , m_name(info.name()) + , m_detail(info.detail().value_or(QString())) + , m_range(info.range()) + , m_selectionRange(info.selectionRange()) + , m_type(info.kind()) +{ + const QList children = sortedSymbols( + info.children().value_or(QList())); + for (const DocumentSymbol &child : children) + appendChild(m_client->createOutlineItem(child)); +} + +QVariant LanguageClientOutlineItem::data(int column, int role) const +{ + switch (role) { + case Qt::DecorationRole: + return symbolIcon(m_type); + case Qt::DisplayRole: + return m_name; + default: + return Utils::TreeItem::data(column, role); + } +} +Qt::ItemFlags LanguageClientOutlineItem::flags(int column) const +{ + Q_UNUSED(column) + return Utils::TypedTreeItem::flags(column) | Qt::ItemIsDragEnabled; +} } // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclientoutline.h b/src/plugins/languageclient/languageclientoutline.h index e333563dc2d..4ddc9c79c08 100644 --- a/src/plugins/languageclient/languageclientoutline.h +++ b/src/plugins/languageclient/languageclientoutline.h @@ -3,7 +3,11 @@ #pragma once +#include "languageclient_global.h" + +#include #include +#include namespace TextEditor { class TextDocument; @@ -12,6 +16,40 @@ class BaseTextEditor; namespace Utils { class TreeViewComboBox; } namespace LanguageClient { +class Client; + +class LANGUAGECLIENT_EXPORT LanguageClientOutlineItem + : public Utils::TypedTreeItem +{ +public: + LanguageClientOutlineItem() = default; + LanguageClientOutlineItem(const LanguageServerProtocol::SymbolInformation &info); + LanguageClientOutlineItem(Client *client, const LanguageServerProtocol::DocumentSymbol &info); + + LanguageServerProtocol::Range range() const { return m_range; } + LanguageServerProtocol::Range selectionRange() const { return m_selectionRange; } + LanguageServerProtocol::Position pos() const { return m_range.start(); } + bool contains(const LanguageServerProtocol::Position &pos) const { + return m_range.contains(pos); + } + +protected: + // TreeItem interface + QVariant data(int column, int role) const override; + Qt::ItemFlags flags(int column) const override; + + QString name() const { return m_name; } + QString detail() const { return m_detail; } + int type() const { return m_type; } + +private: + Client * const m_client = nullptr; + QString m_name; + QString m_detail; + LanguageServerProtocol::Range m_range; + LanguageServerProtocol::Range m_selectionRange; + int m_type = -1; +}; class Client; diff --git a/src/plugins/languageclient/languageclientplugin.cpp b/src/plugins/languageclient/languageclientplugin.cpp index 7332f03e2b1..b6b411f8cb4 100644 --- a/src/plugins/languageclient/languageclientplugin.cpp +++ b/src/plugins/languageclient/languageclientplugin.cpp @@ -60,12 +60,12 @@ void LanguageClientPlugin::extensionsInitialized() ExtensionSystem::IPlugin::ShutdownFlag LanguageClientPlugin::aboutToShutdown() { LanguageClientManager::shutdown(); - if (LanguageClientManager::clients().isEmpty()) + if (LanguageClientManager::isShutdownFinished()) return ExtensionSystem::IPlugin::SynchronousShutdown; QTC_ASSERT(LanguageClientManager::instance(), return ExtensionSystem::IPlugin::SynchronousShutdown); connect(LanguageClientManager::instance(), &LanguageClientManager::shutdownFinished, - this, &ExtensionSystem::IPlugin::asynchronousShutdownFinished, Qt::QueuedConnection); + this, &ExtensionSystem::IPlugin::asynchronousShutdownFinished); return ExtensionSystem::IPlugin::AsynchronousShutdown; } diff --git a/src/plugins/languageclient/languageclientsettings.cpp b/src/plugins/languageclient/languageclientsettings.cpp index 6f2cf7dafe5..650b450a5d3 100644 --- a/src/plugins/languageclient/languageclientsettings.cpp +++ b/src/plugins/languageclient/languageclientsettings.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include #include #include @@ -104,17 +104,39 @@ private: QList m_removed; }; -class LanguageClientSettingsPageWidget : public QWidget +class LanguageClientSettingsPageWidget : public Core::IOptionsPageWidget { public: - LanguageClientSettingsPageWidget(LanguageClientSettingsModel &settings); + LanguageClientSettingsPageWidget(LanguageClientSettingsModel &settings, + QSet &changedSettings); + void currentChanged(const QModelIndex &index); int currentRow() const; void resetCurrentSettings(int row); void applyCurrentSettings(); + void apply() final + { + applyCurrentSettings(); + LanguageClientManager::applySettings(); + + for (BaseSettings *setting : m_model.removed()) { + for (Client *client : LanguageClientManager::clientsForSetting(setting)) + LanguageClientManager::shutdownClient(client); + } + + int row = currentRow(); + m_model.reset(LanguageClientManager::currentSettings()); + resetCurrentSettings(row); + } + + void finish() + { + m_settings.reset(LanguageClientManager::currentSettings()); + m_changedSettings.clear(); + } + private: - LanguageClientSettingsModel &m_settings; QTreeView *m_view = nullptr; struct CurrentSettings { BaseSettings *setting = nullptr; @@ -123,30 +145,10 @@ private: void addItem(const Utils::Id &clientTypeId); void deleteItem(); -}; -class LanguageClientSettingsPage : public Core::IOptionsPage -{ -public: - LanguageClientSettingsPage(); - ~LanguageClientSettingsPage() override; - - void init(); - - // IOptionsPage interface - QWidget *widget() override; - void apply() override; - void finish() override; - - QList settings() const; - QList changedSettings() const; - void addSettings(BaseSettings *settings); - void enableSettings(const QString &id, bool enable = true); - -private: + LanguageClientSettingsModel &m_settings; + QSet &m_changedSettings; LanguageClientSettingsModel m_model; - QSet m_changedSettings; - QPointer m_widget; }; QMap &clientTypes() @@ -155,9 +157,11 @@ QMap &clientTypes() return types; } -LanguageClientSettingsPageWidget::LanguageClientSettingsPageWidget(LanguageClientSettingsModel &settings) - : m_settings(settings) - , m_view(new QTreeView()) +LanguageClientSettingsPageWidget::LanguageClientSettingsPageWidget(LanguageClientSettingsModel &settings, + QSet &changedSettings) + : m_view(new QTreeView()) + , m_settings(settings) + , m_changedSettings(changedSettings) { auto mainLayout = new QVBoxLayout(); auto layout = new QHBoxLayout(); @@ -264,6 +268,23 @@ void LanguageClientSettingsPageWidget::deleteItem() m_settings.removeRows(index.row()); } +class LanguageClientSettingsPage : public Core::IOptionsPage +{ +public: + LanguageClientSettingsPage(); + + void init(); + + QList settings() const; + QList changedSettings() const; + void addSettings(BaseSettings *settings); + void enableSettings(const QString &id, bool enable = true); + +private: + LanguageClientSettingsModel m_model; + QSet m_changedSettings; +}; + LanguageClientSettingsPage::LanguageClientSettingsPage() { setId(Constants::LANGUAGECLIENT_SETTINGS_PAGE); @@ -271,18 +292,13 @@ LanguageClientSettingsPage::LanguageClientSettingsPage() setCategory(Constants::LANGUAGECLIENT_SETTINGS_CATEGORY); setDisplayCategory(Tr::tr(Constants::LANGUAGECLIENT_SETTINGS_TR)); setCategoryIconPath(":/languageclient/images/settingscategory_languageclient.png"); - connect(&m_model, &LanguageClientSettingsModel::dataChanged, [this](const QModelIndex &index) { + setWidgetCreator([this] { return new LanguageClientSettingsPageWidget(m_model, m_changedSettings); }); + QObject::connect(&m_model, &LanguageClientSettingsModel::dataChanged, [this](const QModelIndex &index) { if (BaseSettings *setting = m_model.settingForIndex(index)) m_changedSettings << setting->m_id; }); } -LanguageClientSettingsPage::~LanguageClientSettingsPage() -{ - if (m_widget) - delete m_widget; -} - void LanguageClientSettingsPage::init() { m_model.reset(LanguageClientSettings::fromSettings(Core::ICore::settings())); @@ -290,39 +306,6 @@ void LanguageClientSettingsPage::init() finish(); } -QWidget *LanguageClientSettingsPage::widget() -{ - if (!m_widget) - m_widget = new LanguageClientSettingsPageWidget(m_model); - return m_widget; -} - -void LanguageClientSettingsPage::apply() -{ - if (m_widget) - m_widget->applyCurrentSettings(); - LanguageClientManager::applySettings(); - - for (BaseSettings *setting : m_model.removed()) { - for (Client *client : LanguageClientManager::clientsForSetting(setting)) - LanguageClientManager::shutdownClient(client); - } - - if (m_widget) { - int row = m_widget->currentRow(); - m_model.reset(LanguageClientManager::currentSettings()); - m_widget->resetCurrentSettings(row); - } else { - m_model.reset(LanguageClientManager::currentSettings()); - } -} - -void LanguageClientSettingsPage::finish() -{ - m_model.reset(LanguageClientManager::currentSettings()); - m_changedSettings.clear(); -} - QList LanguageClientSettingsPage::settings() const { return m_model.settings(); @@ -1047,6 +1030,7 @@ bool LanguageFilter::operator!=(const LanguageFilter &other) const TextEditor::BaseTextEditor *jsonEditor() { using namespace TextEditor; + using namespace Utils::Text; BaseTextEditor *editor = PlainTextEditorFactory::createPlainTextEditor(); TextDocument *document = editor->textDocument(); TextEditorWidget *widget = editor->editorWidget(); @@ -1069,12 +1053,11 @@ TextEditor::BaseTextEditor *jsonEditor() QJsonDocument::fromJson(content.toUtf8(), &error); if (error.error == QJsonParseError::NoError) return; - const Utils::OptionalLineColumn lineColumn - = Utils::Text::convertPosition(document->document(), error.offset); - if (!lineColumn.has_value()) + const Position pos = Position::fromPositionInDocument(document->document(), error.offset); + if (!pos.isValid()) return; auto mark = new TextMark(Utils::FilePath(), - lineColumn->line, + pos.line, {::LanguageClient::Tr::tr("JSON Error"), jsonMarkId}); mark->setLineAnnotation(error.errorString()); mark->setColor(Utils::Theme::CodeModel_Error_TextMarkColor); diff --git a/src/plugins/languageclient/languageclientsymbolsupport.cpp b/src/plugins/languageclient/languageclientsymbolsupport.cpp index b5f301d4433..7069360b021 100644 --- a/src/plugins/languageclient/languageclientsymbolsupport.cpp +++ b/src/plugins/languageclient/languageclientsymbolsupport.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include #include #include @@ -193,7 +193,7 @@ bool SymbolSupport::supportsFindUsages(TextEditor::TextDocument *document) const struct ItemData { - Core::Search::TextRange range; + Utils::Text::Range range; QVariant userData; }; @@ -216,12 +216,12 @@ QStringList SymbolSupport::getFileContents(const Utils::FilePath &filePath) return fileContent.split("\n"); } -QList generateSearchResultItems( +Utils::SearchResultItems generateSearchResultItems( const QMap> &rangesInDocument, Core::SearchResult *search = nullptr, bool limitToProjects = false) { - QList result; + Utils::SearchResultItems result; const bool renaming = search && search->supportsReplace(); QString oldSymbolName; QVariantList userData; @@ -233,11 +233,11 @@ QList generateSearchResultItems( for (auto it = rangesInDocument.begin(); it != rangesInDocument.end(); ++it) { const Utils::FilePath &filePath = it.key(); - Core::SearchResultItem item; + Utils::SearchResultItem item; item.setFilePath(filePath); item.setUseTextEditorFont(true); if (renaming && limitToProjects) { - const bool fileBelongsToProject = ProjectExplorer::SessionManager::projectForFile( + const bool fileBelongsToProject = ProjectExplorer::ProjectManager::projectForFile( filePath); item.setSelectForReplacement(fileBelongsToProject); if (fileBelongsToProject @@ -264,7 +264,7 @@ QList generateSearchResultItems( return result; } -QList generateSearchResultItems( +Utils::SearchResultItems generateSearchResultItems( const LanguageClientArray &locations, const DocumentUri::PathMapper &pathMapper) { if (locations.isNull()) @@ -291,7 +291,7 @@ void SymbolSupport::handleFindReferencesResponse(const FindReferencesRequest::Re Tr::tr("Find References with %1 for:").arg(m_client->name()), "", wordUnderCursor); search->addResults(generateSearchResultItems(*result, m_client->hostPathMapper()), Core::SearchResult::AddOrdered); - connect(search, &Core::SearchResult::activated, [](const Core::SearchResultItem &item) { + connect(search, &Core::SearchResult::activated, [](const Utils::SearchResultItem &item) { Core::EditorManager::openEditorAtSearchResult(item); }); search->finishSearch(false); @@ -463,7 +463,7 @@ void SymbolSupport::requestRename(const TextDocumentPositionParams &positionPara search->popup(); } -QList generateReplaceItems(const WorkspaceEdit &edits, +Utils::SearchResultItems generateReplaceItems(const WorkspaceEdit &edits, Core::SearchResult *search, bool limitToProjects, const DocumentUri::PathMapper &pathMapper) @@ -506,7 +506,7 @@ Core::SearchResult *SymbolSupport::createSearch(const TextDocumentPositionParams if (callback) search->makeNonInteractive(callback); - connect(search, &Core::SearchResult::activated, [](const Core::SearchResultItem &item) { + connect(search, &Core::SearchResult::activated, [](const Utils::SearchResultItem &item) { Core::EditorManager::openEditorAtSearchResult(item); }); connect(search, &Core::SearchResult::replaceTextChanged, this, [this, search, positionParams]() { @@ -524,7 +524,7 @@ Core::SearchResult *SymbolSupport::createSearch(const TextDocumentPositionParams connect(search, &Core::SearchResult::replaceButtonClicked, this, [this, search, resetConnection](const QString & /*replaceText*/, - const QList &checkedItems) { + const Utils::SearchResultItems &checkedItems) { applyRename(checkedItems, search); disconnect(resetConnection); }); @@ -571,12 +571,12 @@ void SymbolSupport::handleRenameResponse(Core::SearchResult *search, } } -void SymbolSupport::applyRename(const QList &checkedItems, +void SymbolSupport::applyRename(const Utils::SearchResultItems &checkedItems, Core::SearchResult *search) { QSet affectedNonOpenFilePaths; QMap> editsForDocuments; - for (const Core::SearchResultItem &item : checkedItems) { + for (const Utils::SearchResultItem &item : checkedItems) { const auto filePath = Utils::FilePath::fromString(item.path().value(0)); if (!m_client->documentForFilePath(filePath)) affectedNonOpenFilePaths << filePath; @@ -616,12 +616,12 @@ QString SymbolSupport::derivePlaceholder(const QString &oldSymbol, const QString return m_defaultSymbolMapper ? m_defaultSymbolMapper(oldSymbol) : oldSymbol; } -Core::Search::TextRange SymbolSupport::convertRange(const Range &range) +Utils::Text::Range SymbolSupport::convertRange(const Range &range) { - auto convertPosition = [](const Position &pos) { - return Core::Search::TextPosition(pos.line() + 1, pos.character()); + const auto convertPosition = [](const Position &pos) { + return Utils::Text::Position{pos.line() + 1, pos.character()}; }; - return Core::Search::TextRange(convertPosition(range.start()), convertPosition(range.end())); + return {convertPosition(range.start()), convertPosition(range.end())}; } void SymbolSupport::setDefaultRenamingSymbolMapper(const SymbolMapper &mapper) diff --git a/src/plugins/languageclient/languageclientsymbolsupport.h b/src/plugins/languageclient/languageclientsymbolsupport.h index a4b910a9dbd..3dcc7b0ddc3 100644 --- a/src/plugins/languageclient/languageclientsymbolsupport.h +++ b/src/plugins/languageclient/languageclientsymbolsupport.h @@ -5,18 +5,15 @@ #include "languageclient_global.h" -#include #include #include +#include + #include -namespace Core { -class SearchResult; -class SearchResultItem; -} - +namespace Core { class SearchResult; } namespace LanguageServerProtocol { class MessageId; } namespace LanguageClient { @@ -46,7 +43,7 @@ public: const std::function &callback = {}, bool preferLowerCaseFileNames = true); - static Core::Search::TextRange convertRange(const LanguageServerProtocol::Range &range); + static Utils::Text::Range convertRange(const LanguageServerProtocol::Range &range); static QStringList getFileContents(const Utils::FilePath &filePath); using SymbolMapper = std::function; @@ -76,7 +73,7 @@ private: const std::function &callback, bool preferLowerCaseFileNames); void handleRenameResponse(Core::SearchResult *search, const LanguageServerProtocol::RenameRequest::Response &response); - void applyRename(const QList &checkedItems, Core::SearchResult *search); + void applyRename(const Utils::SearchResultItems &checkedItems, Core::SearchResult *search); QString derivePlaceholder(const QString &oldSymbol, const QString &newSymbol); Client *m_client = nullptr; diff --git a/src/plugins/languageclient/languageclientutils.cpp b/src/plugins/languageclient/languageclientutils.cpp index 44168d1dd25..c421b2a9376 100644 --- a/src/plugins/languageclient/languageclientutils.cpp +++ b/src/plugins/languageclient/languageclientutils.cpp @@ -97,11 +97,10 @@ void applyTextEdit(TextDocumentManipulatorInterface &manipulator, const TextEdit &edit, bool newTextIsSnippet) { - using namespace Utils::Text; const Range range = edit.range(); const QTextDocument *doc = manipulator.textCursorAt(manipulator.currentPosition()).document(); - const int start = positionInText(doc, range.start().line() + 1, range.start().character() + 1); - const int end = positionInText(doc, range.end().line() + 1, range.end().character() + 1); + const int start = Text::positionInText(doc, range.start().line() + 1, range.start().character() + 1); + const int end = Text::positionInText(doc, range.end().line() + 1, range.end().character() + 1); if (newTextIsSnippet) { manipulator.replace(start, end - start, {}); manipulator.insertCodeSnippet(start, edit.newText(), &parseSnippet); diff --git a/src/plugins/languageclient/locatorfilter.cpp b/src/plugins/languageclient/locatorfilter.cpp index ddcfc6b43bc..da03b2e7c50 100644 --- a/src/plugins/languageclient/locatorfilter.cpp +++ b/src/plugins/languageclient/locatorfilter.cpp @@ -3,358 +3,290 @@ #include "locatorfilter.h" -#include "documentsymbolcache.h" -#include "languageclient_global.h" +#include "clientrequesttask.h" +#include "currentdocumentsymbolsrequest.h" #include "languageclientmanager.h" #include "languageclienttr.h" -#include "languageclientutils.h" -#include - -#include -#include - -#include -#include +#include +#include #include -#include -#include #include +using namespace Core; using namespace LanguageServerProtocol; +using namespace Utils; namespace LanguageClient { -DocumentLocatorFilter::DocumentLocatorFilter() +void filterResults(QPromise &promise, const LocatorStorage &storage, Client *client, + const QList &results, const QList &filter) +{ + const auto doFilter = [&](const SymbolInformation &info) { + return filter.contains(SymbolKind(info.kind())); + }; + if (promise.isCanceled()) + return; + const QList filteredResults = filter.isEmpty() ? results + : Utils::filtered(results, doFilter); + const auto generateEntry = [client](const SymbolInformation &info) { + LocatorFilterEntry entry; + entry.displayName = info.name(); + if (std::optional container = info.containerName()) + entry.extraInfo = container.value_or(QString()); + entry.displayIcon = symbolIcon(info.kind()); + entry.linkForEditor = info.location().toLink(client->hostPathMapper()); + return entry; + }; + storage.reportOutput(Utils::transform(filteredResults, generateEntry)); +} + +LocatorMatcherTask locatorMatcher(Client *client, int maxResultCount, + const QList &filter) +{ + using namespace Tasking; + + TreeStorage storage; + TreeStorage> resultStorage; + + const auto onQuerySetup = [storage, client, maxResultCount](WorkspaceSymbolRequestTask &request) { + request.setClient(client); + WorkspaceSymbolParams params; + params.setQuery(storage->input()); + if (maxResultCount > 0) + params.setLimit(maxResultCount); + request.setParams(params); + }; + const auto onQueryDone = [resultStorage](const WorkspaceSymbolRequestTask &request) { + const std::optional> result + = request.response().result(); + if (result.has_value()) + *resultStorage = result->toList(); + }; + + const auto onFilterSetup = [storage, resultStorage, client, filter](Async &async) { + const QList results = *resultStorage; + if (results.isEmpty()) + return TaskAction::StopWithDone; + async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer()); + async.setConcurrentCallData(filterResults, *storage, client, results, filter); + return TaskAction::Continue; + }; + + const Group root { + Storage(resultStorage), + SymbolRequest(onQuerySetup, onQueryDone), + AsyncTask(onFilterSetup) + }; + return {root, storage}; +} + +LocatorMatcherTask allSymbolsMatcher(Client *client, int maxResultCount) +{ + return locatorMatcher(client, maxResultCount, {}); +} + +LocatorMatcherTask classMatcher(Client *client, int maxResultCount) +{ + return locatorMatcher(client, maxResultCount, {SymbolKind::Class, SymbolKind::Struct}); +} + +LocatorMatcherTask functionMatcher(Client *client, int maxResultCount) +{ + return locatorMatcher(client, maxResultCount, + {SymbolKind::Method, SymbolKind::Function, SymbolKind::Constructor}); +} + +static void filterCurrentResults(QPromise &promise, const LocatorStorage &storage, + const CurrentDocumentSymbolsData ¤tSymbolsData) +{ + Q_UNUSED(promise) + const auto docSymbolModifier = [](LocatorFilterEntry &entry, const DocumentSymbol &info, + const LocatorFilterEntry &parent) { + Q_UNUSED(parent) + entry.displayName = info.name(); + if (std::optional detail = info.detail()) + entry.extraInfo = *detail; + }; + // TODO: Pass promise into currentSymbols + storage.reportOutput(LanguageClient::currentDocumentSymbols(storage.input(), currentSymbolsData, + docSymbolModifier)); +} + +LocatorMatcherTask currentDocumentMatcher() +{ + using namespace Tasking; + + TreeStorage storage; + TreeStorage resultStorage; + + const auto onQuerySetup = [](CurrentDocumentSymbolsRequest &request) { + Q_UNUSED(request) + }; + const auto onQueryDone = [resultStorage](const CurrentDocumentSymbolsRequest &request) { + *resultStorage = request.currentDocumentSymbolsData(); + }; + + const auto onFilterSetup = [storage, resultStorage](Async &async) { + async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer()); + async.setConcurrentCallData(filterCurrentResults, *storage, *resultStorage); + }; + + const Group root { + Storage(resultStorage), + CurrentDocumentSymbolsRequestTask(onQuerySetup, onQueryDone), + AsyncTask(onFilterSetup) + }; + return {root, storage}; +} + +using MatcherCreator = std::function; + +static MatcherCreator creatorForType(MatcherType type) +{ + switch (type) { + case MatcherType::AllSymbols: return &allSymbolsMatcher; + case MatcherType::Classes: return &classMatcher; + case MatcherType::Functions: return &functionMatcher; + case MatcherType::CurrentDocumentSymbols: QTC_CHECK(false); return {}; + } + return {}; +} + +LocatorMatcherTasks languageClientMatchers(MatcherType type, const QList &clients, + int maxResultCount) +{ + if (type == MatcherType::CurrentDocumentSymbols) + return {currentDocumentMatcher()}; + const MatcherCreator creator = creatorForType(type); + if (!creator) + return {}; + LocatorMatcherTasks matchers; + for (Client *client : clients) + matchers << creator(client, maxResultCount); + return matchers; +} + +LanguageCurrentDocumentFilter::LanguageCurrentDocumentFilter() { setId(Constants::LANGUAGECLIENT_DOCUMENT_FILTER_ID); setDisplayName(Tr::tr(Constants::LANGUAGECLIENT_DOCUMENT_FILTER_DISPLAY_NAME)); - setDescription( - Tr::tr("Matches all symbols from the current document, based on a language server.")); + setDescription(Tr::tr(Constants::LANGUAGECLIENT_DOCUMENT_FILTER_DESCRIPTION)); setDefaultShortcutString("."); - setDefaultIncludedByDefault(false); setPriority(ILocatorFilter::Low); - connect(Core::EditorManager::instance(), &Core::EditorManager::currentEditorChanged, - this, &DocumentLocatorFilter::updateCurrentClient); } -void DocumentLocatorFilter::updateCurrentClient() +LocatorMatcherTasks LanguageCurrentDocumentFilter::matchers() { - resetSymbols(); - disconnect(m_resetSymbolsConnection); - - TextEditor::TextDocument *document = TextEditor::TextDocument::currentTextDocument(); - if (Client *client = LanguageClientManager::clientForDocument(document); - client && (client->locatorsEnabled() || m_forced)) { - - setEnabled(!m_forced); - if (m_symbolCache != client->documentSymbolCache()) { - disconnect(m_updateSymbolsConnection); - m_symbolCache = client->documentSymbolCache(); - m_updateSymbolsConnection = connect(m_symbolCache, &DocumentSymbolCache::gotSymbols, - this, &DocumentLocatorFilter::updateSymbols); - } - m_resetSymbolsConnection = connect(document, &Core::IDocument::contentsChanged, - this, &DocumentLocatorFilter::resetSymbols); - m_currentUri = client->hostPathToServerUri(document->filePath()); - m_pathMapper = client->hostPathMapper(); - } else { - disconnect(m_updateSymbolsConnection); - m_symbolCache.clear(); - m_currentUri.clear(); - setEnabled(false); - m_pathMapper = DocumentUri::PathMapper(); - } + return {currentDocumentMatcher()}; } -void DocumentLocatorFilter::updateSymbols(const DocumentUri &uri, - const DocumentSymbolsResult &symbols) +static LocatorFilterEntry entryForSymbolInfo(const SymbolInformation &info, + const DocumentUri::PathMapper &pathMapper) { - if (uri != m_currentUri) - return; - QMutexLocker locker(&m_mutex); - m_currentSymbols = symbols; - emit symbolsUpToDate(QPrivateSignal()); -} - -void DocumentLocatorFilter::resetSymbols() -{ - QMutexLocker locker(&m_mutex); - m_currentSymbols.reset(); -} - -static Core::LocatorFilterEntry generateLocatorEntry(const SymbolInformation &info, - Core::ILocatorFilter *filter, - DocumentUri::PathMapper pathMapper) -{ - Core::LocatorFilterEntry entry; - entry.filter = filter; + LocatorFilterEntry entry; entry.displayName = info.name(); if (std::optional container = info.containerName()) entry.extraInfo = container.value_or(QString()); entry.displayIcon = symbolIcon(info.kind()); - entry.internalData = QVariant::fromValue(info.location().toLink(pathMapper)); + entry.linkForEditor = info.location().toLink(pathMapper); return entry; } -Core::LocatorFilterEntry DocumentLocatorFilter::generateLocatorEntry(const SymbolInformation &info) +LocatorFilterEntries entriesForSymbolsInfo(const QList &infoList, + const QRegularExpression ®exp, const DocumentUri::PathMapper &pathMapper) { - QTC_ASSERT(m_pathMapper, return {}); - return LanguageClient::generateLocatorEntry(info, this, m_pathMapper); -} - -QList DocumentLocatorFilter::generateLocatorEntries( - const SymbolInformation &info, const QRegularExpression ®exp, - const Core::LocatorFilterEntry &parent) -{ - Q_UNUSED(parent) - if (regexp.match(info.name()).hasMatch()) - return {generateLocatorEntry(info)}; - return {}; -} - -Core::LocatorFilterEntry DocumentLocatorFilter::generateLocatorEntry( - const DocumentSymbol &info, - const Core::LocatorFilterEntry &parent) -{ - Q_UNUSED(parent) - Core::LocatorFilterEntry entry; - entry.filter = this; - entry.displayName = info.name(); - if (std::optional detail = info.detail()) - entry.extraInfo = detail.value_or(QString()); - entry.displayIcon = symbolIcon(info.kind()); - const Position &pos = info.range().start(); - entry.internalData = QVariant::fromValue(Utils::LineColumn(pos.line(), pos.character())); - return entry; -} - -QList DocumentLocatorFilter::generateLocatorEntries( - const DocumentSymbol &info, const QRegularExpression ®exp, - const Core::LocatorFilterEntry &parent) -{ - QList entries; - const QList children = info.children().value_or(QList()); - const bool hasMatch = regexp.match(info.name()).hasMatch(); - Core::LocatorFilterEntry entry; - if (hasMatch || !children.isEmpty()) - entry = generateLocatorEntry(info, parent); - if (hasMatch) - entries << entry; - for (const DocumentSymbol &child : children) - entries << generateLocatorEntries(child, regexp, entry); - return entries; -} - -template -QList DocumentLocatorFilter::generateEntries(const QList &list, - const QString &filter) -{ - QList entries; - FuzzyMatcher::CaseSensitivity caseSensitivity - = ILocatorFilter::caseSensitivity(filter) == Qt::CaseSensitive - ? FuzzyMatcher::CaseSensitivity::CaseSensitive - : FuzzyMatcher::CaseSensitivity::CaseInsensitive; - const QRegularExpression regexp = FuzzyMatcher::createRegExp(filter, caseSensitivity); - if (!regexp.isValid()) - return entries; - - for (const T &item : list) - entries << generateLocatorEntries(item, regexp, {}); - return entries; -} - -void DocumentLocatorFilter::prepareSearch(const QString &/*entry*/) -{ - QMutexLocker locker(&m_mutex); - if (m_symbolCache && !m_currentSymbols.has_value()) { - locker.unlock(); - m_symbolCache->requestSymbols(m_currentUri, Schedule::Now); + QTC_ASSERT(pathMapper, return {}); + LocatorFilterEntries entries; + for (const SymbolInformation &info : infoList) { + if (regexp.match(info.name()).hasMatch()) + entries << LanguageClient::entryForSymbolInfo(info, pathMapper); } + return entries; } -QList DocumentLocatorFilter::matchesFor( - QFutureInterface &future, const QString &entry) +LocatorFilterEntries entriesForDocSymbols(const QList &infoList, + const QRegularExpression ®exp, const FilePath &filePath, + const DocSymbolModifier &docSymbolModifier, const LocatorFilterEntry &parent = {}) { - QMutexLocker locker(&m_mutex); - if (!m_symbolCache) + LocatorFilterEntries entries; + for (const DocumentSymbol &info : infoList) { + const QList children = info.children().value_or(QList()); + const bool hasMatch = regexp.match(info.name()).hasMatch(); + LocatorFilterEntry entry; + if (hasMatch) { + entry.displayIcon = LanguageClient::symbolIcon(info.kind()); + const Position &pos = info.range().start(); + entry.linkForEditor = {filePath, pos.line() + 1, pos.character()}; + docSymbolModifier(entry, info, parent); + entries << entry; + } else { + entry = parent; + } + entries << entriesForDocSymbols(children, regexp, filePath, docSymbolModifier, entry); + } + return entries; +} + +Core::LocatorFilterEntries currentDocumentSymbols(const QString &input, + const CurrentDocumentSymbolsData ¤tSymbolsData, + const DocSymbolModifier &docSymbolModifier) +{ + const Qt::CaseSensitivity caseSensitivity = ILocatorFilter::caseSensitivity(input); + const QRegularExpression regExp = ILocatorFilter::createRegExp(input, caseSensitivity); + if (!regExp.isValid()) return {}; - if (!m_currentSymbols.has_value()) { - QEventLoop loop; - connect(this, &DocumentLocatorFilter::symbolsUpToDate, &loop, [&]() { loop.exit(1); }); - QFutureWatcher watcher; - connect(&watcher, - &QFutureWatcher::canceled, - &loop, - &QEventLoop::quit); - watcher.setFuture(future.future()); - locker.unlock(); - if (!loop.exec()) - return {}; - locker.relock(); - } - - QTC_ASSERT(m_currentSymbols.has_value(), return {}); - - if (auto list = std::get_if>(&*m_currentSymbols)) - return generateEntries(*list, entry); - else if (auto list = std::get_if>(&*m_currentSymbols)) - return generateEntries(*list, entry); + if (auto list = std::get_if>(¤tSymbolsData.m_symbols)) + return entriesForDocSymbols(*list, regExp, currentSymbolsData.m_filePath, docSymbolModifier); + else if (auto list = std::get_if>(¤tSymbolsData.m_symbols)) + return entriesForSymbolsInfo(*list, regExp, currentSymbolsData.m_pathMapper); return {}; } -void DocumentLocatorFilter::accept(const Core::LocatorFilterEntry &selection, - QString * /*newText*/, - int * /*selectionStart*/, - int * /*selectionLength*/) const -{ - if (selection.internalData.canConvert()) { - QTC_ASSERT(m_pathMapper, return); - auto lineColumn = qvariant_cast(selection.internalData); - const Utils::Link link(m_currentUri.toFilePath(m_pathMapper), - lineColumn.line + 1, - lineColumn.column); - Core::EditorManager::openEditorAt(link, {}, Core::EditorManager::AllowExternalEditor); - } else if (selection.internalData.canConvert()) { - Core::EditorManager::openEditorAt(qvariant_cast(selection.internalData), - {}, - Core::EditorManager::AllowExternalEditor); - } -} - -WorkspaceLocatorFilter::WorkspaceLocatorFilter() - : WorkspaceLocatorFilter(QVector()) -{} - -WorkspaceLocatorFilter::WorkspaceLocatorFilter(const QVector &filter) - : m_filterKinds(filter) +LanguageAllSymbolsFilter::LanguageAllSymbolsFilter() { setId(Constants::LANGUAGECLIENT_WORKSPACE_FILTER_ID); setDisplayName(Tr::tr(Constants::LANGUAGECLIENT_WORKSPACE_FILTER_DISPLAY_NAME)); + setDescription(Tr::tr(Constants::LANGUAGECLIENT_WORKSPACE_FILTER_DESCRIPTION)); setDefaultShortcutString(":"); - setDefaultIncludedByDefault(false); setPriority(ILocatorFilter::Low); } -void WorkspaceLocatorFilter::prepareSearch(const QString &entry) +LocatorMatcherTasks LanguageAllSymbolsFilter::matchers() { - prepareSearch(entry, LanguageClientManager::clients(), false); + return languageClientMatchers(MatcherType::AllSymbols, + Utils::filtered(LanguageClientManager::clients(), &Client::locatorsEnabled)); } -void WorkspaceLocatorFilter::prepareSearch(const QString &entry, const QList &clients) -{ - prepareSearch(entry, clients, true); -} - -void WorkspaceLocatorFilter::prepareSearch(const QString &entry, - const QList &clients, - bool force) -{ - m_pendingRequests.clear(); - m_results.clear(); - - WorkspaceSymbolParams params; - params.setQuery(entry); - if (m_maxResultCount > 0) - params.setLimit(m_maxResultCount); - - QMutexLocker locker(&m_mutex); - for (auto client : std::as_const(clients)) { - if (!client->reachable()) - continue; - if (!(force || client->locatorsEnabled())) - continue; - std::optional> capability - = client->capabilities().workspaceSymbolProvider(); - if (!capability.has_value()) - continue; - if (std::holds_alternative(*capability) && !std::get(*capability)) - continue; - WorkspaceSymbolRequest request(params); - request.setResponseCallback( - [this, client](const WorkspaceSymbolRequest::Response &response) { - handleResponse(client, response); - }); - m_pendingRequests[client] = request.id(); - client->sendMessage(request); - } -} - -QList WorkspaceLocatorFilter::matchesFor( - QFutureInterface &future, const QString & /*entry*/) -{ - QMutexLocker locker(&m_mutex); - if (!m_pendingRequests.isEmpty()) { - QEventLoop loop; - connect(this, &WorkspaceLocatorFilter::allRequestsFinished, &loop, [&]() { loop.exit(1); }); - QFutureWatcher watcher; - connect(&watcher, - &QFutureWatcher::canceled, - &loop, - &QEventLoop::quit); - watcher.setFuture(future.future()); - locker.unlock(); - if (!loop.exec()) - return {}; - - locker.relock(); - } - - - if (!m_filterKinds.isEmpty()) { - m_results = Utils::filtered(m_results, [&](const SymbolInfoWithPathMapper &info) { - return m_filterKinds.contains(SymbolKind(info.symbol.kind())); - }); - } - auto generateEntry = [this](const SymbolInfoWithPathMapper &info) { - return generateLocatorEntry(info.symbol, this, info.mapper); - }; - return Utils::transform(m_results, generateEntry).toList(); -} - -void WorkspaceLocatorFilter::accept(const Core::LocatorFilterEntry &selection, - QString * /*newText*/, - int * /*selectionStart*/, - int * /*selectionLength*/) const -{ - if (selection.internalData.canConvert()) - Core::EditorManager::openEditorAt(qvariant_cast(selection.internalData), - {}, - Core::EditorManager::AllowExternalEditor); -} - -void WorkspaceLocatorFilter::handleResponse(Client *client, - const WorkspaceSymbolRequest::Response &response) -{ - QMutexLocker locker(&m_mutex); - m_pendingRequests.remove(client); - auto result = response.result().value_or(LanguageClientArray()); - if (!result.isNull()) - m_results.append( - Utils::transform(result.toList().toVector(), [client](const SymbolInformation &info) { - return SymbolInfoWithPathMapper{info, client->hostPathMapper()}; - })); - if (m_pendingRequests.isEmpty()) - emit allRequestsFinished(QPrivateSignal()); -} - -WorkspaceClassLocatorFilter::WorkspaceClassLocatorFilter() - : WorkspaceLocatorFilter({SymbolKind::Class, SymbolKind::Struct}) +LanguageClassesFilter::LanguageClassesFilter() { setId(Constants::LANGUAGECLIENT_WORKSPACE_CLASS_FILTER_ID); setDisplayName(Tr::tr(Constants::LANGUAGECLIENT_WORKSPACE_CLASS_FILTER_DISPLAY_NAME)); + setDescription(Tr::tr(Constants::LANGUAGECLIENT_WORKSPACE_CLASS_FILTER_DESCRIPTION)); setDefaultShortcutString("c"); } -WorkspaceMethodLocatorFilter::WorkspaceMethodLocatorFilter() - : WorkspaceLocatorFilter({SymbolKind::Method, SymbolKind::Function, SymbolKind::Constructor}) +LocatorMatcherTasks LanguageClassesFilter::matchers() +{ + return languageClientMatchers(MatcherType::Classes, + Utils::filtered(LanguageClientManager::clients(), &Client::locatorsEnabled)); +} + +LanguageFunctionsFilter::LanguageFunctionsFilter() { setId(Constants::LANGUAGECLIENT_WORKSPACE_METHOD_FILTER_ID); setDisplayName(Tr::tr(Constants::LANGUAGECLIENT_WORKSPACE_METHOD_FILTER_DISPLAY_NAME)); + setDescription(Tr::tr(Constants::LANGUAGECLIENT_WORKSPACE_METHOD_FILTER_DESCRIPTION)); setDefaultShortcutString("m"); } +LocatorMatcherTasks LanguageFunctionsFilter::matchers() +{ + return languageClientMatchers(MatcherType::Functions, + Utils::filtered(LanguageClientManager::clients(), &Client::locatorsEnabled)); +} + } // namespace LanguageClient diff --git a/src/plugins/languageclient/locatorfilter.h b/src/plugins/languageclient/locatorfilter.h index 3eebd907a26..39d135723ce 100644 --- a/src/plugins/languageclient/locatorfilter.h +++ b/src/plugins/languageclient/locatorfilter.h @@ -3,129 +3,60 @@ #pragma once -#include "client.h" #include "languageclient_global.h" #include -#include -#include -#include - -#include -#include - -namespace Core { class IEditor; } +namespace LanguageServerProtocol { class DocumentSymbol; }; namespace LanguageClient { -class LANGUAGECLIENT_EXPORT DocumentLocatorFilter : public Core::ILocatorFilter +class Client; +class CurrentDocumentSymbolsData; + +using DocSymbolModifier = std::function; + +Core::LocatorFilterEntries LANGUAGECLIENT_EXPORT currentDocumentSymbols(const QString &input, + const CurrentDocumentSymbolsData ¤tSymbolsData, const DocSymbolModifier &docSymbolModifier); + +Core::LocatorMatcherTasks LANGUAGECLIENT_EXPORT languageClientMatchers( + Core::MatcherType type, const QList &clients = {}, int maxResultCount = 0); + +class LanguageAllSymbolsFilter : public Core::ILocatorFilter { - Q_OBJECT public: - DocumentLocatorFilter(); - - void updateCurrentClient(); - void prepareSearch(const QString &entry) override; - QList matchesFor(QFutureInterface &future, - const QString &entry) override; - void accept(const Core::LocatorFilterEntry &selection, - QString *newText, - int *selectionStart, - int *selectionLength) const override; - -signals: - void symbolsUpToDate(QPrivateSignal); - -protected: - void forceUse() { m_forced = true; } - - QPointer m_symbolCache; - LanguageServerProtocol::DocumentUri m_currentUri; + LanguageAllSymbolsFilter(); private: - void updateSymbols(const LanguageServerProtocol::DocumentUri &uri, - const LanguageServerProtocol::DocumentSymbolsResult &symbols); - void resetSymbols(); - - template - QList generateEntries(const QList &list, const QString &filter); - QList generateLocatorEntries( - const LanguageServerProtocol::SymbolInformation &info, - const QRegularExpression ®exp, - const Core::LocatorFilterEntry &parent); - QList generateLocatorEntries( - const LanguageServerProtocol::DocumentSymbol &info, - const QRegularExpression ®exp, - const Core::LocatorFilterEntry &parent); - virtual Core::LocatorFilterEntry generateLocatorEntry( - const LanguageServerProtocol::DocumentSymbol &info, - const Core::LocatorFilterEntry &parent); - virtual Core::LocatorFilterEntry generateLocatorEntry( - const LanguageServerProtocol::SymbolInformation &info); - - QMutex m_mutex; - QMetaObject::Connection m_updateSymbolsConnection; - QMetaObject::Connection m_resetSymbolsConnection; - std::optional m_currentSymbols; - LanguageServerProtocol::DocumentUri::PathMapper m_pathMapper; - bool m_forced = false; + Core::LocatorMatcherTasks matchers() final; }; -class LANGUAGECLIENT_EXPORT WorkspaceLocatorFilter : public Core::ILocatorFilter +class LanguageClassesFilter : public Core::ILocatorFilter { - Q_OBJECT public: - WorkspaceLocatorFilter(); - - /// request workspace symbols for all clients with enabled locator - void prepareSearch(const QString &entry) override; - /// force request workspace symbols for all given clients - void prepareSearch(const QString &entry, const QList &clients); - QList matchesFor(QFutureInterface &future, - const QString &entry) override; - void accept(const Core::LocatorFilterEntry &selection, - QString *newText, - int *selectionStart, - int *selectionLength) const override; - -signals: - void allRequestsFinished(QPrivateSignal); - -protected: - explicit WorkspaceLocatorFilter(const QVector &filter); - - void setMaxResultCount(qint64 limit) { m_maxResultCount = limit; } + LanguageClassesFilter(); private: - void prepareSearch(const QString &entry, const QList &clients, bool force); - void handleResponse(Client *client, - const LanguageServerProtocol::WorkspaceSymbolRequest::Response &response); - - QMutex m_mutex; - - struct SymbolInfoWithPathMapper - { - LanguageServerProtocol::SymbolInformation symbol; - LanguageServerProtocol::DocumentUri::PathMapper mapper; - }; - - QMap m_pendingRequests; - QVector m_results; - QVector m_filterKinds; - qint64 m_maxResultCount = 0; + Core::LocatorMatcherTasks matchers() final; }; -class LANGUAGECLIENT_EXPORT WorkspaceClassLocatorFilter : public WorkspaceLocatorFilter +class LanguageFunctionsFilter : public Core::ILocatorFilter { public: - WorkspaceClassLocatorFilter(); + LanguageFunctionsFilter(); + +private: + Core::LocatorMatcherTasks matchers() final; }; -class LANGUAGECLIENT_EXPORT WorkspaceMethodLocatorFilter : public WorkspaceLocatorFilter +class LanguageCurrentDocumentFilter : public Core::ILocatorFilter { public: - WorkspaceMethodLocatorFilter(); + LanguageCurrentDocumentFilter(); + +private: + Core::LocatorMatcherTasks matchers() final; }; } // namespace LanguageClient diff --git a/src/plugins/languageclient/progressmanager.cpp b/src/plugins/languageclient/progressmanager.cpp index 285080e4980..6b82d289dfc 100644 --- a/src/plugins/languageclient/progressmanager.cpp +++ b/src/plugins/languageclient/progressmanager.cpp @@ -8,6 +8,7 @@ #include #include +#include using namespace LanguageServerProtocol; @@ -81,9 +82,28 @@ void ProgressManager::beginProgress(const ProgressToken &token, const WorkDonePr auto interface = new QFutureInterface(); interface->reportStarted(); interface->setProgressRange(0, 100); // LSP always reports percentage of the task - const QString title = m_titles.value(token, begin.title()); - Core::FutureProgress *progress = Core::ProgressManager::addTask( - interface->future(), title, languageClientProgressId(token)); + ProgressItem progressItem; + progressItem.futureInterface = interface; + progressItem.title = m_titles.value(token, begin.title()); + if (LOGPROGRESS().isDebugEnabled()) + progressItem.timer.start(); + progressItem.showBarTimer = new QTimer(); + progressItem.showBarTimer->setSingleShot(true); + progressItem.showBarTimer->setInterval(750); + progressItem.showBarTimer->callOnTimeout([this, token]() { spawnProgressBar(token); }); + progressItem.showBarTimer->start(); + m_progress[token] = progressItem; + reportProgress(token, begin); +} + +void ProgressManager::spawnProgressBar(const LanguageServerProtocol::ProgressToken &token) +{ + ProgressItem &progressItem = m_progress[token]; + QTC_ASSERT(progressItem.futureInterface, return); + Core::FutureProgress *progress + = Core::ProgressManager::addTask(progressItem.futureInterface->future(), + progressItem.title, + languageClientProgressId(token)); const std::function clickHandler = m_clickHandlers.value(token); if (clickHandler) QObject::connect(progress, &Core::FutureProgress::clicked, clickHandler); @@ -92,23 +112,26 @@ void ProgressManager::beginProgress(const ProgressToken &token, const WorkDonePr QObject::connect(progress, &Core::FutureProgress::canceled, cancelHandler); else progress->setCancelEnabled(false); - m_progress[token] = {progress, interface}; - if (LOGPROGRESS().isDebugEnabled()) - m_timer[token].start(); - reportProgress(token, begin); + if (!progressItem.message.isEmpty()) { + progress->setSubtitle(progressItem.message); + progress->setSubtitleVisibleInStatusBar(true); + } + progressItem.progressInterface = progress; } void ProgressManager::reportProgress(const ProgressToken &token, const WorkDoneProgressReport &report) { - const LanguageClientProgress &progress = m_progress.value(token); + ProgressItem &progress = m_progress[token]; + const std::optional &message = report.message(); if (progress.progressInterface) { - const std::optional &message = report.message(); if (message.has_value()) { progress.progressInterface->setSubtitle(*message); const bool showSubtitle = !message->isEmpty(); progress.progressInterface->setSubtitleVisibleInStatusBar(showSubtitle); } + } else if (message.has_value()) { + progress.message = *message; } if (progress.futureInterface) { if (const std::optional &percentage = report.percentage(); percentage.has_value()) @@ -118,7 +141,7 @@ void ProgressManager::reportProgress(const ProgressToken &token, void ProgressManager::endProgress(const ProgressToken &token, const WorkDoneProgressEnd &end) { - const LanguageClientProgress &progress = m_progress.value(token); + const ProgressItem &progress = m_progress.value(token); const QString &message = end.message().value_or(QString()); if (progress.progressInterface) { if (!message.isEmpty()) { @@ -127,11 +150,11 @@ void ProgressManager::endProgress(const ProgressToken &token, const WorkDoneProg } progress.progressInterface->setSubtitle(message); progress.progressInterface->setSubtitleVisibleInStatusBar(!message.isEmpty()); - auto timer = m_timer.take(token); - if (timer.isValid()) { + if (progress.timer.isValid()) { qCDebug(LOGPROGRESS) << QString("%1 took %2") .arg(progress.progressInterface->title()) - .arg(QTime::fromMSecsSinceStartOfDay(timer.elapsed()) + .arg(QTime::fromMSecsSinceStartOfDay( + progress.timer.elapsed()) .toString(Qt::ISODateWithMs)); } } @@ -140,7 +163,8 @@ void ProgressManager::endProgress(const ProgressToken &token, const WorkDoneProg void ProgressManager::endProgressReport(const ProgressToken &token) { - const LanguageClientProgress &progress = m_progress.take(token); + ProgressItem progress = m_progress.take(token); + delete progress.showBarTimer; if (progress.futureInterface) progress.futureInterface->reportFinished(); delete progress.futureInterface; diff --git a/src/plugins/languageclient/progressmanager.h b/src/plugins/languageclient/progressmanager.h index 1e0ad781806..05b9640745d 100644 --- a/src/plugins/languageclient/progressmanager.h +++ b/src/plugins/languageclient/progressmanager.h @@ -11,6 +11,10 @@ #include #include +QT_BEGIN_NAMESPACE +class QTimer; +QT_END_NAMESPACE + namespace LanguageServerProtocol { class ProgressParams; class ProgressToken; @@ -46,15 +50,20 @@ private: const LanguageServerProtocol::WorkDoneProgressReport &report); void endProgress(const LanguageServerProtocol::ProgressToken &token, const LanguageServerProtocol::WorkDoneProgressEnd &end); + void spawnProgressBar(const LanguageServerProtocol::ProgressToken &token); - struct LanguageClientProgress { + struct ProgressItem + { QPointer progressInterface = nullptr; QFutureInterface *futureInterface = nullptr; + QElapsedTimer timer; + QTimer *showBarTimer = nullptr; + QString message; + QString title; }; - QMap m_progress; + QMap m_progress; QMap m_titles; - QMap m_timer; QMap> m_clickHandlers; QMap> m_cancelHandlers; }; diff --git a/src/plugins/macros/CMakeLists.txt b/src/plugins/macros/CMakeLists.txt index aef9a332ddc..588a9a330f3 100644 --- a/src/plugins/macros/CMakeLists.txt +++ b/src/plugins/macros/CMakeLists.txt @@ -9,7 +9,6 @@ add_qtc_plugin(Macros macrolocatorfilter.cpp macrolocatorfilter.h macromanager.cpp macromanager.h macrooptionspage.cpp macrooptionspage.h - macrooptionswidget.cpp macrooptionswidget.h macros.qrc macrosconstants.h macrosplugin.cpp macrosplugin.h diff --git a/src/plugins/macros/macrolocatorfilter.cpp b/src/plugins/macros/macrolocatorfilter.cpp index 43a9a489459..f43758bd034 100644 --- a/src/plugins/macros/macrolocatorfilter.cpp +++ b/src/plugins/macros/macrolocatorfilter.cpp @@ -8,13 +8,13 @@ #include "macrostr.h" #include -#include -#include #include +using namespace Core; using namespace Macros; using namespace Macros::Internal; +using namespace Utils; MacroLocatorFilter::MacroLocatorFilter() : m_icon(QPixmap(":/macros/images/macro.png")) @@ -26,54 +26,50 @@ MacroLocatorFilter::MacroLocatorFilter() setDefaultShortcutString("rm"); } -MacroLocatorFilter::~MacroLocatorFilter() = default; - -QList MacroLocatorFilter::matchesFor(QFutureInterface &future, const QString &entry) +LocatorMatcherTasks MacroLocatorFilter::matchers() { - Q_UNUSED(future) - QList goodEntries; - QList betterEntries; + using namespace Tasking; - const Qt::CaseSensitivity entryCaseSensitivity = caseSensitivity(entry); + TreeStorage storage; - const QMap ¯os = MacroManager::macros(); + const auto onSetup = [storage, icon = m_icon] { + const QString input = storage->input(); + const Qt::CaseSensitivity entryCaseSensitivity = caseSensitivity(input); + const QMap ¯os = MacroManager::macros(); + LocatorFilterEntries goodEntries; + LocatorFilterEntries betterEntries; + for (auto it = macros.cbegin(); it != macros.cend(); ++it) { + const QString displayName = it.key(); + const QString description = it.value()->description(); + int index = displayName.indexOf(input, 0, entryCaseSensitivity); + LocatorFilterEntry::HighlightInfo::DataType hDataType + = LocatorFilterEntry::HighlightInfo::DisplayName; + if (index < 0) { + index = description.indexOf(input, 0, entryCaseSensitivity); + hDataType = LocatorFilterEntry::HighlightInfo::ExtraInfo; + } - for (auto it = macros.cbegin(), end = macros.cend(); it != end; ++it) { - const QString displayName = it.key(); - const QString description = it.value()->description(); - - int index = displayName.indexOf(entry, 0, entryCaseSensitivity); - Core::LocatorFilterEntry::HighlightInfo::DataType hDataType = Core::LocatorFilterEntry::HighlightInfo::DisplayName; - if (index < 0) { - index = description.indexOf(entry, 0, entryCaseSensitivity); - hDataType = Core::LocatorFilterEntry::HighlightInfo::ExtraInfo; + if (index >= 0) { + LocatorFilterEntry filterEntry; + filterEntry.displayName = displayName; + filterEntry.acceptor = [displayName] { + IEditor *editor = EditorManager::currentEditor(); + if (editor) + editor->widget()->setFocus(Qt::OtherFocusReason); + MacroManager::instance()->executeMacro(displayName); + return AcceptResult(); + }; + filterEntry.displayIcon = icon; + filterEntry.extraInfo = description; + filterEntry.highlightInfo = LocatorFilterEntry::HighlightInfo(index, input.length(), + hDataType); + if (index == 0) + betterEntries.append(filterEntry); + else + goodEntries.append(filterEntry); + } } - - if (index >= 0) { - Core::LocatorFilterEntry filterEntry(this, displayName, {}, m_icon); - filterEntry.extraInfo = description; - filterEntry.highlightInfo = Core::LocatorFilterEntry::HighlightInfo(index, entry.length(), hDataType); - - if (index == 0) - betterEntries.append(filterEntry); - else - goodEntries.append(filterEntry); - } - } - betterEntries.append(goodEntries); - return betterEntries; -} - -void MacroLocatorFilter::accept(const Core::LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const -{ - Q_UNUSED(newText) - Q_UNUSED(selectionStart) - Q_UNUSED(selectionLength) - // Give the focus back to the editor - Core::IEditor *editor = Core::EditorManager::currentEditor(); - if (editor) - editor->widget()->setFocus(Qt::OtherFocusReason); - - MacroManager::instance()->executeMacro(selection.displayName); + storage->reportOutput(betterEntries + goodEntries); + }; + return {{Sync(onSetup), storage}}; } diff --git a/src/plugins/macros/macrolocatorfilter.h b/src/plugins/macros/macrolocatorfilter.h index 61fa7a34db4..b6acc6d2c7f 100644 --- a/src/plugins/macros/macrolocatorfilter.h +++ b/src/plugins/macros/macrolocatorfilter.h @@ -5,27 +5,17 @@ #include -#include - -namespace Macros { -namespace Internal { +namespace Macros::Internal { class MacroLocatorFilter : public Core::ILocatorFilter { - Q_OBJECT - public: MacroLocatorFilter(); - ~MacroLocatorFilter() override; - - QList matchesFor(QFutureInterface &future, - const QString &entry) override; - void accept(const Core::LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const override; private: + Core::LocatorMatcherTasks matchers() final; + const QIcon m_icon; }; -} // namespace Internal -} // namespace Macros +} // namespace Macros::Internal diff --git a/src/plugins/macros/macrooptionspage.cpp b/src/plugins/macros/macrooptionspage.cpp index 6295ea94e78..616547283e0 100644 --- a/src/plugins/macros/macrooptionspage.cpp +++ b/src/plugins/macros/macrooptionspage.cpp @@ -3,15 +3,195 @@ #include "macrooptionspage.h" +#include "macro.h" #include "macromanager.h" -#include "macrooptionswidget.h" #include "macrosconstants.h" #include "macrostr.h" +#include +#include +#include +#include + #include -namespace Macros { -namespace Internal { +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Macros::Internal { + +const int NAME_ROLE = Qt::UserRole; +const int WRITE_ROLE = Qt::UserRole + 1; + +class MacroOptionsWidget final : public Core::IOptionsPageWidget +{ +public: + MacroOptionsWidget(); + + void initialize(); + + void apply() final; + +private: + void remove(); + void changeCurrentItem(QTreeWidgetItem *current); + + void createTable(); + + void changeDescription(const QString &description); + + QStringList m_macroToRemove; + bool m_changingCurrent = false; + + QMap m_macroToChange; + + QTreeWidget *m_treeWidget; + QPushButton *m_removeButton; + QGroupBox *m_macroGroup; + QLineEdit *m_description; +}; + +MacroOptionsWidget::MacroOptionsWidget() +{ + m_treeWidget = new QTreeWidget; + m_treeWidget->setTextElideMode(Qt::ElideLeft); + m_treeWidget->setUniformRowHeights(true); + m_treeWidget->setSortingEnabled(true); + m_treeWidget->setColumnCount(3); + m_treeWidget->header()->setSortIndicatorShown(true); + m_treeWidget->header()->setStretchLastSection(true); + m_treeWidget->header()->setSortIndicator(0, Qt::AscendingOrder); + m_treeWidget->setHeaderLabels({Tr::tr("Name"), Tr::tr("Description"), Tr::tr("Shortcut")}); + + m_description = new QLineEdit; + + m_removeButton = new QPushButton(Tr::tr("Remove")); + + m_macroGroup = new QGroupBox(Tr::tr("Macro"), this); + + using namespace Layouting; + + Row { + Tr::tr("Description:"), m_description + }.attachTo(m_macroGroup); + + Column { + Group { + title(Tr::tr("Preferences")), + Row { + m_treeWidget, + Column { m_removeButton, st }, + } + }, + m_macroGroup + }.attachTo(this); + + connect(m_treeWidget, &QTreeWidget::currentItemChanged, + this, &MacroOptionsWidget::changeCurrentItem); + connect(m_removeButton, &QPushButton::clicked, + this, &MacroOptionsWidget::remove); + connect(m_description, &QLineEdit::textChanged, + this, &MacroOptionsWidget::changeDescription); + + initialize(); +} + +void MacroOptionsWidget::initialize() +{ + m_macroToRemove.clear(); + m_macroToChange.clear(); + m_treeWidget->clear(); + changeCurrentItem(nullptr); + + // Create the treeview + createTable(); +} + +void MacroOptionsWidget::createTable() +{ + QDir dir(MacroManager::macrosDirectory()); + const Utils::Id base = Utils::Id(Constants::PREFIX_MACRO); + for (Macro *macro : MacroManager::macros()) { + QFileInfo fileInfo(macro->fileName()); + if (fileInfo.absoluteDir() == dir.absolutePath()) { + auto macroItem = new QTreeWidgetItem(m_treeWidget); + macroItem->setText(0, macro->displayName()); + macroItem->setText(1, macro->description()); + macroItem->setData(0, NAME_ROLE, macro->displayName()); + macroItem->setData(0, WRITE_ROLE, macro->isWritable()); + + Core::Command *command = + Core::ActionManager::command(base.withSuffix(macro->displayName())); + if (command && command->action()) { + macroItem->setText(2, + command->action()->shortcut().toString(QKeySequence::NativeText)); + } + } + } +} + +void MacroOptionsWidget::changeCurrentItem(QTreeWidgetItem *current) +{ + m_changingCurrent = true; + m_removeButton->setEnabled(current); + m_macroGroup->setEnabled(current); + if (!current) { + m_description->clear(); + } else { + m_description->setText(current->text(1)); + m_description->setEnabled(current->data(0, WRITE_ROLE).toBool()); + } + m_changingCurrent = false; +} + +void MacroOptionsWidget::remove() +{ + QTreeWidgetItem *current = m_treeWidget->currentItem(); + m_macroToRemove.append(current->data(0, NAME_ROLE).toString()); + delete current; +} + +void MacroOptionsWidget::apply() +{ + // Remove macro + for (const QString &name : std::as_const(m_macroToRemove)) { + MacroManager::instance()->deleteMacro(name); + m_macroToChange.remove(name); + } + + // Change macro + for (auto it = m_macroToChange.cbegin(), end = m_macroToChange.cend(); it != end; ++it) + MacroManager::instance()->changeMacro(it.key(), it.value()); + + // Reinitialize the page + initialize(); +} + +void MacroOptionsWidget::changeDescription(const QString &description) +{ + QTreeWidgetItem *current = m_treeWidget->currentItem(); + if (m_changingCurrent || !current) + return; + + QString macroName = current->data(0, NAME_ROLE).toString(); + m_macroToChange[macroName] = description; + current->setText(1, description); + QFont font = current->font(1); + font.setItalic(true); + current->setFont(1, font); +} MacroOptionsPage::MacroOptionsPage() { @@ -21,5 +201,4 @@ MacroOptionsPage::MacroOptionsPage() setWidgetCreator([] { return new MacroOptionsWidget; }); } -} // Internal -} // Macros +} // Macros::Internal diff --git a/src/plugins/macros/macrooptionspage.h b/src/plugins/macros/macrooptionspage.h index e9948cb1673..511b8b2b870 100644 --- a/src/plugins/macros/macrooptionspage.h +++ b/src/plugins/macros/macrooptionspage.h @@ -5,8 +5,7 @@ #include -namespace Macros { -namespace Internal { +namespace Macros::Internal { class MacroOptionsPage final : public Core::IOptionsPage { @@ -14,5 +13,4 @@ public: MacroOptionsPage(); }; -} // namespace Internal -} // namespace Macros +} // Macros::Internal diff --git a/src/plugins/macros/macrooptionswidget.cpp b/src/plugins/macros/macrooptionswidget.cpp deleted file mode 100644 index c6a94d44980..00000000000 --- a/src/plugins/macros/macrooptionswidget.cpp +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright (C) 2016 Nicolas Arnaud-Cormos -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "macrooptionswidget.h" - -#include "macro.h" -#include "macromanager.h" -#include "macrosconstants.h" -#include "macrostr.h" - -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Macros::Internal { - -const int NAME_ROLE = Qt::UserRole; -const int WRITE_ROLE = Qt::UserRole + 1; - -MacroOptionsWidget::MacroOptionsWidget() -{ - m_treeWidget = new QTreeWidget; - m_treeWidget->setTextElideMode(Qt::ElideLeft); - m_treeWidget->setUniformRowHeights(true); - m_treeWidget->setSortingEnabled(true); - m_treeWidget->setColumnCount(3); - m_treeWidget->header()->setSortIndicatorShown(true); - m_treeWidget->header()->setStretchLastSection(true); - m_treeWidget->header()->setSortIndicator(0, Qt::AscendingOrder); - m_treeWidget->setHeaderLabels({Tr::tr("Name"), Tr::tr("Description"), Tr::tr("Shortcut")}); - - m_description = new QLineEdit; - - m_removeButton = new QPushButton(Tr::tr("Remove")); - - m_macroGroup = new QGroupBox(Tr::tr("Macro"), this); - - using namespace Utils::Layouting; - - Row { - Tr::tr("Description:"), m_description - }.attachTo(m_macroGroup); - - Column { - Group { - title(Tr::tr("Preferences")), - Row { - m_treeWidget, - Column { m_removeButton, st }, - } - }, - m_macroGroup - }.attachTo(this); - - connect(m_treeWidget, &QTreeWidget::currentItemChanged, - this, &MacroOptionsWidget::changeCurrentItem); - connect(m_removeButton, &QPushButton::clicked, - this, &MacroOptionsWidget::remove); - connect(m_description, &QLineEdit::textChanged, - this, &MacroOptionsWidget::changeDescription); - - initialize(); -} - -MacroOptionsWidget::~MacroOptionsWidget() = default; - -void MacroOptionsWidget::initialize() -{ - m_macroToRemove.clear(); - m_macroToChange.clear(); - m_treeWidget->clear(); - changeCurrentItem(nullptr); - - // Create the treeview - createTable(); -} - -void MacroOptionsWidget::createTable() -{ - QDir dir(MacroManager::macrosDirectory()); - const Utils::Id base = Utils::Id(Constants::PREFIX_MACRO); - for (Macro *macro : MacroManager::macros()) { - QFileInfo fileInfo(macro->fileName()); - if (fileInfo.absoluteDir() == dir.absolutePath()) { - auto macroItem = new QTreeWidgetItem(m_treeWidget); - macroItem->setText(0, macro->displayName()); - macroItem->setText(1, macro->description()); - macroItem->setData(0, NAME_ROLE, macro->displayName()); - macroItem->setData(0, WRITE_ROLE, macro->isWritable()); - - Core::Command *command = - Core::ActionManager::command(base.withSuffix(macro->displayName())); - if (command && command->action()) { - macroItem->setText(2, - command->action()->shortcut().toString(QKeySequence::NativeText)); - } - } - } -} - -void MacroOptionsWidget::changeCurrentItem(QTreeWidgetItem *current) -{ - m_changingCurrent = true; - m_removeButton->setEnabled(current); - m_macroGroup->setEnabled(current); - if (!current) { - m_description->clear(); - } else { - m_description->setText(current->text(1)); - m_description->setEnabled(current->data(0, WRITE_ROLE).toBool()); - } - m_changingCurrent = false; -} - -void MacroOptionsWidget::remove() -{ - QTreeWidgetItem *current = m_treeWidget->currentItem(); - m_macroToRemove.append(current->data(0, NAME_ROLE).toString()); - delete current; -} - -void MacroOptionsWidget::apply() -{ - // Remove macro - for (const QString &name : std::as_const(m_macroToRemove)) { - MacroManager::instance()->deleteMacro(name); - m_macroToChange.remove(name); - } - - // Change macro - for (auto it = m_macroToChange.cbegin(), end = m_macroToChange.cend(); it != end; ++it) - MacroManager::instance()->changeMacro(it.key(), it.value()); - - // Reinitialize the page - initialize(); -} - -void MacroOptionsWidget::changeDescription(const QString &description) -{ - QTreeWidgetItem *current = m_treeWidget->currentItem(); - if (m_changingCurrent || !current) - return; - - QString macroName = current->data(0, NAME_ROLE).toString(); - m_macroToChange[macroName] = description; - current->setText(1, description); - QFont font = current->font(1); - font.setItalic(true); - current->setFont(1, font); -} - -} // Macros::Internal diff --git a/src/plugins/macros/macrooptionswidget.h b/src/plugins/macros/macrooptionswidget.h deleted file mode 100644 index 9810da6dd18..00000000000 --- a/src/plugins/macros/macrooptionswidget.h +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (C) 2016 Nicolas Arnaud-Cormos -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -#include -#include - -QT_BEGIN_NAMESPACE -class QGroupBox; -class QLineEdit; -class QPushButton; -class QTreeWidget; -class QTreeWidgetItem; -QT_END_NAMESPACE - -namespace Macros { -namespace Internal { - -class MacroOptionsWidget final : public Core::IOptionsPageWidget -{ - Q_OBJECT - -public: - MacroOptionsWidget(); - ~MacroOptionsWidget() final; - - void initialize(); - - void apply() final; - -private: - void remove(); - void changeCurrentItem(QTreeWidgetItem *current); - - void createTable(); - - void changeDescription(const QString &description); - -private: - QStringList m_macroToRemove; - bool m_changingCurrent = false; - - QMap m_macroToChange; - - QTreeWidget *m_treeWidget; - QPushButton *m_removeButton; - QGroupBox *m_macroGroup; - QLineEdit *m_description; -}; - -} // namespace Internal -} // namespace Macros diff --git a/src/plugins/macros/macros.qbs b/src/plugins/macros/macros.qbs index f6579c7394d..c975f756d7d 100644 --- a/src/plugins/macros/macros.qbs +++ b/src/plugins/macros/macros.qbs @@ -29,8 +29,6 @@ QtcPlugin { "macromanager.h", "macrooptionspage.cpp", "macrooptionspage.h", - "macrooptionswidget.cpp", - "macrooptionswidget.h", "macros.qrc", "macrosconstants.h", "macrosplugin.cpp", diff --git a/src/plugins/macros/savedialog.cpp b/src/plugins/macros/savedialog.cpp index 3d8f7d6ada4..cb45f96da03 100644 --- a/src/plugins/macros/savedialog.cpp +++ b/src/plugins/macros/savedialog.cpp @@ -12,8 +12,6 @@ #include #include -using namespace Utils; - namespace Macros::Internal { SaveDialog::SaveDialog(QWidget *parent) : diff --git a/src/plugins/marketplace/productlistmodel.cpp b/src/plugins/marketplace/productlistmodel.cpp index cdfb8d79e5f..36b047abc21 100644 --- a/src/plugins/marketplace/productlistmodel.cpp +++ b/src/plugins/marketplace/productlistmodel.cpp @@ -269,7 +269,7 @@ void SectionedProducts::onImageDownloadFinished(QNetworkReply *reply) if (pixmap.loadFromData(data, imageFormat.toLatin1())) { const QString url = imageUrl.toString(); const int dpr = qApp->devicePixelRatio(); - pixmap = pixmap.scaled(ListModel::defaultImageSize * dpr, + pixmap = pixmap.scaled(WelcomePageHelpers::GridItemImageSize * dpr, Qt::KeepAspectRatio, Qt::SmoothTransformation); pixmap.setDevicePixelRatio(dpr); diff --git a/src/plugins/mcusupport/CMakeLists.txt b/src/plugins/mcusupport/CMakeLists.txt index 81fadd4ddfb..e2d81ae5a73 100644 --- a/src/plugins/mcusupport/CMakeLists.txt +++ b/src/plugins/mcusupport/CMakeLists.txt @@ -24,6 +24,7 @@ add_qtc_plugin(McuSupport settingshandler.cpp settingshandler.h mcuqmlprojectnode.cpp mcuqmlprojectnode.h mcubuildstep.cpp mcubuildstep.h + dialogs/mcukitcreationdialog.cpp dialogs/mcukitcreationdialog.h ) add_subdirectory(test) diff --git a/src/plugins/mcusupport/dialogs/mcukitcreationdialog.cpp b/src/plugins/mcusupport/dialogs/mcukitcreationdialog.cpp new file mode 100644 index 00000000000..9702fb77bfb --- /dev/null +++ b/src/plugins/mcusupport/dialogs/mcukitcreationdialog.cpp @@ -0,0 +1,134 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "mcukitcreationdialog.h" + +#include "../mcuabstractpackage.h" +#include "../mcusupportconstants.h" +#include "../mcusupporttr.h" + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace McuSupport::Internal { + +McuKitCreationDialog::McuKitCreationDialog(const MessagesList &messages, + const SettingsHandler::Ptr &settingsHandler, + McuPackagePtr qtMCUPackage, + QWidget *parent) + : QDialog(parent) + , m_messages(messages) +{ + resize(500, 300); + setWindowTitle(Tr::tr("Qt for MCUs Kit Creation")); + + m_iconLabel = new QLabel; + m_iconLabel->setAlignment(Qt::AlignTop); + + m_textLabel = new QLabel; + + m_informationLabel = new QLabel; + m_informationLabel->setWordWrap(true); + m_informationLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + m_informationLabel->setAlignment(Qt::AlignTop); + + m_qtMCUsPathLabel = new QLabel; + + auto line = new QFrame; + line->setFrameShape(QFrame::VLine); + line->setFrameShadow(QFrame::Sunken); + + auto buttonBox = new QDialogButtonBox(Qt::Vertical); + buttonBox->setStandardButtons(QDialogButtonBox::Ignore); + + m_messageCountLabel = new QLabel; + m_messageCountLabel->setAlignment(Qt::AlignCenter); + + using namespace Layouting; + Row { + Column { + Row { + m_iconLabel, + Column { + m_textLabel, + m_informationLabel, + }, + }, + m_qtMCUsPathLabel, + }, + line, + Column { + buttonBox, + m_messageCountLabel, + }, + }.attachTo(this); + + m_previousButton = buttonBox->addButton("<", QDialogButtonBox::ActionRole); + m_nextButton = buttonBox->addButton(">", QDialogButtonBox::ActionRole); + QPushButton *fixButton = buttonBox->addButton(Tr::tr("Fix"), QDialogButtonBox::ActionRole); + QPushButton *helpButton = buttonBox->addButton(Tr::tr("Help"), QDialogButtonBox::HelpRole); + + if (messages.size() == 1) { + m_nextButton->setVisible(false); + m_previousButton->setVisible(false); + } + //display first message + if (messages.size() > 1) + updateMessage(1); + + if (qtMCUPackage->isValidStatus()) + m_qtMCUsPathLabel->setText( + Tr::tr("Qt for MCUs path %1").arg(qtMCUPackage->path().toUserOutput())); + connect(m_nextButton, &QPushButton::clicked, [=] { updateMessage(1); }); + connect(m_previousButton, &QPushButton::clicked, [=] { updateMessage(-1); }); + connect(fixButton, &QPushButton::clicked, [=] { + // Open the MCU Options widget on the current platform + settingsHandler->setInitialPlatformName(m_messages[m_currentIndex].platform); + Core::ICore::showOptionsDialog(Constants::SETTINGS_ID); + // reset the initial platform name + settingsHandler->setInitialPlatformName(""); + }); + connect(helpButton, &QPushButton::clicked, [] { + QDesktopServices::openUrl(QUrl("https://doc.qt.io/QtForMCUs/qtul-prerequisites.html")); + }); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); +} + +void McuKitCreationDialog::updateMessage(const int inc) +{ + m_currentIndex += inc; + m_nextButton->setEnabled(m_currentIndex < (m_messages.size() - 1)); + m_previousButton->setEnabled(m_currentIndex > 0); + m_textLabel->setText(QString("%1 %2 : %3") + .arg(Tr::tr("Target"), + (m_messages[m_currentIndex].status == McuSupportMessage::Warning + ? Tr::tr("Warning") + : Tr::tr("Error")), + m_messages[m_currentIndex].platform)); + m_iconLabel->setPixmap( + QApplication::style() + ->standardIcon(m_messages[m_currentIndex].status == McuSupportMessage::Warning + ? QStyle::SP_MessageBoxWarning + : QStyle::SP_MessageBoxCritical) + .pixmap(64, 64)); + m_informationLabel->setText(QString("%1: %2

%3: %4") + .arg(Tr::tr("Package"), + m_messages[m_currentIndex].packageName, + Tr::tr("Status"), + m_messages.at(m_currentIndex).message)); + m_messageCountLabel->setText(QString("%1 / %2").arg(QString::number(m_currentIndex + 1), + QString::number(m_messages.size()))); +} + +} // namespace McuSupport::Internal diff --git a/src/plugins/mcusupport/dialogs/mcukitcreationdialog.h b/src/plugins/mcusupport/dialogs/mcukitcreationdialog.h new file mode 100644 index 00000000000..6caae59678f --- /dev/null +++ b/src/plugins/mcusupport/dialogs/mcukitcreationdialog.h @@ -0,0 +1,42 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "../mcusupport_global.h" +#include "../settingshandler.h" + +#include + +QT_BEGIN_NAMESPACE +class QLabel; +class QPushButton; +QT_END_NAMESPACE + +namespace McuSupport::Internal { + +class McuKitCreationDialog : public QDialog +{ + Q_OBJECT + +public: + explicit McuKitCreationDialog(const MessagesList &messages, + const SettingsHandler::Ptr &settingsHandler, + McuPackagePtr qtMCUPackage, + QWidget *parent = nullptr); + +private slots: + void updateMessage(const int inc); + +private: + int m_currentIndex = -1; + QLabel *m_iconLabel; + QLabel *m_textLabel; + QLabel *m_informationLabel; + QLabel *m_qtMCUsPathLabel; + QLabel *m_messageCountLabel; + QPushButton *m_previousButton; + QPushButton *m_nextButton; + const MessagesList &m_messages; +}; +} // namespace McuSupport::Internal diff --git a/src/plugins/mcusupport/mcubuildstep.cpp b/src/plugins/mcusupport/mcubuildstep.cpp index a26e8ed0d1e..3b0c639df5e 100644 --- a/src/plugins/mcusupport/mcubuildstep.cpp +++ b/src/plugins/mcusupport/mcubuildstep.cpp @@ -2,18 +2,21 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include "mcubuildstep.h" + #include "mcukitmanager.h" #include "mculegacyconstants.h" #include "mcusupportconstants.h" #include -#include +#include #include #include #include #include +#include #include +#include #include #include #include @@ -23,12 +26,25 @@ #include #include -#include +#include #include namespace McuSupport::Internal { +class DeployMcuProcessStep : public ProjectExplorer::AbstractProcessStep +{ +public: + static const Utils::Id id; + static void showError(const QString &text); + + DeployMcuProcessStep(ProjectExplorer::BuildStepList *bc, Utils::Id id); + +private: + QString findKitInformation(ProjectExplorer::Kit *kit, const QString &key); + QTemporaryDir m_tmpDir; +}; + const Utils::Id DeployMcuProcessStep::id = "QmlProject.Mcu.DeployStep"; void DeployMcuProcessStep::showError(const QString &text) @@ -58,9 +74,8 @@ DeployMcuProcessStep::DeployMcuProcessStep(ProjectExplorer::BuildStepList *bc, U QString root = findKitInformation(kit, Internal::Legacy::Constants::QUL_CMAKE_VAR); auto rootPath = Utils::FilePath::fromString(root); - auto cmd = addAspect(); + auto cmd = addAspect(); cmd->setSettingsKey("QmlProject.Mcu.ProcessStep.Command"); - cmd->setDisplayStyle(Utils::StringAspect::PathChooserDisplay); cmd->setExpectedKind(Utils::PathChooser::Command); cmd->setLabelText(QmlProjectManager::Tr::tr("Command:")); cmd->setFilePath(rootPath.pathAppended("/bin/qmlprojectexporter")); @@ -87,9 +102,8 @@ DeployMcuProcessStep::DeployMcuProcessStep(ProjectExplorer::BuildStepList *bc, U args->setLabelText(QmlProjectManager::Tr::tr("Arguments:")); args->setValue(Utils::ProcessArgs::joinArgs(arguments)); - auto outDir = addAspect(); + auto outDir = addAspect(); outDir->setSettingsKey("QmlProject.Mcu.ProcessStep.BuildDirectory"); - outDir->setDisplayStyle(Utils::StringAspect::PathChooserDisplay); outDir->setExpectedKind(Utils::PathChooser::Directory); outDir->setLabelText(QmlProjectManager::Tr::tr("Build directory:")); outDir->setPlaceHolderText(m_tmpDir.path()); @@ -119,13 +133,6 @@ QString DeployMcuProcessStep::findKitInformation(ProjectExplorer::Kit *kit, cons return {}; } -MCUBuildStepFactory::MCUBuildStepFactory() - : BuildStepFactory() -{ - setDisplayName(QmlProjectManager::Tr::tr("Qt for MCUs Deploy Step")); - registerStep(DeployMcuProcessStep::id); -} - ProjectExplorer::Kit *MCUBuildStepFactory::findMostRecentQulKit() { ProjectExplorer::Kit *mcuKit = nullptr; @@ -168,4 +175,11 @@ void MCUBuildStepFactory::updateDeployStep(ProjectExplorer::Target *target, bool } } + +MCUBuildStepFactory::MCUBuildStepFactory() +{ + setDisplayName(QmlProjectManager::Tr::tr("Qt for MCUs Deploy Step")); + registerStep(DeployMcuProcessStep::id); +} + } // namespace McuSupport::Internal diff --git a/src/plugins/mcusupport/mcubuildstep.h b/src/plugins/mcusupport/mcubuildstep.h index aebca09eba8..5a202336d7a 100644 --- a/src/plugins/mcusupport/mcubuildstep.h +++ b/src/plugins/mcusupport/mcubuildstep.h @@ -1,35 +1,17 @@ // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + #pragma once -#include #include -#include -#include - -#include - -#include namespace McuSupport::Internal { -class DeployMcuProcessStep : public ProjectExplorer::AbstractProcessStep -{ -public: - static const Utils::Id id; - static void showError(const QString &text); - - DeployMcuProcessStep(ProjectExplorer::BuildStepList *bc, Utils::Id id); - -private: - QString findKitInformation(ProjectExplorer::Kit *kit, const QString &key); - QTemporaryDir m_tmpDir; -}; - class MCUBuildStepFactory : public ProjectExplorer::BuildStepFactory { public: MCUBuildStepFactory(); + static ProjectExplorer::Kit *findMostRecentQulKit(); static void updateDeployStep(ProjectExplorer::Target *target, bool enabled); }; diff --git a/src/plugins/mcusupport/mcukitinformation.cpp b/src/plugins/mcusupport/mcukitinformation.cpp index 801b7cdeccb..2fd6f846e1e 100644 --- a/src/plugins/mcusupport/mcukitinformation.cpp +++ b/src/plugins/mcusupport/mcukitinformation.cpp @@ -22,7 +22,7 @@ public: void makeReadOnly() override {} void refresh() override {} - void addToLayout(Utils::Layouting::LayoutBuilder &) override {} + void addToLayout(Layouting::LayoutItem &) override {} }; } // anonymous namespace diff --git a/src/plugins/mcusupport/mcukitmanager.cpp b/src/plugins/mcusupport/mcukitmanager.cpp index 61f268f0215..00709b126f5 100644 --- a/src/plugins/mcusupport/mcukitmanager.cpp +++ b/src/plugins/mcusupport/mcukitmanager.cpp @@ -3,6 +3,7 @@ #include "mcukitmanager.h" #include "mculegacyconstants.h" +#include "mcusupport_global.h" #include "mcusupportoptions.h" #include "mcukitinformation.h" @@ -289,8 +290,7 @@ public: cMakeToolchainFile.toString().toUtf8()); if (!cMakeToolchainFile.exists()) { printMessage( - Tr::tr( - "Warning for target %1: missing CMake toolchain file expected at %2.") + Tr::tr("Warning for target %1: missing CMake toolchain file expected at %2.") .arg(generateKitNameFromTarget(mcuTarget), cMakeToolchainFile.toUserOutput()), false); @@ -301,8 +301,7 @@ public: "/lib/cmake/Qul/QulGenerators.cmake"); configMap.insert("QUL_GENERATORS", generatorsPath.toString().toUtf8()); if (!generatorsPath.exists()) { - printMessage(Tr::tr( - "Warning for target %1: missing QulGenerators expected at %2.") + printMessage(Tr::tr("Warning for target %1: missing QulGenerators expected at %2.") .arg(generateKitNameFromTarget(mcuTarget), generatorsPath.toUserOutput()), false); @@ -488,32 +487,37 @@ void createAutomaticKits(const SettingsHandler::Ptr &settingsHandler) { McuPackagePtr qtForMCUsPackage{createQtForMCUsPackage(settingsHandler)}; - const auto createKits = [qtForMCUsPackage, settingsHandler]() { + // add a list of package, board, errormessage, + MessagesList autoGenerationMessages; + + const auto createKits = [&autoGenerationMessages, qtForMCUsPackage, settingsHandler] { if (settingsHandler->isAutomaticKitCreationEnabled()) { qtForMCUsPackage->updateStatus(); if (!qtForMCUsPackage->isValidStatus()) { switch (qtForMCUsPackage->status()) { case McuAbstractPackage::Status::ValidPathInvalidPackage: { - printMessage(Tr::tr("Path %1 exists, but does not contain %2.") - .arg(qtForMCUsPackage->path().toUserOutput(), - qtForMCUsPackage->detectionPath().toUserOutput()), - true); + const QString message + = Tr::tr("Path %1 exists, but does not contain %2.") + .arg(qtForMCUsPackage->path().toUserOutput(), + qtForMCUsPackage->detectionPath().toUserOutput()); + autoGenerationMessages.push_back({qtForMCUsPackage->label(), "", message}); + printMessage(message, true); break; } case McuAbstractPackage::Status::InvalidPath: { - printMessage(Tr::tr( - "Path %1 does not exist. Add the path in Edit > Preferences > " - "Devices > MCU.") - .arg(qtForMCUsPackage->path().toUserOutput()), - true); + const QString message + = Tr::tr("Path %1 does not exist. Add the path in Edit > Preferences > " + "Devices > MCU.") + .arg(qtForMCUsPackage->path().toUserOutput()); + autoGenerationMessages.push_back({qtForMCUsPackage->label(), "", message}); + printMessage(message, true); break; } case McuAbstractPackage::Status::EmptyPath: { - printMessage( - Tr::tr( - "Missing %1. Add the path in Edit > Preferences > Devices > MCU.") - .arg(qtForMCUsPackage->detectionPath().toUserOutput()), - true); + const QString message = Tr::tr("Missing %1. Add the path in Edit > Preferences > Devices > MCU.") + .arg(qtForMCUsPackage->detectionPath().toUserOutput()); + autoGenerationMessages.push_back({qtForMCUsPackage->label(), "", message}); + printMessage(message, true); return; } default: @@ -523,11 +527,10 @@ void createAutomaticKits(const SettingsHandler::Ptr &settingsHandler) } if (CMakeProjectManager::CMakeToolManager::cmakeTools().isEmpty()) { - printMessage( - Tr::tr( - "No CMake tool was detected. Add a CMake tool in Edit > Preferences > " - "Kits > CMake."), - true); + const QString message = Tr::tr("No CMake tool was detected. Add a CMake tool in Edit > Preferences > " + "Kits > CMake."); + autoGenerationMessages.push_back({qtForMCUsPackage->label(), "", message}); + printMessage(message, true); return; } @@ -547,7 +550,7 @@ void createAutomaticKits(const SettingsHandler::Ptr &settingsHandler) // if no kits for this target, create if (target->isValid()) newKit(target.get(), qtForMCUsPackage); - target->printPackageProblems(); + target->handlePackageProblems(autoGenerationMessages); } } if (needsUpgrade) @@ -556,6 +559,9 @@ void createAutomaticKits(const SettingsHandler::Ptr &settingsHandler) }; createKits(); + McuSupportOptions::displayKitCreationMessages(autoGenerationMessages, + settingsHandler, + qtForMCUsPackage); } // Maintenance @@ -573,6 +579,7 @@ void upgradeKitsByCreatingNewPackage(const SettingsHandler::Ptr &settingsHandler McuSdkRepository repo{targetsAndPackages(qtForMCUsPackage, settingsHandler)}; + MessagesList messages; for (const auto &target : std::as_const(repo.mcuTargets)) { if (!matchingKits(target.get(), qtForMCUsPackage).empty()) // already up-to-date @@ -587,9 +594,11 @@ void upgradeKitsByCreatingNewPackage(const SettingsHandler::Ptr &settingsHandler if (target->isValid()) newKit(target.get(), qtForMCUsPackage); - target->printPackageProblems(); + target->handlePackageProblems(messages); } } + // Open the dialog showing warnings and errors in packages + McuSupportOptions::displayKitCreationMessages(messages, settingsHandler, qtForMCUsPackage); } // Maintenance @@ -744,11 +753,17 @@ static bool anyKitDescriptionFileExists(const FilePaths &jsonFiles, const QRegularExpressionMatch match = re.match(jsonFile.fileName()); QStringList kitsPropertiesFromFileName; if (match.hasMatch()) { - const QString toolchain = match.captured(1).replace( - "gnu", "gcc"); // kitFileName contains gnu while profiles.xml contains gcc + QString toolchain = match.captured(1); const QString vendor = match.captured(2); const QString device = match.captured(3); + /* + * file name of kit starts with "gnu" while in profiles.xml name of + * toolchain is "gcc" on Linux and "mingw" on Windows + */ + toolchain = HostOsInfo::isLinuxHost() ? toolchain.replace("gnu", "gcc") + : toolchain.replace("gnu", "mingw"); + kitsPropertiesFromFileName << toolchain << vendor << device; } diff --git a/src/plugins/mcusupport/mcuqmlprojectnode.h b/src/plugins/mcusupport/mcuqmlprojectnode.h index 2a06e5cde55..bf40a696f6b 100644 --- a/src/plugins/mcusupport/mcuqmlprojectnode.h +++ b/src/plugins/mcusupport/mcuqmlprojectnode.h @@ -9,7 +9,7 @@ #include #include -#include +#include #include #include diff --git a/src/plugins/mcusupport/mcusupport.qbs b/src/plugins/mcusupport/mcusupport.qbs index e6aec151a06..173fe55e6e8 100644 --- a/src/plugins/mcusupport/mcusupport.qbs +++ b/src/plugins/mcusupport/mcusupport.qbs @@ -20,6 +20,8 @@ QtcPlugin { files: [ "mcuabstractpackage.h", + "mcubuildstep.cpp", + "mcubuildstep.h", "mcupackage.cpp", "mcupackage.h", "mcutarget.cpp", @@ -56,10 +58,11 @@ QtcPlugin { "mcuhelpers.h", "settingshandler.h", "settingshandler.cpp", + "dialogs/mcukitcreationdialog.h", + "dialogs/mcukitcreationdialog.cpp", ] - Group { - name: "McuSupport test files" + QtcTestFiles { condition: qtc.testsEnabled && (qtc_gtest_gmock.hasRepo || qtc_gtest_gmock.externalLibsPresent) prefix: "test/" files: [ diff --git a/src/plugins/mcusupport/mcusupport.qrc b/src/plugins/mcusupport/mcusupport.qrc index abbfb969e3a..7aeb7b93873 100644 --- a/src/plugins/mcusupport/mcusupport.qrc +++ b/src/plugins/mcusupport/mcusupport.qrc @@ -20,5 +20,6 @@ wizards/qmlproject/module.qmlproject.tpl wizards/qmlproject/component.qml.tpl wizards/qmlproject/wizard.json + wizards/qmlproject/Qul.cmake diff --git a/src/plugins/mcusupport/mcusupport_global.h b/src/plugins/mcusupport/mcusupport_global.h index c1a20746a24..20de9a73298 100644 --- a/src/plugins/mcusupport/mcusupport_global.h +++ b/src/plugins/mcusupport/mcusupport_global.h @@ -30,4 +30,13 @@ static const QVersionNumber newVersion{2, 3}; using Targets = QList; using Packages = QSet; +struct McuSupportMessage +{ + QString packageName; + QString platform; + QString message; + enum Status { Warning, Error } status = Status::Error; +}; +using MessagesList = QList; + } // namespace McuSupport::Internal diff --git a/src/plugins/mcusupport/mcusupportconstants.h b/src/plugins/mcusupport/mcusupportconstants.h index 31e67c7b1ca..780880d9d11 100644 --- a/src/plugins/mcusupport/mcusupportconstants.h +++ b/src/plugins/mcusupport/mcusupportconstants.h @@ -22,5 +22,7 @@ const char SETTINGS_GROUP[]{"McuSupport"}; const char SETTINGS_KEY_PACKAGE_PREFIX[]{"Package_"}; const char SETTINGS_KEY_PACKAGE_QT_FOR_MCUS_SDK[]{"QtForMCUsSdk"}; // Key known by SDK installer const char SETTINGS_KEY_AUTOMATIC_KIT_CREATION[]{"AutomaticKitCreation"}; +// Used to decide which platform will be displayed first in the MCU Options page +const char SETTINGS_KEY_INITIAL_PLATFORM_KEY[]{"McuSupport.InitialPlatform"}; } // namespace McuSupport::Internal::Constants diff --git a/src/plugins/mcusupport/mcusupportdevice.h b/src/plugins/mcusupport/mcusupportdevice.h index 262edc39ab8..78bf6b68e3a 100644 --- a/src/plugins/mcusupport/mcusupportdevice.h +++ b/src/plugins/mcusupport/mcusupportdevice.h @@ -4,6 +4,7 @@ #pragma once #include +#include namespace McuSupport { namespace Internal { diff --git a/src/plugins/mcusupport/mcusupportoptions.cpp b/src/plugins/mcusupport/mcusupportoptions.cpp index 474314b6a6b..51cb1eacd81 100644 --- a/src/plugins/mcusupport/mcusupportoptions.cpp +++ b/src/plugins/mcusupport/mcusupportoptions.cpp @@ -3,9 +3,11 @@ #include "mcusupportoptions.h" +#include "dialogs/mcukitcreationdialog.h" #include "mcuhelpers.h" #include "mcukitmanager.h" #include "mcupackage.h" +#include "mcusupport_global.h" #include "mcusupportconstants.h" #include "mcusupportsdk.h" #include "mcusupporttr.h" @@ -19,6 +21,7 @@ #include #include #include +#include #include #include @@ -252,6 +255,30 @@ McuKitManager::UpgradeOption McuSupportOptions::askForKitUpgrades() return McuKitManager::UpgradeOption::Ignore; } +void McuSupportOptions::displayKitCreationMessages(const MessagesList &messages, + const SettingsHandler::Ptr &settingsHandler, + McuPackagePtr qtMCUsPackage) +{ + if (messages.isEmpty() || !qtMCUsPackage->isValidStatus()) + return; + static const char mcuKitCreationErrorInfoId[] = "ErrorWhileCreatingMCUKits"; + if (!Core::ICore::infoBar()->canInfoBeAdded(mcuKitCreationErrorInfoId)) + return; + + Utils::InfoBarEntry info(mcuKitCreationErrorInfoId, + Tr::tr("Errors while creating Qt for MCUs kits"), + Utils::InfoBarEntry::GlobalSuppression::Enabled); + + info.addCustomButton(Tr::tr("Details"), [=] { + auto popup = new McuKitCreationDialog(messages, settingsHandler, qtMCUsPackage); + popup->exec(); + delete popup; + Core::ICore::infoBar()->removeInfo(mcuKitCreationErrorInfoId); + }); + + Core::ICore::infoBar()->addInfo(info); +} + void McuSupportOptions::checkUpgradeableKits() { if (!qtForMCUsSdkPackage->isValidStatus() || sdkRepository.mcuTargets.isEmpty()) diff --git a/src/plugins/mcusupport/mcusupportoptions.h b/src/plugins/mcusupport/mcusupportoptions.h index a442c0864a7..1090f871004 100644 --- a/src/plugins/mcusupport/mcusupportoptions.h +++ b/src/plugins/mcusupport/mcusupportoptions.h @@ -61,6 +61,9 @@ public: [[nodiscard]] Utils::FilePath qulDirFromSettings() const; [[nodiscard]] Utils::FilePath qulDocsDir() const; static McuKitManager::UpgradeOption askForKitUpgrades(); + static void displayKitCreationMessages(const MessagesList &messages, + const SettingsHandler::Ptr &settingsHandler, + McuPackagePtr qtMCUsPackage); void registerQchFiles() const; void registerExamples() const; diff --git a/src/plugins/mcusupport/mcusupportoptionspage.cpp b/src/plugins/mcusupport/mcusupportoptionspage.cpp index d75568a8f66..a05a3771b07 100644 --- a/src/plugins/mcusupport/mcusupportoptionspage.cpp +++ b/src/plugins/mcusupport/mcusupportoptionspage.cpp @@ -350,10 +350,17 @@ void McuSupportOptionsWidget::populateMcuTargetsComboBox() { m_options.populatePackagesAndTargets(); m_mcuTargetsComboBox->clear(); + int initialPlatformIndex = 0; + int targetsCounter = -1; m_mcuTargetsComboBox->addItems( - Utils::transform(m_options.sdkRepository.mcuTargets, [](const McuTargetPtr &t) { + Utils::transform(m_options.sdkRepository.mcuTargets, [&](const McuTargetPtr &t) { + if (t->platform().name == m_settingsHandler->initialPlatformName()) + initialPlatformIndex = m_options.sdkRepository.mcuTargets.indexOf(t); + targetsCounter++; return McuKitManager::generateKitNameFromTarget(t.get()); })); + if (targetsCounter != -1) + m_mcuTargetsComboBox->setCurrentIndex(initialPlatformIndex); updateStatus(); } diff --git a/src/plugins/mcusupport/mcusupportplugin.cpp b/src/plugins/mcusupport/mcusupportplugin.cpp index 274cd738e11..f2ec2ef69aa 100644 --- a/src/plugins/mcusupport/mcusupportplugin.cpp +++ b/src/plugins/mcusupport/mcusupportplugin.cpp @@ -28,8 +28,8 @@ #include #include #include +#include #include -#include #include #include @@ -115,8 +115,8 @@ void McuSupportPlugin::initialize() setObjectName("McuSupportPlugin"); dd = new McuSupportPluginPrivate; - connect(SessionManager::instance(), - &SessionManager::projectFinishedParsing, + connect(ProjectManager::instance(), + &ProjectManager::projectFinishedParsing, updateMCUProjectTree); dd->m_options.registerQchFiles(); diff --git a/src/plugins/mcusupport/mcusupportversiondetection.cpp b/src/plugins/mcusupport/mcusupportversiondetection.cpp index b31d01d8bc6..f92a443c4fc 100644 --- a/src/plugins/mcusupport/mcusupportversiondetection.cpp +++ b/src/plugins/mcusupport/mcusupportversiondetection.cpp @@ -3,7 +3,7 @@ #include "mcusupportversiondetection.h" -#include +#include #include #include @@ -41,7 +41,7 @@ QString McuPackageExecutableVersionDetector::parseVersion(const FilePath &packag return {}; const int timeout = 3000; // usually runs below 1s, but we want to be on the safe side - QtcProcess process; + Process process; process.setCommand({binaryPath, m_detectionArgs}); process.start(); if (!process.waitForFinished(timeout) || process.result() != ProcessResult::FinishedWithSuccess) diff --git a/src/plugins/mcusupport/mcutarget.cpp b/src/plugins/mcusupport/mcutarget.cpp index 2040c260398..d7a3b0fe397 100644 --- a/src/plugins/mcusupport/mcutarget.cpp +++ b/src/plugins/mcusupport/mcutarget.cpp @@ -1,11 +1,12 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "mcutarget.h" #include "mcukitmanager.h" #include "mcupackage.h" +#include "mcusupport_global.h" #include "mcusupportplugin.h" #include "mcusupporttr.h" -#include "mcutarget.h" #include @@ -82,22 +83,33 @@ QString McuTarget::desktopCompilerId() const return QLatin1String("invalid"); } -void McuTarget::printPackageProblems() const +void McuTarget::handlePackageProblems(MessagesList &messages) const { for (auto package : packages()) { package->updateStatus(); - if (!package->isValidStatus()) + if (!package->isValidStatus()) { printMessage(Tr::tr("Error creating kit for target %1, package %2: %3") .arg(McuKitManager::generateKitNameFromTarget(this), package->label(), package->statusText()), true); - if (package->status() == McuAbstractPackage::Status::ValidPackageMismatchedVersion) + messages.push_back({package->label(), + this->platform().name, + package->statusText(), + McuSupportMessage::Error}); + } + if (package->status() == McuAbstractPackage::Status::ValidPackageMismatchedVersion) { printMessage(Tr::tr("Warning creating kit for target %1, package %2: %3") .arg(McuKitManager::generateKitNameFromTarget(this), package->label(), package->statusText()), false); + + messages.push_back({package->label(), + this->platform().name, + package->statusText(), + McuSupportMessage::Warning}); + } } } diff --git a/src/plugins/mcusupport/mcutarget.h b/src/plugins/mcusupport/mcutarget.h index 57f28030be7..8ee19f54ab0 100644 --- a/src/plugins/mcusupport/mcutarget.h +++ b/src/plugins/mcusupport/mcutarget.h @@ -54,7 +54,7 @@ public: int colorDepth() const; bool isValid() const; QString desktopCompilerId() const; - void printPackageProblems() const; + void handlePackageProblems(MessagesList &messages) const; private: const QVersionNumber m_qulVersion; diff --git a/src/plugins/mcusupport/settingshandler.cpp b/src/plugins/mcusupport/settingshandler.cpp index d292ea6cd69..67fbd27f978 100644 --- a/src/plugins/mcusupport/settingshandler.cpp +++ b/src/plugins/mcusupport/settingshandler.cpp @@ -68,4 +68,17 @@ void SettingsHandler::setAutomaticKitCreation(bool isEnabled) settings->setValue(automaticKitCreationSettingsKey, isEnabled); } +void SettingsHandler::setInitialPlatformName(const QString &platform) +{ + QSettings *settings = Core::ICore::settings(QSettings::UserScope); + settings->setValue(Constants::SETTINGS_KEY_INITIAL_PLATFORM_KEY, platform); +} + +QString SettingsHandler::initialPlatformName() const +{ + QSettings *settings = Core::ICore::settings(QSettings::UserScope); + const QString name + = settings->value(Constants::SETTINGS_KEY_INITIAL_PLATFORM_KEY, "").toString(); + return name; +} } // namespace McuSupport::Internal diff --git a/src/plugins/mcusupport/settingshandler.h b/src/plugins/mcusupport/settingshandler.h index 02bbbcf26df..145049e31db 100644 --- a/src/plugins/mcusupport/settingshandler.h +++ b/src/plugins/mcusupport/settingshandler.h @@ -27,5 +27,8 @@ public: virtual bool isAutomaticKitCreationEnabled() const; void setAutomaticKitCreation(bool isEnabled); + void setInitialPlatformName(const QString &platform); + QString initialPlatformName() const; + }; //class SettingsHandler } // namespace McuSupport::Internal diff --git a/src/plugins/mcusupport/test/unittest.cpp b/src/plugins/mcusupport/test/unittest.cpp index b9fc108ef76..a8a8f091399 100644 --- a/src/plugins/mcusupport/test/unittest.cpp +++ b/src/plugins/mcusupport/test/unittest.cpp @@ -152,11 +152,7 @@ const QString renesasProgrammerEnvVar{"RenesasFlashProgrammer_PATH"}; const char renesasProgrammerLabel[]{"Renesas Flash Programmer"}; const QString renesasProgrammerDetectionPath{HostOsInfo::withExecutableSuffix("rfp-cli")}; -const char renesasE2StudioCmakeVar[]{"EK_RA6M3G_E2_PROJECT_PATH"}; -const char renesasE2StudioDefaultPath[]{"%{Env:HOME}/e2_studio/workspace"}; const QString renesasE2StudioPath{(FileUtils::homePath() / "/e2_studio/workspace").toUserOutput()}; -const char renesasE2StudioLabel[]{"Path to project for Renesas e2 Studio"}; -const char renesasE2StudioSetting[]{"RenesasE2StudioPath"}; const char cypressProgrammerSetting[]{"CypressAutoFlashUtil"}; const char cypressProgrammerCmakeVar[]{"INFINEON_AUTO_FLASH_UTILITY_DIR"}; @@ -1760,9 +1756,9 @@ void McuSupportTest::test_nonemptyVersionDetector() // pkgDesc.versionDetection.xmlAttribute left empty pkgDesc.shouldAddToSystemPath = false; const auto package = targetFactory.createPackage(pkgDesc); - QVERIFY(package->getVersionDetector() != nullptr); - QCOMPARE(typeid(*package->getVersionDetector()).name(), - typeid(McuPackageExecutableVersionDetector).name()); + const McuPackageVersionDetector *detector = package->getVersionDetector(); + QVERIFY(detector != nullptr); + QCOMPARE(typeid(*detector).name(), typeid(McuPackageExecutableVersionDetector).name()); } void McuSupportTest::test_emptyVersionDetector() diff --git a/src/plugins/mcusupport/wizards/qmlproject/CMakeLists.txt b/src/plugins/mcusupport/wizards/qmlproject/CMakeLists.txt index 49a4a8f949f..47eed40c785 100644 --- a/src/plugins/mcusupport/wizards/qmlproject/CMakeLists.txt +++ b/src/plugins/mcusupport/wizards/qmlproject/CMakeLists.txt @@ -4,5 +4,4 @@ project(%{CorrectedProjectName} VERSION 0.0.1 LANGUAGES C CXX ASM) find_package(Qul) -qul_add_target(%{CorrectedProjectName} QML_PROJECT %{QmlProjectFile} GENERATE_ENTRYPOINT) -app_target_setup_os(%{CorrectedProjectName}) +add_subdirectory(qmlproject) diff --git a/src/plugins/mcusupport/wizards/qmlproject/Qul.cmake b/src/plugins/mcusupport/wizards/qmlproject/Qul.cmake new file mode 100644 index 00000000000..1ece8f48d6d --- /dev/null +++ b/src/plugins/mcusupport/wizards/qmlproject/Qul.cmake @@ -0,0 +1,3 @@ + +qul_add_target(%{CorrectedProjectName} QML_PROJECT %{QmlProjectFile} GENERATE_ENTRYPOINT) +app_target_setup_os(%{CorrectedProjectName}) diff --git a/src/plugins/mcusupport/wizards/qmlproject/wizard.json b/src/plugins/mcusupport/wizards/qmlproject/wizard.json index 173c430eeb4..9c1e036e910 100644 --- a/src/plugins/mcusupport/wizards/qmlproject/wizard.json +++ b/src/plugins/mcusupport/wizards/qmlproject/wizard.json @@ -3,7 +3,7 @@ "supportedProjectTypes": [ "CMakeProjectManager.CMakeProject" ], "id": "M.McuSupportApplication", "category": "D.ApplicationMCU", - "trDescription": "Suitable for Qt for MCUs versions 2.4 and later. Creates a Qt for MCUs application with a simple UI, based on qmlproject.", + "trDescription": "Suitable for Qt for MCUs versions 2.4 and later. Creates an application that uses a subset of Qt QML and Qt Quick Controls types (as supported by Qt for MCUs) that you can deploy, run, and debug on MCU boards.", "trDisplayName": "Qt for MCUs Application", "trDisplayCategory": "QmlProject Application (Qt for MCUs)", "icon": "../icon.png", @@ -12,6 +12,7 @@ "options": [ + { "key": "QmlProjectDirectory", "value": "%{ProjectDirectory}/qmlproject"}, { "key": "CorrectedProjectName", "value": "%{JS: '%{ProjectName}'.replace(/-/g, '_')}"}, { "key": "MainQmlFile", "value": "%{CorrectedProjectName}.qml" }, { "key": "QmlProjectFile", "value": "%{CorrectedProjectName}.qmlproject" }, @@ -55,49 +56,54 @@ "source": "CMakeLists.txt", "openAsProject": true }, + { + "source": "Qul.cmake", + "target": "%{QmlProjectDirectory}/CMakeLists.txt", + "openInEditor": false + }, { "source": "BackendObject.h", - "target": "%{ProjectDirectory}/src/%{InterfaceFile}", + "target": "%{QmlProjectDirectory}/src/%{InterfaceFile}", "openInEditor": true }, { "source": "component.qml.tpl", - "target": "%{ProjectDirectory}/imports/CustomModule/%{QmlComponent}", + "target": "%{QmlProjectDirectory}/imports/CustomModule/%{QmlComponent}", "openInEditor": true }, { "source": "module.qmlproject.tpl", - "target": "%{ProjectDirectory}/imports/CustomModule/%{ModuleFile}", + "target": "%{QmlProjectDirectory}/imports/CustomModule/%{ModuleFile}", "openInEditor": true }, { "source": "project.qmlproject.tpl", - "target": "%{ProjectDirectory}/%{QmlProjectFile}", + "target": "%{QmlProjectDirectory}/%{QmlProjectFile}", "openInEditor": true }, { "source": "main.qml.tpl", - "target": "%{ProjectDirectory}/%{MainQmlFile}", + "target": "%{QmlProjectDirectory}/%{MainQmlFile}", "openInEditor": true }, { "source": "../icon.png", - "target": "%{ProjectDirectory}/images/icon.png", + "target": "%{QmlProjectDirectory}/images/icon.png", "isBinary": true }, { "source": "DejaVuSansMono.ttf", - "target": "%{ProjectDirectory}/fonts/DejaVuSansMono.ttf", + "target": "%{QmlProjectDirectory}/fonts/DejaVuSansMono.ttf", "isBinary": true }, { "source": "LICENSE", - "target": "%{ProjectDirectory}/fonts/LICENSE", + "target": "%{QmlProjectDirectory}/fonts/LICENSE", "isBinary": true }, { "source": "translation.nb_NO.ts", - "target": "%{ProjectDirectory}/translations/%{TsFile}", + "target": "%{QmlProjectDirectory}/translations/%{TsFile}", "openInEditor": false }, { diff --git a/src/plugins/mercurial/authenticationdialog.cpp b/src/plugins/mercurial/authenticationdialog.cpp index 8538fd2812c..d9d552274d0 100644 --- a/src/plugins/mercurial/authenticationdialog.cpp +++ b/src/plugins/mercurial/authenticationdialog.cpp @@ -24,7 +24,7 @@ AuthenticationDialog::AuthenticationDialog(const QString &username, const QStrin auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); - using namespace Utils::Layouting; + using namespace Layouting; Column { Form { diff --git a/src/plugins/mercurial/mercurialclient.cpp b/src/plugins/mercurial/mercurialclient.cpp index 43f942a1675..6a0c2574d43 100644 --- a/src/plugins/mercurial/mercurialclient.cpp +++ b/src/plugins/mercurial/mercurialclient.cpp @@ -12,8 +12,8 @@ #include #include #include +#include #include -#include #include #include @@ -55,17 +55,17 @@ MercurialDiffEditorController::MercurialDiffEditorController(IDocument *document const TreeStorage diffInputStorage = inputStorage(); - const auto setupDiff = [=](QtcProcess &process) { + const auto setupDiff = [=](Process &process) { setupCommand(process, {addConfigurationArguments(args)}); VcsOutputWindow::appendCommand(process.workingDirectory(), process.commandLine()); }; - const auto onDiffDone = [diffInputStorage](const QtcProcess &process) { - *diffInputStorage.activeStorage() = process.cleanedStdOut(); + const auto onDiffDone = [diffInputStorage](const Process &process) { + *diffInputStorage = process.cleanedStdOut(); }; const Group root { Storage(diffInputStorage), - Process(setupDiff, onDiffDone), + ProcessTask(setupDiff, onDiffDone), postProcessTask() }; setReloadRecipe(root); @@ -81,7 +81,8 @@ QStringList MercurialDiffEditorController::addConfigurationArguments(const QStri ///////////////////////////////////////////////////////////// -MercurialClient::MercurialClient(MercurialSettings *settings) : VcsBaseClient(settings) +MercurialClient::MercurialClient() + : VcsBaseClient(&Internal::settings()) { } @@ -427,7 +428,7 @@ void MercurialClient::requestReload(const QString &documentId, const FilePath &s IDocument *document = DiffEditorController::findOrCreateDocument(documentId, title); QTC_ASSERT(document, return); auto controller = new MercurialDiffEditorController(document, args); - controller->setVcsBinary(settings().binaryPath.filePath()); + controller->setVcsBinary(settings().binaryPath()); controller->setProcessEnvironment(processEnvironment()); controller->setWorkingDirectory(workingDirectory); diff --git a/src/plugins/mercurial/mercurialclient.h b/src/plugins/mercurial/mercurialclient.h index 15c31845db5..27badd45d00 100644 --- a/src/plugins/mercurial/mercurialclient.h +++ b/src/plugins/mercurial/mercurialclient.h @@ -16,8 +16,9 @@ class MercurialDiffEditorController; class MercurialClient : public VcsBase::VcsBaseClient { Q_OBJECT + public: - explicit MercurialClient(MercurialSettings *settings); + MercurialClient(); bool synchronousClone(const Utils::FilePath &workingDir, const QString &srcLocation, diff --git a/src/plugins/mercurial/mercurialcommitwidget.cpp b/src/plugins/mercurial/mercurialcommitwidget.cpp index 7fcd3339307..c6b35a67d08 100644 --- a/src/plugins/mercurial/mercurialcommitwidget.cpp +++ b/src/plugins/mercurial/mercurialcommitwidget.cpp @@ -101,7 +101,7 @@ public: m_authorLineEdit = new QLineEdit; m_emailLineEdit = new QLineEdit; - using namespace Utils::Layouting; + using namespace Layouting; Column { Group { @@ -119,8 +119,9 @@ public: Tr::tr("Email:"), m_emailLineEdit, }, } - } - }.attachTo(this, Utils::Layouting::WithoutMargins); + }, + noMargin + }.attachTo(this); } QLabel *m_repositoryLabel; diff --git a/src/plugins/mercurial/mercurialplugin.cpp b/src/plugins/mercurial/mercurialplugin.cpp index e84c26a6caa..cc294cad57a 100644 --- a/src/plugins/mercurial/mercurialplugin.cpp +++ b/src/plugins/mercurial/mercurialplugin.cpp @@ -170,8 +170,7 @@ private: // Variables MercurialSettings m_settings; - MercurialClient m_client{&m_settings}; - MercurialSettingsPage m_settingsPage{&m_settings}; + MercurialClient m_client; Core::CommandLocator *m_commandLocator = nullptr; Core::ActionContainer *m_mercurialContainer = nullptr; @@ -254,7 +253,7 @@ MercurialPluginPrivate::MercurialPluginPrivate() createMenu(context); - connect(&m_settings, &AspectContainer::applied, this, &IVersionControl::configurationChanged); + connect(&settings(), &AspectContainer::applied, this, &IVersionControl::configurationChanged); } void MercurialPluginPrivate::createMenu(const Core::Context &context) @@ -633,8 +632,8 @@ void MercurialPluginPrivate::showCommitWidget(const QListsetFields(m_submitRepository, branch, - m_settings.userName.value(), - m_settings.userEmail.value(), status); + settings().userName(), + settings().userEmail(), status); } void MercurialPluginPrivate::diffFromEditorSelected(const QStringList &files) @@ -716,7 +715,7 @@ bool MercurialPluginPrivate::managesFile(const FilePath &workingDirectory, const bool MercurialPluginPrivate::isConfigured() const { - const FilePath binary = m_settings.binaryPath.filePath(); + const FilePath binary = settings().binaryPath(); if (binary.isEmpty()) return false; QFileInfo fi = binary.toFileInfo(); @@ -784,7 +783,7 @@ VcsCommand *MercurialPluginPrivate::createInitialCheckoutCommand(const QString & QStringList args; args << QLatin1String("clone") << extraArgs << url << localName; auto command = VcsBaseClient::createVcsCommand(baseDirectory, m_client.processEnvironment()); - command->addJob({m_settings.binaryPath.filePath(), args}, -1); + command->addJob({settings().binaryPath(), args}, -1); return command; } diff --git a/src/plugins/mercurial/mercurialsettings.cpp b/src/plugins/mercurial/mercurialsettings.cpp index 6d21550bc70..0b493e277bf 100644 --- a/src/plugins/mercurial/mercurialsettings.cpp +++ b/src/plugins/mercurial/mercurialsettings.cpp @@ -14,74 +14,64 @@ using namespace Utils; namespace Mercurial::Internal { +static MercurialSettings *theSettings; + +MercurialSettings &settings() +{ + return *theSettings; +} + MercurialSettings::MercurialSettings() { - setSettingsGroup("Mercurial"); - setAutoApply(false); + theSettings = this; + + setId(VcsBase::Constants::VCS_ID_MERCURIAL); + setDisplayName(Tr::tr("Mercurial")); + setCategory(VcsBase::Constants::VCS_SETTINGS_CATEGORY); + setSettingsGroup("Mercurial"); - registerAspect(&binaryPath); - binaryPath.setDisplayStyle(StringAspect::PathChooserDisplay); binaryPath.setExpectedKind(PathChooser::ExistingCommand); binaryPath.setDefaultValue(Constants::MERCURIALDEFAULT); binaryPath.setDisplayName(Tr::tr("Mercurial Command")); binaryPath.setHistoryCompleter("Bazaar.Command.History"); binaryPath.setLabelText(Tr::tr("Command:")); - registerAspect(&userName); userName.setDisplayStyle(StringAspect::LineEditDisplay); userName.setLabelText(Tr::tr("Default username:")); userName.setToolTip(Tr::tr("Username to use by default on commit.")); - registerAspect(&userEmail); userEmail.setDisplayStyle(StringAspect::LineEditDisplay); userEmail.setLabelText(Tr::tr("Default email:")); userEmail.setToolTip(Tr::tr("Email to use by default on commit.")); - registerAspect(&diffIgnoreWhiteSpace); diffIgnoreWhiteSpace.setSettingsKey("diffIgnoreWhiteSpace"); - registerAspect(&diffIgnoreBlankLines); diffIgnoreBlankLines.setSettingsKey("diffIgnoreBlankLines"); -} -// MercurialSettingsPage - -MercurialSettingsPage::MercurialSettingsPage(MercurialSettings *settings) -{ - setId(VcsBase::Constants::VCS_ID_MERCURIAL); - setDisplayName(Tr::tr("Mercurial")); - setCategory(VcsBase::Constants::VCS_SETTINGS_CATEGORY); - setSettings(settings); - - setLayouter([settings](QWidget *widget) { - MercurialSettings &s = *settings; + setLayouter([this] { using namespace Layouting; - Column { + return Column { Group { title(Tr::tr("Configuration")), - Row { s.binaryPath } + Row { binaryPath } }, Group { title(Tr::tr("User")), Form { - s.userName, - s.userEmail + userName, br, + userEmail } }, Group { title(Tr::tr("Miscellaneous")), - Row { - s.logCount, - s.timeout, - st - } + Row { logCount, timeout, st } }, st - }.attachTo(widget); + }; }); } diff --git a/src/plugins/mercurial/mercurialsettings.h b/src/plugins/mercurial/mercurialsettings.h index 881bcd04476..2102d7037d6 100644 --- a/src/plugins/mercurial/mercurialsettings.h +++ b/src/plugins/mercurial/mercurialsettings.h @@ -3,8 +3,6 @@ #pragma once -#include - #include namespace Mercurial::Internal { @@ -14,14 +12,10 @@ class MercurialSettings : public VcsBase::VcsBaseSettings public: MercurialSettings(); - Utils::StringAspect diffIgnoreWhiteSpace; - Utils::StringAspect diffIgnoreBlankLines; + Utils::StringAspect diffIgnoreWhiteSpace{this}; + Utils::StringAspect diffIgnoreBlankLines{this}; }; -class MercurialSettingsPage final : public Core::IOptionsPage -{ -public: - explicit MercurialSettingsPage(MercurialSettings *settings); -}; +MercurialSettings &settings(); } // Mercurial::Internal diff --git a/src/plugins/mercurial/revertdialog.cpp b/src/plugins/mercurial/revertdialog.cpp index c36eb85fe74..29ee250e55e 100644 --- a/src/plugins/mercurial/revertdialog.cpp +++ b/src/plugins/mercurial/revertdialog.cpp @@ -11,8 +11,6 @@ #include #include -using namespace Utils; - namespace Mercurial::Internal { RevertDialog::RevertDialog(QWidget *parent) @@ -32,8 +30,8 @@ RevertDialog::RevertDialog(QWidget *parent) using namespace Layouting; Form { - Tr::tr("Revision:"), m_revisionLineEdit, - }.attachTo(groupBox, WithMargins); + Tr::tr("Revision:"), m_revisionLineEdit, normalMargin + }.attachTo(groupBox); Column { groupBox, diff --git a/src/plugins/mesonprojectmanager/CMakeLists.txt b/src/plugins/mesonprojectmanager/CMakeLists.txt index d4d7420d7b0..3c541a1219e 100644 --- a/src/plugins/mesonprojectmanager/CMakeLists.txt +++ b/src/plugins/mesonprojectmanager/CMakeLists.txt @@ -75,8 +75,6 @@ add_qtc_plugin(MesonProjectManager toolssettingsaccessor.h toolssettingspage.cpp toolssettingspage.h - toolssettingswidget.cpp - toolssettingswidget.h tooltreeitem.cpp tooltreeitem.h toolwrapper.cpp diff --git a/src/plugins/mesonprojectmanager/mesonbuildconfiguration.cpp b/src/plugins/mesonprojectmanager/mesonbuildconfiguration.cpp index a24a1f52bf5..514c529212a 100644 --- a/src/plugins/mesonprojectmanager/mesonbuildconfiguration.cpp +++ b/src/plugins/mesonprojectmanager/mesonbuildconfiguration.cpp @@ -19,7 +19,7 @@ #include #include -#include +#include #include @@ -91,7 +91,8 @@ void MesonBuildConfiguration::build(const QString &target) QStringList MesonBuildConfiguration::mesonConfigArgs() { - return Utils::ProcessArgs::splitArgs(m_parameters) + QStringList{QString("-Dbuildtype=%1").arg(mesonBuildTypeName(m_buildType))}; + return Utils::ProcessArgs::splitArgs(m_parameters, HostOsInfo::hostOs()) + + QStringList{QString("-Dbuildtype=%1").arg(mesonBuildTypeName(m_buildType))}; } const QString &MesonBuildConfiguration::parameters() const diff --git a/src/plugins/mesonprojectmanager/mesonbuildconfiguration.h b/src/plugins/mesonprojectmanager/mesonbuildconfiguration.h index 6bc4a96afe8..a4059091892 100644 --- a/src/plugins/mesonprojectmanager/mesonbuildconfiguration.h +++ b/src/plugins/mesonprojectmanager/mesonbuildconfiguration.h @@ -2,8 +2,8 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #pragma once -#include "projectexplorer/buildconfiguration.h" -#include "projectexplorer/target.h" +#include +#include namespace MesonProjectManager { namespace Internal { diff --git a/src/plugins/mesonprojectmanager/mesonbuildsettingswidget.cpp b/src/plugins/mesonprojectmanager/mesonbuildsettingswidget.cpp index cedd9c5f3ef..32719c08d61 100644 --- a/src/plugins/mesonprojectmanager/mesonbuildsettingswidget.cpp +++ b/src/plugins/mesonprojectmanager/mesonbuildsettingswidget.cpp @@ -67,16 +67,18 @@ MesonBuildSettingsWidget::MesonBuildSettingsWidget(MesonBuildConfiguration *buil buildDirWidget, optionsFilterLineEdit, optionsTreeView, - }.attachTo(details, WithoutMargins); + noMargin + }.attachTo(details); Column { container, - Row { configureButton, wipeButton, } - }.attachTo(this, WithoutMargins); + Row { configureButton, wipeButton, noMargin } + }.attachTo(this); - Form buildDirWBuilder; - buildCfg->buildDirectoryAspect()->addToLayout(buildDirWBuilder); - buildDirWBuilder.attachTo(buildDirWidget, WithoutMargins); + Form { + buildCfg->buildDirectoryAspect(), + noMargin + }.attachTo(buildDirWidget); parametersLineEdit->setText(buildCfg->parameters()); optionsFilterLineEdit->setFiltering(true); diff --git a/src/plugins/mesonprojectmanager/mesonbuildsystem.cpp b/src/plugins/mesonprojectmanager/mesonbuildsystem.cpp index 13617fa0140..6d3e5f20c4f 100644 --- a/src/plugins/mesonprojectmanager/mesonbuildsystem.cpp +++ b/src/plugins/mesonprojectmanager/mesonbuildsystem.cpp @@ -192,7 +192,7 @@ void MesonBuildSystem::init() bool MesonBuildSystem::parseProject() { QTC_ASSERT(buildConfiguration(), return false); - if (!isSetup(buildConfiguration()->buildDirectory()) && Settings::instance()->autorunMeson.value()) + if (!isSetup(buildConfiguration()->buildDirectory()) && settings().autorunMeson()) return configure(); LEAVE_IF_BUSY(); LOCK(); diff --git a/src/plugins/mesonprojectmanager/mesonpluginconstants.h b/src/plugins/mesonprojectmanager/mesonpluginconstants.h index 995e41cbb59..108c125f502 100644 --- a/src/plugins/mesonprojectmanager/mesonpluginconstants.h +++ b/src/plugins/mesonprojectmanager/mesonpluginconstants.h @@ -18,7 +18,6 @@ const char PARAMETERS_KEY[] = "MesonProjectManager.BuildConfig.Parameters"; // Settings page namespace SettingsPage { -const char GENERAL_ID[] = "A.MesonProjectManager.SettingsPage.General"; const char TOOLS_ID[] = "Z.MesonProjectManager.SettingsPage.Tools"; const char CATEGORY[] = "Z.Meson"; } // namespace SettingsPage diff --git a/src/plugins/mesonprojectmanager/mesonprocess.cpp b/src/plugins/mesonprojectmanager/mesonprocess.cpp index 0c828588c20..2e4ba024559 100644 --- a/src/plugins/mesonprojectmanager/mesonprocess.cpp +++ b/src/plugins/mesonprojectmanager/mesonprocess.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include #include #include @@ -63,12 +63,12 @@ void MesonProcess::setupProcess(const Command &command, const Environment &env, { if (m_process) m_process.release()->deleteLater(); - m_process.reset(new QtcProcess); - connect(m_process.get(), &QtcProcess::done, this, &MesonProcess::handleProcessDone); + m_process.reset(new Process); + connect(m_process.get(), &Process::done, this, &MesonProcess::handleProcessDone); if (!captureStdo) { - connect(m_process.get(), &QtcProcess::readyReadStandardOutput, + connect(m_process.get(), &Process::readyReadStandardOutput, this, &MesonProcess::processStandardOutput); - connect(m_process.get(), &QtcProcess::readyReadStandardError, + connect(m_process.get(), &Process::readyReadStandardError, this, &MesonProcess::processStandardError); } diff --git a/src/plugins/mesonprojectmanager/mesonprocess.h b/src/plugins/mesonprojectmanager/mesonprocess.h index 949af3339f0..c01427c8fb9 100644 --- a/src/plugins/mesonprojectmanager/mesonprocess.h +++ b/src/plugins/mesonprojectmanager/mesonprocess.h @@ -12,7 +12,7 @@ namespace Utils { class Environment; -class QtcProcess; +class Process; } namespace MesonProjectManager { @@ -44,7 +44,7 @@ private: void processStandardOutput(); void processStandardError(); - std::unique_ptr m_process; + std::unique_ptr m_process; QElapsedTimer m_elapsed; QByteArray m_stdo; QByteArray m_stderr; diff --git a/src/plugins/mesonprojectmanager/mesonprojectmanager.qbs b/src/plugins/mesonprojectmanager/mesonprojectmanager.qbs index 9de8b4ccde9..c721ab2d207 100644 --- a/src/plugins/mesonprojectmanager/mesonprojectmanager.qbs +++ b/src/plugins/mesonprojectmanager/mesonprojectmanager.qbs @@ -95,8 +95,6 @@ Project { "toolssettingsaccessor.h", "toolssettingspage.cpp", "toolssettingspage.h", - "toolssettingswidget.cpp", - "toolssettingswidget.h", "tooltreeitem.cpp", "tooltreeitem.h", "versionhelper.h", diff --git a/src/plugins/mesonprojectmanager/mesonprojectparser.cpp b/src/plugins/mesonprojectmanager/mesonprojectparser.cpp index 5a9babeedfd..c84b5e4b3d6 100644 --- a/src/plugins/mesonprojectmanager/mesonprojectparser.cpp +++ b/src/plugins/mesonprojectmanager/mesonprojectparser.cpp @@ -12,8 +12,8 @@ #include +#include #include -#include #include #include @@ -197,7 +197,7 @@ QList MesonProjectParser::appsTargets() const bool MesonProjectParser::startParser() { - m_parserFutureResult = Utils::runAsync( + m_parserFutureResult = Utils::asyncRun( ProjectExplorer::ProjectExplorerPlugin::sharedThreadPool(), [processOutput = m_process.stdOut(), introType = m_introType, buildDir = m_buildDir.toString(), srcDir = m_srcDir] { diff --git a/src/plugins/mesonprojectmanager/mesonprojectplugin.cpp b/src/plugins/mesonprojectmanager/mesonprojectplugin.cpp index f15f65c4afd..87beb589bbd 100644 --- a/src/plugins/mesonprojectmanager/mesonprojectplugin.cpp +++ b/src/plugins/mesonprojectmanager/mesonprojectplugin.cpp @@ -45,7 +45,7 @@ public: ~MesonProjectPluginPrivate() {} private: - GeneralSettingsPage m_generalSettingsPage; + Settings m_settings; ToolsSettingsPage m_toolslSettingsPage; ToolsSettingsAccessor m_toolsSettings; MesonToolKitAspect m_mesonKitAspect; @@ -60,7 +60,6 @@ private: void saveAll() { m_toolsSettings.saveMesonTools(MesonTools::tools(), ICore::dialogParent()); - Settings::instance()->writeSettings(ICore::settings()); } }; @@ -76,7 +75,6 @@ void MesonProjectPlugin::initialize() ProjectManager::registerProjectType(Constants::Project::MIMETYPE); FileIconProvider::registerIconOverlayForFilename(Constants::Icons::MESON, "meson.build"); FileIconProvider::registerIconOverlayForFilename(Constants::Icons::MESON, "meson_options.txt"); - Settings::instance()->readSettings(ICore::settings()); } } // MesonProjectManager::Internal diff --git a/src/plugins/mesonprojectmanager/mesonrunconfiguration.cpp b/src/plugins/mesonprojectmanager/mesonrunconfiguration.cpp index 3731ec4da6b..74919a49696 100644 --- a/src/plugins/mesonprojectmanager/mesonrunconfiguration.cpp +++ b/src/plugins/mesonprojectmanager/mesonrunconfiguration.cpp @@ -6,67 +6,65 @@ #include "mesonpluginconstants.h" #include -#include -#include -#include #include #include #include -#include #include using namespace ProjectExplorer; +using namespace Utils; -namespace MesonProjectManager { -namespace Internal { +namespace MesonProjectManager::Internal { -MesonRunConfiguration::MesonRunConfiguration(Target *target, Utils::Id id) - : RunConfiguration{target, id} +class MesonRunConfiguration final : public RunConfiguration { - auto envAspect = addAspect(target); +public: + MesonRunConfiguration(Target *target, Id id) + : RunConfiguration(target, id) + { + auto envAspect = addAspect(); + envAspect->setSupportForBuildEnvironment(target); - addAspect(target, ExecutableAspect::RunDevice); - addAspect(macroExpander()); - addAspect(macroExpander(), envAspect); - addAspect(); + addAspect(target, ExecutableAspect::RunDevice); + addAspect(macroExpander()); + addAspect(macroExpander(), envAspect); + addAspect(); - auto libAspect = addAspect(); - connect(libAspect, &UseLibraryPathsAspect::changed, - envAspect, &EnvironmentAspect::environmentChanged); - - if (Utils::HostOsInfo::isMacHost()) { - auto dyldAspect = addAspect(); - connect(dyldAspect, &UseLibraryPathsAspect::changed, + auto libAspect = addAspect(); + connect(libAspect, &UseLibraryPathsAspect::changed, envAspect, &EnvironmentAspect::environmentChanged); - envAspect->addModifier([dyldAspect](Utils::Environment &env) { - if (dyldAspect->value()) - env.set(QLatin1String("DYLD_IMAGE_SUFFIX"), QLatin1String("_debug")); + + if (HostOsInfo::isMacHost()) { + auto dyldAspect = addAspect(); + connect(dyldAspect, &UseLibraryPathsAspect::changed, + envAspect, &EnvironmentAspect::environmentChanged); + envAspect->addModifier([dyldAspect](Utils::Environment &env) { + if (dyldAspect->value()) + env.set(QLatin1String("DYLD_IMAGE_SUFFIX"), QLatin1String("_debug")); + }); + } + + envAspect->addModifier([this, libAspect](Environment &env) { + BuildTargetInfo bti = buildTargetInfo(); + if (bti.runEnvModifier) + bti.runEnvModifier(env, libAspect->value()); }); + + setUpdater([this] { + if (!activeBuildSystem()) + return; + + BuildTargetInfo bti = buildTargetInfo(); + aspect()->setUseTerminalHint(bti.usesTerminal); + aspect()->setExecutable(bti.targetFilePath); + aspect()->setDefaultWorkingDirectory(bti.workingDirectory); + emit aspect()->environmentChanged(); + }); + + connect(target, &Target::buildSystemUpdated, this, &RunConfiguration::update); } - - envAspect->addModifier([this, libAspect](Utils::Environment &env) { - BuildTargetInfo bti = buildTargetInfo(); - if (bti.runEnvModifier) - bti.runEnvModifier(env, libAspect->value()); - }); - - setUpdater([this] { updateTargetInformation(); }); - - connect(target, &Target::buildSystemUpdated, this, &RunConfiguration::update); -} - -void MesonRunConfiguration::updateTargetInformation() -{ - if (!activeBuildSystem()) - return; - - BuildTargetInfo bti = buildTargetInfo(); - aspect()->setUseTerminalHint(bti.usesTerminal); - aspect()->setExecutable(bti.targetFilePath); - aspect()->setDefaultWorkingDirectory(bti.workingDirectory); - emit aspect()->environmentChanged(); -} +}; MesonRunConfigurationFactory::MesonRunConfigurationFactory() { @@ -75,5 +73,4 @@ MesonRunConfigurationFactory::MesonRunConfigurationFactory() addSupportedTargetDeviceType(ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE); } -} // namespace Internal -} // namespace MesonProjectManager +} // MesonProjectManager::Internal diff --git a/src/plugins/mesonprojectmanager/mesonrunconfiguration.h b/src/plugins/mesonprojectmanager/mesonrunconfiguration.h index 8a23dcbec4a..8c1f7580975 100644 --- a/src/plugins/mesonprojectmanager/mesonrunconfiguration.h +++ b/src/plugins/mesonprojectmanager/mesonrunconfiguration.h @@ -5,17 +5,7 @@ #include -namespace MesonProjectManager { -namespace Internal { - -class MesonRunConfiguration final : public ProjectExplorer::RunConfiguration -{ -public: - MesonRunConfiguration(ProjectExplorer::Target *target, Utils::Id id); - -private: - void updateTargetInformation(); -}; +namespace MesonProjectManager::Internal { class MesonRunConfigurationFactory final : public ProjectExplorer::RunConfigurationFactory { @@ -23,5 +13,4 @@ public: MesonRunConfigurationFactory(); }; -} // namespace Internal -} // namespace MesonProjectManager +} // MesonProjectManager::Internal diff --git a/src/plugins/mesonprojectmanager/mesonwrapper.h b/src/plugins/mesonprojectmanager/mesonwrapper.h index b229ab65550..6ec1e57a0f1 100644 --- a/src/plugins/mesonprojectmanager/mesonwrapper.h +++ b/src/plugins/mesonprojectmanager/mesonwrapper.h @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include @@ -35,7 +35,7 @@ bool containsFiles(const QString &path, const File_t &file, const T &...files) inline bool run_meson(const Command &command, QIODevice *output = nullptr) { - Utils::QtcProcess process; + Utils::Process process; process.setWorkingDirectory(command.workDir()); process.setCommand(command.cmdLine()); process.start(); diff --git a/src/plugins/mesonprojectmanager/ninjabuildstep.cpp b/src/plugins/mesonprojectmanager/ninjabuildstep.cpp index 325056d4918..ac41aeecfaa 100644 --- a/src/plugins/mesonprojectmanager/ninjabuildstep.cpp +++ b/src/plugins/mesonprojectmanager/ninjabuildstep.cpp @@ -44,7 +44,7 @@ NinjaBuildStep::NinjaBuildStep(BuildStepList *bsl, Id id) setUseEnglishOutput(); connect(target(), &ProjectExplorer::Target::parsingFinished, this, &NinjaBuildStep::update); - connect(&Settings::instance()->verboseNinja, &BaseAspect::changed, + connect(&settings().verboseNinja, &BaseAspect::changed, this, &NinjaBuildStep::commandChanged); } @@ -119,17 +119,15 @@ QWidget *NinjaBuildStep::createConfigWidget() // --verbose is only supported since // https://github.com/ninja-build/ninja/commit/bf7517505ad1def03e13bec2b4131399331bc5c4 // TODO check when to switch back to --verbose -Utils::CommandLine NinjaBuildStep::command() +CommandLine NinjaBuildStep::command() { - Utils::CommandLine cmd = [this] { - auto tool = NinjaToolKitAspect::ninjaTool(kit()); - if (tool) - return Utils::CommandLine{tool->exe()}; - return Utils::CommandLine{}; - }(); + CommandLine cmd; + if (auto tool = NinjaToolKitAspect::ninjaTool(kit())) + cmd.setExecutable(tool->exe()); + if (!m_commandArgs.isEmpty()) - cmd.addArgs(m_commandArgs, Utils::CommandLine::RawType::Raw); - if (Settings::instance()->verboseNinja.value()) + cmd.addArgs(m_commandArgs, CommandLine::RawType::Raw); + if (settings().verboseNinja()) cmd.addArg("-v"); cmd.addArg(m_targetName); return cmd; diff --git a/src/plugins/mesonprojectmanager/settings.cpp b/src/plugins/mesonprojectmanager/settings.cpp index b26e09f29b6..e1fe290bc5c 100644 --- a/src/plugins/mesonprojectmanager/settings.cpp +++ b/src/plugins/mesonprojectmanager/settings.cpp @@ -8,13 +8,26 @@ #include -namespace MesonProjectManager { -namespace Internal { +namespace MesonProjectManager::Internal { + +static Settings *s_instance; + +Settings &settings() +{ + return *s_instance; +} Settings::Settings() { + s_instance = this; + setSettingsGroup("MesonProjectManager"); - setAutoApply(false); + + setId("A.MesonProjectManager.SettingsPage.General"); + setDisplayName(Tr::tr("General")); + setDisplayCategory("Meson"); + setCategory(Constants::SettingsPage::CATEGORY); + setCategoryIconPath(Constants::Icons::MESON_BW); autorunMeson.setSettingsKey("meson.autorun"); autorunMeson.setLabelText(Tr::tr("Autorun Meson")); @@ -24,36 +37,16 @@ Settings::Settings() verboseNinja.setLabelText(Tr::tr("Ninja verbose mode")); verboseNinja.setToolTip(Tr::tr("Enables verbose mode by default when invoking Ninja.")); - registerAspect(&autorunMeson); - registerAspect(&verboseNinja); -} - -Settings *Settings::instance() -{ - static Settings m_settings; - return &m_settings; -} - -GeneralSettingsPage::GeneralSettingsPage() -{ - setId(Constants::SettingsPage::GENERAL_ID); - setDisplayName(Tr::tr("General")); - setDisplayCategory("Meson"); - setCategory(Constants::SettingsPage::CATEGORY); - setCategoryIconPath(Constants::Icons::MESON_BW); - setSettings(Settings::instance()); - - setLayouter([](QWidget *widget) { - Settings &s = *Settings::instance(); - using namespace Utils::Layouting; - - Column { - s.autorunMeson, - s.verboseNinja, + setLayouter([this] { + using namespace Layouting; + return Column { + autorunMeson, + verboseNinja, st, - }.attachTo(widget); + }; }); + + readSettings(); } -} // namespace Internal -} // namespace MesonProjectManager +} // MesonProjectManager::Internal diff --git a/src/plugins/mesonprojectmanager/settings.h b/src/plugins/mesonprojectmanager/settings.h index 2e23d0f4482..34c2e9e97d9 100644 --- a/src/plugins/mesonprojectmanager/settings.h +++ b/src/plugins/mesonprojectmanager/settings.h @@ -7,25 +7,17 @@ #include -namespace MesonProjectManager { -namespace Internal { +namespace MesonProjectManager::Internal { -class Settings : public Utils::AspectContainer +class Settings : public Core::PagedSettings { public: Settings(); - static Settings *instance(); - - Utils::BoolAspect autorunMeson; - Utils::BoolAspect verboseNinja; + Utils::BoolAspect autorunMeson{this}; + Utils::BoolAspect verboseNinja{this}; }; -class GeneralSettingsPage final : public Core::IOptionsPage -{ -public: - GeneralSettingsPage(); -}; +Settings &settings(); -} // namespace Internal -} // namespace MesonProjectManager +} // MesonProjectManager::Internal diff --git a/src/plugins/mesonprojectmanager/toolitemsettings.cpp b/src/plugins/mesonprojectmanager/toolitemsettings.cpp index 148e3e5faed..a0591b905c5 100644 --- a/src/plugins/mesonprojectmanager/toolitemsettings.cpp +++ b/src/plugins/mesonprojectmanager/toolitemsettings.cpp @@ -29,7 +29,8 @@ ToolItemSettings::ToolItemSettings(QWidget *parent) Form { Tr::tr("Name:"), m_mesonNameLineEdit, br, Tr::tr("Path:"), m_mesonPathChooser, br, - }.attachTo(this, WithoutMargins); + noMargin + }.attachTo(this); connect(m_mesonPathChooser, &PathChooser::rawPathChanged, this, &ToolItemSettings::store); connect(m_mesonNameLineEdit, &QLineEdit::textChanged, this, &ToolItemSettings::store); diff --git a/src/plugins/mesonprojectmanager/toolkitaspectwidget.h b/src/plugins/mesonprojectmanager/toolkitaspectwidget.h index b403afd0578..c327fb4e2b1 100644 --- a/src/plugins/mesonprojectmanager/toolkitaspectwidget.h +++ b/src/plugins/mesonprojectmanager/toolkitaspectwidget.h @@ -36,11 +36,11 @@ private: void makeReadOnly() override { m_toolsComboBox->setEnabled(false); } - void addToLayout(Utils::Layouting::LayoutBuilder &builder) override + void addToLayout(Layouting::LayoutItem &parent) override { addMutableAction(m_toolsComboBox); - builder.addItem(m_toolsComboBox); - builder.addItem(m_manageButton); + parent.addItem(m_toolsComboBox); + parent.addItem(m_manageButton); } void refresh() override diff --git a/src/plugins/mesonprojectmanager/toolssettingsaccessor.cpp b/src/plugins/mesonprojectmanager/toolssettingsaccessor.cpp index fff0259741b..a67175742bb 100644 --- a/src/plugins/mesonprojectmanager/toolssettingsaccessor.cpp +++ b/src/plugins/mesonprojectmanager/toolssettingsaccessor.cpp @@ -21,15 +21,13 @@ namespace Internal { static QString entryName(int index) { - using namespace Constants; - return QString("%1%2").arg(ToolsSettings::ENTRY_KEY).arg(index); + return QString("%1%2").arg(Constants::ToolsSettings::ENTRY_KEY).arg(index); } ToolsSettingsAccessor::ToolsSettingsAccessor() - : UpgradingSettingsAccessor("QtCreatorMesonTools", - Tr::tr("Meson"), - Core::Constants::IDE_DISPLAY_NAME) { + setDocType("QtCreatorMesonTools"); + setApplicationDisplayName(Core::Constants::IDE_DISPLAY_NAME); setBaseFilePath(Core::ICore::userResourcePath(Constants::ToolsSettings::FILENAME)); } diff --git a/src/plugins/mesonprojectmanager/toolssettingspage.cpp b/src/plugins/mesonprojectmanager/toolssettingspage.cpp index 4ac576b365e..67ecd99aacc 100644 --- a/src/plugins/mesonprojectmanager/toolssettingspage.cpp +++ b/src/plugins/mesonprojectmanager/toolssettingspage.cpp @@ -5,10 +5,120 @@ #include "mesonpluginconstants.h" #include "mesonprojectmanagertr.h" -#include "toolssettingswidget.h" +#include "toolitemsettings.h" +#include "toolsmodel.h" +#include "tooltreeitem.h" + +#include +#include + +#include +#include +#include + +using namespace Utils; + +namespace MesonProjectManager::Internal { + +class ToolsSettingsWidget final : public Core::IOptionsPageWidget +{ +public: + ToolsSettingsWidget(); + +private: + void apply() final { m_model.apply(); } + + void cloneMesonTool(); + void removeMesonTool(); + void currentMesonToolChanged(const QModelIndex &newCurrent); + + ToolsModel m_model; + ToolItemSettings *m_itemSettings; + ToolTreeItem *m_currentItem = nullptr; + + QTreeView *m_mesonList; + DetailsWidget *m_mesonDetails; + QPushButton *m_cloneButton; + QPushButton *m_removeButton; +}; + +ToolsSettingsWidget::ToolsSettingsWidget() +{ + m_mesonList = new QTreeView; + m_mesonList->setModel(&m_model); + m_mesonList->expandAll(); + m_mesonList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + m_mesonList->header()->setSectionResizeMode(1, QHeaderView::Stretch); + + m_itemSettings = new ToolItemSettings; + + m_mesonDetails = new DetailsWidget; + m_mesonDetails->setState(DetailsWidget::NoSummary); + m_mesonDetails->setVisible(false); + m_mesonDetails->setWidget(m_itemSettings); + + auto addButton = new QPushButton(Tr::tr("Add")); + + m_cloneButton = new QPushButton(Tr::tr("Clone")); + m_cloneButton->setEnabled(false); + + m_removeButton = new QPushButton(Tr::tr("Remove")); + m_removeButton->setEnabled(false); + + auto makeDefaultButton = new QPushButton(Tr::tr("Make Default")); + makeDefaultButton->setEnabled(false); + makeDefaultButton->setVisible(false); + makeDefaultButton->setToolTip(Tr::tr("Set as the default Meson executable to use " + "when creating a new kit or when no value is set.")); + + using namespace Layouting; + + Row { + Column { + m_mesonList, + m_mesonDetails + }, + Column { + addButton, + m_cloneButton, + m_removeButton, + makeDefaultButton, + st + } + }.attachTo(this); + + connect(m_mesonList->selectionModel(), &QItemSelectionModel::currentChanged, + this, &ToolsSettingsWidget::currentMesonToolChanged); + connect(m_itemSettings, &ToolItemSettings::applyChanges, &m_model, &ToolsModel::updateItem); + + connect(addButton, &QPushButton::clicked, &m_model, &ToolsModel::addMesonTool); + connect(m_cloneButton, &QPushButton::clicked, this, &ToolsSettingsWidget::cloneMesonTool); + connect(m_removeButton, &QPushButton::clicked, this, &ToolsSettingsWidget::removeMesonTool); +} + +void ToolsSettingsWidget::cloneMesonTool() +{ + if (m_currentItem) { + auto newItem = m_model.cloneMesonTool(m_currentItem); + m_mesonList->setCurrentIndex(newItem->index()); + } +} + +void ToolsSettingsWidget::removeMesonTool() +{ + if (m_currentItem) + m_model.removeMesonTool(m_currentItem); +} + +void ToolsSettingsWidget::currentMesonToolChanged(const QModelIndex &newCurrent) +{ + m_currentItem = m_model.mesoneToolTreeItem(newCurrent); + m_itemSettings->load(m_currentItem); + m_mesonDetails->setVisible(m_currentItem); + m_cloneButton->setEnabled(m_currentItem); + m_removeButton->setEnabled(m_currentItem && !m_currentItem->isAutoDetected()); +} -namespace MesonProjectManager { -namespace Internal { ToolsSettingsPage::ToolsSettingsPage() { @@ -18,5 +128,4 @@ ToolsSettingsPage::ToolsSettingsPage() setWidgetCreator([]() { return new ToolsSettingsWidget; }); } -} // namespace Internal } // namespace MesonProjectManager diff --git a/src/plugins/mesonprojectmanager/toolssettingspage.h b/src/plugins/mesonprojectmanager/toolssettingspage.h index de62b94d918..815120c30f0 100644 --- a/src/plugins/mesonprojectmanager/toolssettingspage.h +++ b/src/plugins/mesonprojectmanager/toolssettingspage.h @@ -5,8 +5,7 @@ #include -namespace MesonProjectManager { -namespace Internal { +namespace MesonProjectManager::Internal { class ToolsSettingsPage final : public Core::IOptionsPage { @@ -14,5 +13,4 @@ public: ToolsSettingsPage(); }; -} // namespace Internal -} // namespace MesonProjectManager +} // MesonProjectManager::Internal diff --git a/src/plugins/mesonprojectmanager/toolssettingswidget.cpp b/src/plugins/mesonprojectmanager/toolssettingswidget.cpp deleted file mode 100644 index ef30d79ae43..00000000000 --- a/src/plugins/mesonprojectmanager/toolssettingswidget.cpp +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (C) 2020 Alexis Jeandet. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "toolssettingswidget.h" - -#include "mesonprojectmanagertr.h" -#include "toolsmodel.h" -#include "tooltreeitem.h" - -#include -#include - -#include -#include -#include - -using namespace Utils; - -namespace MesonProjectManager::Internal { - -ToolsSettingsWidget::ToolsSettingsWidget() - : Core::IOptionsPageWidget() -{ - m_mesonList = new QTreeView; - m_mesonList->setModel(&m_model); - m_mesonList->expandAll(); - m_mesonList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); - m_mesonList->header()->setSectionResizeMode(1, QHeaderView::Stretch); - - m_itemSettings = new ToolItemSettings; - - m_mesonDetails = new DetailsWidget; - m_mesonDetails->setState(DetailsWidget::NoSummary); - m_mesonDetails->setVisible(false); - m_mesonDetails->setWidget(m_itemSettings); - - auto addButton = new QPushButton(Tr::tr("Add")); - - m_cloneButton = new QPushButton(Tr::tr("Clone")); - m_cloneButton->setEnabled(false); - - m_removeButton = new QPushButton(Tr::tr("Remove")); - m_removeButton->setEnabled(false); - - auto makeDefaultButton = new QPushButton(Tr::tr("Make Default")); - makeDefaultButton->setEnabled(false); - makeDefaultButton->setVisible(false); - makeDefaultButton->setToolTip(Tr::tr("Set as the default Meson executable to use " - "when creating a new kit or when no value is set.")); - - using namespace Layouting; - - Row { - Column { - m_mesonList, - m_mesonDetails - }, - Column { - addButton, - m_cloneButton, - m_removeButton, - makeDefaultButton, - st - } - }.attachTo(this); - - connect(m_mesonList->selectionModel(), &QItemSelectionModel::currentChanged, - this, &ToolsSettingsWidget::currentMesonToolChanged); - connect(m_itemSettings, &ToolItemSettings::applyChanges, &m_model, &ToolsModel::updateItem); - - connect(addButton, &QPushButton::clicked, &m_model, &ToolsModel::addMesonTool); - connect(m_cloneButton, &QPushButton::clicked, this, &ToolsSettingsWidget::cloneMesonTool); - connect(m_removeButton, &QPushButton::clicked, this, &ToolsSettingsWidget::removeMesonTool); -} - -ToolsSettingsWidget::~ToolsSettingsWidget() = default; - -void ToolsSettingsWidget::cloneMesonTool() -{ - if (m_currentItem) { - auto newItem = m_model.cloneMesonTool(m_currentItem); - m_mesonList->setCurrentIndex(newItem->index()); - } -} - -void ToolsSettingsWidget::removeMesonTool() -{ - if (m_currentItem) { - m_model.removeMesonTool(m_currentItem); - } -} - -void ToolsSettingsWidget::currentMesonToolChanged(const QModelIndex &newCurrent) -{ - m_currentItem = m_model.mesoneToolTreeItem(newCurrent); - m_itemSettings->load(m_currentItem); - m_mesonDetails->setVisible(m_currentItem); - m_cloneButton->setEnabled(m_currentItem); - m_removeButton->setEnabled(m_currentItem && !m_currentItem->isAutoDetected()); -} - -void ToolsSettingsWidget::apply() -{ - m_model.apply(); -} - -} // MesonProjectManager::Internal diff --git a/src/plugins/mesonprojectmanager/toolssettingswidget.h b/src/plugins/mesonprojectmanager/toolssettingswidget.h deleted file mode 100644 index 1c39b7460ab..00000000000 --- a/src/plugins/mesonprojectmanager/toolssettingswidget.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (C) 2020 Alexis Jeandet. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "toolitemsettings.h" -#include "toolsmodel.h" - -#include - -namespace Utils { class DetailsWidget; } - -namespace MesonProjectManager::Internal { - -class ToolTreeItem; - -class ToolsSettingsWidget final : public Core::IOptionsPageWidget -{ -public: - explicit ToolsSettingsWidget(); - ~ToolsSettingsWidget(); - -private: - void apply() final; - - void cloneMesonTool(); - void removeMesonTool(); - void currentMesonToolChanged(const QModelIndex &newCurrent); - - ToolsModel m_model; - ToolItemSettings *m_itemSettings; - ToolTreeItem *m_currentItem = nullptr; - - QTreeView *m_mesonList; - Utils::DetailsWidget *m_mesonDetails; - QPushButton *m_cloneButton; - QPushButton *m_removeButton; -}; - -} // MesonProjectManager::Internal diff --git a/src/plugins/mesonprojectmanager/toolwrapper.cpp b/src/plugins/mesonprojectmanager/toolwrapper.cpp index 9864fd030b6..83ed27f495d 100644 --- a/src/plugins/mesonprojectmanager/toolwrapper.cpp +++ b/src/plugins/mesonprojectmanager/toolwrapper.cpp @@ -3,7 +3,7 @@ #include "toolwrapper.h" -#include +#include namespace MesonProjectManager { namespace Internal { @@ -40,7 +40,7 @@ void ToolWrapper::setExe(const Utils::FilePath &newExe) Version ToolWrapper::read_version(const Utils::FilePath &toolPath) { if (toolPath.toFileInfo().isExecutable()) { - Utils::QtcProcess process; + Utils::Process process; process.setCommand({ toolPath, { "--version" } }); process.start(); if (process.waitForFinished()) diff --git a/src/plugins/modeleditor/componentviewcontroller.cpp b/src/plugins/modeleditor/componentviewcontroller.cpp index 4ce7cc16caf..4071c5b0159 100644 --- a/src/plugins/modeleditor/componentviewcontroller.cpp +++ b/src/plugins/modeleditor/componentviewcontroller.cpp @@ -18,9 +18,9 @@ #include #include -#include -#include #include +#include +#include #include @@ -29,6 +29,7 @@ // TODO implement removing include dependencies that are not longer used // TODO refactor add/remove relations between ancestor packages into extra controller class +using namespace ProjectExplorer; using namespace Utils; namespace ModelEditor { @@ -136,7 +137,7 @@ void UpdateIncludeDependenciesVisitor::setModelUtilities(ModelUtilities *modelUt void UpdateIncludeDependenciesVisitor::updateFilePaths() { m_filePaths.clear(); - for (const ProjectExplorer::Project *project : ProjectExplorer::SessionManager::projects()) { + for (const ProjectExplorer::Project *project : ProjectExplorer::ProjectManager::projects()) { ProjectExplorer::ProjectNode *projectNode = project->rootProjectNode(); if (projectNode) collectElementPaths(projectNode, &m_filePaths); @@ -217,17 +218,16 @@ QStringList UpdateIncludeDependenciesVisitor::findFilePathOfComponent(const qmt: void UpdateIncludeDependenciesVisitor::collectElementPaths(const ProjectExplorer::FolderNode *folderNode, QMultiHash *filePathsMap) { - const QList fileNodes = folderNode->fileNodes(); - for (const ProjectExplorer::FileNode *fileNode : fileNodes) { + folderNode->forEachFileNode([&](FileNode *fileNode) { QString elementName = qmt::NameController::convertFileNameToElementName(fileNode->filePath().toString()); QFileInfo fileInfo = fileNode->filePath().toFileInfo(); QString nodePath = fileInfo.path(); QStringList elementsPath = qmt::NameController::buildElementsPath(nodePath, false); filePathsMap->insert(elementName, Node(fileNode->filePath().toString(), elementsPath)); - } - const QList subNodes = folderNode->folderNodes(); - for (const ProjectExplorer::FolderNode *subNode : subNodes) + }); + folderNode->forEachFolderNode([&](FolderNode *subNode) { collectElementPaths(subNode, filePathsMap); + }); } qmt::MComponent *UpdateIncludeDependenciesVisitor::findComponentFromFilePath(const QString &filePath) diff --git a/src/plugins/modeleditor/elementtasks.cpp b/src/plugins/modeleditor/elementtasks.cpp index e0efd6914a6..6413686f61d 100644 --- a/src/plugins/modeleditor/elementtasks.cpp +++ b/src/plugins/modeleditor/elementtasks.cpp @@ -24,15 +24,17 @@ #include "qmt/project/project.h" #include -#include +#include #include #include #include -#include #include #include +using namespace Core; +using namespace CppEditor; + namespace ModelEditor { namespace Internal { @@ -83,23 +85,16 @@ void ElementTasks::openElement(const qmt::DElement *element, const qmt::MDiagram bool ElementTasks::hasClassDefinition(const qmt::MElement *element) const { if (auto klass = dynamic_cast(element)) { - QString qualifiedClassName = klass->umlNamespace().isEmpty() - ? klass->name() - : klass->umlNamespace() + "::" + klass->name(); - - Core::ILocatorFilter *classesFilter - = CppEditor::CppModelManager::instance()->classesFilter(); - if (!classesFilter) + const QString qualifiedClassName = klass->umlNamespace().isEmpty() ? klass->name() + : klass->umlNamespace() + "::" + klass->name(); + auto *locatorData = CppModelManager::instance()->locatorData(); + if (!locatorData) return false; - - QFutureInterface dummyInterface; - const QList matches - = classesFilter->matchesFor(dummyInterface, qualifiedClassName); - for (const Core::LocatorFilterEntry &entry : matches) { - CppEditor::IndexItem::Ptr info = qvariant_cast(entry.internalData); - if (info->scopedSymbolName() != qualifiedClassName) - continue; - return true; + const QList matches = locatorData->findSymbols(IndexItem::Class, + qualifiedClassName); + for (const IndexItem::Ptr &info : matches) { + if (info->scopedSymbolName() == qualifiedClassName) + return true; } } return false; @@ -120,26 +115,19 @@ bool ElementTasks::hasClassDefinition(const qmt::DElement *element, void ElementTasks::openClassDefinition(const qmt::MElement *element) { if (auto klass = dynamic_cast(element)) { - QString qualifiedClassName = klass->umlNamespace().isEmpty() - ? klass->name() - : klass->umlNamespace() + "::" + klass->name(); + const QString qualifiedClassName = klass->umlNamespace().isEmpty() ? klass->name() + : klass->umlNamespace() + "::" + klass->name(); - Core::ILocatorFilter *classesFilter - = CppEditor::CppModelManager::instance()->classesFilter(); - if (!classesFilter) + auto *locatorData = CppModelManager::instance()->locatorData(); + if (!locatorData) return; - - QFutureInterface dummyInterface; - const QList matches - = classesFilter->matchesFor(dummyInterface, qualifiedClassName); - for (const Core::LocatorFilterEntry &entry : matches) { - CppEditor::IndexItem::Ptr info = qvariant_cast(entry.internalData); + const QList matches = locatorData->findSymbols(IndexItem::Class, + qualifiedClassName); + for (const IndexItem::Ptr &info : matches) { if (info->scopedSymbolName() != qualifiedClassName) continue; - if (Core::EditorManager::instance()->openEditorAt( - {info->filePath(), info->line(), info->column()})) { + if (EditorManager::openEditorAt({info->filePath(), info->line(), info->column()})) return; - } } } } diff --git a/src/plugins/modeleditor/modelindexer.cpp b/src/plugins/modeleditor/modelindexer.cpp index c6ace660248..d4471fe0ec6 100644 --- a/src/plugins/modeleditor/modelindexer.cpp +++ b/src/plugins/modeleditor/modelindexer.cpp @@ -18,7 +18,7 @@ #include "qmt/tasks/findrootdiagramvisitor.h" #include -#include +#include #include #include @@ -34,6 +34,8 @@ #include #include +using namespace ProjectExplorer; + namespace ModelEditor { namespace Internal { @@ -308,9 +310,9 @@ ModelIndexer::ModelIndexer(QObject *parent) connect(this, &ModelIndexer::filesQueued, d->indexerThread, &ModelIndexer::IndexerThread::onFilesQueued); d->indexerThread->start(); - connect(ProjectExplorer::SessionManager::instance(), &ProjectExplorer::SessionManager::projectAdded, + connect(ProjectExplorer::ProjectManager::instance(), &ProjectExplorer::ProjectManager::projectAdded, this, &ModelIndexer::onProjectAdded); - connect(ProjectExplorer::SessionManager::instance(), &ProjectExplorer::SessionManager::aboutToRemoveProject, + connect(ProjectExplorer::ProjectManager::instance(), &ProjectExplorer::ProjectManager::aboutToRemoveProject, this, &ModelIndexer::onAboutToRemoveProject); } @@ -447,18 +449,20 @@ QString ModelIndexer::findFirstModel(ProjectExplorer::FolderNode *folderNode, { if (!mimeType.isValid()) return QString(); - const QList fileNodes = folderNode->fileNodes(); - for (const ProjectExplorer::FileNode *fileNode : fileNodes) { - if (mimeType.suffixes().contains(fileNode->filePath().completeSuffix())) - return fileNode->filePath().toString(); - } - const QList subFolderNodes = folderNode->folderNodes(); - for (ProjectExplorer::FolderNode *subFolderNode : subFolderNodes) { - QString modelFileName = findFirstModel(subFolderNode, mimeType); - if (!modelFileName.isEmpty()) - return modelFileName; - } - return QString(); + + const QStringList suffixes = mimeType.suffixes(); + FileNode *foundFileNode = folderNode->findChildFileNode([&](FileNode *fn) { + return suffixes.contains(fn->filePath().completeSuffix()); + }); + if (foundFileNode) + return foundFileNode->filePath().toString(); + + QString modelFileName; + folderNode->findChildFolderNode([&](FolderNode *fn) { + modelFileName = findFirstModel(fn, mimeType); + return !modelFileName.isEmpty(); + }); + return modelFileName; } void ModelIndexer::forgetProject(ProjectExplorer::Project *project) diff --git a/src/plugins/nim/editor/nimcompletionassistprovider.cpp b/src/plugins/nim/editor/nimcompletionassistprovider.cpp index 7179a8a64ec..f732c45d173 100644 --- a/src/plugins/nim/editor/nimcompletionassistprovider.cpp +++ b/src/plugins/nim/editor/nimcompletionassistprovider.cpp @@ -5,7 +5,7 @@ #include "suggest/nimsuggestcache.h" #include "suggest/nimsuggest.h" -#include +#include #include #include #include @@ -142,8 +142,8 @@ private: { int line = 0, column = 0; Utils::Text::convertPosition(interface->textDocument(), pos, &line, &column); - QTC_ASSERT(column >= 1, return nullptr); - return suggest->sug(interface->filePath().toString(), line, column - 1, dirtyFile); + QTC_ASSERT(column >= 0, return nullptr); + return suggest->sug(interface->filePath().toString(), line, column, dirtyFile); } static std::unique_ptr writeDirtyFile(const TextEditor::AssistInterface *interface) diff --git a/src/plugins/nim/editor/nimtexteditorwidget.cpp b/src/plugins/nim/editor/nimtexteditorwidget.cpp index 52bcf64dca4..b297607dced 100644 --- a/src/plugins/nim/editor/nimtexteditorwidget.cpp +++ b/src/plugins/nim/editor/nimtexteditorwidget.cpp @@ -50,7 +50,7 @@ void NimTextEditorWidget::findLinkAt(const QTextCursor &c, const Utils::LinkHand std::shared_ptr request = suggest->def(path.toString(), line, - column - 1, + column, dirtyFile->fileName()); if (!request) diff --git a/src/plugins/nim/images/settingscategory_nim.png b/src/plugins/nim/images/settingscategory_nim.png index 016c570eb49..bee1123cff8 100644 Binary files a/src/plugins/nim/images/settingscategory_nim.png and b/src/plugins/nim/images/settingscategory_nim.png differ diff --git a/src/plugins/nim/images/settingscategory_nim@2x.png b/src/plugins/nim/images/settingscategory_nim@2x.png index 4fb9c101887..2cbe6cfdc1b 100644 Binary files a/src/plugins/nim/images/settingscategory_nim@2x.png and b/src/plugins/nim/images/settingscategory_nim@2x.png differ diff --git a/src/plugins/nim/nimplugin.cpp b/src/plugins/nim/nimplugin.cpp index fcca3ef8d38..b3ef6fc49f0 100644 --- a/src/plugins/nim/nimplugin.cpp +++ b/src/plugins/nim/nimplugin.cpp @@ -62,7 +62,6 @@ public: NimCompilerBuildStepFactory buildStepFactory; NimCompilerCleanStepFactory cleanStepFactory; NimCodeStyleSettingsPage codeStyleSettingsPage; - NimToolsSettingsPage toolsSettingsPage{&settings}; NimCodeStylePreferencesFactory codeStylePreferencesPage; NimToolChainFactory toolChainFactory; diff --git a/src/plugins/nim/project/nimblebuildsystem.cpp b/src/plugins/nim/project/nimblebuildsystem.cpp index b1317153a81..3a8ccb08d62 100644 --- a/src/plugins/nim/project/nimblebuildsystem.cpp +++ b/src/plugins/nim/project/nimblebuildsystem.cpp @@ -10,8 +10,8 @@ #include #include +#include #include -#include using namespace ProjectExplorer; using namespace Utils; @@ -20,7 +20,7 @@ namespace Nim { const char C_NIMBLEPROJECT_TASKS[] = "Nim.NimbleProject.Tasks"; -static QList linesFromProcessOutput(QtcProcess *process) +static QList linesFromProcessOutput(Process *process) { QList lines = process->readAllRawStandardOutput().split('\n'); lines = Utils::transform(lines, [](const QByteArray &line){ return line.trimmed(); }); @@ -30,7 +30,7 @@ static QList linesFromProcessOutput(QtcProcess *process) static std::vector parseTasks(const FilePath &nimblePath, const FilePath &workingDirectory) { - QtcProcess process; + Process process; process.setCommand({nimblePath, {"tasks"}}); process.setWorkingDirectory(workingDirectory); process.start(); @@ -58,7 +58,7 @@ static std::vector parseTasks(const FilePath &nimblePath, const File static NimbleMetadata parseMetadata(const FilePath &nimblePath, const FilePath &workingDirectory) { - QtcProcess process; + Process process; process.setCommand({nimblePath, {"dump"}}); process.setWorkingDirectory(workingDirectory); process.start(); diff --git a/src/plugins/nim/project/nimblerunconfiguration.cpp b/src/plugins/nim/project/nimblerunconfiguration.cpp index f26a375410b..8fa24ee33e4 100644 --- a/src/plugins/nim/project/nimblerunconfiguration.cpp +++ b/src/plugins/nim/project/nimblerunconfiguration.cpp @@ -7,7 +7,6 @@ #include "nimconstants.h" #include "nimtr.h" -#include #include #include #include @@ -27,7 +26,9 @@ public: NimbleRunConfiguration(Target *target, Utils::Id id) : RunConfiguration(target, id) { - auto envAspect = addAspect(target); + auto envAspect = addAspect(); + envAspect->setSupportForBuildEnvironment(target); + addAspect(target, ExecutableAspect::RunDevice); addAspect(macroExpander()); addAspect(macroExpander(), envAspect); diff --git a/src/plugins/nim/project/nimbletaskstep.cpp b/src/plugins/nim/project/nimbletaskstep.cpp index 06cf14a1077..1885a9daa15 100644 --- a/src/plugins/nim/project/nimbletaskstep.cpp +++ b/src/plugins/nim/project/nimbletaskstep.cpp @@ -47,8 +47,8 @@ private: bool validate(); - StringAspect *m_taskName = nullptr; - StringAspect *m_taskArgs = nullptr; + StringAspect m_taskName{this}; + StringAspect m_taskArgs{this}; QStandardItemModel m_tasks; bool m_selecting = false; @@ -62,19 +62,17 @@ NimbleTaskStep::NimbleTaskStep(BuildStepList *parentList, Id id) setDisplayName(display); setCommandLineProvider([this] { - QString args = m_taskName->value() + " " + m_taskArgs->value(); + QString args = m_taskName() + " " + m_taskArgs(); return CommandLine(Nim::nimblePathFromKit(target()->kit()), args, CommandLine::Raw); }); setWorkingDirectoryProvider([this] { return project()->projectDirectory(); }); - m_taskName = addAspect(); - m_taskName->setSettingsKey(Constants::C_NIMBLETASKSTEP_TASKNAME); + m_taskName.setSettingsKey(Constants::C_NIMBLETASKSTEP_TASKNAME); - m_taskArgs = addAspect(); - m_taskArgs->setSettingsKey(Constants::C_NIMBLETASKSTEP_TASKARGS); - m_taskArgs->setDisplayStyle(StringAspect::LineEditDisplay); - m_taskArgs->setLabelText(Tr::tr("Task arguments:")); + m_taskArgs.setSettingsKey(Constants::C_NIMBLETASKSTEP_TASKARGS); + m_taskArgs.setDisplayStyle(StringAspect::LineEditDisplay); + m_taskArgs.setLabelText(Tr::tr("Task arguments:")); } QWidget *NimbleTaskStep::createConfigWidget() @@ -88,22 +86,22 @@ QWidget *NimbleTaskStep::createConfigWidget() using namespace Layouting; auto widget = Form { m_taskArgs, - Tr::tr("Tasks:"), taskList - }.emerge(WithoutMargins); + Tr::tr("Tasks:"), taskList, + noMargin + }.emerge(); auto buildSystem = dynamic_cast(this->buildSystem()); QTC_ASSERT(buildSystem, return widget); updateTaskList(); - selectTask(m_taskName->value()); + selectTask(m_taskName()); connect(&m_tasks, &QAbstractItemModel::dataChanged, this, &NimbleTaskStep::onDataChanged); connect(buildSystem, &NimbleBuildSystem::tasksChanged, this, &NimbleTaskStep::updateTaskList); setSummaryUpdater([this] { - return QString("%1: nimble %2 %3") - .arg(displayName(), m_taskName->value(), m_taskArgs->value()); + return QString("%1: nimble %2 %3").arg(displayName(), m_taskName(), m_taskArgs()); }); return widget; @@ -198,24 +196,24 @@ void NimbleTaskStep::uncheckedAllDifferentFrom(QStandardItem *toSkip) void NimbleTaskStep::setTaskName(const QString &name) { - if (m_taskName->value() == name) + if (m_taskName() == name) return; - m_taskName->setValue(name); + m_taskName.setValue(name); selectTask(name); } bool NimbleTaskStep::validate() { - if (m_taskName->value().isEmpty()) + if (m_taskName().isEmpty()) return true; auto nimbleBuildSystem = dynamic_cast(buildSystem()); QTC_ASSERT(nimbleBuildSystem, return false); - auto matchName = [this](const NimbleTask &task) { return task.name == m_taskName->value(); }; + auto matchName = [this](const NimbleTask &task) { return task.name == m_taskName(); }; if (!Utils::contains(nimbleBuildSystem->tasks(), matchName)) { emit addTask(BuildSystemTask(Task::Error, Tr::tr("Nimble task %1 not found.") - .arg(m_taskName->value()))); + .arg(m_taskName()))); emitFaultyConfigurationMessage(); return false; } diff --git a/src/plugins/nim/project/nimbuildsystem.cpp b/src/plugins/nim/project/nimbuildsystem.cpp index 97661644dcd..00d68034eb2 100644 --- a/src/plugins/nim/project/nimbuildsystem.cpp +++ b/src/plugins/nim/project/nimbuildsystem.cpp @@ -14,8 +14,6 @@ #include #include -#include - using namespace ProjectExplorer; using namespace Utils; @@ -180,10 +178,10 @@ FilePath nimPathFromKit(Kit *kit) FilePath nimblePathFromKit(Kit *kit) { // There's no extra setting for "nimble", derive it from the "nim" path. - const QString nimbleFromPath = QStandardPaths::findExecutable("nimble"); + const FilePath nimbleFromPath = FilePath("nimble").searchInPath(); const FilePath nimPath = nimPathFromKit(kit); const FilePath nimbleFromKit = nimPath.pathAppended("nimble").withExecutableSuffix(); - return nimbleFromKit.exists() ? nimbleFromKit.canonicalPath() : FilePath::fromString(nimbleFromPath); + return nimbleFromKit.exists() ? nimbleFromKit.canonicalPath() : nimbleFromPath; } bool NimBuildSystem::supportsAction(Node *context, ProjectAction action, const Node *node) const diff --git a/src/plugins/nim/project/nimcompilerbuildstep.cpp b/src/plugins/nim/project/nimcompilerbuildstep.cpp index 3ca151def72..588098619a2 100644 --- a/src/plugins/nim/project/nimcompilerbuildstep.cpp +++ b/src/plugins/nim/project/nimcompilerbuildstep.cpp @@ -16,8 +16,8 @@ #include #include +#include #include -#include #include #include @@ -76,7 +76,7 @@ QWidget *NimCompilerBuildStep::createConfigWidget() auto updateUi = [=] { const CommandLine cmd = commandLine(); - const QStringList parts = ProcessArgs::splitArgs(cmd.toUserOutput()); + const QStringList parts = ProcessArgs::splitArgs(cmd.toUserOutput(), HostOsInfo::hostOs()); commandTextEdit->setText(parts.join(QChar::LineFeed)); diff --git a/src/plugins/nim/project/nimcompilercleanstep.cpp b/src/plugins/nim/project/nimcompilercleanstep.cpp index c17f9437a53..8e13c068131 100644 --- a/src/plugins/nim/project/nimcompilercleanstep.cpp +++ b/src/plugins/nim/project/nimcompilercleanstep.cpp @@ -110,7 +110,7 @@ bool NimCompilerCleanStep::removeOutFilePath() NimCompilerCleanStepFactory::NimCompilerCleanStepFactory() { registerStep(Constants::C_NIMCOMPILERCLEANSTEP_ID); - setFlags(BuildStepInfo::Unclonable); + setFlags(BuildStep::Unclonable); setSupportedStepList(ProjectExplorer::Constants::BUILDSTEPS_CLEAN); setSupportedConfiguration(Constants::C_NIMBUILDCONFIGURATION_ID); setRepeatable(false); diff --git a/src/plugins/nim/project/nimrunconfiguration.cpp b/src/plugins/nim/project/nimrunconfiguration.cpp index 5faaa0871c0..79eeeb898dd 100644 --- a/src/plugins/nim/project/nimrunconfiguration.cpp +++ b/src/plugins/nim/project/nimrunconfiguration.cpp @@ -8,7 +8,6 @@ #include "../nimtr.h" #include -#include #include #include @@ -27,7 +26,9 @@ public: NimRunConfiguration(Target *target, Utils::Id id) : RunConfiguration(target, id) { - auto envAspect = addAspect(target); + auto envAspect = addAspect(); + envAspect->setSupportForBuildEnvironment(target); + addAspect(target, ExecutableAspect::RunDevice); addAspect(macroExpander()); addAspect(macroExpander(), envAspect); diff --git a/src/plugins/nim/project/nimtoolchain.cpp b/src/plugins/nim/project/nimtoolchain.cpp index b373fc94182..21b441fa6ce 100644 --- a/src/plugins/nim/project/nimtoolchain.cpp +++ b/src/plugins/nim/project/nimtoolchain.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include #include @@ -95,7 +95,7 @@ bool NimToolChain::fromMap(const QVariantMap &data) bool NimToolChain::parseVersion(const FilePath &path, std::tuple &result) { - QtcProcess process; + Process process; process.setCommand({path, {"--version"}}); process.start(); if (!process.waitForFinished()) diff --git a/src/plugins/nim/project/nimtoolchainfactory.cpp b/src/plugins/nim/project/nimtoolchainfactory.cpp index 4301140865a..01ec5373b55 100644 --- a/src/plugins/nim/project/nimtoolchainfactory.cpp +++ b/src/plugins/nim/project/nimtoolchainfactory.cpp @@ -35,10 +35,7 @@ Toolchains NimToolChainFactory::autoDetect(const ToolchainDetector &detector) co { Toolchains result; - IDevice::ConstPtr dev = - detector.device ? detector.device : DeviceManager::defaultDesktopDevice(); - - const FilePath compilerPath = dev->searchExecutableInPath("nim"); + const FilePath compilerPath = detector.device->searchExecutableInPath("nim"); if (compilerPath.isEmpty()) return result; diff --git a/src/plugins/nim/settings/nimcodestylepreferenceswidget.cpp b/src/plugins/nim/settings/nimcodestylepreferenceswidget.cpp index beefeb56ace..ec9cfa170e0 100644 --- a/src/plugins/nim/settings/nimcodestylepreferenceswidget.cpp +++ b/src/plugins/nim/settings/nimcodestylepreferenceswidget.cpp @@ -37,14 +37,15 @@ NimCodeStylePreferencesWidget::NimCodeStylePreferencesWidget(ICodeStylePreferenc m_previewTextEdit = new SnippetEditorWidget; m_previewTextEdit->setPlainText(Nim::Constants::C_NIMCODESTYLEPREVIEWSNIPPET); - using namespace Utils::Layouting; + using namespace Layouting; Row { Column { tabPreferencesWidget, st, }, m_previewTextEdit, - }.attachTo(this, WithoutMargins); + noMargin, + }.attachTo(this); decorateEditor(TextEditorSettings::fontSettings()); connect(TextEditorSettings::instance(), &TextEditorSettings::fontSettingsChanged, diff --git a/src/plugins/nim/settings/nimcodestylesettingspage.cpp b/src/plugins/nim/settings/nimcodestylesettingspage.cpp index 79c3634cf25..56919a6c156 100644 --- a/src/plugins/nim/settings/nimcodestylesettingspage.cpp +++ b/src/plugins/nim/settings/nimcodestylesettingspage.cpp @@ -36,13 +36,9 @@ public: auto layout = new QVBoxLayout(this); layout->addWidget(editor); - layout->setContentsMargins(0, 0, 0, 0); } private: - void apply() final {} - void finish() final {} - TextEditor::SimpleCodeStylePreferences *m_nimCodeStylePreferences; }; diff --git a/src/plugins/nim/settings/nimsettings.cpp b/src/plugins/nim/settings/nimsettings.cpp index 5904b8b6e60..fac77109fde 100644 --- a/src/plugins/nim/settings/nimsettings.cpp +++ b/src/plugins/nim/settings/nimsettings.cpp @@ -26,32 +26,24 @@ static SimpleCodeStylePreferences *m_globalCodeStyle = nullptr; NimSettings::NimSettings() { - setAutoApply(false); setSettingsGroups("Nim", "NimSuggest"); + setId(Nim::Constants::C_NIMTOOLSSETTINGSPAGE_ID); + setDisplayName(Tr::tr("Tools")); + setCategory(Nim::Constants::C_NIMTOOLSSETTINGSPAGE_CATEGORY); + setDisplayCategory(Tr::tr("Nim")); + setCategoryIconPath(":/nim/images/settingscategory_nim.png"); - InitializeCodeStyleSettings(); + setLayouter([this] { + using namespace Layouting; + return Column { + Group { + title("Nimsuggest"), + Column { nimSuggestPath } + }, + st + }; + }); - registerAspect(&nimSuggestPath); - nimSuggestPath.setSettingsKey("Command"); - nimSuggestPath.setDisplayStyle(StringAspect::PathChooserDisplay); - nimSuggestPath.setExpectedKind(PathChooser::ExistingCommand); - nimSuggestPath.setLabelText(Tr::tr("Path:")); - - readSettings(Core::ICore::settings()); -} - -NimSettings::~NimSettings() -{ - TerminateCodeStyleSettings(); -} - -SimpleCodeStylePreferences *NimSettings::globalCodeStyle() -{ - return m_globalCodeStyle; -} - -void NimSettings::InitializeCodeStyleSettings() -{ // code style factory auto factory = new NimCodeStylePreferencesFactory(); TextEditorSettings::registerCodeStyleFactory(factory); @@ -93,9 +85,15 @@ void NimSettings::InitializeCodeStyleSettings() Nim::Constants::C_NIMLANGUAGE_ID); TextEditorSettings::registerMimeTypeForLanguageId(Nim::Constants::C_NIM_SCRIPT_MIMETYPE, Nim::Constants::C_NIMLANGUAGE_ID); + + nimSuggestPath.setSettingsKey("Command"); + nimSuggestPath.setExpectedKind(PathChooser::ExistingCommand); + nimSuggestPath.setLabelText(Tr::tr("Path:")); + + readSettings(); } -void NimSettings::TerminateCodeStyleSettings() +NimSettings::~NimSettings() { TextEditorSettings::unregisterCodeStyle(Nim::Constants::C_NIMLANGUAGE_ID); TextEditorSettings::unregisterCodeStylePool(Nim::Constants::C_NIMLANGUAGE_ID); @@ -105,28 +103,9 @@ void NimSettings::TerminateCodeStyleSettings() m_globalCodeStyle = nullptr; } - -// NimToolSettingsPage - -NimToolsSettingsPage::NimToolsSettingsPage(NimSettings *settings) +SimpleCodeStylePreferences *NimSettings::globalCodeStyle() { - setId(Nim::Constants::C_NIMTOOLSSETTINGSPAGE_ID); - setDisplayName(Tr::tr("Tools")); - setCategory(Nim::Constants::C_NIMTOOLSSETTINGSPAGE_CATEGORY); - setDisplayCategory(Tr::tr("Nim")); - setCategoryIconPath(":/nim/images/settingscategory_nim.png"); - setSettings(settings); - - setLayouter([settings](QWidget *widget) { - using namespace Layouting; - Column { - Group { - title("Nimsuggest"), - Column { settings->nimSuggestPath } - }, - st - }.attachTo(widget); - }); + return m_globalCodeStyle; } } // namespace Nim diff --git a/src/plugins/nim/settings/nimsettings.h b/src/plugins/nim/settings/nimsettings.h index d144618f7d2..a7c6628b65b 100644 --- a/src/plugins/nim/settings/nimsettings.h +++ b/src/plugins/nim/settings/nimsettings.h @@ -4,31 +4,20 @@ #pragma once #include -#include namespace TextEditor { class SimpleCodeStylePreferences; } namespace Nim { -class NimSettings : public Utils::AspectContainer +class NimSettings : public Core::PagedSettings { public: NimSettings(); ~NimSettings(); - Utils::StringAspect nimSuggestPath; + Utils::FilePathAspect nimSuggestPath{this}; static TextEditor::SimpleCodeStylePreferences *globalCodeStyle(); - -private: - void InitializeCodeStyleSettings(); - void TerminateCodeStyleSettings(); -}; - -class NimToolsSettingsPage final : public Core::IOptionsPage -{ -public: - explicit NimToolsSettingsPage(NimSettings *settings); }; } // Nim diff --git a/src/plugins/nim/suggest/server.cpp b/src/plugins/nim/suggest/server.cpp index b891404fe8c..7573505f727 100644 --- a/src/plugins/nim/suggest/server.cpp +++ b/src/plugins/nim/suggest/server.cpp @@ -10,8 +10,8 @@ namespace Suggest { NimSuggestServer::NimSuggestServer(QObject *parent) : QObject(parent) { - connect(&m_process, &QtcProcess::done, this, &NimSuggestServer::onDone); - connect(&m_process, &QtcProcess::readyReadStandardOutput, this, + connect(&m_process, &Process::done, this, &NimSuggestServer::onDone); + connect(&m_process, &Process::readyReadStandardOutput, this, &NimSuggestServer::onStandardOutputAvailable); } diff --git a/src/plugins/nim/suggest/server.h b/src/plugins/nim/suggest/server.h index 9d8e880663b..9eb2bac141d 100644 --- a/src/plugins/nim/suggest/server.h +++ b/src/plugins/nim/suggest/server.h @@ -7,7 +7,7 @@ #include #include -#include +#include namespace Nim { namespace Suggest { @@ -36,7 +36,7 @@ private: void clearState(); bool m_portAvailable = false; - Utils::QtcProcess m_process; + Utils::Process m_process; quint16 m_port = 0; QString m_projectFilePath; QString m_executablePath; diff --git a/src/plugins/perforce/changenumberdialog.cpp b/src/plugins/perforce/changenumberdialog.cpp index b5a0aa27870..5ba8dcced07 100644 --- a/src/plugins/perforce/changenumberdialog.cpp +++ b/src/plugins/perforce/changenumberdialog.cpp @@ -27,7 +27,7 @@ ChangeNumberDialog::ChangeNumberDialog(QWidget *parent) connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - using namespace Utils::Layouting; + using namespace Layouting; Column { Row { Tr::tr("Change number:"), m_lineEdit }, diff --git a/src/plugins/perforce/pendingchangesdialog.cpp b/src/plugins/perforce/pendingchangesdialog.cpp index 589d17b276e..84b10a8a072 100644 --- a/src/plugins/perforce/pendingchangesdialog.cpp +++ b/src/plugins/perforce/pendingchangesdialog.cpp @@ -48,7 +48,7 @@ PendingChangesDialog::PendingChangesDialog(const QString &data, QWidget *parent) submitButton->setEnabled(false); } - using namespace Utils::Layouting; + using namespace Layouting; Column { m_listWidget, diff --git a/src/plugins/perforce/perforcechecker.cpp b/src/plugins/perforce/perforcechecker.cpp index fc2aeae3e1e..cf63d9e6913 100644 --- a/src/plugins/perforce/perforcechecker.cpp +++ b/src/plugins/perforce/perforcechecker.cpp @@ -21,13 +21,15 @@ namespace Internal { PerforceChecker::PerforceChecker(QObject *parent) : QObject(parent) { - connect(&m_process, &QtcProcess::done, this, &PerforceChecker::slotDone); + connect(&m_process, &Process::done, this, &PerforceChecker::slotDone); } PerforceChecker::~PerforceChecker() { - m_process.kill(); - m_process.waitForFinished(); + if (m_process.isRunning()) { + m_process.kill(); + m_process.waitForFinished(); + } resetOverrideCursor(); } diff --git a/src/plugins/perforce/perforcechecker.h b/src/plugins/perforce/perforcechecker.h index e9325b4cedd..d0f608de705 100644 --- a/src/plugins/perforce/perforcechecker.h +++ b/src/plugins/perforce/perforcechecker.h @@ -4,7 +4,7 @@ #pragma once #include -#include +#include namespace Perforce::Internal { @@ -44,7 +44,7 @@ private: void parseOutput(const QString &); inline void resetOverrideCursor(); - Utils::QtcProcess m_process; + Utils::Process m_process; Utils::FilePath m_binary; int m_timeOutMS = -1; bool m_timedOut = false; diff --git a/src/plugins/perforce/perforceplugin.cpp b/src/plugins/perforce/perforceplugin.cpp index ccd4faec162..ae61d12d342 100644 --- a/src/plugins/perforce/perforceplugin.cpp +++ b/src/plugins/perforce/perforceplugin.cpp @@ -27,8 +27,8 @@ #include #include #include +#include #include -#include #include #include @@ -226,6 +226,10 @@ public: void discardCommit() override { cleanCommitMessageFile(); } QString commitDisplayName() const final; + QString commitAbortTitle() const final; + QString commitAbortMessage() const final; + QString commitErrorMessage(const QString &error) const final; + void p4Diff(const PerforceDiffParameters &p); void openCurrentFile(); @@ -889,8 +893,8 @@ void PerforcePluginPrivate::filelog(const FilePath &workingDir, const QString &f QTextCodec *codec = VcsBaseEditor::getCodec(workingDir, QStringList(fileName)); QStringList args; args << QLatin1String("filelog") << QLatin1String("-li"); - if (m_settings.logCount.value() > 0) - args << "-m" << QString::number(m_settings.logCount.value()); + if (m_settings.logCount() > 0) + args << "-m" << QString::number(m_settings.logCount()); if (!fileName.isEmpty()) args.append(fileName); const PerforceResponse result = runP4Cmd(workingDir, args, @@ -911,8 +915,8 @@ void PerforcePluginPrivate::changelists(const FilePath &workingDir, const QStrin QTextCodec *codec = VcsBaseEditor::getCodec(workingDir, QStringList(fileName)); QStringList args; args << QLatin1String("changelists") << QLatin1String("-lit"); - if (m_settings.logCount.value() > 0) - args << "-m" << QString::number(m_settings.logCount.value()); + if (m_settings.logCount() > 0) + args << "-m" << QString::number(m_settings.logCount()); if (!fileName.isEmpty()) args.append(fileName); const PerforceResponse result = runP4Cmd(workingDir, args, @@ -1225,8 +1229,8 @@ PerforceResponse PerforcePluginPrivate::synchronousProcess(const FilePath &worki QTC_ASSERT(stdInput.isEmpty(), return PerforceResponse()); // Not supported here // Run, connect stderr to the output window - QtcProcess process; - const int timeOutS = (flags & LongTimeOut) ? m_settings.longTimeOutS() : m_settings.timeOutS.value(); + Process process; + const int timeOutS = (flags & LongTimeOut) ? m_settings.longTimeOutS() : m_settings.timeOutS(); process.setTimeoutS(timeOutS); if (outputCodec) process.setCodec(outputCodec); @@ -1283,7 +1287,7 @@ PerforceResponse PerforcePluginPrivate::fullySynchronousProcess(const FilePath & const QByteArray &stdInput, QTextCodec *outputCodec) const { - QtcProcess process; + Process process; if (flags & OverrideDiffEnvironment) process.setEnvironment(overrideDiffEnvironmentVariable()); @@ -1303,7 +1307,7 @@ PerforceResponse PerforcePluginPrivate::fullySynchronousProcess(const FilePath & QByteArray stdOut; QByteArray stdErr; - const int timeOutS = (flags & LongTimeOut) ? m_settings.longTimeOutS() : m_settings.timeOutS.value(); + const int timeOutS = (flags & LongTimeOut) ? m_settings.longTimeOutS() : m_settings.timeOutS(); if (!process.readDataFromProcess(&stdOut, &stdErr, timeOutS)) { process.stop(); process.waitForFinished(); @@ -1443,9 +1447,27 @@ void PerforceDiffConfig::triggerReRun() QString PerforcePluginPrivate::commitDisplayName() const { + //: Name of the "commit" action of the VCS return Tr::tr("Submit"); } +QString PerforcePluginPrivate::commitAbortTitle() const +{ + return Tr::tr("Close Submit Editor"); +} + +QString PerforcePluginPrivate::commitAbortMessage() const +{ + return Tr::tr("Closing this editor will abort the submit."); +} + +QString PerforcePluginPrivate::commitErrorMessage(const QString &error) const +{ + if (error.isEmpty()) + return Tr::tr("Cannot submit."); + return Tr::tr("Cannot submit: %1.").arg(error); +} + void PerforcePluginPrivate::p4Diff(const FilePath &workingDir, const QStringList &files) { PerforceDiffParameters p; diff --git a/src/plugins/perforce/perforcesettings.cpp b/src/plugins/perforce/perforcesettings.cpp index ac9d0b26b05..47ef5a03b35 100644 --- a/src/plugins/perforce/perforcesettings.cpp +++ b/src/plugins/perforce/perforcesettings.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -34,7 +35,6 @@ PerforceSettings::PerforceSettings() setSettingsGroup("Perforce"); setAutoApply(false); - registerAspect(&p4BinaryPath); p4BinaryPath.setDisplayStyle(StringAspect::PathChooserDisplay); p4BinaryPath.setSettingsKey("Command"); p4BinaryPath.setDefaultValue( @@ -44,28 +44,23 @@ PerforceSettings::PerforceSettings() p4BinaryPath.setDisplayName(Tr::tr("Perforce Command")); p4BinaryPath.setLabelText(Tr::tr("P4 command:")); - registerAspect(&p4Port); p4Port.setDisplayStyle(StringAspect::LineEditDisplay); p4Port.setSettingsKey("Port"); p4Port.setLabelText(Tr::tr("P4 port:")); - registerAspect(&p4Client); p4Client.setDisplayStyle(StringAspect::LineEditDisplay); p4Client.setSettingsKey("Client"); p4Client.setLabelText(Tr::tr("P4 client:")); - registerAspect(&p4User); p4User.setDisplayStyle(StringAspect::LineEditDisplay); p4User.setSettingsKey("User"); p4User.setLabelText(Tr::tr("P4 user:")); - registerAspect(&logCount); logCount.setSettingsKey("LogCount"); logCount.setRange(1000, 10000); logCount.setDefaultValue(1000); logCount.setLabelText(Tr::tr("Log count:")); - registerAspect(&customEnv); // The settings value has been stored with the opposite meaning for a while. // Avoid changing the stored value, but flip it on read/write: customEnv.setSettingsKey("Default"); @@ -73,14 +68,12 @@ PerforceSettings::PerforceSettings() customEnv.setFromSettingsTransformation(invertBoolVariant); customEnv.setToSettingsTransformation(invertBoolVariant); - registerAspect(&timeOutS); timeOutS.setSettingsKey("TimeOut"); timeOutS.setRange(1, 360); timeOutS.setDefaultValue(30); timeOutS.setLabelText(Tr::tr("Timeout:")); timeOutS.setSuffix(Tr::tr("s")); - registerAspect(&autoOpen); autoOpen.setSettingsKey("PromptToOpen"); autoOpen.setDefaultValue(true); autoOpen.setLabelText(Tr::tr("Automatically open files when editing")); @@ -106,6 +99,23 @@ QStringList PerforceSettings::commonP4Arguments() const return lst; } +QStringList PerforceSettings::commonP4Arguments_volatile() const +{ + QStringList lst; + if (customEnv.volatileValue().toBool()) { + auto p4C = p4Client.volatileValue().toString(); + if (!p4C.isEmpty()) + lst << "-c" << p4C; + auto p4P = p4Port.volatileValue().toString(); + if (!p4P.isEmpty()) + lst << "-p" << p4P; + auto p4U = p4User.volatileValue().toString(); + if (!p4U.isEmpty()) + lst << "-u" << p4U; + } + return lst; +} + bool PerforceSettings::isValid() const { return !m_topLevel.isEmpty() && !p4BinaryPath.value().isEmpty(); @@ -206,35 +216,41 @@ PerforceSettingsPage::PerforceSettingsPage(PerforceSettings *settings) setCategory(VcsBase::Constants::VCS_SETTINGS_CATEGORY); setSettings(settings); - setLayouter([settings, this](QWidget *widget) { + setLayouter([settings] { PerforceSettings &s = *settings; using namespace Layouting; - auto errorLabel = new QLabel; + auto errorLabel = new InfoLabel({}, InfoLabel::None); + errorLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); + errorLabel->setFilled(true); auto testButton = new QPushButton(Tr::tr("Test")); - connect(testButton, &QPushButton::clicked, this, [settings, errorLabel, testButton] { + QObject::connect(testButton, &QPushButton::clicked, errorLabel, + [settings, errorLabel, testButton] { testButton->setEnabled(false); auto checker = new PerforceChecker(errorLabel); checker->setUseOverideCursor(true); - connect(checker, &PerforceChecker::failed, errorLabel, + QObject::connect(checker, &PerforceChecker::failed, errorLabel, [errorLabel, testButton, checker](const QString &t) { - errorLabel->setStyleSheet("background-color: red"); + errorLabel->setType(InfoLabel::Error); errorLabel->setText(t); testButton->setEnabled(true); checker->deleteLater(); }); - connect(checker, &PerforceChecker::succeeded, errorLabel, + QObject::connect(checker, &PerforceChecker::succeeded, errorLabel, [errorLabel, testButton, checker](const FilePath &repo) { - errorLabel->setStyleSheet({}); + errorLabel->setType(InfoLabel::Ok); errorLabel->setText(Tr::tr("Test succeeded (%1).") .arg(repo.toUserOutput())); testButton->setEnabled(true); checker->deleteLater(); }); - errorLabel->setStyleSheet(QString()); + errorLabel->setType(InfoLabel::Information); errorLabel->setText(Tr::tr("Testing...")); - checker->start(settings->p4BinaryPath.filePath(), {}, settings->commonP4Arguments(), 10000); + + const FilePath p4Bin = FilePath::fromUserInput( + settings->p4BinaryPath.volatileValue().toString()); + checker->start(p4Bin, {}, settings->commonP4Arguments_volatile(), 10000); }); Group config { @@ -243,7 +259,8 @@ PerforceSettingsPage::PerforceSettingsPage(PerforceSettings *settings) }; Group environment { - title(Tr::tr("Environment Variables"), &s.customEnv), + title(Tr::tr("Environment Variables")), + s.customEnv.groupChecker(), Row { s.p4Port, s.p4Client, s.p4User } }; @@ -255,13 +272,13 @@ PerforceSettingsPage::PerforceSettingsPage(PerforceSettings *settings) } }; - Column { + return Column { config, environment, misc, Row { errorLabel, st, testButton }, st - }.attachTo(widget); + }; }); } diff --git a/src/plugins/perforce/perforcesettings.h b/src/plugins/perforce/perforcesettings.h index f6c52957e6f..0801c310907 100644 --- a/src/plugins/perforce/perforcesettings.h +++ b/src/plugins/perforce/perforcesettings.h @@ -41,8 +41,8 @@ public: QString *repositoryRoot /* = 0 */, QString *errorMessage); - int longTimeOutS() const { return timeOutS.value() * 10; } - int timeOutMS() const { return timeOutS.value() * 1000; } + int longTimeOutS() const { return timeOutS() * 10; } + int timeOutMS() const { return timeOutS() * 1000; } Utils::FilePath topLevel() const; Utils::FilePath topLevelSymLinkTarget() const; @@ -64,17 +64,18 @@ public: // Return basic arguments, including -d and server connection parameters. QStringList commonP4Arguments() const; QStringList commonP4Arguments(const QString &workingDir) const; + QStringList commonP4Arguments_volatile() const; // remove when auto apply is done void clearTopLevel(); - Utils::StringAspect p4BinaryPath; - Utils::StringAspect p4Port; - Utils::StringAspect p4Client; - Utils::StringAspect p4User; - Utils::IntegerAspect logCount; - Utils::BoolAspect customEnv; - Utils::IntegerAspect timeOutS; - Utils::BoolAspect autoOpen; + Utils::StringAspect p4BinaryPath{this}; + Utils::StringAspect p4Port{this}; + Utils::StringAspect p4Client{this}; + Utils::StringAspect p4User{this}; + Utils::IntegerAspect logCount{this}; + Utils::BoolAspect customEnv{this}; + Utils::IntegerAspect timeOutS{this}; + Utils::BoolAspect autoOpen{this}; private: QStringList workingDirectoryArguments(const QString &workingDir) const; diff --git a/src/plugins/perforce/perforcesubmiteditorwidget.cpp b/src/plugins/perforce/perforcesubmiteditorwidget.cpp index ddf4f1feb14..6887472822e 100644 --- a/src/plugins/perforce/perforcesubmiteditorwidget.cpp +++ b/src/plugins/perforce/perforcesubmiteditorwidget.cpp @@ -22,7 +22,6 @@ public: , m_clientName(createLabel()) , m_userName(createLabel()) { - resize(402, 134); setFlat(true); setTitle(Tr::tr("Submit")); diff --git a/src/plugins/perfprofiler/perfconfigeventsmodel.cpp b/src/plugins/perfprofiler/perfconfigeventsmodel.cpp index a760ffb7bc5..a235a6122db 100644 --- a/src/plugins/perfprofiler/perfconfigeventsmodel.cpp +++ b/src/plugins/perfprofiler/perfconfigeventsmodel.cpp @@ -8,13 +8,16 @@ #include +using namespace Utils; + namespace PerfProfiler { namespace Internal { PerfConfigEventsModel::PerfConfigEventsModel(PerfSettings *settings, QObject *parent) : QAbstractTableModel(parent), m_settings(settings) { - connect(m_settings, &PerfSettings::changed, this, &PerfConfigEventsModel::reset); + connect(m_settings, &AspectContainer::changed, this, &PerfConfigEventsModel::reset); + connect(m_settings, &AspectContainer::fromMapFinished, this, &PerfConfigEventsModel::reset); } int PerfConfigEventsModel::rowCount(const QModelIndex &parent) const diff --git a/src/plugins/perfprofiler/perfconfigwidget.cpp b/src/plugins/perfprofiler/perfconfigwidget.cpp index 68c86ff720b..e5e3fd37256 100644 --- a/src/plugins/perfprofiler/perfconfigwidget.cpp +++ b/src/plugins/perfprofiler/perfconfigwidget.cpp @@ -14,8 +14,8 @@ #include #include +#include #include -#include #include #include @@ -120,9 +120,9 @@ void PerfConfigWidget::setTarget(ProjectExplorer::Target *target) QTC_ASSERT(device, return); QTC_CHECK(!m_process || m_process->state() == QProcess::NotRunning); - m_process.reset(new QtcProcess); + m_process.reset(new Process); m_process->setCommand({device->filePath("perf"), {"probe", "-l"}}); - connect(m_process.get(), &QtcProcess::done, + connect(m_process.get(), &Process::done, this, &PerfConfigWidget::handleProcessDone); useTracePointsButton->setEnabled(true); diff --git a/src/plugins/perfprofiler/perfconfigwidget.h b/src/plugins/perfprofiler/perfconfigwidget.h index 3fc65c47534..5372647064e 100644 --- a/src/plugins/perfprofiler/perfconfigwidget.h +++ b/src/plugins/perfprofiler/perfconfigwidget.h @@ -14,7 +14,7 @@ class QPushButton; class QTableView; QT_END_NAMESPACE -namespace Utils { class QtcProcess; } +namespace Utils { class Process; } namespace PerfProfiler { namespace Internal { @@ -37,7 +37,7 @@ private: void handleProcessDone(); PerfSettings *m_settings; - std::unique_ptr m_process; + std::unique_ptr m_process; QTableView *eventsView; QPushButton *useTracePointsButton; diff --git a/src/plugins/perfprofiler/perfdatareader.cpp b/src/plugins/perfprofiler/perfdatareader.cpp index 603d2ad016b..a545dff913e 100644 --- a/src/plugins/perfprofiler/perfdatareader.cpp +++ b/src/plugins/perfprofiler/perfdatareader.cpp @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/plugins/perfprofiler/perfloaddialog.cpp b/src/plugins/perfprofiler/perfloaddialog.cpp index 8bf34316fe6..8f162ed5969 100644 --- a/src/plugins/perfprofiler/perfloaddialog.cpp +++ b/src/plugins/perfprofiler/perfloaddialog.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include @@ -109,7 +109,7 @@ void PerfLoadDialog::on_browseExecutableDirButton_pressed() void PerfLoadDialog::chooseDefaults() { - ProjectExplorer::Target *target = ProjectExplorer::SessionManager::startupTarget(); + ProjectExplorer::Target *target = ProjectExplorer::ProjectManager::startupTarget(); if (!target) return; diff --git a/src/plugins/perfprofiler/perfprofiler.qbs b/src/plugins/perfprofiler/perfprofiler.qbs index f3151ae0a63..7a037e7fa47 100644 --- a/src/plugins/perfprofiler/perfprofiler.qbs +++ b/src/plugins/perfprofiler/perfprofiler.qbs @@ -75,9 +75,7 @@ QtcPlugin { files: [ "PerfProfilerFlameGraphView.qml" ] } - Group { - name: "Unit tests" - condition: qtc.testsEnabled + QtcTestFiles { prefix: "tests/" files: [ "perfprofilertracefile_test.cpp", diff --git a/src/plugins/perfprofiler/perfprofilerflamegraphmodel.h b/src/plugins/perfprofiler/perfprofilerflamegraphmodel.h index d213bba0993..4347728536d 100644 --- a/src/plugins/perfprofiler/perfprofilerflamegraphmodel.h +++ b/src/plugins/perfprofiler/perfprofilerflamegraphmodel.h @@ -19,10 +19,9 @@ class PerfProfilerFlameGraphModel : public QAbstractItemModel Q_OBJECT QML_ELEMENT QML_UNCREATABLE("use the context property") - Q_DISABLE_COPY(PerfProfilerFlameGraphModel); + Q_DISABLE_COPY_MOVE(PerfProfilerFlameGraphModel); + public: - PerfProfilerFlameGraphModel(PerfProfilerFlameGraphModel &&) = delete; - PerfProfilerFlameGraphModel &operator=(PerfProfilerFlameGraphModel &&) = delete; enum Role { TypeIdRole = Qt::UserRole + 1, // Sort by data, not by displayed string diff --git a/src/plugins/perfprofiler/perfprofilerruncontrol.cpp b/src/plugins/perfprofiler/perfprofilerruncontrol.cpp index d14bfb87cde..3d633160133 100644 --- a/src/plugins/perfprofiler/perfprofilerruncontrol.cpp +++ b/src/plugins/perfprofiler/perfprofilerruncontrol.cpp @@ -17,7 +17,7 @@ #include #include -#include +#include #include #include @@ -95,20 +95,19 @@ public: : RunWorker(runControl) { setId("LocalPerfRecordWorker"); - - auto perfAspect = runControl->aspect(); - QTC_ASSERT(perfAspect, return); - PerfSettings *settings = static_cast(perfAspect->currentSettings); - QTC_ASSERT(settings, return); - m_perfRecordArguments = settings->perfRecordArguments(); } void start() override { - m_process = new QtcProcess(this); + auto perfAspect = runControl()->aspect(); + QTC_ASSERT(perfAspect, reportFailure(); return); + PerfSettings *settings = static_cast(perfAspect->currentSettings); + QTC_ASSERT(settings, reportFailure(); return); - connect(m_process, &QtcProcess::started, this, &RunWorker::reportStarted); - connect(m_process, &QtcProcess::done, this, [this] { + m_process = new Process(this); + + connect(m_process, &Process::started, this, &RunWorker::reportStarted); + connect(m_process, &Process::done, this, [this] { // The terminate() below will frequently lead to QProcess::Crashed. We're not interested // in that. FailedToStart is the only actual failure. if (m_process->error() == QProcess::FailedToStart) { @@ -125,7 +124,7 @@ public: }); CommandLine cmd({device()->filePath("perf"), {"record"}}); - cmd.addArgs(m_perfRecordArguments); + settings->addPerfRecordArguments(&cmd); cmd.addArgs({"-o", "-", "--"}); cmd.addCommandLineAsArgs(runControl()->commandLine(), CommandLine::Raw); @@ -141,11 +140,10 @@ public: m_process->terminate(); } - QtcProcess *recorder() { return m_process; } + Process *recorder() { return m_process; } private: - QPointer m_process; - QStringList m_perfRecordArguments; + QPointer m_process; }; @@ -193,12 +191,12 @@ void PerfProfilerRunner::start() PerfDataReader *reader = m_perfParserWorker->reader(); if (auto prw = qobject_cast(m_perfRecordWorker)) { // That's the local case. - QtcProcess *recorder = prw->recorder(); - connect(recorder, &QtcProcess::readyReadStandardError, this, [this, recorder] { + Process *recorder = prw->recorder(); + connect(recorder, &Process::readyReadStandardError, this, [this, recorder] { appendMessage(QString::fromLocal8Bit(recorder->readAllRawStandardError()), Utils::StdErrFormat); }); - connect(recorder, &QtcProcess::readyReadStandardOutput, this, [this, reader, recorder] { + connect(recorder, &Process::readyReadStandardOutput, this, [this, reader, recorder] { if (!reader->feedParser(recorder->readAllRawStandardOutput())) reportFailure(Tr::tr("Failed to transfer Perf data to perfparser.")); }); diff --git a/src/plugins/perfprofiler/perfprofilertool.cpp b/src/plugins/perfprofiler/perfprofilertool.cpp index d4515f79b85..60bbcc3ae31 100644 --- a/src/plugins/perfprofiler/perfprofilertool.cpp +++ b/src/plugins/perfprofiler/perfprofilertool.cpp @@ -27,13 +27,14 @@ #include #include #include -#include +#include #include #include #include #include +#include #include #include @@ -120,6 +121,7 @@ PerfProfilerTool::PerfProfilerTool() options->addAction(command); m_tracePointsButton = new QToolButton; + StyleHelper::setPanelWidget(m_tracePointsButton); m_tracePointsButton->setDefaultAction(tracePointsAction); m_objectsToDelete << m_tracePointsButton; @@ -146,14 +148,18 @@ PerfProfilerTool::PerfProfilerTool() this, &PerfProfilerTool::updateRunActions); m_recordButton = new QToolButton; + StyleHelper::setPanelWidget(m_recordButton); m_clearButton = new QToolButton; + StyleHelper::setPanelWidget(m_clearButton); m_filterButton = new QToolButton; + StyleHelper::setPanelWidget(m_filterButton); m_filterMenu = new QMenu(m_filterButton); m_aggregateButton = new QToolButton; + StyleHelper::setPanelWidget(m_aggregateButton); m_recordedLabel = new QLabel; - m_recordedLabel->setProperty("panelwidget", true); + StyleHelper::setPanelWidget(m_recordedLabel); m_delayLabel = new QLabel; - m_delayLabel->setProperty("panelwidget", true); + StyleHelper::setPanelWidget(m_delayLabel); m_objectsToDelete << m_recordButton << m_clearButton << m_filterButton << m_aggregateButton << m_recordedLabel << m_delayLabel; @@ -224,7 +230,7 @@ void PerfProfilerTool::createViews() connect(recordMenu, &QMenu::aboutToShow, recordMenu, [recordMenu] { recordMenu->hide(); PerfSettings *settings = nullptr; - Target *target = SessionManager::startupTarget(); + Target *target = ProjectManager::startupTarget(); if (target) { if (auto runConfig = target->activeRunConfiguration()) settings = runConfig->currentSettings(Constants::PerfSettingsId); @@ -250,7 +256,7 @@ void PerfProfilerTool::createViews() m_filterButton->setIcon(Utils::Icons::FILTER.icon()); m_filterButton->setPopupMode(QToolButton::InstantPopup); - m_filterButton->setProperty("noArrow", true); + m_filterButton->setProperty(StyleHelper::C_NO_ARROW, true); m_filterButton->setMenu(m_filterMenu); m_aggregateButton->setIcon(Utils::Icons::EXPAND_ALL_TOOLBAR.icon()); @@ -572,7 +578,7 @@ static Utils::FilePaths sourceFiles(const Project *currentProject = nullptr) if (currentProject) sourceFiles.append(currentProject->files(Project::SourceFiles)); - const QList projects = SessionManager::projects(); + const QList projects = ProjectManager::projects(); for (const Project *project : projects) { if (project != currentProject) sourceFiles.append(project->files(Project::SourceFiles)); @@ -609,7 +615,7 @@ void PerfProfilerTool::showLoadTraceDialog() startLoading(); - const Project *currentProject = SessionManager::startupProject(); + const Project *currentProject = ProjectManager::startupProject(); const Target *target = currentProject ? currentProject->activeTarget() : nullptr; const Kit *kit = target ? target->kit() : nullptr; populateFileFinder(currentProject, kit); diff --git a/src/plugins/perfprofiler/perfsettings.cpp b/src/plugins/perfprofiler/perfsettings.cpp index 8175dece0ea..59a753e5dde 100644 --- a/src/plugins/perfprofiler/perfsettings.cpp +++ b/src/plugins/perfprofiler/perfsettings.cpp @@ -10,7 +10,7 @@ #include -#include +#include using namespace Utils; @@ -25,19 +25,16 @@ PerfSettings::PerfSettings(ProjectExplorer::Target *target) return widget; }); - registerAspect(&period); period.setSettingsKey("Analyzer.Perf.Frequency"); period.setRange(250, 2147483647); period.setDefaultValue(250); period.setLabelText(Tr::tr("Sample period:")); - registerAspect(&stackSize); stackSize.setSettingsKey("Analyzer.Perf.StackSize"); stackSize.setRange(4096, 65536); stackSize.setDefaultValue(4096); stackSize.setLabelText(Tr::tr("Stack snapshot size (kB):")); - registerAspect(&sampleMode); sampleMode.setSettingsKey("Analyzer.Perf.SampleMode"); sampleMode.setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox); sampleMode.setLabelText(Tr::tr("Sample mode:")); @@ -45,7 +42,6 @@ PerfSettings::PerfSettings(ProjectExplorer::Target *target) sampleMode.addOption({Tr::tr("event count"), {}, QString("-c")}); sampleMode.setDefaultValue(0); - registerAspect(&callgraphMode); callgraphMode.setSettingsKey("Analyzer.Perf.CallgraphMode"); callgraphMode.setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox); callgraphMode.setLabelText(Tr::tr("Call graph mode:")); @@ -54,11 +50,9 @@ PerfSettings::PerfSettings(ProjectExplorer::Target *target) callgraphMode.addOption({Tr::tr("last branch record"), {}, QString("lbr")}); callgraphMode.setDefaultValue(0); - registerAspect(&events); events.setSettingsKey("Analyzer.Perf.Events"); events.setDefaultValue({"cpu-cycles"}); - registerAspect(&extraArguments); extraArguments.setSettingsKey("Analyzer.Perf.ExtraArguments"); extraArguments.setDisplayStyle(StringAspect::DisplayStyle::LineEditDisplay); extraArguments.setLabelText(Tr::tr("Additional arguments:")); @@ -68,8 +62,6 @@ PerfSettings::PerfSettings(ProjectExplorer::Target *target) stackSize.setEnabled(index == 0); }); - connect(this, &AspectContainer::fromMapFinished, this, &PerfSettings::changed); - readGlobalSettings(); } @@ -103,7 +95,7 @@ void PerfSettings::writeGlobalSettings() const settings->endGroup(); } -QStringList PerfSettings::perfRecordArguments() const +void PerfSettings::addPerfRecordArguments(CommandLine *cmd) const { QString callgraphArg = callgraphMode.itemValue().toString(); if (callgraphArg == Constants::PerfCallgraphDwarf) @@ -118,11 +110,11 @@ QStringList PerfSettings::perfRecordArguments() const } } - return QStringList({"-e", events, - "--call-graph", callgraphArg, - sampleMode.itemValue().toString(), - QString::number(period.value())}) - + ProcessArgs::splitArgs(extraArguments.value()); + cmd->addArgs({"-e", events, + "--call-graph", callgraphArg, + sampleMode.itemValue().toString(), + QString::number(period.value())}); + cmd->addArgs(extraArguments(), CommandLine::Raw); } void PerfSettings::resetToDefault() diff --git a/src/plugins/perfprofiler/perfsettings.h b/src/plugins/perfprofiler/perfsettings.h index fc808c1e118..7c10706098b 100644 --- a/src/plugins/perfprofiler/perfsettings.h +++ b/src/plugins/perfprofiler/perfsettings.h @@ -14,7 +14,6 @@ namespace PerfProfiler { class PERFPROFILER_EXPORT PerfSettings final : public ProjectExplorer::ISettingsAspect { Q_OBJECT - Q_PROPERTY(QStringList perfRecordArguments READ perfRecordArguments NOTIFY changed) public: explicit PerfSettings(ProjectExplorer::Target *target = nullptr); @@ -23,19 +22,16 @@ public: void readGlobalSettings(); void writeGlobalSettings() const; - QStringList perfRecordArguments() const; + void addPerfRecordArguments(Utils::CommandLine *cmd) const; void resetToDefault(); - Utils::IntegerAspect period; - Utils::IntegerAspect stackSize; - Utils::SelectionAspect sampleMode; - Utils::SelectionAspect callgraphMode; - Utils::StringListAspect events; - Utils::StringAspect extraArguments; - -signals: - void changed(); + Utils::IntegerAspect period{this}; + Utils::IntegerAspect stackSize{this}; + Utils::SelectionAspect sampleMode{this}; + Utils::SelectionAspect callgraphMode{this}; + Utils::StringListAspect events{this}; + Utils::StringAspect extraArguments{this}; }; } // namespace PerfProfiler diff --git a/src/plugins/perfprofiler/perftracepointdialog.cpp b/src/plugins/perfprofiler/perftracepointdialog.cpp index da3b4c38c32..3920b526b89 100644 --- a/src/plugins/perfprofiler/perftracepointdialog.cpp +++ b/src/plugins/perfprofiler/perftracepointdialog.cpp @@ -8,12 +8,12 @@ #include #include #include -#include +#include #include -#include -#include #include +#include +#include #include #include @@ -41,7 +41,7 @@ PerfTracePointDialog::PerfTracePointDialog() m_privilegesChooser->addItems({ELEVATE_METHOD_NA, ELEVATE_METHOD_PKEXEC, ELEVATE_METHOD_SUDO}); m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - using namespace Utils::Layouting; + using namespace Layouting; Column { m_label, m_textEdit, @@ -51,7 +51,7 @@ PerfTracePointDialog::PerfTracePointDialog() m_buttonBox, }.attachTo(this); - if (const Target *target = SessionManager::startupTarget()) { + if (const Target *target = ProjectManager::startupTarget()) { const Kit *kit = target->kit(); QTC_ASSERT(kit, return); @@ -93,7 +93,7 @@ void PerfTracePointDialog::runScript() m_privilegesChooser->setEnabled(false); m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - m_process.reset(new QtcProcess(this)); + m_process.reset(new Process(this)); m_process->setWriteData(m_textEdit->toPlainText().toUtf8()); m_textEdit->clear(); @@ -103,7 +103,7 @@ void PerfTracePointDialog::runScript() else m_process->setCommand({m_device->filePath("sh"), {}}); - connect(m_process.get(), &QtcProcess::done, this, &PerfTracePointDialog::handleProcessDone); + connect(m_process.get(), &Process::done, this, &PerfTracePointDialog::handleProcessDone); m_process->start(); } diff --git a/src/plugins/perfprofiler/perftracepointdialog.h b/src/plugins/perfprofiler/perftracepointdialog.h index 427220e64e5..d5affdcc7cb 100644 --- a/src/plugins/perfprofiler/perftracepointdialog.h +++ b/src/plugins/perfprofiler/perftracepointdialog.h @@ -15,7 +15,7 @@ class QLabel; class QTextEdit; QT_END_NAMESPACE -namespace Utils { class QtcProcess; } +namespace Utils { class Process; } namespace PerfProfiler { namespace Internal { @@ -40,7 +40,7 @@ private: QComboBox *m_privilegesChooser; QDialogButtonBox *m_buttonBox; ProjectExplorer::IDeviceConstPtr m_device; - std::unique_ptr m_process; + std::unique_ptr m_process; void accept() final; void reject() final; diff --git a/src/plugins/plugins.qbs b/src/plugins/plugins.qbs index b878e76c3e3..6deee972959 100644 --- a/src/plugins/plugins.qbs +++ b/src/plugins/plugins.qbs @@ -7,6 +7,7 @@ Project { "android/android.qbs", "autotest/autotest.qbs", "autotoolsprojectmanager/autotoolsprojectmanager.qbs", + "axivion/axivion.qbs", "baremetal/baremetal.qbs", "bazaar/bazaar.qbs", "beautifier/beautifier.qbs", @@ -23,6 +24,7 @@ Project { "coco/coco.qbs", "compilationdatabaseprojectmanager/compilationdatabaseprojectmanager.qbs", "conan/conan.qbs", + "copilot/copilot.qbs", "coreplugin/coreplugin.qbs", "coreplugin/images/logo/logo.qbs", "cpaster/cpaster.qbs", @@ -43,6 +45,7 @@ Project { "git/git.qbs", "gitlab/gitlab.qbs", "glsleditor/glsleditor.qbs", + "haskell/haskell.qbs", "helloworld/helloworld.qbs", "help/help.qbs", "imageviewer/imageviewer.qbs", @@ -68,6 +71,7 @@ Project { "qmlprojectmanager/qmlprojectmanager.qbs", "qnx/qnx.qbs", "qmakeprojectmanager/qmakeprojectmanager.qbs", + "qmldesignerbase/qmldesignerbase.qbs", "qtsupport/qtsupport.qbs", "remotelinux/remotelinux.qbs", "resourceeditor/resourceeditor.qbs", @@ -78,12 +82,14 @@ Project { "squish/squish.qbs", "studiowelcome/studiowelcome.qbs", "subversion/subversion.qbs", + "terminal/terminal.qbs", "texteditor/texteditor.qbs", "todo/todo.qbs", "updateinfo/updateinfo.qbs", "valgrind/valgrind.qbs", + "vcpkg/vcpkg.qbs", "vcsbase/vcsbase.qbs", "webassembly/webassembly.qbs", - "welcome/welcome.qbs" + "welcome/welcome.qbs", ].concat(project.additionalPlugins) } diff --git a/src/plugins/projectexplorer/CMakeLists.txt b/src/plugins/projectexplorer/CMakeLists.txt index d05509636ec..d16e868a5cc 100644 --- a/src/plugins/projectexplorer/CMakeLists.txt +++ b/src/plugins/projectexplorer/CMakeLists.txt @@ -28,6 +28,7 @@ add_qtc_plugin(ProjectExplorer codestylesettingspropertiespage.cpp codestylesettingspropertiespage.h compileoutputwindow.cpp compileoutputwindow.h configtaskhandler.cpp configtaskhandler.h + copystep.cpp copystep.h copytaskhandler.cpp copytaskhandler.h currentprojectfilter.cpp currentprojectfilter.h currentprojectfind.cpp currentprojectfind.h @@ -65,8 +66,7 @@ add_qtc_plugin(ProjectExplorer devicesupport/idevicefactory.cpp devicesupport/idevicefactory.h devicesupport/idevicefwd.h devicesupport/idevicewidget.h - devicesupport/localprocesslist.cpp devicesupport/localprocesslist.h - devicesupport/sshdeviceprocesslist.cpp devicesupport/sshdeviceprocesslist.h + devicesupport/processlist.cpp devicesupport/processlist.h devicesupport/sshparameters.cpp devicesupport/sshparameters.h devicesupport/sshsettings.cpp devicesupport/sshsettings.h devicesupport/sshsettingspage.cpp devicesupport/sshsettingspage.h @@ -115,7 +115,6 @@ add_qtc_plugin(ProjectExplorer ldparser.cpp ldparser.h lldparser.cpp lldparser.h linuxiccparser.cpp linuxiccparser.h - localenvironmentaspect.cpp localenvironmentaspect.h makestep.cpp makestep.h miniprojecttargetselector.cpp miniprojecttargetselector.h msvcparser.cpp msvcparser.h @@ -135,13 +134,12 @@ add_qtc_plugin(ProjectExplorer projectexplorerconstants.cpp projectexplorerconstants.h projectexplorericons.cpp projectexplorericons.h - projectexplorersettings.h - projectexplorersettingspage.cpp projectexplorersettingspage.h + projectexplorersettings.cpp projectexplorersettings.h projectexplorertr.h projectfilewizardextension.cpp projectfilewizardextension.h projectimporter.cpp projectimporter.h projectmacro.cpp projectmacro.h - projectmanager.h + projectmanager.cpp projectmanager.h projectmodels.cpp projectmodels.h projectnodes.cpp projectnodes.h projectpanelfactory.cpp projectpanelfactory.h @@ -159,10 +157,6 @@ add_qtc_plugin(ProjectExplorer runsettingspropertiespage.cpp runsettingspropertiespage.h sanitizerparser.cpp sanitizerparser.h selectablefilesmodel.cpp selectablefilesmodel.h - session.cpp session.h - sessiondialog.cpp sessiondialog.h - sessionmodel.cpp sessionmodel.h - sessionview.cpp sessionview.h showineditortaskhandler.cpp showineditortaskhandler.h showoutputtaskhandler.cpp showoutputtaskhandler.h simpleprojectwizard.cpp simpleprojectwizard.h diff --git a/src/plugins/projectexplorer/ProjectExplorer.json.in b/src/plugins/projectexplorer/ProjectExplorer.json.in index d96dcc26c6d..c9b54ad441c 100644 --- a/src/plugins/projectexplorer/ProjectExplorer.json.in +++ b/src/plugins/projectexplorer/ProjectExplorer.json.in @@ -24,14 +24,6 @@ \"Name\" : \"-ensure-kit-for-binary\", \"Parameter\" : \"file path\", \"Description\" : \"Create kit with architecture matching a given application or library\" - }, - { - \"Name\" : \"-lastsession\", - \"Description\" : \"Restore the last session\" - }, - { - \"Name\" : \"\", - \"Description\" : \"Restore a saved session\" } ], $$dependencyList, diff --git a/src/plugins/projectexplorer/abstractprocessstep.cpp b/src/plugins/projectexplorer/abstractprocessstep.cpp index c838458aa5d..806d9159f95 100644 --- a/src/plugins/projectexplorer/abstractprocessstep.cpp +++ b/src/plugins/projectexplorer/abstractprocessstep.cpp @@ -12,14 +12,15 @@ #include #include +#include #include -#include #include #include #include +using namespace Tasking; using namespace Utils; namespace ProjectExplorer { @@ -76,7 +77,7 @@ public: void cleanUp(int exitCode, QProcess::ExitStatus status); AbstractProcessStep *q; - std::unique_ptr m_process; + std::unique_ptr m_process; std::unique_ptr m_taskTree; ProcessParameters m_param; ProcessParameters *m_displayedParams = &m_param; @@ -182,9 +183,9 @@ void AbstractProcessStep::doRun() setupStreams(); - d->m_process.reset(new QtcProcess); + d->m_process.reset(new Process); setupProcess(d->m_process.get()); - connect(d->m_process.get(), &QtcProcess::done, this, &AbstractProcessStep::handleProcessDone); + connect(d->m_process.get(), &Process::done, this, &AbstractProcessStep::handleProcessDone); d->m_process->start(); } @@ -209,7 +210,7 @@ void AbstractProcessStep::setupStreams() d->stderrStream = std::make_unique(QTextCodec::codecForLocale()); } -void AbstractProcessStep::setupProcess(QtcProcess *process) +void AbstractProcessStep::setupProcess(Process *process) { process->setUseCtrlCStub(HostOsInfo::isWindowsHost()); process->setWorkingDirectory(d->m_param.effectiveWorkingDirectory()); @@ -224,15 +225,15 @@ void AbstractProcessStep::setupProcess(QtcProcess *process) if (d->m_lowPriority && ProjectExplorerPlugin::projectExplorerSettings().lowBuildPriority) process->setLowPriority(); - connect(process, &QtcProcess::readyReadStandardOutput, this, [this, process] { + connect(process, &Process::readyReadStandardOutput, this, [this, process] { emit addOutput(d->stdoutStream->toUnicode(process->readAllRawStandardOutput()), OutputFormat::Stdout, DontAppendNewline); }); - connect(process, &QtcProcess::readyReadStandardError, this, [this, process] { + connect(process, &Process::readyReadStandardError, this, [this, process] { emit addOutput(d->stderrStream->toUnicode(process->readAllRawStandardError()), OutputFormat::Stderr, DontAppendNewline); }); - connect(process, &QtcProcess::started, this, [this] { + connect(process, &Process::started, this, [this] { ProcessParameters *params = displayedParameters(); emit addOutput(Tr::tr("Starting: \"%1\" %2") .arg(params->effectiveCommand().toUserOutput(), params->prettyArguments()), @@ -240,7 +241,7 @@ void AbstractProcessStep::setupProcess(QtcProcess *process) }); } -void AbstractProcessStep::runTaskTree(const Tasking::Group &recipe) +void AbstractProcessStep::runTaskTree(const Group &recipe) { setupStreams(); @@ -306,7 +307,7 @@ bool AbstractProcessStep::setupProcessParameters(ProcessParameters *params) cons const bool looksGood = executable.isEmpty() || executable.ensureReachable(workingDirectory); QTC_ASSERT(looksGood, return false); - params->setWorkingDirectory(workingDirectory.onDevice(executable)); + params->setWorkingDirectory(executable.withNewPath(workingDirectory.path())); return true; } diff --git a/src/plugins/projectexplorer/abstractprocessstep.h b/src/plugins/projectexplorer/abstractprocessstep.h index 81125414471..fc1ed131357 100644 --- a/src/plugins/projectexplorer/abstractprocessstep.h +++ b/src/plugins/projectexplorer/abstractprocessstep.h @@ -10,10 +10,11 @@ namespace Utils { class CommandLine; enum class ProcessResult; -class QtcProcess; -namespace Tasking { class Group; } +class Process; } +namespace Tasking { class Group; } + namespace ProjectExplorer { class ProcessParameters; @@ -51,8 +52,8 @@ protected: virtual void finish(Utils::ProcessResult result); bool checkWorkingDirectory(); - void setupProcess(Utils::QtcProcess *process); - void runTaskTree(const Utils::Tasking::Group &recipe); + void setupProcess(Utils::Process *process); + void runTaskTree(const Tasking::Group &recipe); ProcessParameters *displayedParameters() const; private: diff --git a/src/plugins/projectexplorer/allprojectsfilter.cpp b/src/plugins/projectexplorer/allprojectsfilter.cpp index ea4d86dba7d..42e110d3504 100644 --- a/src/plugins/projectexplorer/allprojectsfilter.cpp +++ b/src/plugins/projectexplorer/allprojectsfilter.cpp @@ -3,14 +3,17 @@ #include "allprojectsfilter.h" +#include "project.h" #include "projectexplorer.h" #include "projectexplorertr.h" -#include "session.h" -#include "project.h" +#include "projectmanager.h" #include +#include + using namespace Core; +using namespace Utils; namespace ProjectExplorer::Internal { @@ -18,38 +21,29 @@ AllProjectsFilter::AllProjectsFilter() { setId("Files in any project"); setDisplayName(Tr::tr("Files in Any Project")); - setDescription(Tr::tr("Matches all files of all open projects. Append \"+\" or " - "\":\" to jump to the given line number. Append another " - "\"+\" or \":\" to jump to the column number as well.")); + setDescription(Tr::tr("Locates files of all open projects. Append \"+\" or " + "\":\" to jump to the given line number. Append another " + "\"+\" or \":\" to jump to the column number as well.")); setDefaultShortcutString("a"); setDefaultIncludedByDefault(true); + setRefreshRecipe(Tasking::Sync([this] { m_cache.invalidate(); })); connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::fileListChanged, - this, &AllProjectsFilter::markFilesAsOutOfDate); + this, [this] { m_cache.invalidate(); }); + m_cache.setGeneratorProvider([] { + // This body runs in main thread + FilePaths filePaths; + for (Project *project : ProjectManager::projects()) + filePaths.append(project->files(Project::SourceFiles)); + return [filePaths](const QFuture &future) { + // This body runs in non-main thread + FilePaths sortedPaths = filePaths; + if (future.isCanceled()) + return FilePaths(); + Utils::sort(sortedPaths); + return sortedPaths; + }; + }); } -void AllProjectsFilter::markFilesAsOutOfDate() -{ - setFileIterator(nullptr); -} - -void AllProjectsFilter::prepareSearch(const QString &entry) -{ - Q_UNUSED(entry) - if (!fileIterator()) { - Utils::FilePaths paths; - for (Project *project : SessionManager::projects()) - paths.append(project->files(Project::SourceFiles)); - Utils::sort(paths); - setFileIterator(new BaseFileFilter::ListIterator(paths)); - } - BaseFileFilter::prepareSearch(entry); -} - -void AllProjectsFilter::refresh(QFutureInterface &future) -{ - Q_UNUSED(future) - QMetaObject::invokeMethod(this, &AllProjectsFilter::markFilesAsOutOfDate, Qt::QueuedConnection); -} - -} // ProjectExplorer::Internal +} // namespace ProjectExplorer::Internal diff --git a/src/plugins/projectexplorer/allprojectsfilter.h b/src/plugins/projectexplorer/allprojectsfilter.h index ae9fdffe92b..6782ef46115 100644 --- a/src/plugins/projectexplorer/allprojectsfilter.h +++ b/src/plugins/projectexplorer/allprojectsfilter.h @@ -3,25 +3,18 @@ #pragma once -#include +#include -#include +namespace ProjectExplorer::Internal { -namespace ProjectExplorer { -namespace Internal { - -class AllProjectsFilter : public Core::BaseFileFilter +class AllProjectsFilter : public Core::ILocatorFilter { - Q_OBJECT - public: AllProjectsFilter(); - void refresh(QFutureInterface &future) override; - void prepareSearch(const QString &entry) override; private: - void markFilesAsOutOfDate(); + Core::LocatorMatcherTasks matchers() final { return {m_cache.matcher()}; } + Core::LocatorFileCache m_cache; }; -} // namespace Internal -} // namespace ProjectExplorer +} // namespace ProjectExplorer::Internal diff --git a/src/plugins/projectexplorer/allprojectsfind.cpp b/src/plugins/projectexplorer/allprojectsfind.cpp index ac4afc1d6e2..d61b1560523 100644 --- a/src/plugins/projectexplorer/allprojectsfind.cpp +++ b/src/plugins/projectexplorer/allprojectsfind.cpp @@ -7,7 +7,7 @@ #include "project.h" #include "projectexplorer.h" #include "projectexplorertr.h" -#include "session.h" +#include "projectmanager.h" #include @@ -44,7 +44,7 @@ QString AllProjectsFind::displayName() const bool AllProjectsFind::isEnabled() const { - return BaseFileFind::isEnabled() && SessionManager::hasProjects(); + return BaseFileFind::isEnabled() && ProjectManager::hasProjects(); } FileIterator *AllProjectsFind::files(const QStringList &nameFilters, @@ -52,7 +52,7 @@ FileIterator *AllProjectsFind::files(const QStringList &nameFilters, const QVariant &additionalParameters) const { Q_UNUSED(additionalParameters) - return filesForProjects(nameFilters, exclusionFilters, SessionManager::projects()); + return filesForProjects(nameFilters, exclusionFilters, ProjectManager::projects()); } FileIterator *AllProjectsFind::filesForProjects(const QStringList &nameFilters, diff --git a/src/plugins/projectexplorer/appoutputpane.cpp b/src/plugins/projectexplorer/appoutputpane.cpp index 6f7e22bacd2..3c76c2e62a0 100644 --- a/src/plugins/projectexplorer/appoutputpane.cpp +++ b/src/plugins/projectexplorer/appoutputpane.cpp @@ -8,7 +8,6 @@ #include "projectexplorericons.h" #include "projectexplorertr.h" #include "runcontrol.h" -#include "session.h" #include "showoutputtaskhandler.h" #include "windebuginterface.h" @@ -17,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +46,7 @@ static Q_LOGGING_CATEGORY(appOutputLog, "qtc.projectexplorer.appoutput", QtWarningMsg); +using namespace Core; using namespace Utils; namespace ProjectExplorer { diff --git a/src/plugins/projectexplorer/buildaspects.cpp b/src/plugins/projectexplorer/buildaspects.cpp index adf37a33620..606911654fd 100644 --- a/src/plugins/projectexplorer/buildaspects.cpp +++ b/src/plugins/projectexplorer/buildaspects.cpp @@ -41,7 +41,6 @@ BuildDirectoryAspect::BuildDirectoryAspect(const BuildConfiguration *bc) { setSettingsKey("ProjectExplorer.BuildConfiguration.BuildDirectory"); setLabelText(Tr::tr("Build directory:")); - setDisplayStyle(PathChooserDisplay); setExpectedKind(Utils::PathChooser::Directory); setValidationFunction([this](FancyLineEdit *edit, QString *error) { const FilePath fixedDir = fixupDir(FilePath::fromUserInput(edit->text())); @@ -107,12 +106,12 @@ void BuildDirectoryAspect::fromMap(const QVariantMap &map) } } -void BuildDirectoryAspect::addToLayout(Layouting::LayoutBuilder &builder) +void BuildDirectoryAspect::addToLayout(Layouting::LayoutItem &parent) { - StringAspect::addToLayout(builder); + StringAspect::addToLayout(parent); d->problemLabel = new InfoLabel({}, InfoLabel::Warning); d->problemLabel->setElideMode(Qt::ElideNone); - builder.addRow({{}, d->problemLabel.data()}); + parent.addItems({{}, d->problemLabel.data()}); updateProblemLabel(); if (!d->sourceDir.isEmpty()) { connect(this, &StringAspect::checkedChanged, this, [this] { diff --git a/src/plugins/projectexplorer/buildaspects.h b/src/plugins/projectexplorer/buildaspects.h index 9788cc69cad..bcc8987cd0c 100644 --- a/src/plugins/projectexplorer/buildaspects.h +++ b/src/plugins/projectexplorer/buildaspects.h @@ -7,12 +7,11 @@ #include -namespace Utils { class FilePath; } - namespace ProjectExplorer { + class BuildConfiguration; -class PROJECTEXPLORER_EXPORT BuildDirectoryAspect : public Utils::StringAspect +class PROJECTEXPLORER_EXPORT BuildDirectoryAspect : public Utils::FilePathAspect { Q_OBJECT public: @@ -23,7 +22,7 @@ public: bool isShadowBuild() const; void setProblem(const QString &description); - void addToLayout(Utils::Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; static Utils::FilePath fixupDir(const Utils::FilePath &dir); diff --git a/src/plugins/projectexplorer/buildconfiguration.cpp b/src/plugins/projectexplorer/buildconfiguration.cpp index ff14b7ab07b..70d5a348c1c 100644 --- a/src/plugins/projectexplorer/buildconfiguration.cpp +++ b/src/plugins/projectexplorer/buildconfiguration.cpp @@ -17,8 +17,8 @@ #include "projectexplorer.h" #include "projectexplorertr.h" #include "project.h" +#include "projectmanager.h" #include "projecttree.h" -#include "session.h" #include "target.h" #include @@ -167,6 +167,10 @@ BuildConfiguration::BuildConfiguration(Target *target, Utils::Id id) expander->registerVariable("buildDir", Tr::tr("Build directory"), [this] { return buildDirectory().toUserOutput(); }); + expander->registerFileVariables("BuildConfig:BuildDirectory", + Tr::tr("Build directory"), + [this] { return buildDirectory(); }); + expander->registerVariable("BuildConfig:Name", Tr::tr("Name of the build configuration"), [this] { return displayName(); }); @@ -209,7 +213,7 @@ BuildConfiguration::BuildConfiguration(Target *target, Utils::Id id) connect(target, &Target::parsingStarted, this, &BuildConfiguration::enabledChanged); connect(target, &Target::parsingFinished, this, &BuildConfiguration::enabledChanged); connect(this, &BuildConfiguration::enabledChanged, this, [this] { - if (isActive() && project() == SessionManager::startupProject()) { + if (isActive() && project() == ProjectManager::startupProject()) { ProjectExplorerPlugin::updateActions(); ProjectExplorerPlugin::updateRunActions(); } @@ -318,12 +322,15 @@ NamedWidget *BuildConfiguration::createConfigWidget() widget = named; } - Layouting::Form builder; + Layouting::Form form; for (BaseAspect *aspect : aspects()) { - if (aspect->isVisible()) - aspect->addToLayout(builder.finishRow()); + if (aspect->isVisible()) { + form.addItem(aspect); + form.addItem(Layouting::br); + } } - builder.attachTo(widget, Layouting::WithoutMargins); + form.addItem(Layouting::noMargin); + form.attachTo(widget); return named; } @@ -612,13 +619,27 @@ FilePath BuildConfiguration::buildDirectoryFromTemplate(const FilePath &projectD [buildType] { return buildTypeName(buildType); }); exp.registerSubProvider([kit] { return kit->macroExpander(); }); - QString buildDir = ProjectExplorerPlugin::buildDirectoryTemplate(); - qCDebug(bcLog) << "build dir template:" << buildDir; + FilePath buildDir = FilePath::fromUserInput(ProjectExplorerPlugin::buildDirectoryTemplate()); + qCDebug(bcLog) << "build dir template:" << buildDir.toUserOutput(); buildDir = exp.expand(buildDir); - qCDebug(bcLog) << "expanded build:" << buildDir; - buildDir.replace(" ", "-"); + qCDebug(bcLog) << "expanded build:" << buildDir.toUserOutput(); + buildDir = buildDir.withNewPath(buildDir.path().replace(" ", "-")); - return projectDir.resolvePath(buildDir); + auto buildDevice = BuildDeviceKitAspect::device(kit); + + if (buildDir.isAbsolutePath()) { + bool isReachable = buildDevice->ensureReachable(buildDir); + if (!isReachable) + return {}; + return buildDevice->rootPath().withNewMappedPath(buildDir); + } + + bool isReachable = buildDevice->ensureReachable(projectDir); + if (!isReachable) + return {}; + + const FilePath baseDir = buildDevice->rootPath().withNewMappedPath(projectDir); + return baseDir.resolvePath(buildDir); } /// // IBuildConfigurationFactory diff --git a/src/plugins/projectexplorer/buildmanager.cpp b/src/plugins/projectexplorer/buildmanager.cpp index 57bfde6e46a..28169513cb7 100644 --- a/src/plugins/projectexplorer/buildmanager.cpp +++ b/src/plugins/projectexplorer/buildmanager.cpp @@ -16,8 +16,8 @@ #include "projectexplorerconstants.h" #include "projectexplorersettings.h" #include "projectexplorertr.h" +#include "projectmanager.h" #include "runcontrol.h" -#include "session.h" #include "target.h" #include "task.h" #include "taskhub.h" @@ -258,7 +258,7 @@ BuildManager::BuildManager(QObject *parent, QAction *cancelBuildAction) m_instance = this; d = new BuildManagerPrivate; - connect(SessionManager::instance(), &SessionManager::aboutToRemoveProject, + connect(ProjectManager::instance(), &ProjectManager::aboutToRemoveProject, this, &BuildManager::aboutToRemoveProject); d->m_outputWindow = new Internal::CompileOutputWindow(cancelBuildAction); @@ -318,19 +318,19 @@ void BuildManager::rebuildProjectWithoutDependencies(Project *project) void BuildManager::buildProjectWithDependencies(Project *project, ConfigSelection configSelection) { - queue(SessionManager::projectOrder(project), {Id(Constants::BUILDSTEPS_BUILD)}, + queue(ProjectManager::projectOrder(project), {Id(Constants::BUILDSTEPS_BUILD)}, configSelection); } void BuildManager::cleanProjectWithDependencies(Project *project, ConfigSelection configSelection) { - queue(SessionManager::projectOrder(project), {Id(Constants::BUILDSTEPS_CLEAN)}, + queue(ProjectManager::projectOrder(project), {Id(Constants::BUILDSTEPS_CLEAN)}, configSelection); } void BuildManager::rebuildProjectWithDependencies(Project *project, ConfigSelection configSelection) { - queue(SessionManager::projectOrder(project), + queue(ProjectManager::projectOrder(project), {Id(Constants::BUILDSTEPS_CLEAN), Id(Constants::BUILDSTEPS_BUILD)}, configSelection); } @@ -384,7 +384,7 @@ BuildForRunConfigStatus BuildManager::potentiallyBuildForRunConfig(RunConfigurat } Project * const pro = rc->target()->project(); - const int queueCount = queue(SessionManager::projectOrder(pro), stepIds, + const int queueCount = queue(ProjectManager::projectOrder(pro), stepIds, ConfigSelection::Active, rc); if (rc->target()->activeBuildConfiguration()) rc->target()->activeBuildConfiguration()->restrictNextBuild(nullptr); @@ -442,16 +442,6 @@ int BuildManager::getErrorTaskCount() return errors; } -void BuildManager::setCompileOutputSettings(const CompileOutputSettings &settings) -{ - d->m_outputWindow->setSettings(settings); -} - -const CompileOutputSettings &BuildManager::compileOutputSettings() -{ - return d->m_outputWindow->settings(); -} - QString BuildManager::displayNameForStepId(Id stepId) { if (stepId == Constants::BUILDSTEPS_CLEAN) { @@ -853,7 +843,7 @@ bool BuildManager::buildLists(const QList bsls, const QStringLi return false; } - if (d->m_outputWindow->settings().popUp) + if (CompileOutputSettings::instance().popUp()) d->m_outputWindow->popup(IOutputPane::NoModeSwitch); startBuildQueue(); return true; @@ -866,7 +856,7 @@ void BuildManager::appendStep(BuildStep *step, const QString &name) d->m_outputWindow->popup(IOutputPane::NoModeSwitch); return; } - if (d->m_outputWindow->settings().popUp) + if (CompileOutputSettings::instance().popUp()) d->m_outputWindow->popup(IOutputPane::NoModeSwitch); startBuildQueue(); } diff --git a/src/plugins/projectexplorer/buildmanager.h b/src/plugins/projectexplorer/buildmanager.h index d7d2c91562a..d9478db4093 100644 --- a/src/plugins/projectexplorer/buildmanager.h +++ b/src/plugins/projectexplorer/buildmanager.h @@ -10,12 +10,10 @@ #include namespace ProjectExplorer { -class RunConfiguration; -namespace Internal { class CompileOutputSettings; } - -class Task; class Project; +class RunConfiguration; +class Task; enum class BuildForRunConfigStatus { Building, NotBuilding, BuildFailed }; enum class ConfigSelection { All, Active }; @@ -66,9 +64,6 @@ public: static int getErrorTaskCount(); - static void setCompileOutputSettings(const Internal::CompileOutputSettings &settings); - static const Internal::CompileOutputSettings &compileOutputSettings(); - static QString displayNameForStepId(Utils::Id stepId); public slots: diff --git a/src/plugins/projectexplorer/buildpropertiessettings.cpp b/src/plugins/projectexplorer/buildpropertiessettings.cpp index a9126551e3e..6df8fdd328e 100644 --- a/src/plugins/projectexplorer/buildpropertiessettings.cpp +++ b/src/plugins/projectexplorer/buildpropertiessettings.cpp @@ -16,15 +16,33 @@ namespace ProjectExplorer { const char DEFAULT_BUILD_DIRECTORY_TEMPLATE[] = "../%{JS: Util.asciify(\"build-%{Project:Name}-%{Kit:FileSystemName}-%{BuildConfig:Name}\")}"; -BuildPropertiesSettings::BuildTriStateAspect::BuildTriStateAspect() - : TriStateAspect{Tr::tr("Enable"), Tr::tr("Disable"), Tr::tr("Use Project Default")} +BuildPropertiesSettings::BuildTriStateAspect::BuildTriStateAspect(AspectContainer *container) + : TriStateAspect(container, Tr::tr("Enable"), Tr::tr("Disable"), Tr::tr("Use Project Default")) {} BuildPropertiesSettings::BuildPropertiesSettings() { setAutoApply(false); - registerAspect(&buildDirectoryTemplate); + setId("AB.ProjectExplorer.BuildPropertiesSettingsPage"); + setDisplayName(Tr::tr("Default Build Properties")); + setCategory(ProjectExplorer::Constants::BUILD_AND_RUN_SETTINGS_CATEGORY); + setSettings(this); + + setLayouter([this] { + using namespace Layouting; + + return Column { + Form { + buildDirectoryTemplate, br, + separateDebugInfo, br, + qmlDebugging, br, + qtQuickCompiler + }, + st + }; + }); + buildDirectoryTemplate.setDisplayStyle(StringAspect::LineEditDisplay); buildDirectoryTemplate.setSettingsKey("Directories/BuildDirectory.TemplateV2"); buildDirectoryTemplate.setDefaultValue(DEFAULT_BUILD_DIRECTORY_TEMPLATE); @@ -32,19 +50,12 @@ BuildPropertiesSettings::BuildPropertiesSettings() buildDirectoryTemplate.setUseGlobalMacroExpander(); buildDirectoryTemplate.setUseResetButton(); - registerAspect(&buildDirectoryTemplateOld); // TODO: Remove in ~4.16 - buildDirectoryTemplateOld.setSettingsKey("Directories/BuildDirectory.Template"); - buildDirectoryTemplateOld.setDefaultValue(DEFAULT_BUILD_DIRECTORY_TEMPLATE); - - registerAspect(&separateDebugInfo); separateDebugInfo.setSettingsKey("ProjectExplorer/Settings/SeparateDebugInfo"); separateDebugInfo.setLabelText(Tr::tr("Separate debug info:")); - registerAspect(&qmlDebugging); qmlDebugging.setSettingsKey("ProjectExplorer/Settings/QmlDebugging"); qmlDebugging.setLabelText(Tr::tr("QML debugging:")); - registerAspect(&qtQuickCompiler); qtQuickCompiler.setSettingsKey("ProjectExplorer/Settings/QtQuickCompiler"); qtQuickCompiler.setLabelText(Tr::tr("Use qmlcachegen:")); @@ -54,51 +65,9 @@ BuildPropertiesSettings::BuildPropertiesSettings() &qtQuickCompiler, &BaseAspect::setVisible); } -void BuildPropertiesSettings::readSettings(QSettings *s) -{ - AspectContainer::readSettings(s); - - // TODO: Remove in ~4.16 - QString v = buildDirectoryTemplate.value(); - if (v.isEmpty()) - v = buildDirectoryTemplateOld.value(); - if (v.isEmpty()) - v = DEFAULT_BUILD_DIRECTORY_TEMPLATE; - v.replace("%{CurrentProject:Name}", "%{Project:Name}"); - v.replace("%{CurrentKit:FileSystemName}", "%{Kit:FileSystemName}"); - v.replace("%{CurrentBuild:Name}", "%{BuildConfig:Name}"); - buildDirectoryTemplate.setValue(v); -} - QString BuildPropertiesSettings::defaultBuildDirectoryTemplate() { return QString(DEFAULT_BUILD_DIRECTORY_TEMPLATE); } -namespace Internal { - -BuildPropertiesSettingsPage::BuildPropertiesSettingsPage(BuildPropertiesSettings *settings) -{ - setId("AB.ProjectExplorer.BuildPropertiesSettingsPage"); - setDisplayName(Tr::tr("Default Build Properties")); - setCategory(ProjectExplorer::Constants::BUILD_AND_RUN_SETTINGS_CATEGORY); - setSettings(settings); - - setLayouter([settings](QWidget *widget) { - BuildPropertiesSettings &s = *settings; - using namespace Layouting; - - Column { - Form { - s.buildDirectoryTemplate, - s.separateDebugInfo, - s.qmlDebugging, - s.qtQuickCompiler - }, - st - }.attachTo(widget); - }); -} - -} // Internal } // ProjectExplorer diff --git a/src/plugins/projectexplorer/buildpropertiessettings.h b/src/plugins/projectexplorer/buildpropertiessettings.h index 75ec957a4ea..3b1b2b7c698 100644 --- a/src/plugins/projectexplorer/buildpropertiessettings.h +++ b/src/plugins/projectexplorer/buildpropertiessettings.h @@ -7,11 +7,9 @@ #include -#include - namespace ProjectExplorer { -class PROJECTEXPLORER_EXPORT BuildPropertiesSettings : public Utils::AspectContainer +class PROJECTEXPLORER_EXPORT BuildPropertiesSettings : public Core::PagedSettings { public: BuildPropertiesSettings(); @@ -19,28 +17,16 @@ public: class BuildTriStateAspect : public Utils::TriStateAspect { public: - BuildTriStateAspect(); + explicit BuildTriStateAspect(AspectContainer *container); }; - Utils::StringAspect buildDirectoryTemplate; - Utils::StringAspect buildDirectoryTemplateOld; // TODO: Remove in ~4.16 - BuildTriStateAspect separateDebugInfo; - BuildTriStateAspect qmlDebugging; - BuildTriStateAspect qtQuickCompiler; + Utils::StringAspect buildDirectoryTemplate{this}; + BuildTriStateAspect separateDebugInfo{this}; + BuildTriStateAspect qmlDebugging{this}; + BuildTriStateAspect qtQuickCompiler{this}; Utils::BoolAspect showQtSettings; - void readSettings(QSettings *settings); - QString defaultBuildDirectoryTemplate(); }; -namespace Internal { - -class BuildPropertiesSettingsPage final : public Core::IOptionsPage -{ -public: - explicit BuildPropertiesSettingsPage(BuildPropertiesSettings *settings); -}; - -} // namespace Internal } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/buildsettingspropertiespage.cpp b/src/plugins/projectexplorer/buildsettingspropertiespage.cpp index 410449083c7..2caa44712e0 100644 --- a/src/plugins/projectexplorer/buildsettingspropertiespage.cpp +++ b/src/plugins/projectexplorer/buildsettingspropertiespage.cpp @@ -10,10 +10,10 @@ #include "project.h" #include "projectconfigurationmodel.h" #include "projectexplorertr.h" -#include "session.h" #include "target.h" #include +#include #include #include @@ -185,7 +185,7 @@ void BuildSettingsWidget::currentIndexChanged(int index) { auto buildConfiguration = qobject_cast( m_target->buildConfigurationModel()->projectConfigurationAt(index)); - SessionManager::setActiveBuildConfiguration(m_target, buildConfiguration, SetActive::Cascade); + m_target->setActiveBuildConfiguration(buildConfiguration, SetActive::Cascade); } void BuildSettingsWidget::updateActiveConfiguration() @@ -222,7 +222,7 @@ void BuildSettingsWidget::createConfiguration(const BuildInfo &info_) return; m_target->addBuildConfiguration(bc); - SessionManager::setActiveBuildConfiguration(m_target, bc, SetActive::Cascade); + m_target->setActiveBuildConfiguration(bc, SetActive::Cascade); } QString BuildSettingsWidget::uniqueName(const QString & name) @@ -286,7 +286,7 @@ void BuildSettingsWidget::cloneConfiguration() bc->setDisplayName(name); const FilePath buildDirectory = bc->buildDirectory(); if (buildDirectory != m_target->project()->projectDirectory()) { - const std::function isBuildDirOk = [this](const FilePath &candidate) { + const FilePathPredicate isBuildDirOk = [this](const FilePath &candidate) { if (candidate.exists()) return false; return !anyOf(m_target->buildConfigurations(), [&candidate](const BuildConfiguration *bc) { @@ -295,7 +295,7 @@ void BuildSettingsWidget::cloneConfiguration() bc->setBuildDirectory(makeUniquelyNumbered(buildDirectory, isBuildDirOk)); } m_target->addBuildConfiguration(bc); - SessionManager::setActiveBuildConfiguration(m_target, bc, SetActive::Cascade); + m_target->setActiveBuildConfiguration(bc, SetActive::Cascade); } void BuildSettingsWidget::deleteConfiguration(BuildConfiguration *deleteConfiguration) diff --git a/src/plugins/projectexplorer/buildstep.cpp b/src/plugins/projectexplorer/buildstep.cpp index c078a73d822..32be37048e9 100644 --- a/src/plugins/projectexplorer/buildstep.cpp +++ b/src/plugins/projectexplorer/buildstep.cpp @@ -147,7 +147,7 @@ QWidget *BuildStep::doCreateConfigWidget() setSummaryText(m_summaryUpdater()); }; - for (BaseAspect *aspect : std::as_const(m_aspects)) + for (BaseAspect *aspect : std::as_const(*this)) connect(aspect, &BaseAspect::changed, widget, recreateSummary); connect(buildConfiguration(), &BuildConfiguration::buildDirectoryChanged, @@ -160,12 +160,13 @@ QWidget *BuildStep::doCreateConfigWidget() QWidget *BuildStep::createConfigWidget() { - Layouting::Form builder; - for (BaseAspect *aspect : std::as_const(m_aspects)) { + Layouting::Form form; + for (BaseAspect *aspect : std::as_const(*this)) { if (aspect->isVisible()) - aspect->addToLayout(builder.finishRow()); + form.addItem(aspect); } - auto widget = builder.emerge(Layouting::WithoutMargins); + form.addItem(Layouting::noMargin); + auto widget = form.emerge(); if (m_addMacroExpander) VariableChooser::addSupportForChildWidgets(widget, macroExpander()); @@ -361,7 +362,7 @@ bool BuildStepFactory::canHandle(BuildStepList *bsl) const return false; } - if (!m_isRepeatable && bsl->contains(m_info.id)) + if (!m_isRepeatable && bsl->contains(m_stepId)) return false; if (m_supportedConfiguration.isValid()) { @@ -375,14 +376,42 @@ bool BuildStepFactory::canHandle(BuildStepList *bsl) const return true; } -void BuildStepFactory::setDisplayName(const QString &displayName) +QString BuildStepFactory::displayName() const { - m_info.displayName = displayName; + return m_displayName; } -void BuildStepFactory::setFlags(BuildStepInfo::Flags flags) +void BuildStepFactory::cloneStepCreator(Id exitstingStepId, Id overrideNewStepId) { - m_info.flags = flags; + m_stepId = {}; + m_creator = {}; + for (BuildStepFactory *factory : BuildStepFactory::allBuildStepFactories()) { + if (factory->m_stepId == exitstingStepId) { + m_creator = factory->m_creator; + m_stepId = factory->m_stepId; + m_displayName = factory->m_displayName; + // Other bits are intentionally not copied as they are unlikely to be + // useful in the cloner's context. The cloner can/has to finish the + // setup on its own. + break; + } + } + // Existence should be guaranteed by plugin dependencies. In case it fails, + // bark and keep the factory in a state where the invalid m_stepId keeps it + // inaction. + QTC_ASSERT(m_creator, return); + if (overrideNewStepId.isValid()) + m_stepId = overrideNewStepId; +} + +void BuildStepFactory::setDisplayName(const QString &displayName) +{ + m_displayName = displayName; +} + +void BuildStepFactory::setFlags(BuildStep::Flags flags) +{ + m_flags = flags; } void BuildStepFactory::setSupportedStepList(Id id) @@ -415,20 +444,21 @@ void BuildStepFactory::setSupportedDeviceTypes(const QList &ids) m_supportedDeviceTypes = ids; } -BuildStepInfo BuildStepFactory::stepInfo() const +BuildStep::Flags BuildStepFactory::stepFlags() const { - return m_info; + return m_flags; } Id BuildStepFactory::stepId() const { - return m_info.id; + return m_stepId; } BuildStep *BuildStepFactory::create(BuildStepList *parent) { - BuildStep *step = m_info.creator(parent); - step->setDefaultDisplayName(m_info.displayName); + QTC_ASSERT(m_creator, return nullptr); + BuildStep *step = m_creator(parent); + step->setDefaultDisplayName(m_displayName); return step; } diff --git a/src/plugins/projectexplorer/buildstep.h b/src/plugins/projectexplorer/buildstep.h index 547a5e2027b..46be75f5633 100644 --- a/src/plugins/projectexplorer/buildstep.h +++ b/src/plugins/projectexplorer/buildstep.h @@ -76,6 +76,12 @@ public: enum OutputNewlineSetting { DoAppendNewline, DontAppendNewline }; + enum Flags { + Uncreatable = 1 << 0, + Unclonable = 1 << 1, + UniqueStep = 1 << 8 // Can't be used twice in a BuildStepList + }; + bool widgetExpandedByDefault() const; void setWidgetExpandedByDefault(bool widgetExpandedByDefault); @@ -136,23 +142,6 @@ private: QString m_summaryText; }; -class PROJECTEXPLORER_EXPORT BuildStepInfo -{ -public: - enum Flags { - Uncreatable = 1 << 0, - Unclonable = 1 << 1, - UniqueStep = 1 << 8 // Can't be used twice in a BuildStepList - }; - - using BuildStepCreator = std::function; - - Utils::Id id; - QString displayName; - Flags flags = Flags(); - BuildStepCreator creator; -}; - class PROJECTEXPLORER_EXPORT BuildStepFactory { public: @@ -163,23 +152,26 @@ public: static const QList allBuildStepFactories(); - BuildStepInfo stepInfo() const; + BuildStep::Flags stepFlags() const; Utils::Id stepId() const; BuildStep *create(BuildStepList *parent); BuildStep *restore(BuildStepList *parent, const QVariantMap &map); bool canHandle(BuildStepList *bsl) const; + QString displayName() const; + protected: using BuildStepCreator = std::function; template void registerStep(Utils::Id id) { - QTC_CHECK(!m_info.creator); - m_info.id = id; - m_info.creator = [id](BuildStepList *bsl) { return new BuildStepType(bsl, id); }; + QTC_CHECK(!m_creator); + m_stepId = id; + m_creator = [id](BuildStepList *bsl) { return new BuildStepType(bsl, id); }; } + void cloneStepCreator(Utils::Id exitstingStepId, Utils::Id overrideNewStepId = {}); void setSupportedStepList(Utils::Id id); void setSupportedStepLists(const QList &ids); @@ -189,10 +181,13 @@ protected: void setSupportedDeviceTypes(const QList &ids); void setRepeatable(bool on) { m_isRepeatable = on; } void setDisplayName(const QString &displayName); - void setFlags(BuildStepInfo::Flags flags); + void setFlags(BuildStep::Flags flags); private: - BuildStepInfo m_info; + Utils::Id m_stepId; + QString m_displayName; + BuildStep::Flags m_flags = {}; + BuildStepCreator m_creator; Utils::Id m_supportedProjectType; QList m_supportedDeviceTypes; diff --git a/src/plugins/projectexplorer/buildsteplist.cpp b/src/plugins/projectexplorer/buildsteplist.cpp index 8d6159541ef..0094bf0b683 100644 --- a/src/plugins/projectexplorer/buildsteplist.cpp +++ b/src/plugins/projectexplorer/buildsteplist.cpp @@ -43,16 +43,6 @@ QVariantMap BuildStepList::toMap() const { QVariantMap map; - { - // Only written for compatibility reasons within the 4.11 cycle - const char CONFIGURATION_ID_KEY[] = "ProjectExplorer.ProjectConfiguration.Id"; - const char DISPLAY_NAME_KEY[] = "ProjectExplorer.ProjectConfiguration.DisplayName"; - const char DEFAULT_DISPLAY_NAME_KEY[] = "ProjectExplorer.ProjectConfiguration.DefaultDisplayName"; - map.insert(QLatin1String(CONFIGURATION_ID_KEY), m_id.toSetting()); - map.insert(QLatin1String(DISPLAY_NAME_KEY), displayName()); - map.insert(QLatin1String(DEFAULT_DISPLAY_NAME_KEY), displayName()); - } - // Save build steps map.insert(QString::fromLatin1(STEPS_COUNT_KEY), m_steps.count()); for (int i = 0; i < m_steps.count(); ++i) diff --git a/src/plugins/projectexplorer/buildstepspage.cpp b/src/plugins/projectexplorer/buildstepspage.cpp index 241d2a98352..21fb4c20932 100644 --- a/src/plugins/projectexplorer/buildstepspage.cpp +++ b/src/plugins/projectexplorer/buildstepspage.cpp @@ -209,14 +209,14 @@ void BuildStepListWidget::updateAddBuildStepMenu() if (!factory->canHandle(m_buildStepList)) continue; - const BuildStepInfo &info = factory->stepInfo(); - if (info.flags & BuildStepInfo::Uncreatable) + const BuildStep::Flags flags = factory->stepFlags(); + if (flags & BuildStep::Uncreatable) continue; - if ((info.flags & BuildStepInfo::UniqueStep) && m_buildStepList->contains(info.id)) + if ((flags & BuildStep::UniqueStep) && m_buildStepList->contains(factory->stepId())) continue; - QAction *action = menu->addAction(info.displayName); + QAction *action = menu->addAction(factory->displayName()); connect(action, &QAction::triggered, this, [factory, this] { BuildStep *newStep = factory->create(m_buildStepList); QTC_ASSERT(newStep, return); diff --git a/src/plugins/projectexplorer/buildsystem.cpp b/src/plugins/projectexplorer/buildsystem.cpp index 0a2c2f3a85d..9a120a9863f 100644 --- a/src/plugins/projectexplorer/buildsystem.cpp +++ b/src/plugins/projectexplorer/buildsystem.cpp @@ -7,9 +7,9 @@ #include "extracompiler.h" #include "projectexplorer.h" #include "projectexplorertr.h" +#include "projectmanager.h" #include "runconfiguration.h" #include "runcontrol.h" -#include "session.h" #include "target.h" #include @@ -64,7 +64,7 @@ BuildSystem::BuildSystem(Target *target) connect(&d->m_delayedParsingTimer, &QTimer::timeout, this, [this] { - if (SessionManager::hasProject(project())) + if (ProjectManager::hasProject(project())) triggerParsing(); else requestDelayedParse(); @@ -325,7 +325,6 @@ void BuildSystem::setDeploymentData(const DeploymentData &deploymentData) if (d->m_deploymentData != deploymentData) { d->m_deploymentData = deploymentData; emit deploymentDataChanged(); - emit applicationTargetsChanged(); emit target()->deploymentDataChanged(); } } @@ -337,10 +336,7 @@ DeploymentData BuildSystem::deploymentData() const void BuildSystem::setApplicationTargets(const QList &appTargets) { - if (Utils::toSet(appTargets) != Utils::toSet(d->m_appTargets)) { - d->m_appTargets = appTargets; - emit applicationTargetsChanged(); - } + d->m_appTargets = appTargets; } const QList BuildSystem::applicationTargets() const diff --git a/src/plugins/projectexplorer/buildsystem.h b/src/plugins/projectexplorer/buildsystem.h index 0d19c9110ec..1cd1e41a060 100644 --- a/src/plugins/projectexplorer/buildsystem.h +++ b/src/plugins/projectexplorer/buildsystem.h @@ -152,7 +152,6 @@ signals: void parsingStarted(); void parsingFinished(bool success); void deploymentDataChanged(); - void applicationTargetsChanged(); void testInformationUpdated(); protected: diff --git a/src/plugins/projectexplorer/codestylesettingspropertiespage.cpp b/src/plugins/projectexplorer/codestylesettingspropertiespage.cpp index 2ff0285a61b..f7ed131e575 100644 --- a/src/plugins/projectexplorer/codestylesettingspropertiespage.cpp +++ b/src/plugins/projectexplorer/codestylesettingspropertiespage.cpp @@ -48,12 +48,13 @@ CodeStyleSettingsWidget::CodeStyleSettingsWidget(Project *project) connect(languageComboBox, &QComboBox::currentIndexChanged, stackedWidget, &QStackedWidget::setCurrentIndex); - using namespace Utils::Layouting; + using namespace Layouting; Column { Row { new QLabel(Tr::tr("Language:")), languageComboBox, st }, - stackedWidget - }.attachTo(this, WithoutMargins); + stackedWidget, + noMargin + }.attachTo(this); } } // ProjectExplorer::Internal diff --git a/src/plugins/projectexplorer/compileoutputwindow.cpp b/src/plugins/projectexplorer/compileoutputwindow.cpp index f345792ca83..bafc31fc14a 100644 --- a/src/plugins/projectexplorer/compileoutputwindow.cpp +++ b/src/plugins/projectexplorer/compileoutputwindow.cpp @@ -17,7 +17,9 @@ #include #include #include + #include +#include #include #include #include @@ -43,9 +45,6 @@ namespace Internal { const char SETTINGS_KEY[] = "ProjectExplorer/CompileOutput/Zoom"; const char C_COMPILE_OUTPUT[] = "ProjectExplorer.CompileOutput"; -const char POP_UP_KEY[] = "ProjectExplorer/Settings/ShowCompilerOutput"; -const char WRAP_OUTPUT_KEY[] = "ProjectExplorer/Settings/WrapBuildOutput"; -const char MAX_LINES_KEY[] = "ProjectExplorer/Settings/MaxBuildOutputLines"; const char OPTIONS_PAGE_ID[] = "C.ProjectExplorer.CompileOutputOptions"; CompileOutputWindow::CompileOutputWindow(QAction *cancelBuildAction) : @@ -101,8 +100,17 @@ CompileOutputWindow::CompileOutputWindow(QAction *cancelBuildAction) : Tr::tr("O")); ExtensionSystem::PluginManager::addObject(m_handler); setupContext(C_COMPILE_OUTPUT, m_outputWindow); - loadSettings(); updateFromSettings(); + + m_outputWindow->setWordWrapEnabled(m_settings.wrapOutput()); + m_outputWindow->setMaxCharCount(m_settings.maxCharCount()); + + connect(&m_settings.wrapOutput, &Utils::BaseAspect::changed, m_outputWindow, [this] { + m_outputWindow->setWordWrapEnabled(m_settings.wrapOutput()); + }); + connect(&m_settings.maxCharCount, &Utils::BaseAspect::changed, m_outputWindow, [this] { + m_outputWindow->setMaxCharCount(m_settings.maxCharCount()); + }); } CompileOutputWindow::~CompileOutputWindow() @@ -115,10 +123,7 @@ CompileOutputWindow::~CompileOutputWindow() void CompileOutputWindow::updateFromSettings() { - m_outputWindow->setWordWrapEnabled(m_settings.wrapOutput); - m_outputWindow->setMaxCharCount(m_settings.maxCharCount); } - bool CompileOutputWindow::hasFocus() const { return m_outputWindow->window()->focusWidget() == m_outputWindow; @@ -213,13 +218,6 @@ void CompileOutputWindow::reset() m_outputWindow->reset(); } -void CompileOutputWindow::setSettings(const CompileOutputSettings &settings) -{ - m_settings = settings; - storeSettings(); - updateFromSettings(); -} - Utils::OutputFormatter *CompileOutputWindow::outputFormatter() const { return m_outputWindow->outputFormatter(); @@ -231,75 +229,49 @@ void CompileOutputWindow::updateFilter() filterUsesRegexp(), filterIsInverted()); } -const bool kPopUpDefault = false; -const bool kWrapOutputDefault = true; +// CompileOutputSettings -void CompileOutputWindow::loadSettings() +static CompileOutputSettings *s_compileOutputSettings; + +CompileOutputSettings &CompileOutputSettings::instance() { - QSettings * const s = Core::ICore::settings(); - m_settings.popUp = s->value(POP_UP_KEY, kPopUpDefault).toBool(); - m_settings.wrapOutput = s->value(WRAP_OUTPUT_KEY, kWrapOutputDefault).toBool(); - m_settings.maxCharCount = s->value(MAX_LINES_KEY, - Core::Constants::DEFAULT_MAX_CHAR_COUNT).toInt() * 100; + return *s_compileOutputSettings; } -void CompileOutputWindow::storeSettings() const +CompileOutputSettings::CompileOutputSettings() { - Utils::QtcSettings *const s = Core::ICore::settings(); - s->setValueWithDefault(POP_UP_KEY, m_settings.popUp, kPopUpDefault); - s->setValueWithDefault(WRAP_OUTPUT_KEY, m_settings.wrapOutput, kWrapOutputDefault); - s->setValueWithDefault(MAX_LINES_KEY, - m_settings.maxCharCount / 100, - Core::Constants::DEFAULT_MAX_CHAR_COUNT); -} + s_compileOutputSettings = this; -class CompileOutputSettingsWidget : public Core::IOptionsPageWidget -{ -public: - CompileOutputSettingsWidget() - { - const CompileOutputSettings &settings = BuildManager::compileOutputSettings(); - m_wrapOutputCheckBox.setText(Tr::tr("Word-wrap output")); - m_wrapOutputCheckBox.setChecked(settings.wrapOutput); - m_popUpCheckBox.setText(Tr::tr("Open Compile Output when building")); - m_popUpCheckBox.setChecked(settings.popUp); - m_maxCharsBox.setMaximum(100000000); - m_maxCharsBox.setValue(settings.maxCharCount); - const auto layout = new QVBoxLayout(this); - layout->addWidget(&m_wrapOutputCheckBox); - layout->addWidget(&m_popUpCheckBox); - const auto maxCharsLayout = new QHBoxLayout; - const QString msg = Tr::tr("Limit output to %1 characters"); - const QStringList parts = msg.split("%1") << QString() << QString(); - maxCharsLayout->addWidget(new QLabel(parts.at(0).trimmed())); - maxCharsLayout->addWidget(&m_maxCharsBox); - maxCharsLayout->addWidget(new QLabel(parts.at(1).trimmed())); - maxCharsLayout->addStretch(1); - layout->addLayout(maxCharsLayout); - layout->addStretch(1); - } - - void apply() final - { - CompileOutputSettings s; - s.wrapOutput = m_wrapOutputCheckBox.isChecked(); - s.popUp = m_popUpCheckBox.isChecked(); - s.maxCharCount = m_maxCharsBox.value(); - BuildManager::setCompileOutputSettings(s); - } - -private: - QCheckBox m_wrapOutputCheckBox; - QCheckBox m_popUpCheckBox; - QSpinBox m_maxCharsBox; -}; - -CompileOutputSettingsPage::CompileOutputSettingsPage() -{ setId(OPTIONS_PAGE_ID); setDisplayName(Tr::tr("Compile Output")); setCategory(Constants::BUILD_AND_RUN_SETTINGS_CATEGORY); - setWidgetCreator([] { return new CompileOutputSettingsWidget; }); + + wrapOutput.setSettingsKey("ProjectExplorer/Settings/WrapBuildOutput"); + wrapOutput.setDefaultValue(true); + wrapOutput.setLabelText(Tr::tr("Word-wrap output")); + + popUp.setSettingsKey("ProjectExplorer/Settings/ShowCompilerOutput"); + popUp.setLabelText(Tr::tr("Open Compile Output when building")); + + maxCharCount.setSettingsKey("ProjectExplorer/Settings/MaxBuildOutputLines"); + maxCharCount.setRange(1, Core::Constants::DEFAULT_MAX_CHAR_COUNT); + maxCharCount.setDefaultValue(Core::Constants::DEFAULT_MAX_CHAR_COUNT); + maxCharCount.setToSettingsTransformation([](const QVariant &v) { return v.toInt() / 100; }); + maxCharCount.setFromSettingsTransformation([](const QVariant &v) { return v.toInt() * 100; }); + + setLayouter([this] { + using namespace Layouting; + const QString msg = Tr::tr("Limit output to %1 characters"); + const QStringList parts = msg.split("%1") << QString() << QString(); + return Column { + wrapOutput, + popUp, + Row { parts.at(0), maxCharCount, parts.at(1), st }, + st + }; + }); + + readSettings(); } } // Internal diff --git a/src/plugins/projectexplorer/compileoutputwindow.h b/src/plugins/projectexplorer/compileoutputwindow.h index e1f000de9ae..89c7b749f2e 100644 --- a/src/plugins/projectexplorer/compileoutputwindow.h +++ b/src/plugins/projectexplorer/compileoutputwindow.h @@ -26,6 +26,18 @@ namespace Internal { class ShowOutputTaskHandler; class CompileOutputTextEdit; +class CompileOutputSettings final : public Core::PagedSettings +{ +public: + CompileOutputSettings(); + + static CompileOutputSettings &instance(); + + Utils::BoolAspect popUp{this}; + Utils::BoolAspect wrapOutput{this}; + Utils::IntegerAspect maxCharCount{this}; +}; + class CompileOutputWindow final : public Core::IOutputPane { Q_OBJECT @@ -57,19 +69,13 @@ public: void flush(); void reset(); - const CompileOutputSettings &settings() const { return m_settings; } - void setSettings(const CompileOutputSettings &settings); - Utils::OutputFormatter *outputFormatter() const; private: void updateFilter() override; const QList outputWindows() const override { return {m_outputWindow}; } - void loadSettings(); - void storeSettings() const; void updateFromSettings(); - Core::OutputWindow *m_outputWindow; ShowOutputTaskHandler *m_handler; QToolButton *m_cancelBuildButton; @@ -77,11 +83,5 @@ private: CompileOutputSettings m_settings; }; -class CompileOutputSettingsPage final : public Core::IOptionsPage -{ -public: - CompileOutputSettingsPage(); -}; - } // namespace Internal } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/copystep.cpp b/src/plugins/projectexplorer/copystep.cpp new file mode 100644 index 00000000000..db3b16098e5 --- /dev/null +++ b/src/plugins/projectexplorer/copystep.cpp @@ -0,0 +1,114 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "copystep.h" + +#include "projectexplorerconstants.h" +#include "projectexplorertr.h" + +#include + +using namespace Utils; + +namespace ProjectExplorer::Internal { + +const char SOURCE_KEY[] = "ProjectExplorer.CopyStep.Source"; +const char TARGET_KEY[] = "ProjectExplorer.CopyStep.Target"; + +class CopyStepBase : public BuildStep +{ +public: + CopyStepBase(BuildStepList *bsl, Id id) + : BuildStep(bsl, id) + { + m_sourceAspect.setSettingsKey(SOURCE_KEY); + m_sourceAspect.setLabelText(Tr::tr("Source:")); + + m_targetAspect.setSettingsKey(TARGET_KEY); + m_targetAspect.setLabelText(Tr::tr("Target:")); + + addMacroExpander(); + } + +protected: + bool init() final + { + m_source = m_sourceAspect(); + m_target = m_targetAspect(); + return m_source.exists(); + } + + void doRun() final + { + // FIXME: asyncCopy does not handle directories yet. + QTC_ASSERT(m_source.isFile(), emit finished(false)); + m_source.asyncCopy(m_target, this, [this](const expected_str &cont) { + if (!cont) { + addOutput(cont.error(), OutputFormat::ErrorMessage); + addOutput(Tr::tr("Copying failed"), OutputFormat::ErrorMessage); + emit finished(false); + } else { + addOutput(Tr::tr("Copying finished"), OutputFormat::NormalMessage); + emit finished(true); + } + }); + } + + FilePathAspect m_sourceAspect{this}; + FilePathAspect m_targetAspect{this}; + +private: + FilePath m_source; + FilePath m_target; +}; + +class CopyFileStep final : public CopyStepBase +{ +public: + CopyFileStep(BuildStepList *bsl, Id id) + : CopyStepBase(bsl, id) + { + // Expected kind could be stricter in theory, but since this here is + // a last stand fallback, better not impose extra "nice to have" + // work on the system. + m_sourceAspect.setExpectedKind(PathChooser::Any); // "File" + m_targetAspect.setExpectedKind(PathChooser::Any); // "SaveFile" + + setSummaryUpdater([] { + return QString("" + Tr::tr("Copy file") + ""); + }); + } +}; + +class CopyDirectoryStep final : public CopyStepBase +{ +public: + CopyDirectoryStep(BuildStepList *bsl, Id id) + : CopyStepBase(bsl, id) + { + m_sourceAspect.setExpectedKind(PathChooser::Directory); + m_targetAspect.setExpectedKind(PathChooser::Directory); + + setSummaryUpdater([] { + return QString("" + Tr::tr("Copy directory recursively") + ""); + }); + } +}; + +// Factories + +CopyFileStepFactory::CopyFileStepFactory() +{ + registerStep(Constants::COPY_FILE_STEP); + //: Default CopyStep display name + setDisplayName(Tr::tr("Copy file")); +} + +CopyDirectoryStepFactory::CopyDirectoryStepFactory() +{ + registerStep(Constants::COPY_DIRECTORY_STEP); + //: Default CopyStep display name + setDisplayName(Tr::tr("Copy directory recursively")); +} + +} // ProjectExplorer::Internal diff --git a/src/plugins/projectexplorer/copystep.h b/src/plugins/projectexplorer/copystep.h new file mode 100644 index 00000000000..07940f3a89a --- /dev/null +++ b/src/plugins/projectexplorer/copystep.h @@ -0,0 +1,22 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "buildstep.h" + +namespace ProjectExplorer::Internal { + +class CopyFileStepFactory final : public BuildStepFactory +{ +public: + CopyFileStepFactory(); +}; + +class CopyDirectoryStepFactory final : public BuildStepFactory +{ +public: + CopyDirectoryStepFactory(); +}; + +} // ProjectExplorer::Internal diff --git a/src/plugins/projectexplorer/currentprojectfilter.cpp b/src/plugins/projectexplorer/currentprojectfilter.cpp index 69b88ddf46c..d0f9ed99b0a 100644 --- a/src/plugins/projectexplorer/currentprojectfilter.cpp +++ b/src/plugins/projectexplorer/currentprojectfilter.cpp @@ -7,42 +7,28 @@ #include "projectexplorertr.h" #include "projecttree.h" -#include - using namespace Core; using namespace ProjectExplorer; using namespace ProjectExplorer::Internal; +using namespace Utils; CurrentProjectFilter::CurrentProjectFilter() - : BaseFileFilter() { setId("Files in current project"); setDisplayName(Tr::tr("Files in Current Project")); - setDescription(Tr::tr("Matches all files from the current document's project. Append \"+\" " - "or \":\" to jump to the given line number. Append another " - "\"+\" or \":\" to jump to the column number as well.")); + setDescription(Tr::tr("Locates files from the current document's project. Append \"+\" " + "or \":\" to jump to the given line number. Append another " + "\"+\" or \":\" to jump to the column number as well.")); setDefaultShortcutString("p"); - setDefaultIncludedByDefault(false); + setRefreshRecipe(Tasking::Sync([this] { invalidate(); })); connect(ProjectTree::instance(), &ProjectTree::currentProjectChanged, this, &CurrentProjectFilter::currentProjectChanged); -} -void CurrentProjectFilter::markFilesAsOutOfDate() -{ - setFileIterator(nullptr); -} - -void CurrentProjectFilter::prepareSearch(const QString &entry) -{ - Q_UNUSED(entry) - if (!fileIterator()) { - Utils::FilePaths paths; - if (m_project) - paths = m_project->files(Project::SourceFiles); - setFileIterator(new BaseFileFilter::ListIterator(paths)); - } - BaseFileFilter::prepareSearch(entry); + m_cache.setGeneratorProvider([this] { + const FilePaths paths = m_project ? m_project->files(Project::SourceFiles) : FilePaths(); + return LocatorFileCache::filePathsGenerator(paths); + }); } void CurrentProjectFilter::currentProjectChanged() @@ -50,21 +36,12 @@ void CurrentProjectFilter::currentProjectChanged() Project *project = ProjectTree::currentProject(); if (project == m_project) return; + if (m_project) - disconnect(m_project, &Project::fileListChanged, - this, &CurrentProjectFilter::markFilesAsOutOfDate); - - if (project) - connect(project, &Project::fileListChanged, - this, &CurrentProjectFilter::markFilesAsOutOfDate); - + disconnect(m_project, &Project::fileListChanged, this, &CurrentProjectFilter::invalidate); m_project = project; - markFilesAsOutOfDate(); -} + if (m_project) + connect(m_project, &Project::fileListChanged, this, &CurrentProjectFilter::invalidate); -void CurrentProjectFilter::refresh(QFutureInterface &future) -{ - Q_UNUSED(future) - QMetaObject::invokeMethod(this, &CurrentProjectFilter::markFilesAsOutOfDate, - Qt::QueuedConnection); + invalidate(); } diff --git a/src/plugins/projectexplorer/currentprojectfilter.h b/src/plugins/projectexplorer/currentprojectfilter.h index b9d63db2930..6c500707907 100644 --- a/src/plugins/projectexplorer/currentprojectfilter.h +++ b/src/plugins/projectexplorer/currentprojectfilter.h @@ -3,31 +3,24 @@ #pragma once -#include +#include -#include +namespace ProjectExplorer { class Project; } -namespace ProjectExplorer { +namespace ProjectExplorer::Internal { -class Project; - -namespace Internal { - -class CurrentProjectFilter : public Core::BaseFileFilter +class CurrentProjectFilter : public Core::ILocatorFilter { - Q_OBJECT - public: CurrentProjectFilter(); - void refresh(QFutureInterface &future) override; - void prepareSearch(const QString &entry) override; private: + Core::LocatorMatcherTasks matchers() final { return {m_cache.matcher()}; } void currentProjectChanged(); - void markFilesAsOutOfDate(); + void invalidate() { m_cache.invalidate(); } + Core::LocatorFileCache m_cache; Project *m_project = nullptr; }; -} // namespace Internal -} // namespace ProjectExplorer +} // namespace ProjectExplorer::Internal diff --git a/src/plugins/projectexplorer/currentprojectfind.cpp b/src/plugins/projectexplorer/currentprojectfind.cpp index 7ecbd990697..3bde2bb62f2 100644 --- a/src/plugins/projectexplorer/currentprojectfind.cpp +++ b/src/plugins/projectexplorer/currentprojectfind.cpp @@ -5,8 +5,8 @@ #include "project.h" #include "projectexplorertr.h" +#include "projectmanager.h" #include "projecttree.h" -#include "session.h" #include #include @@ -23,7 +23,7 @@ CurrentProjectFind::CurrentProjectFind() { connect(ProjectTree::instance(), &ProjectTree::currentProjectChanged, this, &CurrentProjectFind::handleProjectChanged); - connect(SessionManager::instance(), &SessionManager::projectDisplayNameChanged, + connect(ProjectManager::instance(), &ProjectManager::projectDisplayNameChanged, this, [this](ProjectExplorer::Project *p) { if (p == ProjectTree::currentProject()) emit displayNameChanged(); @@ -61,14 +61,13 @@ FileIterator *CurrentProjectFind::files(const QStringList &nameFilters, const QStringList &exclusionFilters, const QVariant &additionalParameters) const { - QTC_ASSERT(additionalParameters.isValid(), - return new FileListIterator(FilePaths(), QList())); + QTC_ASSERT(additionalParameters.isValid(), return new FileListIterator); const FilePath projectFile = FilePath::fromVariant(additionalParameters); - for (Project *project : SessionManager::projects()) { + for (Project *project : ProjectManager::projects()) { if (project && projectFile == project->projectFilePath()) return filesForProjects(nameFilters, exclusionFilters, {project}); } - return new FileListIterator(FilePaths(), QList()); + return new FileListIterator; } QString CurrentProjectFind::label() const @@ -87,7 +86,7 @@ void CurrentProjectFind::handleProjectChanged() void CurrentProjectFind::recheckEnabled(Core::SearchResult *search) { const FilePath projectFile = FilePath::fromVariant(getAdditionalParameters(search)); - for (Project *project : SessionManager::projects()) { + for (Project *project : ProjectManager::projects()) { if (projectFile == project->projectFilePath()) { search->setSearchAgainEnabled(true); return; diff --git a/src/plugins/projectexplorer/customexecutablerunconfiguration.cpp b/src/plugins/projectexplorer/customexecutablerunconfiguration.cpp index 9d25bb7dfe6..00f7225d0df 100644 --- a/src/plugins/projectexplorer/customexecutablerunconfiguration.cpp +++ b/src/plugins/projectexplorer/customexecutablerunconfiguration.cpp @@ -3,7 +3,6 @@ #include "customexecutablerunconfiguration.h" -#include "localenvironmentaspect.h" #include "projectexplorerconstants.h" #include "projectexplorertr.h" #include "runconfigurationaspects.h" @@ -24,7 +23,8 @@ CustomExecutableRunConfiguration::CustomExecutableRunConfiguration(Target *targe CustomExecutableRunConfiguration::CustomExecutableRunConfiguration(Target *target, Id id) : RunConfiguration(target, id) { - auto envAspect = addAspect(target); + auto envAspect = addAspect(); + envAspect->setSupportForBuildEnvironment(target); auto exeAspect = addAspect(target, ExecutableAspect::HostDevice); exeAspect->setSettingsKey("ProjectExplorer.CustomExecutableRunConfiguration.Executable"); diff --git a/src/plugins/projectexplorer/customparserconfigdialog.cpp b/src/plugins/projectexplorer/customparserconfigdialog.cpp index 4ebc880c140..cbfd1adfbc7 100644 --- a/src/plugins/projectexplorer/customparserconfigdialog.cpp +++ b/src/plugins/projectexplorer/customparserconfigdialog.cpp @@ -101,7 +101,7 @@ CustomParserConfigDialog::CustomParserConfigDialog(QWidget *parent) auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); - using namespace Utils::Layouting; + using namespace Layouting; auto tabWarning = new QWidget; Column { diff --git a/src/plugins/projectexplorer/customwizard/customwizardscriptgenerator.cpp b/src/plugins/projectexplorer/customwizard/customwizardscriptgenerator.cpp index f2b33378b53..7d1174839dd 100644 --- a/src/plugins/projectexplorer/customwizard/customwizardscriptgenerator.cpp +++ b/src/plugins/projectexplorer/customwizard/customwizardscriptgenerator.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include @@ -64,7 +64,7 @@ static bool const QMap &fieldMap, QString *stdOut /* = 0 */, QString *errorMessage) { - Utils::QtcProcess process; + Utils::Process process; const QString binary = script.front(); QStringList arguments; const int binarySize = script.size(); diff --git a/src/plugins/projectexplorer/dependenciespanel.cpp b/src/plugins/projectexplorer/dependenciespanel.cpp index 1799126a5b9..daac52b8d75 100644 --- a/src/plugins/projectexplorer/dependenciespanel.cpp +++ b/src/plugins/projectexplorer/dependenciespanel.cpp @@ -5,9 +5,10 @@ #include "project.h" #include "projectexplorertr.h" -#include "session.h" +#include "projectmanager.h" #include +#include #include #include @@ -32,19 +33,18 @@ DependenciesModel::DependenciesModel(Project *project, QObject *parent) { resetModel(); - SessionManager *sessionManager = SessionManager::instance(); - connect(sessionManager, &SessionManager::projectRemoved, + connect(ProjectManager::instance(), &ProjectManager::projectRemoved, this, &DependenciesModel::resetModel); - connect(sessionManager, &SessionManager::projectAdded, + connect(ProjectManager::instance(), &ProjectManager::projectAdded, this, &DependenciesModel::resetModel); - connect(sessionManager, &SessionManager::sessionLoaded, + connect(Core::SessionManager::instance(), &Core::SessionManager::sessionLoaded, this, &DependenciesModel::resetModel); } void DependenciesModel::resetModel() { beginResetModel(); - m_projects = SessionManager::projects(); + m_projects = ProjectManager::projects(); m_projects.removeAll(m_project); Utils::sort(m_projects, [](Project *a, Project *b) { return a->displayName() < b->displayName(); @@ -77,7 +77,7 @@ QVariant DependenciesModel::data(const QModelIndex &index, int role) const case Qt::ToolTipRole: return p->projectFilePath().toUserOutput(); case Qt::CheckStateRole: - return SessionManager::hasDependency(m_project, p) ? Qt::Checked : Qt::Unchecked; + return ProjectManager::hasDependency(m_project, p) ? Qt::Checked : Qt::Unchecked; case Qt::DecorationRole: return Utils::FileIconProvider::icon(p->projectFilePath()); default: @@ -92,7 +92,7 @@ bool DependenciesModel::setData(const QModelIndex &index, const QVariant &value, const auto c = static_cast(value.toInt()); if (c == Qt::Checked) { - if (SessionManager::addDependency(m_project, p)) { + if (ProjectManager::addDependency(m_project, p)) { emit dataChanged(index, index); return true; } else { @@ -100,8 +100,8 @@ bool DependenciesModel::setData(const QModelIndex &index, const QVariant &value, Tr::tr("This would create a circular dependency.")); } } else if (c == Qt::Unchecked) { - if (SessionManager::hasDependency(m_project, p)) { - SessionManager::removeDependency(m_project, p); + if (ProjectManager::hasDependency(m_project, p)) { + ProjectManager::removeDependency(m_project, p); emit dataChanged(index, index); return true; } @@ -215,9 +215,9 @@ DependenciesWidget::DependenciesWidget(Project *project, QWidget *parent) : Proj m_cascadeSetActiveCheckBox = new QCheckBox; m_cascadeSetActiveCheckBox->setText(Tr::tr("Synchronize configuration")); m_cascadeSetActiveCheckBox->setToolTip(Tr::tr("Synchronize active kit, build, and deploy configuration between projects.")); - m_cascadeSetActiveCheckBox->setChecked(SessionManager::isProjectConfigurationCascading()); + m_cascadeSetActiveCheckBox->setChecked(ProjectManager::isProjectConfigurationCascading()); connect(m_cascadeSetActiveCheckBox, &QCheckBox::toggled, - SessionManager::instance(), &SessionManager::setProjectConfigurationCascading); + ProjectManager::instance(), &ProjectManager::setProjectConfigurationCascading); layout->addWidget(m_cascadeSetActiveCheckBox, 1, 0, 2, 1); } diff --git a/src/plugins/projectexplorer/desktoprunconfiguration.cpp b/src/plugins/projectexplorer/desktoprunconfiguration.cpp index 82d0123b117..8b8cdf2b793 100644 --- a/src/plugins/projectexplorer/desktoprunconfiguration.cpp +++ b/src/plugins/projectexplorer/desktoprunconfiguration.cpp @@ -4,7 +4,6 @@ #include "desktoprunconfiguration.h" #include "buildsystem.h" -#include "localenvironmentaspect.h" #include "projectexplorerconstants.h" #include "projectexplorertr.h" #include "runconfigurationaspects.h" @@ -43,7 +42,8 @@ private: DesktopRunConfiguration::DesktopRunConfiguration(Target *target, Id id, Kind kind) : RunConfiguration(target, id), m_kind(kind) { - auto envAspect = addAspect(target); + auto envAspect = addAspect(); + envAspect->setSupportForBuildEnvironment(target); addAspect(target, ExecutableAspect::RunDevice); addAspect(macroExpander()); @@ -87,7 +87,8 @@ void DesktopRunConfiguration::updateTargetInformation() BuildTargetInfo bti = buildTargetInfo(); auto terminalAspect = aspect(); - terminalAspect->setUseTerminalHint(bti.usesTerminal); + terminalAspect->setUseTerminalHint(bti.targetFilePath.needsDevice() ? false : bti.usesTerminal); + terminalAspect->setEnabled(!bti.targetFilePath.needsDevice()); if (m_kind == Qmake) { @@ -121,7 +122,7 @@ void DesktopRunConfiguration::updateTargetInformation() aspect()->setExecutable(bti.targetFilePath); aspect()->setDefaultWorkingDirectory(bti.workingDirectory); - emit aspect()->environmentChanged(); + emit aspect()->environmentChanged(); } } diff --git a/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp b/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp index a09c8bac76d..099040d6b24 100644 --- a/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp +++ b/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp @@ -3,11 +3,11 @@ #include "desktopdevice.h" -#include "desktopprocesssignaloperation.h" -#include "deviceprocesslist.h" -#include "localprocesslist.h" #include "../projectexplorerconstants.h" #include "../projectexplorertr.h" +#include "desktopprocesssignaloperation.h" +#include "deviceprocesslist.h" +#include "processlist.h" #include @@ -15,18 +15,19 @@ #include #include #include +#include #include -#include #include +#include #include #include #include #ifdef Q_OS_WIN -#include -#include #include +#include +#include #endif using namespace ProjectExplorer::Constants; @@ -34,58 +35,11 @@ using namespace Utils; namespace ProjectExplorer { -static void startTerminalEmulator(const QString &workingDir, const Environment &env) -{ -#ifdef Q_OS_WIN - STARTUPINFO si; - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - - PROCESS_INFORMATION pinfo; - ZeroMemory(&pinfo, sizeof(pinfo)); - - static const auto quoteWinCommand = [](const QString &program) { - const QChar doubleQuote = QLatin1Char('"'); - - // add the program as the first arg ... it works better - QString programName = program; - programName.replace(QLatin1Char('/'), QLatin1Char('\\')); - if (!programName.startsWith(doubleQuote) && !programName.endsWith(doubleQuote) - && programName.contains(QLatin1Char(' '))) { - programName.prepend(doubleQuote); - programName.append(doubleQuote); - } - return programName; - }; - const QString cmdLine = quoteWinCommand(qtcEnvironmentVariable("COMSPEC")); - // cmdLine is assumed to be detached - - // https://blogs.msdn.microsoft.com/oldnewthing/20090601-00/?p=18083 - - const QString totalEnvironment = env.toStringList().join(QChar(QChar::Null)) + QChar(QChar::Null); - LPVOID envPtr = (env != Environment::systemEnvironment()) - ? (WCHAR *)(totalEnvironment.utf16()) : nullptr; - - const bool success = CreateProcessW(0, (WCHAR *)cmdLine.utf16(), - 0, 0, FALSE, CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT, - envPtr, workingDir.isEmpty() ? 0 : (WCHAR *)workingDir.utf16(), - &si, &pinfo); - - if (success) { - CloseHandle(pinfo.hThread); - CloseHandle(pinfo.hProcess); - } -#else - const TerminalCommand term = TerminalCommand::terminalEmulator(); - QProcess process; - process.setProgram(term.command.nativePath()); - process.setArguments(ProcessArgs::splitArgs(term.openArgs)); - process.setProcessEnvironment(env.toProcessEnvironment()); - process.setWorkingDirectory(workingDir); - process.startDetached(); -#endif -} +class DesktopDevicePrivate : public QObject +{}; DesktopDevice::DesktopDevice() + : d(new DesktopDevicePrivate()) { setFileAccess(DesktopDeviceFileAccess::instance()); @@ -98,20 +52,26 @@ DesktopDevice::DesktopDevice() setMachineType(IDevice::Hardware); setOsType(HostOsInfo::hostOs()); - const QString portRange = - QString::fromLatin1("%1-%2").arg(DESKTOP_PORT_START).arg(DESKTOP_PORT_END); + const QString portRange + = QString::fromLatin1("%1-%2").arg(DESKTOP_PORT_START).arg(DESKTOP_PORT_END); setFreePorts(Utils::PortList::fromString(portRange)); setOpenTerminal([](const Environment &env, const FilePath &path) { - const QFileInfo fileInfo = path.toFileInfo(); - const QString workingDir = QDir::toNativeSeparators(fileInfo.isDir() ? - fileInfo.absoluteFilePath() : - fileInfo.absolutePath()); const Environment realEnv = env.hasChanges() ? env : Environment::systemEnvironment(); - startTerminalEmulator(workingDir, realEnv); + + const FilePath shell = Terminal::defaultShellForDevice(path); + + Process process; + process.setTerminalMode(TerminalMode::Detached); + process.setEnvironment(realEnv); + process.setCommand({shell, {}}); + process.setWorkingDirectory(path); + process.start(); }); } +DesktopDevice::~DesktopDevice() = default; + IDevice::DeviceInfo DesktopDevice::deviceInformation() const { return DeviceInfo(); @@ -125,11 +85,6 @@ IDeviceWidget *DesktopDevice::createWidget() // range can be confusing to the user. Hence, disabling the widget for now. } -bool DesktopDevice::canAutoDetectPorts() const -{ - return true; -} - bool DesktopDevice::canCreateProcessModel() const { return true; @@ -137,7 +92,7 @@ bool DesktopDevice::canCreateProcessModel() const DeviceProcessList *DesktopDevice::createProcessListModel(QObject *parent) const { - return new Internal::LocalProcessList(sharedFromThis(), parent); + return new ProcessList(sharedFromThis(), parent); } DeviceProcessSignalOperation::Ptr DesktopDevice::signalOperation() const @@ -145,31 +100,6 @@ DeviceProcessSignalOperation::Ptr DesktopDevice::signalOperation() const return DeviceProcessSignalOperation::Ptr(new DesktopProcessSignalOperation()); } -PortsGatheringMethod DesktopDevice::portsGatheringMethod() const -{ - return { - [this](QAbstractSocket::NetworkLayerProtocol protocol) -> CommandLine { - // We might encounter the situation that protocol is given IPv6 - // but the consumer of the free port information decides to open - // an IPv4(only) port. As a result the next IPv6 scan will - // report the port again as open (in IPv6 namespace), while the - // same port in IPv4 namespace might still be blocked, and - // re-use of this port fails. - // GDBserver behaves exactly like this. - - Q_UNUSED(protocol) - - if (HostOsInfo::isWindowsHost() || HostOsInfo::isMacHost()) - return {filePath("netstat"), {"-a", "-n"}}; - if (HostOsInfo::isLinuxHost()) - return {filePath("/bin/sh"), {"-c", "cat /proc/net/tcp*"}}; - return {}; - }, - - &Port::parseFromNetstatOutput - }; -} - QUrl DesktopDevice::toolControlChannel(const ControlChannelHint &) const { QUrl url; diff --git a/src/plugins/projectexplorer/devicesupport/desktopdevice.h b/src/plugins/projectexplorer/devicesupport/desktopdevice.h index ee5ac1ca5eb..d9cafbfda9a 100644 --- a/src/plugins/projectexplorer/devicesupport/desktopdevice.h +++ b/src/plugins/projectexplorer/devicesupport/desktopdevice.h @@ -6,25 +6,27 @@ #include "../projectexplorer_export.h" #include "idevice.h" -#include "idevicefactory.h" #include +#include + namespace ProjectExplorer { class ProjectExplorerPlugin; +class DesktopDevicePrivate; namespace Internal { class DesktopDeviceFactory; } class PROJECTEXPLORER_EXPORT DesktopDevice : public IDevice { public: + ~DesktopDevice() override; + IDevice::DeviceInfo deviceInformation() const override; IDeviceWidget *createWidget() override; - bool canAutoDetectPorts() const override; bool canCreateProcessModel() const override; DeviceProcessList *createProcessListModel(QObject *parent) const override; - ProjectExplorer::PortsGatheringMethod portsGatheringMethod() const override; DeviceProcessSignalOperation::Ptr signalOperation() const override; QUrl toolControlChannel(const ControlChannelHint &) const override; bool usableAsBuildDevice() const override; @@ -40,6 +42,8 @@ protected: friend class ProjectExplorerPlugin; friend class Internal::DesktopDeviceFactory; + + std::unique_ptr d; }; } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/devicesupport/devicecheckbuildstep.cpp b/src/plugins/projectexplorer/devicesupport/devicecheckbuildstep.cpp index acce8bf29b6..84e3b274084 100644 --- a/src/plugins/projectexplorer/devicesupport/devicecheckbuildstep.cpp +++ b/src/plugins/projectexplorer/devicesupport/devicecheckbuildstep.cpp @@ -4,6 +4,7 @@ #include "devicecheckbuildstep.h" #include "../kitinformation.h" +#include "../projectexplorerconstants.h" #include "../projectexplorertr.h" #include "devicemanager.h" @@ -12,60 +13,61 @@ #include -using namespace ProjectExplorer; +namespace ProjectExplorer { -DeviceCheckBuildStep::DeviceCheckBuildStep(BuildStepList *bsl, Utils::Id id) - : BuildStep(bsl, id) +class DeviceCheckBuildStep : public BuildStep { - setWidgetExpandedByDefault(false); -} - -bool DeviceCheckBuildStep::init() -{ - IDevice::ConstPtr device = DeviceKitAspect::device(kit()); - if (!device) { - Utils::Id deviceTypeId = DeviceTypeKitAspect::deviceTypeId(kit()); - IDeviceFactory *factory = IDeviceFactory::find(deviceTypeId); - if (!factory || !factory->canCreate()) { - emit addOutput(Tr::tr("No device configured."), BuildStep::OutputFormat::ErrorMessage); - return false; - } - - QMessageBox msgBox(QMessageBox::Question, Tr::tr("Set Up Device"), - Tr::tr("There is no device set up for this kit. Do you want to add a device?"), - QMessageBox::Yes|QMessageBox::No); - msgBox.setDefaultButton(QMessageBox::Yes); - if (msgBox.exec() == QMessageBox::No) { - emit addOutput(Tr::tr("No device configured."), BuildStep::OutputFormat::ErrorMessage); - return false; - } - - IDevice::Ptr newDevice = factory->create(); - if (newDevice.isNull()) { - emit addOutput(Tr::tr("No device configured."), BuildStep::OutputFormat::ErrorMessage); - return false; - } - - DeviceManager *dm = DeviceManager::instance(); - dm->addDevice(newDevice); - - DeviceKitAspect::setDevice(kit(), newDevice); +public: + DeviceCheckBuildStep(BuildStepList *bsl, Utils::Id id) + : BuildStep(bsl, id) + { + setWidgetExpandedByDefault(false); } - return true; + bool init() override + { + IDevice::ConstPtr device = DeviceKitAspect::device(kit()); + if (!device) { + Utils::Id deviceTypeId = DeviceTypeKitAspect::deviceTypeId(kit()); + IDeviceFactory *factory = IDeviceFactory::find(deviceTypeId); + if (!factory || !factory->canCreate()) { + emit addOutput(Tr::tr("No device configured."), BuildStep::OutputFormat::ErrorMessage); + return false; + } + + QMessageBox msgBox(QMessageBox::Question, Tr::tr("Set Up Device"), + Tr::tr("There is no device set up for this kit. Do you want to add a device?"), + QMessageBox::Yes|QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::Yes); + if (msgBox.exec() == QMessageBox::No) { + emit addOutput(Tr::tr("No device configured."), BuildStep::OutputFormat::ErrorMessage); + return false; + } + + IDevice::Ptr newDevice = factory->create(); + if (newDevice.isNull()) { + emit addOutput(Tr::tr("No device configured."), BuildStep::OutputFormat::ErrorMessage); + return false; + } + + DeviceManager *dm = DeviceManager::instance(); + dm->addDevice(newDevice); + + DeviceKitAspect::setDevice(kit(), newDevice); + } + + return true; + } + + void doRun() override { emit finished(true); } +}; + +// Factory + +DeviceCheckBuildStepFactory::DeviceCheckBuildStepFactory() +{ + registerStep(Constants::DEVICE_CHECK_STEP); + setDisplayName(Tr::tr("Check for a configured device")); } -void DeviceCheckBuildStep::doRun() -{ - emit finished(true); -} - -Utils::Id DeviceCheckBuildStep::stepId() -{ - return "ProjectExplorer.DeviceCheckBuildStep"; -} - -QString DeviceCheckBuildStep::displayName() -{ - return Tr::tr("Check for a configured device"); -} +} // ProjectExplorer diff --git a/src/plugins/projectexplorer/devicesupport/devicecheckbuildstep.h b/src/plugins/projectexplorer/devicesupport/devicecheckbuildstep.h index 8b7d3687b0f..6b7a8edb11b 100644 --- a/src/plugins/projectexplorer/devicesupport/devicecheckbuildstep.h +++ b/src/plugins/projectexplorer/devicesupport/devicecheckbuildstep.h @@ -8,18 +8,10 @@ namespace ProjectExplorer { -class PROJECTEXPLORER_EXPORT DeviceCheckBuildStep : public BuildStep +class PROJECTEXPLORER_EXPORT DeviceCheckBuildStepFactory : public BuildStepFactory { - Q_OBJECT - public: - DeviceCheckBuildStep(BuildStepList *bsl, Utils::Id id); - - bool init() override; - void doRun() override; - - static Utils::Id stepId(); - static QString displayName(); + DeviceCheckBuildStepFactory(); }; -} // namespace ProjectExplorer +} // ProjectExplorer diff --git a/src/plugins/projectexplorer/devicesupport/devicefactoryselectiondialog.cpp b/src/plugins/projectexplorer/devicesupport/devicefactoryselectiondialog.cpp index ed47999432b..987f2e0c39b 100644 --- a/src/plugins/projectexplorer/devicesupport/devicefactoryselectiondialog.cpp +++ b/src/plugins/projectexplorer/devicesupport/devicefactoryselectiondialog.cpp @@ -24,7 +24,7 @@ DeviceFactorySelectionDialog::DeviceFactorySelectionDialog(QWidget *parent) : m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); m_buttonBox->button(QDialogButtonBox::Ok)->setText(Tr::tr("Start Wizard")); - using namespace Utils::Layouting; + using namespace Layouting; Column { Tr::tr("Available device types:"), m_listWidget, diff --git a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp index 9f8e0594fd8..aa017df46b0 100644 --- a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp +++ b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp @@ -14,9 +14,10 @@ #include #include #include +#include #include -#include #include +#include #include #include @@ -442,6 +443,13 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_uniqueopenTerminal(env, filePath); }; + deviceHooks.osType = [](const FilePath &filePath) { + auto device = DeviceManager::deviceForPath(filePath); + if (!device) + return OsTypeLinux; + return device->osType(); + }; + DeviceProcessHooks processHooks; processHooks.processImplHook = [](const FilePath &filePath) -> ProcessInterface * { @@ -456,7 +464,23 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_uniquesystemEnvironment(); }; - QtcProcess::setRemoteProcessHooks(processHooks); + Process::setRemoteProcessHooks(processHooks); + + Terminal::Hooks::instance().getTerminalCommandsForDevicesHook().set( + [this]() -> QList { + QList result; + for (const IDevice::ConstPtr device : d->devices) { + if (device->type() == Constants::DESKTOP_DEVICE_TYPE) + continue; + + const FilePath shell = Terminal::defaultShellForDevice(device->rootPath()); + + if (!shell.isEmpty()) + result << Terminal::NameAndCommandLine{device->displayName(), + CommandLine{shell, {}}}; + } + return result; + }); } DeviceManager::~DeviceManager() diff --git a/src/plugins/projectexplorer/devicesupport/devicesettingswidget.cpp b/src/plugins/projectexplorer/devicesupport/devicesettingswidget.cpp index 39af4713455..a35387a3c28 100644 --- a/src/plugins/projectexplorer/devicesupport/devicesettingswidget.cpp +++ b/src/plugins/projectexplorer/devicesupport/devicesettingswidget.cpp @@ -17,13 +17,16 @@ #include #include +#include #include +#include #include #include #include #include #include +#include #include #include #include @@ -100,21 +103,47 @@ void DeviceSettingsWidget::initGui() m_deviceStateTextLabel = new QLabel; m_osSpecificGroupBox = new QGroupBox(Tr::tr("Type Specific")); m_osSpecificGroupBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); - m_addConfigButton = new QPushButton(Tr::tr("&Add...")); m_removeConfigButton = new QPushButton(Tr::tr("&Remove")); m_defaultDeviceButton = new QPushButton(Tr::tr("Set As Default")); - auto line = new QFrame; - line->setFrameShape(QFrame::HLine); - line->setFrameShadow(QFrame::Sunken); - auto customButtonsContainer = new QWidget; - m_buttonsLayout = new QVBoxLayout(customButtonsContainer); + + OptionPushButton *addButton = new OptionPushButton(Tr::tr("&Add...")); + connect(addButton, &OptionPushButton::clicked, this, &DeviceSettingsWidget::addDevice); + + QMenu *deviceTypeMenu = new QMenu(addButton); + QAction *defaultAction = new QAction(Tr::tr("&Start Wizard to Add Device...")); + connect(defaultAction, &QAction::triggered, this, &DeviceSettingsWidget::addDevice); + deviceTypeMenu->addAction(defaultAction); + deviceTypeMenu->addSeparator(); + + for (IDeviceFactory *factory : IDeviceFactory::allDeviceFactories()) { + if (!factory->canCreate()) + continue; + if (!factory->quickCreationAllowed()) + continue; + + QAction *action = new QAction(Tr::tr("Add %1").arg(factory->displayName())); + deviceTypeMenu->addAction(action); + + connect(action, &QAction::triggered, this, [factory, this] { + IDevice::Ptr device = factory->construct(); + QTC_ASSERT(device, return); + m_deviceManager->addDevice(device); + m_removeConfigButton->setEnabled(true); + m_configurationComboBox->setCurrentIndex(m_deviceManagerModel->indexOf(device)); + saveSettings(); + }); + } + + addButton->setOptionalMenu(deviceTypeMenu); + + m_buttonsLayout = new QVBoxLayout; m_buttonsLayout->setContentsMargins({}); auto scrollAreaWidget = new QWidget; auto scrollArea = new QScrollArea; scrollArea->setWidgetResizable(true); scrollArea->setWidget(scrollAreaWidget); - using namespace Utils::Layouting; + using namespace Layouting; Column { m_generalGroupBox, m_osSpecificGroupBox, @@ -127,25 +156,27 @@ void DeviceSettingsWidget::initGui() Tr::tr("Current state:"), Row { m_deviceStateIconLabel, m_deviceStateTextLabel, st, }, br, }.attachTo(m_generalGroupBox); + // clang-format off Row { Column { Form { m_configurationLabel, m_configurationComboBox, br, }, scrollArea, }, Column { - m_addConfigButton, + addButton, + Space(30), m_removeConfigButton, m_defaultDeviceButton, - line, - customButtonsContainer, + m_buttonsLayout, st, }, }.attachTo(this); + // clang-format on bool hasDeviceFactories = Utils::anyOf(IDeviceFactory::allDeviceFactories(), &IDeviceFactory::canCreate); - m_addConfigButton->setEnabled(hasDeviceFactories); + addButton->setEnabled(hasDeviceFactories); int lastIndex = ICore::settings() ->value(QLatin1String(LastDeviceIndexKey), 0).toInt(); @@ -160,10 +191,10 @@ void DeviceSettingsWidget::initGui() this, &DeviceSettingsWidget::setDefaultDevice); connect(m_removeConfigButton, &QAbstractButton::clicked, this, &DeviceSettingsWidget::removeDevice); - connect(m_nameLineEdit, &QLineEdit::editingFinished, - this, &DeviceSettingsWidget::deviceNameEditingFinished); - connect(m_addConfigButton, &QAbstractButton::clicked, - this, &DeviceSettingsWidget::addDevice); + connect(m_nameLineEdit, + &QLineEdit::editingFinished, + this, + &DeviceSettingsWidget::deviceNameEditingFinished); } void DeviceSettingsWidget::addDevice() @@ -182,6 +213,8 @@ void DeviceSettingsWidget::addDevice() if (device.isNull()) return; + Utils::asyncRun([device] { device->checkOsType(); }); + m_deviceManager->addDevice(device); m_removeConfigButton->setEnabled(true); m_configurationComboBox->setCurrentIndex(m_deviceManagerModel->indexOf(device)); diff --git a/src/plugins/projectexplorer/devicesupport/devicesettingswidget.h b/src/plugins/projectexplorer/devicesupport/devicesettingswidget.h index 5cd5cec85e2..207b60cf749 100644 --- a/src/plugins/projectexplorer/devicesupport/devicesettingswidget.h +++ b/src/plugins/projectexplorer/devicesupport/devicesettingswidget.h @@ -76,7 +76,6 @@ private: QLabel *m_deviceStateIconLabel; QLabel *m_deviceStateTextLabel; QGroupBox *m_osSpecificGroupBox; - QPushButton *m_addConfigButton; QPushButton *m_removeConfigButton; QPushButton *m_defaultDeviceButton; QVBoxLayout *m_buttonsLayout; diff --git a/src/plugins/projectexplorer/devicesupport/devicetestdialog.cpp b/src/plugins/projectexplorer/devicesupport/devicetestdialog.cpp index 91a83531b49..0277fc827a0 100644 --- a/src/plugins/projectexplorer/devicesupport/devicetestdialog.cpp +++ b/src/plugins/projectexplorer/devicesupport/devicetestdialog.cpp @@ -43,7 +43,7 @@ DeviceTestDialog::DeviceTestDialog(const IDevice::Ptr &deviceConfiguration, d->textEdit->setReadOnly(true); d->buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel); - using namespace Utils::Layouting; + using namespace Layouting; Column { d->textEdit, d->buttonBox, diff --git a/src/plugins/projectexplorer/devicesupport/deviceusedportsgatherer.cpp b/src/plugins/projectexplorer/devicesupport/deviceusedportsgatherer.cpp index 8081c9745df..c3f3f195ca1 100644 --- a/src/plugins/projectexplorer/devicesupport/deviceusedportsgatherer.cpp +++ b/src/plugins/projectexplorer/devicesupport/deviceusedportsgatherer.cpp @@ -9,8 +9,8 @@ #include #include +#include #include -#include #include #include @@ -22,7 +22,7 @@ namespace Internal { class DeviceUsedPortsGathererPrivate { public: - std::unique_ptr process; + std::unique_ptr process; QList usedPorts; IDevice::ConstPtr device; PortsGatheringMethod portsGatheringMethod; @@ -54,10 +54,10 @@ void DeviceUsedPortsGatherer::start() const QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::AnyIPProtocol; - d->process.reset(new QtcProcess); + d->process.reset(new Process); d->process->setCommand(d->portsGatheringMethod.commandLine(protocol)); - connect(d->process.get(), &QtcProcess::done, this, &DeviceUsedPortsGatherer::handleProcessDone); + connect(d->process.get(), &Process::done, this, &DeviceUsedPortsGatherer::handleProcessDone); d->process->start(); } @@ -116,12 +116,6 @@ void DeviceUsedPortsGatherer::handleProcessDone() stop(); } -DeviceUsedPortsGathererAdapter::DeviceUsedPortsGathererAdapter() -{ - connect(task(), &DeviceUsedPortsGatherer::portListReady, this, [this] { emit done(true); }); - connect(task(), &DeviceUsedPortsGatherer::error, this, [this] { emit done(false); }); -} - // PortGatherer PortsGatherer::PortsGatherer(RunControl *runControl) diff --git a/src/plugins/projectexplorer/devicesupport/deviceusedportsgatherer.h b/src/plugins/projectexplorer/devicesupport/deviceusedportsgatherer.h index 3519c7d22fe..20b0c9c9e8f 100644 --- a/src/plugins/projectexplorer/devicesupport/deviceusedportsgatherer.h +++ b/src/plugins/projectexplorer/devicesupport/deviceusedportsgatherer.h @@ -7,8 +7,9 @@ #include +#include + #include -#include namespace ProjectExplorer { @@ -43,11 +44,14 @@ private: Internal::DeviceUsedPortsGathererPrivate * const d; }; -class PROJECTEXPLORER_EXPORT DeviceUsedPortsGathererAdapter - : public Utils::Tasking::TaskAdapter +class PROJECTEXPLORER_EXPORT DeviceUsedPortsGathererTaskAdapter + : public Tasking::TaskAdapter { public: - DeviceUsedPortsGathererAdapter(); + DeviceUsedPortsGathererTaskAdapter() { + connect(task(), &DeviceUsedPortsGatherer::portListReady, this, [this] { emit done(true); }); + connect(task(), &DeviceUsedPortsGatherer::error, this, [this] { emit done(false); }); + } void start() final { task()->start(); } }; @@ -85,4 +89,5 @@ private: } // namespace ProjectExplorer -QTC_DECLARE_CUSTOM_TASK(PortGatherer, ProjectExplorer::DeviceUsedPortsGathererAdapter); +TASKING_DECLARE_TASK(DeviceUsedPortsGathererTask, + ProjectExplorer::DeviceUsedPortsGathererTaskAdapter); diff --git a/src/plugins/projectexplorer/devicesupport/filetransfer.cpp b/src/plugins/projectexplorer/devicesupport/filetransfer.cpp index bd386db0d11..05815797672 100644 --- a/src/plugins/projectexplorer/devicesupport/filetransfer.cpp +++ b/src/plugins/projectexplorer/devicesupport/filetransfer.cpp @@ -16,43 +16,18 @@ using namespace Utils; namespace ProjectExplorer { -FileTransferDirection FileToTransfer::direction() const -{ - if (m_source.needsDevice() == m_target.needsDevice()) - return FileTransferDirection::Invalid; - return m_source.needsDevice() ? FileTransferDirection::Download : FileTransferDirection::Upload; -} - QString FileTransferSetupData::defaultRsyncFlags() { return "-av"; } -static FileTransferDirection transferDirection(const FilesToTransfer &files) -{ - if (files.isEmpty()) - return FileTransferDirection::Invalid; - - const FileTransferDirection direction = files.first().direction(); - for (const FileToTransfer &file : files) { - if (file.direction() != direction) - return FileTransferDirection::Invalid; - } - return direction; -} - -static const FilePath &remoteFile(FileTransferDirection direction, const FileToTransfer &file) -{ - return direction == FileTransferDirection::Upload ? file.m_target : file.m_source; -} - -static IDeviceConstPtr matchedDevice(FileTransferDirection direction, const FilesToTransfer &files) +static IDeviceConstPtr matchedDevice(const FilesToTransfer &files) { if (files.isEmpty()) return {}; - const FilePath &filePath = remoteFile(direction, files.first()); + const FilePath filePath = files.first().m_target; for (const FileToTransfer &file : files) { - if (!filePath.isSameDevice(remoteFile(direction, file))) + if (!filePath.isSameDevice(file.m_target)) return {}; } return DeviceManager::deviceForPath(filePath); @@ -102,15 +77,11 @@ void FileTransferPrivate::start() if (m_setup.m_files.isEmpty()) return startFailed(Tr::tr("No files to transfer.")); - const FileTransferDirection direction = transferDirection(m_setup.m_files); - - IDeviceConstPtr device; - if (direction != FileTransferDirection::Invalid) - device = matchedDevice(direction, m_setup.m_files); + IDeviceConstPtr device = matchedDevice(m_setup.m_files); if (!device) { // Fall back to generic copy. - const FilePath &filePath = m_setup.m_files.first().m_target; + const FilePath filePath = m_setup.m_files.first().m_target; device = DeviceManager::deviceForPath(filePath); m_setup.m_method = FileTransferMethod::GenericCopy; } @@ -222,7 +193,7 @@ QString FileTransfer::transferMethodName(FileTransferMethod method) return {}; } -FileTransferAdapter::FileTransferAdapter() +FileTransferTaskAdapter::FileTransferTaskAdapter() { connect(task(), &FileTransfer::done, this, [this](const ProcessResultData &result) { emit done(result.m_exitStatus == QProcess::NormalExit diff --git a/src/plugins/projectexplorer/devicesupport/filetransfer.h b/src/plugins/projectexplorer/devicesupport/filetransfer.h index dcc04f05c0b..f73a3741682 100644 --- a/src/plugins/projectexplorer/devicesupport/filetransfer.h +++ b/src/plugins/projectexplorer/devicesupport/filetransfer.h @@ -7,7 +7,7 @@ #include "filetransferinterface.h" #include "idevicefwd.h" -#include +#include namespace Utils { class ProcessResultData; } @@ -46,14 +46,14 @@ private: FileTransferPrivate *d; }; -class PROJECTEXPLORER_EXPORT FileTransferAdapter : public Utils::Tasking::TaskAdapter +class PROJECTEXPLORER_EXPORT FileTransferTaskAdapter : public Tasking::TaskAdapter { public: - FileTransferAdapter(); + FileTransferTaskAdapter(); void start() override { task()->start(); } }; -class PROJECTEXPLORER_EXPORT FileTransferTestAdapter : public FileTransferAdapter +class PROJECTEXPLORER_EXPORT FileTransferTestTaskAdapter : public FileTransferTaskAdapter { public: void start() final { task()->test(); } @@ -61,5 +61,5 @@ public: } // namespace ProjectExplorer -QTC_DECLARE_CUSTOM_TASK(Transfer, ProjectExplorer::FileTransferAdapter); -QTC_DECLARE_CUSTOM_TASK(TransferTest, ProjectExplorer::FileTransferTestAdapter); +TASKING_DECLARE_TASK(FileTransferTask, ProjectExplorer::FileTransferTaskAdapter); +TASKING_DECLARE_TASK(FileTransferTestTask, ProjectExplorer::FileTransferTestTaskAdapter); diff --git a/src/plugins/projectexplorer/devicesupport/filetransferinterface.h b/src/plugins/projectexplorer/devicesupport/filetransferinterface.h index 2907f9c2573..ce4e662db43 100644 --- a/src/plugins/projectexplorer/devicesupport/filetransferinterface.h +++ b/src/plugins/projectexplorer/devicesupport/filetransferinterface.h @@ -11,12 +11,6 @@ namespace Utils { class ProcessResultData; } namespace ProjectExplorer { -enum class FileTransferDirection { - Invalid, - Upload, - Download -}; - enum class FileTransferMethod { Sftp, Rsync, @@ -29,8 +23,6 @@ class PROJECTEXPLORER_EXPORT FileToTransfer public: Utils::FilePath m_source; Utils::FilePath m_target; - - FileTransferDirection direction() const; }; using FilesToTransfer = QList; diff --git a/src/plugins/projectexplorer/devicesupport/idevice.cpp b/src/plugins/projectexplorer/devicesupport/idevice.cpp index b884237b3b2..50f552444b3 100644 --- a/src/plugins/projectexplorer/devicesupport/idevice.cpp +++ b/src/plugins/projectexplorer/devicesupport/idevice.cpp @@ -15,6 +15,7 @@ #include +#include #include #include #include @@ -92,6 +93,7 @@ static Id newId() const char DisplayNameKey[] = "Name"; const char TypeKey[] = "OsType"; +const char ClientOsTypeKey[] = "ClientOsType"; const char IdKey[] = "InternalId"; const char OriginKey[] = "Origin"; const char MachineTypeKey[] = "Type"; @@ -369,6 +371,27 @@ const QList IDevice::deviceActions() const return d->deviceActions; } +PortsGatheringMethod IDevice::portsGatheringMethod() const +{ + return {[this](QAbstractSocket::NetworkLayerProtocol protocol) -> CommandLine { + // We might encounter the situation that protocol is given IPv6 + // but the consumer of the free port information decides to open + // an IPv4(only) port. As a result the next IPv6 scan will + // report the port again as open (in IPv6 namespace), while the + // same port in IPv4 namespace might still be blocked, and + // re-use of this port fails. + // GDBserver behaves exactly like this. + + Q_UNUSED(protocol) + + if (filePath("/proc/net").isReadableDir()) + return {filePath("/bin/sh"), {"-c", "cat /proc/net/tcp*"}}; + + return {filePath("netstat"), {"-a", "-n"}}; + }, + &Port::parseFromCommandOutput}; +}; + DeviceProcessList *IDevice::createProcessListModel(QObject *parent) const { Q_UNUSED(parent) @@ -425,6 +448,8 @@ void IDevice::fromMap(const QVariantMap &map) d->type = typeFromMap(map); d->displayName.fromMap(map, DisplayNameKey); d->id = Id::fromSetting(map.value(QLatin1String(IdKey))); + d->osType = osTypeFromString( + map.value(QLatin1String(ClientOsTypeKey), osTypeToString(OsTypeLinux)).toString()); if (!d->id.isValid()) d->id = newId(); d->origin = static_cast(map.value(QLatin1String(OriginKey), ManuallyAdded).toInt()); @@ -471,6 +496,7 @@ QVariantMap IDevice::toMap() const QVariantMap map; d->displayName.toMap(map, DisplayNameKey); map.insert(QLatin1String(TypeKey), d->type.toString()); + map.insert(QLatin1String(ClientOsTypeKey), osTypeToString(d->osType)); map.insert(QLatin1String(IdKey), d->id.toSetting()); map.insert(QLatin1String(OriginKey), d->origin); @@ -503,9 +529,6 @@ IDevice::Ptr IDevice::clone() const device->d->deviceState = d->deviceState; device->d->deviceActions = d->deviceActions; device->d->deviceIcons = d->deviceIcons; - // Os type is only set in the constructor, always to the same value. - // But make sure we notice if that changes in the future (which it shouldn't). - QTC_CHECK(device->d->osType == d->osType); device->d->osType = d->osType; device->fromMap(toMap()); return device; @@ -578,16 +601,6 @@ void IDevice::setDebugServerPath(const FilePath &path) d->debugServerPath = path; } -FilePath IDevice::debugDumperPath() const -{ - return d->debugDumperPath; -} - -void IDevice::setDebugDumperPath(const FilePath &path) -{ - d->debugDumperPath = path; -} - FilePath IDevice::qmlRunCommand() const { return d->qmlRunCommand; @@ -682,12 +695,12 @@ void DeviceProcessKiller::start() m_signalOperation->killProcess(m_processPath.path()); } -KillerAdapter::KillerAdapter() +DeviceProcessKillerTaskAdapter::DeviceProcessKillerTaskAdapter() { - connect(task(), &DeviceProcessKiller::done, this, &KillerAdapter::done); + connect(task(), &DeviceProcessKiller::done, this, &TaskInterface::done); } -void KillerAdapter::start() +void DeviceProcessKillerTaskAdapter::start() { task()->start(); } diff --git a/src/plugins/projectexplorer/devicesupport/idevice.h b/src/plugins/projectexplorer/devicesupport/idevice.h index 6323b8589bb..e28f88aaf46 100644 --- a/src/plugins/projectexplorer/devicesupport/idevice.h +++ b/src/plugins/projectexplorer/devicesupport/idevice.h @@ -6,11 +6,12 @@ #include "../projectexplorer_export.h" #include "idevicefwd.h" +#include + #include #include #include #include -#include #include #include @@ -35,7 +36,7 @@ class Icon; class PortList; class Port; class ProcessInterface; -class QtcProcess; +class Process; } // Utils namespace ProjectExplorer { @@ -139,10 +140,7 @@ public: void addDeviceAction(const DeviceAction &deviceAction); const QList deviceActions() const; - // Devices that can auto detect ports need not return a ports gathering method. Such devices can - // obtain a free port on demand. eg: Desktop device. - virtual bool canAutoDetectPorts() const { return false; } - virtual PortsGatheringMethod portsGatheringMethod() const { return {}; } + virtual PortsGatheringMethod portsGatheringMethod() const; virtual bool canCreateProcessModel() const { return false; } virtual DeviceProcessList *createProcessListModel(QObject *parent = nullptr) const; virtual bool hasDeviceTester() const { return false; } @@ -179,9 +177,6 @@ public: Utils::FilePath debugServerPath() const; void setDebugServerPath(const Utils::FilePath &path); - Utils::FilePath debugDumperPath() const; - void setDebugDumperPath(const Utils::FilePath &path); - Utils::FilePath qmlRunCommand() const; void setQmlRunCommand(const Utils::FilePath &path); @@ -221,6 +216,8 @@ public: virtual bool prepareForBuild(const Target *target); virtual std::optional clangdExecutable() const; + virtual void checkOsType() {} + protected: IDevice(); @@ -280,13 +277,14 @@ private: QString m_errorString; }; -class PROJECTEXPLORER_EXPORT KillerAdapter : public Utils::Tasking::TaskAdapter +class PROJECTEXPLORER_EXPORT DeviceProcessKillerTaskAdapter + : public Tasking::TaskAdapter { public: - KillerAdapter(); + DeviceProcessKillerTaskAdapter(); void start() final; }; } // namespace ProjectExplorer -QTC_DECLARE_CUSTOM_TASK(Killer, ProjectExplorer::KillerAdapter); +TASKING_DECLARE_TASK(DeviceProcessKillerTask, ProjectExplorer::DeviceProcessKillerTaskAdapter); diff --git a/src/plugins/projectexplorer/devicesupport/idevicefactory.cpp b/src/plugins/projectexplorer/devicesupport/idevicefactory.cpp index c33bc125bb5..b5178445702 100644 --- a/src/plugins/projectexplorer/devicesupport/idevicefactory.cpp +++ b/src/plugins/projectexplorer/devicesupport/idevicefactory.cpp @@ -63,12 +63,25 @@ bool IDeviceFactory::canCreate() const IDevice::Ptr IDeviceFactory::create() const { - return m_creator ? m_creator() : IDevice::Ptr(); + if (!m_creator) + return {}; + + IDevice::Ptr device = m_creator(); + if (!device) // e.g. Cancel used on the dialog to create a device + return {}; + device->setDefaultDisplayName(displayName()); + return device; } IDevice::Ptr IDeviceFactory::construct() const { - return m_constructor ? m_constructor() : IDevice::Ptr(); + if (!m_constructor) + return {}; + + IDevice::Ptr device = m_constructor(); + QTC_ASSERT(device, return {}); + device->setDefaultDisplayName(displayName()); + return device; } static QList g_deviceFactories; @@ -105,6 +118,16 @@ void IDeviceFactory::setCreator(const std::function &creator) m_creator = creator; } +void IDeviceFactory::setQuickCreationAllowed(bool on) +{ + m_quickCreationAllowed = on; +} + +bool IDeviceFactory::quickCreationAllowed() const +{ + return m_quickCreationAllowed; +} + void IDeviceFactory::setConstructionFunction(const std::function &constructor) { m_constructor = constructor; diff --git a/src/plugins/projectexplorer/devicesupport/idevicefactory.h b/src/plugins/projectexplorer/devicesupport/idevicefactory.h index 87a54244adb..665059f5b2c 100644 --- a/src/plugins/projectexplorer/devicesupport/idevicefactory.h +++ b/src/plugins/projectexplorer/devicesupport/idevicefactory.h @@ -26,6 +26,7 @@ public: bool canCreate() const; IDevicePtr construct() const; IDevicePtr create() const; + bool quickCreationAllowed() const; virtual bool canRestore(const QVariantMap &) const { return true; } @@ -41,6 +42,7 @@ protected: void setCombinedIcon(const Utils::FilePath &smallIcon, const Utils::FilePath &largeIcon); void setConstructionFunction(const std::function &constructor); void setCreator(const std::function &creator); + void setQuickCreationAllowed(bool on); private: std::function m_creator; @@ -48,6 +50,7 @@ private: QString m_displayName; QIcon m_icon; std::function m_constructor; + bool m_quickCreationAllowed = false; }; } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/devicesupport/localprocesslist.cpp b/src/plugins/projectexplorer/devicesupport/localprocesslist.cpp deleted file mode 100644 index c6fd49012fb..00000000000 --- a/src/plugins/projectexplorer/devicesupport/localprocesslist.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "localprocesslist.h" - -#include -#include - -#include - -#if defined(Q_OS_UNIX) -#include -#elif defined(Q_OS_WIN) -#include -#endif - -using namespace Utils; - -namespace ProjectExplorer { -namespace Internal { - -LocalProcessList::LocalProcessList(const IDevice::ConstPtr &device, QObject *parent) - : DeviceProcessList(device, parent) -{ -#if defined(Q_OS_UNIX) - setOwnPid(getpid()); -#elif defined(Q_OS_WIN) - setOwnPid(GetCurrentProcessId()); -#endif -} - -void LocalProcessList::doKillProcess(const ProcessInfo &processInfo) -{ - DeviceProcessSignalOperation::Ptr signalOperation = device()->signalOperation(); - connect(signalOperation.data(), &DeviceProcessSignalOperation::finished, - this, &LocalProcessList::reportDelayedKillStatus); - signalOperation->killProcess(processInfo.processId); -} - -void LocalProcessList::handleUpdate() -{ - reportProcessListUpdated(ProcessInfo::processInfoList()); -} - -void LocalProcessList::doUpdate() -{ - QTimer::singleShot(0, this, &LocalProcessList::handleUpdate); -} - -void LocalProcessList::reportDelayedKillStatus(const QString &errorMessage) -{ - if (errorMessage.isEmpty()) - reportProcessKilled(); - else - reportError(errorMessage); -} - -} // namespace Internal -} // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/devicesupport/processlist.cpp b/src/plugins/projectexplorer/devicesupport/processlist.cpp new file mode 100644 index 00000000000..11e0932832d --- /dev/null +++ b/src/plugins/projectexplorer/devicesupport/processlist.cpp @@ -0,0 +1,61 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "processlist.h" + +#include +#include + +#include + +#if defined(Q_OS_UNIX) +#include +#elif defined(Q_OS_WIN) +#include +#endif + +using namespace Utils; + +namespace ProjectExplorer { + +ProcessList::ProcessList(const IDevice::ConstPtr &device, QObject *parent) + : DeviceProcessList(device, parent) +{ +#if defined(Q_OS_UNIX) + setOwnPid(getpid()); +#elif defined(Q_OS_WIN) + setOwnPid(GetCurrentProcessId()); +#endif +} + +void ProcessList::doKillProcess(const ProcessInfo &processInfo) +{ + m_signalOperation = device()->signalOperation(); + connect(m_signalOperation.data(), + &DeviceProcessSignalOperation::finished, + this, + &ProcessList::reportDelayedKillStatus); + m_signalOperation->killProcess(processInfo.processId); +} + +void ProcessList::handleUpdate() +{ + reportProcessListUpdated(ProcessInfo::processInfoList(DeviceProcessList::device()->rootPath())); +} + +void ProcessList::doUpdate() +{ + QTimer::singleShot(0, this, &ProcessList::handleUpdate); +} + +void ProcessList::reportDelayedKillStatus(const QString &errorMessage) +{ + if (errorMessage.isEmpty()) + reportProcessKilled(); + else + reportError(errorMessage); + + m_signalOperation.reset(); +} + +} // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/devicesupport/localprocesslist.h b/src/plugins/projectexplorer/devicesupport/processlist.h similarity index 66% rename from src/plugins/projectexplorer/devicesupport/localprocesslist.h rename to src/plugins/projectexplorer/devicesupport/processlist.h index e3445f47b20..caebaf22f97 100644 --- a/src/plugins/projectexplorer/devicesupport/localprocesslist.h +++ b/src/plugins/projectexplorer/devicesupport/processlist.h @@ -4,16 +4,16 @@ #pragma once #include "deviceprocesslist.h" +#include "idevice.h" namespace ProjectExplorer { -namespace Internal { -class LocalProcessList : public DeviceProcessList +class PROJECTEXPLORER_EXPORT ProcessList : public DeviceProcessList { Q_OBJECT public: - explicit LocalProcessList(const IDeviceConstPtr &device, QObject *parent = nullptr); + explicit ProcessList(const IDeviceConstPtr &device, QObject *parent = nullptr); private: void doUpdate() override; @@ -22,7 +22,9 @@ private: private: void handleUpdate(); void reportDelayedKillStatus(const QString &errorMessage); + +private: + DeviceProcessSignalOperation::Ptr m_signalOperation; }; -} // namespace Internal } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/devicesupport/sshdeviceprocesslist.cpp b/src/plugins/projectexplorer/devicesupport/sshdeviceprocesslist.cpp deleted file mode 100644 index d64cde6e3ed..00000000000 --- a/src/plugins/projectexplorer/devicesupport/sshdeviceprocesslist.cpp +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "sshdeviceprocesslist.h" - -#include "idevice.h" -#include "../projectexplorertr.h" - -#include -#include -#include -#include - -using namespace Utils; - -namespace ProjectExplorer { - -class SshDeviceProcessListPrivate -{ -public: - QtcProcess m_process; - DeviceProcessSignalOperation::Ptr m_signalOperation; -}; - -SshDeviceProcessList::SshDeviceProcessList(const IDevice::ConstPtr &device, QObject *parent) : - DeviceProcessList(device, parent), d(std::make_unique()) -{ - connect(&d->m_process, &QtcProcess::done, this, &SshDeviceProcessList::handleProcessDone); -} - -SshDeviceProcessList::~SshDeviceProcessList() = default; - -void SshDeviceProcessList::doUpdate() -{ - d->m_process.close(); - d->m_process.setCommand({device()->filePath("/bin/sh"), {"-c", listProcessesCommandLine()}}); - d->m_process.start(); -} - -void SshDeviceProcessList::doKillProcess(const ProcessInfo &process) -{ - d->m_signalOperation = device()->signalOperation(); - QTC_ASSERT(d->m_signalOperation, return); - connect(d->m_signalOperation.data(), &DeviceProcessSignalOperation::finished, - this, &SshDeviceProcessList::handleKillProcessFinished); - d->m_signalOperation->killProcess(process.processId); -} - -void SshDeviceProcessList::handleProcessDone() -{ - if (d->m_process.result() == ProcessResult::FinishedWithSuccess) { - reportProcessListUpdated(buildProcessList(d->m_process.cleanedStdOut())); - } else { - const QString errorString = d->m_process.exitStatus() == QProcess::NormalExit - ? Tr::tr("Process listing command failed with exit code %1.").arg(d->m_process.exitCode()) - : d->m_process.errorString(); - const QString stdErr = d->m_process.cleanedStdErr(); - const QString outputString - = stdErr.isEmpty() ? stdErr : Tr::tr("Remote stderr was: %1").arg(stdErr); - reportError(Utils::joinStrings({errorString, outputString}, '\n')); - } - setFinished(); -} - -void SshDeviceProcessList::handleKillProcessFinished(const QString &errorString) -{ - if (errorString.isEmpty()) - reportProcessKilled(); - else - reportError(Tr::tr("Error: Kill process failed: %1").arg(errorString)); - setFinished(); -} - -void SshDeviceProcessList::setFinished() -{ - d->m_process.close(); - if (d->m_signalOperation) { - d->m_signalOperation->disconnect(this); - d->m_signalOperation.clear(); - } -} - -} // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/devicesupport/sshdeviceprocesslist.h b/src/plugins/projectexplorer/devicesupport/sshdeviceprocesslist.h deleted file mode 100644 index fd560f53755..00000000000 --- a/src/plugins/projectexplorer/devicesupport/sshdeviceprocesslist.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "deviceprocesslist.h" - -#include - -namespace ProjectExplorer { - -class SshDeviceProcessListPrivate; - -class PROJECTEXPLORER_EXPORT SshDeviceProcessList : public DeviceProcessList -{ - Q_OBJECT -public: - explicit SshDeviceProcessList(const IDeviceConstPtr &device, QObject *parent = nullptr); - ~SshDeviceProcessList() override; - -private: - void handleProcessDone(); - void handleKillProcessFinished(const QString &errorString); - - virtual QString listProcessesCommandLine() const = 0; - virtual QList buildProcessList(const QString &listProcessesReply) const = 0; - - void doUpdate() override; - void doKillProcess(const Utils::ProcessInfo &process) override; - - void setFinished(); - - const std::unique_ptr d; -}; - -} // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/devicesupport/sshparameters.cpp b/src/plugins/projectexplorer/devicesupport/sshparameters.cpp index 3cc6364a677..744e350eaef 100644 --- a/src/plugins/projectexplorer/devicesupport/sshparameters.cpp +++ b/src/plugins/projectexplorer/devicesupport/sshparameters.cpp @@ -8,8 +8,8 @@ #include #include #include +#include #include -#include #include @@ -19,9 +19,17 @@ using namespace Utils; namespace ProjectExplorer { -SshParameters::SshParameters() +SshParameters::SshParameters() = default; + +QString SshParameters::userAtHost() const { - url.setPort(0); + QString res; + if (!m_userName.isEmpty()) + res = m_userName + '@'; + res += m_host; + if (m_port != 22) + res += QString(":%1").arg(m_port); + return res; } QStringList SshParameters::connectionOptions(const FilePath &binary) const @@ -61,7 +69,7 @@ QStringList SshParameters::connectionOptions(const FilePath &binary) const return args; } -bool SshParameters::setupSshEnvironment(QtcProcess *process) +bool SshParameters::setupSshEnvironment(Process *process) { Environment env = process->controlEnvironment(); if (!env.hasChanges()) @@ -81,10 +89,11 @@ bool SshParameters::setupSshEnvironment(QtcProcess *process) return hasDisplay; } - -static inline bool equals(const SshParameters &p1, const SshParameters &p2) +bool operator==(const SshParameters &p1, const SshParameters &p2) { - return p1.url == p2.url + return p1.m_host == p2.m_host + && p1.m_port == p2.m_port + && p1.m_userName == p2.m_userName && p1.authenticationType == p2.authenticationType && p1.privateKeyFile == p2.privateKeyFile && p1.hostKeyCheckingMode == p2.hostKeyCheckingMode @@ -92,16 +101,6 @@ static inline bool equals(const SshParameters &p1, const SshParameters &p2) && p1.timeout == p2.timeout; } -bool operator==(const SshParameters &p1, const SshParameters &p2) -{ - return equals(p1, p2); -} - -bool operator!=(const SshParameters &p1, const SshParameters &p2) -{ - return !equals(p1, p2); -} - #ifdef WITH_TESTS namespace SshTest { const QString getHostFromEnvironment() diff --git a/src/plugins/projectexplorer/devicesupport/sshparameters.h b/src/plugins/projectexplorer/devicesupport/sshparameters.h index 00b63e3aacf..3ee483d5f3d 100644 --- a/src/plugins/projectexplorer/devicesupport/sshparameters.h +++ b/src/plugins/projectexplorer/devicesupport/sshparameters.h @@ -7,9 +7,7 @@ #include -#include - -namespace Utils { class QtcProcess; } +namespace Utils { class Process; } namespace ProjectExplorer { @@ -29,28 +27,34 @@ public: SshParameters(); - QString host() const { return url.host(); } - quint16 port() const { return url.port(); } - QString userName() const { return url.userName(); } - QString userAtHost() const { return userName().isEmpty() ? host() : userName() + '@' + host(); } - void setHost(const QString &host) { url.setHost(host); } - void setPort(int port) { url.setPort(port); } - void setUserName(const QString &name) { url.setUserName(name); } + QString host() const { return m_host; } + quint16 port() const { return m_port; } + QString userName() const { return m_userName; } + + QString userAtHost() const; + + void setHost(const QString &host) { m_host = host; } + void setPort(int port) { m_port = port; } + void setUserName(const QString &name) { m_userName = name; } QStringList connectionOptions(const Utils::FilePath &binary) const; - QUrl url; Utils::FilePath privateKeyFile; QString x11DisplayName; int timeout = 0; // In seconds. AuthenticationType authenticationType = AuthenticationTypeAll; SshHostKeyCheckingMode hostKeyCheckingMode = SshHostKeyCheckingAllowNoMatch; - static bool setupSshEnvironment(Utils::QtcProcess *process); -}; + static bool setupSshEnvironment(Utils::Process *process); -PROJECTEXPLORER_EXPORT bool operator==(const SshParameters &p1, const SshParameters &p2); -PROJECTEXPLORER_EXPORT bool operator!=(const SshParameters &p1, const SshParameters &p2); + friend PROJECTEXPLORER_EXPORT bool operator==(const SshParameters &p1, const SshParameters &p2); + friend bool operator!=(const SshParameters &p1, const SshParameters &p2) { return !(p1 == p2); } + +private: + QString m_host; + quint16 m_port = 22; + QString m_userName; +}; #ifdef WITH_TESTS namespace SshTest { diff --git a/src/plugins/projectexplorer/editorconfiguration.cpp b/src/plugins/projectexplorer/editorconfiguration.cpp index fc3933d7344..ff99bc25e83 100644 --- a/src/plugins/projectexplorer/editorconfiguration.cpp +++ b/src/plugins/projectexplorer/editorconfiguration.cpp @@ -5,7 +5,7 @@ #include "project.h" #include "projectexplorertr.h" -#include "session.h" +#include "projectmanager.h" #include @@ -88,7 +88,7 @@ EditorConfiguration::EditorConfiguration() : d(std::make_uniquem_defaultCodeStyle->setCurrentDelegate(TextEditorSettings::codeStyle()); - connect(SessionManager::instance(), &SessionManager::aboutToRemoveProject, + connect(ProjectManager::instance(), &ProjectManager::aboutToRemoveProject, this, &EditorConfiguration::slotAboutToRemoveProject); } @@ -263,7 +263,7 @@ void EditorConfiguration::setUseGlobalSettings(bool use) const QList editors = Core::DocumentModel::editorsForOpenedDocuments(); for (Core::IEditor *editor : editors) { if (auto widget = TextEditorWidget::fromEditor(editor)) { - Project *project = SessionManager::projectForFile(editor->document()->filePath()); + Project *project = ProjectManager::projectForFile(editor->document()->filePath()); if (project && project->editorConfiguration() == this) switchSettings(widget); } @@ -399,7 +399,7 @@ TabSettings actualTabSettings(const Utils::FilePath &file, { if (baseTextdocument) return baseTextdocument->tabSettings(); - if (Project *project = SessionManager::projectForFile(file)) + if (Project *project = ProjectManager::projectForFile(file)) return project->editorConfiguration()->codeStyle()->tabSettings(); return TextEditorSettings::codeStyle()->tabSettings(); } diff --git a/src/plugins/projectexplorer/editorsettingspropertiespage.cpp b/src/plugins/projectexplorer/editorsettingspropertiespage.cpp index 52450b2bc89..e0c2b93bb26 100644 --- a/src/plugins/projectexplorer/editorsettingspropertiespage.cpp +++ b/src/plugins/projectexplorer/editorsettingspropertiespage.cpp @@ -48,7 +48,7 @@ EditorSettingsWidget::EditorSettingsWidget(Project *project) : m_project(project m_behaviorSettings = new TextEditor::BehaviorSettingsWidget(this); - using namespace Utils::Layouting; + using namespace Layouting; Row { m_showWrapColumn, @@ -63,7 +63,8 @@ EditorSettingsWidget::EditorSettingsWidget(Project *project) : m_project(project m_displaySettings, m_behaviorSettings, st, - }.attachTo(this, WithoutMargins); + noMargin + }.attachTo(this); const EditorConfiguration *config = m_project->editorConfiguration(); settingsToUi(config); diff --git a/src/plugins/projectexplorer/environmentaspect.cpp b/src/plugins/projectexplorer/environmentaspect.cpp index 66a2b3bc990..09bfeb9c80d 100644 --- a/src/plugins/projectexplorer/environmentaspect.cpp +++ b/src/plugins/projectexplorer/environmentaspect.cpp @@ -3,8 +3,11 @@ #include "environmentaspect.h" +#include "buildconfiguration.h" #include "environmentaspectwidget.h" +#include "kit.h" #include "projectexplorertr.h" +#include "target.h" #include #include @@ -12,6 +15,7 @@ using namespace Utils; namespace ProjectExplorer { +const char PRINT_ON_RUN_KEY[] = "PE.EnvironmentAspect.PrintOnRun"; // -------------------------------------------------------------------- // EnvironmentAspect: @@ -101,16 +105,39 @@ int EnvironmentAspect::addPreferredBaseEnvironment(const QString &displayName, return index; } +void EnvironmentAspect::setSupportForBuildEnvironment(Target *target) +{ + setIsLocal(true); + addSupportedBaseEnvironment(Tr::tr("Clean Environment"), {}); + + addSupportedBaseEnvironment(Tr::tr("System Environment"), [] { + return Environment::systemEnvironment(); + }); + addPreferredBaseEnvironment(Tr::tr("Build Environment"), [target] { + if (BuildConfiguration *bc = target->activeBuildConfiguration()) + return bc->environment(); + // Fallback for targets without buildconfigurations: + return target->kit()->buildEnvironment(); + }); + + connect(target, &Target::activeBuildConfigurationChanged, + this, &EnvironmentAspect::environmentChanged); + connect(target, &Target::buildEnvironmentChanged, + this, &EnvironmentAspect::environmentChanged); +} + void EnvironmentAspect::fromMap(const QVariantMap &map) { m_base = map.value(QLatin1String(BASE_KEY), -1).toInt(); m_userChanges = Utils::EnvironmentItem::fromStringList(map.value(QLatin1String(CHANGES_KEY)).toStringList()); + m_printOnRun = map.value(PRINT_ON_RUN_KEY).toBool(); } void EnvironmentAspect::toMap(QVariantMap &data) const { data.insert(QLatin1String(BASE_KEY), m_base); data.insert(QLatin1String(CHANGES_KEY), Utils::EnvironmentItem::toStringList(m_userChanges)); + data.insert(PRINT_ON_RUN_KEY, m_printOnRun); } QString EnvironmentAspect::currentDisplayName() const diff --git a/src/plugins/projectexplorer/environmentaspect.h b/src/plugins/projectexplorer/environmentaspect.h index 5b1d0d1c41a..97ce26f017d 100644 --- a/src/plugins/projectexplorer/environmentaspect.h +++ b/src/plugins/projectexplorer/environmentaspect.h @@ -39,6 +39,8 @@ public: int addPreferredBaseEnvironment(const QString &displayName, const std::function &getter); + void setSupportForBuildEnvironment(Target *target); + QString currentDisplayName() const; const QStringList displayNames() const; @@ -48,6 +50,10 @@ public: bool isLocal() const { return m_isLocal; } + bool isPrintOnRunAllowed() const { return m_allowPrintOnRun; } + bool isPrintOnRunEnabled() const { return m_printOnRun; } + void setPrintOnRun(bool enabled) { m_printOnRun = enabled; } + struct Data : BaseAspect::Data { Utils::Environment environment; @@ -66,6 +72,7 @@ protected: void toMap(QVariantMap &map) const override; void setIsLocal(bool local) { m_isLocal = local; } + void setAllowPrintOnRun(bool allow) { m_allowPrintOnRun = allow; } static constexpr char BASE_KEY[] = "PE.EnvironmentAspect.Base"; static constexpr char CHANGES_KEY[] = "PE.EnvironmentAspect.Changes"; @@ -84,6 +91,8 @@ private: QList m_baseEnvironments; int m_base = -1; bool m_isLocal = false; + bool m_allowPrintOnRun = true; + bool m_printOnRun = false; }; } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/environmentaspectwidget.cpp b/src/plugins/projectexplorer/environmentaspectwidget.cpp index 18babfcf615..34e48af42c9 100644 --- a/src/plugins/projectexplorer/environmentaspectwidget.cpp +++ b/src/plugins/projectexplorer/environmentaspectwidget.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -63,6 +64,14 @@ EnvironmentAspectWidget::EnvironmentAspectWidget(EnvironmentAspect *aspect) m_environmentWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); topLayout->addWidget(m_environmentWidget); + if (m_aspect->isPrintOnRunAllowed()) { + const auto printOnRunCheckBox = new QCheckBox(Tr::tr("Show in output pane when running")); + printOnRunCheckBox->setChecked(m_aspect->isPrintOnRunEnabled()); + connect(printOnRunCheckBox, &QCheckBox::toggled, + m_aspect, &EnvironmentAspect::setPrintOnRun); + topLayout->addWidget(printOnRunCheckBox); + } + connect(m_environmentWidget, &EnvironmentWidget::userChangesChanged, this, &EnvironmentAspectWidget::userChangesEdited); diff --git a/src/plugins/projectexplorer/extraabi.cpp b/src/plugins/projectexplorer/extraabi.cpp index 24a28611033..19f970b11a6 100644 --- a/src/plugins/projectexplorer/extraabi.cpp +++ b/src/plugins/projectexplorer/extraabi.cpp @@ -4,7 +4,6 @@ #include "extraabi.h" #include "abi.h" -#include "projectexplorertr.h" #include @@ -36,17 +35,15 @@ public: class AbiFlavorAccessor : public UpgradingSettingsAccessor { public: - AbiFlavorAccessor(); + AbiFlavorAccessor() + { + setDocType("QtCreatorExtraAbi"); + setApplicationDisplayName(Core::Constants::IDE_DISPLAY_NAME); + setBaseFilePath(Core::ICore::installerResourcePath("abi.xml")); + addVersionUpgrader(std::make_unique()); + } }; -AbiFlavorAccessor::AbiFlavorAccessor() : - UpgradingSettingsAccessor("QtCreatorExtraAbi", Tr::tr("ABI"), - Core::Constants::IDE_DISPLAY_NAME) -{ - setBaseFilePath(Core::ICore::installerResourcePath("abi.xml")); - - addVersionUpgrader(std::make_unique()); -} // -------------------------------------------------------------------- // ExtraAbi: diff --git a/src/plugins/projectexplorer/extracompiler.cpp b/src/plugins/projectexplorer/extracompiler.cpp index ab72d3608ff..67bb4a63fc3 100644 --- a/src/plugins/projectexplorer/extracompiler.cpp +++ b/src/plugins/projectexplorer/extracompiler.cpp @@ -5,23 +5,26 @@ #include "buildmanager.h" #include "kitinformation.h" -#include "session.h" +#include "projectmanager.h" #include "target.h" #include #include -#include +#include + +#include #include #include -#include +#include #include -#include #include #include #include +using namespace Core; +using namespace Tasking; using namespace Utils; namespace ProjectExplorer { @@ -37,15 +40,14 @@ public: FilePath source; FileNameToContentsHash contents; QDateTime compileTime; - Core::IEditor *lastEditor = nullptr; + IEditor *lastEditor = nullptr; QMetaObject::Connection activeBuildConfigConnection; QMetaObject::Connection activeEnvironmentConnection; - Utils::Guard lock; + Guard lock; bool dirty = false; QTimer timer; - FutureSynchronizer m_futureSynchronizer; std::unique_ptr m_taskTree; }; @@ -63,16 +65,16 @@ ExtraCompiler::ExtraCompiler(const Project *project, const FilePath &source, connect(BuildManager::instance(), &BuildManager::buildStateChanged, this, &ExtraCompiler::onTargetsBuilt); - connect(SessionManager::instance(), &SessionManager::projectRemoved, + connect(ProjectManager::instance(), &ProjectManager::projectRemoved, this, [this](Project *project) { if (project == d->project) deleteLater(); }); - Core::EditorManager *editorManager = Core::EditorManager::instance(); - connect(editorManager, &Core::EditorManager::currentEditorChanged, + EditorManager *editorManager = EditorManager::instance(); + connect(editorManager, &EditorManager::currentEditorChanged, this, &ExtraCompiler::onEditorChanged); - connect(editorManager, &Core::EditorManager::editorAboutToClose, + connect(editorManager, &EditorManager::editorAboutToClose, this, &ExtraCompiler::onEditorAboutToClose); // Use existing target files, where possible. Otherwise run the compiler. @@ -135,7 +137,7 @@ QThreadPool *ExtraCompiler::extraCompilerThreadPool() return s_extraCompilerThreadPool(); } -Tasking::TaskItem ExtraCompiler::compileFileItem() +TaskItem ExtraCompiler::compileFileItem() { return taskItemImpl(fromFileProvider()); } @@ -228,12 +230,12 @@ void ExtraCompiler::onTargetsBuilt(Project *project) }); } -void ExtraCompiler::onEditorChanged(Core::IEditor *editor) +void ExtraCompiler::onEditorChanged(IEditor *editor) { // Handle old editor if (d->lastEditor) { - Core::IDocument *doc = d->lastEditor->document(); - disconnect(doc, &Core::IDocument::contentsChanged, + IDocument *doc = d->lastEditor->document(); + disconnect(doc, &IDocument::contentsChanged, this, &ExtraCompiler::setDirty); if (d->dirty) { @@ -246,7 +248,7 @@ void ExtraCompiler::onEditorChanged(Core::IEditor *editor) d->lastEditor = editor; // Handle new editor - connect(d->lastEditor->document(), &Core::IDocument::contentsChanged, + connect(d->lastEditor->document(), &IDocument::contentsChanged, this, &ExtraCompiler::setDirty); } else { d->lastEditor = nullptr; @@ -259,15 +261,15 @@ void ExtraCompiler::setDirty() d->timer.start(1000); } -void ExtraCompiler::onEditorAboutToClose(Core::IEditor *editor) +void ExtraCompiler::onEditorAboutToClose(IEditor *editor) { if (d->lastEditor != editor) return; // Oh no our editor is going to be closed // get the content first - Core::IDocument *doc = d->lastEditor->document(); - disconnect(doc, &Core::IDocument::contentsChanged, + IDocument *doc = d->lastEditor->document(); + disconnect(doc, &IDocument::contentsChanged, this, &ExtraCompiler::setDirty); if (d->dirty) { d->dirty = false; @@ -278,24 +280,17 @@ void ExtraCompiler::onEditorAboutToClose(Core::IEditor *editor) Environment ExtraCompiler::buildEnvironment() const { - if (Target *target = project()->activeTarget()) { - if (BuildConfiguration *bc = target->activeBuildConfiguration()) { - return bc->environment(); - } else { - EnvironmentItems changes = - EnvironmentKitAspect::environmentChanges(target->kit()); - Environment env = Environment::systemEnvironment(); - env.modify(changes); - return env; - } - } + Target *target = project()->activeTarget(); + if (!target) + return Environment::systemEnvironment(); - return Environment::systemEnvironment(); -} + if (BuildConfiguration *bc = target->activeBuildConfiguration()) + return bc->environment(); -Utils::FutureSynchronizer *ExtraCompiler::futureSynchronizer() const -{ - return &d->m_futureSynchronizer; + const EnvironmentItems changes = EnvironmentKitAspect::environmentChanges(target->kit()); + Environment env = Environment::systemEnvironment(); + env.modify(changes); + return env; } void ExtraCompiler::setContent(const FilePath &file, const QByteArray &contents) @@ -331,15 +326,16 @@ ProcessExtraCompiler::ProcessExtraCompiler(const Project *project, const FilePat ExtraCompiler(project, source, targets, parent) { } -Tasking::TaskItem ProcessExtraCompiler::taskItemImpl(const ContentProvider &provider) +TaskItem ProcessExtraCompiler::taskItemImpl(const ContentProvider &provider) { - const auto setupTask = [=](AsyncTask &async) { + const auto setupTask = [=](Async &async) { async.setThreadPool(extraCompilerThreadPool()); - async.setAsyncCallData(&ProcessExtraCompiler::runInThread, this, command(), - workingDirectory(), arguments(), provider, buildEnvironment()); - async.setFutureSynchronizer(futureSynchronizer()); + // The passed synchronizer has cancelOnWait set to true by default. + async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer()); + async.setConcurrentCallData(&ProcessExtraCompiler::runInThread, this, command(), + workingDirectory(), arguments(), provider, buildEnvironment()); }; - const auto taskDone = [=](const AsyncTask &async) { + const auto taskDone = [=](const Async &async) { if (!async.isResultAvailable()) return; const FileNameToContentsHash data = async.result(); @@ -349,7 +345,7 @@ Tasking::TaskItem ProcessExtraCompiler::taskItemImpl(const ContentProvider &prov setContent(it.key(), it.value()); updateCompileTime(); }; - return Tasking::Async(setupTask, taskDone); + return AsyncTask(setupTask, taskDone); } FilePath ProcessExtraCompiler::workingDirectory() const @@ -374,7 +370,7 @@ Tasks ProcessExtraCompiler::parseIssues(const QByteArray &stdErr) return {}; } -void ProcessExtraCompiler::runInThread(QFutureInterface &futureInterface, +void ProcessExtraCompiler::runInThread(QPromise &promise, const FilePath &cmd, const FilePath &workDir, const QStringList &args, const ContentProvider &provider, const Environment &env) @@ -386,7 +382,7 @@ void ProcessExtraCompiler::runInThread(QFutureInterface if (sourceContents.isNull() || !prepareToRun(sourceContents)) return; - QtcProcess process; + Process process; process.setEnvironment(env); if (!workDir.isEmpty()) @@ -397,15 +393,15 @@ void ProcessExtraCompiler::runInThread(QFutureInterface if (!process.waitForStarted()) return; - while (!futureInterface.isCanceled()) { + while (!promise.isCanceled()) { if (process.waitForFinished(200)) break; } - if (futureInterface.isCanceled()) + if (promise.isCanceled()) return; - futureInterface.reportResult(handleProcessFinished(&process)); + promise.addResult(handleProcessFinished(&process)); } } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/extracompiler.h b/src/plugins/projectexplorer/extracompiler.h index 34993f39406..1dbda0e4242 100644 --- a/src/plugins/projectexplorer/extracompiler.h +++ b/src/plugins/projectexplorer/extracompiler.h @@ -10,7 +10,6 @@ #include #include -#include #include #include @@ -21,14 +20,12 @@ QT_BEGIN_NAMESPACE template -class QFutureInterface; +class QPromise; class QThreadPool; QT_END_NAMESPACE -namespace Utils { -class FutureSynchronizer; -class QtcProcess; -} +namespace Tasking { class TaskItem; } +namespace Utils { class Process; } namespace ProjectExplorer { @@ -52,7 +49,7 @@ public: Utils::FilePaths targets() const; void forEachTarget(std::function func) const; - Utils::Tasking::TaskItem compileFileItem(); + Tasking::TaskItem compileFileItem(); void compileFile(); bool isDirty() const; void block(); @@ -64,7 +61,6 @@ signals: protected: static QThreadPool *extraCompilerThreadPool(); - Utils::FutureSynchronizer *futureSynchronizer() const; void setContent(const Utils::FilePath &file, const QByteArray &content); void updateCompileTime(); Utils::Environment buildEnvironment() const; @@ -79,7 +75,7 @@ private: void compileContent(const QByteArray &content); void compileImpl(const ContentProvider &provider); void compileIfDirty(); - virtual Utils::Tasking::TaskItem taskItemImpl(const ContentProvider &provider) = 0; + virtual Tasking::TaskItem taskItemImpl(const ContentProvider &provider) = 0; const std::unique_ptr d; }; @@ -100,13 +96,13 @@ protected: virtual bool prepareToRun(const QByteArray &sourceContents); - virtual FileNameToContentsHash handleProcessFinished(Utils::QtcProcess *process) = 0; + virtual FileNameToContentsHash handleProcessFinished(Utils::Process *process) = 0; virtual Tasks parseIssues(const QByteArray &stdErr); private: - Utils::Tasking::TaskItem taskItemImpl(const ContentProvider &provider) final; - void runInThread(QFutureInterface &futureInterface, + Tasking::TaskItem taskItemImpl(const ContentProvider &provider) final; + void runInThread(QPromise &promise, const Utils::FilePath &cmd, const Utils::FilePath &workDir, const QStringList &args, const ContentProvider &provider, const Utils::Environment &env); diff --git a/src/plugins/projectexplorer/fileinsessionfinder.cpp b/src/plugins/projectexplorer/fileinsessionfinder.cpp index 78f75ce1d52..d8df6c74306 100644 --- a/src/plugins/projectexplorer/fileinsessionfinder.cpp +++ b/src/plugins/projectexplorer/fileinsessionfinder.cpp @@ -4,7 +4,7 @@ #include "fileinsessionfinder.h" #include "project.h" -#include "session.h" +#include "projectmanager.h" #include @@ -30,12 +30,12 @@ private: FileInSessionFinder::FileInSessionFinder() { - connect(SessionManager::instance(), &SessionManager::projectAdded, + connect(ProjectManager::instance(), &ProjectManager::projectAdded, this, [this](const Project *p) { invalidateFinder(); connect(p, &Project::fileListChanged, this, &FileInSessionFinder::invalidateFinder); }); - connect(SessionManager::instance(), &SessionManager::projectRemoved, + connect(ProjectManager::instance(), &ProjectManager::projectRemoved, this, [this](const Project *p) { invalidateFinder(); p->disconnect(this); @@ -45,11 +45,11 @@ FileInSessionFinder::FileInSessionFinder() FilePaths FileInSessionFinder::doFindFile(const FilePath &filePath) { if (!m_finderIsUpToDate) { - m_finder.setProjectDirectory(SessionManager::startupProject() - ? SessionManager::startupProject()->projectDirectory() + m_finder.setProjectDirectory(ProjectManager::startupProject() + ? ProjectManager::startupProject()->projectDirectory() : FilePath()); FilePaths allFiles; - for (const Project * const p : SessionManager::projects()) + for (const Project * const p : ProjectManager::projects()) allFiles << p->files(Project::SourceFiles); m_finder.setProjectFiles(allFiles); m_finderIsUpToDate = true; diff --git a/src/plugins/projectexplorer/filesinallprojectsfind.cpp b/src/plugins/projectexplorer/filesinallprojectsfind.cpp index 720cfbabae8..0f9d6c40e25 100644 --- a/src/plugins/projectexplorer/filesinallprojectsfind.cpp +++ b/src/plugins/projectexplorer/filesinallprojectsfind.cpp @@ -5,7 +5,7 @@ #include "project.h" #include "projectexplorertr.h" -#include "session.h" +#include "projectmanager.h" #include #include @@ -52,7 +52,7 @@ Utils::FileIterator *FilesInAllProjectsFind::files(const QStringList &nameFilter const QVariant &additionalParameters) const { Q_UNUSED(additionalParameters) - const QSet dirs = Utils::transform(SessionManager::projects(), [](Project *p) { + const QSet dirs = Utils::transform(ProjectManager::projects(), [](Project *p) { return p->projectFilePath().parentDir(); }); return new SubDirFileIterator(FilePaths(dirs.constBegin(), dirs.constEnd()), diff --git a/src/plugins/projectexplorer/gcctoolchain.cpp b/src/plugins/projectexplorer/gcctoolchain.cpp index d2449bd69ae..4012fbd6690 100644 --- a/src/plugins/projectexplorer/gcctoolchain.cpp +++ b/src/plugins/projectexplorer/gcctoolchain.cpp @@ -20,8 +20,8 @@ #include #include #include +#include #include -#include #include #include @@ -119,7 +119,7 @@ static QString runGcc(const FilePath &gcc, const QStringList &arguments, const E if (!gcc.isExecutableFile()) return {}; - QtcProcess cpp; + Process cpp; Environment environment(env); environment.setupEnglishOutput(); @@ -196,7 +196,7 @@ HeaderPaths GccToolChain::gccHeaderPaths(const FilePath &gcc, } const FilePath headerPath - = FilePath::fromString(QString::fromUtf8(line)).onDevice(gcc).canonicalPath(); + = gcc.withNewPath(QString::fromUtf8(line)).canonicalPath(); if (!headerPath.isEmpty()) builtInHeaderPaths.append({headerPath, thisHeaderKind}); @@ -569,7 +569,7 @@ WarningFlags GccToolChain::warningFlags(const QStringList &cflags) const return flags; } -QStringList GccToolChain::includedFiles(const QStringList &flags, const QString &directoryPath) const +FilePaths GccToolChain::includedFiles(const QStringList &flags, const FilePath &directoryPath) const { return ToolChain::includedFiles("-include", flags, directoryPath, PossiblyConcatenatedFlag::No); } @@ -1040,10 +1040,9 @@ GccToolChainFactory::GccToolChainFactory() Toolchains GccToolChainFactory::autoDetect(const ToolchainDetector &detector) const { // GCC is almost never what you want on macOS, but it is by default found in /usr/bin - if (HostOsInfo::isMacHost() - && (!detector.device || detector.device->type() == Constants::DESKTOP_DEVICE_TYPE)) { + if (HostOsInfo::isMacHost() && detector.device->type() == Constants::DESKTOP_DEVICE_TYPE) return {}; - } + Toolchains tcs; static const auto tcChecker = [](const ToolChain *tc) { return tc->targetAbi().osFlavor() != Abi::WindowsMSysFlavor @@ -1086,7 +1085,7 @@ static FilePaths findCompilerCandidates(const ToolchainDetector &detector, { const IDevice::ConstPtr device = detector.device; const QFileInfo fi(compilerName); - if (device.isNull() && fi.isAbsolute() && fi.isFile()) + if (device->type() == ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE && fi.isAbsolute() && fi.isFile()) return {FilePath::fromString(compilerName)}; QStringList nameFilters(compilerName); @@ -1385,8 +1384,10 @@ void GccToolChainConfigWidget::setFromToolchain() QSignalBlocker blocker(this); auto tc = static_cast(toolChain()); m_compilerCommand->setFilePath(tc->compilerCommand()); - m_platformCodeGenFlagsLineEdit->setText(ProcessArgs::joinArgs(tc->platformCodeGenFlags())); - m_platformLinkerFlagsLineEdit->setText(ProcessArgs::joinArgs(tc->platformLinkerFlags())); + m_platformCodeGenFlagsLineEdit->setText(ProcessArgs::joinArgs(tc->platformCodeGenFlags(), + HostOsInfo::hostOs())); + m_platformLinkerFlagsLineEdit->setText(ProcessArgs::joinArgs(tc->platformLinkerFlags(), + HostOsInfo::hostOs())); if (m_abiWidget) { m_abiWidget->setAbis(tc->supportedAbis(), tc->targetAbi()); if (!m_isReadOnly && !m_compilerCommand->filePath().toString().isEmpty()) @@ -1569,7 +1570,7 @@ bool ClangToolChain::matchesCompilerCommand(const FilePath &command) const m_resolvedCompilerCommand = FilePath(); if (HostOsInfo::isMacHost() && compilerCommand().parentDir() == FilePath::fromString("/usr/bin")) { - QtcProcess xcrun; + Process xcrun; xcrun.setCommand({"/usr/bin/xcrun", {"-f", compilerCommand().fileName()}}); xcrun.runBlocking(); const FilePath output = FilePath::fromString(xcrun.cleanedStdOut().trimmed()); diff --git a/src/plugins/projectexplorer/gcctoolchain.h b/src/plugins/projectexplorer/gcctoolchain.h index 55ba61e9fb1..1e6353d0a75 100644 --- a/src/plugins/projectexplorer/gcctoolchain.h +++ b/src/plugins/projectexplorer/gcctoolchain.h @@ -55,8 +55,8 @@ public: Utils::LanguageExtensions languageExtensions(const QStringList &cxxflags) const override; Utils::WarningFlags warningFlags(const QStringList &cflags) const override; - QStringList includedFiles(const QStringList &flags, - const QString &directoryPath) const override; + Utils::FilePaths includedFiles(const QStringList &flags, + const Utils::FilePath &directoryPath) const override; MacroInspectionRunner createMacroInspectionRunner() const override; BuiltInHeaderPathsRunner createBuiltInHeaderPathsRunner(const Utils::Environment &env) const override; diff --git a/src/plugins/projectexplorer/ipotentialkit.h b/src/plugins/projectexplorer/ipotentialkit.h index ae032fb9e33..038a3a9fac8 100644 --- a/src/plugins/projectexplorer/ipotentialkit.h +++ b/src/plugins/projectexplorer/ipotentialkit.h @@ -4,18 +4,16 @@ #pragma once #include -#include + #include "projectexplorer_export.h" namespace ProjectExplorer { -class PROJECTEXPLORER_EXPORT IPotentialKit : public QObject +class PROJECTEXPLORER_EXPORT IPotentialKit { - Q_OBJECT - public: IPotentialKit(); - ~IPotentialKit() override; + virtual ~IPotentialKit(); virtual QString displayName() const = 0; virtual void executeFromMenu() = 0; @@ -23,4 +21,4 @@ public: virtual bool isEnabled() const = 0; }; -} +} // ProjectExplorer diff --git a/src/plugins/projectexplorer/jsonwizard/jsonfieldpage.cpp b/src/plugins/projectexplorer/jsonwizard/jsonfieldpage.cpp index 25a897bd799..49a95711893 100644 --- a/src/plugins/projectexplorer/jsonwizard/jsonfieldpage.cpp +++ b/src/plugins/projectexplorer/jsonwizard/jsonfieldpage.cpp @@ -16,10 +16,7 @@ #include #include -#include #include -#include -#include #include #include @@ -29,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -603,25 +599,21 @@ void LineEditField::setupCompletion(FancyLineEdit *lineEdit) using namespace Utils; if (m_completion == Completion::None) return; - ILocatorFilter * const classesFilter = findOrDefault( - ILocatorFilter::allLocatorFilters(), - equal(&ILocatorFilter::id, Id("Classes"))); - if (!classesFilter) - return; - classesFilter->prepareSearch({}); - const auto watcher = new QFutureWatcher; - const auto handleResults = [this, lineEdit, watcher](int firstIndex, int endIndex) { + LocatorMatcher *matcher = new LocatorMatcher; + matcher->setParent(lineEdit); + matcher->setTasks(LocatorMatcher::matchers(MatcherType::Classes)); + const auto handleResults = [lineEdit, matcher, completion = m_completion] { QSet namespaces; QStringList classes; Project * const project = ProjectTree::currentProject(); - for (int i = firstIndex; i < endIndex; ++i) { + const LocatorFilterEntries entries = matcher->outputData(); + for (const LocatorFilterEntry &entry : entries) { static const auto isReservedName = [](const QString &name) { static const QRegularExpression rx1("^_[A-Z].*"); static const QRegularExpression rx2(".*::_[A-Z].*"); return name.contains("__") || rx1.match(name).hasMatch() || rx2.match(name).hasMatch(); }; - const LocatorFilterEntry &entry = watcher->resultAt(i); const bool hasNamespace = !entry.extraInfo.isEmpty() && !entry.extraInfo.startsWith('<') && !entry.extraInfo.contains("::<") && !isReservedName(entry.extraInfo) @@ -635,7 +627,7 @@ void LineEditField::setupCompletion(FancyLineEdit *lineEdit) if (hasNamespace) { if (isBaseClassCandidate) classes << (entry.extraInfo + "::" + entry.displayName); - if (m_completion == Completion::Namespaces) { + if (completion == Completion::Namespaces) { if (!project || entry.filePath.startsWith(project->projectDirectory().toString())) { namespaces << entry.extraInfo; @@ -644,7 +636,7 @@ void LineEditField::setupCompletion(FancyLineEdit *lineEdit) } } QStringList completionList; - if (m_completion == Completion::Namespaces) { + if (completion == Completion::Namespaces) { completionList = toList(namespaces); completionList = filtered(completionList, [&classes](const QString &ns) { return !classes.contains(ns); @@ -658,16 +650,9 @@ void LineEditField::setupCompletion(FancyLineEdit *lineEdit) completionList.sort(); lineEdit->setSpecialCompleter(new QCompleter(completionList, lineEdit)); }; - QObject::connect(watcher, &QFutureWatcher::resultsReadyAt, lineEdit, - handleResults); - QObject::connect(watcher, &QFutureWatcher::finished, - watcher, &QFutureWatcher::deleteLater); - watcher->setFuture(runAsync([classesFilter](QFutureInterface &f) { - const QList matches = classesFilter->matchesFor(f, {}); - if (!matches.isEmpty()) - f.reportResults(QVector(matches.cbegin(), matches.cend())); - f.reportFinished(); - })); + QObject::connect(matcher, &LocatorMatcher::done, lineEdit, handleResults); + QObject::connect(matcher, &LocatorMatcher::done, matcher, &QObject::deleteLater); + matcher->start(); } void LineEditField::setText(const QString &text) diff --git a/src/plugins/projectexplorer/jsonwizard/jsonsummarypage.cpp b/src/plugins/projectexplorer/jsonwizard/jsonsummarypage.cpp index 024044d7a0a..ec55e3218fd 100644 --- a/src/plugins/projectexplorer/jsonwizard/jsonsummarypage.cpp +++ b/src/plugins/projectexplorer/jsonwizard/jsonsummarypage.cpp @@ -8,8 +8,8 @@ #include "../projectexplorerconstants.h" #include "../projectexplorertr.h" #include "../projectnodes.h" +#include "../projectmanager.h" #include "../projecttree.h" -#include "../session.h" #include #include @@ -209,7 +209,7 @@ Node *JsonSummaryPage::findWizardContextNode(Node *contextNode) const // Static cast from void * to avoid qobject_cast (which needs a valid object) in value(). auto project = static_cast(m_wizard->value(Constants::PROJECT_POINTER).value()); - if (SessionManager::projects().contains(project) && project->rootProjectNode()) { + if (ProjectManager::projects().contains(project) && project->rootProjectNode()) { const FilePath path = FilePath::fromVariant(m_wizard->value(Constants::PREFERRED_PROJECT_NODE_PATH)); contextNode = project->rootProjectNode()->findNode([path](const Node *n) { return path == n->filePath(); diff --git a/src/plugins/projectexplorer/kitchooser.cpp b/src/plugins/projectexplorer/kitchooser.cpp index 14c848da740..9e45cacd7dd 100644 --- a/src/plugins/projectexplorer/kitchooser.cpp +++ b/src/plugins/projectexplorer/kitchooser.cpp @@ -6,7 +6,7 @@ #include "kitmanager.h" #include "projectexplorerconstants.h" #include "projectexplorertr.h" -#include "session.h" +#include "projectmanager.h" #include "target.h" #include @@ -88,7 +88,7 @@ void KitChooser::populate() const Id lastKit = Id::fromSetting(ICore::settings()->value(lastKitKey)); bool didActivate = false; - if (Target *target = SessionManager::startupTarget()) { + if (Target *target = ProjectManager::startupTarget()) { Kit *kit = target->kit(); if (m_kitPredicate(kit)) { QString display = Tr::tr("Kit of Active Project: %1").arg(kitText(kit)); diff --git a/src/plugins/projectexplorer/kitinformation.cpp b/src/plugins/projectexplorer/kitinformation.cpp index dcb490b70dc..fc8039b402d 100644 --- a/src/plugins/projectexplorer/kitinformation.cpp +++ b/src/plugins/projectexplorer/kitinformation.cpp @@ -34,7 +34,6 @@ #include using namespace Utils; -using namespace Utils::Layouting; namespace ProjectExplorer { @@ -65,7 +64,7 @@ public: private: void makeReadOnly() override { m_chooser->setReadOnly(true); } - void addToLayout(LayoutBuilder &builder) override + void addToLayout(Layouting::LayoutItem &builder) override { addMutableAction(m_chooser); builder.addItem(Layouting::Span(2, m_chooser)); @@ -142,7 +141,7 @@ void SysRootKitAspect::addToMacroExpander(Kit *kit, MacroExpander *expander) con }); } -Id SysRootKitAspect::id() +Utils::Id SysRootKitAspect::id() { return "PE.Profile.SysRoot"; } @@ -231,7 +230,7 @@ public: } private: - void addToLayout(LayoutBuilder &builder) override + void addToLayout(Layouting::LayoutItem &builder) override { addMutableAction(m_mainWidget); builder.addItem(m_mainWidget); @@ -760,7 +759,7 @@ public: ~DeviceTypeKitAspectWidget() override { delete m_comboBox; } private: - void addToLayout(LayoutBuilder &builder) override + void addToLayout(Layouting::LayoutItem &builder) override { addMutableAction(m_comboBox); builder.addItem(m_comboBox); @@ -795,7 +794,7 @@ DeviceTypeKitAspect::DeviceTypeKitAspect() { setObjectName(QLatin1String("DeviceTypeInformation")); setId(DeviceTypeKitAspect::id()); - setDisplayName(Tr::tr("Device type")); + setDisplayName(Tr::tr("Run device type")); setDescription(Tr::tr("The type of device to run applications on.")); setPriority(33000); makeEssential(); @@ -896,7 +895,7 @@ public: } private: - void addToLayout(LayoutBuilder &builder) override + void addToLayout(Layouting::LayoutItem &builder) override { addMutableAction(m_comboBox); builder.addItem(m_comboBox); @@ -942,7 +941,7 @@ DeviceKitAspect::DeviceKitAspect() { setObjectName(QLatin1String("DeviceInformation")); setId(DeviceKitAspect::id()); - setDisplayName(Tr::tr("Device")); + setDisplayName(Tr::tr("Run device")); setDescription(Tr::tr("The device to run the applications on.")); setPriority(32000); @@ -1023,30 +1022,29 @@ KitAspect::ItemList DeviceKitAspect::toUserOutput(const Kit *k) const void DeviceKitAspect::addToMacroExpander(Kit *kit, MacroExpander *expander) const { QTC_ASSERT(kit, return); - expander->registerVariable("Device:HostAddress", Tr::tr("Host address"), - [kit]() -> QString { - const IDevice::ConstPtr device = DeviceKitAspect::device(kit); - return device ? device->sshParameters().host() : QString(); + expander->registerVariable("Device:HostAddress", Tr::tr("Host address"), [kit] { + const IDevice::ConstPtr device = DeviceKitAspect::device(kit); + return device ? device->sshParameters().host() : QString(); }); - expander->registerVariable("Device:SshPort", Tr::tr("SSH port"), - [kit]() -> QString { - const IDevice::ConstPtr device = DeviceKitAspect::device(kit); - return device ? QString::number(device->sshParameters().port()) : QString(); + expander->registerVariable("Device:SshPort", Tr::tr("SSH port"), [kit] { + const IDevice::ConstPtr device = DeviceKitAspect::device(kit); + return device ? QString::number(device->sshParameters().port()) : QString(); }); - expander->registerVariable("Device:UserName", Tr::tr("User name"), - [kit]() -> QString { - const IDevice::ConstPtr device = DeviceKitAspect::device(kit); - return device ? device->sshParameters().userName() : QString(); + expander->registerVariable("Device:UserName", Tr::tr("User name"), [kit] { + const IDevice::ConstPtr device = DeviceKitAspect::device(kit); + return device ? device->sshParameters().userName() : QString(); }); - expander->registerVariable("Device:KeyFile", Tr::tr("Private key file"), - [kit]() -> QString { - const IDevice::ConstPtr device = DeviceKitAspect::device(kit); - return device ? device->sshParameters().privateKeyFile.toString() : QString(); + expander->registerVariable("Device:KeyFile", Tr::tr("Private key file"), [kit] { + const IDevice::ConstPtr device = DeviceKitAspect::device(kit); + return device ? device->sshParameters().privateKeyFile.toString() : QString(); }); - expander->registerVariable("Device:Name", Tr::tr("Device name"), - [kit]() -> QString { - const IDevice::ConstPtr device = DeviceKitAspect::device(kit); - return device ? device->displayName() : QString(); + expander->registerVariable("Device:Name", Tr::tr("Device name"), [kit] { + const IDevice::ConstPtr device = DeviceKitAspect::device(kit); + return device ? device->displayName() : QString(); + }); + expander->registerFileVariables("Device::Root", Tr::tr("Device root directory"), [kit] { + const IDevice::ConstPtr device = DeviceKitAspect::device(kit); + return device ? device->rootPath() : FilePath{}; }); } @@ -1157,7 +1155,7 @@ public: } private: - void addToLayout(LayoutBuilder &builder) override + void addToLayout(Layouting::LayoutItem &builder) override { addMutableAction(m_comboBox); builder.addItem(m_comboBox); @@ -1266,31 +1264,31 @@ KitAspect::ItemList BuildDeviceKitAspect::toUserOutput(const Kit *k) const void BuildDeviceKitAspect::addToMacroExpander(Kit *kit, MacroExpander *expander) const { QTC_ASSERT(kit, return); - expander->registerVariable("BuildDevice:HostAddress", Tr::tr("Build host address"), - [kit]() -> QString { - const IDevice::ConstPtr device = BuildDeviceKitAspect::device(kit); - return device ? device->sshParameters().host() : QString(); + expander->registerVariable("BuildDevice:HostAddress", Tr::tr("Build host address"), [kit] { + const IDevice::ConstPtr device = BuildDeviceKitAspect::device(kit); + return device ? device->sshParameters().host() : QString(); }); - expander->registerVariable("BuildDevice:SshPort", Tr::tr("Build SSH port"), - [kit]() -> QString { - const IDevice::ConstPtr device = BuildDeviceKitAspect::device(kit); - return device ? QString::number(device->sshParameters().port()) : QString(); + expander->registerVariable("BuildDevice:SshPort", Tr::tr("Build SSH port"), [kit] { + const IDevice::ConstPtr device = BuildDeviceKitAspect::device(kit); + return device ? QString::number(device->sshParameters().port()) : QString(); }); - expander->registerVariable("BuildDevice:UserName", Tr::tr("Build user name"), - [kit]() -> QString { - const IDevice::ConstPtr device = BuildDeviceKitAspect::device(kit); - return device ? device->sshParameters().userName() : QString(); + expander->registerVariable("BuildDevice:UserName", Tr::tr("Build user name"), [kit] { + const IDevice::ConstPtr device = BuildDeviceKitAspect::device(kit); + return device ? device->sshParameters().userName() : QString(); }); - expander->registerVariable("BuildDevice:KeyFile", Tr::tr("Build private key file"), - [kit]() -> QString { - const IDevice::ConstPtr device = BuildDeviceKitAspect::device(kit); - return device ? device->sshParameters().privateKeyFile.toString() : QString(); + expander->registerVariable("BuildDevice:KeyFile", Tr::tr("Build private key file"), [kit] { + const IDevice::ConstPtr device = BuildDeviceKitAspect::device(kit); + return device ? device->sshParameters().privateKeyFile.toString() : QString(); }); - expander->registerVariable("BuildDevice:Name", Tr::tr("Build device name"), - [kit]() -> QString { - const IDevice::ConstPtr device = BuildDeviceKitAspect::device(kit); - return device ? device->displayName() : QString(); + expander->registerVariable("BuildDevice:Name", Tr::tr("Build device name"), [kit] { + const IDevice::ConstPtr device = BuildDeviceKitAspect::device(kit); + return device ? device->displayName() : QString(); }); + expander + ->registerFileVariables("BuildDevice::Root", Tr::tr("Build device root directory"), [kit] { + const IDevice::ConstPtr device = BuildDeviceKitAspect::device(kit); + return device ? device->rootPath() : FilePath{}; + }); } Id BuildDeviceKitAspect::id() @@ -1388,7 +1386,7 @@ public: } private: - void addToLayout(LayoutBuilder &builder) override + void addToLayout(Layouting::LayoutItem &builder) override { addMutableAction(m_mainWidget); builder.addItem(m_mainWidget); diff --git a/src/plugins/projectexplorer/kitmanager.cpp b/src/plugins/projectexplorer/kitmanager.cpp index cb67e11fac0..86a3b533c15 100644 --- a/src/plugins/projectexplorer/kitmanager.cpp +++ b/src/plugins/projectexplorer/kitmanager.cpp @@ -747,7 +747,7 @@ KitAspectWidget::~KitAspectWidget() delete m_mutableAction; } -void KitAspectWidget::addToLayoutWithLabel(QWidget *parent) +void KitAspectWidget::addToLayoutWithLabel(Layouting::LayoutItem &parentItem, QWidget *parent) { QTC_ASSERT(parent, return); auto label = createSubWidget(m_kitInformation->displayName() + ':'); @@ -756,10 +756,9 @@ void KitAspectWidget::addToLayoutWithLabel(QWidget *parent) emit labelLinkActivated(link); }); - Layouting::LayoutExtender builder(parent->layout(), Layouting::WithFormAlignment); - builder.finishRow(); - builder.addItem(label); - addToLayout(builder); + parentItem.addItem(label); + addToLayout(parentItem); + parentItem.addItem(Layouting::br); } void KitAspectWidget::addMutableAction(QWidget *child) diff --git a/src/plugins/projectexplorer/kitmanager.h b/src/plugins/projectexplorer/kitmanager.h index a494fde3237..f25f83ca7f4 100644 --- a/src/plugins/projectexplorer/kitmanager.h +++ b/src/plugins/projectexplorer/kitmanager.h @@ -117,7 +117,7 @@ public: virtual void makeReadOnly() = 0; virtual void refresh() = 0; - void addToLayoutWithLabel(QWidget *parent); + void addToLayoutWithLabel(Layouting::LayoutItem &parentItem, QWidget *parent); static QString msgManage(); diff --git a/src/plugins/projectexplorer/kitmanagerconfigwidget.cpp b/src/plugins/projectexplorer/kitmanagerconfigwidget.cpp index 908238d3a7f..822844f57d0 100644 --- a/src/plugins/projectexplorer/kitmanagerconfigwidget.cpp +++ b/src/plugins/projectexplorer/kitmanagerconfigwidget.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -36,12 +37,14 @@ using namespace Utils; namespace ProjectExplorer { namespace Internal { -KitManagerConfigWidget::KitManagerConfigWidget(Kit *k) : +KitManagerConfigWidget::KitManagerConfigWidget(Kit *k, bool &isDefaultKit, bool &hasUniqueName) : m_iconButton(new QToolButton), m_nameEdit(new QLineEdit), m_fileSystemFriendlyNameLineEdit(new QLineEdit), m_kit(k), - m_modifiedKit(std::make_unique(Utils::Id(WORKING_COPY_KIT_ID))) + m_modifiedKit(std::make_unique(Utils::Id(WORKING_COPY_KIT_ID))), + m_isDefaultKit(isDefaultKit), + m_hasUniqueName(hasUniqueName) { setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); @@ -64,10 +67,13 @@ KitManagerConfigWidget::KitManagerConfigWidget(Kit *k) : this, &KitManagerConfigWidget::setFileSystemFriendlyName); using namespace Layouting; - Grid { + Grid page { + withFormAlignment, + columnStretch(1, 2), label, m_nameEdit, m_iconButton, br, - fsLabel, m_fileSystemFriendlyNameLineEdit - }.attachTo(this, WithFormAlignment); + fsLabel, m_fileSystemFriendlyNameLineEdit, br, + noMargin + }; m_iconButton->setToolTip(Tr::tr("Kit icon.")); auto setIconAction = new QAction(Tr::tr("Select Icon..."), this); @@ -97,7 +103,9 @@ KitManagerConfigWidget::KitManagerConfigWidget(Kit *k) : chooser->addMacroExpanderProvider([this] { return m_modifiedKit->macroExpander(); }); for (KitAspect *aspect : KitManager::kitAspects()) - addAspectToWorkingCopy(aspect); + addAspectToWorkingCopy(page, aspect); + + page.attachTo(this); updateVisibility(); @@ -187,14 +195,14 @@ QString KitManagerConfigWidget::validityMessage() const return m_modifiedKit->toHtml(tmp); } -void KitManagerConfigWidget::addAspectToWorkingCopy(KitAspect *aspect) +void KitManagerConfigWidget::addAspectToWorkingCopy(Layouting::LayoutItem &parent, KitAspect *aspect) { QTC_ASSERT(aspect, return); KitAspectWidget *widget = aspect->createConfigWidget(workingCopy()); QTC_ASSERT(widget, return); QTC_ASSERT(!m_widgets.contains(widget), return); - widget->addToLayoutWithLabel(this); + widget->addToLayoutWithLabel(parent, this); m_widgets.append(widget); connect(widget->mutableAction(), &QAction::toggled, @@ -213,11 +221,6 @@ void KitManagerConfigWidget::updateVisibility() } } -void KitManagerConfigWidget::setHasUniqueName(bool unique) -{ - m_hasUniqueName = unique; -} - void KitManagerConfigWidget::makeStickySubWidgetsReadOnly() { for (KitAspectWidget *w : std::as_const(m_widgets)) { @@ -231,31 +234,11 @@ Kit *KitManagerConfigWidget::workingCopy() const return m_modifiedKit.get(); } -bool KitManagerConfigWidget::configures(Kit *k) const -{ - return m_kit == k; -} - -void KitManagerConfigWidget::setIsDefaultKit(bool d) -{ - if (m_isDefaultKit == d) - return; - m_isDefaultKit = d; - emit dirty(); -} - bool KitManagerConfigWidget::isDefaultKit() const { return m_isDefaultKit; } -void KitManagerConfigWidget::removeKit() -{ - if (!m_kit) - return; - KitManager::deregisterKit(m_kit); -} - void KitManagerConfigWidget::setIcon() { const Utils::Id deviceType = DeviceTypeKitAspect::deviceTypeId(m_modifiedKit.get()); diff --git a/src/plugins/projectexplorer/kitmanagerconfigwidget.h b/src/plugins/projectexplorer/kitmanagerconfigwidget.h index 142eb8fa364..94218c5de8a 100644 --- a/src/plugins/projectexplorer/kitmanagerconfigwidget.h +++ b/src/plugins/projectexplorer/kitmanagerconfigwidget.h @@ -25,7 +25,7 @@ class KitManagerConfigWidget : public QWidget Q_OBJECT public: - explicit KitManagerConfigWidget(Kit *k); + explicit KitManagerConfigWidget(Kit *k, bool &isDefaultKit, bool &hasUniqueName); ~KitManagerConfigWidget() override; QString displayName() const; @@ -35,19 +35,14 @@ public: void discard(); bool isDirty() const; QString validityMessage() const; - void addAspectToWorkingCopy(KitAspect *aspect); + void addAspectToWorkingCopy(Layouting::LayoutItem &parent, KitAspect *aspect); void makeStickySubWidgetsReadOnly(); Kit *workingCopy() const; - bool configures(Kit *k) const; bool isRegistering() const { return m_isRegistering; } - void setIsDefaultKit(bool d); bool isDefaultKit() const; - void removeKit(); void updateVisibility(); - void setHasUniqueName(bool unique); - signals: void dirty(); void isAutoDetectedChanged(); @@ -74,9 +69,9 @@ private: QList m_widgets; Kit *m_kit; std::unique_ptr m_modifiedKit; - bool m_isDefaultKit = false; + bool &m_isDefaultKit; bool m_fixingKit = false; - bool m_hasUniqueName = true; + bool &m_hasUniqueName; bool m_isRegistering = false; mutable QString m_cachedDisplayName; }; diff --git a/src/plugins/projectexplorer/kitmodel.cpp b/src/plugins/projectexplorer/kitmodel.cpp index 4b250f23c98..12f23fb2819 100644 --- a/src/plugins/projectexplorer/kitmodel.cpp +++ b/src/plugins/projectexplorer/kitmodel.cpp @@ -24,57 +24,122 @@ namespace Internal { class KitNode : public TreeItem { public: - KitNode(Kit *k, KitModel *m) - { - widget = new KitManagerConfigWidget(k); + KitNode(Kit *k, KitModel *m, QBoxLayout *parentLayout) + : m_kit(k), m_model(m), m_parentLayout(parentLayout) + {} - QObject::connect(widget, &KitManagerConfigWidget::dirty, m, [this] { update(); }); + ~KitNode() override { delete m_widget; } - QObject::connect(widget, &KitManagerConfigWidget::isAutoDetectedChanged, m, [this, m] { - TreeItem *oldParent = parent(); - TreeItem *newParent = - m->rootItem()->childAt(widget->workingCopy()->isAutoDetected() ? 0 : 1); - if (oldParent && oldParent != newParent) { - m->takeItem(this); - newParent->appendChild(this); - } - }); - } - - ~KitNode() override - { - delete widget; - } + Kit *kit() const { return m_kit; } QVariant data(int, int role) const override { - if (widget) { - if (role == Qt::FontRole) { - QFont f = QApplication::font(); - if (widget->isDirty()) - f.setBold(!f.bold()); - if (widget->isDefaultKit()) - f.setItalic(f.style() != QFont::StyleItalic); - return f; - } - if (role == Qt::DisplayRole) { - QString baseName = widget->displayName(); - if (widget->isDefaultKit()) - //: Mark up a kit as the default one. - baseName = Tr::tr("%1 (default)").arg(baseName); - return baseName; - } - if (role == Qt::DecorationRole) { - return widget->displayIcon(); - } - if (role == Qt::ToolTipRole) { - return widget->validityMessage(); - } + if (role == Qt::FontRole) { + QFont f = QApplication::font(); + if (isDirty()) + f.setBold(!f.bold()); + if (isDefaultKit()) + f.setItalic(f.style() != QFont::StyleItalic); + return f; } - return QVariant(); + if (role == Qt::DisplayRole) { + QString baseName = displayName(); + if (isDefaultKit()) + //: Mark up a kit as the default one. + baseName = Tr::tr("%1 (default)").arg(baseName); + return baseName; + } + + if (role == Qt::DecorationRole) + return displayIcon(); + + if (role == Qt::ToolTipRole) + return widget()->validityMessage(); + + return {}; } - KitManagerConfigWidget *widget; + bool isDirty() const + { + if (m_widget) + return m_widget->isDirty(); + return false; + } + + QIcon displayIcon() const + { + if (m_widget) + return m_widget->displayIcon(); + return m_kit->displayIcon(); + } + + QString displayName() const + { + if (m_widget) + return m_widget->displayName(); + return m_kit->displayName(); + } + + bool isDefaultKit() const + { + return m_isDefaultKit; + } + + bool isRegistering() const + { + if (m_widget) + return m_widget->isRegistering(); + return false; + } + + void setIsDefaultKit(bool on) + { + if (m_isDefaultKit == on) + return; + m_isDefaultKit = on; + if (m_widget) + emit m_widget->dirty(); + } + + KitManagerConfigWidget *widget() const + { + const_cast(this)->ensureWidget(); + return m_widget; + } + + void setHasUniqueName(bool on) + { + m_hasUniqueName = on; + } + +private: + void ensureWidget() + { + if (m_widget) + return; + + m_widget = new KitManagerConfigWidget(m_kit, m_isDefaultKit, m_hasUniqueName); + + QObject::connect(m_widget, &KitManagerConfigWidget::dirty, m_model, [this] { update(); }); + + QObject::connect(m_widget, &KitManagerConfigWidget::isAutoDetectedChanged, m_model, [this] { + TreeItem *oldParent = parent(); + TreeItem *newParent = + m_model->rootItem()->childAt(m_widget->workingCopy()->isAutoDetected() ? 0 : 1); + if (oldParent && oldParent != newParent) { + m_model->takeItem(this); + newParent->appendChild(this); + } + }); + m_parentLayout->addWidget(m_widget); + } + + Kit *m_kit = m_kit; + KitModel *m_model = nullptr; + KitManagerConfigWidget *m_widget = nullptr; + QBoxLayout *m_parentLayout = nullptr; + bool m_isDefaultKit = false; + bool m_hasUniqueName = true; }; // -------------------------------------------------------------------------- @@ -113,7 +178,7 @@ KitModel::KitModel(QBoxLayout *parentLayout, QObject *parent) Kit *KitModel::kit(const QModelIndex &index) { KitNode *n = kitNode(index); - return n ? n->widget->workingCopy() : nullptr; + return n ? n->widget()->workingCopy() : nullptr; } KitNode *KitModel::kitNode(const QModelIndex &index) @@ -136,20 +201,20 @@ void KitModel::setDefaultKit(const QModelIndex &index) bool KitModel::isDefaultKit(Kit *k) const { - return m_defaultNode && m_defaultNode->widget->workingCopy() == k; + return m_defaultNode && m_defaultNode->widget()->workingCopy() == k; } KitManagerConfigWidget *KitModel::widget(const QModelIndex &index) { KitNode *n = kitNode(index); - return n ? n->widget : nullptr; + return n ? n->widget() : nullptr; } void KitModel::validateKitNames() { QHash nameHash; forItemsAtLevel<2>([&nameHash](KitNode *n) { - const QString displayName = n->widget->displayName(); + const QString displayName = n->displayName(); if (nameHash.contains(displayName)) ++nameHash[displayName]; else @@ -157,8 +222,8 @@ void KitModel::validateKitNames() }); forItemsAtLevel<2>([&nameHash](KitNode *n) { - const QString displayName = n->widget->displayName(); - n->widget->setHasUniqueName(nameHash.value(displayName) == 1); + const QString displayName = n->displayName(); + n->setHasUniqueName(nameHash.value(displayName) == 1); }); } @@ -166,8 +231,8 @@ void KitModel::apply() { // Add/update dirty nodes before removing kits. This ensures the right kit ends up as default. forItemsAtLevel<2>([](KitNode *n) { - if (n->widget->isDirty()) { - n->widget->apply(); + if (n->isDirty()) { + n->widget()->apply(); n->update(); } }); @@ -175,7 +240,7 @@ void KitModel::apply() // Remove unused kits: const QList removeList = m_toRemoveList; for (KitNode *n : removeList) - n->widget->removeKit(); + KitManager::deregisterKit(n->kit()); emit layoutChanged(); // Force update. } @@ -197,7 +262,7 @@ void KitModel::markForRemoval(Kit *k) setDefaultNode(findItemAtLevel<2>([node](KitNode *kn) { return kn != node; })); takeItem(node); - if (node->widget->configures(nullptr)) + if (node->kit() == nullptr) delete node; else m_toRemoveList.append(node); @@ -209,7 +274,7 @@ Kit *KitModel::markForAddition(Kit *baseKit) const QString newName = newKitName(baseKit ? baseKit->unexpandedDisplayName() : QString()); KitNode *node = createNode(nullptr); m_manualRoot->appendChild(node); - Kit *k = node->widget->workingCopy(); + Kit *k = node->widget()->workingCopy(); KitGuard g(k); if (baseKit) { k->copyFrom(baseKit); @@ -229,7 +294,7 @@ Kit *KitModel::markForAddition(Kit *baseKit) void KitModel::updateVisibility() { forItemsAtLevel<2>([](const TreeItem *ti) { - static_cast(ti)->widget->updateVisibility(); + static_cast(ti)->widget()->updateVisibility(); }); } @@ -237,32 +302,31 @@ QString KitModel::newKitName(const QString &sourceName) const { QList allKits; forItemsAtLevel<2>([&allKits](const TreeItem *ti) { - allKits << static_cast(ti)->widget->workingCopy(); + allKits << static_cast(ti)->widget()->workingCopy(); }); return Kit::newKitName(sourceName, allKits); } KitNode *KitModel::findWorkingCopy(Kit *k) const { - return findItemAtLevel<2>([k](KitNode *n) { return n->widget->workingCopy() == k; }); + return findItemAtLevel<2>([k](KitNode *n) { return n->widget()->workingCopy() == k; }); } KitNode *KitModel::createNode(Kit *k) { - auto node = new KitNode(k, this); - m_parentLayout->addWidget(node->widget); + auto node = new KitNode(k, this, m_parentLayout); return node; } void KitModel::setDefaultNode(KitNode *node) { if (m_defaultNode) { - m_defaultNode->widget->setIsDefaultKit(false); + m_defaultNode->setIsDefaultKit(false); m_defaultNode->update(); } m_defaultNode = node; if (m_defaultNode) { - m_defaultNode->widget->setIsDefaultKit(true); + m_defaultNode->setIsDefaultKit(true); m_defaultNode->update(); } } @@ -271,7 +335,7 @@ void KitModel::addKit(Kit *k) { for (TreeItem *n : *m_manualRoot) { // Was added by us - if (static_cast(n)->widget->isRegistering()) + if (static_cast(n)->isRegistering()) return; } @@ -292,7 +356,7 @@ void KitModel::removeKit(Kit *k) { QList nodes = m_toRemoveList; for (KitNode *n : std::as_const(nodes)) { - if (n->widget->configures(k)) { + if (n->kit() == k) { m_toRemoveList.removeOne(n); if (m_defaultNode == n) m_defaultNode = nullptr; @@ -303,7 +367,7 @@ void KitModel::removeKit(Kit *k) } KitNode *node = findItemAtLevel<2>([k](KitNode *n) { - return n->widget->configures(k); + return n->kit() == k; }); if (node == m_defaultNode) @@ -319,7 +383,7 @@ void KitModel::changeDefaultKit() { Kit *defaultKit = KitManager::defaultKit(); KitNode *node = findItemAtLevel<2>([defaultKit](KitNode *n) { - return n->widget->configures(defaultKit); + return n->kit() == defaultKit; }); setDefaultNode(node); } diff --git a/src/plugins/projectexplorer/kitoptionspage.cpp b/src/plugins/projectexplorer/kitoptionspage.cpp index d5c709194a1..db578a403e6 100644 --- a/src/plugins/projectexplorer/kitoptionspage.cpp +++ b/src/plugins/projectexplorer/kitoptionspage.cpp @@ -23,11 +23,13 @@ namespace ProjectExplorer { namespace Internal { -// -------------------------------------------------------------------------- -// KitOptionsPageWidget: -// -------------------------------------------------------------------------- +// KitOptionsPageWidget -class KitOptionsPageWidget : public QWidget +class KitOptionsPageWidget; + +static QPointer kitOptionsPageWidget; + +class KitOptionsPageWidget : public Core::IOptionsPageWidget { public: KitOptionsPageWidget(); @@ -42,6 +44,8 @@ public: void makeDefaultKit(); void updateState(); + void apply() final { m_model->apply(); } + public: QTreeView *m_kitsView = nullptr; QPushButton *m_addButton = nullptr; @@ -58,6 +62,7 @@ public: KitOptionsPageWidget::KitOptionsPageWidget() { + kitOptionsPageWidget = this; m_kitsView = new QTreeView(this); m_kitsView->setUniformRowHeights(true); m_kitsView->header()->setStretchLastSection(true); @@ -239,38 +244,14 @@ QModelIndex KitOptionsPageWidget::currentIndex() const // KitOptionsPage: // -------------------------------------------------------------------------- -static KitOptionsPage *theKitOptionsPage = nullptr; - KitOptionsPage::KitOptionsPage() { - theKitOptionsPage = this; setId(Constants::KITS_SETTINGS_PAGE_ID); setDisplayName(Tr::tr("Kits")); setCategory(Constants::KITS_SETTINGS_CATEGORY); setDisplayCategory(Tr::tr("Kits")); setCategoryIconPath(":/projectexplorer/images/settingscategory_kits.png"); -} - -QWidget *KitOptionsPage::widget() -{ - if (!m_widget) - m_widget = new Internal::KitOptionsPageWidget; - - return m_widget; -} - -void KitOptionsPage::apply() -{ - if (m_widget) - m_widget->m_model->apply(); -} - -void KitOptionsPage::finish() -{ - if (m_widget) { - delete m_widget; - m_widget = nullptr; - } + setWidgetCreator([] { return new Internal::KitOptionsPageWidget; }); } void KitOptionsPage::showKit(Kit *k) @@ -278,18 +259,16 @@ void KitOptionsPage::showKit(Kit *k) if (!k) return; - (void) widget(); - QModelIndex index = m_widget->m_model->indexOf(k); - m_widget->m_selectionModel->select(index, + Internal::KitOptionsPageWidget *widget = Internal::kitOptionsPageWidget; + if (!widget) + return; + + QModelIndex index = widget->m_model->indexOf(k); + widget->m_selectionModel->select(index, QItemSelectionModel::Clear | QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); - m_widget->m_kitsView->scrollTo(index); -} - -KitOptionsPage *KitOptionsPage::instance() -{ - return theKitOptionsPage; + widget->m_kitsView->scrollTo(index); } } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/kitoptionspage.h b/src/plugins/projectexplorer/kitoptionspage.h index 5ce589cd6b2..a7a4ba29dbc 100644 --- a/src/plugins/projectexplorer/kitoptionspage.h +++ b/src/plugins/projectexplorer/kitoptionspage.h @@ -7,12 +7,8 @@ #include -#include - namespace ProjectExplorer { -namespace Internal { class KitOptionsPageWidget; } - class Kit; class PROJECTEXPLORER_EXPORT KitOptionsPage : public Core::IOptionsPage @@ -20,15 +16,7 @@ class PROJECTEXPLORER_EXPORT KitOptionsPage : public Core::IOptionsPage public: KitOptionsPage(); - QWidget *widget() override; - void apply() override; - void finish() override; - - void showKit(Kit *k); - static KitOptionsPage *instance(); - -private: - QPointer m_widget; + static void showKit(Kit *k); }; } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/localenvironmentaspect.cpp b/src/plugins/projectexplorer/localenvironmentaspect.cpp deleted file mode 100644 index 7510d636e5f..00000000000 --- a/src/plugins/projectexplorer/localenvironmentaspect.cpp +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "localenvironmentaspect.h" - -#include "buildconfiguration.h" -#include "kit.h" -#include "projectexplorertr.h" -#include "target.h" - -using namespace Utils; - -namespace ProjectExplorer { - -LocalEnvironmentAspect::LocalEnvironmentAspect(Target *target, bool includeBuildEnvironment) -{ - setIsLocal(true); - addSupportedBaseEnvironment(Tr::tr("Clean Environment"), {}); - - addSupportedBaseEnvironment(Tr::tr("System Environment"), [] { - return Environment::systemEnvironment(); - }); - - if (includeBuildEnvironment) { - addPreferredBaseEnvironment(Tr::tr("Build Environment"), [target] { - Environment env; - if (BuildConfiguration *bc = target->activeBuildConfiguration()) { - env = bc->environment(); - } else { // Fallback for targets without buildconfigurations: - env = target->kit()->buildEnvironment(); - } - return env; - }); - - connect(target, - &Target::activeBuildConfigurationChanged, - this, - &EnvironmentAspect::environmentChanged); - connect(target, - &Target::buildEnvironmentChanged, - this, - &EnvironmentAspect::environmentChanged); - } -} - -} // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/localenvironmentaspect.h b/src/plugins/projectexplorer/localenvironmentaspect.h deleted file mode 100644 index ffe2556947e..00000000000 --- a/src/plugins/projectexplorer/localenvironmentaspect.h +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "environmentaspect.h" - -namespace ProjectExplorer { - -class PROJECTEXPLORER_EXPORT LocalEnvironmentAspect : public EnvironmentAspect -{ - Q_OBJECT - -public: - explicit LocalEnvironmentAspect(Target *parent, bool includeBuildEnvironment = true); -}; - -} // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/makestep.cpp b/src/plugins/projectexplorer/makestep.cpp index 57ebc2490b3..ee9ffea832b 100644 --- a/src/plugins/projectexplorer/makestep.cpp +++ b/src/plugins/projectexplorer/makestep.cpp @@ -18,13 +18,10 @@ #include #include #include -#include +#include #include #include -#include -#include -#include #include #include @@ -49,9 +46,8 @@ MakeStep::MakeStep(BuildStepList *parent, Id id) setCommandLineProvider([this] { return effectiveMakeCommand(Execution); }); - m_makeCommandAspect = addAspect(); + m_makeCommandAspect = addAspect(); m_makeCommandAspect->setSettingsKey(id.withSuffix(MAKE_COMMAND_SUFFIX).toString()); - m_makeCommandAspect->setDisplayStyle(StringAspect::PathChooserDisplay); m_makeCommandAspect->setExpectedKind(PathChooser::ExistingCommand); m_makeCommandAspect->setBaseFileName(PathChooser::homePath()); m_makeCommandAspect->setHistoryCompleter("PE.MakeCommand.History"); @@ -318,14 +314,15 @@ CommandLine MakeStep::effectiveMakeCommand(MakeCommandType type) const QWidget *MakeStep::createConfigWidget() { Layouting::Form builder; - builder.addRow(m_makeCommandAspect); - builder.addRow(m_userArgumentsAspect); + builder.addRow({m_makeCommandAspect}); + builder.addRow({m_userArgumentsAspect}); builder.addRow({m_userJobCountAspect, m_overrideMakeflagsAspect, m_nonOverrideWarning}); if (m_disablingForSubDirsSupported) - builder.addRow(m_disabledForSubdirsAspect); - builder.addRow(m_buildTargetsAspect); + builder.addRow({m_disabledForSubdirsAspect}); + builder.addRow({m_buildTargetsAspect}); + builder.addItem(Layouting::noMargin); - auto widget = builder.emerge(Layouting::WithoutMargins); + auto widget = builder.emerge(); VariableChooser::addSupportForChildWidgets(widget, macroExpander()); @@ -383,22 +380,6 @@ QWidget *MakeStep::createConfigWidget() return widget; } -bool MakeStep::buildsTarget(const QString &target) const -{ - return m_buildTargetsAspect->value().contains(target); -} - -void MakeStep::setBuildTarget(const QString &target, bool on) -{ - QStringList old = m_buildTargetsAspect->value(); - if (on && !old.contains(target)) - old << target; - else if (!on && old.contains(target)) - old.removeOne(target); - - m_buildTargetsAspect->setValue(old); -} - QStringList MakeStep::availableTargets() const { return m_buildTargetsAspect->allValues(); diff --git a/src/plugins/projectexplorer/makestep.h b/src/plugins/projectexplorer/makestep.h index b08462db2ff..2f12894ea61 100644 --- a/src/plugins/projectexplorer/makestep.h +++ b/src/plugins/projectexplorer/makestep.h @@ -55,11 +55,6 @@ public: Utils::Environment makeEnvironment() const; - // FIXME: All unused, remove in 4.15. - void setBuildTarget(const QString &buildTarget) { setSelectedBuildTarget(buildTarget); } - bool buildsTarget(const QString &target) const; - void setBuildTarget(const QString &target, bool on); - protected: void supportDisablingForSubdirs() { m_disablingForSubDirsSupported = true; } virtual QStringList displayArguments() const; @@ -78,7 +73,6 @@ private: QStringList jobArguments() const; Utils::MultiSelectionAspect *m_buildTargetsAspect = nullptr; - QStringList m_availableTargets; // FIXME: Unused, remove in 4.15. Utils::StringAspect *m_makeCommandAspect = nullptr; Utils::StringAspect *m_userArgumentsAspect = nullptr; Utils::IntegerAspect *m_userJobCountAspect = nullptr; diff --git a/src/plugins/projectexplorer/miniprojecttargetselector.cpp b/src/plugins/projectexplorer/miniprojecttargetselector.cpp index b169697cd8c..3a1b01b724b 100644 --- a/src/plugins/projectexplorer/miniprojecttargetselector.cpp +++ b/src/plugins/projectexplorer/miniprojecttargetselector.cpp @@ -13,8 +13,8 @@ #include "projectexplorerconstants.h" #include "projectexplorericons.h" #include "projectexplorertr.h" +#include "projectmanager.h" #include "runconfiguration.h" -#include "session.h" #include "target.h" #include @@ -32,6 +32,7 @@ #include #include +#include #include #include #include @@ -141,8 +142,15 @@ private: static bool compareItems(const TreeItem *ti1, const TreeItem *ti2) { - const int result = caseFriendlyCompare(static_cast(ti1)->rawDisplayName(), - static_cast(ti2)->rawDisplayName()); + static const QCollator collator = [] { + QCollator collator; + collator.setNumericMode(true); + collator.setCaseSensitivity(Qt::CaseInsensitive); + return collator; + }(); + + const int result = collator.compare(static_cast(ti1)->rawDisplayName(), + static_cast(ti2)->rawDisplayName()); if (result != 0) return result < 0; return ti1 < ti2; @@ -253,9 +261,9 @@ public: explicit ProjectListView(QWidget *parent = nullptr) : SelectorView(parent) { const auto model = new GenericModel(this); - model->rebuild(transform>(SessionManager::projects(), + model->rebuild(transform>(ProjectManager::projects(), [](Project *p) { return p; })); - connect(SessionManager::instance(), &SessionManager::projectAdded, + connect(ProjectManager::instance(), &ProjectManager::projectAdded, this, [this, model](Project *project) { const GenericItem *projectItem = model->addItemForObject(project); QFontMetrics fn(font()); @@ -264,7 +272,7 @@ public: setOptimalWidth(width); restoreCurrentIndex(); }); - connect(SessionManager::instance(), &SessionManager::aboutToRemoveProject, + connect(ProjectManager::instance(), &ProjectManager::aboutToRemoveProject, this, [this, model](const Project *project) { GenericItem * const item = model->itemForObject(project); if (!item) @@ -272,7 +280,7 @@ public: model->destroyItem(item); resetOptimalWidth(); }); - connect(SessionManager::instance(), &SessionManager::startupProjectChanged, + connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, this, [this, model](const Project *project) { const GenericItem * const item = model->itemForObject(project); if (item) @@ -288,7 +296,7 @@ public: this, [model](const QModelIndex &index) { const GenericItem * const item = model->itemForIndex(index); if (item && item->object()) - SessionManager::setStartupProject(qobject_cast(item->object())); + ProjectManager::setStartupProject(qobject_cast(item->object())); }); } @@ -296,7 +304,7 @@ private: void restoreCurrentIndex() { const GenericItem * const itemForStartupProject - = theModel()->itemForObject(SessionManager::startupProject()); + = theModel()->itemForObject(ProjectManager::startupProject()); if (itemForStartupProject) setCurrentIndex(theModel()->indexForItem(itemForStartupProject)); } @@ -389,6 +397,12 @@ private: TreeView::mouseReleaseEvent(event); } + void showEvent(QShowEvent* event) override + { + scrollTo(currentIndex()); + TreeView::showEvent(event); + } + QObject *objectAt(const QModelIndex &index) const { return theModel()->itemForIndex(index)->object(); @@ -551,6 +565,11 @@ int SelectorView::padding() ///////// // KitAreaWidget ///////// +void doLayout(KitAspectWidget *widget, Layouting::LayoutItem &builder) +{ + widget->addToLayout(builder); +} + class KitAreaWidget : public QWidget { Q_OBJECT @@ -573,18 +592,15 @@ public: delete layout(); - Layouting::LayoutBuilder builder(Layouting::LayoutBuilder::GridLayout); + Layouting::Grid grid; for (KitAspect *aspect : KitManager::kitAspects()) { if (k && k->isMutable(aspect->id())) { KitAspectWidget *widget = aspect->createConfigWidget(k); m_widgets << widget; - QLabel *label = new QLabel(aspect->displayName()); - builder.addItem(label); - widget->addToLayout(builder); - builder.finishRow(); + grid.addItems({aspect->displayName(), widget, Layouting::br}); } } - builder.attachTo(this); + grid.attachTo(this); layout()->setContentsMargins(3, 3, 3, 3); m_kit = k; @@ -653,7 +669,7 @@ MiniProjectTargetSelector::MiniProjectTargetSelector(QAction *targetSelectorActi QWidget(parent), m_projectAction(targetSelectorAction) { - setProperty("panelwidget", true); + StyleHelper::setPanelWidget(this); setContentsMargins(QMargins(0, 1, 1, 8)); setWindowFlags(Qt::Popup); @@ -696,22 +712,22 @@ MiniProjectTargetSelector::MiniProjectTargetSelector(QAction *targetSelectorActi m_listWidgets[RUN]->viewport()->setAttribute(Qt::WA_Hover); // Validate state: At this point the session is still empty! - Project *startup = SessionManager::startupProject(); + Project *startup = ProjectManager::startupProject(); QTC_CHECK(!startup); - QTC_CHECK(SessionManager::projects().isEmpty()); + QTC_CHECK(ProjectManager::projects().isEmpty()); connect(m_summaryLabel, &QLabel::linkActivated, this, &MiniProjectTargetSelector::switchToProjectsMode); - SessionManager *sessionManager = SessionManager::instance(); - connect(sessionManager, &SessionManager::startupProjectChanged, + ProjectManager *sessionManager = ProjectManager::instance(); + connect(sessionManager, &ProjectManager::startupProjectChanged, this, &MiniProjectTargetSelector::changeStartupProject); - connect(sessionManager, &SessionManager::projectAdded, + connect(sessionManager, &ProjectManager::projectAdded, this, &MiniProjectTargetSelector::projectAdded); - connect(sessionManager, &SessionManager::projectRemoved, + connect(sessionManager, &ProjectManager::projectRemoved, this, &MiniProjectTargetSelector::projectRemoved); - connect(sessionManager, &SessionManager::projectDisplayNameChanged, + connect(sessionManager, &ProjectManager::projectDisplayNameChanged, this, &MiniProjectTargetSelector::updateActionAndSummary); // for icon changes: @@ -720,17 +736,17 @@ MiniProjectTargetSelector::MiniProjectTargetSelector(QAction *targetSelectorActi connect(m_listWidgets[TARGET], &GenericListWidget::changeActiveProjectConfiguration, this, [this](QObject *pc) { - SessionManager::setActiveTarget(m_project, static_cast(pc), SetActive::Cascade); + m_project->setActiveTarget(static_cast(pc), SetActive::Cascade); }); connect(m_listWidgets[BUILD], &GenericListWidget::changeActiveProjectConfiguration, this, [this](QObject *pc) { - SessionManager::setActiveBuildConfiguration(m_project->activeTarget(), - static_cast(pc), SetActive::Cascade); + m_project->activeTarget()->setActiveBuildConfiguration( + static_cast(pc), SetActive::Cascade); }); connect(m_listWidgets[DEPLOY], &GenericListWidget::changeActiveProjectConfiguration, this, [this](QObject *pc) { - SessionManager::setActiveDeployConfiguration(m_project->activeTarget(), - static_cast(pc), SetActive::Cascade); + m_project->activeTarget()->setActiveDeployConfiguration( + static_cast(pc), SetActive::Cascade); }); connect(m_listWidgets[RUN], &GenericListWidget::changeActiveProjectConfiguration, this, [this](QObject *pc) { @@ -881,7 +897,7 @@ void MiniProjectTargetSelector::doLayout(bool keepSize) onlySummary = true; } else { if (visibleLineCount < 3) { - if (Utils::anyOf(SessionManager::projects(), &Project::needsConfiguration)) + if (Utils::anyOf(ProjectManager::projects(), &Project::needsConfiguration)) visibleLineCount = 3; } if (visibleLineCount) @@ -1126,7 +1142,7 @@ void MiniProjectTargetSelector::removedRunConfiguration(RunConfiguration *rc, bo void MiniProjectTargetSelector::updateProjectListVisible() { - int count = SessionManager::projects().size(); + int count = ProjectManager::projects().size(); bool visible = count > 1; m_projectListWidget->setVisible(visible); @@ -1139,7 +1155,7 @@ void MiniProjectTargetSelector::updateProjectListVisible() void MiniProjectTargetSelector::updateTargetListVisible() { int maxCount = 0; - for (Project *p : SessionManager::projects()) + for (Project *p : ProjectManager::projects()) maxCount = qMax(p->targets().size(), maxCount); bool visible = maxCount > 1; @@ -1152,7 +1168,7 @@ void MiniProjectTargetSelector::updateTargetListVisible() void MiniProjectTargetSelector::updateBuildListVisible() { int maxCount = 0; - for (Project *p : SessionManager::projects()) { + for (Project *p : ProjectManager::projects()) { const QList targets = p->targets(); for (Target *t : targets) maxCount = qMax(t->buildConfigurations().size(), maxCount); @@ -1168,7 +1184,7 @@ void MiniProjectTargetSelector::updateBuildListVisible() void MiniProjectTargetSelector::updateDeployListVisible() { int maxCount = 0; - for (Project *p : SessionManager::projects()) { + for (Project *p : ProjectManager::projects()) { const QList targets = p->targets(); for (Target *t : targets) maxCount = qMax(t->deployConfigurations().size(), maxCount); @@ -1184,7 +1200,7 @@ void MiniProjectTargetSelector::updateDeployListVisible() void MiniProjectTargetSelector::updateRunListVisible() { int maxCount = 0; - for (Project *p : SessionManager::projects()) { + for (Project *p : ProjectManager::projects()) { const QList targets = p->targets(); for (Target *t : targets) maxCount = qMax(t->runConfigurations().size(), maxCount); @@ -1460,10 +1476,10 @@ void MiniProjectTargetSelector::updateActionAndSummary() ? Icons::DESKTOP_DEVICE.icon() : style()->standardIcon(QStyle::SP_ComputerIcon); - Project *project = SessionManager::startupProject(); + Project *project = ProjectManager::startupProject(); if (project) { projectName = project->displayName(); - for (Project *p : SessionManager::projects()) { + for (Project *p : ProjectManager::projects()) { if (p != project && p->displayName() == projectName) { fileName = project->projectFilePath().toUserOutput(); break; @@ -1515,7 +1531,7 @@ void MiniProjectTargetSelector::updateActionAndSummary() void MiniProjectTargetSelector::updateSummary() { QString summary; - if (Project *startupProject = SessionManager::startupProject()) { + if (Project *startupProject = ProjectManager::startupProject()) { if (!m_projectListWidget->isVisibleTo(this)) summary.append(Tr::tr("Project: %1
").arg(startupProject->displayName())); if (Target *activeTarget = startupProject->activeTarget()) { diff --git a/src/plugins/projectexplorer/msvcparser.cpp b/src/plugins/projectexplorer/msvcparser.cpp index 89c59443e1b..8452afcc5ab 100644 --- a/src/plugins/projectexplorer/msvcparser.cpp +++ b/src/plugins/projectexplorer/msvcparser.cpp @@ -61,7 +61,7 @@ static Task handleNmakeJomMessage(const QString &line) CompileTask task(type, line.mid(matchLength).trimmed()); task.details << line; - return std::move(task); + return task; } static Task::TaskType taskType(const QString &category) diff --git a/src/plugins/projectexplorer/msvctoolchain.cpp b/src/plugins/projectexplorer/msvctoolchain.cpp index d0856e29d44..d4d518de44d 100644 --- a/src/plugins/projectexplorer/msvctoolchain.cpp +++ b/src/plugins/projectexplorer/msvctoolchain.cpp @@ -3,6 +3,7 @@ #include "msvctoolchain.h" +#include "devicesupport/idevice.h" #include "gcctoolchain.h" #include "msvcparser.h" #include "projectexplorer.h" @@ -14,12 +15,12 @@ #include #include +#include #include #include #include +#include #include -#include -#include #include #include @@ -254,7 +255,7 @@ static std::optional detectCppBuildTools2017() static QVector detectVisualStudioFromVsWhere(const QString &vswhere) { QVector installations; - QtcProcess vsWhereProcess; + Process vsWhereProcess; vsWhereProcess.setCodec(QTextCodec::codecForName("UTF-8")); const int timeoutS = 5; vsWhereProcess.setTimeoutS(timeoutS); @@ -648,7 +649,7 @@ Macros MsvcToolChain::msvcPredefinedMacros(const QStringList &cxxflags, qWarning("%s: %s", Q_FUNC_INFO, qPrintable(saver.errorString())); return predefinedMacros; } - Utils::QtcProcess cpp; + Utils::Process cpp; cpp.setEnvironment(env); cpp.setWorkingDirectory(Utils::TemporaryDirectory::masterDirectoryFilePath()); QStringList arguments; @@ -747,10 +748,8 @@ static QString winExpandDelayedEnvReferences(QString in, const Utils::Environmen return in; } -void MsvcToolChain::environmentModifications( - QFutureInterface &future, - QString vcvarsBat, - QString varsBatArg) +void MsvcToolChain::environmentModifications(QPromise &promise, + QString vcvarsBat, QString varsBatArg) { const Utils::Environment inEnv = Utils::Environment::systemEnvironment(); Utils::Environment outEnv; @@ -776,7 +775,7 @@ void MsvcToolChain::environmentModifications( } } - future.reportResult({error, diff}); + promise.addResult(MsvcToolChain::GenerateEnvResult{error, diff}); } void MsvcToolChain::initEnvModWatcher(const QFuture &future) @@ -1004,10 +1003,8 @@ bool MsvcToolChain::fromMap(const QVariantMap &data) data.value(QLatin1String(environModsKeyC)).toList()); rescanForCompiler(); - initEnvModWatcher(Utils::runAsync(envModThreadPool(), - &MsvcToolChain::environmentModifications, - m_vcvarsBat, - m_varsBatArg)); + initEnvModWatcher(Utils::asyncRun(envModThreadPool(), &MsvcToolChain::environmentModifications, + m_vcvarsBat, m_varsBatArg)); const bool valid = !m_vcvarsBat.isEmpty() && targetAbi().isValid(); if (!valid) @@ -1128,8 +1125,8 @@ WarningFlags MsvcToolChain::warningFlags(const QStringList &cflags) const return flags; } -QStringList MsvcToolChain::includedFiles(const QStringList &flags, - const QString &directoryPath) const +FilePaths MsvcToolChain::includedFiles(const QStringList &flags, + const FilePath &directoryPath) const { return ToolChain::includedFiles("/FI", flags, directoryPath, PossiblyConcatenatedFlag::Yes); } @@ -1236,10 +1233,8 @@ void MsvcToolChain::setupVarsBat(const Abi &abi, const QString &varsBat, const Q m_varsBatArg = varsBatArg; if (!varsBat.isEmpty()) { - initEnvModWatcher(Utils::runAsync(envModThreadPool(), - &MsvcToolChain::environmentModifications, - varsBat, - varsBatArg)); + initEnvModWatcher(Utils::asyncRun(envModThreadPool(), + &MsvcToolChain::environmentModifications, varsBat, varsBatArg)); } } @@ -1560,7 +1555,7 @@ static QVersionNumber clangClVersion(const FilePath &clangClPath) if (!dllversion.isEmpty()) return QVersionNumber::fromString(dllversion); - QtcProcess clangClProcess; + Process clangClProcess; clangClProcess.setCommand({clangClPath, {"--version"}}); clangClProcess.runBlocking(); if (clangClProcess.result() != ProcessResult::FinishedWithSuccess) @@ -1777,7 +1772,7 @@ Macros ClangClToolChain::msvcPredefinedMacros(const QStringList &cxxflags, if (!cxxflags.contains("--driver-mode=g++")) return MsvcToolChain::msvcPredefinedMacros(cxxflags, env); - QtcProcess cpp; + Process cpp; cpp.setEnvironment(env); cpp.setWorkingDirectory(Utils::TemporaryDirectory::masterDirectoryFilePath()); @@ -1915,7 +1910,7 @@ static void detectCppBuildTools2015(Toolchains *list) Toolchains MsvcToolChainFactory::autoDetect(const ToolchainDetector &detector) const { - if (!detector.device.isNull()) { + if (detector.device->type() != ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE) { // FIXME currently no support for msvc toolchains on a device return {}; } @@ -2030,7 +2025,7 @@ bool ClangClToolChainFactory::canCreate() const Toolchains ClangClToolChainFactory::autoDetect(const ToolchainDetector &detector) const { - if (!detector.device.isNull()) { + if (detector.device->type() != ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE) { // FIXME currently no support for msvc toolchains on a device return {}; } @@ -2127,7 +2122,7 @@ std::optional MsvcToolChain::generateEnvironmentSettings(const Utils::E return QString(); } - Utils::QtcProcess run; + Utils::Process run; // As of WinSDK 7.1, there is logic preventing the path from being set // correctly if "ORIGINALPATH" is already set. That can cause problems diff --git a/src/plugins/projectexplorer/msvctoolchain.h b/src/plugins/projectexplorer/msvctoolchain.h index 042bbfe38f8..68f8b9dcf19 100644 --- a/src/plugins/projectexplorer/msvctoolchain.h +++ b/src/plugins/projectexplorer/msvctoolchain.h @@ -55,8 +55,8 @@ public: MacroInspectionRunner createMacroInspectionRunner() const override; Utils::LanguageExtensions languageExtensions(const QStringList &cxxflags) const override; Utils::WarningFlags warningFlags(const QStringList &cflags) const override; - QStringList includedFiles(const QStringList &flags, - const QString &directoryPath) const override; + Utils::FilePaths includedFiles(const QStringList &flags, + const Utils::FilePath &directoryPath) const override; BuiltInHeaderPathsRunner createBuiltInHeaderPathsRunner( const Utils::Environment &env) const override; void addToEnvironment(Utils::Environment &env) const override; @@ -81,6 +81,8 @@ public: const QString &batchFile, const QString &batchArgs, QMap &envPairs); + bool environmentInitialized() const { return !m_environmentModifications.isEmpty(); } + protected: class WarningFlagAdder { @@ -111,9 +113,8 @@ protected: std::optional error; Utils::EnvironmentItems environmentItems; }; - static void environmentModifications(QFutureInterface &future, - QString vcvarsBat, - QString varsBatArg); + static void environmentModifications(QPromise &future, + QString vcvarsBat, QString varsBatArg); void initEnvModWatcher(const QFuture &future); protected: diff --git a/src/plugins/projectexplorer/processparameters.cpp b/src/plugins/projectexplorer/processparameters.cpp index 49b50dccb3a..0b3d6d83c7a 100644 --- a/src/plugins/projectexplorer/processparameters.cpp +++ b/src/plugins/projectexplorer/processparameters.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include diff --git a/src/plugins/projectexplorer/processstep.cpp b/src/plugins/projectexplorer/processstep.cpp index 3ced1622d0d..a34ff10b611 100644 --- a/src/plugins/projectexplorer/processstep.cpp +++ b/src/plugins/projectexplorer/processstep.cpp @@ -34,9 +34,8 @@ public: ProcessStep::ProcessStep(BuildStepList *bsl, Id id) : AbstractProcessStep(bsl, id) { - auto command = addAspect(); + auto command = addAspect(); command->setSettingsKey(PROCESS_COMMAND_KEY); - command->setDisplayStyle(StringAspect::PathChooserDisplay); command->setLabelText(Tr::tr("Command:")); command->setExpectedKind(PathChooser::Command); command->setHistoryCompleter("PE.ProcessStepCommand.History"); @@ -46,10 +45,9 @@ ProcessStep::ProcessStep(BuildStepList *bsl, Id id) arguments->setDisplayStyle(StringAspect::LineEditDisplay); arguments->setLabelText(Tr::tr("Arguments:")); - auto workingDirectory = addAspect(); + auto workingDirectory = addAspect(); workingDirectory->setSettingsKey(PROCESS_WORKINGDIRECTORY_KEY); workingDirectory->setValue(Constants::DEFAULT_WORKING_DIR); - workingDirectory->setDisplayStyle(StringAspect::PathChooserDisplay); workingDirectory->setLabelText(Tr::tr("Working directory:")); workingDirectory->setExpectedKind(PathChooser::Directory); diff --git a/src/plugins/projectexplorer/project.cpp b/src/plugins/projectexplorer/project.cpp index fbd4f9e28fd..c021d9b1a64 100644 --- a/src/plugins/projectexplorer/project.cpp +++ b/src/plugins/projectexplorer/project.cpp @@ -11,15 +11,17 @@ #include "environmentaspect.h" #include "kit.h" #include "kitinformation.h" +#include "msvctoolchain.h" #include "projectexplorer.h" #include "projectexplorerconstants.h" #include "projectexplorertr.h" +#include "projectmanager.h" #include "projectnodes.h" #include "runconfiguration.h" #include "runconfigurationaspects.h" -#include "session.h" #include "target.h" #include "taskhub.h" +#include "toolchainmanager.h" #include "userfileaccessor.h" #include @@ -273,7 +275,7 @@ void Project::addTarget(std::unique_ptr &&t) // check activeTarget: if (!activeTarget()) - SessionManager::setActiveTarget(this, pointer, SetActive::Cascade); + setActiveTarget(pointer, SetActive::Cascade); } Target *Project::addTargetForDefaultKit() @@ -309,7 +311,7 @@ bool Project::removeTarget(Target *target) auto keep = take(d->m_targets, target); if (target == d->m_activeTarget) { Target *newActiveTarget = (d->m_targets.size() == 0 ? nullptr : d->m_targets.at(0).get()); - SessionManager::setActiveTarget(this, newActiveTarget, SetActive::Cascade); + setActiveTarget(newActiveTarget, SetActive::Cascade); } emit removedTarget(target); @@ -326,7 +328,7 @@ Target *Project::activeTarget() const return d->m_activeTarget; } -void Project::setActiveTarget(Target *target) +void Project::setActiveTargetHelper(Target *target) { if (d->m_activeTarget == target) return; @@ -414,6 +416,29 @@ Target *Project::target(Kit *k) const return findOrDefault(d->m_targets, equal(&Target::kit, k)); } +void Project::setActiveTarget(Target *target, SetActive cascade) +{ + if (isShuttingDown()) + return; + + setActiveTargetHelper(target); + + if (!target) // never cascade setting no target + return; + + if (cascade != SetActive::Cascade || !ProjectManager::isProjectConfigurationCascading()) + return; + + Utils::Id kitId = target->kit()->id(); + for (Project *otherProject : ProjectManager::projects()) { + if (otherProject == this) + continue; + if (Target *otherTarget = Utils::findOrDefault(otherProject->targets(), + [kitId](Target *t) { return t->kit()->id() == kitId; })) + otherProject->setActiveTargetHelper(otherTarget); + } +} + Tasks Project::projectIssues(const Kit *k) const { Tasks result; @@ -445,12 +470,12 @@ bool Project::copySteps(Target *sourceTarget, Target *newTarget) sourceBc->buildSystem()->name())); newTarget->addBuildConfiguration(newBc); if (sourceTarget->activeBuildConfiguration() == sourceBc) - SessionManager::setActiveBuildConfiguration(newTarget, newBc, SetActive::NoCascade); + newTarget->setActiveBuildConfiguration(newBc, SetActive::NoCascade); } if (!newTarget->activeBuildConfiguration()) { QList bcs = newTarget->buildConfigurations(); if (!bcs.isEmpty()) - SessionManager::setActiveBuildConfiguration(newTarget, bcs.first(), SetActive::NoCascade); + newTarget->setActiveBuildConfiguration(bcs.first(), SetActive::NoCascade); } for (DeployConfiguration *sourceDc : sourceTarget->deployConfigurations()) { @@ -462,12 +487,12 @@ bool Project::copySteps(Target *sourceTarget, Target *newTarget) newDc->setDisplayName(sourceDc->displayName()); newTarget->addDeployConfiguration(newDc); if (sourceTarget->activeDeployConfiguration() == sourceDc) - SessionManager::setActiveDeployConfiguration(newTarget, newDc, SetActive::NoCascade); + newTarget->setActiveDeployConfiguration(newDc, SetActive::NoCascade); } if (!newTarget->activeBuildConfiguration()) { QList dcs = newTarget->deployConfigurations(); if (!dcs.isEmpty()) - SessionManager::setActiveDeployConfiguration(newTarget, dcs.first(), SetActive::NoCascade); + newTarget->setActiveDeployConfiguration(dcs.first(), SetActive::NoCascade); } for (RunConfiguration *sourceRc : sourceTarget->runConfigurations()) { @@ -846,6 +871,34 @@ const Node *Project::nodeForFilePath(const FilePath &filePath, return nullptr; } +FilePaths Project::binariesForSourceFile(const FilePath &sourceFile) const +{ + if (!rootProjectNode()) + return {}; + const QList fileNodes = rootProjectNode()->findNodes([&sourceFile](Node *n) { + return n->filePath() == sourceFile; + }); + FilePaths binaries; + for (const Node * const fileNode : fileNodes) { + for (ProjectNode *projectNode = fileNode->parentProjectNode(); projectNode; + projectNode = projectNode->parentProjectNode()) { + if (!projectNode->isProduct()) + continue; + if (projectNode->productType() == ProductType::App + || projectNode->productType() == ProductType::Lib) { + const QList binaryNodes = projectNode->findNodes([](Node *n) { + return n->asFileNode() && (n->asFileNode()->fileType() == FileType::App + || n->asFileNode()->fileType() == FileType::Lib); + + }); + binaries << Utils::transform(binaryNodes, &Node::filePath); + } + break; + } + } + return binaries; +} + void Project::setProjectLanguages(Context language) { if (d->m_projectLanguages == language) @@ -1130,7 +1183,7 @@ void Project::addVariablesToMacroExpander(const QByteArray &prefix, }); expander->registerVariable(fullPrefix + "Kit:Name", //: %1 is something like "Active project" - Tr::tr("%1: The name the active kit.").arg(descriptor), + Tr::tr("%1: The name of the active kit.").arg(descriptor), [targetGetter]() -> QString { if (const Target *const target = targetGetter()) return target->kit()->displayName(); @@ -1435,8 +1488,7 @@ void ProjectExplorerPlugin::testProject_multipleBuildConfigs() Target * const target = theProject.project()->activeTarget(); QVERIFY(target); QCOMPARE(target->buildConfigurations().size(), 6); - SessionManager::setActiveBuildConfiguration(target, target->buildConfigurations().at(1), - SetActive::Cascade); + target->setActiveBuildConfiguration(target->buildConfigurations().at(1), SetActive::Cascade); BuildSystem * const bs = theProject.project()->activeTarget()->buildSystem(); QVERIFY(bs); QCOMPARE(bs, target->activeBuildConfiguration()->buildSystem()); @@ -1452,12 +1504,94 @@ void ProjectExplorerPlugin::testProject_multipleBuildConfigs() } QVERIFY(!bs->isWaitingForParse() && !bs->isParsing()); - QCOMPARE(SessionManager::startupProject(), theProject.project()); + QCOMPARE(ProjectManager::startupProject(), theProject.project()); QCOMPARE(ProjectTree::currentProject(), theProject.project()); QVERIFY(EditorManager::openEditor(projectDir.pathAppended("main.cpp"))); QVERIFY(ProjectTree::currentNode()); ProjectTree::instance()->expandAll(); - SessionManager::closeAllProjects(); // QTCREATORBUG-25655 + ProjectManager::closeAllProjects(); // QTCREATORBUG-25655 +} + +void ProjectExplorerPlugin::testSourceToBinaryMapping() +{ + // Find suitable kit. + Kit * const kit = findOr(KitManager::kits(), nullptr, [](const Kit *k) { + return k->isValid() && ToolChainKitAspect::cxxToolChain(k); + }); + if (!kit) + QSKIP("The test requires at least one kit with a toolchain."); + + const auto toolchain = ToolChainKitAspect::cxxToolChain(kit); + QVERIFY(toolchain); + if (const auto msvcToolchain = dynamic_cast(toolchain)) { + while (!msvcToolchain->environmentInitialized()) { + QSignalSpy parsingFinishedSpy(ToolChainManager::instance(), + &ToolChainManager::toolChainUpdated); + QVERIFY(parsingFinishedSpy.wait(10000)); + } + } + + // Copy project from qrc. + QTemporaryDir * const tempDir = TemporaryDirectory::masterTemporaryDirectory(); + QVERIFY(tempDir->isValid()); + const FilePath projectDir = FilePath::fromString(tempDir->path() + "/multi-target-project"); + if (!projectDir.exists()) { + const auto result = FilePath(":/projectexplorer/testdata/multi-target-project") + .copyRecursively(projectDir); + QVERIFY2(result, qPrintable(result.error())); + const QFileInfoList files = QDir(projectDir.toString()).entryInfoList(QDir::Files); + for (const QFileInfo &f : files) + QFile(f.absoluteFilePath()).setPermissions(f.permissions() | QFile::WriteUser); + } + + // Load Project. + QFETCH(QString, projectFileName); + const auto theProject = openProject(projectDir.pathAppended(projectFileName)); + if (theProject.errorMessage().contains("text/")) { + QSKIP("This test requires the presence of the qmake/cmake/qbs project managers " + "to be fully functional"); + } + + QVERIFY2(theProject, qPrintable(theProject.errorMessage())); + theProject.project()->configureAsExampleProject(kit); + QCOMPARE(theProject.project()->targets().size(), 1); + Target * const target = theProject.project()->activeTarget(); + QVERIFY(target); + BuildSystem * const bs = target->buildSystem(); + QVERIFY(bs); + QCOMPARE(bs, target->activeBuildConfiguration()->buildSystem()); + if (bs->isWaitingForParse() || bs->isParsing()) { + QSignalSpy parsingFinishedSpy(bs, &BuildSystem::parsingFinished); + QVERIFY(parsingFinishedSpy.wait(10000)); + } + QVERIFY(!bs->isWaitingForParse() && !bs->isParsing()); + + if (QLatin1String(QTest::currentDataTag()) == QLatin1String("qbs")) { + BuildManager::buildProjectWithoutDependencies(theProject.project()); + if (BuildManager::isBuilding()) { + QSignalSpy buildingFinishedSpy(BuildManager::instance(), &BuildManager::buildQueueFinished); + QVERIFY(buildingFinishedSpy.wait(10000)); + } + QVERIFY(!BuildManager::isBuilding()); + QSignalSpy projectUpdateSpy(theProject.project(), &Project::fileListChanged); + QVERIFY(projectUpdateSpy.wait(5000)); + } + + // Check mapping + const auto binariesForSource = [&](const QString &fileName) { + return theProject.project()->binariesForSourceFile(projectDir.pathAppended(fileName)); + }; + QCOMPARE(binariesForSource("multi-target-project-main.cpp").size(), 1); + QCOMPARE(binariesForSource("multi-target-project-lib.cpp").size(), 1); + QCOMPARE(binariesForSource("multi-target-project-shared.h").size(), 2); +} + +void ProjectExplorerPlugin::testSourceToBinaryMapping_data() +{ + QTest::addColumn("projectFileName"); + QTest::addRow("cmake") << "CMakeLists.txt"; + QTest::addRow("qbs") << "multi-target-project.qbs"; + QTest::addRow("qmake") << "multi-target-project.pro"; } #endif // WITH_TESTS diff --git a/src/plugins/projectexplorer/project.h b/src/plugins/projectexplorer/project.h index ed336b5f7f3..832fc3c9423 100644 --- a/src/plugins/projectexplorer/project.h +++ b/src/plugins/projectexplorer/project.h @@ -36,11 +36,11 @@ class ProjectImporter; class ProjectNode; class ProjectPrivate; class Target; +enum class SetActive : int; // Documentation inside. class PROJECTEXPLORER_EXPORT Project : public QObject { - friend class SessionManager; // for setActiveTarget Q_OBJECT public: @@ -89,6 +89,8 @@ public: Target *activeTarget() const; Target *target(Utils::Id id) const; Target *target(Kit *k) const; + void setActiveTarget(Target *target, SetActive cascade); + virtual Tasks projectIssues(const Kit *k) const; static bool copySteps(Target *sourceTarget, Target *newTarget); @@ -107,6 +109,7 @@ public: bool isKnownFile(const Utils::FilePath &filename) const; const Node *nodeForFilePath(const Utils::FilePath &filePath, const NodeMatcher &extraMatcher = {}) const; + Utils::FilePaths binariesForSourceFile(const Utils::FilePath &sourceFile) const; virtual QVariantMap toMap() const; @@ -226,7 +229,7 @@ private: void removeProjectLanguage(Utils::Id id); void handleSubTreeChanged(FolderNode *node); - void setActiveTarget(Target *target); + void setActiveTargetHelper(Target *target); friend class ContainerNode; ProjectPrivate *d; diff --git a/src/plugins/projectexplorer/projectconfiguration.cpp b/src/plugins/projectexplorer/projectconfiguration.cpp index 6d5fa797a67..841be317312 100644 --- a/src/plugins/projectexplorer/projectconfiguration.cpp +++ b/src/plugins/projectexplorer/projectconfiguration.cpp @@ -19,11 +19,9 @@ const char DISPLAY_NAME_KEY[] = "ProjectExplorer.ProjectConfiguration.DisplayNam // ProjectConfiguration ProjectConfiguration::ProjectConfiguration(QObject *parent, Utils::Id id) - : QObject(parent) + : AspectContainer(parent) , m_id(id) { - m_aspects.setOwnsSubAspects(true); - QTC_CHECK(parent); QTC_CHECK(id.isValid()); setObjectName(id.toString()); @@ -89,7 +87,7 @@ QVariantMap ProjectConfiguration::toMap() const QVariantMap map; map.insert(QLatin1String(CONFIGURATION_ID_KEY), m_id.toSetting()); m_displayName.toMap(map, DISPLAY_NAME_KEY); - m_aspects.toMap(map); + AspectContainer::toMap(map); return map; } @@ -106,15 +104,10 @@ bool ProjectConfiguration::fromMap(const QVariantMap &map) QTC_ASSERT(id.toString().startsWith(m_id.toString()), return false); m_displayName.fromMap(map, DISPLAY_NAME_KEY); - m_aspects.fromMap(map); + AspectContainer::fromMap(map); return true; } -BaseAspect *ProjectConfiguration::aspect(Id id) const -{ - return m_aspects.aspect(id); -} - FilePath ProjectConfiguration::mapFromBuildDeviceToGlobalPath(const FilePath &path) const { IDevice::ConstPtr dev = BuildDeviceKitAspect::device(kit()); diff --git a/src/plugins/projectexplorer/projectconfiguration.h b/src/plugins/projectexplorer/projectconfiguration.h index cb716bf66e5..8c5dac1d902 100644 --- a/src/plugins/projectexplorer/projectconfiguration.h +++ b/src/plugins/projectexplorer/projectconfiguration.h @@ -21,7 +21,7 @@ class Kit; class Project; class Target; -class PROJECTEXPLORER_EXPORT ProjectConfiguration : public QObject +class PROJECTEXPLORER_EXPORT ProjectConfiguration : public Utils::AspectContainer { Q_OBJECT @@ -54,26 +54,12 @@ public: static QString settingsIdKey(); - template - Aspect *addAspect(Args && ...args) - { - return m_aspects.addAspect(std::forward(args)...); - } - - const Utils::AspectContainer &aspects() const { return m_aspects; } - - Utils::BaseAspect *aspect(Utils::Id id) const; - template T *aspect() const { return m_aspects.aspect(); } - Utils::FilePath mapFromBuildDeviceToGlobalPath(const Utils::FilePath &path) const; signals: void displayNameChanged(); void toolTipChanged(); -protected: - Utils::AspectContainer m_aspects; - private: QPointer m_target; const Utils::Id m_id; diff --git a/src/plugins/projectexplorer/projectexplorer.cpp b/src/plugins/projectexplorer/projectexplorer.cpp index f76f10175b6..d313cb433de 100644 --- a/src/plugins/projectexplorer/projectexplorer.cpp +++ b/src/plugins/projectexplorer/projectexplorer.cpp @@ -8,6 +8,7 @@ #include "buildsystem.h" #include "compileoutputwindow.h" #include "configtaskhandler.h" +#include "copystep.h" #include "customexecutablerunconfiguration.h" #include "customparserssettingspage.h" #include "customwizard/customwizard.h" @@ -34,11 +35,13 @@ #include "dependenciespanel.h" #include "devicesupport/desktopdevice.h" #include "devicesupport/desktopdevicefactory.h" +#include "devicesupport/devicecheckbuildstep.h" #include "devicesupport/devicemanager.h" #include "devicesupport/devicesettingspage.h" #include "devicesupport/sshsettings.h" #include "devicesupport/sshsettingspage.h" #include "editorsettingspropertiespage.h" +#include "environmentaspect.h" #include "filesinallprojectsfind.h" #include "jsonwizard/jsonwizardfactory.h" #include "jsonwizard/jsonwizardgeneratorfactory.h" @@ -54,7 +57,6 @@ #include "project.h" #include "projectexplorericons.h" #include "projectexplorersettings.h" -#include "projectexplorersettingspage.h" #include "projectexplorertr.h" #include "projectfilewizardextension.h" #include "projectmanager.h" @@ -63,11 +65,8 @@ #include "projecttreewidget.h" #include "projectwindow.h" #include "removetaskhandler.h" -#include "runconfigurationaspects.h" #include "sanitizerparser.h" #include "selectablefilesmodel.h" -#include "session.h" -#include "sessiondialog.h" #include "showineditortaskhandler.h" #include "simpleprojectwizard.h" #include "target.h" @@ -108,6 +107,7 @@ #include #include #include +#include #include #include @@ -127,6 +127,7 @@ #include #include #include +#include #include #include @@ -173,6 +174,7 @@ */ using namespace Core; +using namespace ExtensionSystem; using namespace ProjectExplorer::Internal; using namespace Utils; @@ -240,7 +242,6 @@ const int P_ACTION_BUILDPROJECT = 80; // Menus const char M_RECENTPROJECTS[] = "ProjectExplorer.Menu.Recent"; const char M_UNLOADPROJECTS[] = "ProjectExplorer.Menu.Unload"; -const char M_SESSION[] = "ProjectExplorer.Menu.Session"; const char M_GENERATORS[] = "ProjectExplorer.Menu.Generators"; const char RUNMENUCONTEXTMENU[] = "Project.RunMenu"; @@ -254,7 +255,6 @@ const char BUILD_BEFORE_DEPLOY_SETTINGS_KEY[] = "ProjectExplorer/Settings/BuildB const char DEPLOY_BEFORE_RUN_SETTINGS_KEY[] = "ProjectExplorer/Settings/DeployBeforeRun"; const char SAVE_BEFORE_BUILD_SETTINGS_KEY[] = "ProjectExplorer/Settings/SaveBeforeBuild"; const char USE_JOM_SETTINGS_KEY[] = "ProjectExplorer/Settings/UseJom"; -const char AUTO_RESTORE_SESSION_SETTINGS_KEY[] = "ProjectExplorer/Settings/AutoRestoreLastSession"; const char ADD_LIBRARY_PATHS_TO_RUN_ENV_SETTINGS_KEY[] = "ProjectExplorer/Settings/AddLibraryPathsToRunEnv"; const char PROMPT_TO_STOP_RUN_CONTROL_SETTINGS_KEY[] = @@ -303,12 +303,12 @@ static const RunConfiguration *runConfigForNode(const Target *target, const Proj static bool hideBuildMenu() { - return Core::ICore::settings()->value(Constants::SETTINGS_MENU_HIDE_BUILD, false).toBool(); + return ICore::settings()->value(Constants::SETTINGS_MENU_HIDE_BUILD, false).toBool(); } static bool hideDebugMenu() { - return Core::ICore::settings()->value(Constants::SETTINGS_MENU_HIDE_DEBUG, false).toBool(); + return ICore::settings()->value(Constants::SETTINGS_MENU_HIDE_DEBUG, false).toBool(); } static bool canOpenTerminalWithRunEnv(const Project *project, const ProjectNode *node) @@ -337,7 +337,7 @@ static BuildConfiguration *currentBuildConfiguration() static Target *activeTarget() { - const Project * const project = SessionManager::startupProject(); + const Project * const project = ProjectManager::startupProject(); return project ? project->activeTarget() : nullptr; } @@ -405,40 +405,22 @@ protected: void restoreState(const QJsonObject &object) override; }; -class RunConfigurationLocatorFilter : public Core::ILocatorFilter +class RunConfigurationStartFilter final : public ILocatorFilter { public: - RunConfigurationLocatorFilter(); - - void prepareSearch(const QString &entry) override; - QList matchesFor(QFutureInterface &future, - const QString &entry) override; + RunConfigurationStartFilter(); private: - void targetListUpdated(); - QList m_result; + Core::LocatorMatcherTasks matchers() final; }; -class RunRunConfigurationLocatorFilter final : public RunConfigurationLocatorFilter +class RunConfigurationSwitchFilter final : public ILocatorFilter { public: - RunRunConfigurationLocatorFilter(); + RunConfigurationSwitchFilter(); - void accept(const Core::LocatorFilterEntry &selection, - QString *newText, - int *selectionStart, - int *selectionLength) const final; -}; - -class SwitchToRunConfigurationLocatorFilter final : public RunConfigurationLocatorFilter -{ -public: - SwitchToRunConfigurationLocatorFilter(); - - void accept(const Core::LocatorFilterEntry &selection, - QString *newText, - int *selectionStart, - int *selectionLength) const final; +private: + Core::LocatorMatcherTasks matchers() final; }; class ProjectExplorerPluginPrivate : public QObject @@ -468,12 +450,7 @@ public: void unloadProjectContextMenu(); void unloadOtherProjectsContextMenu(); void closeAllProjects(); - void showSessionManager(); - void updateSessionMenu(); - void setSession(QAction *action); - void determineSessionToRestoreAtStartup(); - void restoreSession(); void runProjectContextMenu(RunConfiguration *rc); void savePersistentSettings(); @@ -492,7 +469,7 @@ public: void deleteFile(); void handleRenameFile(); void handleSetStartupProject(); - void setStartupProject(ProjectExplorer::Project *project); + void setStartupProject(Project *project); bool closeAllFilesInProject(const Project *project); void updateRecentProjectMenu(); @@ -504,11 +481,11 @@ public: void openTerminalHere(const EnvironmentGetter &env); void openTerminalHereWithRunEnv(); - void invalidateProject(ProjectExplorer::Project *project); + void invalidateProject(Project *project); - void projectAdded(ProjectExplorer::Project *pro); - void projectRemoved(ProjectExplorer::Project *pro); - void projectDisplayNameChanged(ProjectExplorer::Project *pro); + void projectAdded(Project *pro); + void projectRemoved(Project *pro); + void projectDisplayNameChanged(Project *pro); void doUpdateRunActions(); @@ -525,12 +502,10 @@ public: void extendFolderNavigationWidgetFactory(); public: - QMenu *m_sessionMenu; QMenu *m_openWithMenu; QMenu *m_openTerminalMenu; QMultiMap m_actionMap; - QAction *m_sessionManagerAction; QAction *m_newAction; QAction *m_loadAction; ParameterAction *m_unloadAction; @@ -599,7 +574,6 @@ public: QAction *m_runSubProject; ProjectWindow *m_proWindow = nullptr; - QString m_sessionToRestoreAtStartup; QStringList m_profileMimeTypes; int m_activeRunControlCount = 0; @@ -618,11 +592,9 @@ public: BuildPropertiesSettings m_buildPropertiesSettings; QList m_customParsers; bool m_shouldHaveRunConfiguration = false; - bool m_shuttingDown = false; Id m_runMode = Constants::NO_RUN_MODE; ToolChainManager *m_toolChainManager = nullptr; - QStringList m_arguments; #ifdef WITH_JOURNALD JournaldWatcher m_journalWatcher; @@ -667,7 +639,7 @@ public: RemoveTaskHandler m_removeTaskHandler; ConfigTaskHandler m_configTaskHandler{Task::compilerMissingTask(), Constants::KITS_SETTINGS_PAGE_ID}; - SessionManager m_sessionManager; + ProjectManager m_sessionManager; AppOutputPane m_outputPane; ProjectTree m_projectTree; @@ -675,9 +647,11 @@ public: AllProjectsFilter m_allProjectsFilter; CurrentProjectFilter m_currentProjectFilter; AllProjectFilesFilter m_allProjectDirectoriesFilter; - RunRunConfigurationLocatorFilter m_runConfigurationLocatorFilter; - SwitchToRunConfigurationLocatorFilter m_switchRunConfigurationLocatorFilter; + RunConfigurationStartFilter m_runConfigurationStartFilter; + RunConfigurationSwitchFilter m_runConfigurationSwitchFilter; + CopyFileStepFactory m_copyFileStepFactory; + CopyDirectoryStepFactory m_copyDirectoryFactory; ProcessStepFactory m_processStepFactory; AllProjectsFind m_allProjectsFind; @@ -691,9 +665,7 @@ public: // Settings pages ProjectExplorerSettingsPage m_projectExplorerSettingsPage; - BuildPropertiesSettingsPage m_buildPropertiesSettingsPage{&m_buildPropertiesSettings}; AppOutputSettingsPage m_appOutputSettingsPage; - CompileOutputSettingsPage m_compileOutputSettingsPage; DeviceSettingsPage m_deviceSettingsPage; SshSettingsPage m_sshSettingsPage; CustomParsersSettingsPage m_customParsersSettingsPage; @@ -721,6 +693,7 @@ public: cmakeRunConfigFactory.runConfigurationId() }}; + DeviceCheckBuildStepFactory deviceCheckBuildStepFactory; SanitizerOutputFormatterFactory sanitizerFormatterFactory; }; @@ -743,7 +716,7 @@ static void openProjectsInDirectory(const FilePath &filePath) { const FilePaths projectFiles = projectsInDirectory(filePath); if (!projectFiles.isEmpty()) - Core::ICore::openFiles(projectFiles); + ICore::openFiles(projectFiles); } static QStringList projectNames(const QVector &folders) @@ -766,7 +739,7 @@ static QVector renamableFolderNodes(const FilePath &before, const return folderNodes; } -static QVector removableFolderNodes(const Utils::FilePath &filePath) +static QVector removableFolderNodes(const FilePath &filePath) { QVector folderNodes; ProjectTree::forEachNode([&](Node *node) { @@ -831,40 +804,33 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er IWizardFactory::registerFeatureProvider(new KitFeatureProvider); IWizardFactory::registerFactoryCreator([] { return new SimpleProjectWizard; }); - connect(&dd->m_welcomePage, &ProjectWelcomePage::manageSessions, - dd, &ProjectExplorerPluginPrivate::showSessionManager); - - SessionManager *sessionManager = &dd->m_sessionManager; - connect(sessionManager, &SessionManager::projectAdded, + ProjectManager *sessionManager = &dd->m_sessionManager; + connect(sessionManager, &ProjectManager::projectAdded, this, &ProjectExplorerPlugin::fileListChanged); - connect(sessionManager, &SessionManager::aboutToRemoveProject, + connect(sessionManager, &ProjectManager::aboutToRemoveProject, dd, &ProjectExplorerPluginPrivate::invalidateProject); - connect(sessionManager, &SessionManager::projectRemoved, + connect(sessionManager, &ProjectManager::projectRemoved, this, &ProjectExplorerPlugin::fileListChanged); - connect(sessionManager, &SessionManager::projectAdded, + connect(sessionManager, &ProjectManager::projectAdded, dd, &ProjectExplorerPluginPrivate::projectAdded); - connect(sessionManager, &SessionManager::projectRemoved, + connect(sessionManager, &ProjectManager::projectRemoved, dd, &ProjectExplorerPluginPrivate::projectRemoved); - connect(sessionManager, &SessionManager::projectDisplayNameChanged, + connect(sessionManager, &ProjectManager::projectDisplayNameChanged, dd, &ProjectExplorerPluginPrivate::projectDisplayNameChanged); - connect(sessionManager, &SessionManager::dependencyChanged, + connect(sessionManager, &ProjectManager::dependencyChanged, dd, &ProjectExplorerPluginPrivate::updateActions); - connect(sessionManager, &SessionManager::sessionLoaded, + connect(SessionManager::instance(), &SessionManager::sessionLoaded, dd, &ProjectExplorerPluginPrivate::updateActions); - connect(sessionManager, &SessionManager::sessionLoaded, + connect(SessionManager::instance(), &SessionManager::sessionLoaded, dd, &ProjectExplorerPluginPrivate::updateWelcomePage); - connect(sessionManager, &SessionManager::sessionLoaded, + connect(SessionManager::instance(), &SessionManager::sessionLoaded, dd, &ProjectExplorerPluginPrivate::loadSesssionTasks); - - connect(sessionManager, &SessionManager::projectAdded, dd, [](ProjectExplorer::Project *project) { - dd->m_allProjectDirectoriesFilter.addDirectory(project->projectDirectory()); - }); - connect(sessionManager, - &SessionManager::projectRemoved, - dd, - [](ProjectExplorer::Project *project) { - dd->m_allProjectDirectoriesFilter.removeDirectory(project->projectDirectory()); - }); + connect(SessionManager::instance(), &SessionManager::sessionCreated, + dd, &ProjectExplorerPluginPrivate::updateWelcomePage); + connect(SessionManager::instance(), &SessionManager::sessionRenamed, + dd, &ProjectExplorerPluginPrivate::updateWelcomePage); + connect(SessionManager::instance(), &SessionManager::sessionRemoved, + dd, &ProjectExplorerPluginPrivate::updateWelcomePage); ProjectTree *tree = &dd->m_projectTree; connect(tree, &ProjectTree::currentProjectChanged, dd, [] { @@ -903,7 +869,7 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er ICore::addPreCloseListener([]() -> bool { return coreAboutToClose(); }); - connect(SessionManager::instance(), &SessionManager::projectRemoved, + connect(ProjectManager::instance(), &ProjectManager::projectRemoved, &dd->m_outputPane, &AppOutputPane::projectRemoved); // ProjectPanelFactories @@ -1182,23 +1148,6 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er connect(mfile->menu(), &QMenu::aboutToShow, dd, &ProjectExplorerPluginPrivate::updateRecentProjectMenu); - // session menu - ActionContainer *msession = ActionManager::createMenu(Constants::M_SESSION); - msession->menu()->setTitle(Tr::tr("S&essions")); - msession->setOnAllDisabledBehavior(ActionContainer::Show); - mfile->addMenu(msession, Core::Constants::G_FILE_OPEN); - dd->m_sessionMenu = msession->menu(); - connect(mfile->menu(), &QMenu::aboutToShow, - dd, &ProjectExplorerPluginPrivate::updateSessionMenu); - - // session manager action - dd->m_sessionManagerAction = new QAction(Tr::tr("&Manage..."), this); - dd->m_sessionMenu->addAction(dd->m_sessionManagerAction); - dd->m_sessionMenu->addSeparator(); - cmd = ActionManager::registerAction(dd->m_sessionManagerAction, - "ProjectExplorer.ManageSessions"); - cmd->setDefaultKeySequence(QKeySequence()); - // unload action dd->m_unloadAction = new ParameterAction(Tr::tr("Close Project"), Tr::tr("Close Pro&ject \"%1\""), ParameterAction::AlwaysEnabled, this); @@ -1345,7 +1294,7 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er // without a project loaded. connect(generatorContainer->menu(), &QMenu::aboutToShow, [menu = generatorContainer->menu()] { menu->clear(); - if (Project * const project = SessionManager::startupProject()) { + if (Project * const project = ProjectManager::startupProject()) { for (const auto &generator : project->allGenerators()) { menu->addAction(generator.second, [project, id = generator.first] { project->runGenerator(id); @@ -1620,7 +1569,7 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er Command * const expandCmd = ActionManager::registerAction( dd->m_projectTreeExpandAllAction, Constants::PROJECTTREE_EXPAND_ALL, projectTreeContext); - for (Core::ActionContainer * const ac : {mfileContextMenu, msubProjectContextMenu, + for (ActionContainer * const ac : {mfileContextMenu, msubProjectContextMenu, mfolderContextMenu, mprojectContextMenu, msessionContextMenu}) { ac->addSeparator(treeGroup); ac->addAction(expandNodeCmd, treeGroup); @@ -1657,12 +1606,8 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er connect(ICore::instance(), &ICore::saveSettingsRequested, dd, &ProjectExplorerPluginPrivate::savePersistentSettings); - connect(EditorManager::instance(), &EditorManager::autoSaved, this, [] { - if (!dd->m_shuttingDown && !SessionManager::loadingSession()) - SessionManager::save(); - }); connect(qApp, &QApplication::applicationStateChanged, this, [](Qt::ApplicationState state) { - if (!dd->m_shuttingDown && state == Qt::ApplicationActive) + if (!PluginManager::isShuttingDown() && state == Qt::ApplicationActive) dd->updateWelcomePage(); }); @@ -1697,10 +1642,6 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er .toBool(); dd->m_projectExplorerSettings.useJom = s->value(Constants::USE_JOM_SETTINGS_KEY, defaultSettings.useJom).toBool(); - dd->m_projectExplorerSettings.autorestoreLastSession - = s->value(Constants::AUTO_RESTORE_SESSION_SETTINGS_KEY, - defaultSettings.autorestoreLastSession) - .toBool(); dd->m_projectExplorerSettings.addLibraryPathsToRunEnv = s->value(Constants::ADD_LIBRARY_PATHS_TO_RUN_ENV_SETTINGS_KEY, defaultSettings.addLibraryPathsToRunEnv) @@ -1723,7 +1664,7 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er if (tmp < 0 || tmp > int(StopBeforeBuild::SameApp)) tmp = int(defaultSettings.stopBeforeBuild); dd->m_projectExplorerSettings.stopBeforeBuild = StopBeforeBuild(tmp); - dd->m_projectExplorerSettings.terminalMode = static_cast( + dd->m_projectExplorerSettings.terminalMode = static_cast( s->value(Constants::TERMINAL_MODE_SETTINGS_KEY, int(defaultSettings.terminalMode)).toInt()); dd->m_projectExplorerSettings.closeSourceFilesWithProject = s->value(Constants::CLOSE_FILES_WITH_PROJECT_SETTINGS_KEY, @@ -1757,27 +1698,25 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er connect(buildManager, &BuildManager::buildQueueFinished, dd, &ProjectExplorerPluginPrivate::buildQueueFinished, Qt::QueuedConnection); - connect(dd->m_sessionManagerAction, &QAction::triggered, - dd, &ProjectExplorerPluginPrivate::showSessionManager); connect(dd->m_newAction, &QAction::triggered, dd, &ProjectExplorerPlugin::openNewProjectDialog); connect(dd->m_loadAction, &QAction::triggered, dd, &ProjectExplorerPluginPrivate::loadAction); connect(dd->m_buildProjectOnlyAction, &QAction::triggered, dd, [] { - BuildManager::buildProjectWithoutDependencies(SessionManager::startupProject()); + BuildManager::buildProjectWithoutDependencies(ProjectManager::startupProject()); }); connect(dd->m_buildAction, &QAction::triggered, dd, [] { - BuildManager::buildProjectWithDependencies(SessionManager::startupProject()); + BuildManager::buildProjectWithDependencies(ProjectManager::startupProject()); }); connect(dd->m_buildProjectForAllConfigsAction, &QAction::triggered, dd, [] { - BuildManager::buildProjectWithDependencies(SessionManager::startupProject(), + BuildManager::buildProjectWithDependencies(ProjectManager::startupProject(), ConfigSelection::All); }); connect(dd->m_buildActionContextMenu, &QAction::triggered, dd, [] { BuildManager::buildProjectWithoutDependencies(ProjectTree::currentProject()); }); connect(dd->m_buildForRunConfigAction, &QAction::triggered, dd, [] { - const Project * const project = SessionManager::startupProject(); + const Project * const project = ProjectManager::startupProject(); QTC_ASSERT(project, return); const Target * const target = project->activeTarget(); QTC_ASSERT(target, return); @@ -1792,20 +1731,20 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er BuildManager::buildProjectWithDependencies(ProjectTree::currentProject()); }); connect(dd->m_buildSessionAction, &QAction::triggered, dd, [] { - BuildManager::buildProjects(SessionManager::projectOrder(), ConfigSelection::Active); + BuildManager::buildProjects(ProjectManager::projectOrder(), ConfigSelection::Active); }); connect(dd->m_buildSessionForAllConfigsAction, &QAction::triggered, dd, [] { - BuildManager::buildProjects(SessionManager::projectOrder(), ConfigSelection::All); + BuildManager::buildProjects(ProjectManager::projectOrder(), ConfigSelection::All); }); connect(dd->m_rebuildProjectOnlyAction, &QAction::triggered, dd, [] { - BuildManager::rebuildProjectWithoutDependencies(SessionManager::startupProject()); + BuildManager::rebuildProjectWithoutDependencies(ProjectManager::startupProject()); }); connect(dd->m_rebuildAction, &QAction::triggered, dd, [] { - BuildManager::rebuildProjectWithDependencies(SessionManager::startupProject(), + BuildManager::rebuildProjectWithDependencies(ProjectManager::startupProject(), ConfigSelection::Active); }); connect(dd->m_rebuildProjectForAllConfigsAction, &QAction::triggered, dd, [] { - BuildManager::rebuildProjectWithDependencies(SessionManager::startupProject(), + BuildManager::rebuildProjectWithDependencies(ProjectManager::startupProject(), ConfigSelection::All); }); connect(dd->m_rebuildActionContextMenu, &QAction::triggered, dd, [] { @@ -1816,32 +1755,32 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er ConfigSelection::Active); }); connect(dd->m_rebuildSessionAction, &QAction::triggered, dd, [] { - BuildManager::rebuildProjects(SessionManager::projectOrder(), ConfigSelection::Active); + BuildManager::rebuildProjects(ProjectManager::projectOrder(), ConfigSelection::Active); }); connect(dd->m_rebuildSessionForAllConfigsAction, &QAction::triggered, dd, [] { - BuildManager::rebuildProjects(SessionManager::projectOrder(), ConfigSelection::All); + BuildManager::rebuildProjects(ProjectManager::projectOrder(), ConfigSelection::All); }); connect(dd->m_deployProjectOnlyAction, &QAction::triggered, dd, [] { - BuildManager::deployProjects({SessionManager::startupProject()}); + BuildManager::deployProjects({ProjectManager::startupProject()}); }); connect(dd->m_deployAction, &QAction::triggered, dd, [] { - BuildManager::deployProjects(SessionManager::projectOrder(SessionManager::startupProject())); + BuildManager::deployProjects(ProjectManager::projectOrder(ProjectManager::startupProject())); }); connect(dd->m_deployActionContextMenu, &QAction::triggered, dd, [] { BuildManager::deployProjects({ProjectTree::currentProject()}); }); connect(dd->m_deploySessionAction, &QAction::triggered, dd, [] { - BuildManager::deployProjects(SessionManager::projectOrder()); + BuildManager::deployProjects(ProjectManager::projectOrder()); }); connect(dd->m_cleanProjectOnlyAction, &QAction::triggered, dd, [] { - BuildManager::cleanProjectWithoutDependencies(SessionManager::startupProject()); + BuildManager::cleanProjectWithoutDependencies(ProjectManager::startupProject()); }); connect(dd->m_cleanAction, &QAction::triggered, dd, [] { - BuildManager::cleanProjectWithDependencies(SessionManager::startupProject(), + BuildManager::cleanProjectWithDependencies(ProjectManager::startupProject(), ConfigSelection::Active); }); connect(dd->m_cleanProjectForAllConfigsAction, &QAction::triggered, dd, [] { - BuildManager::cleanProjectWithDependencies(SessionManager::startupProject(), + BuildManager::cleanProjectWithDependencies(ProjectManager::startupProject(), ConfigSelection::All); }); connect(dd->m_cleanActionContextMenu, &QAction::triggered, dd, [] { @@ -1852,10 +1791,10 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er ConfigSelection::Active); }); connect(dd->m_cleanSessionAction, &QAction::triggered, dd, [] { - BuildManager::cleanProjects(SessionManager::projectOrder(), ConfigSelection::Active); + BuildManager::cleanProjects(ProjectManager::projectOrder(), ConfigSelection::Active); }); connect(dd->m_cleanSessionForAllConfigsAction, &QAction::triggered, dd, [] { - BuildManager::cleanProjects(SessionManager::projectOrder(), ConfigSelection::All); + BuildManager::cleanProjects(ProjectManager::projectOrder(), ConfigSelection::All); }); connect(dd->m_runAction, &QAction::triggered, dd, [] { runStartupProject(Constants::NORMAL_RUN_MODE); }); @@ -1920,7 +1859,7 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er connect(dd->m_setStartupProjectAction, &QAction::triggered, dd, &ProjectExplorerPluginPrivate::handleSetStartupProject); connect(dd->m_closeProjectFilesActionFileMenu, &QAction::triggered, - dd, [] { dd->closeAllFilesInProject(SessionManager::projects().first()); }); + dd, [] { dd->closeAllFilesInProject(ProjectManager::projects().first()); }); connect(dd->m_closeProjectFilesActionContextMenu, &QAction::triggered, dd, [] { dd->closeAllFilesInProject(ProjectTree::currentProject()); }); connect(dd->m_projectTreeCollapseAllAction, &QAction::triggered, @@ -1935,6 +1874,18 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er dd->updateContextMenuActions(ProjectTree::currentNode()); }); + connect(ModeManager::instance(), + &ModeManager::currentModeChanged, + dd, + &ProjectExplorerPluginPrivate::currentModeChanged); + connect(&dd->m_welcomePage, + &ProjectWelcomePage::requestProject, + m_instance, + &ProjectExplorerPlugin::openProjectWelcomePage); + connect(SessionManager::instance(), + &SessionManager::startupSessionRestored, + m_instance, + &ProjectExplorerPlugin::finishedInitialization); dd->updateWelcomePage(); MacroExpander *expander = Utils::globalMacroExpander(); @@ -1965,7 +1916,7 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er Project::addVariablesToMacroExpander("ActiveProject:", "Active project", expander, - &SessionManager::startupProject); + &ProjectManager::startupProject); EnvironmentProvider::addProvider( {"ActiveProject:BuildConfig:Env", Tr::tr("Active build environment of the active project."), [] { if (const BuildConfiguration * const bc = activeBuildConfiguration()) @@ -1981,15 +1932,6 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er return Environment::systemEnvironment(); }}); - const auto fileHandler = [] { - return SessionManager::sessionNameToFileName(SessionManager::activeSession()); - }; - expander->registerFileVariables("Session", Tr::tr("File where current session is saved."), - fileHandler); - expander->registerVariable("Session:Name", Tr::tr("Name of current session."), [] { - return SessionManager::activeSession(); - }); - DeviceManager::instance()->addDevice(IDevice::Ptr(new DesktopDevice)); if (auto sanitizerTester = SanitizerParser::testCreator()) @@ -2032,7 +1974,7 @@ void ProjectExplorerPluginPrivate::unloadProjectContextMenu() void ProjectExplorerPluginPrivate::unloadOtherProjectsContextMenu() { if (Project *currentProject = ProjectTree::currentProject()) { - const QList projects = SessionManager::projects(); + const QList projects = ProjectManager::projects(); QTC_ASSERT(!projects.isEmpty(), return); for (Project *p : projects) { @@ -2045,7 +1987,7 @@ void ProjectExplorerPluginPrivate::unloadOtherProjectsContextMenu() void ProjectExplorerPluginPrivate::handleUnloadProject() { - QList projects = SessionManager::projects(); + QList projects = ProjectManager::projects(); QTC_ASSERT(!projects.isEmpty(), return); ProjectExplorerPlugin::unloadProject(projects.first()); @@ -2072,7 +2014,7 @@ void ProjectExplorerPlugin::unloadProject(Project *project) dd->addToRecentProjects(project->projectFilePath(), project->displayName()); - SessionManager::removeProject(project); + ProjectManager::removeProject(project); dd->updateActions(); } @@ -2081,7 +2023,7 @@ void ProjectExplorerPluginPrivate::closeAllProjects() if (!EditorManager::closeAllDocuments()) return; // Action has been cancelled - SessionManager::closeAllProjects(); + ProjectManager::closeAllProjects(); updateActions(); ModeManager::activateMode(Core::Constants::MODE_WELCOME); @@ -2137,13 +2079,13 @@ void ProjectExplorerPlugin::extensionsInitialized() Tr::tr("Sanitizer", "Category for sanitizer issues listed under 'Issues'")); TaskHub::addCategory(Constants::TASK_CATEGORY_TASKLIST_ID, Tr::tr("My Tasks")); - SshSettings::loadSettings(Core::ICore::settings()); + SshSettings::loadSettings(ICore::settings()); const auto searchPathRetriever = [] { - FilePaths searchPaths = {Core::ICore::libexecPath()}; + FilePaths searchPaths = {ICore::libexecPath()}; if (HostOsInfo::isWindowsHost()) { - const QString gitBinary = Core::ICore::settings()->value("Git/BinaryPath", "git") + const QString gitBinary = ICore::settings()->value("Git/BinaryPath", "git") .toString(); - const QStringList rawGitSearchPaths = Core::ICore::settings()->value("Git/Path") + const QStringList rawGitSearchPaths = ICore::settings()->value("Git/Path") .toString().split(':', Qt::SkipEmptyParts); const FilePaths gitSearchPaths = Utils::transform(rawGitSearchPaths, [](const QString &rawPath) { return FilePath::fromString(rawPath); }); @@ -2177,11 +2119,12 @@ void ProjectExplorerPlugin::extensionsInitialized() void ProjectExplorerPlugin::restoreKits() { - dd->determineSessionToRestoreAtStartup(); ExtraAbi::load(); // Load this before Toolchains! ToolChainManager::restoreToolChains(); KitManager::restoreKits(); - QTimer::singleShot(0, dd, &ProjectExplorerPluginPrivate::restoreSession); // delay a bit... + // restoring startup session is supposed to be done as a result of ICore::coreOpened, + // and that is supposed to happen after restoring kits: + QTC_CHECK(!SessionManager::isStartupSessionRestored()); } void ProjectExplorerPluginPrivate::updateRunWithoutDeployMenu() @@ -2189,15 +2132,13 @@ void ProjectExplorerPluginPrivate::updateRunWithoutDeployMenu() m_runWithoutDeployAction->setVisible(m_projectExplorerSettings.deployBeforeRun); } -ExtensionSystem::IPlugin::ShutdownFlag ProjectExplorerPlugin::aboutToShutdown() +IPlugin::ShutdownFlag ProjectExplorerPlugin::aboutToShutdown() { disconnect(ModeManager::instance(), &ModeManager::currentModeChanged, dd, &ProjectExplorerPluginPrivate::currentModeChanged); ProjectTree::aboutToShutDown(); ToolChainManager::aboutToShutdown(); - SessionManager::closeAllProjects(); - - dd->m_shuttingDown = true; + ProjectManager::closeAllProjects(); // Attempt to synchronously shutdown all run controls. // If that fails, fall back to asynchronous shutdown (Debugger run controls @@ -2210,11 +2151,6 @@ ExtensionSystem::IPlugin::ShutdownFlag ProjectExplorerPlugin::aboutToShutdown() return AsynchronousShutdown; } -void ProjectExplorerPlugin::showSessionManager() -{ - dd->showSessionManager(); -} - void ProjectExplorerPlugin::openNewProjectDialog() { if (!ICore::isNewItemDialogRunning()) { @@ -2226,25 +2162,11 @@ void ProjectExplorerPlugin::openNewProjectDialog() } } -void ProjectExplorerPluginPrivate::showSessionManager() -{ - SessionManager::save(); - SessionDialog sessionDialog(ICore::dialogParent()); - sessionDialog.setAutoLoadSession(dd->m_projectExplorerSettings.autorestoreLastSession); - sessionDialog.exec(); - dd->m_projectExplorerSettings.autorestoreLastSession = sessionDialog.autoLoadSession(); - - updateActions(); - - if (ModeManager::currentModeId() == Core::Constants::MODE_WELCOME) - updateWelcomePage(); -} - void ProjectExplorerPluginPrivate::setStartupProject(Project *project) { if (!project) return; - SessionManager::setStartupProject(project); + ProjectManager::setStartupProject(project); updateActions(); } @@ -2255,7 +2177,7 @@ bool ProjectExplorerPluginPrivate::closeAllFilesInProject(const Project *project Utils::erase(openFiles, [project](const DocumentModel::Entry *entry) { return entry->pinned || !project->isKnownFile(entry->filePath()); }); - for (const Project * const otherProject : SessionManager::projects()) { + for (const Project * const otherProject : ProjectManager::projects()) { if (otherProject == project) continue; Utils::erase(openFiles, [otherProject](const DocumentModel::Entry *entry) { @@ -2267,23 +2189,15 @@ bool ProjectExplorerPluginPrivate::closeAllFilesInProject(const Project *project void ProjectExplorerPluginPrivate::savePersistentSettings() { - if (dd->m_shuttingDown) + if (PluginManager::isShuttingDown()) return; - if (!SessionManager::loadingSession()) { - for (Project *pro : SessionManager::projects()) + if (!SessionManager::isLoadingSession()) { + for (Project *pro : ProjectManager::projects()) pro->saveSettings(); - - SessionManager::save(); } QtcSettings *s = ICore::settings(); - if (SessionManager::isDefaultVirgin()) { - s->remove(Constants::STARTUPSESSION_KEY); - } else { - s->setValue(Constants::STARTUPSESSION_KEY, SessionManager::activeSession()); - s->setValue(Constants::LASTSESSION_KEY, SessionManager::activeSession()); - } s->remove(QLatin1String("ProjectExplorer/RecentProjects/Files")); QStringList fileNames; @@ -2312,9 +2226,6 @@ void ProjectExplorerPluginPrivate::savePersistentSettings() s->setValueWithDefault(Constants::USE_JOM_SETTINGS_KEY, dd->m_projectExplorerSettings.useJom, defaultSettings.useJom); - s->setValueWithDefault(Constants::AUTO_RESTORE_SESSION_SETTINGS_KEY, - dd->m_projectExplorerSettings.autorestoreLastSession, - defaultSettings.autorestoreLastSession); s->setValueWithDefault(Constants::ADD_LIBRARY_PATHS_TO_RUN_ENV_SETTINGS_KEY, dd->m_projectExplorerSettings.addLibraryPathsToRunEnv, defaultSettings.addLibraryPathsToRunEnv); @@ -2368,7 +2279,7 @@ ProjectExplorerPlugin::OpenProjectResult ProjectExplorerPlugin::openProject(cons if (!project) return result; dd->addToRecentProjects(filePath, project->displayName()); - SessionManager::setStartupProject(project); + ProjectManager::setStartupProject(project); return result; } @@ -2420,11 +2331,11 @@ ProjectExplorerPlugin::OpenProjectResult ProjectExplorerPlugin::openProjects(con QTC_ASSERT(!fileName.isEmpty(), continue); const FilePath filePath = fileName.absoluteFilePath(); - Project *found = Utils::findOrDefault(SessionManager::projects(), + Project *found = Utils::findOrDefault(ProjectManager::projects(), Utils::equal(&Project::projectFilePath, filePath)); if (found) { alreadyOpen.append(found); - SessionManager::reportProjectLoadingProgress(); + SessionManager::sessionLoadingProgress(); continue; } @@ -2439,7 +2350,7 @@ ProjectExplorerPlugin::OpenProjectResult ProjectExplorerPlugin::openProjects(con if (restoreResult == Project::RestoreResult::Ok) { connect(pro, &Project::fileListChanged, m_instance, &ProjectExplorerPlugin::fileListChanged); - SessionManager::addProject(pro); + ProjectManager::addProject(pro); openedPro += pro; } else { if (restoreResult == Project::RestoreResult::Error) @@ -2453,7 +2364,7 @@ ProjectExplorerPlugin::OpenProjectResult ProjectExplorerPlugin::openProjects(con .arg(mt.name())); } if (filePaths.size() > 1) - SessionManager::reportProjectLoadingProgress(); + SessionManager::sessionLoadingProgress(); } dd->updateActions(); @@ -2496,33 +2407,6 @@ void ProjectExplorerPluginPrivate::currentModeChanged(Id mode, Id oldMode) updateWelcomePage(); } -void ProjectExplorerPluginPrivate::determineSessionToRestoreAtStartup() -{ - // Process command line arguments first: - const bool lastSessionArg = - ExtensionSystem::PluginManager::specForPlugin(m_instance)->arguments().contains("-lastsession"); - m_sessionToRestoreAtStartup = lastSessionArg ? SessionManager::startupSession() : QString(); - const QStringList arguments = ExtensionSystem::PluginManager::arguments(); - if (!lastSessionArg) { - QStringList sessions = SessionManager::sessions(); - // We have command line arguments, try to find a session in them - // Default to no session loading - for (const QString &arg : arguments) { - if (sessions.contains(arg)) { - // Session argument - m_sessionToRestoreAtStartup = arg; - break; - } - } - } - // Handle settings only after command line arguments: - if (m_sessionToRestoreAtStartup.isEmpty() && m_projectExplorerSettings.autorestoreLastSession) - m_sessionToRestoreAtStartup = SessionManager::startupSession(); - - if (!m_sessionToRestoreAtStartup.isEmpty()) - ModeManager::activateMode(Core::Constants::MODE_EDIT); -} - // Return a list of glob patterns for project files ("*.pro", etc), use first, main pattern only. QStringList ProjectExplorerPlugin::projectFileGlobs() { @@ -2548,70 +2432,6 @@ MiniProjectTargetSelector *ProjectExplorerPlugin::targetSelector() return dd->m_targetSelector; } -/*! - This function is connected to the ICore::coreOpened signal. If - there was no session explicitly loaded, it creates an empty new - default session and puts the list of recent projects and sessions - onto the welcome page. -*/ -void ProjectExplorerPluginPrivate::restoreSession() -{ - // We have command line arguments, try to find a session in them - QStringList arguments = ExtensionSystem::PluginManager::arguments(); - if (!dd->m_sessionToRestoreAtStartup.isEmpty() && !arguments.isEmpty()) - arguments.removeOne(dd->m_sessionToRestoreAtStartup); - - // Massage the argument list. - // Be smart about directories: If there is a session of that name, load it. - // Other than that, look for project files in it. The idea is to achieve - // 'Do what I mean' functionality when starting Creator in a directory with - // the single command line argument '.' and avoid editor warnings about not - // being able to open directories. - // In addition, convert "filename" "+45" or "filename" ":23" into - // "filename+45" and "filename:23". - if (!arguments.isEmpty()) { - const QStringList sessions = SessionManager::sessions(); - for (int a = 0; a < arguments.size(); ) { - const QString &arg = arguments.at(a); - const QFileInfo fi(arg); - if (fi.isDir()) { - const QDir dir(fi.absoluteFilePath()); - // Does the directory name match a session? - if (dd->m_sessionToRestoreAtStartup.isEmpty() - && sessions.contains(dir.dirName())) { - dd->m_sessionToRestoreAtStartup = dir.dirName(); - arguments.removeAt(a); - continue; - } - } // Done directories. - // Converts "filename" "+45" or "filename" ":23" into "filename+45" and "filename:23" - if (a && (arg.startsWith(QLatin1Char('+')) || arg.startsWith(QLatin1Char(':')))) { - arguments[a - 1].append(arguments.takeAt(a)); - continue; - } - ++a; - } // for arguments - } // !arguments.isEmpty() - // Restore latest session or what was passed on the command line - - SessionManager::loadSession(!dd->m_sessionToRestoreAtStartup.isEmpty() - ? dd->m_sessionToRestoreAtStartup : QString(), true); - - // update welcome page - connect(ModeManager::instance(), &ModeManager::currentModeChanged, - dd, &ProjectExplorerPluginPrivate::currentModeChanged); - connect(&dd->m_welcomePage, &ProjectWelcomePage::requestProject, - m_instance, &ProjectExplorerPlugin::openProjectWelcomePage); - dd->m_arguments = arguments; - // delay opening projects from the command line even more - QTimer::singleShot(0, m_instance, [] { - ICore::openFiles(Utils::transform(dd->m_arguments, &FilePath::fromUserInput), - ICore::OpenFilesFlags(ICore::CanContainLineAndColumnNumbers | ICore::SwitchMode)); - emit m_instance->finishedInitialization(); - }); - updateActions(); -} - void ProjectExplorerPluginPrivate::executeRunConfiguration(RunConfiguration *runConfiguration, Id runMode) { const Tasks runConfigIssues = runConfiguration->checkForIssues(); @@ -2675,7 +2495,7 @@ void ProjectExplorerPluginPrivate::checkForShutdown() { --m_activeRunControlCount; QTC_ASSERT(m_activeRunControlCount >= 0, m_activeRunControlCount = 0); - if (m_shuttingDown && m_activeRunControlCount == 0) + if (PluginManager::isShuttingDown() && m_activeRunControlCount == 0) emit m_instance->asynchronousShutdownFinished(); } @@ -2732,7 +2552,7 @@ RecentProjectsEntries ProjectExplorerPluginPrivate::recentProjects() const void ProjectExplorerPluginPrivate::updateActions() { - const Project *const project = SessionManager::startupProject(); + const Project *const project = ProjectManager::startupProject(); const Project *const currentProject = ProjectTree::currentProject(); // for context menu actions const QPair buildActionState = buildSettingsEnabled(project); @@ -2743,10 +2563,10 @@ void ProjectExplorerPluginPrivate::updateActions() const QString projectName = project ? project->displayName() : QString(); const QString projectNameContextMenu = currentProject ? currentProject->displayName() : QString(); - m_unloadAction->setParameter(SessionManager::projects().size() == 1 ? projectName : QString()); + m_unloadAction->setParameter(ProjectManager::projects().size() == 1 ? projectName : QString()); m_unloadActionContextMenu->setParameter(projectNameContextMenu); m_unloadOthersActionContextMenu->setParameter(projectNameContextMenu); - m_closeProjectFilesActionFileMenu->setParameter(SessionManager::projects().size() == 1 + m_closeProjectFilesActionFileMenu->setParameter(ProjectManager::projects().size() == 1 ? projectName : QString()); m_closeProjectFilesActionContextMenu->setParameter(projectNameContextMenu); @@ -2791,7 +2611,7 @@ void ProjectExplorerPluginPrivate::updateActions() m_setStartupProjectAction->setParameter(projectNameContextMenu); m_setStartupProjectAction->setVisible(currentProject != project); - const bool hasDependencies = SessionManager::projectOrder(currentProject).size() > 1; + const bool hasDependencies = ProjectManager::projectOrder(currentProject).size() > 1; m_buildActionContextMenu->setVisible(hasDependencies); m_rebuildActionContextMenu->setVisible(hasDependencies); m_cleanActionContextMenu->setVisible(hasDependencies); @@ -2818,17 +2638,17 @@ void ProjectExplorerPluginPrivate::updateActions() m_cleanProjectOnlyAction->setToolTip(buildActionState.second); // Session actions - m_closeAllProjects->setEnabled(SessionManager::hasProjects()); - m_unloadAction->setEnabled(SessionManager::projects().size() <= 1); - m_unloadAction->setEnabled(SessionManager::projects().size() == 1); - m_unloadActionContextMenu->setEnabled(SessionManager::hasProjects()); - m_unloadOthersActionContextMenu->setEnabled(SessionManager::projects().size() >= 2); - m_closeProjectFilesActionFileMenu->setEnabled(SessionManager::projects().size() == 1); - m_closeProjectFilesActionContextMenu->setEnabled(SessionManager::hasProjects()); + m_closeAllProjects->setEnabled(ProjectManager::hasProjects()); + m_unloadAction->setEnabled(ProjectManager::projects().size() <= 1); + m_unloadAction->setEnabled(ProjectManager::projects().size() == 1); + m_unloadActionContextMenu->setEnabled(ProjectManager::hasProjects()); + m_unloadOthersActionContextMenu->setEnabled(ProjectManager::projects().size() >= 2); + m_closeProjectFilesActionFileMenu->setEnabled(ProjectManager::projects().size() == 1); + m_closeProjectFilesActionContextMenu->setEnabled(ProjectManager::hasProjects()); ActionContainer *aci = ActionManager::actionContainer(Constants::M_UNLOADPROJECTS); - aci->menu()->menuAction()->setEnabled(SessionManager::hasProjects()); + aci->menu()->menuAction()->setEnabled(ProjectManager::hasProjects()); m_buildSessionAction->setEnabled(buildSessionState.first); m_buildSessionForAllConfigsAction->setEnabled(buildSessionState.first); @@ -2846,7 +2666,7 @@ void ProjectExplorerPluginPrivate::updateActions() m_cancelBuildAction->setEnabled(BuildManager::isBuilding()); - const bool hasProjects = SessionManager::hasProjects(); + const bool hasProjects = ProjectManager::hasProjects(); m_projectSelectorAction->setEnabled(hasProjects); m_projectSelectorActionMenu->setEnabled(hasProjects); m_projectSelectorActionQuick->setEnabled(hasProjects); @@ -2923,10 +2743,9 @@ void ProjectExplorerPluginPrivate::extendFolderNavigationWidgetFactory() "The file \"%1\" was renamed to \"%2\", " "but the following projects could not be automatically changed: %3") .arg(before.toUserOutput(), after.toUserOutput(), projects); - QTimer::singleShot(0, Core::ICore::instance(), [errorMessage] { - QMessageBox::warning(Core::ICore::dialogParent(), - Tr::tr("Project Editing Failed"), - errorMessage); + QTimer::singleShot(0, ICore::instance(), [errorMessage] { + QMessageBox::warning(ICore::dialogParent(), + Tr::tr("Project Editing Failed"), errorMessage); }); } }); @@ -2944,8 +2763,8 @@ void ProjectExplorerPluginPrivate::extendFolderNavigationWidgetFactory() const QString errorMessage = Tr::tr("The following projects failed to automatically remove the file: %1") .arg(projects); - QTimer::singleShot(0, Core::ICore::instance(), [errorMessage] { - QMessageBox::warning(Core::ICore::dialogParent(), + QTimer::singleShot(0, ICore::instance(), [errorMessage] { + QMessageBox::warning(ICore::dialogParent(), Tr::tr("Project Editing Failed"), errorMessage); }); @@ -2967,7 +2786,7 @@ void ProjectExplorerPluginPrivate::runProjectContextMenu(RunConfiguration *rc) static bool hasBuildSettings(const Project *pro) { - return Utils::anyOf(SessionManager::projectOrder(pro), [](const Project *project) { + return Utils::anyOf(ProjectManager::projectOrder(pro), [](const Project *project) { return project && project->activeTarget() && project->activeTarget()->activeBuildConfiguration(); @@ -2979,7 +2798,7 @@ static QPair subprojectEnabledState(const Project *pro) QPair result; result.first = true; - const QList &projects = SessionManager::projectOrder(pro); + const QList &projects = ProjectManager::projectOrder(pro); for (const Project *project : projects) { if (project && project->activeTarget() && project->activeTarget()->activeBuildConfiguration() @@ -3021,7 +2840,7 @@ QPair ProjectExplorerPluginPrivate::buildSettingsEnabledForSessio { QPair result; result.first = true; - if (!SessionManager::hasProjects()) { + if (!ProjectManager::hasProjects()) { result.first = false; result.second = Tr::tr("No project loaded."); } else if (BuildManager::isBuilding()) { @@ -3078,7 +2897,7 @@ void ProjectExplorerPlugin::handleCommandLineArguments(const QStringList &argume static bool hasDeploySettings(Project *pro) { - return Utils::anyOf(SessionManager::projectOrder(pro), [](Project *project) { + return Utils::anyOf(ProjectManager::projectOrder(pro), [](Project *project) { return project->activeTarget() && project->activeTarget()->activeDeployConfiguration(); }); @@ -3096,7 +2915,7 @@ void ProjectExplorerPlugin::runProject(Project *pro, Id mode, const bool forceSk void ProjectExplorerPlugin::runStartupProject(Id runMode, bool forceSkipDeploy) { - runProject(SessionManager::startupProject(), runMode, forceSkipDeploy); + runProject(ProjectManager::startupProject(), runMode, forceSkipDeploy); } void ProjectExplorerPlugin::runRunConfiguration(RunConfiguration *rc, @@ -3157,7 +2976,7 @@ void ProjectExplorerPluginPrivate::projectAdded(Project *pro) void ProjectExplorerPluginPrivate::projectRemoved(Project *pro) { Q_UNUSED(pro) - m_projectsMode.setEnabled(SessionManager::hasProjects()); + m_projectsMode.setEnabled(ProjectManager::hasProjects()); } void ProjectExplorerPluginPrivate::projectDisplayNameChanged(Project *pro) @@ -3168,7 +2987,7 @@ void ProjectExplorerPluginPrivate::projectDisplayNameChanged(Project *pro) void ProjectExplorerPluginPrivate::updateDeployActions() { - Project *project = SessionManager::startupProject(); + Project *project = ProjectManager::startupProject(); bool enableDeployActions = project && !BuildManager::isBuilding(project) @@ -3187,7 +3006,7 @@ void ProjectExplorerPluginPrivate::updateDeployActions() enableDeployActionsContextMenu = false; } - bool hasProjects = SessionManager::hasProjects(); + bool hasProjects = ProjectManager::hasProjects(); m_deployAction->setEnabled(enableDeployActions); @@ -3203,7 +3022,7 @@ void ProjectExplorerPluginPrivate::updateDeployActions() && !project->activeTarget()->activeBuildConfiguration()->isEnabled(); }; - if (Utils::anyOf(SessionManager::projectOrder(nullptr), hasDisabledBuildConfiguration)) + if (Utils::anyOf(ProjectManager::projectOrder(nullptr), hasDisabledBuildConfiguration)) enableDeploySessionAction = false; } if (!hasProjects || !hasDeploySettings(nullptr) || BuildManager::isBuilding()) @@ -3215,7 +3034,7 @@ void ProjectExplorerPluginPrivate::updateDeployActions() bool ProjectExplorerPlugin::canRunStartupProject(Id runMode, QString *whyNot) { - Project *project = SessionManager::startupProject(); + Project *project = ProjectManager::startupProject(); if (!project) { if (whyNot) *whyNot = Tr::tr("No active project."); @@ -3320,7 +3139,7 @@ void ProjectExplorerPluginPrivate::updateUnloadProjectMenu() ActionContainer *aci = ActionManager::actionContainer(Constants::M_UNLOADPROJECTS); QMenu *menu = aci->menu(); menu->clear(); - for (Project *project : SessionManager::projects()) { + for (Project *project : ProjectManager::projects()) { QAction *action = menu->addAction(Tr::tr("Close Project \"%1\"").arg(project->displayName())); connect(action, &QAction::triggered, [project] { ProjectExplorerPlugin::unloadProject(project); } ); @@ -3419,7 +3238,7 @@ void ProjectExplorerPluginPrivate::updateContextMenuActions(Node *currentNode) m_removeFileAction->setVisible(true); m_duplicateFileAction->setVisible(false); m_deleteFileAction->setVisible(true); - m_runActionContextMenu->setVisible(false); + m_runActionContextMenu->setEnabled(false); m_defaultRunConfiguration.clear(); m_diffFileAction->setVisible(DiffService::instance()); @@ -3448,7 +3267,7 @@ void ProjectExplorerPluginPrivate::updateContextMenuActions(Node *currentNode) if (pn && project) { if (pn == project->rootProjectNode()) { - m_runActionContextMenu->setVisible(true); + m_runActionContextMenu->setEnabled(true); } else { QList runConfigs; if (Target *t = project->activeTarget()) { @@ -3459,7 +3278,7 @@ void ProjectExplorerPluginPrivate::updateContextMenuActions(Node *currentNode) } } if (runConfigs.count() == 1) { - m_runActionContextMenu->setVisible(true); + m_runActionContextMenu->setEnabled(true); m_defaultRunConfiguration = runConfigs.first(); } else if (runConfigs.count() > 1) { runMenu->menu()->menuAction()->setVisible(true); @@ -3592,9 +3411,7 @@ void ProjectExplorerPluginPrivate::updateLocationSubMenus() : Tr::tr("%1 in %2").arg(li.displayName).arg(li.path.toUserOutput()); auto *action = new QAction(displayName, nullptr); connect(action, &QAction::triggered, this, [line, path] { - Core::EditorManager::openEditorAt(Link(path, line), - {}, - Core::EditorManager::AllowExternalEditor); + EditorManager::openEditorAt(Link(path, line), {}, EditorManager::AllowExternalEditor); }); projectMenu->addAction(action); @@ -3795,6 +3612,13 @@ void ProjectExplorerPluginPrivate::showInFileSystemPane() Core::FileUtils::showInFileSystemView(currentNode->filePath()); } +static BuildConfiguration *activeBuildConfiguration(Project *project) +{ + if (!project || !project->activeTarget() || !project->activeTarget()->activeBuildConfiguration()) + return {}; + return project->activeTarget()->activeBuildConfiguration(); +} + void ProjectExplorerPluginPrivate::openTerminalHere(const EnvironmentGetter &env) { const Node *currentNode = ProjectTree::currentNode(); @@ -3804,7 +3628,29 @@ void ProjectExplorerPluginPrivate::openTerminalHere(const EnvironmentGetter &env if (!environment) return; - Core::FileUtils::openTerminal(currentNode->directory(), environment.value()); + BuildConfiguration *bc = activeBuildConfiguration(ProjectTree::projectForNode(currentNode)); + if (!bc) { + Terminal::Hooks::instance().openTerminal({{}, currentNode->directory(), environment}); + return; + } + + IDeviceConstPtr buildDevice = BuildDeviceKitAspect::device(bc->target()->kit()); + + if (!buildDevice) + return; + + FilePath workingDir = currentNode->directory(); + if (!buildDevice->filePath(workingDir.path()).exists() + && !buildDevice->ensureReachable(workingDir)) + workingDir.clear(); + + const FilePath shell = Terminal::defaultShellForDevice(buildDevice->rootPath()); + + if (!shell.isEmpty() && buildDevice->rootPath().needsDevice()) { + Terminal::Hooks::instance().openTerminal({CommandLine{shell, {}}, workingDir, environment}); + } else { + Terminal::Hooks::instance().openTerminal({std::nullopt, workingDir, environment}); + } } void ProjectExplorerPluginPrivate::openTerminalHereWithRunEnv() @@ -3825,9 +3671,21 @@ void ProjectExplorerPluginPrivate::openTerminalHereWithRunEnv() if (!device) device = DeviceKitAspect::device(target->kit()); QTC_ASSERT(device && device->canOpenTerminal(), return); - const FilePath workingDir = device->type() == Constants::DESKTOP_DEVICE_TYPE - ? currentNode->directory() : runnable.workingDirectory; - device->openTerminal(runnable.environment, workingDir); + + FilePath workingDir = device->type() == Constants::DESKTOP_DEVICE_TYPE + ? currentNode->directory() + : runnable.workingDirectory; + + if (!device->filePath(workingDir.path()).exists() && !device->ensureReachable(workingDir)) + workingDir.clear(); + + const FilePath shell = Terminal::defaultShellForDevice(device->rootPath()); + if (!shell.isEmpty() && device->rootPath().needsDevice()) { + Terminal::Hooks::instance().openTerminal( + {CommandLine{shell, {}}, workingDir, runnable.environment}); + } else { + Terminal::Hooks::instance().openTerminal({std::nullopt, workingDir, runnable.environment}); + } } void ProjectExplorerPluginPrivate::removeFile() @@ -3852,7 +3710,7 @@ void ProjectExplorerPluginPrivate::removeFile() if (!siblings.isEmpty()) { const QMessageBox::StandardButton reply = QMessageBox::question( - Core::ICore::dialogParent(), Tr::tr("Remove More Files?"), + ICore::dialogParent(), Tr::tr("Remove More Files?"), Tr::tr("Remove these files as well?\n %1") .arg(Utils::transform(siblings, [](const NodeAndPath &np) { return np.second.fileName(); @@ -4058,41 +3916,6 @@ void ProjectExplorerPluginPrivate::handleSetStartupProject() setStartupProject(ProjectTree::currentProject()); } -void ProjectExplorerPluginPrivate::updateSessionMenu() -{ - m_sessionMenu->clear(); - dd->m_sessionMenu->addAction(dd->m_sessionManagerAction); - dd->m_sessionMenu->addSeparator(); - auto *ag = new QActionGroup(m_sessionMenu); - connect(ag, &QActionGroup::triggered, this, &ProjectExplorerPluginPrivate::setSession); - const QString activeSession = SessionManager::activeSession(); - const bool isDefaultVirgin = SessionManager::isDefaultVirgin(); - - QStringList sessions = SessionManager::sessions(); - std::sort(std::next(sessions.begin()), sessions.end(), [](const QString &s1, const QString &s2) { - return SessionManager::lastActiveTime(s1) > SessionManager::lastActiveTime(s2); - }); - for (int i = 0; i < sessions.size(); ++i) { - const QString &session = sessions[i]; - - const QString actionText = ActionManager::withNumberAccelerator(Utils::quoteAmpersands( - session), - i + 1); - QAction *act = ag->addAction(actionText); - act->setData(session); - act->setCheckable(true); - if (session == activeSession && !isDefaultVirgin) - act->setChecked(true); - } - m_sessionMenu->addActions(ag->actions()); - m_sessionMenu->setEnabled(true); -} - -void ProjectExplorerPluginPrivate::setSession(QAction *action) -{ - SessionManager::loadSession(action->data().toString()); -} - void ProjectExplorerPlugin::setProjectExplorerSettings(const ProjectExplorerSettings &pes) { QTC_ASSERT(dd->m_projectExplorerSettings.environmentId == pes.environmentId, return); @@ -4213,7 +4036,7 @@ void ProjectExplorerPlugin::updateActions() void ProjectExplorerPlugin::activateProjectPanel(Id panelId) { - Core::ModeManager::activateMode(Constants::MODE_SESSION); + ModeManager::activateMode(Constants::MODE_SESSION); dd->m_proWindow->activateProjectPanel(panelId); } @@ -4242,9 +4065,8 @@ RecentProjectsEntries ProjectExplorerPlugin::recentProjects() return dd->recentProjects(); } -void ProjectExplorerPlugin::renameFilesForSymbol( - const QString &oldSymbolName, const QString &newSymbolName, const Utils::FilePaths &files, - bool preferLowerCaseFileNames) +void ProjectExplorerPlugin::renameFilesForSymbol(const QString &oldSymbolName, + const QString &newSymbolName, const FilePaths &files, bool preferLowerCaseFileNames) { static const auto isAllLowerCase = [](const QString &text) { return text.toLower() == text; }; @@ -4319,10 +4141,18 @@ AllProjectFilesFilter::AllProjectFilesFilter() setDefaultIncludedByDefault(false); // but not included in default setFilters({}); setIsCustomFilter(false); - setDescription(Tr::tr( - "Matches all files from all project directories. Append \"+\" or " - "\":\" to jump to the given line number. Append another " - "\"+\" or \":\" to jump to the column number as well.")); + setDescription(Tr::tr("Locates files from all project directories. Append \"+\" or " + "\":\" to jump to the given line number. Append another " + "\"+\" or \":\" to jump to the column number as well.")); + + ProjectManager *projectManager = ProjectManager::instance(); + QTC_ASSERT(projectManager, return); + connect(projectManager, &ProjectManager::projectAdded, this, [this](Project *project) { + addDirectory(project->projectDirectory()); + }); + connect(projectManager, &ProjectManager::projectRemoved, this, [this](Project *project) { + removeDirectory(project->projectDirectory()); + }); } const char kDirectoriesKey[] = "directories"; @@ -4346,102 +4176,104 @@ void AllProjectFilesFilter::restoreState(const QJsonObject &object) DirectoryFilter::restoreState(withoutDirectories); } -RunConfigurationLocatorFilter::RunConfigurationLocatorFilter() +static void setupFilter(ILocatorFilter *filter) { - connect(SessionManager::instance(), &SessionManager::startupProjectChanged, - this, &RunConfigurationLocatorFilter::targetListUpdated); - - targetListUpdated(); + QObject::connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, + filter, [filter] { filter->setEnabled(ProjectManager::startupProject()); }); + filter->setEnabled(ProjectManager::startupProject()); } -void RunConfigurationLocatorFilter::prepareSearch(const QString &entry) -{ - m_result.clear(); - const Target *target = SessionManager::startupTarget(); - if (!target) - return; - for (auto rc : target->runConfigurations()) { - if (rc->displayName().contains(entry, Qt::CaseInsensitive)) - m_result.append(LocatorFilterEntry(this, rc->displayName())); - } -} - -QList RunConfigurationLocatorFilter::matchesFor( - QFutureInterface &future, const QString &entry) -{ - Q_UNUSED(future) - Q_UNUSED(entry) - return m_result; -} - -void RunConfigurationLocatorFilter::targetListUpdated() -{ - setEnabled(SessionManager::startupProject()); // at least one project opened -} +using RunAcceptor = std::function; static RunConfiguration *runConfigurationForDisplayName(const QString &displayName) { - const Project *project = SessionManager::instance()->startupProject(); - if (!project) + const Target *target = ProjectManager::startupTarget(); + if (!target) return nullptr; - const QList runconfigs = project->activeTarget()->runConfigurations(); + const QList runconfigs = target->runConfigurations(); return Utils::findOrDefault(runconfigs, [displayName](RunConfiguration *rc) { return rc->displayName() == displayName; }); } -RunRunConfigurationLocatorFilter::RunRunConfigurationLocatorFilter() +static LocatorMatcherTasks runConfigurationMatchers(const RunAcceptor &acceptor) +{ + using namespace Tasking; + + TreeStorage storage; + + const auto onSetup = [storage, acceptor] { + const QString input = storage->input(); + const Target *target = ProjectManager::startupTarget(); + if (!target) + return; + + LocatorFilterEntries entries; + for (auto rc : target->runConfigurations()) { + if (rc->displayName().contains(input, Qt::CaseInsensitive)) { + LocatorFilterEntry entry; + entry.displayName = rc->displayName(); + entry.acceptor = [name = entry.displayName, acceptor = acceptor] { + RunConfiguration *config = runConfigurationForDisplayName(name); + if (!config) + return AcceptResult(); + acceptor(config); + return AcceptResult(); + }; + entries.append(entry); + } + } + storage->reportOutput(entries); + }; + return {{Sync(onSetup), storage}}; +} + +static void runAcceptor(RunConfiguration *config) +{ + if (!BuildManager::isBuilding(config->project())) + ProjectExplorerPlugin::runRunConfiguration(config, Constants::NORMAL_RUN_MODE, true); +} + +RunConfigurationStartFilter::RunConfigurationStartFilter() { setId("Run run configuration"); - setDisplayName(Tr::tr("Run run configuration")); - setDescription(Tr::tr("Run a run configuration of the current active project")); + setDisplayName(Tr::tr("Run Run Configuration")); + setDescription(Tr::tr("Runs a run configuration of the active project.")); setDefaultShortcutString("rr"); setPriority(Medium); + setupFilter(this); } -void RunRunConfigurationLocatorFilter::accept(const LocatorFilterEntry &selection, QString *newText, - int *selectionStart, int *selectionLength) const +LocatorMatcherTasks RunConfigurationStartFilter::matchers() { - Q_UNUSED(newText) - Q_UNUSED(selectionStart) - Q_UNUSED(selectionLength) - - RunConfiguration *toStart = runConfigurationForDisplayName(selection.displayName); - if (!toStart) - return; - if (!BuildManager::isBuilding(toStart->project())) - ProjectExplorerPlugin::runRunConfiguration(toStart, Constants::NORMAL_RUN_MODE, true); + return runConfigurationMatchers(&runAcceptor); } -SwitchToRunConfigurationLocatorFilter::SwitchToRunConfigurationLocatorFilter() +static void switchAcceptor(RunConfiguration *config) { - setId("Switch run configuration"); - setDisplayName(Tr::tr("Switch run configuration")); - setDescription(Tr::tr("Switch active run configuration")); - setDefaultShortcutString("sr"); - setPriority(Medium); -} - -void SwitchToRunConfigurationLocatorFilter::accept(const LocatorFilterEntry &selection, - QString *newText, int *selectionStart, - int *selectionLength) const -{ - Q_UNUSED(newText) - Q_UNUSED(selectionStart) - Q_UNUSED(selectionLength) - - RunConfiguration *toSwitchTo = runConfigurationForDisplayName(selection.displayName); - if (!toSwitchTo) - return; - - SessionManager::startupTarget()->setActiveRunConfiguration(toSwitchTo); - QTimer::singleShot(200, this, [displayName = selection.displayName] { + ProjectManager::startupTarget()->setActiveRunConfiguration(config); + QTimer::singleShot(200, ICore::mainWindow(), [name = config->displayName()] { if (auto ks = ICore::mainWindow()->findChild("KitSelector.Button")) { - Utils::ToolTip::show(ks->mapToGlobal(QPoint{25, 25}), - Tr::tr("Switched run configuration to\n%1").arg(displayName), - ICore::dialogParent()); + ToolTip::show(ks->mapToGlobal(QPoint{25, 25}), + Tr::tr("Switched run configuration to\n%1").arg(name), + ICore::dialogParent()); } }); } +RunConfigurationSwitchFilter::RunConfigurationSwitchFilter() +{ + setId("Switch run configuration"); + setDisplayName(Tr::tr("Switch Run Configuration")); + setDescription(Tr::tr("Switches the active run configuration of the active project.")); + setDefaultShortcutString("sr"); + setPriority(Medium); + setupFilter(this); +} + +LocatorMatcherTasks RunConfigurationSwitchFilter::matchers() +{ + return runConfigurationMatchers(&switchAcceptor); +} + } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/projectexplorer.h b/src/plugins/projectexplorer/projectexplorer.h index baa8bb83a6b..0b87f53310b 100644 --- a/src/plugins/projectexplorer/projectexplorer.h +++ b/src/plugins/projectexplorer/projectexplorer.h @@ -155,7 +155,6 @@ public: static QThreadPool *sharedThreadPool(); static Internal::MiniProjectTargetSelector *targetSelector(); - static void showSessionManager(); static void openNewProjectDialog(); static void openOpenProjectDialog(); @@ -261,6 +260,9 @@ private slots: void testProject_projectTree(); void testProject_multipleBuildConfigs(); + void testSourceToBinaryMapping(); + void testSourceToBinaryMapping_data(); + void testSessionSwitch(); #endif // WITH_TESTS }; diff --git a/src/plugins/projectexplorer/projectexplorer.qbs b/src/plugins/projectexplorer/projectexplorer.qbs index e65cc1bdc3f..c955a485905 100644 --- a/src/plugins/projectexplorer/projectexplorer.qbs +++ b/src/plugins/projectexplorer/projectexplorer.qbs @@ -45,6 +45,7 @@ Project { "codestylesettingspropertiespage.cpp", "codestylesettingspropertiespage.h", "compileoutputwindow.cpp", "compileoutputwindow.h", "configtaskhandler.cpp", "configtaskhandler.h", + "copystep.cpp", "copystep.h", "copytaskhandler.cpp", "copytaskhandler.h", "currentprojectfilter.cpp", "currentprojectfilter.h", "currentprojectfind.cpp", "currentprojectfind.h", @@ -91,7 +92,6 @@ Project { "ldparser.cpp", "ldparser.h", "lldparser.cpp", "lldparser.h", "linuxiccparser.cpp", "linuxiccparser.h", - "localenvironmentaspect.cpp", "localenvironmentaspect.h", "makestep.cpp", "makestep.h", "miniprojecttargetselector.cpp", "miniprojecttargetselector.h", "msvcparser.cpp", "msvcparser.h", @@ -110,13 +110,12 @@ Project { "projectexplorerconstants.cpp", "projectexplorerconstants.h", "projectexplorericons.h", "projectexplorericons.cpp", - "projectexplorersettings.h", - "projectexplorersettingspage.cpp", "projectexplorersettingspage.h", + "projectexplorersettings.h", "projectexplorersettings.cpp", "projectexplorertr.h", "projectfilewizardextension.cpp", "projectfilewizardextension.h", "projectimporter.cpp", "projectimporter.h", "projectmacro.cpp", "projectmacro.h", - "projectmanager.h", + "projectmanager.cpp", "projectmanager.h", "projectmodels.cpp", "projectmodels.h", "projectnodes.cpp", "projectnodes.h", "projectpanelfactory.cpp", "projectpanelfactory.h", @@ -134,10 +133,6 @@ Project { "runsettingspropertiespage.cpp", "runsettingspropertiespage.h", "sanitizerparser.cpp", "sanitizerparser.h", "selectablefilesmodel.cpp", "selectablefilesmodel.h", - "session.cpp", "session.h", - "sessionmodel.cpp", "sessionmodel.h", - "sessionview.cpp", "sessionview.h", - "sessiondialog.cpp", "sessiondialog.h", "showineditortaskhandler.cpp", "showineditortaskhandler.h", "showoutputtaskhandler.cpp", "showoutputtaskhandler.h", "simpleprojectwizard.cpp", "simpleprojectwizard.h", @@ -226,8 +221,7 @@ Project { "idevicefactory.cpp", "idevicefactory.h", "idevicefwd.h", "idevicewidget.h", - "localprocesslist.cpp", "localprocesslist.h", - "sshdeviceprocesslist.cpp", "sshdeviceprocesslist.h", + "processlist.cpp", "processlist.h", "sshparameters.cpp", "sshparameters.h", "sshsettings.cpp", "sshsettings.h", "sshsettingspage.cpp", "sshsettingspage.h", @@ -250,9 +244,7 @@ Project { ] } - Group { - name: "Tests" - condition: qtc.testsEnabled + QtcTestFiles { files: ["outputparser_test.h", "outputparser_test.cpp"] } diff --git a/src/plugins/projectexplorer/projectexplorer.qrc b/src/plugins/projectexplorer/projectexplorer.qrc index ececb0854b3..0cc88e3331a 100644 --- a/src/plugins/projectexplorer/projectexplorer.qrc +++ b/src/plugins/projectexplorer/projectexplorer.qrc @@ -86,5 +86,13 @@ images/settingscategory_cpp@2x.png images/importasproject.png images/importasproject@2x.png + testdata/multi-target-project/CMakeLists.txt + testdata/multi-target-project/multi-target-project-app.pro + testdata/multi-target-project/multi-target-project-lib.cpp + testdata/multi-target-project/multi-target-project-lib.pro + testdata/multi-target-project/multi-target-project-main.cpp + testdata/multi-target-project/multi-target-project-shared.h + testdata/multi-target-project/multi-target-project.pro + testdata/multi-target-project/multi-target-project.qbs diff --git a/src/plugins/projectexplorer/projectexplorerconstants.h b/src/plugins/projectexplorer/projectexplorerconstants.h index 9c86d6d9cf4..75054120a83 100644 --- a/src/plugins/projectexplorer/projectexplorerconstants.h +++ b/src/plugins/projectexplorer/projectexplorerconstants.h @@ -132,6 +132,10 @@ const char BUILDSTEPS_CLEAN[] = "ProjectExplorer.BuildSteps.Clean"; const char BUILDSTEPS_BUILD[] = "ProjectExplorer.BuildSteps.Build"; const char BUILDSTEPS_DEPLOY[] = "ProjectExplorer.BuildSteps.Deploy"; +const char COPY_FILE_STEP[] = "ProjectExplorer.CopyFileStep"; +const char COPY_DIRECTORY_STEP[] = "ProjectExplorer.CopyDirectoryStep"; +const char DEVICE_CHECK_STEP[] = "ProjectExplorer.DeviceCheckBuildStep"; + // Language // Keep these short: These constants are exposed to the MacroExplorer! @@ -202,8 +206,6 @@ const char FILEOVERLAY_UNKNOWN[]=":/projectexplorer/images/fileoverlay_unknown.p // Settings const char ADD_FILES_DIALOG_FILTER_HISTORY_KEY[] = "ProjectExplorer.AddFilesFilterKey"; const char PROJECT_ROOT_PATH_KEY[] = "ProjectExplorer.Project.RootPath"; -const char STARTUPSESSION_KEY[] = "ProjectExplorer/SessionToRestore"; -const char LASTSESSION_KEY[] = "ProjectExplorer/StartupSession"; const char SETTINGS_MENU_HIDE_BUILD[] = "Menu/HideBuild"; const char SETTINGS_MENU_HIDE_DEBUG[] = "Menu/HideDebug"; const char SETTINGS_MENU_HIDE_ANALYZE[] = "Menu/HideAnalyze"; diff --git a/src/plugins/projectexplorer/projectexplorersettingspage.cpp b/src/plugins/projectexplorer/projectexplorersettings.cpp similarity index 89% rename from src/plugins/projectexplorer/projectexplorersettingspage.cpp rename to src/plugins/projectexplorer/projectexplorersettings.cpp index 30eb850cd0c..14f66b885bf 100644 --- a/src/plugins/projectexplorer/projectexplorersettingspage.cpp +++ b/src/plugins/projectexplorer/projectexplorersettings.cpp @@ -1,7 +1,7 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "projectexplorersettingspage.h" +#include "projectexplorersettings.h" #include "projectexplorer.h" #include "projectexplorerconstants.h" @@ -25,15 +25,14 @@ using namespace Core; using namespace Utils; -namespace ProjectExplorer { -namespace Internal { +namespace ProjectExplorer::Internal { enum { UseCurrentDirectory, UseProjectDirectory }; -class ProjectExplorerSettingsWidget : public QWidget +class ProjectExplorerSettingsWidget : public IOptionsPageWidget { public: - explicit ProjectExplorerSettingsWidget(QWidget *parent = nullptr); + ProjectExplorerSettingsWidget(); ProjectExplorerSettings settings() const; void setSettings(const ProjectExplorerSettings &s); @@ -44,6 +43,13 @@ public: bool useProjectsDirectory(); void setUseProjectsDirectory(bool v); + void apply() final + { + ProjectExplorerPlugin::setProjectExplorerSettings(settings()); + DocumentManager::setProjectsDirectory(projectsDirectory()); + DocumentManager::setUseProjectsDirectory(useProjectsDirectory()); + } + private: void slotDirectoryButtonGroupChanged(); @@ -68,8 +74,7 @@ private: QButtonGroup *m_directoryButtonGroup; }; -ProjectExplorerSettingsWidget::ProjectExplorerSettingsWidget(QWidget *parent) : - QWidget(parent) +ProjectExplorerSettingsWidget::ProjectExplorerSettingsWidget() { m_currentDirectoryRadioButton = new QRadioButton(Tr::tr("Current directory")); m_directoryRadioButton = new QRadioButton(Tr::tr("Directory")); @@ -117,7 +122,7 @@ ProjectExplorerSettingsWidget::ProjectExplorerSettingsWidget(QWidget *parent) : "Disable it if you experience problems with your builds."); jomLabel->setWordWrap(true); - using namespace Utils::Layouting; + using namespace Layouting; Column { Group { title(Tr::tr("Projects Directory")), @@ -165,6 +170,10 @@ ProjectExplorerSettingsWidget::ProjectExplorerSettingsWidget(QWidget *parent) : connect(m_directoryButtonGroup, &QButtonGroup::buttonClicked, this, &ProjectExplorerSettingsWidget::slotDirectoryButtonGroupChanged); + + setSettings(ProjectExplorerPlugin::projectExplorerSettings()); + setProjectsDirectory(DocumentManager::projectsDirectory()); + setUseProjectsDirectory(DocumentManager::useProjectsDirectory()); } ProjectExplorerSettings ProjectExplorerSettingsWidget::settings() const @@ -236,7 +245,8 @@ void ProjectExplorerSettingsWidget::slotDirectoryButtonGroupChanged() m_projectsDirectoryPathChooser->setEnabled(enable); } -// ------------------ ProjectExplorerSettingsPage +// ProjectExplorerSettingsPage + ProjectExplorerSettingsPage::ProjectExplorerSettingsPage() { setId(Constants::BUILD_AND_RUN_SETTINGS_PAGE_ID); @@ -244,32 +254,7 @@ ProjectExplorerSettingsPage::ProjectExplorerSettingsPage() setCategory(Constants::BUILD_AND_RUN_SETTINGS_CATEGORY); setDisplayCategory(Tr::tr("Build & Run")); setCategoryIconPath(":/projectexplorer/images/settingscategory_buildrun.png"); + setWidgetCreator([] { return new ProjectExplorerSettingsWidget; }); } -QWidget *ProjectExplorerSettingsPage::widget() -{ - if (!m_widget) { - m_widget = new ProjectExplorerSettingsWidget; - m_widget->setSettings(ProjectExplorerPlugin::projectExplorerSettings()); - m_widget->setProjectsDirectory(DocumentManager::projectsDirectory()); - m_widget->setUseProjectsDirectory(DocumentManager::useProjectsDirectory()); - } - return m_widget; -} - -void ProjectExplorerSettingsPage::apply() -{ - if (m_widget) { - ProjectExplorerPlugin::setProjectExplorerSettings(m_widget->settings()); - DocumentManager::setProjectsDirectory(m_widget->projectsDirectory()); - DocumentManager::setUseProjectsDirectory(m_widget->useProjectsDirectory()); - } -} - -void ProjectExplorerSettingsPage::finish() -{ - delete m_widget; -} - -} // namespace Internal -} // namespace ProjectExplorer +} // ProjectExplorer::Internal diff --git a/src/plugins/projectexplorer/projectexplorersettings.h b/src/plugins/projectexplorer/projectexplorersettings.h index b4b80124cd8..48cc85c414b 100644 --- a/src/plugins/projectexplorer/projectexplorersettings.h +++ b/src/plugins/projectexplorer/projectexplorersettings.h @@ -4,6 +4,8 @@ #pragma once #include +#include + #include #include @@ -23,7 +25,6 @@ public: && p1.deployBeforeRun == p2.deployBeforeRun && p1.saveBeforeBuild == p2.saveBeforeBuild && p1.useJom == p2.useJom - && p1.autorestoreLastSession == p2.autorestoreLastSession && p1.prompToStopRunControl == p2.prompToStopRunControl && p1.automaticallyCreateRunConfigurations == p2.automaticallyCreateRunConfigurations && p1.addLibraryPathsToRunEnv == p2.addLibraryPathsToRunEnv @@ -40,7 +41,6 @@ public: bool deployBeforeRun = true; bool saveBeforeBuild = false; bool useJom = true; - bool autorestoreLastSession = false; // This option is set in the Session Manager! bool prompToStopRunControl = false; bool automaticallyCreateRunConfigurations = true; bool addLibraryPathsToRunEnv = true; @@ -74,12 +74,10 @@ public: int maxCharCount = Core::Constants::DEFAULT_MAX_CHAR_COUNT; }; -class CompileOutputSettings +class ProjectExplorerSettingsPage : public Core::IOptionsPage { public: - bool popUp = false; - bool wrapOutput = false; - int maxCharCount = Core::Constants::DEFAULT_MAX_CHAR_COUNT; + ProjectExplorerSettingsPage(); }; } // namespace Internal diff --git a/src/plugins/projectexplorer/projectexplorersettingspage.h b/src/plugins/projectexplorer/projectexplorersettingspage.h deleted file mode 100644 index 82764c38506..00000000000 --- a/src/plugins/projectexplorer/projectexplorersettingspage.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -#include - -namespace ProjectExplorer { -namespace Internal { - -class ProjectExplorerSettingsWidget; - -class ProjectExplorerSettingsPage : public Core::IOptionsPage -{ -public: - ProjectExplorerSettingsPage(); - - QWidget *widget() override; - void apply() override; - void finish() override; - -private: - QPointer m_widget; -}; - -} // namespace Internal -} // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/projectfilewizardextension.cpp b/src/plugins/projectexplorer/projectfilewizardextension.cpp index 53d3eb15781..134db6ea484 100644 --- a/src/plugins/projectexplorer/projectfilewizardextension.cpp +++ b/src/plugins/projectexplorer/projectfilewizardextension.cpp @@ -7,10 +7,10 @@ #include "project.h" #include "projectexplorerconstants.h" #include "projectexplorertr.h" +#include "projectmanager.h" #include "projectnodes.h" #include "projecttree.h" #include "projectwizardpage.h" -#include "session.h" #include @@ -27,7 +27,6 @@ #include #include -#include #include #include #include @@ -134,7 +133,7 @@ Node *ProjectFileWizardExtension::findWizardContextNode(Node *contextNode, Proje const FilePath &path) { if (contextNode && !ProjectTree::hasNode(contextNode)) { - if (SessionManager::projects().contains(project) && project->rootProjectNode()) { + if (ProjectManager::projects().contains(project) && project->rootProjectNode()) { contextNode = project->rootProjectNode()->findNode([path](const Node *n) { return path == n->filePath(); }); diff --git a/src/plugins/projectexplorer/projectmanager.cpp b/src/plugins/projectexplorer/projectmanager.cpp new file mode 100644 index 00000000000..e3c8e65c59b --- /dev/null +++ b/src/plugins/projectexplorer/projectmanager.cpp @@ -0,0 +1,768 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "projectmanager.h" + + +#include "buildconfiguration.h" +#include "editorconfiguration.h" +#include "project.h" +#include "projectexplorer.h" +#include "projectexplorerconstants.h" +#include "projectexplorertr.h" +#include "projectmanager.h" +#include "projectnodes.h" +#include "target.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef WITH_TESTS +#include +#include +#include +#endif + +using namespace Core; +using namespace Utils; +using namespace ProjectExplorer::Internal; + +namespace ProjectExplorer { + +class ProjectManagerPrivate +{ +public: + void loadSession(); + void saveSession(); + void restoreDependencies(); + void restoreStartupProject(); + void restoreProjects(const FilePaths &fileList); + void askUserAboutFailedProjects(); + + bool recursiveDependencyCheck(const FilePath &newDep, const FilePath &checkDep) const; + FilePaths dependencies(const FilePath &proName) const; + FilePaths dependenciesOrder() const; + void dependencies(const FilePath &proName, FilePaths &result) const; + + static QString windowTitleAddition(const FilePath &filePath); + static QString sessionTitle(const FilePath &filePath); + + bool hasProjects() const { return !m_projects.isEmpty(); } + + bool m_casadeSetActive = false; + + Project *m_startupProject = nullptr; + QList m_projects; + FilePaths m_failedProjects; + QMap m_depMap; + +private: + static QString locationInProject(const FilePath &filePath); +}; + +static ProjectManager *m_instance = nullptr; +static ProjectManagerPrivate *d = nullptr; + +static QString projectFolderId(Project *pro) +{ + return pro->projectFilePath().toString(); +} + +const int PROJECT_SORT_VALUE = 100; + +ProjectManager::ProjectManager() +{ + m_instance = this; + d = new ProjectManagerPrivate; + + connect(EditorManager::instance(), &EditorManager::editorCreated, + this, &ProjectManager::configureEditor); + connect(this, &ProjectManager::projectAdded, + EditorManager::instance(), &EditorManager::updateWindowTitles); + connect(this, &ProjectManager::projectRemoved, + EditorManager::instance(), &EditorManager::updateWindowTitles); + connect(this, &ProjectManager::projectDisplayNameChanged, + EditorManager::instance(), &EditorManager::updateWindowTitles); + + EditorManager::setWindowTitleAdditionHandler(&ProjectManagerPrivate::windowTitleAddition); + EditorManager::setSessionTitleHandler(&ProjectManagerPrivate::sessionTitle); + + connect(SessionManager::instance(), &SessionManager::aboutToLoadSession, this, [] { + d->loadSession(); + }); + connect(SessionManager::instance(), &SessionManager::aboutToSaveSession, this, [] { + d->saveSession(); + }); +} + +ProjectManager::~ProjectManager() +{ + EditorManager::setWindowTitleAdditionHandler({}); + EditorManager::setSessionTitleHandler({}); + delete d; + d = nullptr; +} + +ProjectManager *ProjectManager::instance() +{ + return m_instance; +} + +bool ProjectManagerPrivate::recursiveDependencyCheck(const FilePath &newDep, + const FilePath &checkDep) const +{ + if (newDep == checkDep) + return false; + + const FilePaths depList = m_depMap.value(checkDep); + for (const FilePath &dependency : depList) { + if (!recursiveDependencyCheck(newDep, dependency)) + return false; + } + + return true; +} + +/* + * The dependency management exposes an interface based on projects, but + * is internally purely string based. This is suboptimal. Probably it would be + * nicer to map the filenames to projects on load and only map it back to + * filenames when saving. + */ + +QList ProjectManager::dependencies(const Project *project) +{ + const FilePath proName = project->projectFilePath(); + const FilePaths proDeps = d->m_depMap.value(proName); + + QList projects; + for (const FilePath &dep : proDeps) { + Project *pro = Utils::findOrDefault(d->m_projects, [&dep](Project *p) { + return p->projectFilePath() == dep; + }); + if (pro) + projects += pro; + } + + return projects; +} + +bool ProjectManager::hasDependency(const Project *project, const Project *depProject) +{ + const FilePath proName = project->projectFilePath(); + const FilePath depName = depProject->projectFilePath(); + + const FilePaths proDeps = d->m_depMap.value(proName); + return proDeps.contains(depName); +} + +bool ProjectManager::canAddDependency(const Project *project, const Project *depProject) +{ + const FilePath newDep = project->projectFilePath(); + const FilePath checkDep = depProject->projectFilePath(); + + return d->recursiveDependencyCheck(newDep, checkDep); +} + +bool ProjectManager::addDependency(Project *project, Project *depProject) +{ + const FilePath proName = project->projectFilePath(); + const FilePath depName = depProject->projectFilePath(); + + // check if this dependency is valid + if (!d->recursiveDependencyCheck(proName, depName)) + return false; + + FilePaths proDeps = d->m_depMap.value(proName); + if (!proDeps.contains(depName)) { + proDeps.append(depName); + d->m_depMap[proName] = proDeps; + } + emit m_instance->dependencyChanged(project, depProject); + + return true; +} + +void ProjectManager::removeDependency(Project *project, Project *depProject) +{ + const FilePath proName = project->projectFilePath(); + const FilePath depName = depProject->projectFilePath(); + + FilePaths proDeps = d->m_depMap.value(proName); + proDeps.removeAll(depName); + if (proDeps.isEmpty()) + d->m_depMap.remove(proName); + else + d->m_depMap[proName] = proDeps; + emit m_instance->dependencyChanged(project, depProject); +} + +bool ProjectManager::isProjectConfigurationCascading() +{ + return d->m_casadeSetActive; +} + +void ProjectManager::setProjectConfigurationCascading(bool b) +{ + d->m_casadeSetActive = b; + SessionManager::markSessionFileDirty(); +} + +void ProjectManager::setStartupProject(Project *startupProject) +{ + QTC_ASSERT((!startupProject && d->m_projects.isEmpty()) + || (startupProject && d->m_projects.contains(startupProject)), return); + + if (d->m_startupProject == startupProject) + return; + + d->m_startupProject = startupProject; + if (d->m_startupProject && d->m_startupProject->needsConfiguration()) { + ModeManager::activateMode(Constants::MODE_SESSION); + ModeManager::setFocusToCurrentMode(); + } + FolderNavigationWidgetFactory::setFallbackSyncFilePath( + startupProject ? startupProject->projectFilePath().parentDir() : FilePath()); + emit m_instance->startupProjectChanged(startupProject); +} + +Project *ProjectManager::startupProject() +{ + return d->m_startupProject; +} + +Target *ProjectManager::startupTarget() +{ + return d->m_startupProject ? d->m_startupProject->activeTarget() : nullptr; +} + +BuildSystem *ProjectManager::startupBuildSystem() +{ + Target *t = startupTarget(); + return t ? t->buildSystem() : nullptr; +} + +/*! + * Returns the RunConfiguration of the currently active target + * of the startup project, if such exists, or \c nullptr otherwise. + */ + + +RunConfiguration *ProjectManager::startupRunConfiguration() +{ + Target *t = startupTarget(); + return t ? t->activeRunConfiguration() : nullptr; +} + +void ProjectManager::addProject(Project *pro) +{ + QTC_ASSERT(pro, return); + QTC_CHECK(!pro->displayName().isEmpty()); + QTC_CHECK(pro->id().isValid()); + + SessionManager::markSessionFileDirty(); + QTC_ASSERT(!d->m_projects.contains(pro), return); + + d->m_projects.append(pro); + + connect(pro, &Project::displayNameChanged, + m_instance, [pro]() { emit m_instance->projectDisplayNameChanged(pro); }); + + emit m_instance->projectAdded(pro); + const auto updateFolderNavigation = [pro] { + // destructing projects might trigger changes, so check if the project is actually there + if (QTC_GUARD(d->m_projects.contains(pro))) { + const QIcon icon = pro->rootProjectNode() ? pro->rootProjectNode()->icon() : QIcon(); + FolderNavigationWidgetFactory::insertRootDirectory({projectFolderId(pro), + PROJECT_SORT_VALUE, + pro->displayName(), + pro->projectFilePath().parentDir(), + icon}); + } + }; + updateFolderNavigation(); + configureEditors(pro); + connect(pro, &Project::fileListChanged, m_instance, [pro, updateFolderNavigation]() { + configureEditors(pro); + updateFolderNavigation(); // update icon + }); + connect(pro, &Project::displayNameChanged, m_instance, updateFolderNavigation); + + if (!startupProject()) + setStartupProject(pro); +} + +void ProjectManager::removeProject(Project *project) +{ + SessionManager::markSessionFileDirty(); + QTC_ASSERT(project, return); + removeProjects({project}); +} + +void ProjectManagerPrivate::saveSession() +{ + // save the startup project + if (d->m_startupProject) + SessionManager::setSessionValue("StartupProject", + m_startupProject->projectFilePath().toSettings()); + + FilePaths projectFiles = Utils::transform(m_projects, &Project::projectFilePath); + // Restore information on projects that failed to load: + // don't read projects to the list, which the user loaded + for (const FilePath &failed : std::as_const(m_failedProjects)) { + if (!projectFiles.contains(failed)) + projectFiles << failed; + } + + SessionManager::setSessionValue("ProjectList", + Utils::transform(projectFiles, + &FilePath::toString)); + SessionManager::setSessionValue("CascadeSetActive", m_casadeSetActive); + + QVariantMap depMap; + auto i = m_depMap.constBegin(); + while (i != m_depMap.constEnd()) { + QString key = i.key().toString(); + QStringList values; + const FilePaths valueList = i.value(); + for (const FilePath &value : valueList) + values << value.toString(); + depMap.insert(key, values); + ++i; + } + SessionManager::setSessionValue(QLatin1String("ProjectDependencies"), QVariant(depMap)); +} + +/*! + Closes all projects + */ +void ProjectManager::closeAllProjects() +{ + removeProjects(projects()); +} + +const QList ProjectManager::projects() +{ + return d->m_projects; +} + +bool ProjectManager::hasProjects() +{ + return d->hasProjects(); +} + +bool ProjectManager::hasProject(Project *p) +{ + return d->m_projects.contains(p); +} + +FilePaths ProjectManagerPrivate::dependencies(const FilePath &proName) const +{ + FilePaths result; + dependencies(proName, result); + return result; +} + +void ProjectManagerPrivate::dependencies(const FilePath &proName, FilePaths &result) const +{ + const FilePaths depends = m_depMap.value(proName); + + for (const FilePath &dep : depends) + dependencies(dep, result); + + if (!result.contains(proName)) + result.append(proName); +} + +QString ProjectManagerPrivate::sessionTitle(const FilePath &filePath) +{ + const QString sessionName = SessionManager::activeSession(); + if (SessionManager::isDefaultSession(sessionName)) { + if (filePath.isEmpty()) { + // use single project's name if there is only one loaded. + const QList projects = ProjectManager::projects(); + if (projects.size() == 1) + return projects.first()->displayName(); + } + } else { + return sessionName.isEmpty() ? Tr::tr("Untitled") : sessionName; + } + return QString(); +} + +QString ProjectManagerPrivate::locationInProject(const FilePath &filePath) +{ + const Project *project = ProjectManager::projectForFile(filePath); + if (!project) + return QString(); + + const FilePath parentDir = filePath.parentDir(); + if (parentDir == project->projectDirectory()) + return "@ " + project->displayName(); + + if (filePath.isChildOf(project->projectDirectory())) { + const FilePath dirInProject = parentDir.relativeChildPath(project->projectDirectory()); + return "(" + dirInProject.toUserOutput() + " @ " + project->displayName() + ")"; + } + + // For a file that is "outside" the project it belongs to, we display its + // dir's full path because it is easier to read than a series of "../../.". + // Example: /home/hugo/GenericProject/App.files lists /home/hugo/lib/Bar.cpp + return "(" + parentDir.toUserOutput() + " @ " + project->displayName() + ")"; +} + +QString ProjectManagerPrivate::windowTitleAddition(const FilePath &filePath) +{ + return filePath.isEmpty() ? QString() : locationInProject(filePath); +} + +FilePaths ProjectManagerPrivate::dependenciesOrder() const +{ + QList> unordered; + FilePaths ordered; + + // copy the map to a temporary list + for (const Project *pro : m_projects) { + const FilePath proName = pro->projectFilePath(); + const FilePaths depList = filtered(m_depMap.value(proName), + [this](const FilePath &proPath) { + return contains(m_projects, [proPath](const Project *p) { + return p->projectFilePath() == proPath; + }); + }); + unordered.push_back({proName, depList}); + } + + while (!unordered.isEmpty()) { + for (int i = (unordered.count() - 1); i >= 0; --i) { + if (unordered.at(i).second.isEmpty()) { + ordered << unordered.at(i).first; + unordered.removeAt(i); + } + } + + // remove the handled projects from the dependency lists + // of the remaining unordered projects + for (int i = 0; i < unordered.count(); ++i) { + for (const FilePath &pro : std::as_const(ordered)) { + FilePaths depList = unordered.at(i).second; + depList.removeAll(pro); + unordered[i].second = depList; + } + } + } + + return ordered; +} + +QList ProjectManager::projectOrder(const Project *project) +{ + QList result; + + FilePaths pros; + if (project) + pros = d->dependencies(project->projectFilePath()); + else + pros = d->dependenciesOrder(); + + for (const FilePath &proFile : std::as_const(pros)) { + for (Project *pro : projects()) { + if (pro->projectFilePath() == proFile) { + result << pro; + break; + } + } + } + + return result; +} + +Project *ProjectManager::projectForFile(const FilePath &fileName) +{ + if (Project * const project = Utils::findOrDefault(ProjectManager::projects(), + [&fileName](const Project *p) { return p->isKnownFile(fileName); })) { + return project; + } + return Utils::findOrDefault(ProjectManager::projects(), + [&fileName](const Project *p) { + for (const Target * const target : p->targets()) { + for (const BuildConfiguration * const bc : target->buildConfigurations()) { + if (fileName.isChildOf(bc->buildDirectory())) + return false; + } + } + return fileName.isChildOf(p->projectDirectory()); + }); +} + +Project *ProjectManager::projectWithProjectFilePath(const FilePath &filePath) +{ + return Utils::findOrDefault(ProjectManager::projects(), + [&filePath](const Project *p) { return p->projectFilePath() == filePath; }); +} + +void ProjectManager::configureEditor(IEditor *editor, const QString &fileName) +{ + if (auto textEditor = qobject_cast(editor)) { + Project *project = projectForFile(Utils::FilePath::fromString(fileName)); + // Global settings are the default. + if (project) + project->editorConfiguration()->configureEditor(textEditor); + } +} + +void ProjectManager::configureEditors(Project *project) +{ + const QList documents = DocumentModel::openedDocuments(); + for (IDocument *document : documents) { + if (project->isKnownFile(document->filePath())) { + const QList editors = DocumentModel::editorsForDocument(document); + for (IEditor *editor : editors) { + if (auto textEditor = qobject_cast(editor)) { + project->editorConfiguration()->configureEditor(textEditor); + } + } + } + } +} + +void ProjectManager::removeProjects(const QList &remove) +{ + for (Project *pro : remove) + emit m_instance->aboutToRemoveProject(pro); + + bool changeStartupProject = false; + + // Delete projects + for (Project *pro : remove) { + pro->saveSettings(); + pro->markAsShuttingDown(); + + // Remove the project node: + d->m_projects.removeOne(pro); + + if (pro == d->m_startupProject) + changeStartupProject = true; + + FolderNavigationWidgetFactory::removeRootDirectory(projectFolderId(pro)); + disconnect(pro, nullptr, m_instance, nullptr); + emit m_instance->projectRemoved(pro); + } + + if (changeStartupProject) + setStartupProject(hasProjects() ? projects().first() : nullptr); + + qDeleteAll(remove); +} + +void ProjectManagerPrivate::restoreDependencies() +{ + QMap depMap = SessionManager::sessionValue("ProjectDependencies").toMap(); + auto i = depMap.constBegin(); + while (i != depMap.constEnd()) { + const QString &key = i.key(); + FilePaths values; + const QStringList valueList = i.value().toStringList(); + for (const QString &value : valueList) + values << FilePath::fromString(value); + m_depMap.insert(FilePath::fromString(key), values); + ++i; + } +} + +void ProjectManagerPrivate::askUserAboutFailedProjects() +{ + FilePaths failedProjects = m_failedProjects; + if (!failedProjects.isEmpty()) { + QString fileList = FilePath::formatFilePaths(failedProjects, "
"); + QMessageBox box(QMessageBox::Warning, + Tr::tr("Failed to restore project files"), + Tr::tr("Could not restore the following project files:
%1"). + arg(fileList)); + auto keepButton = new QPushButton(Tr::tr("Keep projects in Session"), &box); + auto removeButton = new QPushButton(Tr::tr("Remove projects from Session"), &box); + box.addButton(keepButton, QMessageBox::AcceptRole); + box.addButton(removeButton, QMessageBox::DestructiveRole); + + box.exec(); + + if (box.clickedButton() == removeButton) + m_failedProjects.clear(); + } +} + +void ProjectManagerPrivate::restoreStartupProject() +{ + const FilePath startupProject = FilePath::fromSettings( + SessionManager::sessionValue("StartupProject")); + if (!startupProject.isEmpty()) { + for (Project *pro : std::as_const(m_projects)) { + if (pro->projectFilePath() == startupProject) { + m_instance->setStartupProject(pro); + break; + } + } + } + if (!m_startupProject) { + if (!startupProject.isEmpty()) + qWarning() << "Could not find startup project" << startupProject; + if (hasProjects()) + m_instance->setStartupProject(m_projects.first()); + } +} + +/*! + Loads a session, takes a session name (not filename). +*/ +void ProjectManagerPrivate::restoreProjects(const FilePaths &fileList) +{ + // indirectly adds projects to session + // Keep projects that failed to load in the session! + m_failedProjects = fileList; + if (!fileList.isEmpty()) { + ProjectExplorerPlugin::OpenProjectResult result = ProjectExplorerPlugin::openProjects(fileList); + if (!result) + ProjectExplorerPlugin::showOpenProjectError(result); + const QList projects = result.projects(); + for (const Project *p : projects) + m_failedProjects.removeAll(p->projectFilePath()); + } +} + +void ProjectManagerPrivate::loadSession() +{ + d->m_failedProjects.clear(); + d->m_depMap.clear(); + d->m_casadeSetActive = false; + + // not ideal that this is in ProjectManager + Id modeId = Id::fromSetting(SessionManager::value(QLatin1String("ActiveMode"))); + if (!modeId.isValid()) + modeId = Id(Core::Constants::MODE_EDIT); + + // find a list of projects to close later + const FilePaths fileList = FileUtils::toFilePathList( + SessionManager::sessionValue("ProjectList").toStringList()); + const QList projectsToRemove + = Utils::filtered(ProjectManager::projects(), [&fileList](Project *p) { + return !fileList.contains(p->projectFilePath()); + }); + const QList openProjects = ProjectManager::projects(); + const FilePaths projectPathsToLoad + = Utils::filtered(fileList, [&openProjects](const FilePath &path) { + return !Utils::contains(openProjects, + [&path](Project *p) { return p->projectFilePath() == path; }); + }); + + SessionManager::addSessionLoadingSteps(projectPathsToLoad.count()); + + d->restoreProjects(projectPathsToLoad); + d->restoreDependencies(); + d->restoreStartupProject(); + + // only remove old projects now that the startup project is set! + ProjectManager::removeProjects(projectsToRemove); + + // Fall back to Project mode if the startup project is unconfigured and + // use the mode saved in the session otherwise + if (d->m_startupProject && d->m_startupProject->needsConfiguration()) + modeId = Id(Constants::MODE_SESSION); + + ModeManager::activateMode(modeId); + ModeManager::setFocusToCurrentMode(); + + d->m_casadeSetActive = SessionManager::sessionValue("CascadeSetActive", false).toBool(); + + // Starts a event loop, better do that at the very end + QMetaObject::invokeMethod(m_instance, [this] { askUserAboutFailedProjects(); }); +} + +FilePaths ProjectManager::projectsForSessionName(const QString &session) +{ + const FilePath fileName = SessionManager::sessionNameToFileName(session); + PersistentSettingsReader reader; + if (fileName.exists()) { + if (!reader.load(fileName)) { + qWarning() << "Could not restore session" << fileName.toUserOutput(); + return {}; + } + } + return transform(reader.restoreValue(QLatin1String("ProjectList")).toStringList(), + &FilePath::fromUserInput); +} + +#ifdef WITH_TESTS + +void ProjectExplorerPlugin::testSessionSwitch() +{ + QVERIFY(SessionManager::createSession("session1")); + QVERIFY(SessionManager::createSession("session2")); + QTemporaryFile cppFile("main.cpp"); + QVERIFY(cppFile.open()); + cppFile.close(); + QTemporaryFile projectFile1("XXXXXX.pro"); + QTemporaryFile projectFile2("XXXXXX.pro"); + struct SessionSpec { + SessionSpec(const QString &n, QTemporaryFile &f) : name(n), projectFile(f) {} + const QString name; + QTemporaryFile &projectFile; + }; + std::vector sessionSpecs{SessionSpec("session1", projectFile1), + SessionSpec("session2", projectFile2)}; + for (const SessionSpec &sessionSpec : sessionSpecs) { + static const QByteArray proFileContents + = "TEMPLATE = app\n" + "CONFIG -= qt\n" + "SOURCES = " + cppFile.fileName().toLocal8Bit(); + QVERIFY(sessionSpec.projectFile.open()); + sessionSpec.projectFile.write(proFileContents); + sessionSpec.projectFile.close(); + QVERIFY(SessionManager::loadSession(sessionSpec.name)); + const OpenProjectResult openResult + = ProjectExplorerPlugin::openProject( + FilePath::fromString(sessionSpec.projectFile.fileName())); + if (openResult.errorMessage().contains("text/plain")) + QSKIP("This test requires the presence of QmakeProjectManager to be fully functional"); + QVERIFY(openResult); + QCOMPARE(openResult.projects().count(), 1); + QVERIFY(openResult.project()); + QCOMPARE(ProjectManager::projects().count(), 1); + } + for (int i = 0; i < 30; ++i) { + QVERIFY(SessionManager::loadSession("session1")); + QCOMPARE(SessionManager::activeSession(), "session1"); + QCOMPARE(ProjectManager::projects().count(), 1); + QVERIFY(SessionManager::loadSession("session2")); + QCOMPARE(SessionManager::activeSession(), "session2"); + QCOMPARE(ProjectManager::projects().count(), 1); + } + QVERIFY(SessionManager::loadSession("session1")); + ProjectManager::closeAllProjects(); + QVERIFY(SessionManager::loadSession("session2")); + ProjectManager::closeAllProjects(); + QVERIFY(SessionManager::deleteSession("session1")); + QVERIFY(SessionManager::deleteSession("session2")); +} + +#endif // WITH_TESTS + +} // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/projectmanager.h b/src/plugins/projectexplorer/projectmanager.h index 9d54eba572c..f49cc96e54c 100644 --- a/src/plugins/projectexplorer/projectmanager.h +++ b/src/plugins/projectexplorer/projectmanager.h @@ -6,20 +6,35 @@ #include "projectexplorer_export.h" #include +#include + +namespace Core { class IEditor; } #include namespace Utils { class FilePath; +using FilePaths = QList; class MimeType; } // Utils namespace ProjectExplorer { +class BuildSystem; class Project; +class RunConfiguration; +class Target; -class PROJECTEXPLORER_EXPORT ProjectManager +class PROJECTEXPLORER_EXPORT ProjectManager : public QObject { + Q_OBJECT + +public: + ProjectManager(); + ~ProjectManager() override; + + static ProjectManager *instance(); + public: static bool canOpenProjectForMimeType(const Utils::MimeType &mt); static Project *openProject(const Utils::MimeType &mt, const Utils::FilePath &fileName); @@ -32,7 +47,59 @@ public: }); } + static void closeAllProjects(); + + static void addProject(Project *project); + static void removeProject(Project *project); + static void removeProjects(const QList &remove); + + static void setStartupProject(Project *startupProject); + + static QList dependencies(const Project *project); + static bool hasDependency(const Project *project, const Project *depProject); + static bool canAddDependency(const Project *project, const Project *depProject); + static bool addDependency(Project *project, Project *depProject); + static void removeDependency(Project *project, Project *depProject); + + static bool isProjectConfigurationCascading(); + static void setProjectConfigurationCascading(bool b); + + static Project *startupProject(); + static Target *startupTarget(); + static BuildSystem *startupBuildSystem(); + static RunConfiguration *startupRunConfiguration(); + + static const QList projects(); + static bool hasProjects(); + static bool hasProject(Project *p); + + // NBS rewrite projectOrder (dependency management) + static QList projectOrder(const Project *project = nullptr); + + static Project *projectForFile(const Utils::FilePath &fileName); + static Project *projectWithProjectFilePath(const Utils::FilePath &filePath); + + static Utils::FilePaths projectsForSessionName(const QString &session); + +signals: + void targetAdded(ProjectExplorer::Target *target); + void targetRemoved(ProjectExplorer::Target *target); + void projectAdded(ProjectExplorer::Project *project); + void aboutToRemoveProject(ProjectExplorer::Project *project); + void projectDisplayNameChanged(ProjectExplorer::Project *project); + void projectRemoved(ProjectExplorer::Project *project); + + void startupProjectChanged(ProjectExplorer::Project *project); + + void dependencyChanged(ProjectExplorer::Project *a, ProjectExplorer::Project *b); + + // for tests only + void projectFinishedParsing(ProjectExplorer::Project *project); + private: + static void configureEditor(Core::IEditor *editor, const QString &fileName); + static void configureEditors(Project *project); + static void registerProjectCreator(const QString &mimeType, const std::function &); }; diff --git a/src/plugins/projectexplorer/projectmodels.cpp b/src/plugins/projectexplorer/projectmodels.cpp index 0a761f91e62..9805e654b74 100644 --- a/src/plugins/projectexplorer/projectmodels.cpp +++ b/src/plugins/projectexplorer/projectmodels.cpp @@ -8,8 +8,8 @@ #include "projectnodes.h" #include "projectexplorer.h" #include "projectexplorertr.h" +#include "projectmanager.h" #include "projecttree.h" -#include "session.h" #include "target.h" #include @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -24,8 +25,8 @@ #include #include #include +#include #include -#include #include #include @@ -174,14 +175,15 @@ FlatModel::FlatModel(QObject *parent) ProjectTree *tree = ProjectTree::instance(); connect(tree, &ProjectTree::subtreeChanged, this, &FlatModel::updateSubtree); - SessionManager *sm = SessionManager::instance(); - connect(sm, &SessionManager::projectRemoved, this, &FlatModel::handleProjectRemoved); - connect(sm, &SessionManager::aboutToLoadSession, this, &FlatModel::loadExpandData); - connect(sm, &SessionManager::aboutToSaveSession, this, &FlatModel::saveExpandData); - connect(sm, &SessionManager::projectAdded, this, &FlatModel::handleProjectAdded); - connect(sm, &SessionManager::startupProjectChanged, this, [this] { emit layoutChanged(); }); + ProjectManager *sm = ProjectManager::instance(); + SessionManager *sb = SessionManager::instance(); + connect(sm, &ProjectManager::projectRemoved, this, &FlatModel::handleProjectRemoved); + connect(sb, &SessionManager::aboutToLoadSession, this, &FlatModel::loadExpandData); + connect(sb, &SessionManager::aboutToSaveSession, this, &FlatModel::saveExpandData); + connect(sm, &ProjectManager::projectAdded, this, &FlatModel::handleProjectAdded); + connect(sm, &ProjectManager::startupProjectChanged, this, [this] { emit layoutChanged(); }); - for (Project *project : SessionManager::projects()) + for (Project *project : ProjectManager::projects()) handleProjectAdded(project); } @@ -234,7 +236,7 @@ QVariant FlatModel::data(const QModelIndex &index, int role) const } case Qt::FontRole: { QFont font; - if (project == SessionManager::startupProject()) + if (project == ProjectManager::startupProject()) font.setBold(true); return font; } @@ -407,7 +409,7 @@ void FlatModel::updateSubtree(FolderNode *node) void FlatModel::rebuildModel() { - const QList projects = SessionManager::projects(); + const QList projects = ProjectManager::projects(); for (Project *project : projects) addOrRebuildProjectModel(project); } diff --git a/src/plugins/projectexplorer/projectnodes.cpp b/src/plugins/projectexplorer/projectnodes.cpp index 10699b7d90d..e5c8a9a3c64 100644 --- a/src/plugins/projectexplorer/projectnodes.cpp +++ b/src/plugins/projectexplorer/projectnodes.cpp @@ -326,14 +326,13 @@ FilePath Node::pathOrDirectory(bool dir) const FilePath location; // Virtual Folder case // If there are files directly below or no subfolders, take the folder path - if (!folder->fileNodes().isEmpty() || folder->folderNodes().isEmpty()) { + auto Any = [](auto) { return true; }; + if (folder->findChildFileNode(Any) || !folder->findChildFolderNode(Any)) { location = m_filePath; } else { // Otherwise we figure out a commonPath from the subfolders FilePaths list; - const QList folders = folder->folderNodes(); - for (FolderNode *f : folders) - list << f->filePath(); + folder->forEachFolderNode([&](FolderNode *f) { list << f->filePath(); }); location = FileUtils::commonPath(list); } @@ -547,6 +546,22 @@ void FolderNode::forEachProjectNode(const std::function &fileTask) const +{ + for (const std::unique_ptr &n : m_nodes) { + if (FileNode *fn = n->asFileNode()) + fileTask(fn); + } +} + +void FolderNode::forEachFolderNode(const std::function &folderTask) const +{ + for (const std::unique_ptr &n : m_nodes) { + if (FolderNode *fn = n->asFolderNode()) + folderTask(fn); + } +} + ProjectNode *FolderNode::findProjectNode(const std::function &predicate) { if (ProjectNode *projectNode = asProjectNode()) { @@ -563,21 +578,31 @@ ProjectNode *FolderNode::findProjectNode(const std::function &predicate) const +{ + for (const std::unique_ptr &n : m_nodes) { + if (FolderNode *fn = n->asFolderNode()) + if (predicate(fn)) + return fn; + } + return nullptr; +} + +FileNode *FolderNode::findChildFileNode(const std::function &predicate) const +{ + for (const std::unique_ptr &n : m_nodes) { + if (FileNode *fn = n->asFileNode()) + if (predicate(fn)) + return fn; + } + return nullptr; +} + const QList FolderNode::nodes() const { return Utils::toRawPointer(m_nodes); } -QList FolderNode::fileNodes() const -{ - QList result; - for (const std::unique_ptr &n : m_nodes) { - if (FileNode *fn = n->asFileNode()) - result.append(fn); - } - return result; -} - FileNode *FolderNode::fileNode(const Utils::FilePath &file) const { return static_cast(Utils::findOrDefault(m_nodes, @@ -587,16 +612,6 @@ FileNode *FolderNode::fileNode(const Utils::FilePath &file) const })); } -QList FolderNode::folderNodes() const -{ - QList result; - for (const std::unique_ptr &n : m_nodes) { - if (FolderNode *fn = n->asFolderNode()) - result.append(fn); - } - return result; -} - FolderNode *FolderNode::folderNode(const Utils::FilePath &directory) const { Node *node = Utils::findOrDefault(m_nodes, [directory](const std::unique_ptr &n) { @@ -669,8 +684,7 @@ void FolderNode::compress() compress(); } else { - for (FolderNode *fn : folderNodes()) - fn->compress(); + forEachFolderNode([&](FolderNode *fn) { fn->compress(); }); } } diff --git a/src/plugins/projectexplorer/projectnodes.h b/src/plugins/projectexplorer/projectnodes.h index 35927c02c8e..0e0068ba7b0 100644 --- a/src/plugins/projectexplorer/projectnodes.h +++ b/src/plugins/projectexplorer/projectnodes.h @@ -32,6 +32,8 @@ enum class FileType : quint16 { Resource, QML, Project, + App, + Lib, FileTypeSize }; @@ -228,11 +230,13 @@ public: const std::function &folderFilterTask = {}) const; void forEachGenericNode(const std::function &genericTask) const; void forEachProjectNode(const std::function &genericTask) const; - ProjectNode *findProjectNode(const std::function &predicate); + void forEachFileNode(const std::function &fileTask) const; + void forEachFolderNode(const std::function &folderTask) const; + ProjectNode *findProjectNode(const std::function &predicate); // recursive + FolderNode *findChildFolderNode(const std::function &predicate) const; // non-recursive + FileNode *findChildFileNode(const std::function &predicate) const; // non-recursive const QList nodes() const; - QList fileNodes() const; FileNode *fileNode(const Utils::FilePath &file) const; - QList folderNodes() const; FolderNode *folderNode(const Utils::FilePath &directory) const; using FolderNodeFactory = std::function(const Utils::FilePath &)>; diff --git a/src/plugins/projectexplorer/projectnodeshelper.h b/src/plugins/projectexplorer/projectnodeshelper.h index 727497c6bf5..bcb5454fd89 100644 --- a/src/plugins/projectexplorer/projectnodeshelper.h +++ b/src/plugins/projectexplorer/projectnodeshelper.h @@ -11,18 +11,19 @@ #include #include +#include + namespace ProjectExplorer { template -QList scanForFiles(QFutureInterface &future, - const Utils::FilePath &directory, +QList scanForFiles(QPromise &promise, const Utils::FilePath &directory, const std::function factory); namespace Internal { template QList scanForFilesRecursively( - QFutureInterface &future, + QPromise &promise, double progressStart, double progressRange, const Utils::FilePath &directory, @@ -46,7 +47,7 @@ QList scanForFilesRecursively( const double progressIncrement = progressRange / static_cast(entries.count()); int lastIntProgress = 0; for (const QFileInfo &entry : entries) { - if (future.isCanceled()) + if (promise.isCanceled()) return result; const Utils::FilePath entryName = Utils::FilePath::fromString(entry.absoluteFilePath()); @@ -54,7 +55,7 @@ QList scanForFilesRecursively( return vc->isVcsFileOrDirectory(entryName); })) { if (entry.isDir()) - result.append(scanForFilesRecursively(future, + result.append(scanForFilesRecursively(promise, progress, progressIncrement, entryName, @@ -66,26 +67,25 @@ QList scanForFilesRecursively( } progress += progressIncrement; const int intProgress = std::min(static_cast(progressStart + progress), - future.progressMaximum()); + promise.future().progressMaximum()); if (lastIntProgress < intProgress) { - future.setProgressValue(intProgress); + promise.setProgressValue(intProgress); lastIntProgress = intProgress; } } - future.setProgressValue( - std::min(static_cast(progressStart + progressRange), future.progressMaximum())); + promise.setProgressValue(std::min(static_cast(progressStart + progressRange), + promise.future().progressMaximum())); return result; } } // namespace Internal template -QList scanForFiles(QFutureInterface &future, - const Utils::FilePath &directory, +QList scanForFiles(QPromise &promise, const Utils::FilePath &directory, const std::function factory) { QSet visited; - future.setProgressRange(0, 1000000); - return Internal::scanForFilesRecursively(future, + promise.setProgressRange(0, 1000000); + return Internal::scanForFilesRecursively(promise, 0.0, 1000000.0, directory, diff --git a/src/plugins/projectexplorer/projecttree.cpp b/src/plugins/projectexplorer/projecttree.cpp index 18b03d3ae0e..5f81f473fe4 100644 --- a/src/plugins/projectexplorer/projecttree.cpp +++ b/src/plugins/projectexplorer/projecttree.cpp @@ -6,9 +6,9 @@ #include "project.h" #include "projectexplorerconstants.h" #include "projectexplorertr.h" +#include "projectmanager.h" #include "projectnodes.h" #include "projecttreewidget.h" -#include "session.h" #include "target.h" #include @@ -53,11 +53,11 @@ ProjectTree::ProjectTree(QObject *parent) : QObject(parent) connect(qApp, &QApplication::focusChanged, this, &ProjectTree::update); - connect(SessionManager::instance(), &SessionManager::projectAdded, + connect(ProjectManager::instance(), &ProjectManager::projectAdded, this, &ProjectTree::sessionAndTreeChanged); - connect(SessionManager::instance(), &SessionManager::projectRemoved, + connect(ProjectManager::instance(), &ProjectManager::projectRemoved, this, &ProjectTree::sessionAndTreeChanged); - connect(SessionManager::instance(), &SessionManager::startupProjectChanged, + connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, this, &ProjectTree::sessionChanged); connect(this, &ProjectTree::subtreeChanged, this, &ProjectTree::treeChanged); } @@ -170,7 +170,7 @@ void ProjectTree::updateFromNode(Node *node) if (node) project = projectForNode(node); else - project = SessionManager::startupProject(); + project = ProjectManager::startupProject(); setCurrent(node, project); for (ProjectTreeWidget *widget : std::as_const(m_projectTreeWidgets)) @@ -224,7 +224,7 @@ void ProjectTree::sessionChanged() { if (m_currentProject) { Core::DocumentManager::setDefaultLocationForNewFiles(m_currentProject->projectDirectory()); - } else if (Project *project = SessionManager::startupProject()) { + } else if (Project *project = ProjectManager::startupProject()) { Core::DocumentManager::setDefaultLocationForNewFiles(project->projectDirectory()); updateFromNode(nullptr); // Make startup project current if there is no other current } else { @@ -300,7 +300,7 @@ void ProjectTree::updateFileWarning(Core::IDocument *document, const QString &te if (!infoBar->canInfoBeAdded(infoId)) return; const FilePath filePath = document->filePath(); - const QList projects = SessionManager::projects(); + const QList projects = ProjectManager::projects(); if (projects.isEmpty()) return; for (Project *project : projects) { @@ -394,7 +394,7 @@ void ProjectTree::applyTreeManager(FolderNode *folder, ConstructionPhase phase) bool ProjectTree::hasNode(const Node *node) { - return Utils::contains(SessionManager::projects(), [node](const Project *p) { + return Utils::contains(ProjectManager::projects(), [node](const Project *p) { if (!p) return false; if (p->containerNode() == node) @@ -409,7 +409,7 @@ bool ProjectTree::hasNode(const Node *node) void ProjectTree::forEachNode(const std::function &task) { - const QList projects = SessionManager::projects(); + const QList projects = ProjectManager::projects(); for (Project *project : projects) { if (ProjectNode *projectNode = project->rootProjectNode()) { task(projectNode); @@ -430,7 +430,7 @@ Project *ProjectTree::projectForNode(const Node *node) while (folder && folder->parentFolderNode()) folder = folder->parentFolderNode(); - return Utils::findOrDefault(SessionManager::projects(), [folder](const Project *pro) { + return Utils::findOrDefault(ProjectManager::projects(), [folder](const Project *pro) { return pro->containerNode() == folder; }); } @@ -438,7 +438,7 @@ Project *ProjectTree::projectForNode(const Node *node) Node *ProjectTree::nodeForFile(const FilePath &fileName) { Node *node = nullptr; - for (const Project *project : SessionManager::projects()) { + for (const Project *project : ProjectManager::projects()) { project->nodeForFilePath(fileName, [&](const Node *n) { if (!node || (!node->asFileNode() && n->asFileNode())) node = const_cast(n); diff --git a/src/plugins/projectexplorer/projecttreewidget.cpp b/src/plugins/projectexplorer/projecttreewidget.cpp index 65f38626fef..a3d608ebd80 100644 --- a/src/plugins/projectexplorer/projecttreewidget.cpp +++ b/src/plugins/projectexplorer/projecttreewidget.cpp @@ -6,10 +6,10 @@ #include "project.h" #include "projectexplorerconstants.h" #include "projectexplorertr.h" +#include "projectmanager.h" #include "projectmodels.h" #include "projectnodes.h" #include "projecttree.h" -#include "session.h" #include #include @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -343,7 +344,7 @@ Node *ProjectTreeWidget::nodeForFile(const FilePath &fileName) int bestNodeExpandCount = INT_MAX; // FIXME: Looks like this could be done with less cycles. - for (Project *project : SessionManager::projects()) { + for (Project *project : ProjectManager::projects()) { if (ProjectNode *projectNode = project->rootProjectNode()) { projectNode->forEachGenericNode([&](Node *node) { if (node->filePath() == fileName) { @@ -420,7 +421,7 @@ QList ProjectTreeWidget::createToolButtons() filter->setIcon(Icons::FILTER.icon()); filter->setToolTip(Tr::tr("Filter Tree")); filter->setPopupMode(QToolButton::InstantPopup); - filter->setProperty("noArrow", true); + filter->setProperty(StyleHelper::C_NO_ARROW, true); auto filterMenu = new QMenu(filter); filterMenu->addAction(m_filterProjectsAction); diff --git a/src/plugins/projectexplorer/projectwelcomepage.cpp b/src/plugins/projectexplorer/projectwelcomepage.cpp index 156212a5041..4eef637077c 100644 --- a/src/plugins/projectexplorer/projectwelcomepage.cpp +++ b/src/plugins/projectexplorer/projectwelcomepage.cpp @@ -3,10 +3,9 @@ #include "projectwelcomepage.h" -#include "session.h" -#include "sessionmodel.h" #include "projectexplorer.h" #include "projectexplorertr.h" +#include "projectmanager.h" #include #include @@ -15,6 +14,8 @@ #include #include #include +#include +#include #include #include @@ -324,7 +325,7 @@ public: if (expanded) { painter->setPen(textColor); painter->setFont(sizedFont(12, option.widget)); - const FilePaths projects = SessionManager::projectsForSessionName(sessionName); + const FilePaths projects = ProjectManager::projectsForSessionName(sessionName); int yy = firstBase + SESSION_LINE_HEIGHT - 3; QFontMetrics fm(option.widget->font()); for (const FilePath &projectPath : projects) { @@ -378,7 +379,7 @@ public: int h = SESSION_LINE_HEIGHT; QString sessionName = idx.data(Qt::DisplayRole).toString(); if (m_expandedSessions.contains(sessionName)) { - const FilePaths projects = SessionManager::projectsForSessionName(sessionName); + const FilePaths projects = ProjectManager::projectsForSessionName(sessionName); h += projects.size() * 40 + LINK_HEIGHT - 6; } return QSize(380, h + ItemGap); @@ -579,7 +580,7 @@ public: auto manageSessionsButton = new WelcomePageButton(this); manageSessionsButton->setText(Tr::tr("Manage...")); manageSessionsButton->setWithAccentColor(true); - manageSessionsButton->setOnClicked([] { ProjectExplorerPlugin::showSessionManager(); }); + manageSessionsButton->setOnClicked([] { SessionManager::showSessionManager(); }); auto sessionsLabel = new QLabel(this); sessionsLabel->setFont(brandFont()); diff --git a/src/plugins/projectexplorer/projectwelcomepage.h b/src/plugins/projectexplorer/projectwelcomepage.h index a9c81459635..7fb2a828dbf 100644 --- a/src/plugins/projectexplorer/projectwelcomepage.h +++ b/src/plugins/projectexplorer/projectwelcomepage.h @@ -12,10 +12,11 @@ #include #include +namespace Core { class SessionModel; } + namespace ProjectExplorer { namespace Internal { -class SessionModel; class SessionsPage; class ProjectModel : public QAbstractListModel @@ -56,7 +57,6 @@ public slots: signals: void requestProject(const Utils::FilePath &project); - void manageSessions(); private: void openSessionAt(int index); @@ -64,7 +64,7 @@ private: void createActions(); friend class SessionsPage; - SessionModel *m_sessionModel = nullptr; + Core::SessionModel *m_sessionModel = nullptr; ProjectModel *m_projectModel = nullptr; }; diff --git a/src/plugins/projectexplorer/projectwindow.cpp b/src/plugins/projectexplorer/projectwindow.cpp index 8fae44b2973..a69ee4c855a 100644 --- a/src/plugins/projectexplorer/projectwindow.cpp +++ b/src/plugins/projectexplorer/projectwindow.cpp @@ -12,9 +12,9 @@ #include "projectexplorerconstants.h" #include "projectexplorertr.h" #include "projectimporter.h" +#include "projectmanager.h" #include "projectpanelfactory.h" #include "projectsettingswidget.h" -#include "session.h" #include "target.h" #include "targetsettingspanel.h" @@ -352,7 +352,7 @@ public: case Qt::FontRole: { QFont font; - font.setBold(m_project == SessionManager::startupProject()); + font.setBold(m_project == ProjectManager::startupProject()); return font; } @@ -392,7 +392,7 @@ public: if (role == ItemActivatedDirectlyRole) { // Someone selected the project using the combobox or similar. - SessionManager::setStartupProject(m_project); + ProjectManager::setStartupProject(m_project); m_currentChildIndex = 0; // Use some Target page by defaults m_targetsItem->setData(column, dat, ItemActivatedFromAboveRole); // And propagate downwards. announceChange(); @@ -546,18 +546,18 @@ public: m_projectSelection->showPopup(); }); - SessionManager *sessionManager = SessionManager::instance(); - connect(sessionManager, &SessionManager::projectAdded, + ProjectManager *sessionManager = ProjectManager::instance(); + connect(sessionManager, &ProjectManager::projectAdded, this, &ProjectWindowPrivate::registerProject); - connect(sessionManager, &SessionManager::aboutToRemoveProject, + connect(sessionManager, &ProjectManager::aboutToRemoveProject, this, &ProjectWindowPrivate::deregisterProject); - connect(sessionManager, &SessionManager::startupProjectChanged, + connect(sessionManager, &ProjectManager::startupProjectChanged, this, &ProjectWindowPrivate::startupProjectChanged); m_importBuild = new QPushButton(Tr::tr("Import Existing Build...")); connect(m_importBuild, &QPushButton::clicked, this, &ProjectWindowPrivate::handleImportBuild); - connect(sessionManager, &SessionManager::startupProjectChanged, this, [this](Project *project) { + connect(sessionManager, &ProjectManager::startupProjectChanged, this, [this](Project *project) { m_importBuild->setEnabled(project && project->projectImporter()); }); @@ -572,9 +572,6 @@ public: selectorView->setObjectName("ProjectSelector"); // Needed for dock widget state saving selectorView->setWindowTitle(Tr::tr("Project Selector")); selectorView->setAutoFillBackground(true); - selectorView->setContextMenuPolicy(Qt::CustomContextMenu); - connect(selectorView, &QWidget::customContextMenuRequested, - this, &ProjectWindowPrivate::openContextMenu); auto activeLabel = new QLabel(Tr::tr("Active Project")); QFont font = activeLabel->font(); @@ -677,7 +674,7 @@ public: void projectSelected(int index) { Project *project = m_comboBoxModel.rootItem()->childAt(index)->m_projectItem->project(); - SessionManager::setStartupProject(project); + ProjectManager::setStartupProject(project); } ComboBoxItem *itemForProject(Project *project) const @@ -746,8 +743,7 @@ public: void handleManageKits() { if (ProjectItem *projectItem = m_projectsModel.rootItem()->childAt(0)) { - if (auto kitPage = KitOptionsPage::instance()) - kitPage->showKit(KitManager::kit(Id::fromSetting(projectItem->data(0, KitIdRole)))); + KitOptionsPage::showKit(KitManager::kit(Id::fromSetting(projectItem->data(0, KitIdRole)))); } ICore::showOptionsDialog(Constants::KITS_SETTINGS_PAGE_ID); } @@ -780,8 +776,8 @@ public: } } if (lastTarget && lastBc) { - SessionManager::setActiveBuildConfiguration(lastTarget, lastBc, SetActive::Cascade); - SessionManager::setActiveTarget(project, lastTarget, SetActive::Cascade); + lastTarget->setActiveBuildConfiguration(lastBc, SetActive::Cascade); + project->setActiveTarget(lastTarget, SetActive::Cascade); } } diff --git a/src/plugins/projectexplorer/projectwizardpage.cpp b/src/plugins/projectexplorer/projectwizardpage.cpp index 34dc0b9aaa5..83fd4d2cba8 100644 --- a/src/plugins/projectexplorer/projectwizardpage.cpp +++ b/src/plugins/projectexplorer/projectwizardpage.cpp @@ -5,8 +5,8 @@ #include "project.h" #include "projectexplorertr.h" +#include "projectmanager.h" #include "projectmodels.h" -#include "session.h" #include #include @@ -245,12 +245,10 @@ static AddNewTree *buildAddFilesTree(FolderNode *root, const FilePaths &files, Node *contextNode, BestNodeSelector *selector) { QList children; - const QList folderNodes = root->folderNodes(); - for (FolderNode *fn : folderNodes) { - AddNewTree *child = buildAddFilesTree(fn, files, contextNode, selector); - if (child) + root->forEachFolderNode([&](FolderNode *fn) { + if (AddNewTree *child = buildAddFilesTree(fn, files, contextNode, selector)) children.append(child); - } + }); if (root->supportsAction(AddNewFile, root) && !root->supportsAction(InheritedFromParent, root)) { FolderNode::AddNewInformation info = root->addNewInformation(files, contextNode); @@ -290,7 +288,7 @@ ProjectWizardPage::ProjectWizardPage(QWidget *parent) scrollArea->setWidgetResizable(true); scrollArea->setWidget(m_filesLabel); - using namespace Utils::Layouting; + using namespace Layouting; Column { Form { m_projectLabel, m_projectComboBox, br, @@ -463,7 +461,7 @@ void ProjectWizardPage::initializeProjectTree(Node *context, const FilePaths &pa TreeItem *root = m_model.rootItem(); root->removeChildren(); - for (Project *project : SessionManager::projects()) { + for (Project *project : ProjectManager::projects()) { if (ProjectNode *pn = project->rootProjectNode()) { if (kind == IWizardFactory::ProjectWizard) { if (AddNewTree *child = buildAddProjectTree(pn, paths.first(), context, &selector)) diff --git a/src/plugins/projectexplorer/rawprojectpart.cpp b/src/plugins/projectexplorer/rawprojectpart.cpp index 6abbedde93b..31147c3803b 100644 --- a/src/plugins/projectexplorer/rawprojectpart.cpp +++ b/src/plugins/projectexplorer/rawprojectpart.cpp @@ -11,13 +11,14 @@ #include "target.h" #include + #include namespace ProjectExplorer { RawProjectPartFlags::RawProjectPartFlags(const ToolChain *toolChain, const QStringList &commandLineFlags, - const QString &includeFileBaseDir) + const Utils::FilePath &includeFileBaseDir) { // Keep the following cheap/non-blocking for the ui thread. Expensive // operations are encapsulated in ToolChainInfo as "runners". @@ -25,7 +26,8 @@ RawProjectPartFlags::RawProjectPartFlags(const ToolChain *toolChain, if (toolChain) { warningFlags = toolChain->warningFlags(commandLineFlags); languageExtensions = toolChain->languageExtensions(commandLineFlags); - includedFiles = toolChain->includedFiles(commandLineFlags, includeFileBaseDir); + includedFiles = Utils::transform(toolChain->includedFiles(commandLineFlags, includeFileBaseDir), + &Utils::FilePath::toFSPathString); } } diff --git a/src/plugins/projectexplorer/rawprojectpart.h b/src/plugins/projectexplorer/rawprojectpart.h index 580c83d439d..ca210ed43e2 100644 --- a/src/plugins/projectexplorer/rawprojectpart.h +++ b/src/plugins/projectexplorer/rawprojectpart.h @@ -37,7 +37,7 @@ class PROJECTEXPLORER_EXPORT RawProjectPartFlags public: RawProjectPartFlags() = default; RawProjectPartFlags(const ToolChain *toolChain, const QStringList &commandLineFlags, - const QString &includeFileBaseDir); + const Utils::FilePath &includeFileBaseDir); public: QStringList commandLineFlags; diff --git a/src/plugins/projectexplorer/runconfiguration.cpp b/src/plugins/projectexplorer/runconfiguration.cpp index b5f3f8e703e..29b59ad8984 100644 --- a/src/plugins/projectexplorer/runconfiguration.cpp +++ b/src/plugins/projectexplorer/runconfiguration.cpp @@ -12,10 +12,10 @@ #include "projectexplorer.h" #include "projectexplorerconstants.h" #include "projectexplorertr.h" +#include "projectmanager.h" #include "projectnodes.h" #include "runconfigurationaspects.h" #include "runcontrol.h" -#include "session.h" #include "target.h" #include @@ -33,7 +33,6 @@ #include #include -#include #include #include #include @@ -217,13 +216,15 @@ bool RunConfiguration::isEnabled() const QWidget *RunConfiguration::createConfigurationWidget() { - Layouting::Form builder; - for (BaseAspect *aspect : std::as_const(m_aspects)) { - if (aspect->isVisible()) - aspect->addToLayout(builder.finishRow()); + Layouting::Form form; + for (BaseAspect *aspect : std::as_const(*this)) { + if (aspect->isVisible()) { + form.addItem(aspect); + form.addItem(Layouting::br); + } } - - auto widget = builder.emerge(Layouting::WithoutMargins); + form.addItem(Layouting::noMargin); + auto widget = form.emerge(); VariableChooser::addSupportForChildWidgets(widget, &m_expander); @@ -246,7 +247,7 @@ void RunConfiguration::addAspectFactory(const AspectFactory &aspectFactory) QMap RunConfiguration::settingsData() const { QMap data; - for (BaseAspect *aspect : m_aspects) + for (BaseAspect *aspect : *this) aspect->toActiveMap(data[aspect->id()]); return data; } @@ -254,7 +255,7 @@ QMap RunConfiguration::settingsData() const AspectContainerData RunConfiguration::aspectData() const { AspectContainerData data; - for (BaseAspect *aspect : m_aspects) + for (BaseAspect *aspect : *this) data.append(aspect->extractData()); return data; } @@ -299,6 +300,13 @@ CommandLine RunConfiguration::commandLine() const return m_commandLineGetter(); } +bool RunConfiguration::isPrintEnvironmentEnabled() const +{ + if (const auto envAspect = aspect()) + return envAspect->isPrintOnRunEnabled(); + return false; +} + void RunConfiguration::setRunnableModifier(const RunnableModifier &runnableModifier) { m_runnableModifier = runnableModifier; @@ -313,7 +321,7 @@ void RunConfiguration::update() const bool isActive = target()->isActive() && target()->activeRunConfiguration() == this; - if (isActive && project() == SessionManager::startupProject()) + if (isActive && project() == ProjectManager::startupProject()) ProjectExplorerPlugin::updateRunActions(); } @@ -392,7 +400,7 @@ Runnable RunConfiguration::runnable() const Runnable r; r.command = commandLine(); if (auto workingDirectoryAspect = aspect()) - r.workingDirectory = workingDirectoryAspect->workingDirectory().onDevice(r.command.executable()); + r.workingDirectory = r.command.executable().withNewPath(workingDirectoryAspect->workingDirectory().path()); if (auto environmentAspect = aspect()) r.environment = environmentAspect->environment(); if (m_runnableModifier) @@ -558,7 +566,7 @@ RunConfiguration *RunConfigurationFactory::create(Target *target) const // Add the universal aspects. for (const RunConfiguration::AspectFactory &factory : theAspectFactories) - rc->m_aspects.registerAspect(factory(target)); + rc->registerAspect(factory(target)); return rc; } diff --git a/src/plugins/projectexplorer/runconfiguration.h b/src/plugins/projectexplorer/runconfiguration.h index 32f61b5a306..59704141562 100644 --- a/src/plugins/projectexplorer/runconfiguration.h +++ b/src/plugins/projectexplorer/runconfiguration.h @@ -116,6 +116,7 @@ public: using CommandLineGetter = std::function; void setCommandLineGetter(const CommandLineGetter &cmdGetter); Utils::CommandLine commandLine() const; + bool isPrintEnvironmentEnabled() const; using RunnableModifier = std::function; void setRunnableModifier(const RunnableModifier &extraModifier); @@ -137,6 +138,7 @@ public: return nullptr; } + using ProjectConfiguration::registerAspect; using AspectFactory = std::function; template static void registerAspect() { diff --git a/src/plugins/projectexplorer/runconfigurationaspects.cpp b/src/plugins/projectexplorer/runconfigurationaspects.cpp index 9ba24c04672..ce28582d558 100644 --- a/src/plugins/projectexplorer/runconfigurationaspects.cpp +++ b/src/plugins/projectexplorer/runconfigurationaspects.cpp @@ -19,8 +19,8 @@ #include #include #include +#include #include -#include #include #include @@ -33,7 +33,7 @@ #include using namespace Utils; -using namespace Utils::Layouting; +using namespace Layouting; namespace ProjectExplorer { @@ -62,12 +62,13 @@ TerminalAspect::TerminalAspect() /*! \reimp */ -void TerminalAspect::addToLayout(LayoutBuilder &builder) +void TerminalAspect::addToLayout(LayoutItem &parent) { QTC_CHECK(!m_checkBox); - m_checkBox = new QCheckBox(Tr::tr("Run in terminal")); + m_checkBox = createSubWidget(Tr::tr("Run in terminal")); m_checkBox->setChecked(m_useTerminal); - builder.addItems({{}, m_checkBox.data()}); + m_checkBox->setEnabled(isEnabled()); + parent.addItems({{}, m_checkBox.data()}); connect(m_checkBox.data(), &QAbstractButton::clicked, this, [this] { m_userSet = true; m_useTerminal = m_checkBox->isChecked(); @@ -123,7 +124,7 @@ void TerminalAspect::calculateUseTerminal() */ bool TerminalAspect::useTerminal() const { - return m_useTerminal; + return m_useTerminal && isEnabled(); } /*! @@ -163,7 +164,7 @@ WorkingDirectoryAspect::WorkingDirectoryAspect(const MacroExpander *expander, /*! \reimp */ -void WorkingDirectoryAspect::addToLayout(LayoutBuilder &builder) +void WorkingDirectoryAspect::addToLayout(LayoutItem &builder) { QTC_CHECK(!m_chooser); m_chooser = new PathChooser; @@ -435,7 +436,7 @@ QWidget *ArgumentsAspect::setupChooser() /*! \reimp */ -void ArgumentsAspect::addToLayout(LayoutBuilder &builder) +void ArgumentsAspect::addToLayout(LayoutItem &builder) { QTC_CHECK(!m_chooser && !m_multiLineChooser && !m_multiLineButton); @@ -501,7 +502,7 @@ ExecutableAspect::ExecutableAspect(Target *target, ExecutionDeviceSelector selec setId("ExecutableAspect"); addDataExtractor(this, &ExecutableAspect::executable, &Data::executable); - m_executable.setPlaceHolderText(Tr::tr("")); + m_executable.setPlaceHolderText(Tr::tr("path to the executable cannot be empty")); m_executable.setLabelText(Tr::tr("Executable:")); m_executable.setDisplayStyle(StringAspect::LabelDisplay); @@ -570,19 +571,12 @@ void ExecutableAspect::setExpectedKind(const PathChooser::Kind expectedKind) Sets the environment in which paths will be searched when the expected kind of paths is chosen as PathChooser::Command or PathChooser::ExistingCommand to \a env. - - \sa Utils::StringAspect::setEnvironmentChange() */ -void ExecutableAspect::setEnvironmentChange(const EnvironmentChange &change) -{ - m_executable.setEnvironmentChange(change); - if (m_alternativeExecutable) - m_alternativeExecutable->setEnvironmentChange(change); -} - void ExecutableAspect::setEnvironment(const Environment &env) { - setEnvironmentChange(EnvironmentChange::fromDictionary(env.toDictionary())); + m_executable.setEnvironment(env); + if (m_alternativeExecutable) + m_alternativeExecutable->setEnvironment(env); } /*! @@ -631,7 +625,7 @@ FilePath ExecutableAspect::executable() const : m_executable.filePath(); if (const IDevice::ConstPtr dev = executionDevice(m_target, m_selector)) - exe = exe.onDevice(dev->rootPath()); + exe = dev->rootPath().withNewMappedPath(exe); return exe; } @@ -639,11 +633,11 @@ FilePath ExecutableAspect::executable() const /*! \reimp */ -void ExecutableAspect::addToLayout(LayoutBuilder &builder) +void ExecutableAspect::addToLayout(LayoutItem &builder) { - m_executable.addToLayout(builder); + builder.addItem(m_executable); if (m_alternativeExecutable) - m_alternativeExecutable->addToLayout(builder.finishRow()); + builder.addItems({br, m_alternativeExecutable}); } /*! @@ -837,7 +831,7 @@ void InterpreterAspect::toMap(QVariantMap &map) const saveToMap(map, m_currentId, QString(), settingsKey()); } -void InterpreterAspect::addToLayout(LayoutBuilder &builder) +void InterpreterAspect::addToLayout(LayoutItem &builder) { if (QTC_GUARD(m_comboBox.isNull())) m_comboBox = new QComboBox; diff --git a/src/plugins/projectexplorer/runconfigurationaspects.h b/src/plugins/projectexplorer/runconfigurationaspects.h index 9e948bc6ecf..9fc7364772d 100644 --- a/src/plugins/projectexplorer/runconfigurationaspects.h +++ b/src/plugins/projectexplorer/runconfigurationaspects.h @@ -29,7 +29,7 @@ class PROJECTEXPLORER_EXPORT TerminalAspect : public Utils::BaseAspect public: TerminalAspect(); - void addToLayout(Utils::Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; bool useTerminal() const; void setUseTerminalHint(bool useTerminal); @@ -62,7 +62,7 @@ public: explicit WorkingDirectoryAspect(const Utils::MacroExpander *expander, EnvironmentAspect *envAspect); - void addToLayout(Utils::Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; Utils::FilePath workingDirectory() const; Utils::FilePath defaultWorkingDirectory() const; @@ -91,7 +91,7 @@ class PROJECTEXPLORER_EXPORT ArgumentsAspect : public Utils::BaseAspect public: explicit ArgumentsAspect(const Utils::MacroExpander *macroExpander); - void addToLayout(Utils::Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; QString arguments() const; QString unexpandedArguments() const; @@ -163,12 +163,11 @@ public: void setSettingsKey(const QString &key); void makeOverridable(const QString &overridingKey, const QString &useOverridableKey); - void addToLayout(Utils::Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; void setLabelText(const QString &labelText); void setPlaceHolderText(const QString &placeHolderText); void setHistoryCompleter(const QString &historyCompleterKey); void setExpectedKind(const Utils::PathChooser::Kind expectedKind); - void setEnvironmentChange(const Utils::EnvironmentChange &change); void setEnvironment(const Utils::Environment &env); void setDisplayStyle(Utils::StringAspect::DisplayStyle style); @@ -236,7 +235,7 @@ public: void fromMap(const QVariantMap &) override; void toMap(QVariantMap &) const override; - void addToLayout(Utils::Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; struct Data : Utils::BaseAspect::Data { Interpreter interpreter; }; diff --git a/src/plugins/projectexplorer/runcontrol.cpp b/src/plugins/projectexplorer/runcontrol.cpp index 72b2e76e502..f9bafd234ad 100644 --- a/src/plugins/projectexplorer/runcontrol.cpp +++ b/src/plugins/projectexplorer/runcontrol.cpp @@ -25,9 +25,9 @@ #include #include #include +#include #include #include -#include #include #include @@ -255,9 +255,9 @@ public: // A handle to the actual application process. ProcessHandle applicationProcessHandle; - RunControlState state = RunControlState::Initialized; - QList> m_workers; + RunControlState state = RunControlState::Initialized; + bool printEnvironment = false; }; class RunControlPrivate : public QObject, public RunControlPrivateData @@ -334,6 +334,7 @@ void RunControl::copyDataFromRunConfiguration(RunConfiguration *runConfig) d->buildKey = runConfig->buildKey(); d->settingsData = runConfig->settingsData(); d->aspectData = runConfig->aspectData(); + d->printEnvironment = runConfig->isPrintEnvironmentEnabled(); setTarget(runConfig->target()); @@ -817,6 +818,11 @@ Utils::Id RunControl::runMode() const return d->runMode; } +bool RunControl::isPrintEnvironmentEnabled() const +{ + return d->printEnvironment; +} + const Runnable &RunControl::runnable() const { return d->runnable; @@ -1045,35 +1051,31 @@ bool RunControl::showPromptToStopDialog(const QString &title, { // Show a question message box where user can uncheck this // question for this class. - Utils::CheckableMessageBox messageBox(Core::ICore::dialogParent()); - messageBox.setWindowTitle(title); - messageBox.setText(text); - messageBox.setStandardButtons(QDialogButtonBox::Yes|QDialogButtonBox::Cancel); + QMap buttonTexts; if (!stopButtonText.isEmpty()) - messageBox.button(QDialogButtonBox::Yes)->setText(stopButtonText); + buttonTexts[QMessageBox::Yes] = stopButtonText; if (!cancelButtonText.isEmpty()) - messageBox.button(QDialogButtonBox::Cancel)->setText(cancelButtonText); - messageBox.setDefaultButton(QDialogButtonBox::Yes); - if (prompt) { - messageBox.setCheckBoxText(Utils::CheckableMessageBox::msgDoNotAskAgain()); - messageBox.setChecked(false); - } else { - messageBox.setCheckBoxVisible(false); - } - messageBox.exec(); - const bool close = messageBox.clickedStandardButton() == QDialogButtonBox::Yes; - if (close && prompt && messageBox.isChecked()) - *prompt = false; - return close; + buttonTexts[QMessageBox::Cancel] = cancelButtonText; + + CheckableDecider decider; + if (prompt) + decider = CheckableDecider(prompt); + + auto selected = CheckableMessageBox::question(Core::ICore::dialogParent(), + title, + text, + decider, + QMessageBox::Yes | QMessageBox::Cancel, + QMessageBox::Yes); + + return selected == QMessageBox::Yes; } void RunControl::provideAskPassEntry(Environment &env) { - if (env.value("SUDO_ASKPASS").isEmpty()) { - const FilePath askpass = SshSettings::askpassFilePath(); - if (askpass.exists()) - env.set("SUDO_ASKPASS", askpass.toUserOutput()); - } + const FilePath askpass = SshSettings::askpassFilePath(); + if (askpass.exists()) + env.setFallback("SUDO_ASKPASS", askpass.toUserOutput()); } bool RunControlPrivate::isAllowedTransition(RunControlState from, RunControlState to) @@ -1175,7 +1177,7 @@ public: bool m_runAsRoot = false; - QtcProcess m_process; + Process m_process; QTextCodec *m_outputCodec = nullptr; QTextCodec::ConverterState m_outputCodecState; @@ -1212,11 +1214,11 @@ SimpleTargetRunnerPrivate::SimpleTargetRunnerPrivate(SimpleTargetRunner *parent) : q(parent) { m_process.setProcessChannelMode(defaultProcessChannelMode()); - connect(&m_process, &QtcProcess::started, this, &SimpleTargetRunnerPrivate::forwardStarted); - connect(&m_process, &QtcProcess::done, this, &SimpleTargetRunnerPrivate::handleDone); - connect(&m_process, &QtcProcess::readyReadStandardError, + connect(&m_process, &Process::started, this, &SimpleTargetRunnerPrivate::forwardStarted); + connect(&m_process, &Process::done, this, &SimpleTargetRunnerPrivate::handleDone); + connect(&m_process, &Process::readyReadStandardError, this, &SimpleTargetRunnerPrivate::handleStandardError); - connect(&m_process, &QtcProcess::readyReadStandardOutput, + connect(&m_process, &Process::readyReadStandardOutput, this, &SimpleTargetRunnerPrivate::handleStandardOutput); if (WinDebugInterface::instance()) { @@ -1371,7 +1373,7 @@ void SimpleTargetRunnerPrivate::start() Encapsulates processes running in a console or as GUI processes, captures debug output of GUI processes on Windows (outputDebugString()). - \sa Utils::QtcProcess + \sa Utils::Process */ SimpleTargetRunner::SimpleTargetRunner(RunControl *runControl) @@ -1433,11 +1435,20 @@ void SimpleTargetRunner::start() d->m_stopForced = false; d->m_stopReported = false; d->disconnect(this); - d->m_process.setTerminalMode(useTerminal ? Utils::TerminalMode::On : Utils::TerminalMode::Off); + d->m_process.setTerminalMode(useTerminal ? Utils::TerminalMode::Run : Utils::TerminalMode::Off); d->m_runAsRoot = runAsRoot; const QString msg = Tr::tr("Starting %1...").arg(d->m_command.displayName()); appendMessage(msg, NormalMessageFormat); + if (runControl()->isPrintEnvironmentEnabled()) { + appendMessage(Tr::tr("Environment:"), NormalMessageFormat); + runControl()->runnable().environment + .forEachEntry([this](const QString &key, const QString &value, bool enabled) { + if (enabled) + appendMessage(key + '=' + value, StdOutFormat); + }); + appendMessage({}, StdOutFormat); + } const bool isDesktop = !d->m_command.executable().needsDevice(); if (isDesktop && d->m_command.isEmpty()) { @@ -1653,9 +1664,9 @@ void RunWorker::reportFailure(const QString &msg) * Appends a message in the specified \a format to * the owning RunControl's \uicontrol{Application Output} pane. */ -void RunWorker::appendMessage(const QString &msg, OutputFormat format) +void RunWorker::appendMessage(const QString &msg, OutputFormat format, bool appendNewLine) { - if (msg.endsWith('\n')) + if (!appendNewLine || msg.endsWith('\n')) emit d->runControl->appendMessage(msg, format); else emit d->runControl->appendMessage(msg + '\n', format); diff --git a/src/plugins/projectexplorer/runcontrol.h b/src/plugins/projectexplorer/runcontrol.h index d779b693a84..84a5b5c1667 100644 --- a/src/plugins/projectexplorer/runcontrol.h +++ b/src/plugins/projectexplorer/runcontrol.h @@ -67,7 +67,7 @@ public: QVariant recordedData(const QString &channel) const; // Part of read-only interface of RunControl for convenience. - void appendMessage(const QString &msg, Utils::OutputFormat format); + void appendMessage(const QString &msg, Utils::OutputFormat format, bool appendNewLine = true); void appendMessageChunk(const QString &msg, Utils::OutputFormat format); IDeviceConstPtr device() const; @@ -205,6 +205,7 @@ public: void setupFormatter(Utils::OutputFormatter *formatter) const; Utils::Id runMode() const; + bool isPrintEnvironmentEnabled() const; const Runnable &runnable() const; diff --git a/src/plugins/projectexplorer/runsettingspropertiespage.cpp b/src/plugins/projectexplorer/runsettingspropertiespage.cpp index bd8b30f2b7a..43edee9b668 100644 --- a/src/plugins/projectexplorer/runsettingspropertiespage.cpp +++ b/src/plugins/projectexplorer/runsettingspropertiespage.cpp @@ -10,9 +10,10 @@ #include "projectconfigurationmodel.h" #include "projectexplorertr.h" #include "runconfiguration.h" -#include "session.h" #include "target.h" +#include + #include #include #include @@ -291,11 +292,10 @@ void RunSettingsWidget::currentDeployConfigurationChanged(int index) if (m_ignoreChanges.isLocked()) return; if (index == -1) - SessionManager::setActiveDeployConfiguration(m_target, nullptr, SetActive::Cascade); + m_target->setActiveDeployConfiguration(nullptr, SetActive::Cascade); else - SessionManager::setActiveDeployConfiguration(m_target, - qobject_cast(m_target->deployConfigurationModel()->projectConfigurationAt(index)), - SetActive::Cascade); + m_target->setActiveDeployConfiguration(qobject_cast(m_target->deployConfigurationModel()->projectConfigurationAt(index)), + SetActive::Cascade); } void RunSettingsWidget::aboutToShowDeployMenu() @@ -309,7 +309,7 @@ void RunSettingsWidget::aboutToShowDeployMenu() if (!newDc) return; m_target->addDeployConfiguration(newDc); - SessionManager::setActiveDeployConfiguration(m_target, newDc, SetActive::Cascade); + m_target->setActiveDeployConfiguration(newDc, SetActive::Cascade); m_removeDeployToolButton->setEnabled(m_target->deployConfigurations().size() > 1); }); } diff --git a/src/plugins/projectexplorer/selectablefilesmodel.cpp b/src/plugins/projectexplorer/selectablefilesmodel.cpp index 365c319992d..de5b0b79b40 100644 --- a/src/plugins/projectexplorer/selectablefilesmodel.cpp +++ b/src/plugins/projectexplorer/selectablefilesmodel.cpp @@ -9,10 +9,10 @@ #include #include +#include #include #include #include -#include #include #include @@ -51,13 +51,13 @@ void SelectableFilesFromDirModel::startParsing(const Utils::FilePath &baseDir) m_rootForFuture->fullPath = baseDir; m_rootForFuture->isDir = true; - m_watcher.setFuture(Utils::runAsync(&SelectableFilesFromDirModel::run, this)); + m_watcher.setFuture(Utils::asyncRun(&SelectableFilesFromDirModel::run, this)); } -void SelectableFilesFromDirModel::run(QFutureInterface &fi) +void SelectableFilesFromDirModel::run(QPromise &promise) { m_futureCount = 0; - buildTree(m_baseDir, m_rootForFuture, fi, 5); + buildTree(m_baseDir, m_rootForFuture, promise, 5); } void SelectableFilesFromDirModel::buildTreeFinished() @@ -97,7 +97,7 @@ SelectableFilesModel::FilterState SelectableFilesModel::filter(Tree *t) } void SelectableFilesFromDirModel::buildTree(const Utils::FilePath &baseDir, Tree *tree, - QFutureInterface &fi, int symlinkDepth) + QPromise &promise, int symlinkDepth) { if (symlinkDepth == 0) return; @@ -111,7 +111,7 @@ void SelectableFilesFromDirModel::buildTree(const Utils::FilePath &baseDir, Tree Utils::FilePath fn = Utils::FilePath::fromFileInfo(fileInfo); if (m_futureCount % 100) { emit parsingProgress(fn); - if (fi.isCanceled()) + if (promise.isCanceled()) return; } ++m_futureCount; @@ -121,7 +121,7 @@ void SelectableFilesFromDirModel::buildTree(const Utils::FilePath &baseDir, Tree t->name = fileInfo.fileName(); t->fullPath = fn; t->isDir = true; - buildTree(fn, t, fi, symlinkDepth - fileInfo.isSymLink()); + buildTree(fn, t, promise, symlinkDepth - fileInfo.isSymLink()); allChecked &= t->checked == Qt::Checked; allUnchecked &= t->checked == Qt::Unchecked; tree->childDirectories.append(t); diff --git a/src/plugins/projectexplorer/selectablefilesmodel.h b/src/plugins/projectexplorer/selectablefilesmodel.h index 6340e2d1a2d..8c47fbd990b 100644 --- a/src/plugins/projectexplorer/selectablefilesmodel.h +++ b/src/plugins/projectexplorer/selectablefilesmodel.h @@ -9,7 +9,6 @@ #include #include -#include #include #include #include @@ -147,11 +146,9 @@ signals: void parsingProgress(const Utils::FilePath &fileName); private: - void buildTree(const Utils::FilePath &baseDir, - Tree *tree, - QFutureInterface &fi, + void buildTree(const Utils::FilePath &baseDir, Tree *tree, QPromise &promise, int symlinkDepth); - void run(QFutureInterface &fi); + void run(QPromise &promise); void buildTreeFinished(); // Used in the future thread need to all not used after calling startParsing diff --git a/src/plugins/projectexplorer/session.cpp b/src/plugins/projectexplorer/session.cpp deleted file mode 100644 index 6dcfd9a4619..00000000000 --- a/src/plugins/projectexplorer/session.cpp +++ /dev/null @@ -1,1262 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "session.h" - -#include "buildconfiguration.h" -#include "deployconfiguration.h" -#include "editorconfiguration.h" -#include "kit.h" -#include "project.h" -#include "projectexplorer.h" -#include "projectexplorerconstants.h" -#include "projectexplorertr.h" -#include "projectnodes.h" -#include "target.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -#ifdef WITH_TESTS -#include -#include -#include -#endif - -using namespace Core; -using namespace Utils; -using namespace ProjectExplorer::Internal; - -namespace ProjectExplorer { - -const char DEFAULT_SESSION[] = "default"; -const char LAST_ACTIVE_TIMES_KEY[] = "LastActiveTimes"; - -/*! - \class ProjectExplorer::SessionManager - - \brief The SessionManager class manages sessions. - - TODO the interface of this class is not really great. - The implementation suffers from that all the functions from the - public interface just wrap around functions which do the actual work. - This could be improved. -*/ - -class SessionManagerPrivate -{ -public: - void restoreValues(const PersistentSettingsReader &reader); - void restoreDependencies(const PersistentSettingsReader &reader); - void restoreStartupProject(const PersistentSettingsReader &reader); - void restoreEditors(const PersistentSettingsReader &reader); - void restoreProjects(const FilePaths &fileList); - void askUserAboutFailedProjects(); - void sessionLoadingProgress(); - - bool recursiveDependencyCheck(const FilePath &newDep, const FilePath &checkDep) const; - FilePaths dependencies(const FilePath &proName) const; - FilePaths dependenciesOrder() const; - void dependencies(const FilePath &proName, FilePaths &result) const; - - static QString windowTitleAddition(const FilePath &filePath); - static QString sessionTitle(const FilePath &filePath); - - bool hasProjects() const { return !m_projects.isEmpty(); } - - QString m_sessionName = QLatin1String(DEFAULT_SESSION); - bool m_virginSession = true; - bool m_loadingSession = false; - bool m_casadeSetActive = false; - - mutable QStringList m_sessions; - mutable QHash m_sessionDateTimes; - QHash m_lastActiveTimes; - - Project *m_startupProject = nullptr; - QList m_projects; - FilePaths m_failedProjects; - QMap m_depMap; - QMap m_values; - QFutureInterface m_future; - PersistentSettingsWriter *m_writer = nullptr; - -private: - static QString locationInProject(const FilePath &filePath); -}; - -static SessionManager *m_instance = nullptr; -static SessionManagerPrivate *d = nullptr; - -static QString projectFolderId(Project *pro) -{ - return pro->projectFilePath().toString(); -} - -const int PROJECT_SORT_VALUE = 100; - -SessionManager::SessionManager(QObject *parent) : QObject(parent) -{ - m_instance = this; - d = new SessionManagerPrivate; - - connect(ModeManager::instance(), &ModeManager::currentModeChanged, - this, &SessionManager::saveActiveMode); - - connect(ICore::instance(), &ICore::saveSettingsRequested, this, [] { - QVariantMap times; - for (auto it = d->m_lastActiveTimes.cbegin(); it != d->m_lastActiveTimes.cend(); ++it) - times.insert(it.key(), it.value()); - ICore::settings()->setValue(LAST_ACTIVE_TIMES_KEY, times); - }); - - connect(EditorManager::instance(), &EditorManager::editorCreated, - this, &SessionManager::configureEditor); - connect(this, &SessionManager::projectAdded, - EditorManager::instance(), &EditorManager::updateWindowTitles); - connect(this, &SessionManager::projectRemoved, - EditorManager::instance(), &EditorManager::updateWindowTitles); - connect(this, &SessionManager::projectDisplayNameChanged, - EditorManager::instance(), &EditorManager::updateWindowTitles); - connect(EditorManager::instance(), &EditorManager::editorOpened, - this, &SessionManager::markSessionFileDirty); - connect(EditorManager::instance(), &EditorManager::editorsClosed, - this, &SessionManager::markSessionFileDirty); - - EditorManager::setWindowTitleAdditionHandler(&SessionManagerPrivate::windowTitleAddition); - EditorManager::setSessionTitleHandler(&SessionManagerPrivate::sessionTitle); -} - -SessionManager::~SessionManager() -{ - EditorManager::setWindowTitleAdditionHandler({}); - EditorManager::setSessionTitleHandler({}); - emit m_instance->aboutToUnloadSession(d->m_sessionName); - delete d->m_writer; - delete d; - d = nullptr; -} - -SessionManager *SessionManager::instance() -{ - return m_instance; -} - -bool SessionManager::isDefaultVirgin() -{ - return isDefaultSession(d->m_sessionName) && d->m_virginSession; -} - -bool SessionManager::isDefaultSession(const QString &session) -{ - return session == QLatin1String(DEFAULT_SESSION); -} - -void SessionManager::saveActiveMode(Id mode) -{ - if (mode != Core::Constants::MODE_WELCOME) - setValue(QLatin1String("ActiveMode"), mode.toString()); -} - -bool SessionManagerPrivate::recursiveDependencyCheck(const FilePath &newDep, - const FilePath &checkDep) const -{ - if (newDep == checkDep) - return false; - - const FilePaths depList = m_depMap.value(checkDep); - for (const FilePath &dependency : depList) { - if (!recursiveDependencyCheck(newDep, dependency)) - return false; - } - - return true; -} - -/* - * The dependency management exposes an interface based on projects, but - * is internally purely string based. This is suboptimal. Probably it would be - * nicer to map the filenames to projects on load and only map it back to - * filenames when saving. - */ - -QList SessionManager::dependencies(const Project *project) -{ - const FilePath proName = project->projectFilePath(); - const FilePaths proDeps = d->m_depMap.value(proName); - - QList projects; - for (const FilePath &dep : proDeps) { - Project *pro = Utils::findOrDefault(d->m_projects, [&dep](Project *p) { - return p->projectFilePath() == dep; - }); - if (pro) - projects += pro; - } - - return projects; -} - -bool SessionManager::hasDependency(const Project *project, const Project *depProject) -{ - const FilePath proName = project->projectFilePath(); - const FilePath depName = depProject->projectFilePath(); - - const FilePaths proDeps = d->m_depMap.value(proName); - return proDeps.contains(depName); -} - -bool SessionManager::canAddDependency(const Project *project, const Project *depProject) -{ - const FilePath newDep = project->projectFilePath(); - const FilePath checkDep = depProject->projectFilePath(); - - return d->recursiveDependencyCheck(newDep, checkDep); -} - -bool SessionManager::addDependency(Project *project, Project *depProject) -{ - const FilePath proName = project->projectFilePath(); - const FilePath depName = depProject->projectFilePath(); - - // check if this dependency is valid - if (!d->recursiveDependencyCheck(proName, depName)) - return false; - - FilePaths proDeps = d->m_depMap.value(proName); - if (!proDeps.contains(depName)) { - proDeps.append(depName); - d->m_depMap[proName] = proDeps; - } - emit m_instance->dependencyChanged(project, depProject); - - return true; -} - -void SessionManager::removeDependency(Project *project, Project *depProject) -{ - const FilePath proName = project->projectFilePath(); - const FilePath depName = depProject->projectFilePath(); - - FilePaths proDeps = d->m_depMap.value(proName); - proDeps.removeAll(depName); - if (proDeps.isEmpty()) - d->m_depMap.remove(proName); - else - d->m_depMap[proName] = proDeps; - emit m_instance->dependencyChanged(project, depProject); -} - -bool SessionManager::isProjectConfigurationCascading() -{ - return d->m_casadeSetActive; -} - -void SessionManager::setProjectConfigurationCascading(bool b) -{ - d->m_casadeSetActive = b; - markSessionFileDirty(); -} - -void SessionManager::setActiveTarget(Project *project, Target *target, SetActive cascade) -{ - QTC_ASSERT(project, return); - - if (project->isShuttingDown()) - return; - - project->setActiveTarget(target); - - if (!target) // never cascade setting no target - return; - - if (cascade != SetActive::Cascade || !d->m_casadeSetActive) - return; - - Utils::Id kitId = target->kit()->id(); - for (Project *otherProject : SessionManager::projects()) { - if (otherProject == project) - continue; - if (Target *otherTarget = Utils::findOrDefault(otherProject->targets(), - [kitId](Target *t) { return t->kit()->id() == kitId; })) - otherProject->setActiveTarget(otherTarget); - } -} - -void SessionManager::setActiveBuildConfiguration(Target *target, BuildConfiguration *bc, SetActive cascade) -{ - QTC_ASSERT(target, return); - QTC_ASSERT(target->project(), return); - - if (target->project()->isShuttingDown() || target->isShuttingDown()) - return; - - target->setActiveBuildConfiguration(bc); - - if (!bc) - return; - if (cascade != SetActive::Cascade || !d->m_casadeSetActive) - return; - - Utils::Id kitId = target->kit()->id(); - QString name = bc->displayName(); // We match on displayname - for (Project *otherProject : SessionManager::projects()) { - if (otherProject == target->project()) - continue; - Target *otherTarget = otherProject->activeTarget(); - if (!otherTarget || otherTarget->kit()->id() != kitId) - continue; - - for (BuildConfiguration *otherBc : otherTarget->buildConfigurations()) { - if (otherBc->displayName() == name) { - otherTarget->setActiveBuildConfiguration(otherBc); - break; - } - } - } -} - -void SessionManager::setActiveDeployConfiguration(Target *target, DeployConfiguration *dc, SetActive cascade) -{ - QTC_ASSERT(target, return); - QTC_ASSERT(target->project(), return); - - if (target->project()->isShuttingDown() || target->isShuttingDown()) - return; - - target->setActiveDeployConfiguration(dc); - - if (!dc) - return; - if (cascade != SetActive::Cascade || !d->m_casadeSetActive) - return; - - Utils::Id kitId = target->kit()->id(); - QString name = dc->displayName(); // We match on displayname - for (Project *otherProject : SessionManager::projects()) { - if (otherProject == target->project()) - continue; - Target *otherTarget = otherProject->activeTarget(); - if (!otherTarget || otherTarget->kit()->id() != kitId) - continue; - - for (DeployConfiguration *otherDc : otherTarget->deployConfigurations()) { - if (otherDc->displayName() == name) { - otherTarget->setActiveDeployConfiguration(otherDc); - break; - } - } - } -} - -void SessionManager::setStartupProject(Project *startupProject) -{ - QTC_ASSERT((!startupProject && d->m_projects.isEmpty()) - || (startupProject && d->m_projects.contains(startupProject)), return); - - if (d->m_startupProject == startupProject) - return; - - d->m_startupProject = startupProject; - if (d->m_startupProject && d->m_startupProject->needsConfiguration()) { - ModeManager::activateMode(Constants::MODE_SESSION); - ModeManager::setFocusToCurrentMode(); - } - FolderNavigationWidgetFactory::setFallbackSyncFilePath( - startupProject ? startupProject->projectFilePath().parentDir() : FilePath()); - emit m_instance->startupProjectChanged(startupProject); -} - -Project *SessionManager::startupProject() -{ - return d->m_startupProject; -} - -Target *SessionManager::startupTarget() -{ - return d->m_startupProject ? d->m_startupProject->activeTarget() : nullptr; -} - -BuildSystem *SessionManager::startupBuildSystem() -{ - Target *t = startupTarget(); - return t ? t->buildSystem() : nullptr; -} - -/*! - * Returns the RunConfiguration of the currently active target - * of the startup project, if such exists, or \c nullptr otherwise. - */ - - -RunConfiguration *SessionManager::startupRunConfiguration() -{ - Target *t = startupTarget(); - return t ? t->activeRunConfiguration() : nullptr; -} - -void SessionManager::addProject(Project *pro) -{ - QTC_ASSERT(pro, return); - QTC_CHECK(!pro->displayName().isEmpty()); - QTC_CHECK(pro->id().isValid()); - - d->m_virginSession = false; - QTC_ASSERT(!d->m_projects.contains(pro), return); - - d->m_projects.append(pro); - - connect(pro, &Project::displayNameChanged, - m_instance, [pro]() { emit m_instance->projectDisplayNameChanged(pro); }); - - emit m_instance->projectAdded(pro); - const auto updateFolderNavigation = [pro] { - // destructing projects might trigger changes, so check if the project is actually there - if (QTC_GUARD(d->m_projects.contains(pro))) { - const QIcon icon = pro->rootProjectNode() ? pro->rootProjectNode()->icon() : QIcon(); - FolderNavigationWidgetFactory::insertRootDirectory({projectFolderId(pro), - PROJECT_SORT_VALUE, - pro->displayName(), - pro->projectFilePath().parentDir(), - icon}); - } - }; - updateFolderNavigation(); - configureEditors(pro); - connect(pro, &Project::fileListChanged, m_instance, [pro, updateFolderNavigation]() { - configureEditors(pro); - updateFolderNavigation(); // update icon - }); - connect(pro, &Project::displayNameChanged, m_instance, updateFolderNavigation); - - if (!startupProject()) - setStartupProject(pro); -} - -void SessionManager::removeProject(Project *project) -{ - d->m_virginSession = false; - QTC_ASSERT(project, return); - removeProjects({project}); -} - -bool SessionManager::loadingSession() -{ - return d->m_loadingSession; -} - -bool SessionManager::save() -{ - emit m_instance->aboutToSaveSession(); - - const FilePath filePath = sessionNameToFileName(d->m_sessionName); - QVariantMap data; - - // See the explanation at loadSession() for how we handle the implicit default session. - if (isDefaultVirgin()) { - if (filePath.exists()) { - PersistentSettingsReader reader; - if (!reader.load(filePath)) { - QMessageBox::warning(ICore::dialogParent(), Tr::tr("Error while saving session"), - Tr::tr("Could not save session %1").arg(filePath.toUserOutput())); - return false; - } - data = reader.restoreValues(); - } - } else { - // save the startup project - if (d->m_startupProject) - data.insert("StartupProject", d->m_startupProject->projectFilePath().toSettings()); - - const QColor c = StyleHelper::requestedBaseColor(); - if (c.isValid()) { - QString tmp = QString::fromLatin1("#%1%2%3") - .arg(c.red(), 2, 16, QLatin1Char('0')) - .arg(c.green(), 2, 16, QLatin1Char('0')) - .arg(c.blue(), 2, 16, QLatin1Char('0')); - data.insert(QLatin1String("Color"), tmp); - } - - FilePaths projectFiles = Utils::transform(projects(), &Project::projectFilePath); - // Restore information on projects that failed to load: - // don't read projects to the list, which the user loaded - for (const FilePath &failed : std::as_const(d->m_failedProjects)) { - if (!projectFiles.contains(failed)) - projectFiles << failed; - } - - data.insert("ProjectList", Utils::transform(projectFiles, - &FilePath::toString)); - data.insert("CascadeSetActive", d->m_casadeSetActive); - - QVariantMap depMap; - auto i = d->m_depMap.constBegin(); - while (i != d->m_depMap.constEnd()) { - QString key = i.key().toString(); - QStringList values; - const FilePaths valueList = i.value(); - for (const FilePath &value : valueList) - values << value.toString(); - depMap.insert(key, values); - ++i; - } - data.insert(QLatin1String("ProjectDependencies"), QVariant(depMap)); - data.insert(QLatin1String("EditorSettings"), EditorManager::saveState().toBase64()); - } - - const auto end = d->m_values.constEnd(); - QStringList keys; - for (auto it = d->m_values.constBegin(); it != end; ++it) { - data.insert(QLatin1String("value-") + it.key(), it.value()); - keys << it.key(); - } - data.insert(QLatin1String("valueKeys"), keys); - - if (!d->m_writer || d->m_writer->fileName() != filePath) { - delete d->m_writer; - d->m_writer = new PersistentSettingsWriter(filePath, "QtCreatorSession"); - } - const bool result = d->m_writer->save(data, ICore::dialogParent()); - if (result) { - if (!isDefaultVirgin()) - d->m_sessionDateTimes.insert(activeSession(), QDateTime::currentDateTime()); - } else { - QMessageBox::warning(ICore::dialogParent(), Tr::tr("Error while saving session"), - Tr::tr("Could not save session to file %1").arg(d->m_writer->fileName().toUserOutput())); - } - - return result; -} - -/*! - Closes all projects - */ -void SessionManager::closeAllProjects() -{ - removeProjects(projects()); -} - -const QList SessionManager::projects() -{ - return d->m_projects; -} - -bool SessionManager::hasProjects() -{ - return d->hasProjects(); -} - -bool SessionManager::hasProject(Project *p) -{ - return d->m_projects.contains(p); -} - -FilePaths SessionManagerPrivate::dependencies(const FilePath &proName) const -{ - FilePaths result; - dependencies(proName, result); - return result; -} - -void SessionManagerPrivate::dependencies(const FilePath &proName, FilePaths &result) const -{ - const FilePaths depends = m_depMap.value(proName); - - for (const FilePath &dep : depends) - dependencies(dep, result); - - if (!result.contains(proName)) - result.append(proName); -} - -QString SessionManagerPrivate::sessionTitle(const FilePath &filePath) -{ - if (SessionManager::isDefaultSession(d->m_sessionName)) { - if (filePath.isEmpty()) { - // use single project's name if there is only one loaded. - const QList projects = SessionManager::projects(); - if (projects.size() == 1) - return projects.first()->displayName(); - } - } else { - QString sessionName = d->m_sessionName; - if (sessionName.isEmpty()) - sessionName = Tr::tr("Untitled"); - return sessionName; - } - return QString(); -} - -QString SessionManagerPrivate::locationInProject(const FilePath &filePath) { - const Project *project = SessionManager::projectForFile(filePath); - if (!project) - return QString(); - - const FilePath parentDir = filePath.parentDir(); - if (parentDir == project->projectDirectory()) - return "@ " + project->displayName(); - - if (filePath.isChildOf(project->projectDirectory())) { - const FilePath dirInProject = parentDir.relativeChildPath(project->projectDirectory()); - return "(" + dirInProject.toUserOutput() + " @ " + project->displayName() + ")"; - } - - // For a file that is "outside" the project it belongs to, we display its - // dir's full path because it is easier to read than a series of "../../.". - // Example: /home/hugo/GenericProject/App.files lists /home/hugo/lib/Bar.cpp - return "(" + parentDir.toUserOutput() + " @ " + project->displayName() + ")"; -} - -QString SessionManagerPrivate::windowTitleAddition(const FilePath &filePath) -{ - return filePath.isEmpty() ? QString() : locationInProject(filePath); -} - -FilePaths SessionManagerPrivate::dependenciesOrder() const -{ - QList> unordered; - FilePaths ordered; - - // copy the map to a temporary list - for (const Project *pro : m_projects) { - const FilePath proName = pro->projectFilePath(); - const FilePaths depList = filtered(m_depMap.value(proName), - [this](const FilePath &proPath) { - return contains(m_projects, [proPath](const Project *p) { - return p->projectFilePath() == proPath; - }); - }); - unordered.push_back({proName, depList}); - } - - while (!unordered.isEmpty()) { - for (int i = (unordered.count() - 1); i >= 0; --i) { - if (unordered.at(i).second.isEmpty()) { - ordered << unordered.at(i).first; - unordered.removeAt(i); - } - } - - // remove the handled projects from the dependency lists - // of the remaining unordered projects - for (int i = 0; i < unordered.count(); ++i) { - for (const FilePath &pro : std::as_const(ordered)) { - FilePaths depList = unordered.at(i).second; - depList.removeAll(pro); - unordered[i].second = depList; - } - } - } - - return ordered; -} - -QList SessionManager::projectOrder(const Project *project) -{ - QList result; - - FilePaths pros; - if (project) - pros = d->dependencies(project->projectFilePath()); - else - pros = d->dependenciesOrder(); - - for (const FilePath &proFile : std::as_const(pros)) { - for (Project *pro : projects()) { - if (pro->projectFilePath() == proFile) { - result << pro; - break; - } - } - } - - return result; -} - -Project *SessionManager::projectForFile(const FilePath &fileName) -{ - if (Project * const project = Utils::findOrDefault(SessionManager::projects(), - [&fileName](const Project *p) { return p->isKnownFile(fileName); })) { - return project; - } - return Utils::findOrDefault(SessionManager::projects(), - [&fileName](const Project *p) { - for (const Target * const target : p->targets()) { - for (const BuildConfiguration * const bc : target->buildConfigurations()) { - if (fileName.isChildOf(bc->buildDirectory())) - return false; - } - } - return fileName.isChildOf(p->projectDirectory()); - }); -} - -Project *SessionManager::projectWithProjectFilePath(const FilePath &filePath) -{ - return Utils::findOrDefault(SessionManager::projects(), - [&filePath](const Project *p) { return p->projectFilePath() == filePath; }); -} - -void SessionManager::configureEditor(IEditor *editor, const QString &fileName) -{ - if (auto textEditor = qobject_cast(editor)) { - Project *project = projectForFile(Utils::FilePath::fromString(fileName)); - // Global settings are the default. - if (project) - project->editorConfiguration()->configureEditor(textEditor); - } -} - -void SessionManager::configureEditors(Project *project) -{ - const QList documents = DocumentModel::openedDocuments(); - for (IDocument *document : documents) { - if (project->isKnownFile(document->filePath())) { - const QList editors = DocumentModel::editorsForDocument(document); - for (IEditor *editor : editors) { - if (auto textEditor = qobject_cast(editor)) { - project->editorConfiguration()->configureEditor(textEditor); - } - } - } - } -} - -void SessionManager::removeProjects(const QList &remove) -{ - for (Project *pro : remove) - emit m_instance->aboutToRemoveProject(pro); - - bool changeStartupProject = false; - - // Delete projects - for (Project *pro : remove) { - pro->saveSettings(); - pro->markAsShuttingDown(); - - // Remove the project node: - d->m_projects.removeOne(pro); - - if (pro == d->m_startupProject) - changeStartupProject = true; - - FolderNavigationWidgetFactory::removeRootDirectory(projectFolderId(pro)); - disconnect(pro, nullptr, m_instance, nullptr); - emit m_instance->projectRemoved(pro); - } - - if (changeStartupProject) - setStartupProject(hasProjects() ? projects().first() : nullptr); - - qDeleteAll(remove); -} - -/*! - Lets other plugins store persistent values within the session file. -*/ - -void SessionManager::setValue(const QString &name, const QVariant &value) -{ - if (d->m_values.value(name) == value) - return; - d->m_values.insert(name, value); -} - -QVariant SessionManager::value(const QString &name) -{ - auto it = d->m_values.constFind(name); - return (it == d->m_values.constEnd()) ? QVariant() : *it; -} - -QString SessionManager::activeSession() -{ - return d->m_sessionName; -} - -QStringList SessionManager::sessions() -{ - if (d->m_sessions.isEmpty()) { - // We are not initialized yet, so do that now - const FilePaths sessionFiles = - ICore::userResourcePath().dirEntries({{"*qws"}}, QDir::Time | QDir::Reversed); - const QVariantMap lastActiveTimes = ICore::settings()->value(LAST_ACTIVE_TIMES_KEY).toMap(); - for (const FilePath &file : sessionFiles) { - const QString &name = file.completeBaseName(); - d->m_sessionDateTimes.insert(name, file.lastModified()); - const auto lastActiveTime = lastActiveTimes.find(name); - d->m_lastActiveTimes.insert(name, lastActiveTime != lastActiveTimes.end() - ? lastActiveTime->toDateTime() - : file.lastModified()); - if (name != QLatin1String(DEFAULT_SESSION)) - d->m_sessions << name; - } - d->m_sessions.prepend(QLatin1String(DEFAULT_SESSION)); - } - return d->m_sessions; -} - -QDateTime SessionManager::sessionDateTime(const QString &session) -{ - return d->m_sessionDateTimes.value(session); -} - -QDateTime SessionManager::lastActiveTime(const QString &session) -{ - return d->m_lastActiveTimes.value(session); -} - -FilePath SessionManager::sessionNameToFileName(const QString &session) -{ - return ICore::userResourcePath(session + ".qws"); -} - -/*! - Creates \a session, but does not actually create the file. -*/ - -bool SessionManager::createSession(const QString &session) -{ - if (sessions().contains(session)) - return false; - Q_ASSERT(d->m_sessions.size() > 0); - d->m_sessions.insert(1, session); - d->m_lastActiveTimes.insert(session, QDateTime::currentDateTime()); - return true; -} - -bool SessionManager::renameSession(const QString &original, const QString &newName) -{ - if (!cloneSession(original, newName)) - return false; - if (original == activeSession()) - loadSession(newName); - emit instance()->sessionRenamed(original, newName); - return deleteSession(original); -} - - -/*! - \brief Shows a dialog asking the user to confirm deleting the session \p session -*/ -bool SessionManager::confirmSessionDelete(const QStringList &sessions) -{ - const QString title = sessions.size() == 1 ? Tr::tr("Delete Session") : Tr::tr("Delete Sessions"); - const QString question = sessions.size() == 1 - ? Tr::tr("Delete session %1?").arg(sessions.first()) - : Tr::tr("Delete these sessions?\n %1").arg(sessions.join("\n ")); - return QMessageBox::question(ICore::dialogParent(), - title, - question, - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes; -} - -/*! - Deletes \a session name from session list and the file from disk. -*/ -bool SessionManager::deleteSession(const QString &session) -{ - if (!d->m_sessions.contains(session)) - return false; - d->m_sessions.removeOne(session); - d->m_lastActiveTimes.remove(session); - emit instance()->sessionRemoved(session); - FilePath sessionFile = sessionNameToFileName(session); - if (sessionFile.exists()) - return sessionFile.removeFile(); - return false; -} - -void SessionManager::deleteSessions(const QStringList &sessions) -{ - for (const QString &session : sessions) - deleteSession(session); -} - -bool SessionManager::cloneSession(const QString &original, const QString &clone) -{ - if (!d->m_sessions.contains(original)) - return false; - - FilePath sessionFile = sessionNameToFileName(original); - // If the file does not exist, we can still clone - if (!sessionFile.exists() || sessionFile.copyFile(sessionNameToFileName(clone))) { - d->m_sessions.insert(1, clone); - d->m_sessionDateTimes.insert(clone, sessionNameToFileName(clone).lastModified()); - return true; - } - return false; -} - -void SessionManagerPrivate::restoreValues(const PersistentSettingsReader &reader) -{ - const QStringList keys = reader.restoreValue(QLatin1String("valueKeys")).toStringList(); - for (const QString &key : keys) { - QVariant value = reader.restoreValue(QLatin1String("value-") + key); - m_values.insert(key, value); - } -} - -void SessionManagerPrivate::restoreDependencies(const PersistentSettingsReader &reader) -{ - QMap depMap = reader.restoreValue(QLatin1String("ProjectDependencies")).toMap(); - auto i = depMap.constBegin(); - while (i != depMap.constEnd()) { - const QString &key = i.key(); - FilePaths values; - const QStringList valueList = i.value().toStringList(); - for (const QString &value : valueList) - values << FilePath::fromString(value); - m_depMap.insert(FilePath::fromString(key), values); - ++i; - } -} - -void SessionManagerPrivate::askUserAboutFailedProjects() -{ - FilePaths failedProjects = m_failedProjects; - if (!failedProjects.isEmpty()) { - QString fileList = FilePath::formatFilePaths(failedProjects, "
"); - QMessageBox box(QMessageBox::Warning, - Tr::tr("Failed to restore project files"), - Tr::tr("Could not restore the following project files:
%1"). - arg(fileList)); - auto keepButton = new QPushButton(Tr::tr("Keep projects in Session"), &box); - auto removeButton = new QPushButton(Tr::tr("Remove projects from Session"), &box); - box.addButton(keepButton, QMessageBox::AcceptRole); - box.addButton(removeButton, QMessageBox::DestructiveRole); - - box.exec(); - - if (box.clickedButton() == removeButton) - m_failedProjects.clear(); - } -} - -void SessionManagerPrivate::restoreStartupProject(const PersistentSettingsReader &reader) -{ - const FilePath startupProject = FilePath::fromSettings(reader.restoreValue("StartupProject")); - if (!startupProject.isEmpty()) { - for (Project *pro : std::as_const(m_projects)) { - if (pro->projectFilePath() == startupProject) { - m_instance->setStartupProject(pro); - break; - } - } - } - if (!m_startupProject) { - if (!startupProject.isEmpty()) - qWarning() << "Could not find startup project" << startupProject; - if (hasProjects()) - m_instance->setStartupProject(m_projects.first()); - } -} - -void SessionManagerPrivate::restoreEditors(const PersistentSettingsReader &reader) -{ - const QVariant editorsettings = reader.restoreValue(QLatin1String("EditorSettings")); - if (editorsettings.isValid()) { - EditorManager::restoreState(QByteArray::fromBase64(editorsettings.toByteArray())); - sessionLoadingProgress(); - } -} - -/*! - Loads a session, takes a session name (not filename). -*/ -void SessionManagerPrivate::restoreProjects(const FilePaths &fileList) -{ - // indirectly adds projects to session - // Keep projects that failed to load in the session! - m_failedProjects = fileList; - if (!fileList.isEmpty()) { - ProjectExplorerPlugin::OpenProjectResult result = ProjectExplorerPlugin::openProjects(fileList); - if (!result) - ProjectExplorerPlugin::showOpenProjectError(result); - const QList projects = result.projects(); - for (const Project *p : projects) - m_failedProjects.removeAll(p->projectFilePath()); - } -} - -/* - * ========== Notes on storing and loading the default session ========== - * The default session comes in two flavors: implicit and explicit. The implicit one, - * also referred to as "default virgin" in the code base, is the one that is active - * at start-up, if no session has been explicitly loaded due to command-line arguments - * or the "restore last session" setting in the session manager. - * The implicit default session silently turns into the explicit default session - * by loading a project or a file or changing settings in the Dependencies panel. The explicit - * default session can also be loaded by the user via the Welcome Screen. - * This mechanism somewhat complicates the handling of session-specific settings such as - * the ones in the task pane: Users expect that changes they make there become persistent, even - * when they are in the implicit default session. However, we can't just blindly store - * the implicit default session, because then we'd overwrite the project list of the explicit - * default session. Therefore, we use the following logic: - * - Upon start-up, if no session is to be explicitly loaded, we restore the parts of the - * explicit default session that are not related to projects, editors etc; the - * "general settings" of the session, so to speak. - * - When storing the implicit default session, we overwrite only these "general settings" - * of the explicit default session and keep the others as they are. - * - When switching from the implicit to the explicit default session, we keep the - * "general settings" and load everything else from the session file. - * This guarantees that user changes are properly transferred and nothing gets lost from - * either the implicit or the explicit default session. - * - */ -bool SessionManager::loadSession(const QString &session, bool initial) -{ - const bool loadImplicitDefault = session.isEmpty(); - const bool switchFromImplicitToExplicitDefault = session == DEFAULT_SESSION - && d->m_sessionName == DEFAULT_SESSION && !initial; - - // Do nothing if we have that session already loaded, - // exception if the session is the default virgin session - // we still want to be able to load the default session - if (session == d->m_sessionName && !isDefaultVirgin()) - return true; - - if (!loadImplicitDefault && !sessions().contains(session)) - return false; - - FilePaths fileList; - // Try loading the file - FilePath fileName = sessionNameToFileName(loadImplicitDefault ? DEFAULT_SESSION : session); - PersistentSettingsReader reader; - if (fileName.exists()) { - if (!reader.load(fileName)) { - QMessageBox::warning(ICore::dialogParent(), Tr::tr("Error while restoring session"), - Tr::tr("Could not restore session %1").arg(fileName.toUserOutput())); - - return false; - } - - if (loadImplicitDefault) { - d->restoreValues(reader); - emit m_instance->sessionLoaded(DEFAULT_SESSION); - return true; - } - - fileList = FileUtils::toFilePathList(reader.restoreValue("ProjectList").toStringList()); - } else if (loadImplicitDefault) { - return true; - } - - d->m_loadingSession = true; - - // Allow everyone to set something in the session and before saving - emit m_instance->aboutToUnloadSession(d->m_sessionName); - - if (!save()) { - d->m_loadingSession = false; - return false; - } - - // Clean up - if (!EditorManager::closeAllEditors()) { - d->m_loadingSession = false; - return false; - } - - // find a list of projects to close later - const QList projectsToRemove = Utils::filtered(projects(), [&fileList](Project *p) { - return !fileList.contains(p->projectFilePath()); - }); - const QList openProjects = projects(); - const FilePaths projectPathsToLoad = Utils::filtered(fileList, [&openProjects](const FilePath &path) { - return !Utils::contains(openProjects, [&path](Project *p) { - return p->projectFilePath() == path; - }); - }); - d->m_failedProjects.clear(); - d->m_depMap.clear(); - if (!switchFromImplicitToExplicitDefault) - d->m_values.clear(); - d->m_casadeSetActive = false; - - d->m_sessionName = session; - delete d->m_writer; - d->m_writer = nullptr; - EditorManager::updateWindowTitles(); - - if (fileName.exists()) { - d->m_virginSession = false; - - ProgressManager::addTask(d->m_future.future(), Tr::tr("Loading Session"), - "ProjectExplorer.SessionFile.Load"); - - d->m_future.setProgressRange(0, 1); - d->m_future.setProgressValue(0); - - if (!switchFromImplicitToExplicitDefault) - d->restoreValues(reader); - emit m_instance->aboutToLoadSession(session); - - // retrieve all values before the following code could change them again - Id modeId = Id::fromSetting(value(QLatin1String("ActiveMode"))); - if (!modeId.isValid()) - modeId = Id(Core::Constants::MODE_EDIT); - - QColor c = QColor(reader.restoreValue(QLatin1String("Color")).toString()); - if (c.isValid()) - StyleHelper::setBaseColor(c); - - d->m_future.setProgressRange(0, projectPathsToLoad.count() + 1/*initialization above*/ + 1/*editors*/); - d->m_future.setProgressValue(1); - QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); - - d->restoreProjects(projectPathsToLoad); - d->sessionLoadingProgress(); - d->restoreDependencies(reader); - d->restoreStartupProject(reader); - - removeProjects(projectsToRemove); // only remove old projects now that the startup project is set! - - d->restoreEditors(reader); - - d->m_future.reportFinished(); - d->m_future = QFutureInterface(); - - // Fall back to Project mode if the startup project is unconfigured and - // use the mode saved in the session otherwise - if (d->m_startupProject && d->m_startupProject->needsConfiguration()) - modeId = Id(Constants::MODE_SESSION); - - ModeManager::activateMode(modeId); - ModeManager::setFocusToCurrentMode(); - } else { - removeProjects(projects()); - ModeManager::activateMode(Id(Core::Constants::MODE_EDIT)); - ModeManager::setFocusToCurrentMode(); - } - - d->m_casadeSetActive = reader.restoreValue(QLatin1String("CascadeSetActive"), false).toBool(); - d->m_lastActiveTimes.insert(session, QDateTime::currentDateTime()); - - emit m_instance->sessionLoaded(session); - - // Starts a event loop, better do that at the very end - d->askUserAboutFailedProjects(); - d->m_loadingSession = false; - return true; -} - -/*! - Returns the last session that was opened by the user. -*/ -QString SessionManager::lastSession() -{ - return ICore::settings()->value(Constants::LASTSESSION_KEY).toString(); -} - -/*! - Returns the session that was active when Qt Creator was last closed, if any. -*/ -QString SessionManager::startupSession() -{ - return ICore::settings()->value(Constants::STARTUPSESSION_KEY).toString(); -} - -void SessionManager::reportProjectLoadingProgress() -{ - d->sessionLoadingProgress(); -} - -void SessionManager::markSessionFileDirty() -{ - d->m_virginSession = false; -} - -void SessionManagerPrivate::sessionLoadingProgress() -{ - m_future.setProgressValue(m_future.progressValue() + 1); - QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); -} - -FilePaths SessionManager::projectsForSessionName(const QString &session) -{ - const FilePath fileName = sessionNameToFileName(session); - PersistentSettingsReader reader; - if (fileName.exists()) { - if (!reader.load(fileName)) { - qWarning() << "Could not restore session" << fileName.toUserOutput(); - return {}; - } - } - return transform(reader.restoreValue(QLatin1String("ProjectList")).toStringList(), - &FilePath::fromUserInput); -} - -#ifdef WITH_TESTS - -void ProjectExplorerPlugin::testSessionSwitch() -{ - QVERIFY(SessionManager::createSession("session1")); - QVERIFY(SessionManager::createSession("session2")); - QTemporaryFile cppFile("main.cpp"); - QVERIFY(cppFile.open()); - cppFile.close(); - QTemporaryFile projectFile1("XXXXXX.pro"); - QTemporaryFile projectFile2("XXXXXX.pro"); - struct SessionSpec { - SessionSpec(const QString &n, QTemporaryFile &f) : name(n), projectFile(f) {} - const QString name; - QTemporaryFile &projectFile; - }; - std::vector sessionSpecs{SessionSpec("session1", projectFile1), - SessionSpec("session2", projectFile2)}; - for (const SessionSpec &sessionSpec : sessionSpecs) { - static const QByteArray proFileContents - = "TEMPLATE = app\n" - "CONFIG -= qt\n" - "SOURCES = " + cppFile.fileName().toLocal8Bit(); - QVERIFY(sessionSpec.projectFile.open()); - sessionSpec.projectFile.write(proFileContents); - sessionSpec.projectFile.close(); - QVERIFY(SessionManager::loadSession(sessionSpec.name)); - const OpenProjectResult openResult - = ProjectExplorerPlugin::openProject( - FilePath::fromString(sessionSpec.projectFile.fileName())); - if (openResult.errorMessage().contains("text/plain")) - QSKIP("This test requires the presence of QmakeProjectManager to be fully functional"); - QVERIFY(openResult); - QCOMPARE(openResult.projects().count(), 1); - QVERIFY(openResult.project()); - QCOMPARE(SessionManager::projects().count(), 1); - } - for (int i = 0; i < 30; ++i) { - QVERIFY(SessionManager::loadSession("session1")); - QCOMPARE(SessionManager::activeSession(), "session1"); - QCOMPARE(SessionManager::projects().count(), 1); - QVERIFY(SessionManager::loadSession("session2")); - QCOMPARE(SessionManager::activeSession(), "session2"); - QCOMPARE(SessionManager::projects().count(), 1); - } - QVERIFY(SessionManager::loadSession("session1")); - SessionManager::closeAllProjects(); - QVERIFY(SessionManager::loadSession("session2")); - SessionManager::closeAllProjects(); - QVERIFY(SessionManager::deleteSession("session1")); - QVERIFY(SessionManager::deleteSession("session2")); -} - -#endif // WITH_TESTS - -} // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/session.h b/src/plugins/projectexplorer/session.h deleted file mode 100644 index a29e478046b..00000000000 --- a/src/plugins/projectexplorer/session.h +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "projectexplorer_export.h" - -#include -#include - -#include -#include -#include - -namespace Core { class IEditor; } - -namespace ProjectExplorer { - -class Project; -class Target; -class BuildConfiguration; -class BuildSystem; -class DeployConfiguration; -class RunConfiguration; - -enum class SetActive { Cascade, NoCascade }; - -class PROJECTEXPLORER_EXPORT SessionManager : public QObject -{ - Q_OBJECT - -public: - explicit SessionManager(QObject *parent = nullptr); - ~SessionManager() override; - - static SessionManager *instance(); - - // higher level session management - static QString activeSession(); - static QString lastSession(); - static QString startupSession(); - static QStringList sessions(); - static QDateTime sessionDateTime(const QString &session); - static QDateTime lastActiveTime(const QString &session); - - static bool createSession(const QString &session); - - static bool confirmSessionDelete(const QStringList &sessions); - static bool deleteSession(const QString &session); - static void deleteSessions(const QStringList &sessions); - - static bool cloneSession(const QString &original, const QString &clone); - static bool renameSession(const QString &original, const QString &newName); - - static bool loadSession(const QString &session, bool initial = false); - - static bool save(); - static void closeAllProjects(); - - static void addProject(Project *project); - static void removeProject(Project *project); - static void removeProjects(const QList &remove); - - static void setStartupProject(Project *startupProject); - - static QList dependencies(const Project *project); - static bool hasDependency(const Project *project, const Project *depProject); - static bool canAddDependency(const Project *project, const Project *depProject); - static bool addDependency(Project *project, Project *depProject); - static void removeDependency(Project *project, Project *depProject); - - static bool isProjectConfigurationCascading(); - static void setProjectConfigurationCascading(bool b); - - static void setActiveTarget(Project *p, Target *t, SetActive cascade); - static void setActiveBuildConfiguration(Target *t, BuildConfiguration *bc, SetActive cascade); - static void setActiveDeployConfiguration(Target *t, DeployConfiguration *dc, SetActive cascade); - - static Utils::FilePath sessionNameToFileName(const QString &session); - static Project *startupProject(); - static Target *startupTarget(); - static BuildSystem *startupBuildSystem(); - static RunConfiguration *startupRunConfiguration(); - - static const QList projects(); - static bool hasProjects(); - static bool hasProject(Project *p); - - static bool isDefaultVirgin(); - static bool isDefaultSession(const QString &session); - - // Let other plugins store persistent values within the session file - static void setValue(const QString &name, const QVariant &value); - static QVariant value(const QString &name); - - // NBS rewrite projectOrder (dependency management) - static QList projectOrder(const Project *project = nullptr); - - static Project *projectForFile(const Utils::FilePath &fileName); - static Project *projectWithProjectFilePath(const Utils::FilePath &filePath); - - static Utils::FilePaths projectsForSessionName(const QString &session); - - static void reportProjectLoadingProgress(); - static bool loadingSession(); - -signals: - void targetAdded(ProjectExplorer::Target *target); - void targetRemoved(ProjectExplorer::Target *target); - void projectAdded(ProjectExplorer::Project *project); - void aboutToRemoveProject(ProjectExplorer::Project *project); - void projectDisplayNameChanged(ProjectExplorer::Project *project); - void projectRemoved(ProjectExplorer::Project *project); - - void startupProjectChanged(ProjectExplorer::Project *project); - - void aboutToUnloadSession(QString sessionName); - void aboutToLoadSession(QString sessionName); - void sessionLoaded(QString sessionName); - void aboutToSaveSession(); - void dependencyChanged(ProjectExplorer::Project *a, ProjectExplorer::Project *b); - - void sessionRenamed(const QString &oldName, const QString &newName); - void sessionRemoved(const QString &name); - - // for tests only - void projectFinishedParsing(ProjectExplorer::Project *project); - -private: - static void saveActiveMode(Utils::Id mode); - static void configureEditor(Core::IEditor *editor, const QString &fileName); - static void markSessionFileDirty(); - static void configureEditors(Project *project); -}; - -} // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/target.cpp b/src/plugins/projectexplorer/target.cpp index 8f227512eec..d2e59aab063 100644 --- a/src/plugins/projectexplorer/target.cpp +++ b/src/plugins/projectexplorer/target.cpp @@ -21,8 +21,8 @@ #include "projectexplorericons.h" #include "projectexplorersettings.h" #include "projectexplorertr.h" +#include "projectmanager.h" #include "runconfiguration.h" -#include "session.h" #include @@ -120,10 +120,10 @@ Target::Target(Project *project, Kit *k, _constructor_tag) : }); connect(this, &Target::parsingFinished, this, [this, project](bool success) { - if (success && this == SessionManager::startupTarget()) + if (success && this == ProjectManager::startupTarget()) updateDefaultRunConfigurations(); // For testing. - emit SessionManager::instance()->projectFinishedParsing(project); + emit ProjectManager::instance()->projectFinishedParsing(project); emit project->anyParsingFinished(this, success); }, Qt::QueuedConnection); // Must wait for run configs to change their enabled state. @@ -242,6 +242,70 @@ QString Target::activeBuildKey() const return d->m_activeRunConfiguration->buildKey(); } +void Target::setActiveBuildConfiguration(BuildConfiguration *bc, SetActive cascade) +{ + QTC_ASSERT(project(), return); + + if (project()->isShuttingDown() || isShuttingDown()) + return; + + setActiveBuildConfiguration(bc); + + if (!bc) + return; + if (cascade != SetActive::Cascade || !ProjectManager::isProjectConfigurationCascading()) + return; + + Id kitId = kit()->id(); + QString name = bc->displayName(); // We match on displayname + for (Project *otherProject : ProjectManager::projects()) { + if (otherProject == project()) + continue; + Target *otherTarget = otherProject->activeTarget(); + if (!otherTarget || otherTarget->kit()->id() != kitId) + continue; + + for (BuildConfiguration *otherBc : otherTarget->buildConfigurations()) { + if (otherBc->displayName() == name) { + otherTarget->setActiveBuildConfiguration(otherBc); + break; + } + } + } +} + +void Target::setActiveDeployConfiguration(DeployConfiguration *dc, SetActive cascade) +{ + QTC_ASSERT(project(), return); + + if (project()->isShuttingDown() || isShuttingDown()) + return; + + setActiveDeployConfiguration(dc); + + if (!dc) + return; + if (cascade != SetActive::Cascade || !ProjectManager::isProjectConfigurationCascading()) + return; + + Id kitId = kit()->id(); + QString name = dc->displayName(); // We match on displayname + for (Project *otherProject : ProjectManager::projects()) { + if (otherProject == project()) + continue; + Target *otherTarget = otherProject->activeTarget(); + if (!otherTarget || otherTarget->kit()->id() != kitId) + continue; + + for (DeployConfiguration *otherDc : otherTarget->deployConfigurations()) { + if (otherDc->displayName() == name) { + otherTarget->setActiveDeployConfiguration(otherDc); + break; + } + } + } +} + Utils::Id Target::id() const { return d->m_kit->id(); @@ -307,9 +371,9 @@ bool Target::removeBuildConfiguration(BuildConfiguration *bc) if (activeBuildConfiguration() == bc) { if (d->m_buildConfigurations.isEmpty()) - SessionManager::setActiveBuildConfiguration(this, nullptr, SetActive::Cascade); + setActiveBuildConfiguration(nullptr, SetActive::Cascade); else - SessionManager::setActiveBuildConfiguration(this, d->m_buildConfigurations.at(0), SetActive::Cascade); + setActiveBuildConfiguration(d->m_buildConfigurations.at(0), SetActive::Cascade); } emit removedBuildConfiguration(bc); @@ -377,10 +441,9 @@ bool Target::removeDeployConfiguration(DeployConfiguration *dc) if (activeDeployConfiguration() == dc) { if (d->m_deployConfigurations.isEmpty()) - SessionManager::setActiveDeployConfiguration(this, nullptr, SetActive::Cascade); + setActiveDeployConfiguration(nullptr, SetActive::Cascade); else - SessionManager::setActiveDeployConfiguration(this, d->m_deployConfigurations.at(0), - SetActive::Cascade); + setActiveDeployConfiguration(d->m_deployConfigurations.at(0), SetActive::Cascade); } ProjectExplorerPlugin::targetSelector()->removedDeployConfiguration(dc); diff --git a/src/plugins/projectexplorer/target.h b/src/plugins/projectexplorer/target.h index 78f0b5f3b5b..aeca2fd9e0b 100644 --- a/src/plugins/projectexplorer/target.h +++ b/src/plugins/projectexplorer/target.h @@ -26,11 +26,13 @@ class Project; class ProjectConfigurationModel; class RunConfiguration; +enum class SetActive : int { Cascade, NoCascade }; + class TargetPrivate; class PROJECTEXPLORER_EXPORT Target : public QObject { - friend class SessionManager; // for setActiveBuild and setActiveDeployConfiguration + friend class ProjectManager; // for setActiveBuild and setActiveDeployConfiguration Q_OBJECT public: @@ -109,6 +111,9 @@ public: QString activeBuildKey() const; // Build key of active run configuaration + void setActiveBuildConfiguration(BuildConfiguration *bc, SetActive cascade); + void setActiveDeployConfiguration(DeployConfiguration *dc, SetActive cascade); + signals: void targetEnabled(bool); void iconChanged(); diff --git a/src/plugins/projectexplorer/targetsettingspanel.cpp b/src/plugins/projectexplorer/targetsettingspanel.cpp index 6d8c81e634c..d2fdc3b43a8 100644 --- a/src/plugins/projectexplorer/targetsettingspanel.cpp +++ b/src/plugins/projectexplorer/targetsettingspanel.cpp @@ -12,9 +12,9 @@ #include "project.h" #include "projectexplorericons.h" #include "projectexplorertr.h" +#include "projectmanager.h" #include "projectwindow.h" #include "runsettingspropertiespage.h" -#include "session.h" #include "target.h" #include "targetsetuppage.h" #include "task.h" @@ -281,7 +281,7 @@ public: QFont font = parent()->data(column, role).value(); if (TargetItem *targetItem = parent()->currentTargetItem()) { Target *t = targetItem->target(); - if (t && t->id() == m_kitId && m_project == SessionManager::startupProject()) + if (t && t->id() == m_kitId && m_project == ProjectManager::startupProject()) font.setBold(true); } return font; @@ -334,7 +334,7 @@ public: // Go to Run page, when on Run previously etc. TargetItem *previousItem = parent()->currentTargetItem(); m_currentChild = previousItem ? previousItem->m_currentChild : DefaultPage; - SessionManager::setActiveTarget(m_project, target(), SetActive::Cascade); + m_project->setActiveTarget(target(), SetActive::Cascade); parent()->setData(column, QVariant::fromValue(static_cast(this)), ItemActivatedFromBelowRole); } @@ -346,7 +346,7 @@ public: int child = indexOf(data.value()); QTC_ASSERT(child != -1, return false); m_currentChild = child; // Triggered from sub-item. - SessionManager::setActiveTarget(m_project, target(), SetActive::Cascade); + m_project->setActiveTarget(target(), SetActive::Cascade); // Propagate Build/Run selection up. parent()->setData(column, QVariant::fromValue(static_cast(this)), ItemActivatedFromBelowRole); @@ -355,7 +355,7 @@ public: if (role == ItemActivatedFromAboveRole) { // Usually programmatic activation, e.g. after opening the Project mode. - SessionManager::setActiveTarget(m_project, target(), SetActive::Cascade); + m_project->setActiveTarget(target(), SetActive::Cascade); return true; } return false; @@ -377,7 +377,7 @@ public: = menu->addAction(Tr::tr("Enable Kit for All Projects")); enableForAllAction->setEnabled(isSelectable); QObject::connect(enableForAllAction, &QAction::triggered, [kit] { - for (Project * const p : SessionManager::projects()) { + for (Project * const p : ProjectManager::projects()) { if (!p->target(kit)) p->addTargetForKit(kit); } @@ -411,7 +411,7 @@ public: QAction *disableForAllAction = menu->addAction(Tr::tr("Disable Kit for All Projects")); disableForAllAction->setEnabled(isSelectable); QObject::connect(disableForAllAction, &QAction::triggered, [kit] { - for (Project * const p : SessionManager::projects()) { + for (Project * const p : ProjectManager::projects()) { Target * const t = p->target(kit); if (!t) continue; diff --git a/src/plugins/projectexplorer/targetsetuppage.cpp b/src/plugins/projectexplorer/targetsetuppage.cpp index a17aa58c946..fd1e8a78c64 100644 --- a/src/plugins/projectexplorer/targetsetuppage.cpp +++ b/src/plugins/projectexplorer/targetsetuppage.cpp @@ -11,18 +11,17 @@ #include "project.h" #include "projectexplorerconstants.h" #include "projectexplorertr.h" -#include "session.h" #include "target.h" #include "targetsetupwidget.h" #include "task.h" #include -#include -#include -#include #include #include +#include +#include +#include #include #include @@ -658,7 +657,7 @@ bool TargetSetupPage::setupProject(Project *project) if (m_importer) activeTarget = m_importer->preferredTarget(project->targets()); if (activeTarget) - SessionManager::setActiveTarget(project, activeTarget, SetActive::NoCascade); + project->setActiveTarget(activeTarget, SetActive::NoCascade); return true; } diff --git a/src/plugins/projectexplorer/targetsetupwidget.cpp b/src/plugins/projectexplorer/targetsetupwidget.cpp index cae28fc3e1f..63b61401d5f 100644 --- a/src/plugins/projectexplorer/targetsetupwidget.cpp +++ b/src/plugins/projectexplorer/targetsetupwidget.cpp @@ -180,10 +180,8 @@ void TargetSetupWidget::manageKit() if (!m_kit) return; - if (auto kitPage = KitOptionsPage::instance()) { - kitPage->showKit(m_kit); - Core::ICore::showOptionsDialog(Constants::KITS_SETTINGS_PAGE_ID, parentWidget()); - } + KitOptionsPage::showKit(m_kit); + Core::ICore::showOptionsDialog(Constants::KITS_SETTINGS_PAGE_ID, parentWidget()); } void TargetSetupWidget::setProjectPath(const FilePath &projectPath) diff --git a/src/plugins/projectexplorer/task.cpp b/src/plugins/projectexplorer/task.cpp index dab4c7ed17d..03c2a282be0 100644 --- a/src/plugins/projectexplorer/task.cpp +++ b/src/plugins/projectexplorer/task.cpp @@ -8,8 +8,8 @@ #include "projectexplorertr.h" #include +#include #include - #include #include #include @@ -105,11 +105,16 @@ void Task::setFile(const Utils::FilePath &file_) } } -QString Task::description() const +QString Task::description(DescriptionTags tags) const { - QString desc = summary; - if (!details.isEmpty()) - desc.append('\n').append(details.join('\n')); + QString desc; + if (tags & WithSummary) + desc = summary; + if (!details.isEmpty()) { + if (!desc.isEmpty()) + desc.append('\n'); + desc.append(details.join('\n')); + } return desc; } @@ -120,6 +125,39 @@ QIcon Task::icon() const return m_icon; } +QString Task::formattedDescription(DescriptionTags tags, const QString &extraHeading) const +{ + if (isNull()) + return {}; + + QString text = description(tags); + const int offset = (tags & WithSummary) ? 0 : summary.size() + 1; + static const QString linkTagStartPlaceholder("__QTC_LINK_TAG_START__"); + static const QString linkTagEndPlaceholder("__QTC_LINK_TAG_END__"); + static const QString linkEndPlaceholder("__QTC_LINK_END__"); + if (tags & WithLinks) { + for (auto formatRange = formats.crbegin(); formatRange != formats.crend(); ++formatRange) { + if (!formatRange->format.isAnchor()) + continue; + text.insert(formatRange->start - offset + formatRange->length, linkEndPlaceholder); + text.insert(formatRange->start - offset, QString::fromLatin1("%1%2%3").arg( + linkTagStartPlaceholder, formatRange->format.anchorHref(), linkTagEndPlaceholder)); + } + } + text = text.toHtmlEscaped(); + if (tags & WithLinks) { + text.replace(linkEndPlaceholder, ""); + text.replace(linkTagStartPlaceholder, ""); + } + const QString htmlExtraHeading = extraHeading.isEmpty() + ? QString() + : QString::fromUtf8("%1
").arg(extraHeading); + return QString::fromUtf8("%1" + "%3") + .arg(htmlExtraHeading, TextEditor::FontSettings::defaultFixedFontFamily(), text); +} + // // functions // diff --git a/src/plugins/projectexplorer/task.h b/src/plugins/projectexplorer/task.h index f38302f90f9..830cea79397 100644 --- a/src/plugins/projectexplorer/task.h +++ b/src/plugins/projectexplorer/task.h @@ -38,6 +38,9 @@ public: }; using Options = char; + enum DescriptionTag { WithSummary = 1, WithLinks = 2 }; + using DescriptionTags = QFlags; + Task() = default; Task(TaskType type, const QString &description, const Utils::FilePath &file, int line, Utils::Id category, @@ -49,8 +52,9 @@ public: bool isNull() const; void clear(); void setFile(const Utils::FilePath &file); - QString description() const; + QString description(DescriptionTags tags = WithSummary) const; QIcon icon() const; + QString formattedDescription(DescriptionTags tags, const QString &extraHeading = {}) const; friend PROJECTEXPLORER_EXPORT bool operator==(const Task &t1, const Task &t2); friend PROJECTEXPLORER_EXPORT bool operator<(const Task &a, const Task &b); diff --git a/src/plugins/projectexplorer/taskfile.cpp b/src/plugins/projectexplorer/taskfile.cpp index 6375d12ee7e..c967521d894 100644 --- a/src/plugins/projectexplorer/taskfile.cpp +++ b/src/plugins/projectexplorer/taskfile.cpp @@ -6,20 +6,22 @@ #include "projectexplorer.h" #include "projectexplorerconstants.h" #include "projectexplorertr.h" -#include "session.h" #include "taskhub.h" #include #include +#include +#include #include #include +#include #include -#include #include #include +using namespace Core; using namespace Utils; namespace ProjectExplorer { @@ -147,6 +149,10 @@ static bool parseTaskFile(QString *errorString, const FilePath &name) } description = unescape(description); + if (description.trimmed().isEmpty()) { + MessageManager::writeFlashing(Tr::tr("Ignoring invalid task (no text).")); + continue; + } TaskHub::addTask(Task(type, description, FilePath::fromUserInput(file), line, Constants::TASK_CATEGORY_TASKLIST_ID)); } diff --git a/src/plugins/projectexplorer/taskhub.cpp b/src/plugins/projectexplorer/taskhub.cpp index da839e89f19..58e797de803 100644 --- a/src/plugins/projectexplorer/taskhub.cpp +++ b/src/plugins/projectexplorer/taskhub.cpp @@ -51,13 +51,9 @@ public: : Tr::tr("Warning")); setPriority(task.type == Task::Error ? TextEditor::TextMark::NormalPriority : TextEditor::TextMark::LowPriority); - if (task.category == Constants::TASK_CATEGORY_COMPILE) { - setToolTip("" + Tr::tr("Build Issue") - + "
" - + task.description().toHtmlEscaped() + ""); - } else { - setToolTip(task.description()); - } + setToolTip(task.formattedDescription({Task::WithSummary | Task::WithLinks}, + task.category == Constants::TASK_CATEGORY_COMPILE + ? Tr::tr("Build Issue") : QString())); setIcon(task.icon()); setVisible(!task.icon().isNull()); } diff --git a/src/plugins/projectexplorer/taskmodel.cpp b/src/plugins/projectexplorer/taskmodel.cpp index b8f7d723063..11591e707d7 100644 --- a/src/plugins/projectexplorer/taskmodel.cpp +++ b/src/plugins/projectexplorer/taskmodel.cpp @@ -210,58 +210,82 @@ void TaskModel::clearTasks(Utils::Id categoryId) QModelIndex TaskModel::index(int row, int column, const QModelIndex &parent) const { if (parent.isValid()) - return QModelIndex(); + return createIndex(row, column, quintptr(parent.row() + 1)); return createIndex(row, column); } QModelIndex TaskModel::parent(const QModelIndex &child) const { - Q_UNUSED(child) - return QModelIndex(); + if (child.internalId()) + return index(child.internalId() - 1, 0); + return {}; } int TaskModel::rowCount(const QModelIndex &parent) const { - return parent.isValid() ? 0 : m_tasks.count(); + if (!parent.isValid()) + return m_tasks.count(); + if (parent.column() != 0) + return 0; + return task(parent).details.isEmpty() ? 0 : 1; } int TaskModel::columnCount(const QModelIndex &parent) const { - return parent.isValid() ? 0 : 1; + return parent.isValid() ? 1 : 2; } QVariant TaskModel::data(const QModelIndex &index, int role) const { - int row = index.row(); - if (!index.isValid() || row < 0 || row >= m_tasks.count() || index.column() != 0) - return QVariant(); + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index.parent()) + || index.column() >= columnCount(index.parent())) { + return {}; + } - if (role == TaskModel::File) - return m_tasks.at(index.row()).file.toString(); - else if (role == TaskModel::Line) - return m_tasks.at(index.row()).line; - else if (role == TaskModel::MovedLine) - return m_tasks.at(index.row()).movedLine; - else if (role == TaskModel::Description) - return m_tasks.at(index.row()).description(); - else if (role == TaskModel::FileNotFound) - return m_fileNotFound.value(m_tasks.at(index.row()).file.toString()); - else if (role == TaskModel::Type) - return (int)m_tasks.at(index.row()).type; - else if (role == TaskModel::Category) - return m_tasks.at(index.row()).category.uniqueIdentifier(); - else if (role == TaskModel::Icon) - return m_tasks.at(index.row()).icon(); - else if (role == TaskModel::Task_t) - return QVariant::fromValue(task(index)); - return QVariant(); + if (index.internalId()) { + const Task &task = m_tasks.at(index.internalId() - 1); + if (role != Qt::DisplayRole) + return {}; + return task.formattedDescription(Task::WithLinks); + } + + static const auto lineString = [](const Task &task) { + QString file = task.file.fileName(); + const int line = task.movedLine > 0 ? task.movedLine : task.line; + if (line > 0) + file.append(':').append(QString::number(line)); + return file; + }; + + const Task &task = m_tasks.at(index.row()); + if (index.column() == 1) { + if (role == Qt::DisplayRole) + return lineString(task); + if (role == Qt::ToolTipRole) + return task.file.toUserOutput(); + return {}; + } + + switch (role) { + case Qt::DecorationRole: + return task.icon(); + case Qt::DisplayRole: + return task.summary; + case TaskModel::Description: + return task.description(); + case TaskModel::Type: + return int(task.type); + } + return {}; } Task TaskModel::task(const QModelIndex &index) const { int row = index.row(); - if (!index.isValid() || row < 0 || row >= m_tasks.count()) + if (!index.isValid() || row < 0 || row >= m_tasks.count() || index.internalId() + || index.column() > 0) { return Task(); + } return m_tasks.at(row); } @@ -387,7 +411,8 @@ void TaskFilterModel::updateFilterProperties( bool TaskFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { - Q_UNUSED(source_parent) + if (source_parent.isValid()) + return true; return filterAcceptsTask(taskModel()->tasks().at(source_row)); } diff --git a/src/plugins/projectexplorer/taskmodel.h b/src/plugins/projectexplorer/taskmodel.h index e10833f6131..5fa37c02d1a 100644 --- a/src/plugins/projectexplorer/taskmodel.h +++ b/src/plugins/projectexplorer/taskmodel.h @@ -44,7 +44,7 @@ public: int sizeOfLineNumber(const QFont &font); void setFileNotFound(const QModelIndex &index, bool b); - enum Roles { File = Qt::UserRole, Line, MovedLine, Description, FileNotFound, Type, Category, Icon, Task_t }; + enum Roles { Description = Qt::UserRole, Type}; int taskCount(Utils::Id categoryId); int errorTaskCount(Utils::Id categoryId); diff --git a/src/plugins/projectexplorer/taskwindow.cpp b/src/plugins/projectexplorer/taskwindow.cpp index 52e3899d654..9ae93c0bbf0 100644 --- a/src/plugins/projectexplorer/taskwindow.cpp +++ b/src/plugins/projectexplorer/taskwindow.cpp @@ -6,7 +6,6 @@ #include "itaskhandler.h" #include "projectexplorericons.h" #include "projectexplorertr.h" -#include "session.h" #include "task.h" #include "taskhub.h" #include "taskmodel.h" @@ -17,32 +16,38 @@ #include #include #include -#include #include +#include +#include #include #include +#include #include #include #include #include #include +#include #include +#include +#include #include -#include -#include +#include #include -#include +#include #include +#include +#include +#include +#include +using namespace Core; using namespace Utils; -namespace { -const int ELLIPSIS_GRADIENT_WIDTH = 16; const char SESSION_FILTER_CATEGORIES[] = "TaskWindow.Categories"; const char SESSION_FILTER_WARNINGS[] = "TaskWindow.IncludeWarnings"; -} namespace ProjectExplorer { @@ -84,196 +89,43 @@ bool ITaskHandler::canHandle(const Tasks &tasks) const namespace Internal { -class TaskView : public ListView +class TaskView : public TreeView { public: - TaskView(QWidget *parent = nullptr); - ~TaskView() override; + TaskView(); + void resizeColumns(); private: void resizeEvent(QResizeEvent *e) override; + void keyReleaseEvent(QKeyEvent *e) override; + bool event(QEvent *e) override; void mousePressEvent(QMouseEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; - Link locationForPos(const QPoint &pos); + QString anchorAt(const QPoint &pos); + void showToolTip(const Task &task, const QPoint &pos); - bool m_linksActive = true; - Qt::MouseButton m_mouseButtonPressed = Qt::NoButton; + QString m_hoverAnchor; + QString m_clickAnchor; }; class TaskDelegate : public QStyledItemDelegate { - Q_OBJECT - - friend class TaskView; // for using Positions::minimumSize() - public: - TaskDelegate(QObject * parent = nullptr); - ~TaskDelegate() override; - void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + using QStyledItemDelegate::QStyledItemDelegate; + + QTextDocument &doc() { return m_doc; } +private: + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; - // TaskView uses this method if the size of the taskview changes - void emitSizeHintChanged(const QModelIndex &index); + bool needsSpecialHandling(const QModelIndex &index) const; - void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); - - QString hrefForPos(const QPointF &pos); - -private: - void generateGradientPixmap(int width, int height, QColor color, bool selected) const; - - mutable int m_cachedHeight = 0; - mutable QFont m_cachedFont; - mutable QList> m_hrefs; - - /* - Collapsed: - +----------------------------------------------------------------------------------------------------+ - | TASKICONAREA TEXTAREA FILEAREA LINEAREA | - +----------------------------------------------------------------------------------------------------+ - - Expanded: - +----------------------------------------------------------------------------------------------------+ - | TASKICONICON TEXTAREA FILEAREA LINEAREA | - | more text -------------------------------------------------------------------------> | - +----------------------------------------------------------------------------------------------------+ - */ - class Positions - { - public: - Positions(const QStyleOptionViewItem &options, TaskModel *model) : - m_totalWidth(options.rect.width()), - m_maxFileLength(model->sizeOfFile(options.font)), - m_maxLineLength(model->sizeOfLineNumber(options.font)), - m_realFileLength(m_maxFileLength), - m_top(options.rect.top()), - m_bottom(options.rect.bottom()) - { - int flexibleArea = lineAreaLeft() - textAreaLeft() - ITEM_SPACING; - if (m_maxFileLength > flexibleArea / 2) - m_realFileLength = flexibleArea / 2; - m_fontHeight = QFontMetrics(options.font).height(); - } - - int top() const { return m_top + ITEM_MARGIN; } - int left() const { return ITEM_MARGIN; } - int right() const { return m_totalWidth - ITEM_MARGIN; } - int bottom() const { return m_bottom; } - int firstLineHeight() const { return m_fontHeight + 1; } - static int minimumHeight() { return taskIconHeight() + 2 * ITEM_MARGIN; } - - int taskIconLeft() const { return left(); } - static int taskIconWidth() { return TASK_ICON_SIZE; } - static int taskIconHeight() { return TASK_ICON_SIZE; } - int taskIconRight() const { return taskIconLeft() + taskIconWidth(); } - QRect taskIcon() const { return QRect(taskIconLeft(), top(), taskIconWidth(), taskIconHeight()); } - - int textAreaLeft() const { return taskIconRight() + ITEM_SPACING; } - int textAreaWidth() const { return textAreaRight() - textAreaLeft(); } - int textAreaRight() const { return fileAreaLeft() - ITEM_SPACING; } - QRect textArea() const { return QRect(textAreaLeft(), top(), textAreaWidth(), firstLineHeight()); } - - int fileAreaLeft() const { return fileAreaRight() - fileAreaWidth(); } - int fileAreaWidth() const { return m_realFileLength; } - int fileAreaRight() const { return lineAreaLeft() - ITEM_SPACING; } - QRect fileArea() const { return QRect(fileAreaLeft(), top(), fileAreaWidth(), firstLineHeight()); } - - int lineAreaLeft() const { return lineAreaRight() - lineAreaWidth(); } - int lineAreaWidth() const { return m_maxLineLength; } - int lineAreaRight() const { return right(); } - QRect lineArea() const { return QRect(lineAreaLeft(), top(), lineAreaWidth(), firstLineHeight()); } - - private: - int m_totalWidth; - int m_maxFileLength; - int m_maxLineLength; - int m_realFileLength; - int m_top; - int m_bottom; - int m_fontHeight; - - static const int TASK_ICON_SIZE = 16; - static const int ITEM_MARGIN = 2; - static const int ITEM_SPACING = 2 * ITEM_MARGIN; - }; + mutable QTextDocument m_doc; }; -TaskView::TaskView(QWidget *parent) - : ListView(parent) -{ - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); - setMouseTracking(true); - setAutoScroll(false); // QTCREATORBUG-25101 - - QFontMetrics fm(font()); - int vStepSize = fm.height() + 3; - if (vStepSize < TaskDelegate::Positions::minimumHeight()) - vStepSize = TaskDelegate::Positions::minimumHeight(); - - verticalScrollBar()->setSingleStep(vStepSize); -} - -TaskView::~TaskView() = default; - -void TaskView::resizeEvent(QResizeEvent *e) -{ - Q_UNUSED(e) - static_cast(itemDelegate())->emitSizeHintChanged(selectionModel()->currentIndex()); -} - -void TaskView::mousePressEvent(QMouseEvent *e) -{ - m_mouseButtonPressed = e->button(); - ListView::mousePressEvent(e); -} - -void TaskView::mouseReleaseEvent(QMouseEvent *e) -{ - if (m_linksActive && m_mouseButtonPressed == Qt::LeftButton) { - const Link loc = locationForPos(e->pos()); - if (!loc.targetFilePath.isEmpty()) { - Core::EditorManager::openEditorAt(loc, {}, - Core::EditorManager::SwitchSplitIfAlreadyVisible); - } - } - - // Mouse was released, activate links again - m_linksActive = true; - m_mouseButtonPressed = Qt::NoButton; - ListView::mouseReleaseEvent(e); -} - -void TaskView::mouseMoveEvent(QMouseEvent *e) -{ - // Cursor was dragged, deactivate links - if (m_mouseButtonPressed != Qt::NoButton) - m_linksActive = false; - - viewport()->setCursor(m_linksActive && !locationForPos(e->pos()).targetFilePath.isEmpty() - ? Qt::PointingHandCursor : Qt::ArrowCursor); - ListView::mouseMoveEvent(e); -} - -Link TaskView::locationForPos(const QPoint &pos) -{ - const auto delegate = qobject_cast(itemDelegate(indexAt(pos))); - if (!delegate) - return {}; - OutputFormatter formatter; - Link loc; - connect(&formatter, &OutputFormatter::openInEditorRequested, this, [&loc](const Link &link) { - loc = link; - }); - - const QString href = delegate->hrefForPos(pos); - if (!href.isEmpty()) - formatter.handleLink(href); - return loc; -} - ///// // TaskWindow ///// @@ -289,9 +141,8 @@ public: Internal::TaskModel *m_model; Internal::TaskFilterModel *m_filter; - Internal::TaskView *m_listview; + TaskView m_treeView; Core::IContext *m_taskWindowContext; - QMenu *m_contextMenu; QMap m_actionToHandlerMap; ITaskHandler *m_defaultHandler = nullptr; QToolButton *m_filterWarningsButton; @@ -318,45 +169,45 @@ TaskWindow::TaskWindow() : d(std::make_unique()) { d->m_model = new Internal::TaskModel(this); d->m_filter = new Internal::TaskFilterModel(d->m_model); - d->m_listview = new Internal::TaskView; + d->m_filter->setAutoAcceptChildRows(true); auto agg = new Aggregation::Aggregate; - agg->add(d->m_listview); - agg->add(new Core::ItemViewFind(d->m_listview, TaskModel::Description)); + agg->add(&d->m_treeView); + agg->add(new Core::ItemViewFind(&d->m_treeView, TaskModel::Description)); - d->m_listview->setModel(d->m_filter); - d->m_listview->setFrameStyle(QFrame::NoFrame); - d->m_listview->setWindowTitle(displayName()); - d->m_listview->setSelectionMode(QAbstractItemView::ExtendedSelection); - auto *tld = new Internal::TaskDelegate(this); - d->m_listview->setItemDelegate(tld); - d->m_listview->setWindowIcon(Icons::WINDOW.icon()); - d->m_listview->setContextMenuPolicy(Qt::ActionsContextMenu); - d->m_listview->setAttribute(Qt::WA_MacShowFocusRect, false); + d->m_treeView.setHeaderHidden(true); + d->m_treeView.setExpandsOnDoubleClick(false); + d->m_treeView.setAlternatingRowColors(true); + d->m_treeView.setTextElideMode(Qt::ElideMiddle); + d->m_treeView.setItemDelegate(new TaskDelegate(this)); + d->m_treeView.setModel(d->m_filter); + d->m_treeView.setFrameStyle(QFrame::NoFrame); + d->m_treeView.setWindowTitle(displayName()); + d->m_treeView.setSelectionMode(QAbstractItemView::ExtendedSelection); + d->m_treeView.setWindowIcon(Icons::WINDOW.icon()); + d->m_treeView.setContextMenuPolicy(Qt::ActionsContextMenu); + d->m_treeView.setAttribute(Qt::WA_MacShowFocusRect, false); + d->m_treeView.resizeColumns(); - d->m_taskWindowContext = new Core::IContext(d->m_listview); - d->m_taskWindowContext->setWidget(d->m_listview); + d->m_taskWindowContext = new Core::IContext(&d->m_treeView); + d->m_taskWindowContext->setWidget(&d->m_treeView); d->m_taskWindowContext->setContext(Core::Context(Core::Constants::C_PROBLEM_PANE)); Core::ICore::addContextObject(d->m_taskWindowContext); - connect(d->m_listview->selectionModel(), &QItemSelectionModel::currentChanged, - tld, &TaskDelegate::currentChanged); - connect(d->m_listview->selectionModel(), &QItemSelectionModel::currentChanged, - this, [this](const QModelIndex &index) { d->m_listview->scrollTo(index); }); - connect(d->m_listview, &QAbstractItemView::activated, + connect(d->m_treeView.selectionModel(), &QItemSelectionModel::currentChanged, + this, [this](const QModelIndex &index) { d->m_treeView.scrollTo(index); }); + connect(&d->m_treeView, &QAbstractItemView::activated, this, &TaskWindow::triggerDefaultHandler); - connect(d->m_listview->selectionModel(), &QItemSelectionModel::selectionChanged, + connect(d->m_treeView.selectionModel(), &QItemSelectionModel::selectionChanged, this, [this] { - const Tasks tasks = d->m_filter->tasks(d->m_listview->selectionModel()->selectedIndexes()); + const Tasks tasks = d->m_filter->tasks(d->m_treeView.selectionModel()->selectedIndexes()); for (QAction * const action : std::as_const(d->m_actions)) { ITaskHandler * const h = d->handler(action); action->setEnabled(h && h->canHandle(tasks)); } }); - d->m_contextMenu = new QMenu(d->m_listview); - - d->m_listview->setContextMenuPolicy(Qt::ActionsContextMenu); + d->m_treeView.setContextMenuPolicy(Qt::ActionsContextMenu); d->m_filterWarningsButton = createFilterButton( Utils::Icons::WARNING_TOOLBAR.icon(), @@ -365,7 +216,7 @@ TaskWindow::TaskWindow() : d(std::make_unique()) d->m_categoriesButton = new QToolButton; d->m_categoriesButton->setIcon(Utils::Icons::FILTER.icon()); d->m_categoriesButton->setToolTip(Tr::tr("Filter by categories")); - d->m_categoriesButton->setProperty("noArrow", true); + d->m_categoriesButton->setProperty(StyleHelper::C_NO_ARROW, true); d->m_categoriesButton->setPopupMode(QToolButton::InstantPopup); d->m_categoriesMenu = new QMenu(d->m_categoriesButton); @@ -411,7 +262,6 @@ TaskWindow::TaskWindow() : d(std::make_unique()) TaskWindow::~TaskWindow() { delete d->m_filterWarningsButton; - delete d->m_listview; delete d->m_filter; delete d->m_model; } @@ -435,7 +285,7 @@ void TaskWindow::delayedInitialization() connect(action, &QAction::triggered, this, [this, action] { ITaskHandler *h = d->handler(action); if (h) - h->handle(d->m_filter->tasks(d->m_listview->selectionModel()->selectedIndexes())); + h->handle(d->m_filter->tasks(d->m_treeView.selectionModel()->selectedIndexes())); }); d->m_actions << action; @@ -445,7 +295,7 @@ void TaskWindow::delayedInitialization() Core::ActionManager::registerAction(action, id, d->m_taskWindowContext->context(), true); action = cmd->action(); } - d->m_listview->addAction(action); + d->m_treeView.addAction(action); } } @@ -461,7 +311,7 @@ QString TaskWindow::displayName() const QWidget *TaskWindow::outputWidget(QWidget *) { - return d->m_listview; + return &d->m_treeView; } void TaskWindow::clearTasks(Id categoryId) @@ -565,7 +415,7 @@ void TaskWindow::showTask(const Task &task) int sourceRow = d->m_model->rowForTask(task); QModelIndex sourceIdx = d->m_model->index(sourceRow, 0); QModelIndex filterIdx = d->m_filter->mapFromSource(sourceIdx); - d->m_listview->setCurrentIndex(filterIdx); + d->m_treeView.setCurrentIndex(filterIdx); popup(Core::IOutputPane::ModeSwitch); } @@ -582,7 +432,10 @@ void TaskWindow::triggerDefaultHandler(const QModelIndex &index) if (!index.isValid() || !d->m_defaultHandler) return; - Task task(d->m_filter->task(index)); + QModelIndex taskIndex = index; + if (index.parent().isValid()) + taskIndex = index.parent(); + Task task(d->m_filter->task(taskIndex)); if (task.isNull()) return; @@ -599,7 +452,7 @@ void TaskWindow::triggerDefaultHandler(const QModelIndex &index) d->m_defaultHandler->handle(task); } else { if (!task.file.exists()) - d->m_model->setFileNotFound(index, true); + d->m_model->setFileNotFound(taskIndex, true); } } @@ -665,7 +518,7 @@ void TaskWindow::clearContents() bool TaskWindow::hasFocus() const { - return d->m_listview->window()->focusWidget() == d->m_listview; + return d->m_treeView.window()->focusWidget() == &d->m_treeView; } bool TaskWindow::canFocus() const @@ -676,9 +529,13 @@ bool TaskWindow::canFocus() const void TaskWindow::setFocus() { if (d->m_filter->rowCount()) { - d->m_listview->setFocus(); - if (d->m_listview->currentIndex() == QModelIndex()) - d->m_listview->setCurrentIndex(d->m_filter->index(0,0, QModelIndex())); + d->m_treeView.setFocus(); + if (!d->m_treeView.currentIndex().isValid()) + d->m_treeView.setCurrentIndex(d->m_filter->index(0,0, QModelIndex())); + if (d->m_treeView.selectionModel()->selection().isEmpty()) { + d->m_treeView.selectionModel()->setCurrentIndex(d->m_treeView.currentIndex(), + QItemSelectionModel::Select); + } } } @@ -696,7 +553,7 @@ void TaskWindow::goToNext() { if (!canNext()) return; - QModelIndex startIndex = d->m_listview->currentIndex(); + QModelIndex startIndex = d->m_treeView.currentIndex(); QModelIndex currentIndex = startIndex; if (startIndex.isValid()) { @@ -711,7 +568,7 @@ void TaskWindow::goToNext() } else { currentIndex = d->m_filter->index(0, 0); } - d->m_listview->setCurrentIndex(currentIndex); + d->m_treeView.setCurrentIndex(currentIndex); triggerDefaultHandler(currentIndex); } @@ -719,7 +576,7 @@ void TaskWindow::goToPrev() { if (!canPrevious()) return; - QModelIndex startIndex = d->m_listview->currentIndex(); + QModelIndex startIndex = d->m_treeView.currentIndex(); QModelIndex currentIndex = startIndex; if (startIndex.isValid()) { @@ -734,7 +591,7 @@ void TaskWindow::goToPrev() } else { currentIndex = d->m_filter->index(0, 0); } - d->m_listview->setCurrentIndex(currentIndex); + d->m_treeView.setCurrentIndex(currentIndex); triggerDefaultHandler(currentIndex); } @@ -749,268 +606,167 @@ bool TaskWindow::canNavigate() const return true; } -///// -// Delegate -///// +void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + if (!needsSpecialHandling(index)) { + QStyledItemDelegate::paint(painter, option, index); + return; + } -TaskDelegate::TaskDelegate(QObject *parent) : - QStyledItemDelegate(parent) -{ } + QStyleOptionViewItem options = option; + initStyleOption(&options, index); -TaskDelegate::~TaskDelegate() = default; + painter->save(); + m_doc.setHtml(options.text); + options.text = ""; + options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter); + painter->translate(options.rect.left(), options.rect.top()); + QRect clip(0, 0, options.rect.width(), options.rect.height()); + QAbstractTextDocumentLayout::PaintContext paintContext; + paintContext.palette = options.palette; + painter->setClipRect(clip); + paintContext.clip = clip; + if (qobject_cast(options.widget) + ->selectionModel()->isSelected(index)) { + QAbstractTextDocumentLayout::Selection selection; + selection.cursor = QTextCursor(&m_doc); + selection.cursor.select(QTextCursor::Document); + selection.format.setBackground(options.palette.brush(QPalette::Highlight)); + selection.format.setForeground(options.palette.brush(QPalette::HighlightedText)); + paintContext.selections << selection; + } + m_doc.documentLayout()->draw(painter, paintContext); + painter->restore(); +} QSize TaskDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { - QStyleOptionViewItem opt = option; - initStyleOption(&opt, index); + if (!needsSpecialHandling(index)) + return QStyledItemDelegate::sizeHint(option, index); - auto view = qobject_cast(opt.widget); - const bool current = view->selectionModel()->currentIndex() == index; - QSize s; - s.setWidth(option.rect.width()); - - if (!current && option.font == m_cachedFont && m_cachedHeight > 0) { - s.setHeight(m_cachedHeight); - return s; - } - - QFontMetrics fm(option.font); - int fontHeight = fm.height(); - int fontLeading = fm.leading(); - - auto model = static_cast(view->model())->taskModel(); - Positions positions(option, model); - - if (current) { - QString description = index.data(TaskModel::Description).toString(); - // Layout the description - int leading = fontLeading; - int height = 0; - description.replace(QLatin1Char('\n'), QChar::LineSeparator); - QTextLayout tl(description); - tl.setFormats(index.data(TaskModel::Task_t).value().formats); - tl.beginLayout(); - while (true) { - QTextLine line = tl.createLine(); - if (!line.isValid()) - break; - line.setLineWidth(positions.textAreaWidth()); - height += leading; - line.setPosition(QPoint(0, height)); - height += static_cast(line.height()); - } - tl.endLayout(); - - s.setHeight(height + leading + fontHeight + 3); - } else { - s.setHeight(fontHeight + 3); - } - if (s.height() < Positions::minimumHeight()) - s.setHeight(Positions::minimumHeight()); - - if (!current) { - m_cachedHeight = s.height(); - m_cachedFont = option.font; - } - - return s; + QStyleOptionViewItem options = option; + initStyleOption(&options, index); + m_doc.setHtml(options.text); + m_doc.setTextWidth(options.rect.width()); + return QSize(m_doc.idealWidth(), m_doc.size().height()); } -void TaskDelegate::emitSizeHintChanged(const QModelIndex &index) +bool TaskDelegate::needsSpecialHandling(const QModelIndex &index) const { - emit sizeHintChanged(index); + QModelIndex sourceIndex = index; + if (const auto proxyModel = qobject_cast(index.model())) + sourceIndex = proxyModel->mapToSource(index); + return sourceIndex.internalId(); } -void TaskDelegate::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +TaskView::TaskView() { - m_hrefs.clear(); - emit sizeHintChanged(current); - emit sizeHintChanged(previous); + setMouseTracking(true); + setVerticalScrollMode(ScrollPerPixel); } -QString TaskDelegate::hrefForPos(const QPointF &pos) +void TaskView::resizeColumns() { - for (const auto &link : std::as_const(m_hrefs)) { - if (link.first.contains(pos)) - return link.second; - } - return {}; + setColumnWidth(0, width() * 0.85); + setColumnWidth(1, width() * 0.15); } -void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +void TaskView::resizeEvent(QResizeEvent *e) { - QStyleOptionViewItem opt = option; - initStyleOption(&opt, index); - painter->save(); + TreeView::resizeEvent(e); + resizeColumns(); +} - QFontMetrics fm(opt.font); - QColor backgroundColor; - QColor textColor; +void TaskView::mousePressEvent(QMouseEvent *e) +{ + m_clickAnchor = anchorAt(e->pos()); + if (m_clickAnchor.isEmpty()) + TreeView::mousePressEvent(e); +} - auto view = qobject_cast(opt.widget); - const bool selected = view->selectionModel()->isSelected(index); - const bool current = view->selectionModel()->currentIndex() == index; - - if (selected) { - painter->setBrush(opt.palette.highlight().color()); - backgroundColor = opt.palette.highlight().color(); - } else { - painter->setBrush(opt.palette.window().color()); - backgroundColor = opt.palette.window().color(); +void TaskView::mouseMoveEvent(QMouseEvent *e) +{ + const QString anchor = anchorAt(e->pos()); + if (m_clickAnchor != anchor) + m_clickAnchor.clear(); + if (m_hoverAnchor != anchor) { + m_hoverAnchor = anchor; + if (!m_hoverAnchor.isEmpty()) + setCursor(Qt::PointingHandCursor); + else + unsetCursor(); } - painter->setPen(Qt::NoPen); - painter->drawRect(opt.rect); +} - // Set Text Color - if (selected) - textColor = opt.palette.highlightedText().color(); - else - textColor = opt.palette.text().color(); - - painter->setPen(textColor); - - auto model = static_cast(view->model())->taskModel(); - Positions positions(opt, model); - - // Paint TaskIconArea: - QIcon icon = index.data(TaskModel::Icon).value(); - painter->drawPixmap(positions.left(), positions.top(), - icon.pixmap(Positions::taskIconWidth(), Positions::taskIconHeight())); - - // Paint TextArea: - if (!current) { - // in small mode we lay out differently - QString bottom = index.data(TaskModel::Description).toString().split(QLatin1Char('\n')).first(); - painter->setClipRect(positions.textArea()); - painter->drawText(positions.textAreaLeft(), positions.top() + fm.ascent(), bottom); - if (fm.horizontalAdvance(bottom) > positions.textAreaWidth()) { - // draw a gradient to mask the text - int gradientStart = positions.textAreaRight() - ELLIPSIS_GRADIENT_WIDTH + 1; - QLinearGradient lg(gradientStart, 0, gradientStart + ELLIPSIS_GRADIENT_WIDTH, 0); - lg.setColorAt(0, Qt::transparent); - lg.setColorAt(1, backgroundColor); - painter->fillRect(gradientStart, positions.top(), ELLIPSIS_GRADIENT_WIDTH, positions.firstLineHeight(), lg); - } - } else { - // Description - QString description = index.data(TaskModel::Description).toString(); - // Layout the description - int leading = fm.leading(); - int height = 0; - description.replace(QLatin1Char('\n'), QChar::LineSeparator); - QTextLayout tl(description); - QVector formats = index.data(TaskModel::Task_t).value().formats; - for (QTextLayout::FormatRange &format : formats) - format.format.setForeground(textColor); - tl.setFormats(formats); - tl.beginLayout(); - while (true) { - QTextLine line = tl.createLine(); - if (!line.isValid()) - break; - line.setLineWidth(positions.textAreaWidth()); - height += leading; - line.setPosition(QPoint(0, height)); - height += static_cast(line.height()); - } - tl.endLayout(); - const QPoint indexPos = view->visualRect(index).topLeft(); - tl.draw(painter, QPoint(positions.textAreaLeft(), positions.top())); - m_hrefs.clear(); - for (const auto &range : tl.formats()) { - if (!range.format.isAnchor()) - continue; - const QTextLine &firstLinkLine = tl.lineForTextPosition(range.start); - const QTextLine &lastLinkLine = tl.lineForTextPosition(range.start + range.length - 1); - for (int i = firstLinkLine.lineNumber(); i <= lastLinkLine.lineNumber(); ++i) { - const QTextLine &linkLine = tl.lineAt(i); - if (!linkLine.isValid()) - break; - const QPointF linePos = linkLine.position(); - const int linkStartPos = i == firstLinkLine.lineNumber() - ? range.start : linkLine.textStart(); - const qreal startOffset = linkLine.cursorToX(linkStartPos); - const int linkEndPos = i == lastLinkLine.lineNumber() - ? range.start + range.length - : linkLine.textStart() + linkLine.textLength(); - const qreal endOffset = linkLine.cursorToX(linkEndPos); - const QPointF linkPos(indexPos.x() + positions.textAreaLeft() + linePos.x() - + startOffset, positions.top() + linePos.y()); - const QSize linkSize(endOffset - startOffset, linkLine.height()); - const QRectF linkRect(linkPos, linkSize); - m_hrefs.push_back({linkRect, range.format.anchorHref()}); - } - } - - const QColor mix = StyleHelper::mergedColors(textColor, backgroundColor, 70); - const QString directory = QDir::toNativeSeparators(index.data(TaskModel::File).toString()); - int secondBaseLine = positions.top() + fm.ascent() + height + leading; - if (index.data(TaskModel::FileNotFound).toBool() && !directory.isEmpty()) { - const QString fileNotFound = Tr::tr("File not found: %1").arg(directory); - const QColor errorColor = selected ? mix : creatorTheme()->color(Theme::TextColorError); - painter->setPen(errorColor); - painter->drawText(positions.textAreaLeft(), secondBaseLine, fileNotFound); - } else { - painter->setPen(mix); - painter->drawText(positions.textAreaLeft(), secondBaseLine, directory); - } - } - painter->setPen(textColor); - - // Paint FileArea - QString file = index.data(TaskModel::File).toString(); - const int pos = file.lastIndexOf(QLatin1Char('/')); - if (pos != -1) - file = file.mid(pos +1); - const int realFileWidth = fm.horizontalAdvance(file); - painter->setClipRect(positions.fileArea()); - painter->drawText(qMin(positions.fileAreaLeft(), positions.fileAreaRight() - realFileWidth), - positions.top() + fm.ascent(), file); - if (realFileWidth > positions.fileAreaWidth()) { - // draw a gradient to mask the text - int gradientStart = positions.fileAreaLeft() - 1; - QLinearGradient lg(gradientStart + ELLIPSIS_GRADIENT_WIDTH, 0, gradientStart, 0); - lg.setColorAt(0, Qt::transparent); - lg.setColorAt(1, backgroundColor); - painter->fillRect(gradientStart, positions.top(), ELLIPSIS_GRADIENT_WIDTH, positions.firstLineHeight(), lg); +void TaskView::mouseReleaseEvent(QMouseEvent *e) +{ + if (m_clickAnchor.isEmpty() || e->button() == Qt::RightButton) { + TreeView::mouseReleaseEvent(e); + return; } - // Paint LineArea - int line = index.data(TaskModel::Line).toInt(); - int movedLine = index.data(TaskModel::MovedLine).toInt(); - QString lineText; + const QString anchor = anchorAt(e->pos()); + if (anchor == m_clickAnchor) { + Core::EditorManager::openEditorAt(OutputLineParser::parseLinkTarget(m_clickAnchor), {}, + Core::EditorManager::SwitchSplitIfAlreadyVisible); + } + m_clickAnchor.clear(); +} - if (line == -1) { - // No line information at all - } else if (movedLine == -1) { - // removed the line, but we had line information, show the line in () - QFont f = painter->font(); - f.setItalic(true); - painter->setFont(f); - lineText = QLatin1Char('(') + QString::number(line) + QLatin1Char(')'); - } else if (movedLine != line) { - // The line was moved - QFont f = painter->font(); - f.setItalic(true); - painter->setFont(f); - lineText = QString::number(movedLine); - } else { - lineText = QString::number(line); +void TaskView::keyReleaseEvent(QKeyEvent *e) +{ + TreeView::keyReleaseEvent(e); + if (e->key() == Qt::Key_Space) { + const Task task = static_cast(model())->task(currentIndex()); + if (!task.isNull()) { + const QPoint toolTipPos = mapToGlobal(visualRect(currentIndex()).topLeft()); + QMetaObject::invokeMethod(this, [this, task, toolTipPos] { + showToolTip(task, toolTipPos); }, Qt::QueuedConnection); + } + } +} + +bool TaskView::event(QEvent *e) +{ + if (e->type() != QEvent::ToolTip) + return TreeView::event(e); + + const auto helpEvent = static_cast(e); + const Task task = static_cast(model())->task(indexAt(helpEvent->pos())); + if (task.isNull()) + return TreeView::event(e); + showToolTip(task, helpEvent->globalPos()); + e->accept(); + return true; +} + +void TaskView::showToolTip(const Task &task, const QPoint &pos) +{ + if (task.details.isEmpty()) { + ToolTip::hideImmediately(); + return; } - painter->setClipRect(positions.lineArea()); - const int realLineWidth = fm.horizontalAdvance(lineText); - painter->drawText(positions.lineAreaRight() - realLineWidth, positions.top() + fm.ascent(), lineText); - painter->setClipRect(opt.rect); + const auto layout = new QVBoxLayout; + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(new QLabel(task.formattedDescription({}))); + ToolTip::show(pos, layout); +} - // Separator lines - painter->setPen(QColor::fromRgb(150,150,150)); - const QRectF borderRect = QRectF(opt.rect).adjusted(0.5, 0.5, -0.5, -0.5); - painter->drawLine(borderRect.bottomLeft(), borderRect.bottomRight()); - painter->restore(); +QString TaskView::anchorAt(const QPoint &pos) +{ + const QModelIndex index = indexAt(pos); + if (!index.isValid() || !index.internalId()) + return {}; + + const QRect itemRect = visualRect(index); + QTextDocument &doc = static_cast(itemDelegate())->doc(); + doc.setHtml(model()->data(index, Qt::DisplayRole).toString()); + const QAbstractTextDocumentLayout * const textLayout = doc.documentLayout(); + QTC_ASSERT(textLayout, return {}); + return textLayout->anchorAt(pos - itemRect.topLeft()); } } // namespace Internal } // namespace ProjectExplorer - -#include "taskwindow.moc" diff --git a/src/plugins/projectexplorer/testdata/multi-target-project/CMakeLists.txt b/src/plugins/projectexplorer/testdata/multi-target-project/CMakeLists.txt new file mode 100644 index 00000000000..5313539cd79 --- /dev/null +++ b/src/plugins/projectexplorer/testdata/multi-target-project/CMakeLists.txt @@ -0,0 +1,3 @@ +project(multi-target-project) +add_executable(multi-target-project-app multi-target-project-main.cpp multi-target-project-shared.h) +add_library(multi-target-project-lib STATIC multi-target-project-lib.cpp multi-target-project-shared.h) diff --git a/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-app.pro b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-app.pro new file mode 100644 index 00000000000..96870c05c1f --- /dev/null +++ b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-app.pro @@ -0,0 +1,4 @@ +TARGET = app +CONFIG -= qt +SOURCES = multi-target-project-main.cpp +HEADERS = multi-target-project-shared.h diff --git a/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-lib.cpp b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-lib.cpp new file mode 100644 index 00000000000..9b7a34861c7 --- /dev/null +++ b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-lib.cpp @@ -0,0 +1,6 @@ +#include "multi-target-project-shared.h" + +int increaseNumber() +{ + return getNumber() + 1; +} diff --git a/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-lib.pro b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-lib.pro new file mode 100644 index 00000000000..42571540519 --- /dev/null +++ b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-lib.pro @@ -0,0 +1,4 @@ +TEMPLATE = lib +CONFIG += static +SOURCES = multi-target-project-lib.cpp +HEADERS = multi-target-project-shared.h diff --git a/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-main.cpp b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-main.cpp new file mode 100644 index 00000000000..306400b3502 --- /dev/null +++ b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-main.cpp @@ -0,0 +1,6 @@ +#include "multi-target-project-shared.h" + +int main() +{ + return getNumber(); +} diff --git a/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-shared.h b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-shared.h new file mode 100644 index 00000000000..9f839e82d02 --- /dev/null +++ b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-shared.h @@ -0,0 +1,3 @@ +#pragma once + +inline int getNumber() { return 5; } diff --git a/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project.pro b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project.pro new file mode 100644 index 00000000000..5e6289d7b26 --- /dev/null +++ b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project.pro @@ -0,0 +1,4 @@ +TEMPLATE = subdirs +app.file = multi-target-project-app.pro +lib.file = multi-target-project-lib.pro +SUBDIRS = app lib diff --git a/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project.qbs b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project.qbs new file mode 100644 index 00000000000..079ac82c955 --- /dev/null +++ b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project.qbs @@ -0,0 +1,10 @@ +Project { + CppApplication { + name: "app" + files: ["multi-target-project-main.cpp", "multi-target-project-shared.h"] + } + StaticLibrary { + Depends { name: "cpp" } + files: ["multi-target-project-lib.cpp", "multi-target-project-shared.h"] + } +} diff --git a/src/plugins/projectexplorer/toolchain.cpp b/src/plugins/projectexplorer/toolchain.cpp index 1a3d3d43f59..8b8cfce2c80 100644 --- a/src/plugins/projectexplorer/toolchain.cpp +++ b/src/plugins/projectexplorer/toolchain.cpp @@ -192,7 +192,7 @@ bool ToolChain::isValid() const return d->m_isValid.value_or(false); } -QStringList ToolChain::includedFiles(const QStringList &flags, const QString &directory) const +FilePaths ToolChain::includedFiles(const QStringList &flags, const FilePath &directory) const { Q_UNUSED(flags) Q_UNUSED(directory) @@ -466,12 +466,12 @@ Utils::LanguageVersion ToolChain::languageVersion(const Utils::Id &language, con } } -QStringList ToolChain::includedFiles(const QString &option, - const QStringList &flags, - const QString &directoryPath, - PossiblyConcatenatedFlag possiblyConcatenated) +FilePaths ToolChain::includedFiles(const QString &option, + const QStringList &flags, + const FilePath &directoryPath, + PossiblyConcatenatedFlag possiblyConcatenated) { - QStringList result; + FilePaths result; for (int i = 0; i < flags.size(); ++i) { QString includeFile; @@ -484,11 +484,8 @@ QStringList ToolChain::includedFiles(const QString &option, if (includeFile.isEmpty() && flag == option && i + 1 < flags.size()) includeFile = flags[++i]; - if (!includeFile.isEmpty()) { - if (!QFileInfo(includeFile).isAbsolute()) - includeFile = directoryPath + "/" + includeFile; - result.append(QDir::cleanPath(includeFile)); - } + if (!includeFile.isEmpty()) + result.append(directoryPath.resolvePath(includeFile)); } return result; @@ -677,7 +674,9 @@ ToolchainDetector::ToolchainDetector(const Toolchains &alreadyKnown, const IDevice::ConstPtr &device, const FilePaths &searchPaths) : alreadyKnown(alreadyKnown), device(device), searchPaths(searchPaths) -{} +{ + QTC_CHECK(device); +} BadToolchain::BadToolchain(const Utils::FilePath &filePath) : BadToolchain(filePath, filePath.symLinkTarget(), filePath.lastModified()) diff --git a/src/plugins/projectexplorer/toolchain.h b/src/plugins/projectexplorer/toolchain.h index 15bee2fdaa8..35cad5333ee 100644 --- a/src/plugins/projectexplorer/toolchain.h +++ b/src/plugins/projectexplorer/toolchain.h @@ -102,7 +102,7 @@ public: virtual Utils::LanguageExtensions languageExtensions(const QStringList &cxxflags) const = 0; virtual Utils::WarningFlags warningFlags(const QStringList &cflags) const = 0; - virtual QStringList includedFiles(const QStringList &flags, const QString &directory) const; + virtual Utils::FilePaths includedFiles(const QStringList &flags, const Utils::FilePath &directory) const; virtual QString sysRoot() const; QString explicitCodeModelTargetTriple() const; @@ -184,10 +184,10 @@ protected: virtual bool fromMap(const QVariantMap &data); enum class PossiblyConcatenatedFlag { No, Yes }; - static QStringList includedFiles(const QString &option, - const QStringList &flags, - const QString &directoryPath, - PossiblyConcatenatedFlag possiblyConcatenated); + static Utils::FilePaths includedFiles(const QString &option, + const QStringList &flags, + const Utils::FilePath &directoryPath, + PossiblyConcatenatedFlag possiblyConcatenated); private: ToolChain(const ToolChain &) = delete; diff --git a/src/plugins/projectexplorer/toolchainconfigwidget.cpp b/src/plugins/projectexplorer/toolchainconfigwidget.cpp index ef97ad72cfd..84b80b4e047 100644 --- a/src/plugins/projectexplorer/toolchainconfigwidget.cpp +++ b/src/plugins/projectexplorer/toolchainconfigwidget.cpp @@ -7,8 +7,8 @@ #include "projectexplorertr.h" #include +#include #include -#include #include #include diff --git a/src/plugins/projectexplorer/toolchainoptionspage.cpp b/src/plugins/projectexplorer/toolchainoptionspage.cpp index b8df603850f..e0717ab7804 100644 --- a/src/plugins/projectexplorer/toolchainoptionspage.cpp +++ b/src/plugins/projectexplorer/toolchainoptionspage.cpp @@ -48,20 +48,8 @@ class ToolChainTreeItem : public TreeItem { public: ToolChainTreeItem(QStackedWidget *parentWidget, ToolChain *tc, bool c) : - toolChain(tc), changed(c) - { - widget = tc->createConfigurationWidget().release(); - if (widget) { - parentWidget->addWidget(widget); - if (tc->isAutoDetected()) - widget->makeReadOnly(); - QObject::connect(widget, &ToolChainConfigWidget::dirty, - [this] { - changed = true; - update(); - }); - } - } + toolChain(tc), changed(c), m_parentWidget(parentWidget) + {} QVariant data(int column, int role) const override { @@ -93,9 +81,30 @@ public: return QVariant(); } + ToolChainConfigWidget *widget() + { + if (!m_widget) { + m_widget = toolChain->createConfigurationWidget().release(); + if (m_widget) { + m_parentWidget->addWidget(m_widget); + if (toolChain->isAutoDetected()) + m_widget->makeReadOnly(); + QObject::connect(m_widget, &ToolChainConfigWidget::dirty, + [this] { + changed = true; + update(); + }); + } + } + return m_widget; + } + ToolChain *toolChain; - ToolChainConfigWidget *widget; bool changed; + +private: + ToolChainConfigWidget *m_widget = nullptr; + QStackedWidget *m_parentWidget = nullptr; }; class DetectionSettingsDialog : public QDialog @@ -423,7 +432,7 @@ void ToolChainOptionsWidget::toolChainSelectionChanged() { ToolChainTreeItem *item = currentTreeItem(); - QWidget *currentTcWidget = item ? item->widget : nullptr; + QWidget *currentTcWidget = item ? item->widget() : nullptr; if (currentTcWidget) m_widgetStack->setCurrentWidget(currentTcWidget); m_container->setVisible(currentTcWidget); @@ -447,8 +456,8 @@ void ToolChainOptionsWidget::apply() for (TreeItem *item : *parent) { auto tcItem = static_cast(item); Q_ASSERT(tcItem->toolChain); - if (!tcItem->toolChain->isAutoDetected() && tcItem->widget && tcItem->changed) - tcItem->widget->apply(); + if (!tcItem->toolChain->isAutoDetected() && tcItem->widget() && tcItem->changed) + tcItem->widget()->apply(); tcItem->changed = false; tcItem->update(); } diff --git a/src/plugins/projectexplorer/toolchainsettingsaccessor.cpp b/src/plugins/projectexplorer/toolchainsettingsaccessor.cpp index 4152e9882ee..35224e4d02a 100644 --- a/src/plugins/projectexplorer/toolchainsettingsaccessor.cpp +++ b/src/plugins/projectexplorer/toolchainsettingsaccessor.cpp @@ -3,6 +3,7 @@ #include "toolchainsettingsaccessor.h" +#include "devicesupport/devicemanager.h" #include "projectexplorerconstants.h" #include "projectexplorertr.h" #include "toolchain.h" @@ -168,11 +169,10 @@ static ToolChainOperations mergeToolChainLists(const Toolchains &systemFileTcs, // ToolChainSettingsAccessor: // -------------------------------------------------------------------- -ToolChainSettingsAccessor::ToolChainSettingsAccessor() : - UpgradingSettingsAccessor("QtCreatorToolChains", - Tr::tr("Tool Chains"), - Core::Constants::IDE_DISPLAY_NAME) +ToolChainSettingsAccessor::ToolChainSettingsAccessor() { + setDocType("QtCreatorToolChains"); + setApplicationDisplayName(Core::Constants::IDE_DISPLAY_NAME); setBaseFilePath(Core::ICore::userResourcePath(TOOLCHAIN_FILENAME)); addVersionUpgrader(std::make_unique()); @@ -192,9 +192,11 @@ Toolchains ToolChainSettingsAccessor::restoreToolChains(QWidget *parent) const // Autodetect: Pass autodetected toolchains from user file so the information can be reused: const Toolchains autodetectedUserFileTcs = Utils::filtered(userFileTcs, &ToolChain::isAutoDetected); - // FIXME: Use real device? - const Toolchains autodetectedTcs = - autoDetectToolChains(ToolchainDetector(autodetectedUserFileTcs, {}, {})); + + // Autodect from system paths on the desktop device. + // The restriction is intentional to keep startup and automatic validation a limited effort + ToolchainDetector detector(autodetectedUserFileTcs, DeviceManager::defaultDesktopDevice(), {}); + const Toolchains autodetectedTcs = autoDetectToolChains(detector); // merge tool chains and register those that we need to keep: const ToolChainOperations ops = mergeToolChainLists(systemFileTcs, userFileTcs, autodetectedTcs); diff --git a/src/plugins/projectexplorer/treescanner.cpp b/src/plugins/projectexplorer/treescanner.cpp index abcc66c3cc3..294ef4988e1 100644 --- a/src/plugins/projectexplorer/treescanner.cpp +++ b/src/plugins/projectexplorer/treescanner.cpp @@ -9,9 +9,9 @@ #include #include +#include #include #include -#include #include @@ -42,9 +42,9 @@ bool TreeScanner::asyncScanForFiles(const Utils::FilePath &directory) if (!m_futureWatcher.isFinished()) return false; - m_scanFuture = Utils::runAsync( - [directory, filter = m_filter, factory = m_factory] (FutureInterface &fi) { - TreeScanner::scanForFiles(fi, directory, filter, factory); + m_scanFuture = Utils::asyncRun( + [directory, filter = m_filter, factory = m_factory] (Promise &promise) { + TreeScanner::scanForFiles(promise, directory, filter, factory); }); m_futureWatcher.setFuture(m_scanFuture); @@ -139,10 +139,10 @@ static std::unique_ptr createFolderNode(const Utils::FilePath &direc return fileSystemNode; } -void TreeScanner::scanForFiles(FutureInterface &fi, const Utils::FilePath& directory, +void TreeScanner::scanForFiles(Promise &promise, const Utils::FilePath& directory, const FileFilter &filter, const FileTypeFactory &factory) { - QList nodes = ProjectExplorer::scanForFiles(fi, directory, + QList nodes = ProjectExplorer::scanForFiles(promise, directory, [&filter, &factory](const Utils::FilePath &fn) -> FileNode * { const Utils::MimeType mimeType = Utils::mimeTypeForFile(fn); @@ -160,10 +160,10 @@ void TreeScanner::scanForFiles(FutureInterface &fi, const Utils::FilePath& direc Utils::sort(nodes, ProjectExplorer::Node::sortByPath); - fi.setProgressValue(fi.progressMaximum()); + promise.setProgressValue(promise.future().progressMaximum()); Result result{createFolderNode(directory, nodes), nodes}; - fi.reportResult(result); + promise.addResult(result); } } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/treescanner.h b/src/plugins/projectexplorer/treescanner.h index e324303c172..f8d019121b9 100644 --- a/src/plugins/projectexplorer/treescanner.h +++ b/src/plugins/projectexplorer/treescanner.h @@ -31,7 +31,7 @@ public: }; using Future = QFuture; using FutureWatcher = QFutureWatcher; - using FutureInterface = QFutureInterface; + using Promise = QPromise; using FileFilter = std::function; using FileTypeFactory = std::function; @@ -69,7 +69,7 @@ signals: void finished(); private: - static void scanForFiles(FutureInterface &fi, const Utils::FilePath &directory, + static void scanForFiles(Promise &fi, const Utils::FilePath &directory, const FileFilter &filter, const FileTypeFactory &factory); private: diff --git a/src/plugins/projectexplorer/userfileaccessor.cpp b/src/plugins/projectexplorer/userfileaccessor.cpp index cf347e032f2..ae900ea818d 100644 --- a/src/plugins/projectexplorer/userfileaccessor.cpp +++ b/src/plugins/projectexplorer/userfileaccessor.cpp @@ -18,8 +18,8 @@ #include #include #include +#include #include -#include #include @@ -283,19 +283,21 @@ FilePaths UserFileBackUpStrategy::readFileCandidates(const FilePath &baseFileNam // UserFileAccessor: // -------------------------------------------------------------------- -UserFileAccessor::UserFileAccessor(Project *project) : - MergingSettingsAccessor(std::make_unique(this), - "QtCreatorProject", project->displayName(), - Core::Constants::IDE_DISPLAY_NAME), - m_project(project) +UserFileAccessor::UserFileAccessor(Project *project) + : m_project(project) { + setStrategy(std::make_unique(this)); + setDocType("QtCreatorProject"); + setApplicationDisplayName(Core::Constants::IDE_DISPLAY_NAME); + // Setup: const FilePath externalUser = externalUserFile(); const FilePath projectUser = projectUserFile(); setBaseFilePath(externalUser.isEmpty() ? projectUser : externalUser); - auto secondary - = std::make_unique(docType, displayName, applicationDisplayName); + auto secondary = std::make_unique(); + secondary->setDocType(m_docType); + secondary->setApplicationDisplayName(m_applicationDisplayName); secondary->setBaseFilePath(sharedFile()); secondary->setReadOnly(); setSecondaryAccessor(std::move(secondary)); diff --git a/src/plugins/python/CMakeLists.txt b/src/plugins/python/CMakeLists.txt index e98190a5691..0e4ca944194 100644 --- a/src/plugins/python/CMakeLists.txt +++ b/src/plugins/python/CMakeLists.txt @@ -20,4 +20,5 @@ add_qtc_plugin(Python pythonsettings.cpp pythonsettings.h pythontr.h pythonutils.cpp pythonutils.h + pythonwizardpage.cpp pythonwizardpage.h ) diff --git a/src/plugins/python/Python.json.in b/src/plugins/python/Python.json.in index 3e62d146313..98fd52ec8d5 100644 --- a/src/plugins/python/Python.json.in +++ b/src/plugins/python/Python.json.in @@ -30,12 +30,16 @@ \" Python module interface file\", \" \", \" \", - \" \", + \" \", \" \", \" Qt Creator Python project file\", - \" \", \" \", \" \", + \" \", + \" \", + \" Qt Creator Python project file\", + \" \", + \" \", \"\" ] } diff --git a/src/plugins/python/pipsupport.cpp b/src/plugins/python/pipsupport.cpp index a282702d9bd..b420f24aa50 100644 --- a/src/plugins/python/pipsupport.cpp +++ b/src/plugins/python/pipsupport.cpp @@ -10,13 +10,13 @@ #include #include -#include +#include #include #include +#include #include -#include -#include +#include using namespace Utils; @@ -27,31 +27,39 @@ const char pipInstallTaskId[] = "Python::pipInstallTask"; PipInstallTask::PipInstallTask(const FilePath &python) : m_python(python) { - connect(&m_process, &QtcProcess::done, this, &PipInstallTask::handleDone); - connect(&m_process, &QtcProcess::readyReadStandardError, this, &PipInstallTask::handleError); - connect(&m_process, &QtcProcess::readyReadStandardOutput, this, &PipInstallTask::handleOutput); + connect(&m_process, &Process::done, this, &PipInstallTask::handleDone); + connect(&m_process, &Process::readyReadStandardError, this, &PipInstallTask::handleError); + connect(&m_process, &Process::readyReadStandardOutput, this, &PipInstallTask::handleOutput); connect(&m_killTimer, &QTimer::timeout, this, &PipInstallTask::cancel); connect(&m_watcher, &QFutureWatcher::canceled, this, &PipInstallTask::cancel); m_watcher.setFuture(m_future.future()); } -void PipInstallTask::setPackage(const PipPackage &package) +void PipInstallTask::addPackage(const PipPackage &package) { - m_package = package; + m_packages << package; +} + +void PipInstallTask::setPackages(const QList &packages) +{ + m_packages = packages; } void PipInstallTask::run() { - if (m_package.packageName.isEmpty()) { + if (m_packages.isEmpty()) { emit finished(false); return; } - const QString taskTitle = Tr::tr("Install %1").arg(m_package.displayName); + const QString taskTitle = Tr::tr("Install Python Packages"); Core::ProgressManager::addTask(m_future.future(), taskTitle, pipInstallTaskId); - QString package = m_package.packageName; - if (!m_package.version.isEmpty()) - package += "==" + m_package.version; - QStringList arguments = {"-m", "pip", "install", package}; + QStringList arguments = {"-m", "pip", "install"}; + for (const PipPackage &package : m_packages) { + QString pipPackage = package.packageName; + if (!package.version.isEmpty()) + pipPackage += "==" + package.version; + arguments << pipPackage; + } // add --user to global pythons, but skip it for venv pythons if (!QDir(m_python.parentDir().toString()).exists("activate")) @@ -62,7 +70,7 @@ void PipInstallTask::run() Core::MessageManager::writeDisrupting( Tr::tr("Running \"%1\" to install %2.") - .arg(m_process.commandLine().toUserOutput(), m_package.displayName)); + .arg(m_process.commandLine().toUserOutput(), packagesDisplayName())); m_killTimer.setSingleShot(true); m_killTimer.start(5 /*minutes*/ * 60 * 1000); @@ -73,8 +81,10 @@ void PipInstallTask::cancel() m_process.stop(); m_process.waitForFinished(); Core::MessageManager::writeFlashing( - Tr::tr("The %1 installation was canceled by %2.") - .arg(m_package.displayName, m_killTimer.isActive() ? Tr::tr("user") : Tr::tr("time out"))); + m_killTimer.isActive() + ? Tr::tr("The installation of \"%1\" was canceled by timeout.").arg(packagesDisplayName()) + : Tr::tr("The installation of \"%1\" was canceled by the user.") + .arg(packagesDisplayName())); } void PipInstallTask::handleDone() @@ -82,8 +92,8 @@ void PipInstallTask::handleDone() m_future.reportFinished(); const bool success = m_process.result() == ProcessResult::FinishedWithSuccess; if (!success) { - Core::MessageManager::writeFlashing(Tr::tr("Installing the %1 failed with exit code %2") - .arg(m_package.displayName).arg(m_process.exitCode())); + Core::MessageManager::writeFlashing(Tr::tr("Installing %1 failed with exit code %2") + .arg(packagesDisplayName()).arg(m_process.exitCode())); } emit finished(success); } @@ -102,6 +112,11 @@ void PipInstallTask::handleError() Core::MessageManager::writeSilently(stdErr); } +QString PipInstallTask::packagesDisplayName() const +{ + return Utils::transform(m_packages, &PipPackage::displayName).join(", "); +} + void PipPackageInfo::parseField(const QString &field, const QStringList &data) { if (field.isEmpty()) @@ -147,7 +162,7 @@ static PipPackageInfo infoImpl(const PipPackage &package, const FilePath &python { PipPackageInfo result; - QtcProcess pip; + Process pip; pip.setCommand(CommandLine(python, {"-m", "pip", "show", "-f", package.packageName})); pip.runBlocking(); QString fieldName; @@ -175,7 +190,7 @@ static PipPackageInfo infoImpl(const PipPackage &package, const FilePath &python QFuture Pip::info(const PipPackage &package) { - return Utils::runAsync(infoImpl, package, m_python); + return Utils::asyncRun(infoImpl, package, m_python); } Pip::Pip(const Utils::FilePath &python) diff --git a/src/plugins/python/pipsupport.h b/src/plugins/python/pipsupport.h index 4ef6fcca730..4d08a3b1ea6 100644 --- a/src/plugins/python/pipsupport.h +++ b/src/plugins/python/pipsupport.h @@ -4,7 +4,7 @@ #pragma once #include -#include +#include #include #include @@ -63,7 +63,8 @@ class PipInstallTask : public QObject Q_OBJECT public: explicit PipInstallTask(const Utils::FilePath &python); - void setPackage(const PipPackage &package); + void addPackage(const PipPackage &package); + void setPackages(const QList &packages); void run(); signals: @@ -75,9 +76,11 @@ private: void handleOutput(); void handleError(); + QString packagesDisplayName() const; + const Utils::FilePath m_python; - PipPackage m_package; - Utils::QtcProcess m_process; + QList m_packages; + Utils::Process m_process; QFutureInterface m_future; QFutureWatcher m_watcher; QTimer m_killTimer; diff --git a/src/plugins/python/pyside.cpp b/src/plugins/python/pyside.cpp index 592addd3085..4b9df185a37 100644 --- a/src/plugins/python/pyside.cpp +++ b/src/plugins/python/pyside.cpp @@ -5,7 +5,6 @@ #include "pipsupport.h" #include "pythonplugin.h" -#include "pythonsettings.h" #include "pythontr.h" #include "pythonutils.h" @@ -17,10 +16,10 @@ #include #include +#include #include +#include #include -#include -#include #include #include @@ -55,7 +54,7 @@ bool PySideInstaller::missingPySideInstallation(const FilePath &pythonPath, if (pythonWithPyside[pythonPath].contains(pySide)) return false; - QtcProcess pythonProcess; + Process pythonProcess; pythonProcess.setCommand({pythonPath, {"-c", "import " + pySide}}); pythonProcess.runBlocking(); const bool missing = pythonProcess.result() != ProcessResult::FinishedWithSuccess; @@ -88,19 +87,10 @@ void PySideInstaller::installPyside(const FilePath &python, if (success) emit pySideInstalled(python, pySide); }); - install->setPackage(PipPackage(pySide)); + install->setPackages({PipPackage(pySide)}); install->run(); } -void PySideInstaller::changeInterpreter(const QString &interpreterId, - RunConfiguration *runConfig) -{ - if (runConfig) { - if (auto aspect = runConfig->aspect()) - aspect->setCurrentInterpreter(PythonSettings::interpreter(interpreterId)); - } -} - void PySideInstaller::handlePySideMissing(const FilePath &python, const QString &pySide, TextEditor::TextDocument *document) @@ -140,8 +130,7 @@ void PySideInstaller::runPySideChecker(const FilePath &python, handlePySideMissing(python, pySide, document); watcher->deleteLater(); }); - watcher->setFuture( - Utils::runAsync(&missingPySideInstallation, python, pySide)); + watcher->setFuture(Utils::asyncRun(&missingPySideInstallation, python, pySide)); } } // Python::Internal diff --git a/src/plugins/python/pyside.h b/src/plugins/python/pyside.h index 299a095412a..3b4f99974a5 100644 --- a/src/plugins/python/pyside.h +++ b/src/plugins/python/pyside.h @@ -30,7 +30,6 @@ private: void installPyside(const Utils::FilePath &python, const QString &pySide, TextEditor::TextDocument *document); - void changeInterpreter(const QString &interpreterId, ProjectExplorer::RunConfiguration *runConfig); void handlePySideMissing(const Utils::FilePath &python, const QString &pySide, TextEditor::TextDocument *document); diff --git a/src/plugins/python/pysidebuildconfiguration.cpp b/src/plugins/python/pysidebuildconfiguration.cpp index 3480be3e87a..5f6f7dadea5 100644 --- a/src/plugins/python/pysidebuildconfiguration.cpp +++ b/src/plugins/python/pysidebuildconfiguration.cpp @@ -23,6 +23,69 @@ namespace Python::Internal { const char pySideBuildStep[] = "Python.PysideBuildStep"; +PySideBuildStepFactory::PySideBuildStepFactory() +{ + registerStep(pySideBuildStep); + setSupportedProjectType(PythonProjectId); + setDisplayName(Tr::tr("Run PySide6 project tool")); + setFlags(BuildStep::UniqueStep); +} + +PySideBuildStep::PySideBuildStep(BuildStepList *bsl, Id id) + : AbstractProcessStep(bsl, id) +{ + m_pysideProject.setSettingsKey("Python.PySideProjectTool"); + m_pysideProject.setLabelText(Tr::tr("PySide project tool:")); + m_pysideProject.setToolTip(Tr::tr("Enter location of PySide project tool.")); + m_pysideProject.setExpectedKind(PathChooser::Command); + m_pysideProject.setHistoryCompleter("Python.PySideProjectTool.History"); + + const FilePath pySideProjectPath = FilePath("pyside6-project").searchInPath(); + if (pySideProjectPath.isExecutableFile()) + m_pysideProject.setFilePath(pySideProjectPath); + + setCommandLineProvider([this] { return CommandLine(m_pysideProject(), {"build"}); }); + setWorkingDirectoryProvider([this] { + return m_pysideProject().withNewMappedPath(project()->projectDirectory()); // FIXME: new path needed? + }); + setEnvironmentModifier([this](Environment &env) { + env.prependOrSetPath(m_pysideProject().parentDir()); + }); +} + +void PySideBuildStep::updatePySideProjectPath(const FilePath &pySideProjectPath) +{ + m_pysideProject.setFilePath(pySideProjectPath); +} + +void PySideBuildStep::doRun() +{ + if (processParameters()->effectiveCommand().isExecutableFile()) + AbstractProcessStep::doRun(); + else + emit finished(true); +} + + +// PySideBuildConfiguration + +class PySideBuildConfiguration : public BuildConfiguration +{ +public: + PySideBuildConfiguration(Target *target, Id id) + : BuildConfiguration(target, id) + { + setConfigWidgetDisplayName(Tr::tr("General")); + + setInitializer([this](const BuildInfo &) { + buildSteps()->appendStep(pySideBuildStep); + updateCacheAndEmitEnvironmentChanged(); + }); + + updateCacheAndEmitEnvironmentChanged(); + } +}; + PySideBuildConfigurationFactory::PySideBuildConfigurationFactory() { registerBuildConfiguration("Python.PySideBuildConfiguration"); @@ -37,63 +100,4 @@ PySideBuildConfigurationFactory::PySideBuildConfigurationFactory() }); } -PySideBuildStepFactory::PySideBuildStepFactory() -{ - registerStep(pySideBuildStep); - setSupportedProjectType(PythonProjectId); - setDisplayName(Tr::tr("Run PySide6 project tool")); - setFlags(BuildStepInfo::UniqueStep); -} - -PySideBuildStep::PySideBuildStep(BuildStepList *bsl, Id id) - : AbstractProcessStep(bsl, id) -{ - m_pysideProject = addAspect(); - m_pysideProject->setSettingsKey("Python.PySideProjectTool"); - m_pysideProject->setLabelText(Tr::tr("PySide project tool:")); - m_pysideProject->setToolTip(Tr::tr("Enter location of PySide project tool.")); - m_pysideProject->setDisplayStyle(StringAspect::PathChooserDisplay); - m_pysideProject->setExpectedKind(PathChooser::Command); - m_pysideProject->setHistoryCompleter("Python.PySideProjectTool.History"); - - const FilePath pySideProjectPath = Environment::systemEnvironment().searchInPath( - "pyside6-project"); - if (pySideProjectPath.isExecutableFile()) - m_pysideProject->setFilePath(pySideProjectPath); - - setCommandLineProvider([this] { return CommandLine(m_pysideProject->filePath(), {"build"}); }); - setWorkingDirectoryProvider([this] { - return target()->project()->projectDirectory().onDevice(m_pysideProject->filePath()); - }); - setEnvironmentModifier([this](Environment &env) { - env.prependOrSetPath(m_pysideProject->filePath().parentDir()); - }); -} - -void PySideBuildStep::updatePySideProjectPath(const Utils::FilePath &pySideProjectPath) -{ - m_pysideProject->setFilePath(pySideProjectPath); -} - -void PySideBuildStep::doRun() -{ - if (processParameters()->effectiveCommand().isExecutableFile()) - AbstractProcessStep::doRun(); - else - emit finished(true); -} - -PySideBuildConfiguration::PySideBuildConfiguration(Target *target, Id id) - : BuildConfiguration(target, id) -{ - setConfigWidgetDisplayName(Tr::tr("General")); - - setInitializer([this](const BuildInfo &) { - buildSteps()->appendStep(pySideBuildStep); - updateCacheAndEmitEnvironmentChanged(); - }); - - updateCacheAndEmitEnvironmentChanged(); -} - } // Python::Internal diff --git a/src/plugins/python/pysidebuildconfiguration.h b/src/plugins/python/pysidebuildconfiguration.h index 17022c6be2b..e686b9c2220 100644 --- a/src/plugins/python/pysidebuildconfiguration.h +++ b/src/plugins/python/pysidebuildconfiguration.h @@ -9,18 +9,6 @@ namespace Python::Internal { -class PySideBuildConfiguration : public ProjectExplorer::BuildConfiguration -{ -public: - PySideBuildConfiguration(ProjectExplorer::Target *target, Utils::Id id); -}; - -class PySideBuildConfigurationFactory : public ProjectExplorer::BuildConfigurationFactory -{ -public: - PySideBuildConfigurationFactory(); -}; - class PySideBuildStep : public ProjectExplorer::AbstractProcessStep { Q_OBJECT @@ -28,11 +16,10 @@ public: PySideBuildStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id); void updatePySideProjectPath(const Utils::FilePath &pySideProjectPath); -private: - Utils::StringAspect *m_pysideProject; - private: void doRun() override; + + Utils::FilePathAspect m_pysideProject{this}; }; class PySideBuildStepFactory : public ProjectExplorer::BuildStepFactory @@ -41,4 +28,10 @@ public: PySideBuildStepFactory(); }; +class PySideBuildConfigurationFactory : public ProjectExplorer::BuildConfigurationFactory +{ +public: + PySideBuildConfigurationFactory(); +}; + } // Python::Internal diff --git a/src/plugins/python/pysideuicextracompiler.cpp b/src/plugins/python/pysideuicextracompiler.cpp index b1e7e31bb7f..45fb68066f9 100644 --- a/src/plugins/python/pysideuicextracompiler.cpp +++ b/src/plugins/python/pysideuicextracompiler.cpp @@ -3,7 +3,7 @@ #include "pysideuicextracompiler.h" -#include +#include using namespace ProjectExplorer; using namespace Utils; @@ -30,7 +30,7 @@ FilePath PySideUicExtraCompiler::command() const return m_pySideUic; } -FileNameToContentsHash PySideUicExtraCompiler::handleProcessFinished(QtcProcess *process) +FileNameToContentsHash PySideUicExtraCompiler::handleProcessFinished(Process *process) { FileNameToContentsHash result; if (process->exitStatus() != QProcess::NormalExit && process->exitCode() != 0) diff --git a/src/plugins/python/pysideuicextracompiler.h b/src/plugins/python/pysideuicextracompiler.h index f2a09bed78a..058717621ed 100644 --- a/src/plugins/python/pysideuicextracompiler.h +++ b/src/plugins/python/pysideuicextracompiler.h @@ -21,7 +21,7 @@ public: private: Utils::FilePath command() const override; ProjectExplorer::FileNameToContentsHash handleProcessFinished( - Utils::QtcProcess *process) override; + Utils::Process *process) override; Utils::FilePath m_pySideUic; }; diff --git a/src/plugins/python/python.qbs b/src/plugins/python/python.qbs index 9f8288cb11a..5186dafcdcd 100644 --- a/src/plugins/python/python.qbs +++ b/src/plugins/python/python.qbs @@ -49,6 +49,8 @@ QtcPlugin { "pythontr.h", "pythonutils.cpp", "pythonutils.h", + "pythonwizardpage.cpp", + "pythonwizardpage.h", ] } } diff --git a/src/plugins/python/pythoneditor.cpp b/src/plugins/python/pythoneditor.cpp index 6551facef50..d23685bc3b7 100644 --- a/src/plugins/python/pythoneditor.cpp +++ b/src/plugins/python/pythoneditor.cpp @@ -19,12 +19,14 @@ #include #include -#include +#include #include #include #include +#include + #include #include #include @@ -120,7 +122,7 @@ private: PythonEditorWidget::PythonEditorWidget(QWidget *parent) : TextEditorWidget(parent) { auto replButton = new QToolButton(this); - replButton->setProperty("noArrow", true); + replButton->setProperty(StyleHelper::C_NO_ARROW, true); replButton->setText(Tr::tr("REPL")); replButton->setPopupMode(QToolButton::InstantPopup); replButton->setToolTip(Tr::tr("Open interactive Python. Either importing nothing, " @@ -152,7 +154,7 @@ void PythonEditorWidget::setUserDefinedPython(const Interpreter &interpreter) QTC_ASSERT(pythonDocument, return); FilePath documentPath = pythonDocument->filePath(); QTC_ASSERT(!documentPath.isEmpty(), return); - if (Project *project = SessionManager::projectForFile(documentPath)) { + if (Project *project = ProjectManager::projectForFile(documentPath)) { if (Target *target = project->activeTarget()) { if (RunConfiguration *rc = target->activeRunConfiguration()) { if (auto interpretersAspect= rc->aspect()) { @@ -163,6 +165,7 @@ void PythonEditorWidget::setUserDefinedPython(const Interpreter &interpreter) } } definePythonForDocument(textDocument()->filePath(), interpreter.command); + updateInterpretersSelector(); pythonDocument->checkForPyls(); } @@ -174,7 +177,7 @@ void PythonEditorWidget::updateInterpretersSelector() m_interpreters->setMenu(new QMenu(m_interpreters)); m_interpreters->setPopupMode(QToolButton::InstantPopup); m_interpreters->setToolButtonStyle(Qt::ToolButtonTextOnly); - m_interpreters->setProperty("noArrow", true); + m_interpreters->setProperty(StyleHelper::C_NO_ARROW, true); } QMenu *menu = m_interpreters->menu(); @@ -184,7 +187,7 @@ void PythonEditorWidget::updateInterpretersSelector() disconnect(connection); m_projectConnections.clear(); const FilePath documentPath = textDocument()->filePath(); - if (Project *project = SessionManager::projectForFile(documentPath)) { + if (Project *project = ProjectManager::projectForFile(documentPath)) { m_projectConnections << connect(project, &Project::activeTargetChanged, this, @@ -212,33 +215,51 @@ void PythonEditorWidget::updateInterpretersSelector() m_interpreters->setText(text); }; - const FilePath currentInterpreter = detectPython(textDocument()->filePath()); + const FilePath currentInterpreterPath = detectPython(textDocument()->filePath()); const QList configuredInterpreters = PythonSettings::interpreters(); - bool foundCurrentInterpreter = false; auto interpretersGroup = new QActionGroup(menu); interpretersGroup->setExclusive(true); + std::optional currentInterpreter; for (const Interpreter &interpreter : configuredInterpreters) { QAction *action = interpretersGroup->addAction(interpreter.name); connect(action, &QAction::triggered, this, [this, interpreter]() { setUserDefinedPython(interpreter); }); action->setCheckable(true); - if (!foundCurrentInterpreter && interpreter.command == currentInterpreter) { - foundCurrentInterpreter = true; + if (!currentInterpreter && interpreter.command == currentInterpreterPath) { + currentInterpreter = interpreter; action->setChecked(true); setButtonText(interpreter.name); m_interpreters->setToolTip(interpreter.command.toUserOutput()); } } menu->addActions(interpretersGroup->actions()); - if (!foundCurrentInterpreter) { - if (currentInterpreter.exists()) - setButtonText(currentInterpreter.toUserOutput()); + if (!currentInterpreter) { + if (currentInterpreterPath.exists()) + setButtonText(currentInterpreterPath.toUserOutput()); else setButtonText(Tr::tr("No Python Selected")); } - if (!interpretersGroup->actions().isEmpty()) - menu->addSeparator(); + if (!interpretersGroup->actions().isEmpty()) { + menu->addSeparator(); + auto venvAction = menu->addAction(Tr::tr("Create Virtual Environment")); + connect(venvAction, + &QAction::triggered, + this, + [self = QPointer(this), currentInterpreter]() { + if (!currentInterpreter) + return; + auto callback = [self](const std::optional &venvInterpreter) { + if (self && venvInterpreter) + self->setUserDefinedPython(*venvInterpreter); + }; + PythonSettings::createVirtualEnvironmentInteractive(self->textDocument() + ->filePath() + .parentDir(), + *currentInterpreter, + callback); + }); + } auto settingsAction = menu->addAction(Tr::tr("Manage Python Interpreters")); connect(settingsAction, &QAction::triggered, this, []() { Core::ICore::showOptionsDialog(Constants::C_PYTHONOPTIONS_PAGE_ID); diff --git a/src/plugins/python/pythonlanguageclient.cpp b/src/plugins/python/pythonlanguageclient.cpp index dde324dbd95..9f347d8e486 100644 --- a/src/plugins/python/pythonlanguageclient.cpp +++ b/src/plugins/python/pythonlanguageclient.cpp @@ -23,15 +23,15 @@ #include #include -#include +#include #include #include #include +#include #include -#include -#include +#include #include #include @@ -80,7 +80,7 @@ FilePath getPylsModulePath(CommandLine pylsCommand) pylsCommand.addArg("-h"); - QtcProcess pythonProcess; + Process pythonProcess; Environment env = pythonProcess.environment(); env.set("PYTHONVERBOSE", "x"); pythonProcess.setEnvironment(env); @@ -114,7 +114,7 @@ static PythonLanguageServerState checkPythonLanguageServer(const FilePath &pytho const CommandLine pythonLShelpCommand(python, {"-m", "pylsp", "-h"}); const FilePath &modulePath = getPylsModulePath(pythonLShelpCommand); - QtcProcess pythonProcess; + Process pythonProcess; pythonProcess.setCommand(pythonLShelpCommand); pythonProcess.runBlocking(); if (pythonProcess.allOutput().contains("Python Language Server")) @@ -305,7 +305,7 @@ void PyLSConfigureAssistant::installPythonLanguageServer(const FilePath &python, install->deleteLater(); }); - install->setPackage(PipPackage{"python-lsp-server[all]", "Python Language Server"}); + install->setPackages({PipPackage{"python-lsp-server[all]", "Python Language Server"}}); install->run(); } @@ -341,7 +341,7 @@ void PyLSConfigureAssistant::openDocumentWithPython(const FilePath &python, instance()->handlePyLSState(python, watcher->result(), document); watcher->deleteLater(); }); - watcher->setFuture(Utils::runAsync(&checkPythonLanguageServer, python)); + watcher->setFuture(Utils::asyncRun(&checkPythonLanguageServer, python)); } void PyLSConfigureAssistant::handlePyLSState(const FilePath &python, diff --git a/src/plugins/python/pythonplugin.cpp b/src/plugins/python/pythonplugin.cpp index 3bc19a8725b..3cbbe817f9d 100644 --- a/src/plugins/python/pythonplugin.cpp +++ b/src/plugins/python/pythonplugin.cpp @@ -6,17 +6,17 @@ #include "pysidebuildconfiguration.h" #include "pythoneditor.h" #include "pythonproject.h" -#include "pythonsettings.h" #include "pythonrunconfiguration.h" +#include "pythonsettings.h" +#include "pythonwizardpage.h" #include -#include +#include #include #include #include #include -#include #include using namespace ProjectExplorer; @@ -36,7 +36,6 @@ public: PySideBuildConfigurationFactory buildConfigFactory; SimpleTargetRunnerFactory runWorkerFactory{{runConfigFactory.runConfigurationId()}}; PythonSettings settings; - FutureSynchronizer m_futureSynchronizer; }; PythonPlugin::PythonPlugin() @@ -55,17 +54,13 @@ PythonPlugin *PythonPlugin::instance() return m_instance; } -FutureSynchronizer *PythonPlugin::futureSynchronizer() -{ - QTC_ASSERT(m_instance, return nullptr); - return &m_instance->d->m_futureSynchronizer; -} - void PythonPlugin::initialize() { d = new PythonPluginPrivate; ProjectManager::registerProjectType(PythonMimeType); + ProjectManager::registerProjectType(PythonMimeTypeLegacy); + JsonWizardFactory::registerPageFactory(new PythonWizardPageFactory); } void PythonPlugin::extensionsInitialized() diff --git a/src/plugins/python/pythonplugin.h b/src/plugins/python/pythonplugin.h index 7c8ca121f3c..ef0860bbca4 100644 --- a/src/plugins/python/pythonplugin.h +++ b/src/plugins/python/pythonplugin.h @@ -5,8 +5,6 @@ #include -namespace Utils { class FutureSynchronizer; } - namespace Python::Internal { class PythonPlugin final : public ExtensionSystem::IPlugin @@ -19,7 +17,6 @@ public: ~PythonPlugin() final; static PythonPlugin *instance(); - static Utils::FutureSynchronizer *futureSynchronizer(); private: void initialize() final; diff --git a/src/plugins/python/pythonproject.h b/src/plugins/python/pythonproject.h index 0217a77b615..676e010a015 100644 --- a/src/plugins/python/pythonproject.h +++ b/src/plugins/python/pythonproject.h @@ -7,7 +7,8 @@ namespace Python::Internal { -const char PythonMimeType[] = "text/x-python-project"; // ### FIXME +const char PythonMimeType[] = "text/x-python-project"; +const char PythonMimeTypeLegacy[] = "text/x-pyqt-project"; const char PythonProjectId[] = "PythonProject"; const char PythonErrorTaskCategory[] = "Task.Category.Python"; diff --git a/src/plugins/python/pythonrunconfiguration.cpp b/src/plugins/python/pythonrunconfiguration.cpp index d21e7f8bfcb..e7c5b4dee14 100644 --- a/src/plugins/python/pythonrunconfiguration.cpp +++ b/src/plugins/python/pythonrunconfiguration.cpp @@ -9,7 +9,6 @@ #include "pysideuicextracompiler.h" #include "pythonconstants.h" #include "pythonlanguageclient.h" -#include "pythonplugin.h" #include "pythonproject.h" #include "pythonsettings.h" #include "pythontr.h" @@ -17,13 +16,14 @@ #include #include +#include + #include #include #include #include #include -#include #include #include #include @@ -189,7 +189,8 @@ PythonRunConfiguration::PythonRunConfiguration(Target *target, Id id) scriptAspect->setLabelText(Tr::tr("Script:")); scriptAspect->setDisplayStyle(StringAspect::LabelDisplay); - addAspect(target); + auto envAspect = addAspect(); + envAspect->setSupportForBuildEnvironment(target); auto argumentsAspect = addAspect(macroExpander()); @@ -218,7 +219,7 @@ PythonRunConfiguration::PythonRunConfiguration(Target *target, Id id) currentInterpreterChanged(); setRunnableModifier([](Runnable &r) { - r.workingDirectory = r.workingDirectory.onDevice(r.command.executable()); + r.workingDirectory = r.command.executable().withNewMappedPath(r.workingDirectory); // FIXME: Needed? }); connect(PySideInstaller::instance(), &PySideInstaller::pySideInstalled, this, @@ -248,7 +249,7 @@ void PythonRunConfigurationPrivate::checkForPySide(const FilePath &python, }); const auto future = Pip::instance(python)->info(package); m_watcher.setFuture(future); - PythonPlugin::futureSynchronizer()->addFuture(future); + ExtensionSystem::PluginManager::futureSynchronizer()->addFuture(future); } void PythonRunConfigurationPrivate::handlePySidePackageInfo(const PipPackageInfo &pySideInfo, @@ -279,12 +280,12 @@ void PythonRunConfigurationPrivate::handlePySidePackageInfo(const PipPackageInfo = OsSpecificAspects::withExecutableSuffix(python.osType(), "pyside6-uic"); for (const FilePath &file : files) { if (file.fileName() == pySide6ProjectName) { - result.pySideProjectPath = location.resolvePath(file).onDevice(python); + result.pySideProjectPath = python.withNewMappedPath(location.resolvePath(file)); result.pySideProjectPath = result.pySideProjectPath.cleanPath(); if (!result.pySideUicPath.isEmpty()) return result; } else if (file.fileName() == pySide6UicName) { - result.pySideUicPath = location.resolvePath(file).onDevice(python); + result.pySideUicPath = python.withNewMappedPath(location.resolvePath(file)); result.pySideUicPath = result.pySideUicPath.cleanPath(); if (!result.pySideProjectPath.isEmpty()) return result; diff --git a/src/plugins/python/pythonsettings.cpp b/src/plugins/python/pythonsettings.cpp index 9306fe3b6f4..0fe6576170f 100644 --- a/src/plugins/python/pythonsettings.cpp +++ b/src/plugins/python/pythonsettings.cpp @@ -6,6 +6,7 @@ #include "pythonconstants.h" #include "pythonplugin.h" #include "pythontr.h" +#include "pythonutils.h" #include #include @@ -26,23 +27,26 @@ #include #include #include -#include +#include #include #include +#include +#include +#include #include +#include +#include +#include +#include #include -#include #include +#include #include #include #include -#include #include -#include -#include -#include -#include +#include using namespace ProjectExplorer; using namespace Utils; @@ -58,7 +62,7 @@ static Interpreter createInterpreter(const FilePath &python, result.id = QUuid::createUuid().toString(); result.command = python; - QtcProcess pythonProcess; + Process pythonProcess; pythonProcess.setProcessChannelMode(QProcess::MergedChannels); pythonProcess.setTimeoutS(1); pythonProcess.setCommand({python, {"--version"}}); @@ -69,7 +73,7 @@ static Interpreter createInterpreter(const FilePath &python, result.name = defaultName; QDir pythonDir(python.parentDir().toString()); if (pythonDir.exists() && pythonDir.exists("activate") && pythonDir.cdUp()) - result.name += QString(" (%1 Virtual Environment)").arg(pythonDir.dirName()); + result.name += QString(" (%1)").arg(pythonDir.dirName()); if (!suffix.isEmpty()) result.name += ' ' + suffix; @@ -92,8 +96,9 @@ public: Form { Tr::tr("Name:"), m_name, br, - Tr::tr("Executable"), m_executable - }.attachTo(this, WithoutMargins); + Tr::tr("Executable"), m_executable, + noMargin + }.attachTo(this); } void updateInterpreter(const Interpreter &interpreter) @@ -166,9 +171,10 @@ InterpreterOptionsWidget::InterpreterOptionsWidget() if (interpreter.command.isEmpty()) return Tr::tr("Executable is empty."); if (!interpreter.command.exists()) - return Tr::tr("%1 does not exist.").arg(interpreter.command.toUserOutput()); + return Tr::tr("\"%1\" does not exist.").arg(interpreter.command.toUserOutput()); if (!interpreter.command.isExecutableFile()) - return Tr::tr("%1 is not an executable file.").arg(interpreter.command.toUserOutput()); + return Tr::tr("\"%1\" is not an executable file.") + .arg(interpreter.command.toUserOutput()); break; case Qt::DecorationRole: if (column == 0 && !interpreter.command.isExecutableFile()) @@ -314,34 +320,37 @@ public: setCategory(Constants::C_PYTHON_SETTINGS_CATEGORY); setDisplayCategory(Tr::tr("Python")); setCategoryIconPath(":/python/images/settingscategory_python.png"); - setWidgetCreator([]() { return new InterpreterOptionsWidget(); }); + setWidgetCreator([this] { m_widget = new InterpreterOptionsWidget; return m_widget; }); } QList interpreters() { - if (auto w = static_cast(widget())) - return w->interpreters(); + if (m_widget) + return m_widget->interpreters(); return {}; } void addInterpreter(const Interpreter &interpreter) { - if (auto w = static_cast(widget())) - w->addInterpreter(interpreter); + if (m_widget) + m_widget->addInterpreter(interpreter); } void removeInterpreterFrom(const QString &detectionSource) { - if (auto w = static_cast(widget())) - w->removeInterpreterFrom(detectionSource); + if (m_widget) + m_widget->removeInterpreterFrom(detectionSource); } QList interpreterFrom(const QString &detectionSource) { - if (auto w = static_cast(widget())) - return w->interpreterFrom(detectionSource); + if (m_widget) + return m_widget->interpreterFrom(detectionSource); return {}; } + +private: + InterpreterOptionsWidget *m_widget = nullptr; }; static bool alreadyRegistered(const QList &pythons, const FilePath &pythonExecutable) @@ -405,11 +414,11 @@ public: mainGroupLayout->addWidget(m_pluginsGroup); - const QString labelText = Tr::tr( - "For a complete list of available options, consult the
Python LSP Server configuration documentation."); - + const QString labelText = Tr::tr("For a complete list of available options, consult the " + "[Python LSP Server configuration documentation](%1).") + .arg("https://github.com/python-lsp/python-lsp-server/blob/" + "develop/CONFIGURATION.md"); + m_advancedLabel->setTextFormat(Qt::MarkdownText); m_advancedLabel->setText(labelText); m_advancedLabel->setOpenExternalLinks(true); mainGroupLayout->addWidget(m_advancedLabel); @@ -625,7 +634,8 @@ static void addPythonsFromRegistry(QList &pythons) const FilePath &executable = FilePath::fromUserInput(regVal.toString()); if (executable.exists() && !alreadyRegistered(pythons, executable)) { pythons << Interpreter{QUuid::createUuid().toString(), - name + Tr::tr(" (Windowed)"), + //: (Windowed) + Tr::tr("%1 (Windowed)").arg(name), FilePath::fromUserInput(regVal.toString())}; } } @@ -645,17 +655,15 @@ static void addPythonsFromRegistry(QList &pythons) static void addPythonsFromPath(QList &pythons) { - const auto &env = Environment::systemEnvironment(); - if (HostOsInfo::isWindowsHost()) { - for (const FilePath &executable : env.findAllInPath("python")) { + for (const FilePath &executable : FilePath("python").searchAllInPath()) { // Windows creates empty redirector files that may interfere if (executable.toFileInfo().size() == 0) continue; if (executable.exists() && !alreadyRegistered(pythons, executable)) pythons << createInterpreter(executable, "Python from Path"); } - for (const FilePath &executable : env.findAllInPath("pythonw")) { + for (const FilePath &executable : FilePath("pythonw").searchAllInPath()) { if (executable.exists() && !alreadyRegistered(pythons, executable)) pythons << createInterpreter(executable, "Python from Path", "(Windowed)"); } @@ -664,7 +672,8 @@ static void addPythonsFromPath(QList &pythons) "python[1-9].[0-9]", "python[1-9].[1-9][0-9]", "python[1-9]"}; - for (const FilePath &path : env.path()) { + const FilePaths dirs = Environment::systemEnvironment().path(); + for (const FilePath &path : dirs) { const QDir dir(path.toString()); for (const QFileInfo &fi : dir.entryInfoList(filters)) { const FilePath executable = Utils::FilePath::fromFileInfo(fi); @@ -677,9 +686,9 @@ static void addPythonsFromPath(QList &pythons) static QString idForPythonFromPath(const QList &pythons) { - FilePath pythonFromPath = Environment::systemEnvironment().searchInPath("python3"); + FilePath pythonFromPath = FilePath("python3").searchInPath(); if (pythonFromPath.isEmpty()) - pythonFromPath = Environment::systemEnvironment().searchInPath("python"); + pythonFromPath = FilePath("python").searchInPath(); if (pythonFromPath.isEmpty()) return {}; const Interpreter &defaultInterpreter @@ -769,12 +778,86 @@ void PythonSettings::addInterpreter(const Interpreter &interpreter, bool isDefau saveSettings(); } +Interpreter PythonSettings::addInterpreter(const FilePath &interpreterPath, + bool isDefault, + const QString &nameSuffix) +{ + const Interpreter interpreter = createInterpreter(interpreterPath, {}, nameSuffix); + addInterpreter(interpreter, isDefault); + return interpreter; +} + PythonSettings *PythonSettings::instance() { QTC_CHECK(settingsInstance); return settingsInstance; } +void PythonSettings::createVirtualEnvironmentInteractive( + const FilePath &startDirectory, + const Interpreter &defaultInterpreter, + const std::function)> &callback) +{ + QDialog dialog; + dialog.setModal(true); + auto layout = new QFormLayout(&dialog); + auto interpreters = new QComboBox; + const QString preselectedId = defaultInterpreter.id.isEmpty() + ? PythonSettings::defaultInterpreter().id + : defaultInterpreter.id; + for (const Interpreter &interpreter : PythonSettings::interpreters()) { + interpreters->addItem(interpreter.name, interpreter.id); + if (!preselectedId.isEmpty() && interpreter.id == preselectedId) + interpreters->setCurrentIndex(interpreters->count() - 1); + } + layout->addRow(Tr::tr("Python Interpreter"), interpreters); + auto pathChooser = new PathChooser(); + pathChooser->setInitialBrowsePathBackup(startDirectory); + pathChooser->setExpectedKind(PathChooser::Directory); + pathChooser->setPromptDialogTitle(Tr::tr("New Python Virtual Environment Directory")); + layout->addRow(Tr::tr("Virtual Environment Directory"), pathChooser); + auto buttons = new QDialogButtonBox(QDialogButtonBox::Cancel); + auto createButton = buttons->addButton(Tr::tr("Create"), QDialogButtonBox::AcceptRole); + createButton->setEnabled(false); + connect(pathChooser, + &PathChooser::validChanged, + createButton, + [createButton](bool valid) { createButton->setEnabled(valid); }); + connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + layout->addRow(buttons); + dialog.setLayout(layout); + if (dialog.exec() == QDialog::Rejected) { + callback({}); + return; + } + + const Interpreter interpreter = PythonSettings::interpreter( + interpreters->currentData().toString()); + + auto venvDir = pathChooser->filePath(); + createVirtualEnvironment(venvDir, interpreter, callback); +} + +void PythonSettings::createVirtualEnvironment( + const FilePath &directory, + const Interpreter &interpreter, + const std::function)> &callback, + const QString &nameSuffix) +{ + createVenv(interpreter.command, directory, [directory, callback, nameSuffix](bool success) { + std::optional result; + if (success) { + FilePath venvPython = directory.osType() == Utils::OsTypeWindows ? directory / "Scripts" + : directory / "bin"; + venvPython = venvPython.pathAppended("python").withExecutableSuffix(); + if (venvPython.exists()) + result = PythonSettings::addInterpreter(venvPython, false, nameSuffix); + } + callback(result); + }); +} + QList PythonSettings::detectPythonVenvs(const FilePath &path) { QList result; diff --git a/src/plugins/python/pythonsettings.h b/src/plugins/python/pythonsettings.h index 693c7322085..35939c1ecd1 100644 --- a/src/plugins/python/pythonsettings.h +++ b/src/plugins/python/pythonsettings.h @@ -24,12 +24,23 @@ public: static Interpreter interpreter(const QString &interpreterId); static void setInterpreter(const QList &interpreters, const QString &defaultId); static void addInterpreter(const Interpreter &interpreter, bool isDefault = false); + static Interpreter addInterpreter(const Utils::FilePath &interpreterPath, + bool isDefault = false, + const QString &nameSuffix = {}); static void setPyLSConfiguration(const QString &configuration); static bool pylsEnabled(); static void setPylsEnabled(const bool &enabled); static QString pylsConfiguration(); static PythonSettings *instance(); - + static void createVirtualEnvironmentInteractive( + const Utils::FilePath &startDirectory, + const Interpreter &defaultInterpreter, + const std::function)> &callback); + static void createVirtualEnvironment( + const Utils::FilePath &directory, + const Interpreter &interpreter, + const std::function)> &callback, + const QString &nameSuffix = {}); static QList detectPythonVenvs(const Utils::FilePath &path); signals: diff --git a/src/plugins/python/pythonutils.cpp b/src/plugins/python/pythonutils.cpp index 346c42d183d..43d23a256bc 100644 --- a/src/plugins/python/pythonutils.cpp +++ b/src/plugins/python/pythonutils.cpp @@ -8,14 +8,15 @@ #include "pythontr.h" #include +#include #include -#include +#include #include #include #include -#include +#include using namespace ProjectExplorer; using namespace Utils; @@ -31,11 +32,11 @@ static QHash &userDefinedPythonsForDocument() FilePath detectPython(const FilePath &documentPath) { Project *project = documentPath.isEmpty() ? nullptr - : SessionManager::projectForFile(documentPath); + : ProjectManager::projectForFile(documentPath); if (!project) - project = SessionManager::startupProject(); + project = ProjectManager::startupProject(); - Environment env = Environment::systemEnvironment(); + FilePaths dirs = Environment::systemEnvironment().path(); if (project) { if (auto target = project->activeTarget()) { @@ -43,7 +44,7 @@ FilePath detectPython(const FilePath &documentPath) if (auto interpreter = runConfig->aspect()) return interpreter->currentInterpreter().command; if (auto environmentAspect = runConfig->aspect()) - env = environmentAspect->environment(); + dirs = environmentAspect->environment().path(); } } } @@ -61,8 +62,9 @@ FilePath detectPython(const FilePath &documentPath) if (defaultInterpreter.exists()) return defaultInterpreter; - auto pythonFromPath = [=](const QString toCheck) { - for (const FilePath &python : env.findAllInPath(toCheck)) { + auto pythonFromPath = [dirs](const FilePath &toCheck) { + const FilePaths found = toCheck.searchAllInDirectories(dirs); + for (const FilePath &python : found) { // Windows creates empty redirector files that may interfere if (python.exists() && python.osType() == OsTypeWindows && python.fileSize() != 0) return python; @@ -105,9 +107,11 @@ static QStringList replImportArgs(const FilePath &pythonFile, ReplType type) void openPythonRepl(QObject *parent, const FilePath &file, ReplType type) { + Q_UNUSED(parent) + static const auto workingDir = [](const FilePath &file) { if (file.isEmpty()) { - if (Project *project = SessionManager::startupProject()) + if (Project *project = ProjectManager::startupProject()) return project->projectDirectory(); return FilePath::currentWorkingPath(); } @@ -115,23 +119,21 @@ void openPythonRepl(QObject *parent, const FilePath &file, ReplType type) }; const auto args = QStringList{"-i"} + replImportArgs(file, type); - auto process = new QtcProcess(parent); - process->setTerminalMode(TerminalMode::On); const FilePath pythonCommand = detectPython(file); - process->setCommand({pythonCommand, args}); - process->setWorkingDirectory(workingDir(file)); - const QString commandLine = process->commandLine().toUserOutput(); - QObject::connect(process, &QtcProcess::done, process, [process, commandLine] { - if (process->error() != QProcess::UnknownError) { - Core::MessageManager::writeDisrupting(Tr::tr( - (process->error() == QProcess::FailedToStart) - ? "Failed to run Python (%1): \"%2\"." - : "Error while running Python (%1): \"%2\".") - .arg(commandLine, process->errorString())); - } - process->deleteLater(); - }); - process->start(); + + Process process; + process.setCommand({pythonCommand, args}); + process.setWorkingDirectory(workingDir(file)); + process.setTerminalMode(TerminalMode::Detached); + process.start(); + + if (process.error() != QProcess::UnknownError) { + Core::MessageManager::writeDisrupting( + Tr::tr((process.error() == QProcess::FailedToStart) + ? "Failed to run Python (%1): \"%2\"." + : "Error while running Python (%1): \"%2\".") + .arg(process.commandLine().toUserOutput(), process.errorString())); + } } QString pythonName(const FilePath &pythonPath) @@ -141,7 +143,7 @@ QString pythonName(const FilePath &pythonPath) return {}; QString name = nameForPython.value(pythonPath); if (name.isEmpty()) { - QtcProcess pythonProcess; + Process pythonProcess; pythonProcess.setTimeoutS(2); pythonProcess.setCommand({pythonPath, {"--version"}}); pythonProcess.runBlocking(); @@ -155,7 +157,7 @@ QString pythonName(const FilePath &pythonPath) PythonProject *pythonProjectForFile(const FilePath &pythonFile) { - for (Project *project : SessionManager::projects()) { + for (Project *project : ProjectManager::projects()) { if (auto pythonProject = qobject_cast(project)) { if (pythonProject->isKnownFile(pythonFile)) return pythonProject; @@ -164,4 +166,24 @@ PythonProject *pythonProjectForFile(const FilePath &pythonFile) return nullptr; } +void createVenv(const Utils::FilePath &python, + const Utils::FilePath &venvPath, + const std::function &callback) +{ + QTC_ASSERT(python.isExecutableFile(), callback(false); return); + QTC_ASSERT(!venvPath.exists() || venvPath.isDir(), callback(false); return); + + const CommandLine command(python, QStringList{"-m", "venv", venvPath.toUserOutput()}); + + auto process = new Process; + auto progress = new Core::ProcessProgress(process); + progress->setDisplayName(Tr::tr("Create Python venv")); + QObject::connect(process, &Process::done, [process, callback](){ + callback(process->result() == ProcessResult::FinishedWithSuccess); + process->deleteLater(); + }); + process->setCommand(command); + process->start(); +} + } // Python::Internal diff --git a/src/plugins/python/pythonutils.h b/src/plugins/python/pythonutils.h index 8d5b06974ef..f3e685b4ae0 100644 --- a/src/plugins/python/pythonutils.h +++ b/src/plugins/python/pythonutils.h @@ -16,4 +16,8 @@ QString pythonName(const Utils::FilePath &pythonPath); class PythonProject; PythonProject *pythonProjectForFile(const Utils::FilePath &pythonFile); +void createVenv(const Utils::FilePath &python, + const Utils::FilePath &venvPath, + const std::function &callback); + } // Python::Internal diff --git a/src/plugins/python/pythonwizardpage.cpp b/src/plugins/python/pythonwizardpage.cpp new file mode 100644 index 00000000000..bcbca5a7072 --- /dev/null +++ b/src/plugins/python/pythonwizardpage.cpp @@ -0,0 +1,219 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "pythonwizardpage.h" + +#include "pythonconstants.h" +#include "pythonsettings.h" +#include "pythontr.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include + +using namespace ProjectExplorer; +using namespace Utils; + +namespace Python::Internal { + +PythonWizardPageFactory::PythonWizardPageFactory() +{ + setTypeIdsSuffix("PythonConfiguration"); +} + +WizardPage *PythonWizardPageFactory::create(JsonWizard *wizard, Id typeId, const QVariant &data) +{ + Q_UNUSED(wizard) + + QTC_ASSERT(canCreate(typeId), return nullptr); + + QList> pySideAndData; + for (const QVariant &item : data.toMap().value("items").toList()) { + const QMap map = item.toMap(); + const QVariant name = map.value("trKey"); + if (name.isValid()) + pySideAndData.emplaceBack(QPair{name.toString(), map.value("value")}); + } + bool validIndex = false; + int defaultPySide = data.toMap().value("index").toInt(&validIndex); + if (!validIndex) + defaultPySide = -1; + return new PythonWizardPage(pySideAndData, defaultPySide); +} + +static bool validItem(const QVariant &item) +{ + QMap map = item.toMap(); + if (!map.value("trKey").canConvert()) + return false; + map = map.value("value").toMap(); + return map.value("PySideVersion").canConvert(); +} + +bool PythonWizardPageFactory::validateData(Id typeId, const QVariant &data, QString *errorMessage) +{ + QTC_ASSERT(canCreate(typeId), return false); + const QList items = data.toMap().value("items").toList(); + + if (items.isEmpty()) { + if (errorMessage) { + *errorMessage = Tr::tr("'data' of a Python wizard page expects a map with 'items' " + "containing a list of objects"); + } + return false; + } + + if (!Utils::allOf(items, &validItem)) { + if (errorMessage) { + *errorMessage = Tr::tr( + "An item of Python wizard page data expects a 'trKey' field containing the ui " + "visible string for that python version and an field 'value' containing an object " + "with a 'PySideVersion' field used for import statements in the python files."); + } + return false; + } + return true; +} + +PythonWizardPage::PythonWizardPage(const QList> &pySideAndData, + const int defaultPyside) +{ + using namespace Layouting; + m_interpreter.setSettingsDialogId(Constants::C_PYTHONOPTIONS_PAGE_ID); + connect(PythonSettings::instance(), + &PythonSettings::interpretersChanged, + this, + &PythonWizardPage::updateInterpreters); + + m_pySideVersion.setLabelText(Tr::tr("PySide version")); + m_pySideVersion.setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox); + for (auto [name, data] : pySideAndData) + m_pySideVersion.addOption(SelectionAspect::Option(name, {}, data)); + if (defaultPyside >= 0) + m_pySideVersion.setDefaultValue(defaultPyside); + + m_createVenv.setLabelText(Tr::tr("Create new Virtual Environment")); + + m_venvPath.setLabelText(Tr::tr("Path to virtual environment")); + m_venvPath.setEnabler(&m_createVenv); + m_venvPath.setExpectedKind(PathChooser::Directory); + + m_stateLabel = new InfoLabel(); + m_stateLabel->setWordWrap(true); + m_stateLabel->setFilled(true); + m_stateLabel->setType(InfoLabel::Error); + connect(&m_venvPath, &StringAspect::valueChanged, this, &PythonWizardPage::updateStateLabel); + connect(&m_createVenv, &BoolAspect::valueChanged, this, &PythonWizardPage::updateStateLabel); + + Grid { + m_pySideVersion, br, + m_interpreter, br, + m_createVenv, br, + m_venvPath, br, + m_stateLabel, br, + noMargin + }.attachTo(this); +} + +void PythonWizardPage::initializePage() +{ + auto wiz = qobject_cast(wizard()); + QTC_ASSERT(wiz, return); + connect(wiz, &JsonWizard::filesPolished, + this, &PythonWizardPage::setupProject, + Qt::UniqueConnection); + + const FilePath projectDir = FilePath::fromString(wiz->property("ProjectDirectory").toString()); + m_createVenv.setValue(!projectDir.isEmpty()); + if (m_venvPath.filePath().isEmpty()) + m_venvPath.setFilePath(projectDir.isEmpty() ? FilePath{} : projectDir / "venv"); + + updateInterpreters(); + updateStateLabel(); +} + +bool PythonWizardPage::validatePage() +{ + if (m_createVenv.value() && !m_venvPath.pathChooser()->isValid()) + return false; + auto wiz = qobject_cast(wizard()); + const QMap data = m_pySideVersion.itemValue().toMap(); + for (auto it = data.begin(), end = data.end(); it != end; ++it) + wiz->setValue(it.key(), it.value()); + return true; +} + +void PythonWizardPage::setupProject(const JsonWizard::GeneratorFiles &files) +{ + for (const JsonWizard::GeneratorFile &f : files) { + if (f.file.attributes() & Core::GeneratedFile::OpenProjectAttribute) { + Interpreter interpreter = m_interpreter.currentInterpreter(); + Project *project = ProjectManager::openProject(Utils::mimeTypeForFile(f.file.filePath()), + f.file.filePath().absoluteFilePath()); + if (m_createVenv.value()) { + auto openProjectWithInterpreter = [f](const std::optional &interpreter) { + if (!interpreter) + return; + Project *project = ProjectManager::projectWithProjectFilePath(f.file.filePath()); + if (!project) + return; + if (Target *target = project->activeTarget()) { + if (RunConfiguration *rc = target->activeRunConfiguration()) { + if (auto interpreters = rc->aspect()) + interpreters->setCurrentInterpreter(*interpreter); + } + } + }; + PythonSettings::createVirtualEnvironment(m_venvPath.filePath(), + interpreter, + openProjectWithInterpreter, + project ? project->displayName() + : QString{}); + } + + if (project) { + project->addTargetForDefaultKit(); + if (Target *target = project->activeTarget()) { + if (RunConfiguration *rc = target->activeRunConfiguration()) { + if (auto interpreters = rc->aspect()) { + interpreters->setCurrentInterpreter(interpreter); + project->saveSettings(); + } + } + } + delete project; + } + } + } +} + +void PythonWizardPage::updateInterpreters() +{ + m_interpreter.setDefaultInterpreter(PythonSettings::defaultInterpreter()); + m_interpreter.updateInterpreters(PythonSettings::interpreters()); +} + +void PythonWizardPage::updateStateLabel() +{ + QTC_ASSERT(m_stateLabel, return); + if (m_createVenv.value()) { + if (PathChooser *pathChooser = m_venvPath.pathChooser()) { + if (!pathChooser->isValid()) { + m_stateLabel->show(); + m_stateLabel->setText(pathChooser->errorMessage()); + return; + } + } + } + m_stateLabel->hide(); +} + +} // namespace Python::Internal + diff --git a/src/plugins/python/pythonwizardpage.h b/src/plugins/python/pythonwizardpage.h new file mode 100644 index 00000000000..6cf8a130c59 --- /dev/null +++ b/src/plugins/python/pythonwizardpage.h @@ -0,0 +1,43 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include +#include +#include + +#include +#include + +namespace Python::Internal { + +class PythonWizardPageFactory : public ProjectExplorer::JsonWizardPageFactory +{ +public: + PythonWizardPageFactory(); + + Utils::WizardPage *create(ProjectExplorer::JsonWizard *wizard, + Utils::Id typeId, + const QVariant &data) override; + bool validateData(Utils::Id typeId, const QVariant &data, QString *errorMessage) override; +}; + +class PythonWizardPage : public Utils::WizardPage +{ +public: + PythonWizardPage(const QList> &pySideAndData, const int defaultPyside); + void initializePage() override; + bool validatePage() override; + +private: + void setupProject(const ProjectExplorer::JsonWizard::GeneratorFiles &files); + void updateInterpreters(); + void updateStateLabel(); + + ProjectExplorer::InterpreterAspect m_interpreter; + Utils::SelectionAspect m_pySideVersion; + Utils::BoolAspect m_createVenv; + Utils::FilePathAspect m_venvPath; + Utils::InfoLabel *m_stateLabel = nullptr; +}; + +} // namespace Python::Internal diff --git a/src/plugins/qbsprojectmanager/customqbspropertiesdialog.cpp b/src/plugins/qbsprojectmanager/customqbspropertiesdialog.cpp index 4f3d88907ad..78e0634bc5f 100644 --- a/src/plugins/qbsprojectmanager/customqbspropertiesdialog.cpp +++ b/src/plugins/qbsprojectmanager/customqbspropertiesdialog.cpp @@ -43,7 +43,7 @@ CustomQbsPropertiesDialog::CustomQbsPropertiesDialog(const QVariantMap &properti m_removeButton = new QPushButton(Tr::tr("&Remove")); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - using namespace Utils::Layouting; + using namespace Layouting; Column { Row { m_propertiesTable, diff --git a/src/plugins/qbsprojectmanager/defaultpropertyprovider.cpp b/src/plugins/qbsprojectmanager/defaultpropertyprovider.cpp index 46433eca5a4..e2d1ae9ee7b 100644 --- a/src/plugins/qbsprojectmanager/defaultpropertyprovider.cpp +++ b/src/plugins/qbsprojectmanager/defaultpropertyprovider.cpp @@ -107,7 +107,7 @@ static QStringList toolchainList(const ProjectExplorer::ToolChain *tc) const Utils::Id type = tc->typeId(); if (type == ProjectExplorer::Constants::CLANG_TOOLCHAIN_TYPEID || (type == Android::Constants::ANDROID_TOOLCHAIN_TYPEID - && tc->compilerCommand().toString().contains("clang"))) { + && tc->compilerCommand().fileName().contains("clang"))) { return {"clang", "llvm", "gcc"}; } if (type == ProjectExplorer::Constants::GCC_TOOLCHAIN_TYPEID @@ -156,7 +156,7 @@ static QString architecture(const ProjectExplorer::Abi &targetAbi) switch (targetAbi.architecture()) { case ProjectExplorer::Abi::X86Architecture: architecture.append(QLatin1Char('_')); - // fall through + [[fallthrough]]; case ProjectExplorer::Abi::ArmArchitecture: // ARM sub-architectures are currently not handled, which is kind of problematic case ProjectExplorer::Abi::MipsArchitecture: diff --git a/src/plugins/qbsprojectmanager/qbsbuildconfiguration.cpp b/src/plugins/qbsprojectmanager/qbsbuildconfiguration.cpp index c5d4b62a0d0..41ae75d8b9b 100644 --- a/src/plugins/qbsprojectmanager/qbsbuildconfiguration.cpp +++ b/src/plugins/qbsprojectmanager/qbsbuildconfiguration.cpp @@ -27,8 +27,8 @@ #include +#include #include -#include #include #include diff --git a/src/plugins/qbsprojectmanager/qbsbuildstep.cpp b/src/plugins/qbsprojectmanager/qbsbuildstep.cpp index f8fad01ff70..afb94652416 100644 --- a/src/plugins/qbsprojectmanager/qbsbuildstep.cpp +++ b/src/plugins/qbsprojectmanager/qbsbuildstep.cpp @@ -28,8 +28,8 @@ #include #include #include +#include #include -#include #include #include @@ -63,7 +63,7 @@ public: ArchitecturesAspect(); void setKit(const ProjectExplorer::Kit *kit) { m_kit = kit; } - void addToLayout(Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; QStringList selectedArchitectures() const; void setSelectedArchitectures(const QStringList& architectures); bool isManagedByTarget() const { return m_isManagedByTarget; } @@ -86,9 +86,9 @@ ArchitecturesAspect::ArchitecturesAspect() setAllValues(m_abisToArchMap.keys()); } -void ArchitecturesAspect::addToLayout(Layouting::LayoutBuilder &builder) +void ArchitecturesAspect::addToLayout(Layouting::LayoutItem &parent) { - MultiSelectionAspect::addToLayout(builder); + MultiSelectionAspect::addToLayout(parent); const auto changeHandler = [this] { const QtVersion *qtVersion = QtKitAspect::qtVersion(m_kit); if (!qtVersion) { @@ -224,7 +224,7 @@ QbsBuildStep::QbsBuildStep(BuildStepList *bsl, Utils::Id id) : m_keepGoing->setToolTip( QbsProjectManager::Tr::tr("Keep going when errors occur (if at all possible).")); m_keepGoing->setLabel(QbsProjectManager::Tr::tr("Keep going"), - BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel); + BoolAspect::LabelPlacement::AtCheckBox); m_maxJobCount = addAspect(); m_maxJobCount->setSettingsKey(QBS_MAXJOBCOUNT); @@ -235,22 +235,22 @@ QbsBuildStep::QbsBuildStep(BuildStepList *bsl, Utils::Id id) : m_showCommandLines = addAspect(); m_showCommandLines->setSettingsKey(QBS_SHOWCOMMANDLINES); m_showCommandLines->setLabel(QbsProjectManager::Tr::tr("Show command lines"), - BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel); + BoolAspect::LabelPlacement::AtCheckBox); m_install = addAspect(); m_install->setSettingsKey(QBS_INSTALL); m_install->setValue(true); - m_install->setLabel(QbsProjectManager::Tr::tr("Install"), BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel); + m_install->setLabel(QbsProjectManager::Tr::tr("Install"), BoolAspect::LabelPlacement::AtCheckBox); m_cleanInstallDir = addAspect(); m_cleanInstallDir->setSettingsKey(QBS_CLEAN_INSTALL_ROOT); m_cleanInstallDir->setLabel(QbsProjectManager::Tr::tr("Clean install root"), - BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel); + BoolAspect::LabelPlacement::AtCheckBox); m_forceProbes = addAspect(); m_forceProbes->setSettingsKey("Qbs.forceProbesKey"); m_forceProbes->setLabel(QbsProjectManager::Tr::tr("Force probes"), - BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel); + BoolAspect::LabelPlacement::AtCheckBox); m_commandLine = addAspect(); m_commandLine->setDisplayStyle(StringAspect::TextEditDisplay); @@ -658,25 +658,27 @@ QbsBuildStepConfigWidget::QbsBuildStepConfigWidget(QbsBuildStep *step) installDirChooser = new PathChooser(this); installDirChooser->setExpectedKind(PathChooser::Directory); - Layouting::Form builder; - builder.addRow(m_qbsStep->m_buildVariant); - builder.addRow(m_qbsStep->m_selectedAbis); - builder.addRow(m_qbsStep->m_maxJobCount); - builder.addRow({QbsProjectManager::Tr::tr("Properties:"), propertyEdit}); + using namespace Layouting; + Form { + m_qbsStep->m_buildVariant, br, + m_qbsStep->m_selectedAbis, br, + m_qbsStep->m_maxJobCount, br, + QbsProjectManager::Tr::tr("Properties:"), propertyEdit, br, - builder.addRow(QbsProjectManager::Tr::tr("Flags:")); - m_qbsStep->m_keepGoing->addToLayout(builder); - m_qbsStep->m_showCommandLines->addToLayout(builder); - m_qbsStep->m_forceProbes->addToLayout(builder); + QbsProjectManager::Tr::tr("Flags:"), + m_qbsStep->m_keepGoing, + m_qbsStep->m_showCommandLines, + m_qbsStep->m_forceProbes, br, - builder.addRow(QbsProjectManager::Tr::tr("Installation flags:")); - m_qbsStep->m_install->addToLayout(builder); - m_qbsStep->m_cleanInstallDir->addToLayout(builder); - builder.addItem(defaultInstallDirCheckBox); + QbsProjectManager::Tr::tr("Installation flags:"), + m_qbsStep->m_install, + m_qbsStep->m_cleanInstallDir, + defaultInstallDirCheckBox, br, - builder.addRow({QbsProjectManager::Tr::tr("Installation directory:"), installDirChooser}); - builder.addRow(m_qbsStep->m_commandLine); - builder.attachTo(this, Layouting::WithoutMargins); + QbsProjectManager::Tr::tr("Installation directory:"), installDirChooser, br, + m_qbsStep->m_commandLine, br, + noMargin, + }.attachTo(this); propertyEdit->setToolTip(QbsProjectManager::Tr::tr("Properties to pass to the project.")); defaultInstallDirCheckBox->setText(QbsProjectManager::Tr::tr("Use default location")); diff --git a/src/plugins/qbsprojectmanager/qbsinstallstep.cpp b/src/plugins/qbsprojectmanager/qbsinstallstep.cpp index 27c875e8688..87fe3ae0f9c 100644 --- a/src/plugins/qbsprojectmanager/qbsinstallstep.cpp +++ b/src/plugins/qbsprojectmanager/qbsinstallstep.cpp @@ -48,7 +48,7 @@ QbsInstallStep::QbsInstallStep(BuildStepList *bsl, Utils::Id id) setDisplayName(Tr::tr("Qbs Install")); setSummaryText(Tr::tr("Qbs: %1").arg("install")); - const auto labelPlacement = BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel; + const auto labelPlacement = BoolAspect::LabelPlacement::AtCheckBox; m_dryRun = addAspect(); m_dryRun->setSettingsKey(QBS_DRY_RUN); m_dryRun->setLabel(Tr::tr("Dry run"), labelPlacement); @@ -81,7 +81,7 @@ void QbsInstallStep::doRun() QJsonObject request; request.insert("type", "install-project"); - request.insert("install-root", installRoot()); + request.insert("install-root", installRoot().path()); request.insert("clean-install-root", m_cleanInstallRoot->value()); request.insert("keep-going", m_keepGoing->value()); request.insert("dry-run", m_dryRun->value()); @@ -102,10 +102,10 @@ void QbsInstallStep::doCancel() m_session->cancelCurrentJob(); } -QString QbsInstallStep::installRoot() const +FilePath QbsInstallStep::installRoot() const { const QbsBuildStep * const bs = buildConfig()->qbsStep(); - return bs ? bs->installRoot().toString() : QString(); + return bs ? bs->installRoot() : FilePath(); } const QbsBuildConfiguration *QbsInstallStep::buildConfig() const @@ -162,7 +162,7 @@ QWidget *QbsInstallStep::createConfigWidget() { auto widget = new QWidget; - auto installRootValueLabel = new QLabel(installRoot()); + auto installRootValueLabel = new QLabel(installRoot().toUserOutput()); auto commandLineKeyLabel = new QLabel(Tr::tr("Equivalent command line:")); commandLineKeyLabel->setAlignment(Qt::AlignTop); @@ -172,18 +172,15 @@ QWidget *QbsInstallStep::createConfigWidget() commandLineTextEdit->setTextInteractionFlags(Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse); commandLineTextEdit->setMinimumHeight(QFontMetrics(widget->font()).height() * 8); - Layouting::Form builder; - builder.addRow({Tr::tr("Install root:"), installRootValueLabel}); - builder.addRow(Tr::tr("Flags:")); - m_dryRun->addToLayout(builder); - m_keepGoing->addToLayout(builder); - m_cleanInstallRoot->addToLayout(builder); - - builder.addRow({commandLineKeyLabel, commandLineTextEdit}); - builder.attachTo(widget); + using namespace Layouting; + Form { + Tr::tr("Install root:"), installRootValueLabel, br, + Tr::tr("Flags:"), m_dryRun, m_keepGoing, m_cleanInstallRoot, br, + commandLineKeyLabel, commandLineTextEdit + }.attachTo(widget); const auto updateState = [this, commandLineTextEdit, installRootValueLabel] { - installRootValueLabel->setText(installRoot()); + installRootValueLabel->setText(installRoot().toUserOutput()); commandLineTextEdit->setPlainText(buildConfig()->equivalentCommandLine(stepData())); }; diff --git a/src/plugins/qbsprojectmanager/qbsinstallstep.h b/src/plugins/qbsprojectmanager/qbsinstallstep.h index 7eef7aa9e14..e0063da8189 100644 --- a/src/plugins/qbsprojectmanager/qbsinstallstep.h +++ b/src/plugins/qbsprojectmanager/qbsinstallstep.h @@ -25,7 +25,7 @@ public: QbsInstallStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id); ~QbsInstallStep() override; - QString installRoot() const; + Utils::FilePath installRoot() const; QbsBuildStepData stepData() const; private: diff --git a/src/plugins/qbsprojectmanager/qbskitinformation.cpp b/src/plugins/qbsprojectmanager/qbskitinformation.cpp index 8bdd801261b..b9cb79231d9 100644 --- a/src/plugins/qbsprojectmanager/qbskitinformation.cpp +++ b/src/plugins/qbsprojectmanager/qbskitinformation.cpp @@ -35,11 +35,11 @@ private: void makeReadOnly() override { m_changeButton->setEnabled(false); } void refresh() override { m_contentLabel->setText(QbsKitAspect::representation(kit())); } - void addToLayout(Utils::Layouting::LayoutBuilder &builder) override + void addToLayout(Layouting::LayoutItem &parent) override { addMutableAction(m_contentLabel); - builder.addItem(m_contentLabel); - builder.addItem(m_changeButton); + parent.addItem(m_contentLabel); + parent.addItem(m_changeButton); } void changeProperties() diff --git a/src/plugins/qbsprojectmanager/qbsnodetreebuilder.cpp b/src/plugins/qbsprojectmanager/qbsnodetreebuilder.cpp index 55ae7d8ac03..82e896e77a8 100644 --- a/src/plugins/qbsprojectmanager/qbsnodetreebuilder.cpp +++ b/src/plugins/qbsprojectmanager/qbsnodetreebuilder.cpp @@ -42,6 +42,10 @@ static FileType fileType(const QJsonObject &artifact) return FileType::StateChart; if (fileTags.contains("qt.qml.qml")) return FileType::QML; + if (fileTags.contains("application")) + return FileType::App; + if (fileTags.contains("staticlibrary") || fileTags.contains("dynamiclibrary")) + return FileType::Lib; return FileType::Unknown; } diff --git a/src/plugins/qbsprojectmanager/qbsprofilemanager.cpp b/src/plugins/qbsprojectmanager/qbsprofilemanager.cpp index c5e658ba54c..2d952513ab8 100644 --- a/src/plugins/qbsprojectmanager/qbsprofilemanager.cpp +++ b/src/plugins/qbsprojectmanager/qbsprofilemanager.cpp @@ -18,8 +18,8 @@ #include #include #include +#include #include -#include #include #include @@ -223,7 +223,7 @@ QString QbsProfileManager::runQbsConfig(QbsConfigOp op, const QString &key, cons const Utils::FilePath qbsConfigExe = QbsSettings::qbsConfigFilePath(); if (qbsConfigExe.isEmpty() || !qbsConfigExe.exists()) return {}; - Utils::QtcProcess qbsConfig; + Utils::Process qbsConfig; qbsConfig.setCommand({qbsConfigExe, args}); qbsConfig.start(); if (!qbsConfig.waitForFinished(5000)) { diff --git a/src/plugins/qbsprojectmanager/qbsprofilessettingspage.cpp b/src/plugins/qbsprojectmanager/qbsprofilessettingspage.cpp index 64c73ed5f0e..fb048f94751 100644 --- a/src/plugins/qbsprojectmanager/qbsprofilessettingspage.cpp +++ b/src/plugins/qbsprojectmanager/qbsprofilessettingspage.cpp @@ -26,8 +26,7 @@ using namespace ProjectExplorer; -namespace QbsProjectManager { -namespace Internal { +namespace QbsProjectManager::Internal { class ProfileTreeItem : public Utils::TypedTreeItem { @@ -53,7 +52,6 @@ private: class ProfileModel : public Utils::TreeModel { - Q_OBJECT public: ProfileModel() : TreeModel(static_cast(nullptr)) { @@ -91,13 +89,14 @@ public: } }; -class QbsProfilesSettingsWidget : public QWidget +class QbsProfilesSettingsWidget : public Core::IOptionsPageWidget { - Q_OBJECT public: QbsProfilesSettingsWidget(); private: + void apply() final {} + void refreshKitsList(); void displayCurrentProfile(); @@ -112,19 +111,7 @@ QbsProfilesSettingsPage::QbsProfilesSettingsPage() setId("Y.QbsProfiles"); setDisplayName(Tr::tr("Profiles")); setCategory(Constants::QBS_SETTINGS_CATEGORY); -} - -QWidget *QbsProfilesSettingsPage::widget() -{ - if (!m_widget) - m_widget = new QbsProfilesSettingsWidget; - return m_widget; -} - -void QbsProfilesSettingsPage::finish() -{ - delete m_widget; - m_widget = nullptr; + setWidgetCreator([] { return new QbsProfilesSettingsWidget; }); } QbsProfilesSettingsWidget::QbsProfilesSettingsWidget() @@ -133,7 +120,7 @@ QbsProfilesSettingsWidget::QbsProfilesSettingsWidget() m_profileValueLabel = new QLabel; m_propertiesView = new QTreeView; - using namespace Utils::Layouting; + using namespace Layouting; Column { Form { Tr::tr("Kit:"), m_kitsComboBox, br, @@ -211,7 +198,4 @@ void QbsProfilesSettingsWidget::displayCurrentProfile() } } -} // namespace Internal -} // namespace QbsProjectManager - -#include "qbsprofilessettingspage.moc" +} // QbsProjectManager::Internal diff --git a/src/plugins/qbsprojectmanager/qbsprofilessettingspage.h b/src/plugins/qbsprojectmanager/qbsprofilessettingspage.h index 4ffab9bce9f..db63e323549 100644 --- a/src/plugins/qbsprojectmanager/qbsprofilessettingspage.h +++ b/src/plugins/qbsprojectmanager/qbsprofilessettingspage.h @@ -5,22 +5,12 @@ #include -namespace QbsProjectManager { -namespace Internal { -class QbsProfilesSettingsWidget; +namespace QbsProjectManager::Internal { class QbsProfilesSettingsPage : public Core::IOptionsPage { public: QbsProfilesSettingsPage(); - -private: - QWidget *widget() override; - void apply() override { } - void finish() override; - - QbsProfilesSettingsWidget *m_widget = nullptr; }; -} // namespace Internal -} // namespace QbsProjectManager +} // QbsProjectManager::Internal diff --git a/src/plugins/qbsprojectmanager/qbsproject.cpp b/src/plugins/qbsprojectmanager/qbsproject.cpp index dc72660e7a3..3b59618310c 100644 --- a/src/plugins/qbsprojectmanager/qbsproject.cpp +++ b/src/plugins/qbsprojectmanager/qbsproject.cpp @@ -41,10 +41,10 @@ #include #include #include +#include #include #include #include -#include #include #include #include @@ -483,7 +483,7 @@ void QbsBuildSystem::updateProjectNodes(const std::function &continuati if (continuation) continuation(); }); - m_treeCreationWatcher->setFuture(runAsync(ProjectExplorerPlugin::sharedThreadPool(), + m_treeCreationWatcher->setFuture(Utils::asyncRun(ProjectExplorerPlugin::sharedThreadPool(), QThread::LowPriority, &QbsNodeTreeBuilder::buildTree, project()->displayName(), project()->projectFilePath(), project()->projectDirectory(), projectData())); @@ -498,7 +498,7 @@ FilePath QbsBuildSystem::installRoot() if (!step->enabled()) continue; if (const auto qbsInstallStep = qobject_cast(step)) - return FilePath::fromUserInput(qbsInstallStep->installRoot()); + return qbsInstallStep->installRoot(); } } const QbsBuildStep * const buildStep = m_buildConfiguration->qbsStep(); @@ -744,6 +744,7 @@ static void getExpandedCompilerFlags(QStringList &cFlags, QStringList &cxxFlags, }; const QJsonValue &enableExceptions = getCppProp("enableExceptions"); const QJsonValue &enableRtti = getCppProp("enableRtti"); + const QString warningLevel = getCppProp("warningLevel").toString(); QStringList commonFlags = arrayToStringList(getCppProp("platformCommonCompilerFlags")); commonFlags << arrayToStringList(getCppProp("commonCompilerFlags")) << arrayToStringList(getCppProp("platformDriverFlags")) @@ -773,6 +774,10 @@ static void getExpandedCompilerFlags(QStringList &cFlags, QStringList &cxxFlags, if (!machineType.isEmpty()) commonFlags << ("-march=" + machineType); } + if (warningLevel == "all") + commonFlags << "-Wall" << "-Wextra"; + else if (warningLevel == "none") + commonFlags << "-w"; const QStringList targetOS = arrayToStringList(properties.value("qbs.targetOS")); if (targetOS.contains("unix")) { const QVariant positionIndependentCode = getCppProp("positionIndependentCode"); @@ -837,6 +842,10 @@ static void getExpandedCompilerFlags(QStringList &cFlags, QStringList &cxxFlags, else if (exceptionModel == "externc") commonFlags << "/EHs"; } + if (warningLevel == "all") + commonFlags << "/Wall"; + else if (warningLevel == "none") + commonFlags << "/w"; cFlags = cxxFlags = commonFlags; cFlags << "/TC"; cxxFlags << "/TP"; diff --git a/src/plugins/qbsprojectmanager/qbsprojectmanagerplugin.cpp b/src/plugins/qbsprojectmanager/qbsprojectmanagerplugin.cpp index 01a58753a20..3245b609bef 100644 --- a/src/plugins/qbsprojectmanager/qbsprojectmanagerplugin.cpp +++ b/src/plugins/qbsprojectmanager/qbsprojectmanagerplugin.cpp @@ -33,7 +33,7 @@ #include #include #include -#include +#include #include #include @@ -60,7 +60,7 @@ static Node *currentEditorNode() static QbsProject *currentEditorProject() { Core::IDocument *doc = Core::EditorManager::currentDocument(); - return doc ? qobject_cast(SessionManager::projectForFile(doc->filePath())) : nullptr; + return doc ? qobject_cast(ProjectManager::projectForFile(doc->filePath())) : nullptr; } class QbsProjectManagerPluginPrivate @@ -226,13 +226,13 @@ void QbsProjectManagerPlugin::initialize() connect(Core::EditorManager::instance(), &Core::EditorManager::currentEditorChanged, this, &QbsProjectManagerPlugin::updateBuildActions); - connect(SessionManager::instance(), &SessionManager::targetAdded, + connect(ProjectManager::instance(), &ProjectManager::targetAdded, this, &QbsProjectManagerPlugin::targetWasAdded); - connect(SessionManager::instance(), &SessionManager::targetRemoved, + connect(ProjectManager::instance(), &ProjectManager::targetRemoved, this, &QbsProjectManagerPlugin::updateBuildActions); - connect(SessionManager::instance(), &SessionManager::startupProjectChanged, + connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, this, &QbsProjectManagerPlugin::updateReparseQbsAction); - connect(SessionManager::instance(), &SessionManager::projectAdded, + connect(ProjectManager::instance(), &ProjectManager::projectAdded, this, [this](Project *project) { auto qbsProject = qobject_cast(project); connect(project, &Project::anyParsingStarted, @@ -283,7 +283,7 @@ void QbsProjectManagerPlugin::updateContextActions(Node *node) void QbsProjectManagerPlugin::updateReparseQbsAction() { - auto project = qobject_cast(SessionManager::startupProject()); + auto project = qobject_cast(ProjectManager::startupProject()); m_reparseQbs->setEnabled(project && !BuildManager::isBuilding(project) && project && project->activeTarget() @@ -342,7 +342,7 @@ void QbsProjectManagerPlugin::projectChanged(QbsProject *project) { auto qbsProject = qobject_cast(project); - if (!qbsProject || qbsProject == SessionManager::startupProject()) + if (!qbsProject || qbsProject == ProjectManager::startupProject()) updateReparseQbsAction(); if (!qbsProject || qbsProject == ProjectTree::currentProject()) @@ -537,7 +537,7 @@ void QbsProjectManagerPlugin::reparseSelectedProject() void QbsProjectManagerPlugin::reparseCurrentProject() { - reparseProject(dynamic_cast(SessionManager::startupProject())); + reparseProject(dynamic_cast(ProjectManager::startupProject())); } void QbsProjectManagerPlugin::reparseProject(QbsProject *project) diff --git a/src/plugins/qbsprojectmanager/qbsprojectparser.cpp b/src/plugins/qbsprojectmanager/qbsprojectparser.cpp index 1620b747ac3..ee1aafdb6b0 100644 --- a/src/plugins/qbsprojectmanager/qbsprojectparser.cpp +++ b/src/plugins/qbsprojectmanager/qbsprojectparser.cpp @@ -67,10 +67,10 @@ void QbsProjectParser::parse(const QVariantMap &config, const Environment &env, request.insert("override-build-graph-data", true); static const auto envToJson = [](const Environment &env) { QJsonObject envObj; - for (auto it = env.constBegin(); it != env.constEnd(); ++it) { - if (env.isEnabled(it)) - envObj.insert(env.key(it), env.value(it)); - } + env.forEachEntry([&](const QString &key, const QString &value, bool enabled) { + if (enabled) + envObj.insert(key, value); + }); return envObj; }; request.insert("environment", envToJson(env)); diff --git a/src/plugins/qbsprojectmanager/qbssession.cpp b/src/plugins/qbsprojectmanager/qbssession.cpp index 06c605e842d..13121cfb66e 100644 --- a/src/plugins/qbsprojectmanager/qbssession.cpp +++ b/src/plugins/qbsprojectmanager/qbssession.cpp @@ -14,8 +14,8 @@ #include #include #include +#include #include -#include #include #include @@ -129,7 +129,7 @@ private: class QbsSession::Private { public: - QtcProcess *qbsProcess = nullptr; + Process *qbsProcess = nullptr; PacketReader *packetReader = nullptr; QJsonObject currentRequest; QJsonObject projectData; @@ -152,16 +152,16 @@ void QbsSession::initialize() d->packetReader = new PacketReader(this); - d->qbsProcess = new QtcProcess(this); + d->qbsProcess = new Process(this); d->qbsProcess->setProcessMode(ProcessMode::Writer); d->qbsProcess->setEnvironment(env); - connect(d->qbsProcess, &QtcProcess::readyReadStandardOutput, this, [this] { + connect(d->qbsProcess, &Process::readyReadStandardOutput, this, [this] { d->packetReader->handleData(d->qbsProcess->readAllRawStandardOutput()); }); - connect(d->qbsProcess, &QtcProcess::readyReadStandardError, this, [this] { + connect(d->qbsProcess, &Process::readyReadStandardError, this, [this] { qCDebug(qbsPmLog) << "[qbs stderr]: " << d->qbsProcess->readAllRawStandardError(); }); - connect(d->qbsProcess, &QtcProcess::done, this, [this] { + connect(d->qbsProcess, &Process::done, this, [this] { if (d->qbsProcess->result() == ProcessResult::StartFailed) { d->eventLoop.exit(1); setError(Error::QbsFailedToStart); @@ -370,6 +370,7 @@ void QbsSession::insertRequestedModuleProperties(QJsonObject &request) "cpp.useCxxPrecompiledHeader", "cpp.useObjcPrecompiledHeader", "cpp.useObjcxxPrecompiledHeader", + "cpp.warningLevel", "qbs.architecture", "qbs.architectures", "qbs.sysroot", diff --git a/src/plugins/qbsprojectmanager/qbssettings.cpp b/src/plugins/qbsprojectmanager/qbssettings.cpp index 78eac2b308c..91e4763bf25 100644 --- a/src/plugins/qbsprojectmanager/qbssettings.cpp +++ b/src/plugins/qbsprojectmanager/qbssettings.cpp @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include @@ -21,8 +21,7 @@ using namespace Utils; -namespace QbsProjectManager { -namespace Internal { +namespace QbsProjectManager::Internal { const char QBS_EXE_KEY[] = "QbsProjectManager/QbsExecutable"; const char QBS_DEFAULT_INSTALL_DIR_KEY[] = "QbsProjectManager/DefaultInstallDir"; @@ -32,7 +31,7 @@ static QString getQbsVersion(const FilePath &qbsExe) { if (qbsExe.isEmpty() || !qbsExe.exists()) return {}; - QtcProcess qbsProc; + Process qbsProc; qbsProc.setCommand({qbsExe, {"--version"}}); qbsProc.start(); if (!qbsProc.waitForFinished(5000) || qbsProc.exitCode() != 0) @@ -142,10 +141,10 @@ void QbsSettings::storeSettings() const s->setValue(USE_CREATOR_SETTINGS_KEY, m_settings.useCreatorSettings); } -class QbsSettingsPage::SettingsWidget : public QWidget +class QbsSettingsPageWidget : public Core::IOptionsPageWidget { public: - SettingsWidget() + QbsSettingsPageWidget() { m_qbsExePathChooser.setExpectedKind(PathChooser::ExistingCommand); m_qbsExePathChooser.setFilePath(QbsSettings::qbsExecutableFilePath()); @@ -166,7 +165,7 @@ public: }); } - void apply() + void apply() final { QbsSettingsData settings = QbsSettings::rawSettingsData(); if (m_qbsExePathChooser.filePath() != QbsSettings::qbsExecutableFilePath()) @@ -197,26 +196,7 @@ QbsSettingsPage::QbsSettingsPage() setCategory(Constants::QBS_SETTINGS_CATEGORY); setDisplayCategory(Tr::tr(Constants::QBS_SETTINGS_TR_CATEGORY)); setCategoryIconPath(":/qbsprojectmanager/images/settingscategory_qbsprojectmanager.png"); + setWidgetCreator([] { return new QbsSettingsPageWidget; }); } -QWidget *QbsSettingsPage::widget() -{ - if (!m_widget) - m_widget = new SettingsWidget; - return m_widget; - -} - -void QbsSettingsPage::apply() -{ - if (m_widget) - m_widget->apply(); -} - -void QbsSettingsPage::finish() -{ - delete m_widget; -} - -} // namespace Internal -} // namespace QbsProjectManager +} // QbsProjectManager::Internal diff --git a/src/plugins/qbsprojectmanager/qbssettings.h b/src/plugins/qbsprojectmanager/qbssettings.h index 8b30e4b247e..a91a9059653 100644 --- a/src/plugins/qbsprojectmanager/qbssettings.h +++ b/src/plugins/qbsprojectmanager/qbssettings.h @@ -4,16 +4,15 @@ #pragma once #include -#include -#include -#include +#include + #include -namespace QbsProjectManager { -namespace Internal { +namespace QbsProjectManager::Internal { -class QbsSettingsData { +class QbsSettingsData +{ public: Utils::FilePath qbsExecutableFilePath; QString defaultInstallDirTemplate; @@ -51,18 +50,8 @@ private: class QbsSettingsPage : public Core::IOptionsPage { - Q_OBJECT public: QbsSettingsPage(); - -private: - QWidget *widget() override; - void apply() override; - void finish() override; - - class SettingsWidget; - QPointer m_widget; }; -} // namespace Internal -} // namespace QbsProjectManager +} // QbsProjectManager::Internal diff --git a/src/plugins/qmakeprojectmanager/addlibrarywizard.cpp b/src/plugins/qmakeprojectmanager/addlibrarywizard.cpp index ebf266d701b..ff74701cf71 100644 --- a/src/plugins/qmakeprojectmanager/addlibrarywizard.cpp +++ b/src/plugins/qmakeprojectmanager/addlibrarywizard.cpp @@ -178,7 +178,6 @@ DetailsPage::DetailsPage(AddLibraryWizard *parent) : QWizardPage(parent), m_libraryWizard(parent) { m_libraryDetailsWidget = new LibraryDetailsWidget(this); - resize(456, 438); PathChooser * const libPathChooser = m_libraryDetailsWidget->libraryPathChooser; libPathChooser->setHistoryCompleter("Qmake.LibDir.History"); diff --git a/src/plugins/qmakeprojectmanager/customwidgetwizard/classdefinition.cpp b/src/plugins/qmakeprojectmanager/customwidgetwizard/classdefinition.cpp index 05c7c25d78d..22958e08197 100644 --- a/src/plugins/qmakeprojectmanager/customwidgetwizard/classdefinition.cpp +++ b/src/plugins/qmakeprojectmanager/customwidgetwizard/classdefinition.cpp @@ -21,7 +21,7 @@ ClassDefinition::ClassDefinition(QWidget *parent) : QTabWidget(parent), m_domXmlChanged(false) { - using namespace Utils::Layouting; + using namespace Layouting; // "Sources" tab auto sourceTab = new QWidget; diff --git a/src/plugins/qmakeprojectmanager/customwidgetwizard/customwidgetpluginwizardpage.cpp b/src/plugins/qmakeprojectmanager/customwidgetwizard/customwidgetpluginwizardpage.cpp index 851f07ce71d..d0885dfc839 100644 --- a/src/plugins/qmakeprojectmanager/customwidgetwizard/customwidgetpluginwizardpage.cpp +++ b/src/plugins/qmakeprojectmanager/customwidgetwizard/customwidgetpluginwizardpage.cpp @@ -34,7 +34,7 @@ CustomWidgetPluginWizardPage::CustomWidgetPluginWizardPage(QWidget *parent) : m_pluginNameEdit = new QLineEdit; m_resourceFileEdit = new QLineEdit(Tr::tr("icons.qrc")); - using namespace Utils::Layouting; + using namespace Layouting; Column { Tr::tr("Specify the properties of the plugin library and the collection class."), Space(10), diff --git a/src/plugins/qmakeprojectmanager/customwidgetwizard/customwidgetwidgetswizardpage.cpp b/src/plugins/qmakeprojectmanager/customwidgetwizard/customwidgetwidgetswizardpage.cpp index 8265f0a8b47..20fe10ac13e 100644 --- a/src/plugins/qmakeprojectmanager/customwidgetwizard/customwidgetwidgetswizardpage.cpp +++ b/src/plugins/qmakeprojectmanager/customwidgetwizard/customwidgetwidgetswizardpage.cpp @@ -39,7 +39,7 @@ CustomWidgetWidgetsWizardPage::CustomWidgetWidgetsWizardPage(QWidget *parent) : dummy->setEnabled(false); m_tabStackLayout->addWidget(dummy); - using namespace Utils::Layouting; + using namespace Layouting; Column { Tr::tr("Specify the list of custom widgets and their properties."), Space(10), diff --git a/src/plugins/qmakeprojectmanager/customwidgetwizard/plugingenerator.cpp b/src/plugins/qmakeprojectmanager/customwidgetwizard/plugingenerator.cpp index ab3f59d8518..28e67ec3ece 100644 --- a/src/plugins/qmakeprojectmanager/customwidgetwizard/plugingenerator.cpp +++ b/src/plugins/qmakeprojectmanager/customwidgetwizard/plugingenerator.cpp @@ -49,17 +49,10 @@ static Core::GeneratedFile generateIconFile(const FilePath &source, return rc; } -static QString qt4PluginExport(const QString &pluginName, const QString &pluginClassName) -{ - return QLatin1String("#if QT_VERSION < 0x050000\nQ_EXPORT_PLUGIN2(") - + pluginName + QLatin1String(", ") + pluginClassName - + QLatin1String(")\n#endif // QT_VERSION < 0x050000"); -} - static QString qt5PluginMetaData(const QString &interfaceName) { - return QLatin1String("#if QT_VERSION >= 0x050000\n Q_PLUGIN_METADATA(IID \"org.qt-project.Qt.") - + interfaceName + QLatin1String("\")\n#endif // QT_VERSION >= 0x050000"); + return QLatin1String(" Q_PLUGIN_METADATA(IID \"org.qt-project.Qt.") + + interfaceName + QLatin1String("\")"); } QList PluginGenerator::generatePlugin(const GenerationParameters& p, const PluginOptions &options, @@ -121,10 +114,8 @@ QList PluginGenerator::generatePlugin(const GenerationPara sm.insert(QLatin1String("WIDGET_TOOLTIP"), cStringQuote(wo.toolTip)); sm.insert(QLatin1String("WIDGET_WHATSTHIS"), cStringQuote(wo.whatsThis)); sm.insert(QLatin1String("WIDGET_ISCONTAINER"), wo.isContainer ? QLatin1String("true") : QLatin1String("false")); - sm.insert(QLatin1String("WIDGET_DOMXML"), cStringQuote(wo.domXml)); - sm.insert(QLatin1String("SINGLE_PLUGIN_EXPORT"), - options.widgetOptions.count() == 1 ? - qt4PluginExport(options.pluginName, wo.pluginClassName) : QString()); + sm.insert(QLatin1String("WIDGET_DOMXML"), QLatin1String("R\"(") + + wo.domXml.trimmed() + QLatin1String(")\"")); const QString pluginSourceContents = processTemplate(p.templatePath + QLatin1String("/tpl_single.cpp"), sm, errorMessage); if (pluginSourceContents.isEmpty()) @@ -239,7 +230,6 @@ QList PluginGenerator::generatePlugin(const GenerationPara options.collectionHeaderFile + QLatin1String("\"")); sm.insert(QLatin1String("PLUGIN_ADDITIONS"), pluginAdditions); - sm.insert(QLatin1String("COLLECTION_PLUGIN_EXPORT"), qt4PluginExport(options.pluginName, options.collectionClassName)); const QString collectionSourceFileContents = processTemplate(p.templatePath + QLatin1String("/tpl_collection.cpp"), sm, errorMessage); if (collectionSourceFileContents.isEmpty()) return QList(); diff --git a/src/plugins/qmakeprojectmanager/librarydetailscontroller.cpp b/src/plugins/qmakeprojectmanager/librarydetailscontroller.cpp index b1427c84bae..800d7350c19 100644 --- a/src/plugins/qmakeprojectmanager/librarydetailscontroller.cpp +++ b/src/plugins/qmakeprojectmanager/librarydetailscontroller.cpp @@ -3,24 +3,25 @@ #include "librarydetailscontroller.h" -#include "qmakebuildconfiguration.h" #include "qmakeparsernodes.h" #include "qmakeproject.h" #include "qmakeprojectmanagertr.h" +#include #include -#include +#include #include #include +#include #include -#include #include #include #include #include #include +#include #include #include @@ -868,7 +869,7 @@ QString PackageLibraryDetailsController::snippet() const bool PackageLibraryDetailsController::isLinkPackageGenerated() const { - const Project *project = SessionManager::projectForFile(proFile()); + const Project *project = ProjectManager::projectForFile(proFile()); if (!project) return false; @@ -1016,7 +1017,7 @@ void InternalLibraryDetailsController::updateProFile() libraryDetailsWidget()->libraryComboBox->clear(); const QmakeProject *project - = dynamic_cast(SessionManager::projectForFile(proFile())); + = dynamic_cast(ProjectManager::projectForFile(proFile())); if (!project) return; @@ -1104,7 +1105,7 @@ QString InternalLibraryDetailsController::snippet() const // the build directory of the active build configuration QDir rootBuildDir = rootDir; // If the project is unconfigured use the project dir - if (const Project *project = SessionManager::projectForFile(proFile())) { + if (const Project *project = ProjectManager::projectForFile(proFile())) { if (ProjectExplorer::Target *t = project->activeTarget()) if (ProjectExplorer::BuildConfiguration *bc = t->activeBuildConfiguration()) rootBuildDir.setPath(bc->buildDirectory().toString()); diff --git a/src/plugins/qmakeprojectmanager/makefileparse.cpp b/src/plugins/qmakeprojectmanager/makefileparse.cpp index e9d8c2878e7..990fd0ff417 100644 --- a/src/plugins/qmakeprojectmanager/makefileparse.cpp +++ b/src/plugins/qmakeprojectmanager/makefileparse.cpp @@ -5,7 +5,7 @@ #include -#include +#include #include #include @@ -359,11 +359,12 @@ void MakeFileParse::parseCommandLine(const QString &command, const QString &proj // Unit tests: #ifdef WITH_TESTS -# include -# include "qmakeprojectmanagerplugin.h" +#include "qmakeprojectmanagerplugin.h" -# include "projectexplorer/outputparser_test.h" +#include + +#include using namespace QmakeProjectManager::Internal; using namespace ProjectExplorer; @@ -479,8 +480,8 @@ void QmakeProjectManagerPlugin::testMakefileParser() MakeFileParse parser("/tmp/something", MakeFileParse::Mode::FilterKnownConfigValues); parser.parseCommandLine(command, project); - QCOMPARE(ProcessArgs::splitArgs(parser.unparsedArguments()), - ProcessArgs::splitArgs(unparsedArguments)); + QCOMPARE(ProcessArgs::splitArgs(parser.unparsedArguments(), HostOsInfo::hostOs()), + ProcessArgs::splitArgs(unparsedArguments, HostOsInfo::hostOs())); QCOMPARE(parser.effectiveBuildConfig({}), effectiveBuildConfig); const QMakeStepConfig qmsc = parser.config(); diff --git a/src/plugins/qmakeprojectmanager/profileeditor.cpp b/src/plugins/qmakeprojectmanager/profileeditor.cpp index 810d5d4f2e0..c17d3d91935 100644 --- a/src/plugins/qmakeprojectmanager/profileeditor.cpp +++ b/src/plugins/qmakeprojectmanager/profileeditor.cpp @@ -7,18 +7,22 @@ #include "profilehighlighter.h" #include "profilehoverhandler.h" #include "qmakenodes.h" -#include "qmakeproject.h" #include "qmakeprojectmanagerconstants.h" #include + #include + #include #include -#include +#include #include + #include + #include #include + #include #include #include @@ -65,7 +69,7 @@ QString ProFileEditorWidget::checkForPrfFile(const QString &baseName) const const QmakePriFileNode *projectNode = nullptr; // FIXME: Remove this check once project nodes are fully "static". - for (const Project * const project : SessionManager::projects()) { + for (const Project * const project : ProjectManager::projects()) { static const auto isParsing = [](const Project *project) { for (const Target * const t : project->targets()) { for (const BuildConfiguration * const bc : t->buildConfigurations()) { @@ -113,23 +117,22 @@ void ProFileEditorWidget::findLinkAt(const QTextCursor &cursor, int line = 0; int column = 0; convertPosition(cursor.position(), &line, &column); - const int positionInBlock = column - 1; const QString block = cursor.block().text(); // check if the current position is commented out const int hashPos = block.indexOf(QLatin1Char('#')); - if (hashPos >= 0 && hashPos < positionInBlock) + if (hashPos >= 0 && hashPos < column) return processLinkCallback(link); // find the beginning of a filename QString buffer; - int beginPos = positionInBlock - 1; - int endPos = positionInBlock; + int beginPos = column - 1; + int endPos = column; // Check is cursor is somewhere on $${PWD}: - const int chunkStart = std::max(0, positionInBlock - 7); - const int chunkLength = 14 + std::min(0, positionInBlock - 7); + const int chunkStart = std::max(0, column - 7); + const int chunkLength = 14 + std::min(0, column - 7); QString chunk = block.mid(chunkStart, chunkLength); const QString curlyPwd = "$${PWD}"; @@ -141,7 +144,7 @@ void ProFileEditorWidget::findLinkAt(const QTextCursor &cursor, if (posCurlyPwd >= 0) { const int end = chunkStart + posCurlyPwd + curlyPwd.count(); const int start = chunkStart + posCurlyPwd; - if (start <= positionInBlock && end >= positionInBlock) { + if (start <= column && end >= column) { buffer = pwd; beginPos = chunkStart + posCurlyPwd - 1; endPos = end; @@ -150,7 +153,7 @@ void ProFileEditorWidget::findLinkAt(const QTextCursor &cursor, } else if (posPwd >= 0) { const int end = chunkStart + posPwd + pwd.count(); const int start = chunkStart + posPwd; - if (start <= positionInBlock && end >= positionInBlock) { + if (start <= column && end >= column) { buffer = pwd; beginPos = start - 1; endPos = end; @@ -225,8 +228,8 @@ void ProFileEditorWidget::findLinkAt(const QTextCursor &cursor, link.targetFilePath = FilePath::fromString(checkForPrfFile(buffer)); } if (!link.targetFilePath.isEmpty()) { - link.linkTextStart = cursor.position() - positionInBlock + beginPos + 1; - link.linkTextEnd = cursor.position() - positionInBlock + endPos; + link.linkTextStart = cursor.position() - column + beginPos + 1; + link.linkTextEnd = cursor.position() - column + endPos; } processLinkCallback(link); } diff --git a/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.cpp b/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.cpp index 779d175b80f..9da9f7f65a7 100644 --- a/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.cpp +++ b/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.cpp @@ -37,8 +37,8 @@ #include #include +#include #include -#include #include #include @@ -57,7 +57,8 @@ class RunSystemAspect : public TriStateAspect { Q_OBJECT public: - RunSystemAspect() : TriStateAspect(Tr::tr("Run"), Tr::tr("Ignore"), Tr::tr("Use global setting")) + RunSystemAspect() + : TriStateAspect(nullptr, Tr::tr("Run"), Tr::tr("Ignore"), Tr::tr("Use global setting")) { setSettingsKey("RunSystemFunction"); setDisplayName(Tr::tr("qmake system() behavior when parsing:")); @@ -160,7 +161,7 @@ QmakeBuildConfiguration::QmakeBuildConfiguration(Target *target, Id id) this, &QmakeBuildConfiguration::updateProblemLabel); connect(this, &QmakeBuildConfiguration::qmakeBuildConfigurationChanged, this, &QmakeBuildConfiguration::updateProblemLabel); - connect(&QmakeSettings::instance(), &QmakeSettings::settingsChanged, + connect(&settings(), &AspectContainer::changed, this, &QmakeBuildConfiguration::updateProblemLabel); connect(target, &Target::parsingFinished, this, &QmakeBuildConfiguration::updateProblemLabel); connect(target, &Target::kitChanged, this, &QmakeBuildConfiguration::updateProblemLabel); @@ -267,7 +268,7 @@ void QmakeBuildConfiguration::updateProblemLabel() } } - const bool unalignedBuildDir = QmakeSettings::warnAgainstUnalignedBuildDir() + const bool unalignedBuildDir = settings().warnAgainstUnalignedBuildDir() && !isBuildDirAtSafeLocation(); if (unalignedBuildDir) allGood = false; @@ -426,7 +427,7 @@ bool QmakeBuildConfiguration::runSystemFunction() const return true; if (runSystem == TriState::Disabled) return false; - return QmakeSettings::runSystemFunction(); + return settings().runSystemFunction(); } QStringList QmakeBuildConfiguration::configCommandLineArguments() const @@ -753,7 +754,7 @@ QmakeBuildConfigurationFactory::QmakeBuildConfigurationFactory() Tasks issues; if (version) issues << version->reportIssues(projectPath, buildDir); - if (QmakeSettings::warnAgainstUnalignedBuildDir() + if (settings().warnAgainstUnalignedBuildDir() && !QmakeBuildConfiguration::isBuildDirAtSafeLocation( projectPath.absolutePath(), buildDir.absoluteFilePath())) { issues.append(BuildSystemTask(Task::Warning, diff --git a/src/plugins/qmakeprojectmanager/qmakekitinformation.cpp b/src/plugins/qmakeprojectmanager/qmakekitinformation.cpp index 06f2011244f..c413758048c 100644 --- a/src/plugins/qmakeprojectmanager/qmakekitinformation.cpp +++ b/src/plugins/qmakeprojectmanager/qmakekitinformation.cpp @@ -40,10 +40,10 @@ public: ~QmakeKitAspectWidget() override { delete m_lineEdit; } private: - void addToLayout(Layouting::LayoutBuilder &builder) override + void addToLayout(Layouting::LayoutItem &parent) override { addMutableAction(m_lineEdit); - builder.addItem(m_lineEdit); + parent.addItem(m_lineEdit); } void makeReadOnly() override { m_lineEdit->setEnabled(false); } diff --git a/src/plugins/qmakeprojectmanager/qmakemakestep.cpp b/src/plugins/qmakeprojectmanager/qmakemakestep.cpp index 0fdaecc3db5..e72d3467582 100644 --- a/src/plugins/qmakeprojectmanager/qmakemakestep.cpp +++ b/src/plugins/qmakeprojectmanager/qmakemakestep.cpp @@ -22,7 +22,7 @@ #include #include -#include +#include #include #include @@ -222,7 +222,7 @@ void QmakeMakeStep::doRun() void QmakeMakeStep::finish(ProcessResult result) { if (!isSuccess(result) && !isCanceled() && m_unalignedBuildDir - && QmakeSettings::warnAgainstUnalignedBuildDir()) { + && settings().warnAgainstUnalignedBuildDir()) { const QString msg = Tr::tr("The build directory is not at the same level as the source " "directory, which could be the reason for the build failure."); emit addTask(BuildSystemTask(Task::Warning, msg)); diff --git a/src/plugins/qmakeprojectmanager/qmakenodes.cpp b/src/plugins/qmakeprojectmanager/qmakenodes.cpp index 4f59d223ced..144a9cc0c9f 100644 --- a/src/plugins/qmakeprojectmanager/qmakenodes.cpp +++ b/src/plugins/qmakeprojectmanager/qmakenodes.cpp @@ -115,9 +115,7 @@ bool QmakeBuildSystem::supportsAction(Node *context, ProjectAction action, const const FolderNode *folder = node->asFolderNode(); if (folder) { FilePaths list; - const auto folderNodes = folder->folderNodes(); - for (FolderNode *f : folderNodes) - list << f->filePath(); + folder->forEachFolderNode([&](FolderNode *f) { list << f->filePath(); }); if (n->deploysFolder(FileUtils::commonPath(list).toString())) addExistingFiles = false; } diff --git a/src/plugins/qmakeprojectmanager/qmakenodes.h b/src/plugins/qmakeprojectmanager/qmakenodes.h index 778f84b64fd..7dc1133fd0c 100644 --- a/src/plugins/qmakeprojectmanager/qmakenodes.h +++ b/src/plugins/qmakeprojectmanager/qmakenodes.h @@ -12,7 +12,6 @@ namespace QmakeProjectManager { class QmakeProFileNode; -class QmakeProject; // Implements ProjectNode for qmake .pri files class QMAKEPROJECTMANAGER_EXPORT QmakePriFileNode : public ProjectExplorer::ProjectNode diff --git a/src/plugins/qmakeprojectmanager/qmakenodetreebuilder.cpp b/src/plugins/qmakeprojectmanager/qmakenodetreebuilder.cpp index f261594e59e..528b18c95eb 100644 --- a/src/plugins/qmakeprojectmanager/qmakenodetreebuilder.cpp +++ b/src/plugins/qmakeprojectmanager/qmakenodetreebuilder.cpp @@ -195,14 +195,29 @@ static void createTree(QmakeBuildSystem *buildSystem, fileNode->setEnabled(fn.second == FileOrigin::ExactParse); vfolder->addNestedNode(std::move(fileNode)); } - for (FolderNode *fn : vfolder->folderNodes()) - fn->compress(); + vfolder->forEachFolderNode([](FolderNode *fn) { fn->compress(); }); } node->addNode(std::move(vfolder)); } } - if (!generatedFiles.empty()) { + FileType targetFileType = FileType::Unknown; + FilePath targetBinary; + if (proFile && proFile->targetInformation().valid) { + if (proFile->projectType() == ProjectType::ApplicationTemplate) { + targetFileType = FileType::App; + targetBinary = buildSystem->executableFor(proFile); + } else if (proFile->projectType() == ProjectType::SharedLibraryTemplate + || proFile->projectType() == ProjectType::StaticLibraryTemplate) { + targetFileType = FileType::Lib; + const FilePaths libs = Utils::sorted(buildSystem->allLibraryTargetFiles(proFile), + [](const FilePath &fp1, const FilePath &fp2) { + return fp1.fileName().length() < fp2.fileName().length(); }); + if (!libs.isEmpty()) + targetBinary = libs.last(); // Longest file name is the one that's not a symlink. + } + } + if (!generatedFiles.empty() || !targetBinary.isEmpty()) { QTC_CHECK(proFile); const FilePath baseDir = generatedFiles.size() == 1 ? generatedFiles.first().parentDir() : buildSystem->buildDir(proFile->filePath()); @@ -214,6 +229,11 @@ static void createTree(QmakeBuildSystem *buildSystem, fileNode->setIsGenerated(true); genFolder->addNestedNode(std::move(fileNode)); } + if (!targetBinary.isEmpty()) { + auto targetFileNode = std::make_unique(targetBinary, targetFileType); + targetFileNode->setIsGenerated(true); + genFolder->addNestedNode(std::move(targetFileNode)); + } node->addNode(std::move(genFolder)); } diff --git a/src/plugins/qmakeprojectmanager/qmakeparser.cpp b/src/plugins/qmakeprojectmanager/qmakeparser.cpp index e95b403bff4..2cb9d941c14 100644 --- a/src/plugins/qmakeprojectmanager/qmakeparser.cpp +++ b/src/plugins/qmakeprojectmanager/qmakeparser.cpp @@ -71,11 +71,12 @@ OutputLineParser::Result QMakeParser::handleLine(const QString &line, OutputForm // Unit tests: #ifdef WITH_TESTS -# include -# include "qmakeprojectmanagerplugin.h" +#include "qmakeprojectmanagerplugin.h" -# include "projectexplorer/outputparser_test.h" +#include + +#include using namespace QmakeProjectManager::Internal; diff --git a/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp b/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp index e15fadb46db..31a55faab01 100644 --- a/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp +++ b/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp @@ -30,10 +30,11 @@ #include #include +#include #include #include +#include #include -#include #include #include @@ -1284,9 +1285,9 @@ void QmakeProFile::asyncUpdate() if (!includedInExactParse()) m_readerExact->setExact(false); QmakeEvalInput input = evalInput(); - QFuture future = runAsync(ProjectExplorerPlugin::sharedThreadPool(), - QThread::LowestPriority, - &QmakeProFile::asyncEvaluate, this, input); + QFuture future = Utils::asyncRun(ProjectExplorerPlugin::sharedThreadPool(), + QThread::LowestPriority, + &QmakeProFile::asyncEvaluate, this, input); m_parseFutureWatcher->setFuture(future); } @@ -1630,9 +1631,9 @@ QmakeEvalResultPtr QmakeProFile::evaluate(const QmakeEvalInput &input) return result; } -void QmakeProFile::asyncEvaluate(QFutureInterface &fi, QmakeEvalInput input) +void QmakeProFile::asyncEvaluate(QPromise &promise, QmakeEvalInput input) { - fi.reportResult(evaluate(input)); + promise.addResult(evaluate(input)); } bool sortByParserNodes(Node *a, Node *b) diff --git a/src/plugins/qmakeprojectmanager/qmakeparsernodes.h b/src/plugins/qmakeprojectmanager/qmakeparsernodes.h index 34c4958564b..9296addfa80 100644 --- a/src/plugins/qmakeprojectmanager/qmakeparsernodes.h +++ b/src/plugins/qmakeprojectmanager/qmakeparsernodes.h @@ -336,7 +336,7 @@ private: static Internal::QmakeEvalResultPtr evaluate(const Internal::QmakeEvalInput &input); void applyEvaluate(const Internal::QmakeEvalResultPtr &parseResult); - void asyncEvaluate(QFutureInterface &fi, + void asyncEvaluate(QPromise &promise, Internal::QmakeEvalInput input); void cleanupProFileReaders(); diff --git a/src/plugins/qmakeprojectmanager/qmakeproject.cpp b/src/plugins/qmakeprojectmanager/qmakeproject.cpp index 6d840187251..d7aefb50b8b 100644 --- a/src/plugins/qmakeprojectmanager/qmakeproject.cpp +++ b/src/plugins/qmakeprojectmanager/qmakeproject.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -40,15 +41,16 @@ #include #include +#include + #include #include #include #include #include -#include -#include -#include +#include +#include #include #include @@ -335,7 +337,7 @@ void QmakeBuildSystem::updateCppCodeModel() rpp.setBuildTargetType(BuildTargetType::Unknown); break; } - const QString includeFileBaseDir = pro->sourceDir().toString(); + const FilePath includeFileBaseDir = pro->sourceDir(); QStringList cxxArgs = pro->variableValue(Variable::CppFlags); QStringList cArgs = pro->variableValue(Variable::CFlags); @@ -776,32 +778,25 @@ Tasks QmakeProject::projectIssues(const Kit *k) const } // Find the folder that contains a file with a certain name (recurse down) -static FolderNode *folderOf(FolderNode *in, const FilePath &fileName) +static FolderNode *folderOf(FolderNode *in, const FilePath &filePath) { - const QList fileNodeList = in->fileNodes(); - for (FileNode *fn : fileNodeList) { - if (fn->filePath() == fileName) - return in; - } - const QList folderNodeList = in->folderNodes(); - for (FolderNode *folder : folderNodeList) { - if (FolderNode *pn = folderOf(folder, fileName)) - return pn; - } - return {}; + if (in->findChildFileNode([&filePath](FileNode *fn) { return fn->filePath() == filePath; })) + return in; + + return in->findChildFolderNode([&filePath](FolderNode *folder) { + return folderOf(folder, filePath); + }); } // Find the QmakeProFileNode that contains a certain file. // First recurse down to folder, then find the pro-file. -static FileNode *fileNodeOf(FolderNode *in, const FilePath &fileName) +static FileNode *fileNodeOf(FolderNode *in, const FilePath &filePath) { - for (FolderNode *folder = folderOf(in, fileName); folder; folder = folder->parentFolderNode()) { - if (auto *proFile = dynamic_cast(folder)) { - const QList fileNodeList = proFile->fileNodes(); - for (FileNode *fileNode : fileNodeList) { - if (fileNode->filePath() == fileName) - return fileNode; - } + for (FolderNode *folder = folderOf(in, filePath); folder; folder = folder->parentFolderNode()) { + if (auto proFile = dynamic_cast(folder)) { + return proFile->findChildFileNode([&filePath](FileNode *fn) { + return fn->filePath() == filePath; + }); } } return nullptr; @@ -872,9 +867,9 @@ QtSupport::ProFileReader *QmakeBuildSystem::createProFileReader(const QmakeProFi rootProFileName, deviceRoot()); - Environment::const_iterator eit = env.constBegin(), eend = env.constEnd(); - for (; eit != eend; ++eit) - m_qmakeGlobals->environment.insert(env.key(eit), env.expandedValueForKey(env.key(eit))); + env.forEachEntry([&](const QString &key, const QString &value, bool) { + m_qmakeGlobals->environment.insert(key, env.expandVariables(value)); + }); m_qmakeGlobals->setCommandLineArguments(rootProFileName, qmakeArgs); m_qmakeGlobals->runSystemFunction = bc->runSystemFunction(); @@ -923,9 +918,9 @@ const FilePath &QmakeBuildSystem::qmakeSysroot() const void QmakeBuildSystem::destroyProFileReader(QtSupport::ProFileReader *reader) { // The ProFileReader destructor is super expensive (but thread-safe). - const auto deleteFuture = runAsync(ProjectExplorerPlugin::sharedThreadPool(), QThread::LowestPriority, - [reader] { delete reader; }); - onFinished(deleteFuture, this, [this](const QFuture &) { + const auto deleteFuture = Utils::asyncRun(ProjectExplorerPlugin::sharedThreadPool(), + [reader] { delete reader; }); + Utils::onFinished(deleteFuture, this, [this](const QFuture &) { if (!--m_qmakeGlobalsRefCnt) { deregisterFromCacheManager(); m_qmakeGlobals.reset(); @@ -1309,15 +1304,13 @@ static FilePath destDirFor(const TargetInformation &ti) return ti.destDir; } -void QmakeBuildSystem::collectLibraryData(const QmakeProFile *file, DeploymentData &deploymentData) +FilePaths QmakeBuildSystem::allLibraryTargetFiles(const QmakeProFile *file) const { - const QString targetPath = file->installsList().targetPath; - if (targetPath.isEmpty()) - return; const ToolChain *const toolchain = ToolChainKitAspect::cxxToolChain(kit()); if (!toolchain) - return; + return {}; + FilePaths libs; TargetInformation ti = file->targetInformation(); QString targetFileName = ti.target; const QStringList config = file->variableValue(Variable::Config); @@ -1337,7 +1330,7 @@ void QmakeBuildSystem::collectLibraryData(const QmakeProFile *file, DeploymentDa } targetFileName += targetVersionExt + QLatin1Char('.'); targetFileName += QLatin1String(isStatic ? "lib" : "dll"); - deploymentData.addFile(destDirFor(ti) / targetFileName, targetPath); + libs << FilePath::fromString(targetFileName); break; } case Abi::DarwinOS: { @@ -1357,10 +1350,10 @@ void QmakeBuildSystem::collectLibraryData(const QmakeProFile *file, DeploymentDa targetFileName += majorVersion; } targetFileName += QLatin1Char('.'); - targetFileName += file->singleVariableValue(isStatic - ? Variable::StaticLibExtension : Variable::ShLibExtension); + targetFileName += file->singleVariableValue(isStatic ? Variable::StaticLibExtension + : Variable::ShLibExtension); } - deploymentData.addFile(destDir / targetFileName, targetPath); + libs << destDir / targetFileName; break; } case Abi::LinuxOS: @@ -1372,10 +1365,10 @@ void QmakeBuildSystem::collectLibraryData(const QmakeProFile *file, DeploymentDa targetFileName += QLatin1Char('.'); if (isStatic) { - targetFileName += QLatin1Char('a'); + libs << destDirFor(ti) / (targetFileName + QLatin1Char('a')); } else { targetFileName += QLatin1String("so"); - deploymentData.addFile(destDirFor(ti) / targetFileName, targetPath); + libs << destDirFor(ti) / targetFileName; if (nameIsVersioned) { QString version = file->singleVariableValue(Variable::Version); if (version.isEmpty()) @@ -1386,9 +1379,7 @@ void QmakeBuildSystem::collectLibraryData(const QmakeProFile *file, DeploymentDa targetFileName += QLatin1Char('.'); while (!versionComponents.isEmpty()) { const QString versionString = versionComponents.join(QLatin1Char('.')); - deploymentData.addFile(destDirFor(ti).pathAppended(targetFileName - + versionString), - targetPath); + libs << destDirFor(ti).pathAppended(targetFileName + versionString); versionComponents.removeLast(); } } @@ -1397,6 +1388,18 @@ void QmakeBuildSystem::collectLibraryData(const QmakeProFile *file, DeploymentDa default: break; } + + return libs; +} + +void QmakeBuildSystem::collectLibraryData(const QmakeProFile *file, DeploymentData &deploymentData) +{ + const QString targetPath = file->installsList().targetPath; + if (!targetPath.isEmpty()) { + const FilePaths libs = allLibraryTargetFiles(file); + for (const FilePath &lib : libs) + deploymentData.addFile(lib, targetPath); + } } static FilePath getFullPathOf(const QmakeProFile *pro, Variable variable, @@ -1450,8 +1453,12 @@ void QmakeBuildSystem::testToolChain(ToolChain *tc, const FilePath &path) const QString QmakeBuildSystem::deviceRoot() const { - if (projectFilePath().needsDevice()) - return projectFilePath().withNewPath("/").toFSPathString(); + IDeviceConstPtr device = BuildDeviceKitAspect::device(target()->kit()); + QTC_ASSERT(device, return {}); + FilePath deviceRoot = device->rootPath(); + if (deviceRoot.needsDevice()) + return deviceRoot.toFSPathString(); + return {}; } @@ -1588,22 +1595,22 @@ void QmakeBuildSystem::runGenerator(Utils::Id id) QTC_ASSERT(false, return); } if (!outDir.ensureWritableDir()) { - showError(Tr::tr("Cannot create output directory \"%1\"").arg(outDir.toUserOutput())); + showError(Tr::tr("Cannot create output directory \"%1\".").arg(outDir.toUserOutput())); return; } - const auto proc = new QtcProcess(this); - connect(proc, &QtcProcess::done, proc, &QtcProcess::deleteLater); - connect(proc, &QtcProcess::readyReadStandardOutput, this, [proc] { + const auto proc = new Process(this); + connect(proc, &Process::done, proc, &Process::deleteLater); + connect(proc, &Process::readyReadStandardOutput, this, [proc] { Core::MessageManager::writeFlashing(QString::fromLocal8Bit(proc->readAllRawStandardOutput())); }); - connect(proc, &QtcProcess::readyReadStandardError, this, [proc] { + connect(proc, &Process::readyReadStandardError, this, [proc] { Core::MessageManager::writeDisrupting(QString::fromLocal8Bit(proc->readAllRawStandardError())); }); proc->setWorkingDirectory(outDir); proc->setEnvironment(buildConfiguration()->environment()); proc->setCommand(cmdLine); - Core::MessageManager::writeFlashing(Tr::tr("Running in %1: %2") - .arg(outDir.toUserOutput(), cmdLine.toUserOutput())); + Core::MessageManager::writeFlashing( + Tr::tr("Running in \"%1\": %2.").arg(outDir.toUserOutput(), cmdLine.toUserOutput())); proc->start(); } diff --git a/src/plugins/qmakeprojectmanager/qmakeproject.h b/src/plugins/qmakeprojectmanager/qmakeproject.h index 6615baaaca5..3097ae7b579 100644 --- a/src/plugins/qmakeprojectmanager/qmakeproject.h +++ b/src/plugins/qmakeprojectmanager/qmakeproject.h @@ -106,6 +106,7 @@ public: void collectData(const QmakeProFile *file, ProjectExplorer::DeploymentData &deploymentData); void collectApplicationData(const QmakeProFile *file, ProjectExplorer::DeploymentData &deploymentData); + Utils::FilePaths allLibraryTargetFiles(const QmakeProFile *file) const; void collectLibraryData(const QmakeProFile *file, ProjectExplorer::DeploymentData &deploymentData); void startAsyncTimer(QmakeProFile::AsyncUpdateDelay delay); diff --git a/src/plugins/qmakeprojectmanager/qmakeprojectimporter.cpp b/src/plugins/qmakeprojectmanager/qmakeprojectimporter.cpp index 8bcbe2027cf..1a929bf9a49 100644 --- a/src/plugins/qmakeprojectmanager/qmakeprojectimporter.cpp +++ b/src/plugins/qmakeprojectmanager/qmakeprojectimporter.cpp @@ -23,8 +23,8 @@ #include #include +#include #include -#include #include #include diff --git a/src/plugins/qmakeprojectmanager/qmakeprojectmanagerplugin.cpp b/src/plugins/qmakeprojectmanager/qmakeprojectmanagerplugin.cpp index e557615507d..61493bf05c3 100644 --- a/src/plugins/qmakeprojectmanager/qmakeprojectmanagerplugin.cpp +++ b/src/plugins/qmakeprojectmanager/qmakeprojectmanagerplugin.cpp @@ -29,7 +29,7 @@ #include #include #include -#include +#include #include #include #include @@ -80,7 +80,7 @@ public: ProFileEditorFactory profileEditorFactory; - QmakeSettingsPage settingsPage; + QmakeSettings settings; QmakeProject *m_previousStartupProject = nullptr; Target *m_previousTarget = nullptr; @@ -244,7 +244,7 @@ void QmakeProjectManagerPlugin::initialize() connect(BuildManager::instance(), &BuildManager::buildStateChanged, d, &QmakeProjectManagerPluginPrivate::buildStateChanged); - connect(SessionManager::instance(), &SessionManager::startupProjectChanged, + connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, d, &QmakeProjectManagerPluginPrivate::projectChanged); connect(ProjectTree::instance(), &ProjectTree::currentProjectChanged, d, &QmakeProjectManagerPluginPrivate::projectChanged); @@ -294,7 +294,7 @@ void QmakeProjectManagerPluginPrivate::projectChanged() if (ProjectTree::currentProject()) m_previousStartupProject = qobject_cast(ProjectTree::currentProject()); else - m_previousStartupProject = qobject_cast(SessionManager::startupProject()); + m_previousStartupProject = qobject_cast(ProjectManager::startupProject()); if (m_previousStartupProject) { connect(m_previousStartupProject, &Project::activeTargetChanged, @@ -357,8 +357,7 @@ void QmakeProjectManagerPluginPrivate::addLibraryImpl(const FilePath &filePath, // add extra \n in case the last line is not empty int line, column; editor->convertPosition(endOfDoc, &line, &column); - const int positionInBlock = column - 1; - if (!editor->textAt(endOfDoc - positionInBlock, positionInBlock).simplified().isEmpty()) + if (!editor->textAt(endOfDoc - column, column).simplified().isEmpty()) snippet = QLatin1Char('\n') + snippet; editor->insert(snippet); @@ -366,7 +365,7 @@ void QmakeProjectManagerPluginPrivate::addLibraryImpl(const FilePath &filePath, void QmakeProjectManagerPluginPrivate::runQMake() { - runQMakeImpl(SessionManager::startupProject(), nullptr); + runQMakeImpl(ProjectManager::startupProject(), nullptr); } void QmakeProjectManagerPluginPrivate::runQMakeContextMenu() @@ -411,7 +410,7 @@ void QmakeProjectManagerPluginPrivate::buildFile() FileNode *node = n ? n->asFileNode() : nullptr; if (!node) return; - Project *project = SessionManager::projectForFile(file); + Project *project = ProjectManager::projectForFile(file); if (!project) return; Target *target = project->activeTarget(); @@ -563,7 +562,7 @@ void QmakeProjectManagerPluginPrivate::enableBuildFileMenus(const FilePath &file bool enabled = false; if (Node *node = ProjectTree::nodeForFile(file)) { - if (Project *project = SessionManager::projectForFile(file)) { + if (Project *project = ProjectManager::projectForFile(file)) { if (const FileNode *fileNode = node->asFileNode()) { const FileType type = fileNode->fileType(); visible = qobject_cast(project) diff --git a/src/plugins/qmakeprojectmanager/qmakesettings.cpp b/src/plugins/qmakeprojectmanager/qmakesettings.cpp index a9e1de07c3b..e69526901f0 100644 --- a/src/plugins/qmakeprojectmanager/qmakesettings.cpp +++ b/src/plugins/qmakeprojectmanager/qmakesettings.cpp @@ -4,105 +4,61 @@ #include "qmakesettings.h" #include "qmakeprojectmanagertr.h" -#include - #include #include #include -#include - using namespace Utils; -namespace QmakeProjectManager { -namespace Internal { +namespace QmakeProjectManager::Internal { + +static QmakeSettings *theSettings; + +QmakeSettings &settings() { return *theSettings; } QmakeSettings::QmakeSettings() { - setAutoApply(false); + theSettings = this; - registerAspect(&m_warnAgainstUnalignedBuildDir); - m_warnAgainstUnalignedBuildDir.setSettingsKey("QmakeProjectManager/WarnAgainstUnalignedBuildDir"); - m_warnAgainstUnalignedBuildDir.setDefaultValue(HostOsInfo::isWindowsHost()); - m_warnAgainstUnalignedBuildDir.setLabelText(Tr::tr("Warn if a project's source and " - "build directories are not at the same level")); - m_warnAgainstUnalignedBuildDir.setToolTip(Tr::tr("Qmake has subtle bugs that " - "can be triggered if source and build directory are not at the same level.")); - - registerAspect(&m_alwaysRunQmake); - m_alwaysRunQmake.setSettingsKey("QmakeProjectManager/AlwaysRunQmake"); - m_alwaysRunQmake.setLabelText(Tr::tr("Run qmake on every build")); - m_alwaysRunQmake.setToolTip(Tr::tr("This option can help to prevent failures on " - "incremental builds, but might slow them down unnecessarily in the general case.")); - - registerAspect(&m_ignoreSystemFunction); - m_ignoreSystemFunction.setSettingsKey("QmakeProjectManager/RunSystemFunction"); - m_ignoreSystemFunction.setLabelText(Tr::tr("Ignore qmake's system() function when parsing a project")); - m_ignoreSystemFunction.setToolTip(Tr::tr("Checking this option avoids unwanted side effects, " - "but may result in inexact parsing results.")); - // The settings value has been stored with the opposite meaning for a while. - // Avoid changing the stored value, but flip it on read/write: - const auto invertBoolVariant = [](const QVariant &v) { return QVariant(!v.toBool()); }; - m_ignoreSystemFunction.setFromSettingsTransformation(invertBoolVariant); - m_ignoreSystemFunction.setToSettingsTransformation(invertBoolVariant); - - readSettings(Core::ICore::settings()); -} - -bool QmakeSettings::warnAgainstUnalignedBuildDir() -{ - return instance().m_warnAgainstUnalignedBuildDir.value(); -} - -bool QmakeSettings::alwaysRunQmake() -{ - return instance().m_alwaysRunQmake.value(); -} - -bool QmakeSettings::runSystemFunction() -{ - return !instance().m_ignoreSystemFunction.value(); // Note: negated. -} - -QmakeSettings &QmakeSettings::instance() -{ - static QmakeSettings theSettings; - return theSettings; -} - -class SettingsWidget final : public Core::IOptionsPageWidget -{ -public: - SettingsWidget() - { - auto &s = QmakeSettings::instance(); - using namespace Layouting; - Column { - s.m_warnAgainstUnalignedBuildDir, - s.m_alwaysRunQmake, - s.m_ignoreSystemFunction, - st - }.attachTo(this); - } - - void apply() final - { - auto &s = QmakeSettings::instance(); - if (s.isDirty()) { - s.apply(); - s.writeSettings(Core::ICore::settings()); - } - } -}; - -QmakeSettingsPage::QmakeSettingsPage() -{ setId("K.QmakeProjectManager.QmakeSettings"); setDisplayName(Tr::tr("Qmake")); setCategory(ProjectExplorer::Constants::BUILD_AND_RUN_SETTINGS_CATEGORY); - setWidgetCreator([] { return new SettingsWidget; }); + setSettingsGroup("QmakeProjectManager"); + + warnAgainstUnalignedBuildDir.setSettingsKey("WarnAgainstUnalignedBuildDir"); + warnAgainstUnalignedBuildDir.setDefaultValue(HostOsInfo::isWindowsHost()); + warnAgainstUnalignedBuildDir.setLabelText(Tr::tr("Warn if a project's source and " + "build directories are not at the same level")); + warnAgainstUnalignedBuildDir.setToolTip(Tr::tr("Qmake has subtle bugs that " + "can be triggered if source and build directory are not at the same level.")); + + alwaysRunQmake.setSettingsKey("AlwaysRunQmake"); + alwaysRunQmake.setLabelText(Tr::tr("Run qmake on every build")); + alwaysRunQmake.setToolTip(Tr::tr("This option can help to prevent failures on " + "incremental builds, but might slow them down unnecessarily in the general case.")); + + ignoreSystemFunction.setSettingsKey("RunSystemFunction"); + ignoreSystemFunction.setLabelText(Tr::tr("Ignore qmake's system() function when parsing a project")); + ignoreSystemFunction.setToolTip(Tr::tr("Checking this option avoids unwanted side effects, " + "but may result in inexact parsing results.")); + // The settings value has been stored with the opposite meaning for a while. + // Avoid changing the stored value, but flip it on read/write: + const auto invertBoolVariant = [](const QVariant &v) { return QVariant(!v.toBool()); }; + ignoreSystemFunction.setFromSettingsTransformation(invertBoolVariant); + ignoreSystemFunction.setToSettingsTransformation(invertBoolVariant); + + setLayouter([this] { + using namespace Layouting; + return Column { + warnAgainstUnalignedBuildDir, + alwaysRunQmake, + ignoreSystemFunction, + st + }; + }); + + readSettings(); } -} // namespace Internal -} // namespace QmakeProjectManager +} // QmakeProjectManager::Internal diff --git a/src/plugins/qmakeprojectmanager/qmakesettings.h b/src/plugins/qmakeprojectmanager/qmakesettings.h index 242f6601e2d..9c2b277f538 100644 --- a/src/plugins/qmakeprojectmanager/qmakesettings.h +++ b/src/plugins/qmakeprojectmanager/qmakesettings.h @@ -5,38 +5,20 @@ #include -#include +namespace QmakeProjectManager::Internal { -namespace QmakeProjectManager { -namespace Internal { - -class QmakeSettings : public Utils::AspectContainer +class QmakeSettings : public Core::PagedSettings { - Q_OBJECT - public: - static QmakeSettings &instance(); - static bool warnAgainstUnalignedBuildDir(); - static bool alwaysRunQmake(); - static bool runSystemFunction(); - -signals: - void settingsChanged(); - -private: QmakeSettings(); - friend class SettingsWidget; - Utils::BoolAspect m_warnAgainstUnalignedBuildDir; - Utils::BoolAspect m_alwaysRunQmake; - Utils::BoolAspect m_ignoreSystemFunction; + bool runSystemFunction() { return !ignoreSystemFunction(); } + + Utils::BoolAspect warnAgainstUnalignedBuildDir{this}; + Utils::BoolAspect alwaysRunQmake{this}; + Utils::BoolAspect ignoreSystemFunction{this}; }; -class QmakeSettingsPage final : public Core::IOptionsPage -{ -public: - QmakeSettingsPage(); -}; +QmakeSettings &settings(); -} // namespace Internal -} // namespace QmakeProjectManager +} // QmakeProjectManager::Internal diff --git a/src/plugins/qmakeprojectmanager/qmakestep.cpp b/src/plugins/qmakeprojectmanager/qmakestep.cpp index c726156e942..0da8bebaad2 100644 --- a/src/plugins/qmakeprojectmanager/qmakestep.cpp +++ b/src/plugins/qmakeprojectmanager/qmakestep.cpp @@ -36,7 +36,7 @@ #include #include #include -#include +#include #include #include @@ -225,7 +225,7 @@ bool QMakeStep::init() } // Check whether we need to run qmake - if (m_forced || QmakeSettings::alwaysRunQmake() + if (m_forced || settings().alwaysRunQmake() || qmakeBc->compareToImportFrom(makeFile) != QmakeBuildConfiguration::MakefileMatches) { m_needToRunQMake = true; } @@ -284,14 +284,14 @@ void QMakeStep::doRun() using namespace Tasking; - const auto setupQMake = [this](QtcProcess &process) { + const auto setupQMake = [this](Process &process) { m_outputFormatter->setLineParsers({new QMakeParser}); ProcessParameters *pp = processParameters(); pp->setCommandLine(m_qmakeCommand); setupProcess(&process); }; - const auto setupMakeQMake = [this](QtcProcess &process) { + const auto setupMakeQMake = [this](Process &process) { auto *parser = new GnuMakeParser; parser->addSearchDir(processParameters()->workingDirectory()); m_outputFormatter->setLineParsers({parser}); @@ -300,13 +300,13 @@ void QMakeStep::doRun() setupProcess(&process); }; - const auto onDone = [this](const QtcProcess &) { + const auto onProcessDone = [this](const Process &) { const QString command = displayedParameters()->effectiveCommand().toUserOutput(); emit addOutput(Tr::tr("The process \"%1\" exited normally.").arg(command), OutputFormat::NormalMessage); }; - const auto onError = [this](const QtcProcess &process) { + const auto onProcessError = [this](const Process &process) { const QString command = displayedParameters()->effectiveCommand().toUserOutput(); if (process.result() == ProcessResult::FinishedWithError) { emit addOutput(Tr::tr("The process \"%1\" exited with code %2.") @@ -326,14 +326,14 @@ void QMakeStep::doRun() m_needToRunQMake = true; }; - const auto onGroupDone = [this] { + const auto onDone = [this] { emit buildConfiguration()->buildDirectoryInitialized(); }; - QList processList = {Process(setupQMake, onDone, onError)}; + QList processList = {ProcessTask(setupQMake, onProcessDone, onProcessError)}; if (m_runMakeQmake) - processList << Process(setupMakeQMake, onDone, onError); - processList << OnGroupDone(onGroupDone); + processList << ProcessTask(setupMakeQMake, onProcessDone, onProcessError); + processList << onGroupDone(onDone); runTaskTree(Group(processList)); } @@ -455,21 +455,6 @@ bool QMakeStep::fromMap(const QVariantMap &map) { m_forced = map.value(QMAKE_FORCED_KEY, false).toBool(); m_selectedAbis = map.value(QMAKE_SELECTED_ABIS_KEY).toStringList(); - - // Backwards compatibility with < Creator 4.12. - const QVariant separateDebugInfo - = map.value("QtProjectManager.QMakeBuildStep.SeparateDebugInfo"); - if (separateDebugInfo.isValid()) - qmakeBuildConfiguration()->forceSeparateDebugInfo(separateDebugInfo.toBool()); - const QVariant qmlDebugging - = map.value("QtProjectManager.QMakeBuildStep.LinkQmlDebuggingLibrary"); - if (qmlDebugging.isValid()) - qmakeBuildConfiguration()->forceQmlDebugging(qmlDebugging.toBool()); - const QVariant useQtQuickCompiler - = map.value("QtProjectManager.QMakeBuildStep.UseQtQuickCompiler"); - if (useQtQuickCompiler.isValid()) - qmakeBuildConfiguration()->forceQtQuickCompiler(useQtQuickCompiler.toBool()); - return BuildStep::fromMap(map); } @@ -481,11 +466,12 @@ QWidget *QMakeStep::createConfigWidget() abisListWidget = new QListWidget; Layouting::Form builder; - builder.addRow(m_buildType); - builder.addRow(m_userArgs); - builder.addRow(m_effectiveCall); + builder.addRow({m_buildType}); + builder.addRow({m_userArgs}); + builder.addRow({m_effectiveCall}); builder.addRow({abisLabel, abisListWidget}); - auto widget = builder.emerge(Layouting::WithoutMargins); + builder.addItem(Layouting::noMargin); + auto widget = builder.emerge(); qmakeBuildConfigChanged(); @@ -744,7 +730,7 @@ QMakeStepFactory::QMakeStepFactory() setSupportedStepList(ProjectExplorer::Constants::BUILDSTEPS_BUILD); //: QMakeStep default display name setDisplayName(::QmakeProjectManager::Tr::tr("qmake")); // Fully qualifying for lupdate - setFlags(BuildStepInfo::UniqueStep); + setFlags(BuildStep::UniqueStep); } QMakeStepConfig::TargetArchConfig QMakeStepConfig::targetArchFor(const Abi &, const QtVersion *) diff --git a/src/plugins/qmakeprojectmanager/wizards/qtprojectparameters.cpp b/src/plugins/qmakeprojectmanager/wizards/qtprojectparameters.cpp index e2db56e8246..1612e36753b 100644 --- a/src/plugins/qmakeprojectmanager/wizards/qtprojectparameters.cpp +++ b/src/plugins/qmakeprojectmanager/wizards/qtprojectparameters.cpp @@ -65,8 +65,10 @@ void QtProjectParameters::writeProFile(QTextStream &str) const switch (type) { case ConsoleApp: // Mac: Command line apps should not be bundles - str << "CONFIG += console\nCONFIG -= app_bundle\n\n"; - // fallthrough + str << "CONFIG += console\n" + "CONFIG -= app_bundle\n\n" + "TEMPLATE = app\n"; + break; case GuiApp: str << "TEMPLATE = app\n"; break; diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 16668bf3281..7987e50808c 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -92,7 +92,6 @@ extend_qtc_library(QmlDesignerCore CONDITION Qt6_VERSION VERSION_GREATER_EQUAL 6.5.0 AND Qt6_VERSION VERSION_LESS 6.6.0 PUBLIC_DEFINES QDS_BUILD_QMLPARSER ) - extend_qtc_library(QmlDesignerCore CONDITION UNIX AND NOT APPLE PUBLIC_DEPENDS rt @@ -294,19 +293,6 @@ extend_qtc_library(QmlDesignerCore qprocessuniqueptr.h ) -extend_qtc_library(QmlDesignerCore - PUBLIC_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/designercore/instances - SOURCES_PREFIX ${CMAKE_CURRENT_LIST_DIR}/designercore/instances - SOURCES_PROPERTIES SKIP_AUTOUIC OFF - SOURCES - puppetbuildprogressdialog.ui - puppetdialog.ui - puppetbuildprogressdialog.cpp - puppetbuildprogressdialog.h - puppetdialog.cpp - puppetdialog.h -) - extend_qtc_library(QmlDesignerCore INCLUDES ${CMAKE_CURRENT_LIST_DIR}/designercore/model SOURCES_PREFIX ${CMAKE_CURRENT_LIST_DIR}/designercore/model @@ -538,10 +524,6 @@ function(get_and_add_as_subdirectory name repository git_tag build_dir source_di endfunction() if (QTC_STATIC_BUILD AND TARGET QmlDesigner) - get_target_property(_designerType Qt5::Designer TYPE) - if (${_designerType} STREQUAL "STATIC_LIBRARY") - extend_qtc_target(QmlDesigner PUBLIC_DEFINES QT_DESIGNER_STATIC) - endif() get_target_property(_designerType Qt::Designer TYPE) if (${_designerType} STREQUAL "STATIC_LIBRARY") extend_qtc_target(QmlDesigner PUBLIC_DEFINES QT_DESIGNER_STATIC) @@ -549,7 +531,6 @@ if (QTC_STATIC_BUILD AND TARGET QmlDesigner) extend_qtc_target(QmlDesigner PUBLIC_DEPENDS TextEditor) endif() - extend_qtc_plugin(QmlDesigner SOURCES_PREFIX components PUBLIC_INCLUDES components diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp index f3460cbca81..e9b5ab7082b 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include @@ -63,7 +63,7 @@ AssetExportDialog::AssetExportDialog(const Utils::FilePath &exportPath, m_ui->exportPath->setExpectedKind(Utils::PathChooser::Kind::SaveFile); m_ui->exportPath->setFilePath( exportPath.pathAppended( - ProjectExplorer::SessionManager::startupProject()->displayName() + ".metadata" + ProjectExplorer::ProjectManager::startupProject()->displayName() + ".metadata" )); m_ui->exportPath->setPromptDialogTitle(tr("Choose Export File")); m_ui->exportPath->setPromptDialogFilter(tr("Metadata file (*.metadata)")); diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp index d9167049a56..7b0604c19a8 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp @@ -10,11 +10,12 @@ #include "rewriterview.h" #include "qmlitemnode.h" #include "qmlobjectnode.h" -#include "coreplugin/editormanager/editormanager.h" -#include "utils/qtcassert.h" -#include "utils/runextensions.h" -#include "projectexplorer/session.h" -#include "projectexplorer/project.h" + +#include +#include +#include +#include +#include #include @@ -69,7 +70,7 @@ public: private: void addAsset(const QPixmap &p, const Utils::FilePath &path); - void doDumping(QFutureInterface &fi); + void doDumping(QPromise &promise); void savePixmap(const QPixmap &p, Utils::FilePath &path) const; QFuture m_dumpFuture; @@ -406,7 +407,7 @@ void AssetExporter::writeMetadata() const m_currentState.change(ParsingState::WritingJson); - auto const startupProject = ProjectExplorer::SessionManager::startupProject(); + auto const startupProject = ProjectExplorer::ProjectManager::startupProject(); QTC_ASSERT(startupProject, return); const QString projectName = startupProject->displayName(); @@ -452,7 +453,7 @@ QDebug operator<<(QDebug os, const AssetExporter::ParsingState &s) AssetDumper::AssetDumper(): m_quitDumper(false) { - m_dumpFuture = Utils::runAsync(&AssetDumper::doDumping, this); + m_dumpFuture = Utils::asyncRun(&AssetDumper::doDumping, this); } AssetDumper::~AssetDumper() @@ -489,7 +490,7 @@ void AssetDumper::addAsset(const QPixmap &p, const Utils::FilePath &path) m_assets.push({p, path}); } -void AssetDumper::doDumping(QFutureInterface &fi) +void AssetDumper::doDumping(QPromise &promise) { auto haveAsset = [this] (std::pair *asset) { QMutexLocker locker(&m_queueMutex); @@ -503,7 +504,7 @@ void AssetDumper::doDumping(QFutureInterface &fi) forever { std::pair asset; if (haveAsset(&asset)) { - if (fi.isCanceled()) + if (promise.isCanceled()) break; savePixmap(asset.first, asset.second); } else { @@ -513,10 +514,9 @@ void AssetDumper::doDumping(QFutureInterface &fi) m_queueCondition.wait(&m_queueMutex); } - if (fi.isCanceled()) + if (promise.isCanceled()) break; } - fi.reportFinished(); } void AssetDumper::savePixmap(const QPixmap &p, Utils::FilePath &path) const diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp index 2d6f6c7d80f..3ab1d1a6869 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp @@ -19,9 +19,9 @@ #include "coreplugin/documentmanager.h" #include "qmldesigner/qmldesignerplugin.h" #include "projectexplorer/projectexplorerconstants.h" -#include "projectexplorer/session.h" +#include "projectexplorer/projectmanager.h" #include "projectexplorer/project.h" -#include "projectexplorer/session.h" +#include "projectexplorer/projectmanager.h" #include "projectexplorer/taskhub.h" #include "extensionsystem/pluginmanager.h" @@ -54,8 +54,8 @@ AssetExporterPlugin::AssetExporterPlugin() // Instantiate actions created by the plugin. addActions(); - connect(ProjectExplorer::SessionManager::instance(), - &ProjectExplorer::SessionManager::startupProjectChanged, + connect(ProjectExplorer::ProjectManager::instance(), + &ProjectExplorer::ProjectManager::startupProjectChanged, this, &AssetExporterPlugin::updateActions); updateActions(); @@ -68,7 +68,7 @@ QString AssetExporterPlugin::pluginName() const void AssetExporterPlugin::onExport() { - auto startupProject = ProjectExplorer::SessionManager::startupProject(); + auto startupProject = ProjectExplorer::ProjectManager::startupProject(); if (!startupProject) return; @@ -97,7 +97,7 @@ void AssetExporterPlugin::addActions() void AssetExporterPlugin::updateActions() { - auto project = ProjectExplorer::SessionManager::startupProject(); + auto project = ProjectExplorer::ProjectManager::startupProject(); QAction* const exportAction = Core::ActionManager::command(Constants::EXPORT_QML)->action(); exportAction->setEnabled(project && !project->needsConfiguration()); } diff --git a/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.cpp b/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.cpp index b50bc3cffba..5020b095a34 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.cpp @@ -4,9 +4,10 @@ #include "exportnotification.h" -#include "projectexplorer/project.h" -#include "projectexplorer/projectnodes.h" -#include "utils/runextensions.h" +#include +#include + +#include #include #include @@ -17,19 +18,19 @@ namespace { Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.filePathModel", QtCriticalMsg) Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.filePathModel", QtInfoMsg) -void findQmlFiles(QFutureInterface &f, const Project *project) +void findQmlFiles(QPromise &promise, const Project *project) { - if (!project || f.isCanceled()) + if (!project || promise.isCanceled()) return; int index = 0; - project->files([&f, &index](const Node* node) ->bool { - if (f.isCanceled()) + project->files([&promise, &index](const Node* node) ->bool { + if (promise.isCanceled()) return false; Utils::FilePath path = node->filePath(); bool isComponent = !path.fileName().isEmpty() && path.fileName().front().isUpper(); if (isComponent && node->filePath().endsWith(".ui.qml")) - f.reportResult(path, index++); + promise.addResult(path, index++); return true; }); } @@ -132,7 +133,7 @@ void FilePathModel::processProject() connect(m_preprocessWatcher.get(), &QFutureWatcher::finished, this, &FilePathModel::endResetModel); - QFuture f = Utils::runAsync(&findQmlFiles, m_project); + QFuture f = Utils::asyncRun(&findQmlFiles, m_project); m_preprocessWatcher->setFuture(f); } diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp index c8bb96bb1ac..6d6efbb5d2d 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp @@ -19,7 +19,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h index 90ae46010a7..33ad100c0b1 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h @@ -23,7 +23,7 @@ QT_END_NAMESPACE class StudioQuickWidget; namespace Utils { - class QtcProcess; + class Process; } namespace QmlDesigner { diff --git a/src/plugins/qmldesigner/components/componentcore/changestyleaction.cpp b/src/plugins/qmldesigner/components/componentcore/changestyleaction.cpp index a7f5a06e01b..6ef8bbb6c94 100644 --- a/src/plugins/qmldesigner/components/componentcore/changestyleaction.cpp +++ b/src/plugins/qmldesigner/components/componentcore/changestyleaction.cpp @@ -5,7 +5,7 @@ #include "designermcumanager.h" #include -#include +#include #include #include @@ -14,7 +14,7 @@ namespace QmlDesigner { static QString styleConfigFileName(const QString &qmlFileName) { - ProjectExplorer::Project *currentProject = ProjectExplorer::SessionManager::projectForFile(Utils::FilePath::fromString(qmlFileName)); + ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::projectForFile(Utils::FilePath::fromString(qmlFileName)); if (currentProject) { const QList fileNames = currentProject->files( diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index 918b06c6379..258ae0ba925 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -55,8 +55,8 @@ #include #include +#include #include -#include "utils/qtcprocess.h" #include #include @@ -1670,10 +1670,10 @@ void openEffectMaker(const QString &filePath) if (env.osType() == Utils::OsTypeMac) env.appendOrSet("QSG_RHI_BACKEND", "metal"); - Utils::QtcProcess *qqemProcess = new Utils::QtcProcess(); + Utils::Process *qqemProcess = new Utils::Process(); qqemProcess->setEnvironment(env); qqemProcess->setCommand({ effectMakerPath, arguments }); - QObject::connect(qqemProcess, &Utils::QtcProcess::done, [qqemProcess]() { + QObject::connect(qqemProcess, &Utils::Process::done, [qqemProcess]() { qqemProcess->deleteLater(); }); qqemProcess->start(); diff --git a/src/plugins/qmldesigner/components/componentcore/zoomaction.cpp b/src/plugins/qmldesigner/components/componentcore/zoomaction.cpp index 3d0464778d3..e967be04ec8 100644 --- a/src/plugins/qmldesigner/components/componentcore/zoomaction.cpp +++ b/src/plugins/qmldesigner/components/componentcore/zoomaction.cpp @@ -7,6 +7,8 @@ #include #include +#include + #include #include #include @@ -123,7 +125,7 @@ QWidget *ZoomAction::createWidget(QWidget *parent) { if (!m_combo && parentIsToolBar(parent)) { m_combo = createZoomComboBox(parent); - m_combo->setProperty("hideborder", true); + m_combo->setProperty(Utils::StyleHelper::C_HIDE_BORDER, true); m_combo->setCurrentIndex(m_index); m_combo->setToolTip(m_combo->currentText()); diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp index 105bfed89dd..3bb579273f6 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp @@ -20,7 +20,7 @@ #ifndef QMLDESIGNER_TEST #include -#include +#include #include #include #include @@ -371,7 +371,7 @@ void ContentLibraryView::updateBundleMaterialsQuick3DVersion() #ifndef QMLDESIGNER_TEST if (hasImport && major == -1) { // Import without specifying version, so we take the kit version - auto target = ProjectExplorer::SessionManager::startupTarget(); + auto target = ProjectExplorer::ProjectManager::startupTarget(); if (target) { QtSupport::QtVersion *qtVersion = QtSupport::QtKitAspect::qtVersion(target->kit()); if (qtVersion) { diff --git a/src/plugins/qmldesigner/components/curveeditor/curveeditortoolbar.cpp b/src/plugins/qmldesigner/components/curveeditor/curveeditortoolbar.cpp index 452940aa5f1..27a14968c61 100644 --- a/src/plugins/qmldesigner/components/curveeditor/curveeditortoolbar.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/curveeditortoolbar.cpp @@ -8,7 +8,9 @@ #include "coreplugin/actionmanager/actionmanager.h" #include "coreplugin/icontext.h" #include "theme.h" -#include "utils/id.h" + +#include +#include #include #include @@ -154,8 +156,8 @@ CurveEditorToolBar::CurveEditorToolBar(CurveEditorModel *model, QWidget* parent) m_zoomSlider = new QSlider(Qt::Horizontal); m_zoomSlider->setRange(0, 100); - m_zoomSlider->setProperty("panelwidget", true); - m_zoomSlider->setProperty("panelwidget_singlerow", true); + Utils::StyleHelper::setPanelWidget(m_zoomSlider); + Utils::StyleHelper::setPanelWidgetSingleRow(m_zoomSlider); m_zoomSlider->setFixedWidth(120); connect(m_zoomSlider, &QSlider::valueChanged, [this](int value) { diff --git a/src/plugins/qmldesigner/components/eventlist/eventlist.cpp b/src/plugins/qmldesigner/components/eventlist/eventlist.cpp index 23e60dbda28..a0d7cc89815 100644 --- a/src/plugins/qmldesigner/components/eventlist/eventlist.cpp +++ b/src/plugins/qmldesigner/components/eventlist/eventlist.cpp @@ -8,7 +8,7 @@ #include "bindingproperty.h" #include "metainfo.h" #include "projectexplorer/project.h" -#include "projectexplorer/session.h" +#include "projectexplorer/projectmanager.h" #include "qmldesignerplugin.h" #include "signalhandlerproperty.h" #include "utils/fileutils.h" @@ -23,7 +23,7 @@ namespace QmlDesigner { Utils::FilePath projectFilePath() { if (auto *doc = QmlDesignerPlugin::instance()->documentManager().currentDesignDocument()) { - if (auto *proj = ProjectExplorer::SessionManager::projectForFile(doc->fileName())) + if (auto *proj = ProjectExplorer::ProjectManager::projectForFile(doc->fileName())) return proj->projectDirectory(); } return Utils::FilePath(); diff --git a/src/plugins/qmldesigner/components/formeditor/backgroundaction.cpp b/src/plugins/qmldesigner/components/formeditor/backgroundaction.cpp index 7403c239b35..a9d72be8ddb 100644 --- a/src/plugins/qmldesigner/components/formeditor/backgroundaction.cpp +++ b/src/plugins/qmldesigner/components/formeditor/backgroundaction.cpp @@ -3,6 +3,8 @@ #include "backgroundaction.h" +#include + #include #include @@ -52,7 +54,7 @@ QWidget *BackgroundAction::createWidget(QWidget *parent) connect(comboBox, &QComboBox::currentIndexChanged, this, &BackgroundAction::emitBackgroundChanged); - comboBox->setProperty("hideborder", true); + comboBox->setProperty(Utils::StyleHelper::C_HIDE_BORDER, true); comboBox->setToolTip(tr("Set the color of the canvas.")); m_comboBox = comboBox; return comboBox; diff --git a/src/plugins/qmldesigner/components/formeditor/seekerslider.cpp b/src/plugins/qmldesigner/components/formeditor/seekerslider.cpp index caa7dcbd307..804f061657a 100644 --- a/src/plugins/qmldesigner/components/formeditor/seekerslider.cpp +++ b/src/plugins/qmldesigner/components/formeditor/seekerslider.cpp @@ -4,6 +4,7 @@ #include "seekerslider.h" #include +#include #include #include @@ -15,8 +16,8 @@ namespace QmlDesigner { SeekerSlider::SeekerSlider(QWidget *parent) : QSlider(parent) { - setProperty("panelwidget", true); - setProperty("panelwidget_singlerow", true); + Utils::StyleHelper::setPanelWidget(this); + Utils::StyleHelper::setPanelWidgetSingleRow(this); setOrientation(Qt::Horizontal); setFixedWidth(120); setMaxValue(30); diff --git a/src/plugins/qmldesigner/components/formeditor/toolbox.cpp b/src/plugins/qmldesigner/components/formeditor/toolbox.cpp index 6d20f3374b0..47a9503b2d3 100644 --- a/src/plugins/qmldesigner/components/formeditor/toolbox.cpp +++ b/src/plugins/qmldesigner/components/formeditor/toolbox.cpp @@ -5,6 +5,8 @@ #include +#include + #include #include #include @@ -17,8 +19,8 @@ ToolBox::ToolBox(QWidget *parentWidget) , m_leftToolBar(new QToolBar(QLatin1String("LeftSidebar"), this)) , m_rightToolBar(new QToolBar(QLatin1String("RightSidebar"), this)) { - setProperty("panelwidget", false); - setProperty("panelwidget_singlerow", false); + Utils::StyleHelper::setPanelWidget(this, false); + Utils::StyleHelper::setPanelWidgetSingleRow(this, false); setFixedHeight(Theme::toolbarSize()); m_leftToolBar->setFloatable(true); @@ -29,18 +31,18 @@ ToolBox::ToolBox(QWidget *parentWidget) horizontalLayout->setContentsMargins(0, 0, 0, 0); horizontalLayout->setSpacing(0); - m_leftToolBar->setProperty("panelwidget", false); - m_leftToolBar->setProperty("panelwidget_singlerow", false); + Utils::StyleHelper::setPanelWidget(m_leftToolBar, false); + Utils::StyleHelper::setPanelWidgetSingleRow(m_leftToolBar, false); m_leftToolBar->setFixedHeight(Theme::toolbarSize()); - m_rightToolBar->setProperty("panelwidget", false); - m_rightToolBar->setProperty("panelwidget_singlerow", false); + Utils::StyleHelper::setPanelWidget(m_rightToolBar, false); + Utils::StyleHelper::setPanelWidgetSingleRow(m_rightToolBar, false); m_rightToolBar->setFixedHeight(Theme::toolbarSize()); m_rightToolBar->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); auto stretchToolbar = new QToolBar(this); - stretchToolbar->setProperty("panelwidget", false); - stretchToolbar->setProperty("panelwidget_singlerow", false); + Utils::StyleHelper::setPanelWidget(stretchToolbar, false); + Utils::StyleHelper::setPanelWidgetSingleRow(stretchToolbar, false); stretchToolbar->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); m_rightToolBar->setOrientation(Qt::Horizontal); diff --git a/src/plugins/qmldesigner/components/integration/designdocument.cpp b/src/plugins/qmldesigner/components/integration/designdocument.cpp index aec12e7ceec..a61aeb50a01 100644 --- a/src/plugins/qmldesigner/components/integration/designdocument.cpp +++ b/src/plugins/qmldesigner/components/integration/designdocument.cpp @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include #include #include @@ -409,7 +409,7 @@ bool DesignDocument::isQtForMCUsProject() const Utils::FilePath DesignDocument::projectFolder() const { - ProjectExplorer::Project *currentProject = ProjectExplorer::SessionManager::projectForFile(fileName()); + ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::projectForFile(fileName()); if (currentProject) return currentProject->projectDirectory(); @@ -440,7 +440,7 @@ void DesignDocument::changeToInFileComponentModel(ComponentTextModifier *textMod void DesignDocument::updateQrcFiles() { - ProjectExplorer::Project *currentProject = ProjectExplorer::SessionManager::projectForFile(fileName()); + ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::projectForFile(fileName()); if (currentProject) { const auto srcFiles = currentProject->files(ProjectExplorer::Project::SourceFiles); @@ -726,7 +726,7 @@ void DesignDocument::redo() static Target *getActiveTarget(DesignDocument *designDocument) { - Project *currentProject = SessionManager::projectForFile(designDocument->fileName()); + Project *currentProject = ProjectManager::projectForFile(designDocument->fileName()); if (!currentProject) currentProject = ProjectTree::currentProject(); diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp index 51698fc2a40..91f403f218a 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2019 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + #include "itemlibraryassetimportdialog.h" #include "ui_itemlibraryassetimportdialog.h" @@ -13,7 +14,8 @@ #include "theme.h" #include -#include +#include + #include #include @@ -330,7 +332,7 @@ void ItemLibraryAssetImportDialog::updateImport(const ModelNode &updateNode, // Unable to find original scene source, launch file dialog to locate it QString initialPath; ProjectExplorer::Project *currentProject - = ProjectExplorer::SessionManager::projectForFile( + = ProjectExplorer::ProjectManager::projectForFile( Utils::FilePath::fromString(compFileName)); if (currentProject) initialPath = currentProject->projectDirectory().toString(); diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp index 3bbe60a0f65..d141b88dc85 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp @@ -19,7 +19,7 @@ #include #include -#include +#include #include #include @@ -682,7 +682,7 @@ void ItemLibraryAssetImporter::finalizeQuick3DImport() if (modelManager) { QmlJS::PathsAndLanguages pathToScan; pathToScan.maybeInsert(::Utils::FilePath::fromString(m_importPath)); - result = ::Utils::runAsync(&QmlJS::ModelManagerInterface::importScan, + result = ::Utils::asyncRun(&QmlJS::ModelManagerInterface::importScan, modelManager->workingCopy(), pathToScan, modelManager, diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp index d6b55cd052a..21f2e6aa55d 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include "qmldesignerconstants.h" #include "qmldesignerplugin.h" #include @@ -314,7 +314,7 @@ void ItemLibraryModel::update(ItemLibraryInfo *itemLibraryInfo, Model *model) DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument(); Utils::FilePath qmlFileName = document->fileName(); - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::projectForFile(qmlFileName); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::projectForFile(qmlFileName); QString projectName = project ? project->displayName() : ""; QString materialBundlePrefix = QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1); diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp index 5afc95564a3..d370f6d4547 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp index 97c7285d49d..f1a00a36c7c 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp @@ -29,7 +29,7 @@ #include #include -#include +#include #include #include @@ -470,14 +470,14 @@ const ProjectExplorer::FileNode *NavigatorView::fileNodeForModelNode(const Model { QString filename = node.metaInfo().componentFileName(); Utils::FilePath filePath = Utils::FilePath::fromString(filename); - ProjectExplorer::Project *currentProject = ProjectExplorer::SessionManager::projectForFile( + ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::projectForFile( filePath); if (!currentProject) { filePath = Utils::FilePath::fromString(node.model()->fileUrl().toLocalFile()); /* If the component does not belong to the project then we can fallback to the current file */ - currentProject = ProjectExplorer::SessionManager::projectForFile(filePath); + currentProject = ProjectExplorer::ProjectManager::projectForFile(filePath); } if (!currentProject) return nullptr; diff --git a/src/plugins/qmldesigner/components/propertyeditor/aligndistribute.cpp b/src/plugins/qmldesigner/components/propertyeditor/aligndistribute.cpp index 5d1900c4947..5d8400b47e2 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/aligndistribute.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/aligndistribute.cpp @@ -663,14 +663,15 @@ AlignDistribute::Dimension AlignDistribute::getDimension(Target target) const bool AlignDistribute::executePixelPerfectDialog() const { - QDialogButtonBox::StandardButton pressed = Utils::CheckableMessageBox::doNotAskAgainQuestion( + Utils::CheckableDecider decider(QString("WarnAboutPixelPerfectDistribution")); + + QMessageBox::StandardButton pressed = Utils::CheckableMessageBox::question( Core::ICore::dialogParent(), tr("Cannot Distribute Perfectly"), tr("These objects cannot be distributed to equal pixel values. " "Do you want to distribute to the nearest possible values?"), - Core::ICore::settings(), - "WarnAboutPixelPerfectDistribution"); - return (pressed == QDialogButtonBox::Yes) ? true : false; + decider); + return (pressed == QMessageBox::Yes) ? true : false; } } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp b/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp index fc9dbb56a62..aae0867c915 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp @@ -15,7 +15,7 @@ #include #include -#include +#include static QString s_lastBrowserPath; @@ -23,7 +23,7 @@ FileResourcesModel::FileResourcesModel(QObject *parent) : QObject(parent) , m_filter(QLatin1String("(*.*)")) { - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::projectForFile( + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::projectForFile( QmlDesigner::DocumentManager::currentFilePath()); if (project) { diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp index c920b8b4288..b4ab0583c70 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -430,8 +431,8 @@ void TimelineToolBar::createRightControls() m_scale = new QSlider(this); m_scale->setOrientation(Qt::Horizontal); - m_scale->setProperty("panelwidget", true); - m_scale->setProperty("panelwidget_singlerow", true); + Utils::StyleHelper::setPanelWidget(m_scale); + Utils::StyleHelper::setPanelWidgetSingleRow(m_scale); m_scale->setMaximumWidth(200); m_scale->setMinimumWidth(100); m_scale->setMinimum(0); diff --git a/src/plugins/qmldesigner/components/toolbar/toolbar.cpp b/src/plugins/qmldesigner/components/toolbar/toolbar.cpp index c8e05999ed0..e4f523c55cc 100644 --- a/src/plugins/qmldesigner/components/toolbar/toolbar.cpp +++ b/src/plugins/qmldesigner/components/toolbar/toolbar.cpp @@ -115,7 +115,7 @@ Utils::UniqueObjectPtr ToolBar::createStatusBar() quickWidget->setSource(QUrl::fromLocalFile(qmlFilePath.toFSPathString())); - for (QWidget *w : Core::ICore::statusBar()->findChildren(Qt::FindDirectChildrenOnly)) { + for (QWidget *w : Core::ICore::statusBar()->findChildren(QString(), Qt::FindDirectChildrenOnly)) { w->hide(); } diff --git a/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp b/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp index 1919aeedad6..1b9f1e3271f 100644 --- a/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp +++ b/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include @@ -55,7 +55,7 @@ static CrumbleBar *crumbleBar() static Utils::FilePath getMainUiFile() { - auto project = ProjectExplorer::SessionManager::startupProject(); + auto project = ProjectExplorer::ProjectManager::startupProject(); if (!project) return {}; @@ -327,8 +327,8 @@ ToolBarBackend::ToolBarBackend(QObject *parent) emit isDesignModeEnabledChanged(); }); - connect(ProjectExplorer::SessionManager::instance(), - &ProjectExplorer::SessionManager::startupProjectChanged, + connect(ProjectExplorer::ProjectManager::instance(), + &ProjectExplorer::ProjectManager::startupProjectChanged, this, [this](ProjectExplorer::Project *project) { disconnect(m_kitConnection); @@ -494,7 +494,7 @@ void ToolBarBackend::setCurrentStyle(int index) void ToolBarBackend::setCurrentKit(int index) { - auto project = ProjectExplorer::SessionManager::startupProject(); + auto project = ProjectExplorer::ProjectManager::startupProject(); QTC_ASSERT(project, return ); const auto kits = ProjectExplorer::KitManager::kits(); @@ -508,9 +508,8 @@ void ToolBarBackend::setCurrentKit(int index) if (!newTarget) newTarget = project->addTargetForKit(kit); - ProjectExplorer::SessionManager::setActiveTarget(project, - newTarget, - ProjectExplorer::SetActive::Cascade); + project->setActiveTarget(newTarget, + ProjectExplorer::SetActive::Cascade); emit currentKitChanged(); } @@ -614,7 +613,7 @@ QStringList ToolBarBackend::kits() const int ToolBarBackend::currentKit() const { - if (auto target = ProjectExplorer::SessionManager::startupTarget()) { + if (auto target = ProjectExplorer::ProjectManager::startupTarget()) { auto kit = target->kit(); if (kit) return kits().indexOf(kit->displayName()); @@ -624,11 +623,11 @@ int ToolBarBackend::currentKit() const bool ToolBarBackend::isQt6() const { - if (!ProjectExplorer::SessionManager::startupTarget()) + if (!ProjectExplorer::ProjectManager::startupTarget()) return false; const QmlProjectManager::QmlBuildSystem *buildSystem = qobject_cast( - ProjectExplorer::SessionManager::startupTarget()->buildSystem()); + ProjectExplorer::ProjectManager::startupTarget()->buildSystem()); QTC_ASSERT(buildSystem, return false); const bool isQt6Project = buildSystem && buildSystem->qt6Project(); @@ -638,7 +637,7 @@ bool ToolBarBackend::isQt6() const bool ToolBarBackend::projectOpened() const { - return ProjectExplorer::SessionManager::instance()->startupProject(); + return ProjectExplorer::ProjectManager::instance()->startupProject(); } void ToolBarBackend::launchGlobalAnnotations() diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.cpp index 81d6b9a11b0..194a4ad221a 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.cpp +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -249,8 +250,8 @@ void TransitionEditorToolBar::createRightControls() addSpacing(10); m_scale = new QSlider(this); - m_scale->setProperty("panelwidget", true); - m_scale->setProperty("panelwidget_singlerow", true); + Utils::StyleHelper::setPanelWidget(m_scale); + Utils::StyleHelper::setPanelWidgetSingleRow(m_scale); m_scale->setOrientation(Qt::Horizontal); m_scale->setMaximumWidth(200); m_scale->setMinimumWidth(100); diff --git a/src/plugins/qmldesigner/designercore/include/externaldependenciesinterface.h b/src/plugins/qmldesigner/designercore/include/externaldependenciesinterface.h index 94d90aad187..d4a087f4c7b 100644 --- a/src/plugins/qmldesigner/designercore/include/externaldependenciesinterface.h +++ b/src/plugins/qmldesigner/designercore/include/externaldependenciesinterface.h @@ -6,6 +6,9 @@ #include #include +#include +#include + namespace QmlDesigner { class DesignerSettings; diff --git a/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h b/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h index 0a9af39fc5e..93b4e82a2a2 100644 --- a/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h +++ b/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h @@ -34,7 +34,7 @@ class Target; } namespace Utils { -class QtcProcess; +class Process; } namespace QmlDesigner { @@ -229,7 +229,7 @@ private: // functions void updateWatcher(const QString &path); void handleShaderChanges(); - void handleQsbProcessExit(Utils::QtcProcess *qsbProcess, const QString &shader); + void handleQsbProcessExit(Utils::Process *qsbProcess, const QString &shader); void updateQsbPathToFilterMap(); void updateRotationBlocks(); void maybeResetOnPropertyChange(const PropertyName &name, const ModelNode &node, diff --git a/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h b/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h index a4fa1ab8ed8..eb652a4010f 100644 --- a/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h +++ b/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h @@ -29,6 +29,7 @@ public: QmlObjectNode(const ModelNode &modelNode) : QmlModelNodeFacade(modelNode) {} + virtual ~QmlObjectNode() = default; static bool isValidQmlObjectNode(const ModelNode &modelNode); bool isValid() const; diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index 32c5c2a807f..0864e137c24 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -75,8 +75,8 @@ #include #include +#include #include -#include #include #include @@ -870,7 +870,7 @@ NodeInstance NodeInstanceView::activeStateInstance() const void NodeInstanceView::updateChildren(const NodeAbstractProperty &newPropertyParent) { - const QVector childNodeVector = newPropertyParent.directSubNodes().toVector(); + const QList childNodeVector = newPropertyParent.directSubNodes(); qint32 parentInstanceId = newPropertyParent.parentModelNode().internalId(); @@ -1506,7 +1506,7 @@ void NodeInstanceView::pixmapChanged(const PixmapChangedCommand &command) m_nodeInstanceServer->benchmark(Q_FUNC_INFO + QString::number(renderImageChangeSet.count())); if (!renderImageChangeSet.isEmpty()) - emitInstancesRenderImageChanged(Utils::toList(renderImageChangeSet).toVector()); + emitInstancesRenderImageChanged(Utils::toList(renderImageChangeSet)); if (!containerVector.isEmpty()) { QMultiHash informationChangeHash = informationChanged( @@ -2071,7 +2071,7 @@ void NodeInstanceView::updateWatcher(const QString &path) m_generateQsbFilesTimer.start(); } -void NodeInstanceView::handleQsbProcessExit(Utils::QtcProcess *qsbProcess, const QString &shader) +void NodeInstanceView::handleQsbProcessExit(Utils::Process *qsbProcess, const QString &shader) { --m_remainingQsbTargets; @@ -2172,8 +2172,8 @@ void NodeInstanceView::handleShaderChanges() QStringList args = baseArgs; args.append(outPath.toString()); args.append(shader); - auto qsbProcess = new Utils::QtcProcess(this); - connect(qsbProcess, &Utils::QtcProcess::done, this, [this, qsbProcess, shader] { + auto qsbProcess = new Utils::Process(this); + connect(qsbProcess, &Utils::Process::done, this, [this, qsbProcess, shader] { handleQsbProcessExit(qsbProcess, shader); }); qsbProcess->setWorkingDirectory(srcPath); diff --git a/src/plugins/qmldesigner/designercore/instances/puppetbuildprogressdialog.cpp b/src/plugins/qmldesigner/designercore/instances/puppetbuildprogressdialog.cpp deleted file mode 100644 index 53489fa5b85..00000000000 --- a/src/plugins/qmldesigner/designercore/instances/puppetbuildprogressdialog.cpp +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "puppetbuildprogressdialog.h" -#include "ui_puppetbuildprogressdialog.h" - -#include -#include -#include - -namespace QmlDesigner { - -PuppetBuildProgressDialog::PuppetBuildProgressDialog() : - QDialog(Core::ICore::dialogParent()), - ui(new Ui::PuppetBuildProgressDialog), - m_lineCount(0), - m_useFallbackPuppet(false) -{ - setWindowFlags(Qt::SplashScreen); - setWindowModality(Qt::ApplicationModal); - ui->setupUi(this); - ui->buildProgressBar->setMaximum(85); - connect(ui->useFallbackPuppetPushButton, &QAbstractButton::clicked, this, &PuppetBuildProgressDialog::setUseFallbackPuppet); -} - -PuppetBuildProgressDialog::~PuppetBuildProgressDialog() -{ - delete ui; -} - -void PuppetBuildProgressDialog::setProgress(int progress) -{ - ui->buildProgressBar->setValue(progress); -} - -bool PuppetBuildProgressDialog::useFallbackPuppet() const -{ - return m_useFallbackPuppet; -} - -void PuppetBuildProgressDialog::setErrorOutputFile(const QString &filePath) -{ - ui->openErrorOutputFileLabel->setText(QString::fromLatin1("%2").arg( - filePath, ui->openErrorOutputFileLabel->text())); -} - -void PuppetBuildProgressDialog::setErrorMessage(const QString &message) -{ - ui->label->setText(QString::fromLatin1("%1").arg(message)); - ui->useFallbackPuppetPushButton->setText(tr("OK")); - connect(ui->useFallbackPuppetPushButton, &QAbstractButton::clicked, this, &QDialog::accept); -} - -void PuppetBuildProgressDialog::setUseFallbackPuppet() -{ - m_useFallbackPuppet = true; -} - -void PuppetBuildProgressDialog::newBuildOutput(const QByteArray &standardOutput) -{ - m_lineCount += standardOutput.count('\n'); - setProgress(m_lineCount); -} - -} diff --git a/src/plugins/qmldesigner/designercore/instances/puppetbuildprogressdialog.h b/src/plugins/qmldesigner/designercore/instances/puppetbuildprogressdialog.h deleted file mode 100644 index c7db04eaeca..00000000000 --- a/src/plugins/qmldesigner/designercore/instances/puppetbuildprogressdialog.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -namespace QmlDesigner { - -namespace Ui { -class PuppetBuildProgressDialog; -} - - -class PuppetBuildProgressDialog : public QDialog -{ - Q_OBJECT - -public: - explicit PuppetBuildProgressDialog(); - ~PuppetBuildProgressDialog() override; - - void setProgress(int progress); - void newBuildOutput(const QByteArray &standardOutput); - bool useFallbackPuppet() const; - void setErrorOutputFile(const QString &filePath); - void setErrorMessage(const QString &message); - -private: - void setUseFallbackPuppet(); - -private: - Ui::PuppetBuildProgressDialog *ui; - int m_lineCount; - bool m_useFallbackPuppet; -}; - -} diff --git a/src/plugins/qmldesigner/designercore/instances/puppetbuildprogressdialog.ui b/src/plugins/qmldesigner/designercore/instances/puppetbuildprogressdialog.ui deleted file mode 100644 index 626eb6b5574..00000000000 --- a/src/plugins/qmldesigner/designercore/instances/puppetbuildprogressdialog.ui +++ /dev/null @@ -1,78 +0,0 @@ - - - QmlDesigner::PuppetBuildProgressDialog - - - - 0 - 0 - 695 - 99 - - - - Build Progress - - - true - - - - - - Building Adapter for the current Qt. Happens only once for every Qt installation. - - - - - - - 0 - - - - - - - - - Open error output file - - - true - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Use Fallback QML Emulation Layer - - - false - - - - - - - - - - diff --git a/src/plugins/qmldesigner/designercore/instances/puppetdialog.cpp b/src/plugins/qmldesigner/designercore/instances/puppetdialog.cpp deleted file mode 100644 index 935b5021680..00000000000 --- a/src/plugins/qmldesigner/designercore/instances/puppetdialog.cpp +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "puppetdialog.h" -#include "ui_puppetdialog.h" - -namespace QmlDesigner { - -PuppetDialog::PuppetDialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::PuppetDialog) -{ - ui->setupUi(this); -} - -PuppetDialog::~PuppetDialog() -{ - delete ui; -} - -void PuppetDialog::setDescription(const QString &description) -{ - ui->descriptionLabel->setText(description); -} - -void PuppetDialog::setCopyAndPasteCode(const QString &text) -{ - ui->copyAndPasteTextEdit->setText(text); -} - -void PuppetDialog::warning(QWidget *parent, const QString &title, const QString &description, const QString ©AndPasteCode) -{ - PuppetDialog dialog(parent); - - dialog.setWindowTitle(title); - dialog.setDescription(description); - dialog.setCopyAndPasteCode(copyAndPasteCode); - - dialog.exec(); -} - -} //QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/puppetdialog.h b/src/plugins/qmldesigner/designercore/instances/puppetdialog.h deleted file mode 100644 index 32d0b8deb42..00000000000 --- a/src/plugins/qmldesigner/designercore/instances/puppetdialog.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -namespace QmlDesigner { - -namespace Ui { -class PuppetDialog; -} - - -class PuppetDialog : public QDialog -{ - Q_OBJECT - -public: - explicit PuppetDialog(QWidget *parent = nullptr); - ~PuppetDialog() override; - - void setDescription(const QString &description); - void setCopyAndPasteCode(const QString &text); - - static void warning(QWidget *parent, - const QString &title, - const QString &description, - const QString ©AndPasteCode); - -private: - Ui::PuppetDialog *ui; -}; - -} //QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/puppetdialog.ui b/src/plugins/qmldesigner/designercore/instances/puppetdialog.ui deleted file mode 100644 index b7efa8c26c3..00000000000 --- a/src/plugins/qmldesigner/designercore/instances/puppetdialog.ui +++ /dev/null @@ -1,96 +0,0 @@ - - - QmlDesigner::PuppetDialog - - - - 0 - 0 - 1148 - 344 - - - - Dialog - - - - 12 - - - - - - 0 - 1 - - - - - - - true - - - - - - - - 0 - 1 - - - - true - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Close - - - - - - - - - buttonBox - accepted() - QmlDesigner::PuppetDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - QmlDesigner::PuppetDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/src/plugins/qmldesigner/documentmanager.cpp b/src/plugins/qmldesigner/documentmanager.cpp index d2bf9eac514..e3460622e7d 100644 --- a/src/plugins/qmldesigner/documentmanager.cpp +++ b/src/plugins/qmldesigner/documentmanager.cpp @@ -21,16 +21,18 @@ #include #include #include -#include + #include +#include +#include #include -#include #include #include #include +using namespace ProjectExplorer; using namespace Utils; namespace QmlDesigner { @@ -335,11 +337,11 @@ Utils::FilePath DocumentManager::currentProjectDirPath() Utils::FilePath qmlFileName = QmlDesignerPlugin::instance()->currentDesignDocument()->fileName(); - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::projectForFile(qmlFileName); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::projectForFile(qmlFileName); if (project) return project->projectDirectory(); - const QList projects = ProjectExplorer::SessionManager::projects(); + const QList projects = ProjectExplorer::ProjectManager::projects(); for (auto p : projects) { if (qmlFileName.startsWith(p->projectDirectory().toString())) return p->projectDirectory(); @@ -402,7 +404,7 @@ void DocumentManager::findPathToIsoProFile(bool *iconResourceFileAlreadyExists, QString *resourceFileProPath, const QString &isoIconsQrcFile) { Utils::FilePath qmlFileName = QmlDesignerPlugin::instance()->currentDesignDocument()->fileName(); - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::projectForFile(qmlFileName); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::projectForFile(qmlFileName); ProjectExplorer::Node *node = ProjectExplorer::ProjectTree::nodeForFile(qmlFileName)->parentFolderNode(); ProjectExplorer::Node *iconQrcFileNode = nullptr; @@ -412,8 +414,10 @@ void DocumentManager::findPathToIsoProFile(bool *iconResourceFileAlreadyExists, if (node->isVirtualFolderType() && node->displayName() == "Resources") { ProjectExplorer::FolderNode *virtualFolderNode = node->asFolderNode(); if (QTC_GUARD(virtualFolderNode)) { - for (int subFolderIndex = 0; subFolderIndex < virtualFolderNode->folderNodes().size() && !iconQrcFileNode; ++subFolderIndex) { - ProjectExplorer::FolderNode *subFolderNode = virtualFolderNode->folderNodes().at(subFolderIndex); + QList folderNodes; + virtualFolderNode->forEachFolderNode([&](FolderNode *fn) { folderNodes.append(fn); }); + for (int subFolderIndex = 0; subFolderIndex < folderNodes.size() && !iconQrcFileNode; ++subFolderIndex) { + ProjectExplorer::FolderNode *subFolderNode = folderNodes.at(subFolderIndex); qCDebug(documentManagerLog) << "Checking if" << subFolderNode->displayName() << "(" << subFolderNode << ") is" << isoIconsQrcFile; @@ -492,7 +496,7 @@ bool DocumentManager::belongsToQmakeProject() return false; Utils::FilePath qmlFileName = QmlDesignerPlugin::instance()->currentDesignDocument()->fileName(); - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::projectForFile(qmlFileName); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::projectForFile(qmlFileName); if (!project) return false; diff --git a/src/plugins/qmldesigner/generateresource.cpp b/src/plugins/qmldesigner/generateresource.cpp index 1901a8e2ea1..439e124b705 100644 --- a/src/plugins/qmldesigner/generateresource.cpp +++ b/src/plugins/qmldesigner/generateresource.cpp @@ -12,8 +12,8 @@ #include #include -#include #include +#include #include #include @@ -22,9 +22,9 @@ #include #include +#include #include #include -#include #include #include @@ -177,7 +177,7 @@ QList getFilesFromQrc(QFile *file, bool inProjec static bool runRcc(const CommandLine &command, const FilePath &workingDir, const QString &resourceFile) { - Utils::QtcProcess rccProcess; + Utils::Process rccProcess; rccProcess.setWorkingDirectory(workingDir); rccProcess.setCommand(command); rccProcess.start(); @@ -223,16 +223,16 @@ void GenerateResource::generateMenuEntry(QObject *parent) auto action = new QAction(QCoreApplication::translate("QmlDesigner::GenerateResource", "Generate QRC Resource File..."), parent); - action->setEnabled(ProjectExplorer::SessionManager::startupProject() != nullptr); + action->setEnabled(ProjectExplorer::ProjectManager::startupProject() != nullptr); // todo make it more intelligent when it gets enabled - QObject::connect(ProjectExplorer::SessionManager::instance(), - &ProjectExplorer::SessionManager::startupProjectChanged, [action]() { - action->setEnabled(ProjectExplorer::SessionManager::startupProject()); + QObject::connect(ProjectExplorer::ProjectManager::instance(), + &ProjectExplorer::ProjectManager::startupProjectChanged, [action]() { + action->setEnabled(ProjectExplorer::ProjectManager::startupProject()); }); Core::Command *cmd = Core::ActionManager::registerAction(action, "QmlProject.CreateResource"); QObject::connect(action, &QAction::triggered, [] () { - auto currentProject = ProjectExplorer::SessionManager::startupProject(); + auto currentProject = ProjectExplorer::ProjectManager::startupProject(); QTC_ASSERT(currentProject, return); const FilePath projectPath = currentProject->projectFilePath().parentDir(); @@ -331,16 +331,16 @@ void GenerateResource::generateMenuEntry(QObject *parent) auto rccAction = new QAction(QCoreApplication::translate("QmlDesigner::GenerateResource", "Generate Deployable Package..."), parent); - rccAction->setEnabled(ProjectExplorer::SessionManager::startupProject() != nullptr); - QObject::connect(ProjectExplorer::SessionManager::instance(), - &ProjectExplorer::SessionManager::startupProjectChanged, [rccAction]() { - rccAction->setEnabled(ProjectExplorer::SessionManager::startupProject()); + rccAction->setEnabled(ProjectExplorer::ProjectManager::startupProject() != nullptr); + QObject::connect(ProjectExplorer::ProjectManager::instance(), + &ProjectExplorer::ProjectManager::startupProjectChanged, [rccAction]() { + rccAction->setEnabled(ProjectExplorer::ProjectManager::startupProject()); }); Core::Command *cmd2 = Core::ActionManager::registerAction(rccAction, "QmlProject.CreateRCCResource"); QObject::connect(rccAction, &QAction::triggered, []() { - auto currentProject = ProjectExplorer::SessionManager::startupProject(); + auto currentProject = ProjectExplorer::ProjectManager::startupProject(); QTC_ASSERT(currentProject, return); const FilePath projectPath = currentProject->projectFilePath().parentDir(); diff --git a/src/plugins/qmldesigner/openuiqmlfiledialog.cpp b/src/plugins/qmldesigner/openuiqmlfiledialog.cpp index 6c1dcf132c0..eacb81aa000 100644 --- a/src/plugins/qmldesigner/openuiqmlfiledialog.cpp +++ b/src/plugins/qmldesigner/openuiqmlfiledialog.cpp @@ -30,7 +30,7 @@ OpenUiQmlFileDialog::OpenUiQmlFileDialog(QWidget *parent) : m_listWidget = new QListWidget; - using namespace Utils::Layouting; + using namespace Layouting; Column { tr("You are opening a .qml file in the designer. Do you want to open a .ui.qml file instead?"), diff --git a/src/plugins/qmldesigner/qmldesignerexternaldependencies.cpp b/src/plugins/qmldesigner/qmldesignerexternaldependencies.cpp index a2ffbdc94bf..1bd58991657 100644 --- a/src/plugins/qmldesigner/qmldesignerexternaldependencies.cpp +++ b/src/plugins/qmldesigner/qmldesignerexternaldependencies.cpp @@ -9,14 +9,13 @@ #include #include #include -#include -#include +#include #include #include -#include #include #include #include +#include #include @@ -112,7 +111,7 @@ QString ExternalDependencies::itemLibraryImportUserComponentsTitle() const bool ExternalDependencies::isQt6Import() const { - auto target = ProjectExplorer::SessionManager::startupTarget(); + auto target = ProjectExplorer::ProjectManager::startupTarget(); if (target) { QtSupport::QtVersion *currentQtVersion = QtSupport::QtKitAspect::qtVersion(target->kit()); if (currentQtVersion && currentQtVersion->isValid()) { @@ -125,7 +124,7 @@ bool ExternalDependencies::isQt6Import() const bool ExternalDependencies::hasStartupTarget() const { - auto target = ProjectExplorer::SessionManager::startupTarget(); + auto target = ProjectExplorer::ProjectManager::startupTarget(); if (target) { QtSupport::QtVersion *currentQtVersion = QtSupport::QtKitAspect::qtVersion(target->kit()); if (currentQtVersion && currentQtVersion->isValid()) { @@ -163,7 +162,7 @@ QString createFreeTypeOption(ProjectExplorer::Target *target) PuppetStartData ExternalDependencies::puppetStartData(const Model &model) const { PuppetStartData data; - auto target = ProjectExplorer::SessionManager::startupTarget(); + auto target = ProjectExplorer::ProjectManager::startupTarget(); auto [workingDirectory, puppetPath] = QmlPuppetPaths::qmlPuppetPaths(target, m_designerSettings); data.puppetPath = puppetPath.toString(); @@ -183,7 +182,7 @@ bool ExternalDependencies::instantQmlTextUpdate() const Utils::FilePath ExternalDependencies::qmlPuppetPath() const { - auto target = ProjectExplorer::SessionManager::startupTarget(); + auto target = ProjectExplorer::ProjectManager::startupTarget(); auto [workingDirectory, puppetPath] = QmlPuppetPaths::qmlPuppetPaths(target, m_designerSettings); return puppetPath; } @@ -207,7 +206,7 @@ QString qmlPath(ProjectExplorer::Target *target) std::tuple activeProjectEntries() { - auto project = ProjectExplorer::SessionManager::startupProject(); + auto project = ProjectExplorer::ProjectManager::startupProject(); if (!project) return {}; diff --git a/src/plugins/qmldesigner/qmldesignerplugin.cpp b/src/plugins/qmldesigner/qmldesignerplugin.cpp index 0cff0ffad8f..836b7d2e706 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.cpp +++ b/src/plugins/qmldesigner/qmldesignerplugin.cpp @@ -56,7 +56,7 @@ #include #include #include -#include +#include #include #include #include @@ -388,7 +388,7 @@ ExtensionSystem::IPlugin::ShutdownFlag QmlDesignerPlugin::aboutToShutdown() static QStringList allUiQmlFilesforCurrentProject(const Utils::FilePath &fileName) { QStringList list; - ProjectExplorer::Project *currentProject = ProjectExplorer::SessionManager::projectForFile(fileName); + ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::projectForFile(fileName); if (currentProject) { const QList fileNames = currentProject->files(ProjectExplorer::Project::SourceFiles); @@ -404,7 +404,7 @@ static QStringList allUiQmlFilesforCurrentProject(const Utils::FilePath &fileNam static QString projectPath(const Utils::FilePath &fileName) { QString path; - ProjectExplorer::Project *currentProject = ProjectExplorer::SessionManager::projectForFile(fileName); + ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::projectForFile(fileName); if (currentProject) path = currentProject->projectDirectory().toString(); diff --git a/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp b/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp index ef36b3957a1..4d327b5491d 100644 --- a/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp +++ b/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include #include @@ -246,15 +246,15 @@ QmlDesignerProjectManager::QmlDesignerProjectManager(ExternalDependenciesInterfa QObject::connect(editorManager, &::Core::EditorManager::editorsClosed, [&](const auto &editors) { editorsClosed(editors); }); - auto sessionManager = ::ProjectExplorer::SessionManager::instance(); + auto sessionManager = ::ProjectExplorer::ProjectManager::instance(); QObject::connect(sessionManager, - &::ProjectExplorer::SessionManager::projectAdded, + &::ProjectExplorer::ProjectManager::projectAdded, [&](auto *project) { projectAdded(project); }); QObject::connect(sessionManager, - &::ProjectExplorer::SessionManager::aboutToRemoveProject, + &::ProjectExplorer::ProjectManager::aboutToRemoveProject, [&](auto *project) { aboutToRemoveProject(project); }); QObject::connect(sessionManager, - &::ProjectExplorer::SessionManager::projectRemoved, + &::ProjectExplorer::ProjectManager::projectRemoved, [&](auto *project) { projectRemoved(project); }); QObject::connect(&m_previewImageCacheData->timer, @@ -488,7 +488,7 @@ QmlDesignerProjectManager::ImageCacheData *QmlDesignerProjectManager::imageCache imageCacheData->nodeInstanceCollector.setTarget(target); }; - if (auto project = ProjectExplorer::SessionManager::startupProject(); project) { + if (auto project = ProjectExplorer::ProjectManager::startupProject(); project) { // TODO wrap in function in image cache data m_imageCacheData->meshImageCollector.setTarget(project->activeTarget()); m_imageCacheData->nodeInstanceCollector.setTarget(project->activeTarget()); @@ -497,8 +497,8 @@ QmlDesignerProjectManager::ImageCacheData *QmlDesignerProjectManager::imageCache this, setTargetInImageCache); } - QObject::connect(ProjectExplorer::SessionManager::instance(), - &ProjectExplorer::SessionManager::startupProjectChanged, + QObject::connect(ProjectExplorer::ProjectManager::instance(), + &ProjectExplorer::ProjectManager::startupProjectChanged, this, [=](ProjectExplorer::Project *project) { setTargetInImageCache(activeTarget(project)); diff --git a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp index 969ca8d5a88..ccd9a970096 100644 --- a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp +++ b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include @@ -36,7 +36,7 @@ static void handleAction(const SelectionContext &context) if (context.view()->isAttached()) { if (context.toggled()) { bool skipDeploy = false; - if (const Target *startupTarget = SessionManager::startupTarget()) { + if (const Target *startupTarget = ProjectManager::startupTarget()) { const Kit *kit = startupTarget->kit(); if (kit && (kit->supportedPlatforms().contains(Android::Constants::ANDROID_DEVICE_TYPE) @@ -241,10 +241,10 @@ QWidget *SwitchLanguageComboboxAction::createWidget(QWidget *parent) } } }; - connect(ProjectExplorer::SessionManager::instance(), &ProjectExplorer::SessionManager::startupProjectChanged, + connect(ProjectExplorer::ProjectManager::instance(), &ProjectExplorer::ProjectManager::startupProjectChanged, comboBox, refreshComboBoxFunction); - if (auto project = SessionManager::startupProject()) + if (auto project = ProjectManager::startupProject()) refreshComboBoxFunction(project); // do this after refreshComboBoxFunction so we do not get currentLocaleChanged signals at initialization diff --git a/src/plugins/qmldesigner/settingspage.cpp b/src/plugins/qmldesigner/settingspage.cpp index 52a58e01a5e..8c3222d72c3 100644 --- a/src/plugins/qmldesigner/settingspage.cpp +++ b/src/plugins/qmldesigner/settingspage.cpp @@ -196,7 +196,7 @@ SettingsPageWidget::SettingsPageWidget(ExternalDependencies &externalDependencie m_debugPuppetComboBox = new QComboBox; - using namespace Utils::Layouting; + using namespace Layouting; Column { m_useDefaultPuppetRadioButton, diff --git a/src/plugins/qmldesigner/utils/filedownloader.h b/src/plugins/qmldesigner/utils/filedownloader.h index b1202e57cb8..4ecb6b49671 100644 --- a/src/plugins/qmldesigner/utils/filedownloader.h +++ b/src/plugins/qmldesigner/utils/filedownloader.h @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #pragma once +#include #include #include #include diff --git a/src/plugins/qmldesigner/utils/multifiledownloader.cpp b/src/plugins/qmldesigner/utils/multifiledownloader.cpp index de3e8c52516..6a8ac9761e9 100644 --- a/src/plugins/qmldesigner/utils/multifiledownloader.cpp +++ b/src/plugins/qmldesigner/utils/multifiledownloader.cpp @@ -44,6 +44,11 @@ void MultiFileDownloader::setDownloader(FileDownloader *downloader) }); } +FileDownloader *MultiFileDownloader::downloader() +{ + return m_downloader; +} + void MultiFileDownloader::start() { m_canceled = false; diff --git a/src/plugins/qmldesigner/utils/multifiledownloader.h b/src/plugins/qmldesigner/utils/multifiledownloader.h index 548896dbc8d..794f85538cb 100644 --- a/src/plugins/qmldesigner/utils/multifiledownloader.h +++ b/src/plugins/qmldesigner/utils/multifiledownloader.h @@ -13,7 +13,7 @@ class MultiFileDownloader : public QObject { Q_OBJECT - Q_PROPERTY(FileDownloader *downloader WRITE setDownloader) + Q_PROPERTY(FileDownloader *downloader READ downloader WRITE setDownloader) Q_PROPERTY(bool finished READ finished NOTIFY finishedChanged) Q_PROPERTY(int progress READ progress NOTIFY progressChanged) Q_PROPERTY(QUrl baseUrl READ baseUrl WRITE setBaseUrl NOTIFY baseUrlChanged) @@ -34,6 +34,7 @@ public: void setTargetDirPath(const QString &path); QString targetDirPath() const; void setDownloader(FileDownloader *downloader); + FileDownloader *downloader(); bool finished() const; int progress() const; diff --git a/src/plugins/qmldesignerbase/qmldesignerbase.qbs b/src/plugins/qmldesignerbase/qmldesignerbase.qbs new file mode 100644 index 00000000000..98f4cdf5562 --- /dev/null +++ b/src/plugins/qmldesignerbase/qmldesignerbase.qbs @@ -0,0 +1,40 @@ +import qbs + +QtcPlugin { + name: "QmlDesignerBase" + + Depends { name: "Core" } + Depends { name: "ProjectExplorer" } + Depends { name: "QtSupport" } + Depends { name: "app_version_header" } + Depends { name: "Qt.quickwidgets" } + + files: [ + "qmldesignerbase_global.h", + "qmldesignerbaseplugin.cpp", + "qmldesignerbaseplugin.h", + ] + + Group { + prefix: "studio/" + files: [ + "studiosettingspage.cpp", + "studiosettingspage.h", + "studiostyle.cpp", + "studiostyle.h", + "studioquickwidget.cpp", + "studioquickwidget.h", + ] + } + Group { + prefix: "utils/" + files: [ + "designerpaths.cpp", + "designerpaths.h", + "designersettings.cpp", + "designersettings.h", + "qmlpuppetpaths.cpp", + "qmlpuppetpaths.h", + ] + } +} diff --git a/src/plugins/qmldesignerbase/qmldesignerbaseplugin.cpp b/src/plugins/qmldesignerbase/qmldesignerbaseplugin.cpp index c9af4f22836..c59fcbb2ea1 100644 --- a/src/plugins/qmldesignerbase/qmldesignerbaseplugin.cpp +++ b/src/plugins/qmldesignerbase/qmldesignerbaseplugin.cpp @@ -3,7 +3,7 @@ #include "qmldesignerbaseplugin.h" -#include "studiosettingspage.h" +#include "studio/studiosettingspage.h" #include "studio/studiostyle.h" #include "utils/designersettings.h" diff --git a/src/plugins/qmldesignerbase/qmldesignerbaseplugin.h b/src/plugins/qmldesignerbase/qmldesignerbaseplugin.h index 1418a6421b5..997189dacf6 100644 --- a/src/plugins/qmldesignerbase/qmldesignerbaseplugin.h +++ b/src/plugins/qmldesignerbase/qmldesignerbaseplugin.h @@ -15,7 +15,6 @@ QT_END_NAMESPACE namespace QmlDesigner { - class QMLDESIGNERBASE_EXPORT QmlDesignerBasePlugin final : public ExtensionSystem::IPlugin { Q_OBJECT diff --git a/src/plugins/qmldesignerbase/studio/studiosettingspage.cpp b/src/plugins/qmldesignerbase/studio/studiosettingspage.cpp index affe25e7da2..d94efbd666b 100644 --- a/src/plugins/qmldesignerbase/studio/studiosettingspage.cpp +++ b/src/plugins/qmldesignerbase/studio/studiosettingspage.cpp @@ -178,15 +178,15 @@ void StudioSettingsPage::apply() QSettings *s = Core::ICore::settings(); const QString value = m_pathChooserExamples->filePath().toString(); - if (s->value(Paths::exampleDownloadPath, false).toString() != value) { - s->setValue(Paths::exampleDownloadPath, value); + if (s->value(Paths::exampleDownloadPath.toString(), false).toString() != value) { + s->setValue(Paths::exampleDownloadPath.toString(), value); emit examplesDownloadPathChanged(value); } const QString bundlesPath = m_pathChooserBundles->filePath().toString(); - if (s->value(Paths::bundlesDownloadPath).toString() != bundlesPath) { - s->setValue(Paths::bundlesDownloadPath, bundlesPath); + if (s->value(Paths::bundlesDownloadPath.toString()).toString() != bundlesPath) { + s->setValue(Paths::bundlesDownloadPath.toString(), bundlesPath); emit bundlesDownloadPathChanged(bundlesPath); const QString restartText = tr("Changing bundle path will take effect after restart."); @@ -202,11 +202,11 @@ StudioConfigSettingsPage::StudioConfigSettingsPage() setCategory(Core::Constants::SETTINGS_CATEGORY_CORE); setWidgetCreator([&] { auto page = new StudioSettingsPage; - connect(page, + QObject::connect(page, &StudioSettingsPage::examplesDownloadPathChanged, this, &StudioConfigSettingsPage::examplesDownloadPathChanged); - connect(page, + QObject::connect(page, &StudioSettingsPage::bundlesDownloadPathChanged, this, &StudioConfigSettingsPage::bundlesDownloadPathChanged); diff --git a/src/plugins/qmldesignerbase/studio/studiosettingspage.h b/src/plugins/qmldesignerbase/studio/studiosettingspage.h index 0d0149f1167..075367e4bb1 100644 --- a/src/plugins/qmldesignerbase/studio/studiosettingspage.h +++ b/src/plugins/qmldesignerbase/studio/studiosettingspage.h @@ -34,7 +34,7 @@ private: Utils::PathChooser *m_pathChooserBundles; }; -class QMLDESIGNERBASE_EXPORT StudioConfigSettingsPage : public Core::IOptionsPage +class QMLDESIGNERBASE_EXPORT StudioConfigSettingsPage : public QObject, Core::IOptionsPage { Q_OBJECT diff --git a/src/plugins/qmldesignerbase/studio/studiostyle.h b/src/plugins/qmldesignerbase/studio/studiostyle.h index 4d6424cbef1..c55797354b3 100644 --- a/src/plugins/qmldesignerbase/studio/studiostyle.h +++ b/src/plugins/qmldesignerbase/studio/studiostyle.h @@ -3,7 +3,7 @@ #pragma once -#include "qmldesignerbase_global.h" +#include "../qmldesignerbase_global.h" #include diff --git a/src/plugins/qmldesignerbase/utils/designerpaths.cpp b/src/plugins/qmldesignerbase/utils/designerpaths.cpp index d4ae2a46456..53b4b7ea37e 100644 --- a/src/plugins/qmldesignerbase/utils/designerpaths.cpp +++ b/src/plugins/qmldesignerbase/utils/designerpaths.cpp @@ -31,14 +31,14 @@ Utils::FilePath defaultBundlesPath() QString examplesPathSetting() { return Core::ICore::settings() - ->value(exampleDownloadPath, defaultExamplesPath().toString()) + ->value(exampleDownloadPath.toString(), defaultExamplesPath().toString()) .toString(); } QString bundlesPathSetting() { return Core::ICore::settings() - ->value(bundlesDownloadPath, defaultBundlesPath().toString()) + ->value(bundlesDownloadPath.toString(), defaultBundlesPath().toString()) .toString(); } diff --git a/src/plugins/qmljseditor/qmljscomponentnamedialog.cpp b/src/plugins/qmljseditor/qmljscomponentnamedialog.cpp index ad370633bdd..91bcc0ac89d 100644 --- a/src/plugins/qmljseditor/qmljscomponentnamedialog.cpp +++ b/src/plugins/qmljseditor/qmljscomponentnamedialog.cpp @@ -34,7 +34,7 @@ ComponentNameDialog::ComponentNameDialog(QWidget *parent) : m_checkBox = new QCheckBox(Tr::tr("ui.qml file")); m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - using namespace Utils::Layouting; + using namespace Layouting; Column { Form { Tr::tr("Component name:"), m_componentNameEdit, br, diff --git a/src/plugins/qmljseditor/qmljseditingsettingspage.cpp b/src/plugins/qmljseditor/qmljseditingsettingspage.cpp index 2a0ecb41771..bfb5a002cfa 100644 --- a/src/plugins/qmljseditor/qmljseditingsettingspage.cpp +++ b/src/plugins/qmljseditor/qmljseditingsettingspage.cpp @@ -7,18 +7,24 @@ #include #include +#include +#include +#include +#include #include #include #include +#include #include -#include #include #include #include #include +#include #include #include +#include const char AUTO_FORMAT_ON_SAVE[] = "QmlJSEditor.AutoFormatOnSave"; const char AUTO_FORMAT_ONLY_CURRENT_PROJECT[] = "QmlJSEditor.AutoFormatOnlyCurrentProject"; @@ -31,11 +37,30 @@ const char UIQML_OPEN_MODE[] = "QmlJSEditor.openUiQmlMode"; const char FORMAT_COMMAND[] = "QmlJSEditor.formatCommand"; const char FORMAT_COMMAND_OPTIONS[] = "QmlJSEditor.formatCommandOptions"; const char CUSTOM_COMMAND[] = "QmlJSEditor.useCustomFormatCommand"; +const char CUSTOM_ANALYZER[] = "QmlJSEditor.useCustomAnalyzer"; +const char DISABLED_MESSAGES[] = "QmlJSEditor.disabledMessages"; +const char DISABLED_MESSAGES_NONQUICKUI[] = "QmlJSEditor.disabledMessagesNonQuickUI"; const char DEFAULT_CUSTOM_FORMAT_COMMAND[] = "%{CurrentDocument:Project:QT_HOST_BINS}/qmlformat"; using namespace QmlJSEditor; using namespace QmlJSEditor::Internal; +static QList defaultDisabledMessages() +{ + static const QList disabledByDefault = Utils::transform( + QmlJS::Check::defaultDisabledMessages(), + [](QmlJS::StaticAnalysis::Type t) { return int(t); }); + return disabledByDefault; +} + +static QList defaultDisabledMessagesNonQuickUi() +{ + static const QList disabledForNonQuickUi = Utils::transform( + QmlJS::Check::defaultDisabledMessagesForNonQuickUi(), + [](QmlJS::StaticAnalysis::Type t){ return int(t); }); + return disabledForNonQuickUi; +} + void QmlJsEditingSettings::set() { if (get() != *this) @@ -57,6 +82,17 @@ void QmlJsEditingSettings::fromSettings(QSettings *settings) m_formatCommand = settings->value(FORMAT_COMMAND, {}).toString(); m_formatCommandOptions = settings->value(FORMAT_COMMAND_OPTIONS, {}).toString(); m_useCustomFormatCommand = settings->value(CUSTOM_COMMAND, QVariant(false)).toBool(); + m_useCustomAnalyzer = settings->value(CUSTOM_ANALYZER, QVariant(false)).toBool(); + + m_disabledMessages = Utils::transform( + settings->value(DISABLED_MESSAGES, + QVariant::fromValue(defaultDisabledMessages())).toList(), + [](const QVariant &v){ return v.toInt(); }); + m_disabledMessagesForNonQuickUi = Utils::transform( + settings->value(DISABLED_MESSAGES_NONQUICKUI, + QVariant::fromValue(defaultDisabledMessagesNonQuickUi())).toList(), + [](const QVariant &v) { return v.toInt(); }); + settings->endGroup(); } @@ -80,6 +116,18 @@ void QmlJsEditingSettings::toSettings(QSettings *settings) const CUSTOM_COMMAND, m_useCustomFormatCommand, false); + Utils::QtcSettings::setValueWithDefault(settings, + CUSTOM_ANALYZER, + m_useCustomAnalyzer, + false); + Utils::QtcSettings::setValueWithDefault(settings, + DISABLED_MESSAGES, + Utils::sorted(Utils::toList(m_disabledMessages)), + defaultDisabledMessages()); + Utils::QtcSettings::setValueWithDefault(settings, + DISABLED_MESSAGES_NONQUICKUI, + Utils::sorted(Utils::toList(m_disabledMessagesForNonQuickUi)), + defaultDisabledMessagesNonQuickUi()); settings->endGroup(); QmllsSettingsManager::instance()->checkForChanges(); } @@ -93,7 +141,10 @@ bool QmlJsEditingSettings::equals(const QmlJsEditingSettings &other) const && m_foldAuxData == other.m_foldAuxData && m_qmllsSettings == other.m_qmllsSettings && m_uiQmlOpenMode == other.m_uiQmlOpenMode && m_formatCommand == other.m_formatCommand && m_formatCommandOptions == other.m_formatCommandOptions - && m_useCustomFormatCommand == other.m_useCustomFormatCommand; + && m_useCustomFormatCommand == other.m_useCustomFormatCommand + && m_useCustomAnalyzer == other.m_useCustomAnalyzer + && m_disabledMessages == other.m_disabledMessages + && m_disabledMessagesForNonQuickUi == other.m_disabledMessagesForNonQuickUi; } bool QmlJsEditingSettings::enableContextPane() const @@ -181,12 +232,12 @@ void QmlJsEditingSettings::setUseCustomFormatCommand(bool customCommand) m_useCustomFormatCommand = customCommand; } -QmllsSettings &QmlJsEditingSettings::qmllsSettigs() +QmllsSettings &QmlJsEditingSettings::qmllsSettings() { return m_qmllsSettings; } -const QmllsSettings &QmlJsEditingSettings::qmllsSettigs() const +const QmllsSettings &QmlJsEditingSettings::qmllsSettings() const { return m_qmllsSettings; } @@ -201,6 +252,92 @@ void QmlJsEditingSettings::setUiQmlOpenMode(const QString &mode) m_uiQmlOpenMode = mode; } +bool QmlJsEditingSettings::useCustomAnalyzer() const +{ + return m_useCustomAnalyzer; +} + +void QmlJsEditingSettings::setUseCustomAnalyzer(bool customAnalyzer) +{ + m_useCustomAnalyzer = customAnalyzer; +} + +QSet QmlJsEditingSettings::disabledMessages() const +{ + return m_disabledMessages; +} + +void QmlJsEditingSettings::setDisabledMessages(const QSet &disabled) +{ + m_disabledMessages = disabled; +} + +QSet QmlJsEditingSettings::disabledMessagesForNonQuickUi() const +{ + return m_disabledMessagesForNonQuickUi; +} + +void QmlJsEditingSettings::setDisabledMessagesForNonQuickUi(const QSet &disabled) +{ + m_disabledMessagesForNonQuickUi = disabled; +} + +class AnalyzerMessageItem final : public Utils::TreeItem +{ +public: + AnalyzerMessageItem() = default; + AnalyzerMessageItem(int number, const QString &message) + : m_messageNumber(number) + , m_message(message) + {} + + QVariant data(int column, int role) const final + { + if (role == Qt::DisplayRole) { + if (column == 0) + return QString("M%1").arg(m_messageNumber); + if (column == 2) + return m_message.split('\n').first(); + } else if (role == Qt::CheckStateRole) { + if (column == 0) + return m_checked ? Qt::Checked : Qt::Unchecked; + if (column == 1) + return m_disabledInNonQuickUi ? Qt::Checked : Qt::Unchecked; + } + return TreeItem::data(column, role); + } + + bool setData(int column, const QVariant &value, int role) final + { + if (role == Qt::CheckStateRole) { + if (column == 0) { + m_checked = value.toBool(); + return true; + } + if (column == 1) { + m_disabledInNonQuickUi = value.toBool(); + return true; + } + } + return false; + } + + Qt::ItemFlags flags(int column) const final + { + if (column == 0 || column == 1) + return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; + else + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; + } + + int messageNumber() const { return m_messageNumber; } +private: + int m_messageNumber = -1; + QString m_message; + bool m_checked = true; + bool m_disabledInNonQuickUi = false; +}; + class QmlJsEditingSettingsPageWidget final : public Core::IOptionsPageWidget { public: @@ -239,17 +376,38 @@ public: uiQmlOpenComboBox->setSizeAdjustPolicy(QComboBox::QComboBox::AdjustToContents); useQmlls = new QCheckBox(Tr::tr("Use qmlls (EXPERIMENTAL!)")); - useQmlls->setChecked(s.qmllsSettigs().useQmlls); + useQmlls->setChecked(s.qmllsSettings().useQmlls); useLatestQmlls = new QCheckBox(Tr::tr("Always use latest qmlls")); - useLatestQmlls->setChecked(s.qmllsSettigs().useLatestQmlls); - useLatestQmlls->setEnabled(s.qmllsSettigs().useQmlls); + useLatestQmlls->setChecked(s.qmllsSettings().useLatestQmlls); + useLatestQmlls->setEnabled(s.qmllsSettings().useQmlls); QObject::connect(useQmlls, &QCheckBox::stateChanged, this, [this](int checked) { useLatestQmlls->setEnabled(checked != Qt::Unchecked); }); - using namespace Utils::Layouting; + + useCustomAnalyzer = new QCheckBox(Tr::tr("Use customized static analyzer")); + useCustomAnalyzer->setChecked(s.useCustomAnalyzer()); + analyzerMessageModel = new Utils::TreeModel(this); + analyzerMessageModel->setHeader({Tr::tr("Enabled"), + Tr::tr("Disabled for non Qt Quick UI"), + Tr::tr("Message")}); + analyzerMessagesView = new QTreeView; + analyzerMessagesView->setModel(analyzerMessageModel); + analyzerMessagesView->setEnabled(s.useCustomAnalyzer()); + QObject::connect(useCustomAnalyzer, &QCheckBox::stateChanged, this, [this](int checked){ + analyzerMessagesView->setEnabled(checked != Qt::Unchecked); + }); + analyzerMessagesView->setToolTip(Tr::tr("Enabled checks can be disabled for non Qt Quick UI" + " files,\nbut disabled checks cannot get explicitly" + " enabled for non Qt Quick UI files.")); + analyzerMessagesView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(analyzerMessagesView, &QTreeView::customContextMenuRequested, + this, &QmlJsEditingSettingsPageWidget::showContextMenu); + using namespace Layouting; // clang-format off - const auto formattingGroup = + QWidget *formattingGroup = nullptr; + Column { Group { + bindTo(&formattingGroup), title(Tr::tr("Automatic Formatting on File Save")), Column { autoFormatOnSave, @@ -260,10 +418,7 @@ public: formatCommandOptionsLabel, formatCommandOptions } }, - }; - - Column { - formattingGroup, + }, Group { title(Tr::tr("Qt Quick Toolbars")), Column { pinContextPane, enableContextPane }, @@ -279,16 +434,19 @@ public: title(Tr::tr("Language Server")), Column{useQmlls, useLatestQmlls}, }, + Group { + title(Tr::tr("Static Analyzer")), + Column{ useCustomAnalyzer, analyzerMessagesView }, + }, st, }.attachTo(this); // clang-format on - Utils::VariableChooser::addSupportForChildWidgets(formattingGroup.widget, + Utils::VariableChooser::addSupportForChildWidgets(formattingGroup, Utils::globalMacroExpander()); const auto updateFormatCommandState = [&, formatCommandLabel, formatCommandOptionsLabel] { - const bool enabled = useCustomFormatCommand->isChecked() - && autoFormatOnSave->isChecked(); + const bool enabled = useCustomFormatCommand->isChecked(); formatCommandLabel->setEnabled(enabled); formatCommand->setEnabled(enabled); formatCommandOptionsLabel->setEnabled(enabled); @@ -298,10 +456,11 @@ public: connect(autoFormatOnSave, &QCheckBox::toggled, this, [&, updateFormatCommandState]() { autoFormatOnlyCurrentProject->setEnabled(autoFormatOnSave->isChecked()); - useCustomFormatCommand->setEnabled(autoFormatOnSave->isChecked()); updateFormatCommandState(); }); connect(useCustomFormatCommand, &QCheckBox::toggled, this, updateFormatCommandState); + + populateAnalyzerMessages(s.disabledMessages(), s.disabledMessagesForNonQuickUi()); } void apply() final @@ -316,12 +475,55 @@ public: s.setFormatCommandOptions(formatCommandOptions->text()); s.setFoldAuxData(foldAuxData->isChecked()); s.setUiQmlOpenMode(uiQmlOpenComboBox->currentData().toString()); - s.qmllsSettigs().useQmlls = useQmlls->isChecked(); - s.qmllsSettigs().useLatestQmlls = useLatestQmlls->isChecked(); + s.qmllsSettings().useQmlls = useQmlls->isChecked(); + s.qmllsSettings().useLatestQmlls = useLatestQmlls->isChecked(); + s.setUseCustomAnalyzer(useCustomAnalyzer->isChecked()); + QSet disabled; + QSet disabledForNonQuickUi; + analyzerMessageModel->forAllItems( + [&disabled, &disabledForNonQuickUi](AnalyzerMessageItem *item){ + if (item->data(0, Qt::CheckStateRole) == Qt::Unchecked) + disabled.insert(item->messageNumber()); + if (item->data(1, Qt::CheckStateRole) == Qt::Checked) + disabledForNonQuickUi.insert(item->messageNumber()); + }); + s.setDisabledMessages(disabled); + s.setDisabledMessagesForNonQuickUi(disabledForNonQuickUi); s.set(); } private: + void populateAnalyzerMessages(const QSet &disabled, const QSet &disabledForNonQuickUi) + { + using namespace QmlJS::StaticAnalysis; + auto knownMessages = Utils::sorted(Message::allMessageTypes()); + auto root = analyzerMessageModel->rootItem(); + for (auto msgType : knownMessages) { + const QString msg = Message::prototypeForMessageType(msgType).message; + auto item = new AnalyzerMessageItem(msgType, msg); + item->setData(0, !disabled.contains(msgType), Qt::CheckStateRole); + item->setData(1, disabledForNonQuickUi.contains(msgType), Qt::CheckStateRole); + root->appendChild(item); + } + + for (int column = 0; column < 3; ++column) + analyzerMessagesView->resizeColumnToContents(column); + } + + void showContextMenu(const QPoint &position) + { + QMenu menu; + QAction *reset = new QAction(Tr::tr("Reset to Default"), &menu); + menu.addAction(reset); + connect(reset, &QAction::triggered, this, [this](){ + analyzerMessageModel->clear(); + populateAnalyzerMessages(Utils::toSet(defaultDisabledMessages()), + Utils::toSet(defaultDisabledMessagesNonQuickUi())); + + }); + menu.exec(analyzerMessagesView->mapToGlobal(position)); + } + QCheckBox *autoFormatOnSave; QCheckBox *autoFormatOnlyCurrentProject; QCheckBox *useCustomFormatCommand; @@ -333,6 +535,9 @@ private: QCheckBox *useQmlls; QCheckBox *useLatestQmlls; QComboBox *uiQmlOpenComboBox; + QCheckBox *useCustomAnalyzer; + QTreeView *analyzerMessagesView; + Utils::TreeModel *analyzerMessageModel; }; diff --git a/src/plugins/qmljseditor/qmljseditingsettingspage.h b/src/plugins/qmljseditor/qmljseditingsettingspage.h index 43aa9ee336a..d7f8f78bd30 100644 --- a/src/plugins/qmljseditor/qmljseditingsettingspage.h +++ b/src/plugins/qmljseditor/qmljseditingsettingspage.h @@ -52,12 +52,21 @@ public: bool useCustomFormatCommand() const; void setUseCustomFormatCommand(bool customCommand); - QmllsSettings &qmllsSettigs(); - const QmllsSettings &qmllsSettigs() const; + QmllsSettings &qmllsSettings(); + const QmllsSettings &qmllsSettings() const; const QString uiQmlOpenMode() const; void setUiQmlOpenMode(const QString &mode); + bool useCustomAnalyzer() const; + void setUseCustomAnalyzer(bool customAnalyzer); + + QSet disabledMessages() const; + void setDisabledMessages(const QSet &disabled); + + QSet disabledMessagesForNonQuickUi() const; + void setDisabledMessagesForNonQuickUi(const QSet &disabled); + friend bool operator==(const QmlJsEditingSettings &s1, const QmlJsEditingSettings &s2) { return s1.equals(s2); } friend bool operator!=(const QmlJsEditingSettings &s1, const QmlJsEditingSettings &s2) @@ -70,10 +79,13 @@ private: bool m_autoFormatOnlyCurrentProject = false; bool m_foldAuxData = true; bool m_useCustomFormatCommand = false; + bool m_useCustomAnalyzer = false; QmllsSettings m_qmllsSettings; QString m_uiQmlOpenMode; QString m_formatCommand; QString m_formatCommandOptions; + QSet m_disabledMessages; + QSet m_disabledMessagesForNonQuickUi; }; namespace Internal { diff --git a/src/plugins/qmljseditor/qmljseditorplugin.cpp b/src/plugins/qmljseditor/qmljseditorplugin.cpp index 884670c4faf..d695989825d 100644 --- a/src/plugins/qmljseditor/qmljseditorplugin.cpp +++ b/src/plugins/qmljseditor/qmljseditorplugin.cpp @@ -210,11 +210,6 @@ void QmlJSEditorPlugin::extensionsInitialized() QmllsSettingsManager::instance()->setupAutoupdate(); } -ExtensionSystem::IPlugin::ShutdownFlag QmlJSEditorPlugin::aboutToShutdown() -{ - return IPlugin::aboutToShutdown(); -} - Utils::JsonSchemaManager *QmlJSEditorPlugin::jsonManager() { return &m_instance->d->m_jsonManager; diff --git a/src/plugins/qmljseditor/qmljseditorplugin.h b/src/plugins/qmljseditor/qmljseditorplugin.h index cc1b63fe68a..aa653ac6cb0 100644 --- a/src/plugins/qmljseditor/qmljseditorplugin.h +++ b/src/plugins/qmljseditor/qmljseditorplugin.h @@ -29,7 +29,6 @@ public: private: void initialize() final; void extensionsInitialized() final; - ShutdownFlag aboutToShutdown() final; class QmlJSEditorPluginPrivate *d = nullptr; }; diff --git a/src/plugins/qmljseditor/qmljsfindreferences.cpp b/src/plugins/qmljseditor/qmljsfindreferences.cpp index b37d0e8758d..5e72cd642b9 100644 --- a/src/plugins/qmljseditor/qmljsfindreferences.cpp +++ b/src/plugins/qmljseditor/qmljsfindreferences.cpp @@ -12,8 +12,8 @@ #include #include #include +#include #include -#include #include #include @@ -26,19 +26,10 @@ #include #include -#include "qmljseditorconstants.h" - -#include #include -#include #include -#include -#include -#include #include -#include - using namespace Core; using namespace QmlJS; using namespace QmlJS::AST; @@ -704,7 +695,7 @@ class ProcessFile using Usage = FindReferences::Usage; const QString name; const ObjectValue *scope; - QFutureInterface *future; + QPromise &m_promise; public: // needed by QtConcurrent @@ -714,16 +705,15 @@ public: ProcessFile(const ContextPtr &context, const QString &name, const ObjectValue *scope, - QFutureInterface *future) - : context(context), name(name), scope(scope), future(future) + QPromise &promise) + : context(context), name(name), scope(scope), m_promise(promise) { } QList operator()(const Utils::FilePath &fileName) { QList usages; - if (future->isPaused()) - future->waitForResume(); - if (future->isCanceled()) + m_promise.suspendIfRequested(); + if (m_promise.isCanceled()) return usages; ModelManagerInterface *modelManager = ModelManagerInterface::instance(); Document::Ptr doc = context->snapshot().document(fileName); @@ -739,8 +729,7 @@ public: loc.startLine, loc.startColumn - 1, loc.length)); - if (future->isPaused()) - future->waitForResume(); + m_promise.suspendIfRequested(); return usages; } }; @@ -751,7 +740,7 @@ class SearchFileForType using Usage = FindReferences::Usage; const QString name; const ObjectValue *scope; - QFutureInterface *future; + QPromise &m_promise; public: // needed by QtConcurrent @@ -761,16 +750,15 @@ public: SearchFileForType(const ContextPtr &context, const QString &name, const ObjectValue *scope, - QFutureInterface *future) - : context(context), name(name), scope(scope), future(future) + QPromise &promise) + : context(context), name(name), scope(scope), m_promise(promise) { } QList operator()(const Utils::FilePath &fileName) { QList usages; - if (future->isPaused()) - future->waitForResume(); - if (future->isCanceled()) + m_promise.suspendIfRequested(); + if (m_promise.isCanceled()) return usages; Document::Ptr doc = context->snapshot().document(fileName); if (!doc) @@ -781,8 +769,7 @@ public: const FindTypeUsages::Result results = findUsages(name, scope); for (const SourceLocation &loc : results) usages.append(Usage(fileName, matchingLine(loc.offset, doc->source()), loc.startLine, loc.startColumn - 1, loc.length)); - if (future->isPaused()) - future->waitForResume(); + m_promise.suspendIfRequested(); return usages; } }; @@ -790,7 +777,7 @@ public: class UpdateUI { using Usage = FindReferences::Usage; - QFutureInterface *future; + QPromise &m_promise; public: // needed by QtConcurrent @@ -798,14 +785,13 @@ public: using second_argument_type = const QList &; using result_type = void; - UpdateUI(QFutureInterface *future): future(future) {} + UpdateUI(QPromise &promise): m_promise(promise) {} void operator()(QList &, const QList &usages) { for (const Usage &u : usages) - future->reportResult(u); - - future->setProgressValue(future->progressValue() + 1); + m_promise.addResult(u); + m_promise.setProgressValue(m_promise.future().progressValue() + 1); } }; @@ -817,12 +803,11 @@ FindReferences::FindReferences(QObject *parent) m_watcher.setPendingResultsLimit(1); connect(&m_watcher, &QFutureWatcherBase::resultsReadyAt, this, &FindReferences::displayResults); connect(&m_watcher, &QFutureWatcherBase::finished, this, &FindReferences::searchFinished); - m_synchronizer.setCancelOnWait(true); } FindReferences::~FindReferences() = default; -static void find_helper(QFutureInterface &future, +static void find_helper(QPromise &promise, const ModelManagerInterface::WorkingCopy &workingCopy, Snapshot snapshot, const Utils::FilePath &fileName, @@ -885,7 +870,7 @@ static void find_helper(QFutureInterface &future, } files = Utils::filteredUnique(files); - future.setProgressRange(0, files.size()); + promise.setProgressRange(0, files.size()); // report a dummy usage to indicate the search is starting FindReferences::Usage searchStarting(Utils::FilePath::fromString(replacement), name, 0, 0, 0); @@ -894,10 +879,10 @@ static void find_helper(QFutureInterface &future, const ObjectValue *typeValue = value_cast(findTarget.targetValue()); if (!typeValue) return; - future.reportResult(searchStarting); + promise.addResult(searchStarting); - SearchFileForType process(context, name, typeValue, &future); - UpdateUI reduce(&future); + SearchFileForType process(context, name, typeValue, promise); + UpdateUI reduce(promise); QtConcurrent::blockingMappedReduced > (files, process, reduce); } else { @@ -909,21 +894,21 @@ static void find_helper(QFutureInterface &future, return; if (!scope->className().isEmpty()) searchStarting.lineText.prepend(scope->className() + QLatin1Char('.')); - future.reportResult(searchStarting); + promise.addResult(searchStarting); - ProcessFile process(context, name, scope, &future); - UpdateUI reduce(&future); + ProcessFile process(context, name, scope, promise); + UpdateUI reduce(promise); QtConcurrent::blockingMappedReduced > (files, process, reduce); } - future.setProgressValue(files.size()); + promise.setProgressValue(files.size()); } void FindReferences::findUsages(const Utils::FilePath &fileName, quint32 offset) { ModelManagerInterface *modelManager = ModelManagerInterface::instance(); - QFuture result = Utils::runAsync(&find_helper, ModelManagerInterface::workingCopy(), + QFuture result = Utils::asyncRun(&find_helper, ModelManagerInterface::workingCopy(), modelManager->snapshot(), fileName, offset, QString()); m_watcher.setFuture(result); m_synchronizer.addFuture(result); @@ -940,7 +925,7 @@ void FindReferences::renameUsages(const Utils::FilePath &fileName, if (newName.isNull()) newName = QLatin1String(""); - QFuture result = Utils::runAsync(&find_helper, ModelManagerInterface::workingCopy(), + QFuture result = Utils::asyncRun(&find_helper, ModelManagerInterface::workingCopy(), modelManager->snapshot(), fileName, offset, newName); m_watcher.setFuture(result); m_synchronizer.addFuture(result); @@ -1007,7 +992,7 @@ void FindReferences::displayResults(int first, int last) this, &FindReferences::onReplaceButtonClicked); } connect(m_currentSearch.data(), &SearchResult::activated, - [](const Core::SearchResultItem& item) { + [](const Utils::SearchResultItem &item) { Core::EditorManager::openEditorAtSearchResult(item); }); connect(m_currentSearch.data(), &SearchResult::canceled, this, &FindReferences::cancel); @@ -1028,7 +1013,7 @@ void FindReferences::displayResults(int first, int last) } for (int index = first; index != last; ++index) { Usage result = m_watcher.future().resultAt(index); - SearchResultItem item; + Utils::SearchResultItem item; item.setFilePath(result.path); item.setLineText(result.lineText); item.setMainRange(result.line, result.col, result.len); @@ -1056,7 +1041,8 @@ void FindReferences::setPaused(bool paused) m_watcher.setPaused(paused); } -void FindReferences::onReplaceButtonClicked(const QString &text, const QList &items, bool preserveCase) +void FindReferences::onReplaceButtonClicked(const QString &text, + const Utils::SearchResultItems &items, bool preserveCase) { const Utils::FilePaths filePaths = TextEditor::BaseFileFind::replaceAll(text, items, diff --git a/src/plugins/qmljseditor/qmljsfindreferences.h b/src/plugins/qmljseditor/qmljsfindreferences.h index 22bdff0a954..c45d77ac180 100644 --- a/src/plugins/qmljseditor/qmljsfindreferences.h +++ b/src/plugins/qmljseditor/qmljsfindreferences.h @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -14,10 +15,7 @@ QT_FORWARD_DECLARE_CLASS(QTimer) -namespace Core { -class SearchResultItem; -class SearchResult; -} // namespace Core +namespace Core { class SearchResult; } namespace QmlJSEditor { @@ -64,7 +62,8 @@ private: void searchFinished(); void cancel(); void setPaused(bool paused); - void onReplaceButtonClicked(const QString &text, const QList &items, bool preserveCase); + void onReplaceButtonClicked(const QString &text, const Utils::SearchResultItems &items, + bool preserveCase); QPointer m_currentSearch; QFutureWatcher m_watcher; diff --git a/src/plugins/qmljseditor/qmljsquickfix.h b/src/plugins/qmljseditor/qmljsquickfix.h index 643334a94c7..b050ba4d1f1 100644 --- a/src/plugins/qmljseditor/qmljsquickfix.h +++ b/src/plugins/qmljseditor/qmljsquickfix.h @@ -44,7 +44,7 @@ protected: const QmlJSTools::SemanticInfo &semanticInfo() const; - /// \returns The name of the file for for which this operation is invoked. + /// Returns The name of the file for for which this operation is invoked. Utils::FilePath fileName() const; private: diff --git a/src/plugins/qmljseditor/qmljssemantichighlighter.cpp b/src/plugins/qmljseditor/qmljssemantichighlighter.cpp index 0ca826cb005..67e14dfbcd5 100644 --- a/src/plugins/qmljseditor/qmljssemantichighlighter.cpp +++ b/src/plugins/qmljseditor/qmljssemantichighlighter.cpp @@ -20,9 +20,9 @@ #include #include #include +#include #include #include -#include #include #include @@ -156,11 +156,11 @@ public: AddMessagesHighlights, SkipMessagesHighlights, }; - CollectionTask(QFutureInterface &futureInterface, + CollectionTask(QPromise &promise, const QmlJSTools::SemanticInfo &semanticInfo, const TextEditor::FontSettings &fontSettings, Flags flags) - : m_futureInterface(futureInterface) + : m_promise(promise) , m_semanticInfo(semanticInfo) , m_fontSettings(fontSettings) , m_scopeChain(semanticInfo.scopeChain()) @@ -211,7 +211,7 @@ public: protected: void accept(Node *ast) { - if (m_futureInterface.isCanceled()) + if (m_promise.isCanceled()) return; if (ast) ast->accept(this); @@ -219,7 +219,7 @@ protected: void scopedAccept(Node *ast, Node *child) { - if (m_futureInterface.isCanceled()) + if (m_promise.isCanceled()) return; m_scopeBuilder.push(ast); accept(child); @@ -510,12 +510,13 @@ private: return; Utils::sort(m_uses, sortByLinePredicate); - m_futureInterface.reportResults(m_uses); + for (const SemanticHighlighter::Use &use : std::as_const(m_uses)) + m_promise.addResult(use); m_uses.clear(); m_uses.reserve(chunkSize); } - QFutureInterface &m_futureInterface; + QPromise &m_promise; const QmlJSTools::SemanticInfo &m_semanticInfo; const TextEditor::FontSettings &m_fontSettings; ScopeChain m_scopeChain; @@ -541,7 +542,6 @@ SemanticHighlighter::SemanticHighlighter(QmlJSEditorDocument *document) this, &SemanticHighlighter::applyResults); connect(&m_watcher, &QFutureWatcherBase::finished, this, &SemanticHighlighter::finished); - m_futureSynchronizer.setCancelOnWait(true); } void SemanticHighlighter::rerun(const QmlJSTools::SemanticInfo &semanticInfo) @@ -549,11 +549,8 @@ void SemanticHighlighter::rerun(const QmlJSTools::SemanticInfo &semanticInfo) m_watcher.cancel(); m_startRevision = m_document->document()->revision(); - auto future = Utils::runAsync(QThread::LowestPriority, - &SemanticHighlighter::run, - this, - semanticInfo, - TextEditor::TextEditorSettings::fontSettings()); + auto future = Utils::asyncRun(QThread::LowestPriority, &SemanticHighlighter::run, this, + semanticInfo, TextEditor::TextEditorSettings::fontSettings()); m_watcher.setFuture(future); m_futureSynchronizer.addFuture(future); } @@ -590,11 +587,11 @@ void SemanticHighlighter::finished() m_document->syntaxHighlighter(), m_watcher.future()); } -void SemanticHighlighter::run(QFutureInterface &futureInterface, +void SemanticHighlighter::run(QPromise &promise, const QmlJSTools::SemanticInfo &semanticInfo, const TextEditor::FontSettings &fontSettings) { - CollectionTask task(futureInterface, + CollectionTask task(promise, semanticInfo, fontSettings, (m_enableWarnings ? CollectionTask::AddMessagesHighlights diff --git a/src/plugins/qmljseditor/qmljssemantichighlighter.h b/src/plugins/qmljseditor/qmljssemantichighlighter.h index 6ea1e85c6e6..74c51d12d7f 100644 --- a/src/plugins/qmljseditor/qmljssemantichighlighter.h +++ b/src/plugins/qmljseditor/qmljssemantichighlighter.h @@ -62,7 +62,7 @@ public: private: void applyResults(int from, int to); void finished(); - void run(QFutureInterface &futureInterface, + void run(QPromise &promise, const QmlJSTools::SemanticInfo &semanticInfo, const TextEditor::FontSettings &fontSettings); diff --git a/src/plugins/qmljseditor/qmljssemanticinfoupdater.cpp b/src/plugins/qmljseditor/qmljssemanticinfoupdater.cpp index e0a20c18451..764f125e543 100644 --- a/src/plugins/qmljseditor/qmljssemanticinfoupdater.cpp +++ b/src/plugins/qmljseditor/qmljssemanticinfoupdater.cpp @@ -104,7 +104,7 @@ QmlJSTools::SemanticInfo SemanticInfoUpdater::makeNewSemanticInfo(const QmlJS::D semanticInfo.staticAnalysisMessages = jsonChecker(schema); } } else { - Check checker(doc, semanticInfo.context); + Check checker(doc, semanticInfo.context, Core::ICore::settings()); semanticInfo.staticAnalysisMessages = checker(); } diff --git a/src/plugins/qmljseditor/qmllssettings.cpp b/src/plugins/qmljseditor/qmllssettings.cpp index 572d8aec786..eb98c041fa5 100644 --- a/src/plugins/qmljseditor/qmllssettings.cpp +++ b/src/plugins/qmljseditor/qmllssettings.cpp @@ -90,7 +90,7 @@ void QmllsSettingsManager::setupAutoupdate() void QmllsSettingsManager::checkForChanges() { FilePath newLatest = evaluateLatestQmlls(); - QmllsSettings newSettings = QmlJsEditingSettings::get().qmllsSettigs(); + QmllsSettings newSettings = QmlJsEditingSettings::get().qmllsSettings(); if (m_lastSettings == newSettings && newLatest == m_latestQmlls) return; qCDebug(qmllsLog) << "qmlls settings changed:" << newSettings.useQmlls diff --git a/src/plugins/qmljseditor/qmltaskmanager.cpp b/src/plugins/qmljseditor/qmltaskmanager.cpp index 08019019da0..ca985f4022e 100644 --- a/src/plugins/qmljseditor/qmltaskmanager.cpp +++ b/src/plugins/qmljseditor/qmltaskmanager.cpp @@ -2,9 +2,9 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qmltaskmanager.h" -#include "qmljseditor.h" #include "qmljseditorconstants.h" +#include #include #include #include @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include @@ -57,7 +57,7 @@ static Tasks convertToTasks(const QList &messages, cons return convertToTasks(diagnostics, fileName, category); } -void QmlTaskManager::collectMessages(QFutureInterface &future, +void QmlTaskManager::collectMessages(QPromise &promise, Snapshot snapshot, const QList &projectInfos, ViewerContext vContext, @@ -88,7 +88,7 @@ void QmlTaskManager::collectMessages(QFutureInterface &future fileName, Constants::TASK_CATEGORY_QML_ANALYSIS); - Check checker(document, context); + Check checker(document, context, Core::ICore::settings()); result.tasks += convertToTasks(checker(), fileName, Constants::TASK_CATEGORY_QML_ANALYSIS); @@ -96,8 +96,8 @@ void QmlTaskManager::collectMessages(QFutureInterface &future } if (!result.tasks.isEmpty()) - future.reportResult(result); - if (future.isCanceled()) + promise.addResult(result); + if (promise.isCanceled()) break; } } @@ -127,8 +127,7 @@ void QmlTaskManager::updateMessagesNow(bool updateSemantic) ModelManagerInterface *modelManager = ModelManagerInterface::instance(); // process them - QFuture future = - Utils::runAsync( + QFuture future = Utils::asyncRun( &collectMessages, modelManager->newestSnapshot(), modelManager->projectInfos(), modelManager->defaultVContext(Dialect::AnyLanguage), updateSemantic); m_messageCollector.setFuture(future); diff --git a/src/plugins/qmljseditor/qmltaskmanager.h b/src/plugins/qmljseditor/qmltaskmanager.h index 26fa3bd347f..226fd2203d4 100644 --- a/src/plugins/qmljseditor/qmltaskmanager.h +++ b/src/plugins/qmljseditor/qmltaskmanager.h @@ -45,7 +45,7 @@ private: Utils::FilePath fileName; ProjectExplorer::Tasks tasks; }; - static void collectMessages(QFutureInterface &future, + static void collectMessages(QPromise &promise, QmlJS::Snapshot snapshot, const QList &projectInfos, QmlJS::ViewerContext vContext, diff --git a/src/plugins/qmljstools/qmljsbundleprovider.cpp b/src/plugins/qmljstools/qmljsbundleprovider.cpp index b4902aa960c..623a6f4684c 100644 --- a/src/plugins/qmljstools/qmljsbundleprovider.cpp +++ b/src/plugins/qmljstools/qmljsbundleprovider.cpp @@ -25,7 +25,8 @@ BasicBundleProvider::BasicBundleProvider(QObject *parent) : IBundleProvider(parent) { } -QmlBundle BasicBundleProvider::defaultBundle(const QString &bundleInfoName) +QmlBundle BasicBundleProvider::defaultBundle(const QString &bundleInfoName, + QtSupport::QtVersion *qtVersion) { static bool wroteErrors = false; QmlBundle res; @@ -37,7 +38,8 @@ QmlBundle BasicBundleProvider::defaultBundle(const QString &bundleInfoName) return res; } QStringList errors; - if (!res.readFrom(defaultBundlePath.toString(), &errors) && !wroteErrors) { + bool stripVersions = qtVersion && qtVersion->qtVersion().majorVersion() > 5; + if (!res.readFrom(defaultBundlePath.toString(), stripVersions, &errors) && !wroteErrors) { qWarning() << "BasicBundleProvider: ERROR reading " << defaultBundlePath << " : " << errors; wroteErrors = true; @@ -45,24 +47,31 @@ QmlBundle BasicBundleProvider::defaultBundle(const QString &bundleInfoName) return res; } -QmlBundle BasicBundleProvider::defaultQt5QtQuick2Bundle() +QmlBundle BasicBundleProvider::defaultQt5QtQuick2Bundle(QtSupport::QtVersion *qtVersion) { - return defaultBundle(QLatin1String("qt5QtQuick2-bundle.json")); + QmlBundle result = defaultBundle(QLatin1String("qt5QtQuick2-bundle.json"), qtVersion); + if (!qtVersion || qtVersion->qtVersion().majorVersion() < 6) + return result; + if (Utils::HostOsInfo::isMacHost()) + result.merge(defaultBundle(QLatin1String("qt5QtQuick2ext-macos-bundle.json"), qtVersion)); + if (Utils::HostOsInfo::isWindowsHost()) + result.merge(defaultBundle(QLatin1String("qt5QtQuick2ext-win-bundle.json"), qtVersion)); + return result; } QmlBundle BasicBundleProvider::defaultQbsBundle() { - return defaultBundle(QLatin1String("qbs-bundle.json")); + return defaultBundle(QLatin1String("qbs-bundle.json"), nullptr); } QmlBundle BasicBundleProvider::defaultQmltypesBundle() { - return defaultBundle(QLatin1String("qmltypes-bundle.json")); + return defaultBundle(QLatin1String("qmltypes-bundle.json"), nullptr); } QmlBundle BasicBundleProvider::defaultQmlprojectBundle() { - return defaultBundle(QLatin1String("qmlproject-bundle.json")); + return defaultBundle(QLatin1String("qmlproject-bundle.json"), nullptr); } void BasicBundleProvider::mergeBundlesForKit(ProjectExplorer::Kit *kit @@ -77,7 +86,7 @@ void BasicBundleProvider::mergeBundlesForKit(ProjectExplorer::Kit *kit QtSupport::QtVersion *qtVersion = QtSupport::QtKitAspect::qtVersion(kit); if (!qtVersion) { - QmlBundle b2(defaultQt5QtQuick2Bundle()); + QmlBundle b2(defaultQt5QtQuick2Bundle(qtVersion)); bundles.mergeBundleForLanguage(Dialect::Qml, b2); bundles.mergeBundleForLanguage(Dialect::QmlQtQuick2, b2); bundles.mergeBundleForLanguage(Dialect::QmlQtQuick2Ui, b2); @@ -90,17 +99,18 @@ void BasicBundleProvider::mergeBundlesForKit(ProjectExplorer::Kit *kit qtQuick2Bundles.setNameFilters(QStringList(QLatin1String("*-bundle.json"))); QmlBundle qtQuick2Bundle; QFileInfoList list = qtQuick2Bundles.entryInfoList(); + bool stripVersions = qtVersion->qtVersion().majorVersion() > 5; for (int i = 0; i < list.size(); ++i) { QmlBundle bAtt; QStringList errors; - if (!bAtt.readFrom(list.value(i).filePath(), &errors)) + if (!bAtt.readFrom(list.value(i).filePath(), stripVersions, &errors)) qWarning() << "BasicBundleProvider: ERROR reading " << list[i].filePath() << " : " << errors; qtQuick2Bundle.merge(bAtt); } if (!qtQuick2Bundle.supportedImports().contains(QLatin1String("QtQuick 2."), PersistentTrie::Partial)) { - qtQuick2Bundle.merge(defaultQt5QtQuick2Bundle()); + qtQuick2Bundle.merge(defaultQt5QtQuick2Bundle(qtVersion)); } qtQuick2Bundle.replaceVars(myReplacements); bundles.mergeBundleForLanguage(Dialect::Qml, qtQuick2Bundle); diff --git a/src/plugins/qmljstools/qmljsbundleprovider.h b/src/plugins/qmljstools/qmljsbundleprovider.h index 1f7e6868d0c..d8cd8351508 100644 --- a/src/plugins/qmljstools/qmljsbundleprovider.h +++ b/src/plugins/qmljstools/qmljsbundleprovider.h @@ -19,6 +19,10 @@ class QmlLanguageBundles; class QmlBundle; } // namespace QmlJS +namespace QtSupport { +class QtVersion; +} + namespace QmlJSTools { class QMLJSTOOLS_EXPORT IBundleProvider : public QObject @@ -43,8 +47,9 @@ public: void mergeBundlesForKit(ProjectExplorer::Kit *kit, QmlJS::QmlLanguageBundles &bundles, const QHash &replacements) override; - static QmlJS::QmlBundle defaultBundle(const QString &bundleInfoName); - static QmlJS::QmlBundle defaultQt5QtQuick2Bundle(); + static QmlJS::QmlBundle defaultBundle(const QString &bundleInfoName, + QtSupport::QtVersion *qtVersion); + static QmlJS::QmlBundle defaultQt5QtQuick2Bundle(QtSupport::QtVersion *qtVersion); static QmlJS::QmlBundle defaultQbsBundle(); static QmlJS::QmlBundle defaultQmltypesBundle(); static QmlJS::QmlBundle defaultQmlprojectBundle(); diff --git a/src/plugins/qmljstools/qmljscodestylesettingspage.cpp b/src/plugins/qmljstools/qmljscodestylesettingspage.cpp index cef7df08c3d..fc97aa53c82 100644 --- a/src/plugins/qmljstools/qmljscodestylesettingspage.cpp +++ b/src/plugins/qmljstools/qmljscodestylesettingspage.cpp @@ -5,7 +5,6 @@ #include "qmljscodestylepreferences.h" #include "qmljscodestylepreferenceswidget.h" -#include "qmljsindenter.h" #include "qmljsqtstylecodeformatter.h" #include "qmljstoolsconstants.h" #include "qmljstoolssettings.h" @@ -25,13 +24,13 @@ #include #include +#include using namespace TextEditor; -namespace QmlJSTools { -namespace Internal { +namespace QmlJSTools::Internal { -// ------------------ CppCodeStyleSettingsWidget +// QmlJSCodeStylePreferencesWidget QmlJSCodeStylePreferencesWidget::QmlJSCodeStylePreferencesWidget( const TextEditor::ICodeStylePreferencesFactory *factory, QWidget *parent) @@ -47,7 +46,7 @@ QmlJSCodeStylePreferencesWidget::QmlJSCodeStylePreferencesWidget( decorateEditor(TextEditorSettings::fontSettings()); - using namespace Utils::Layouting; + using namespace Layouting; Row { Column { m_tabPreferencesWidget, @@ -55,7 +54,8 @@ QmlJSCodeStylePreferencesWidget::QmlJSCodeStylePreferencesWidget( st, }, m_previewTextEdit, - }.attachTo(this, WithoutMargins); + noMargin + }.attachTo(this); connect(TextEditorSettings::instance(), &TextEditorSettings::fontSettingsChanged, this, &QmlJSCodeStylePreferencesWidget::decorateEditor); @@ -120,7 +120,51 @@ void QmlJSCodeStylePreferencesWidget::updatePreview() tc.endEditBlock(); } -// ------------------ CppCodeStyleSettingsPage +// QmlJSCodeStyleSettingsPageWidget + +class QmlJSCodeStyleSettingsPageWidget : public Core::IOptionsPageWidget +{ +public: + QmlJSCodeStyleSettingsPageWidget() + { + + QmlJSCodeStylePreferences *originalPreferences + = QmlJSToolsSettings::globalCodeStyle(); + m_preferences.setDelegatingPool(originalPreferences->delegatingPool()); + m_preferences.setCodeStyleSettings(originalPreferences->codeStyleSettings()); + m_preferences.setTabSettings(originalPreferences->tabSettings()); + m_preferences.setCurrentDelegate(originalPreferences->currentDelegate()); + m_preferences.setId(originalPreferences->id()); + + auto vbox = new QVBoxLayout(this); + vbox->addWidget(new CodeStyleEditor( + TextEditorSettings::codeStyleFactory(QmlJSTools::Constants::QML_JS_SETTINGS_ID), + &m_preferences)); + } + + void apply() final + { + QSettings *s = Core::ICore::settings(); + + QmlJSCodeStylePreferences *originalPreferences = QmlJSToolsSettings::globalCodeStyle(); + if (originalPreferences->codeStyleSettings() != m_preferences.codeStyleSettings()) { + originalPreferences->setCodeStyleSettings(m_preferences.codeStyleSettings()); + originalPreferences->toSettings(QLatin1String(QmlJSTools::Constants::QML_JS_SETTINGS_ID), s); + } + if (originalPreferences->tabSettings() != m_preferences.tabSettings()) { + originalPreferences->setTabSettings(m_preferences.tabSettings()); + originalPreferences->toSettings(QLatin1String(QmlJSTools::Constants::QML_JS_SETTINGS_ID), s); + } + if (originalPreferences->currentDelegate() != m_preferences.currentDelegate()) { + originalPreferences->setCurrentDelegate(m_preferences.currentDelegate()); + originalPreferences->toSettings(QLatin1String(QmlJSTools::Constants::QML_JS_SETTINGS_ID), s); + } + } + + QmlJSCodeStylePreferences m_preferences; +}; + +// QmlJSCodeStyleSettingsPage QmlJSCodeStyleSettingsPage::QmlJSCodeStyleSettingsPage() { @@ -129,50 +173,7 @@ QmlJSCodeStyleSettingsPage::QmlJSCodeStyleSettingsPage() setCategory(QmlJSEditor::Constants::SETTINGS_CATEGORY_QML); setDisplayCategory(Tr::tr("Qt Quick")); setCategoryIconPath(":/qmljstools/images/settingscategory_qml.png"); + setWidgetCreator([] { return new QmlJSCodeStyleSettingsPageWidget; }); } -QWidget *QmlJSCodeStyleSettingsPage::widget() -{ - if (!m_widget) { - QmlJSCodeStylePreferences *originalPreferences - = QmlJSToolsSettings::globalCodeStyle(); - m_preferences = new QmlJSCodeStylePreferences(m_widget); - m_preferences->setDelegatingPool(originalPreferences->delegatingPool()); - m_preferences->setCodeStyleSettings(originalPreferences->codeStyleSettings()); - m_preferences->setTabSettings(originalPreferences->tabSettings()); - m_preferences->setCurrentDelegate(originalPreferences->currentDelegate()); - m_preferences->setId(originalPreferences->id()); - m_widget = new CodeStyleEditor(TextEditorSettings::codeStyleFactory(QmlJSTools::Constants::QML_JS_SETTINGS_ID), - m_preferences); - } - return m_widget; -} - -void QmlJSCodeStyleSettingsPage::apply() -{ - if (m_widget) { - QSettings *s = Core::ICore::settings(); - - QmlJSCodeStylePreferences *originalPreferences = QmlJSToolsSettings::globalCodeStyle(); - if (originalPreferences->codeStyleSettings() != m_preferences->codeStyleSettings()) { - originalPreferences->setCodeStyleSettings(m_preferences->codeStyleSettings()); - originalPreferences->toSettings(QLatin1String(QmlJSTools::Constants::QML_JS_SETTINGS_ID), s); - } - if (originalPreferences->tabSettings() != m_preferences->tabSettings()) { - originalPreferences->setTabSettings(m_preferences->tabSettings()); - originalPreferences->toSettings(QLatin1String(QmlJSTools::Constants::QML_JS_SETTINGS_ID), s); - } - if (originalPreferences->currentDelegate() != m_preferences->currentDelegate()) { - originalPreferences->setCurrentDelegate(m_preferences->currentDelegate()); - originalPreferences->toSettings(QLatin1String(QmlJSTools::Constants::QML_JS_SETTINGS_ID), s); - } - } -} - -void QmlJSCodeStyleSettingsPage::finish() -{ - delete m_widget; -} - -} // namespace Internal -} // namespace QmlJSTools +} // QmlJSTools::Internal diff --git a/src/plugins/qmljstools/qmljscodestylesettingspage.h b/src/plugins/qmljstools/qmljscodestylesettingspage.h index e9b244865ba..853d482b95e 100644 --- a/src/plugins/qmljstools/qmljscodestylesettingspage.h +++ b/src/plugins/qmljstools/qmljscodestylesettingspage.h @@ -6,16 +6,8 @@ #include #include -#include -#include - -QT_BEGIN_NAMESPACE -class QSettings; -QT_END_NAMESPACE - namespace TextEditor { class FontSettings; - class CodeStyleEditor; class SimpleCodeStylePreferencesWidget; class SnippetEditorWidget; } @@ -54,14 +46,6 @@ class QmlJSCodeStyleSettingsPage : public Core::IOptionsPage { public: QmlJSCodeStyleSettingsPage(); - - QWidget *widget() override; - void apply() override; - void finish() override; - -private: - QmlJSCodeStylePreferences *m_preferences = nullptr; - QPointer m_widget; }; } // namespace Internal diff --git a/src/plugins/qmljstools/qmljscodestylesettingswidget.cpp b/src/plugins/qmljstools/qmljscodestylesettingswidget.cpp index 455ffcd0f80..9fee7205b3d 100644 --- a/src/plugins/qmljstools/qmljscodestylesettingswidget.cpp +++ b/src/plugins/qmljstools/qmljscodestylesettingswidget.cpp @@ -19,15 +19,18 @@ QmlJSCodeStyleSettingsWidget::QmlJSCodeStyleSettingsWidget(QWidget *parent) m_lineLengthSpinBox->setMinimum(0); m_lineLengthSpinBox->setMaximum(999); - using namespace Utils::Layouting; + using namespace Layouting; + // clang-format off Column { Group { - title(Tr::tr("Qml JS Code Style")), + title(Tr::tr("Other")), Form { Tr::tr("&Line length:"), m_lineLengthSpinBox, br, } - } - }.attachTo(this, WithoutMargins); + }, + noMargin + }.attachTo(this); + // clang-format on connect(m_lineLengthSpinBox, &QSpinBox::valueChanged, this, &QmlJSCodeStyleSettingsWidget::slotSettingsChanged); diff --git a/src/plugins/qmljstools/qmljsfunctionfilter.cpp b/src/plugins/qmljstools/qmljsfunctionfilter.cpp index 4d4ce05fb72..34a60040f3c 100644 --- a/src/plugins/qmljstools/qmljsfunctionfilter.cpp +++ b/src/plugins/qmljstools/qmljsfunctionfilter.cpp @@ -5,80 +5,85 @@ #include "qmljslocatordata.h" #include "qmljstoolstr.h" -#include +#include + #include +#include #include -#include - +using namespace Core; using namespace QmlJSTools::Internal; +using namespace Utils; Q_DECLARE_METATYPE(LocatorData::Entry) -FunctionFilter::FunctionFilter(LocatorData *data, QObject *parent) - : Core::ILocatorFilter(parent) - , m_data(data) +QmlJSFunctionsFilter::QmlJSFunctionsFilter(LocatorData *data) + : m_data(data) { setId("Functions"); setDisplayName(Tr::tr("QML Functions")); + setDescription(Tr::tr("Locates QML functions in any open project.")); setDefaultShortcutString("m"); - setDefaultIncludedByDefault(false); } -FunctionFilter::~FunctionFilter() = default; - -QList FunctionFilter::matchesFor( - QFutureInterface &future, - const QString &entry) +static void matches(QPromise &promise, const LocatorStorage &storage, + const QHash> &locatorEntries) { - QList entries[int(MatchLevel::Count)]; - const Qt::CaseSensitivity caseSensitivityForPrefix = caseSensitivity(entry); - - const QRegularExpression regexp = createRegExp(entry); + const QString input = storage.input(); + const Qt::CaseSensitivity caseSensitivityForPrefix = ILocatorFilter::caseSensitivity(input); + const QRegularExpression regexp = ILocatorFilter::createRegExp(input); if (!regexp.isValid()) - return {}; + return; - const QHash> locatorEntries = m_data->entries(); + LocatorFilterEntries entries[int(ILocatorFilter::MatchLevel::Count)]; for (const QList &items : locatorEntries) { - if (future.isCanceled()) - break; - for (const LocatorData::Entry &info : items) { + if (promise.isCanceled()) + return; + if (info.type != LocatorData::Function) continue; const QRegularExpressionMatch match = regexp.match(info.symbolName); if (match.hasMatch()) { - QVariant id = QVariant::fromValue(info); - Core::LocatorFilterEntry filterEntry(this, info.displayName, id/*, info.icon*/); + LocatorFilterEntry filterEntry; + filterEntry.displayName = info.displayName; + filterEntry.linkForEditor = {info.fileName, info.line, info.column}; filterEntry.extraInfo = info.extraInfo; - filterEntry.highlightInfo = highlightInfo(match); + filterEntry.highlightInfo = ILocatorFilter::highlightInfo(match); - if (filterEntry.displayName.startsWith(entry, caseSensitivityForPrefix)) - entries[int(MatchLevel::Best)].append(filterEntry); - else if (filterEntry.displayName.contains(entry, caseSensitivityForPrefix)) - entries[int(MatchLevel::Better)].append(filterEntry); + if (filterEntry.displayName.startsWith(input, caseSensitivityForPrefix)) + entries[int(ILocatorFilter::MatchLevel::Best)].append(filterEntry); + else if (filterEntry.displayName.contains(input, caseSensitivityForPrefix)) + entries[int(ILocatorFilter::MatchLevel::Better)].append(filterEntry); else - entries[int(MatchLevel::Good)].append(filterEntry); + entries[int(ILocatorFilter::MatchLevel::Good)].append(filterEntry); } } } for (auto &entry : entries) { + if (promise.isCanceled()) + return; + if (entry.size() < 1000) - Utils::sort(entry, Core::LocatorFilterEntry::compareLexigraphically); + Utils::sort(entry, LocatorFilterEntry::compareLexigraphically); } - - return std::accumulate(std::begin(entries), std::end(entries), QList()); + storage.reportOutput(std::accumulate(std::begin(entries), std::end(entries), + LocatorFilterEntries())); } -void FunctionFilter::accept(const Core::LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const +LocatorMatcherTasks QmlJSFunctionsFilter::matchers() { - Q_UNUSED(newText) - Q_UNUSED(selectionStart) - Q_UNUSED(selectionLength) - const LocatorData::Entry entry = qvariant_cast(selection.internalData); - Core::EditorManager::openEditorAt({entry.fileName, entry.line, entry.column}); + using namespace Tasking; + + TreeStorage storage; + + const auto onSetup = [storage, entries = m_data->entries()](Async &async) { + async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer()); + async.setConcurrentCallData(matches, *storage, entries); + }; + + return {{AsyncTask(onSetup), storage}}; } diff --git a/src/plugins/qmljstools/qmljsfunctionfilter.h b/src/plugins/qmljstools/qmljsfunctionfilter.h index 83b93e8f361..3775259fb27 100644 --- a/src/plugins/qmljstools/qmljsfunctionfilter.h +++ b/src/plugins/qmljstools/qmljsfunctionfilter.h @@ -5,27 +5,19 @@ #include -namespace QmlJSTools { -namespace Internal { +namespace QmlJSTools::Internal { class LocatorData; -class FunctionFilter : public Core::ILocatorFilter +class QmlJSFunctionsFilter : public Core::ILocatorFilter { - Q_OBJECT - public: - explicit FunctionFilter(LocatorData *data, QObject *parent = nullptr); - ~FunctionFilter() override; - - QList matchesFor(QFutureInterface &future, - const QString &entry) override; - void accept(const Core::LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const override; + QmlJSFunctionsFilter(LocatorData *data); private: + Core::LocatorMatcherTasks matchers() final; + LocatorData *m_data = nullptr; }; -} // namespace Internal -} // namespace QmlJSTools +} // namespace QmlJSTools::Internal diff --git a/src/plugins/qmljstools/qmljslocatordata.cpp b/src/plugins/qmljstools/qmljslocatordata.cpp index 679a230e8e1..477447c9e36 100644 --- a/src/plugins/qmljstools/qmljslocatordata.cpp +++ b/src/plugins/qmljstools/qmljslocatordata.cpp @@ -4,7 +4,7 @@ #include "qmljslocatordata.h" #include -#include +#include #include #include @@ -40,10 +40,10 @@ LocatorData::LocatorData() connect(manager, &ModelManagerInterface::aboutToRemoveFiles, this, &LocatorData::onAboutToRemoveFiles); - ProjectExplorer::SessionManager *session = ProjectExplorer::SessionManager::instance(); + ProjectExplorer::ProjectManager *session = ProjectExplorer::ProjectManager::instance(); if (session) connect(session, - &ProjectExplorer::SessionManager::projectRemoved, + &ProjectExplorer::ProjectManager::projectRemoved, this, [this](ProjectExplorer::Project *) { m_entries.clear(); }); } diff --git a/src/plugins/qmljstools/qmljsmodelmanager.cpp b/src/plugins/qmljstools/qmljsmodelmanager.cpp index da1d82b73d0..914a4f77945 100644 --- a/src/plugins/qmljstools/qmljsmodelmanager.cpp +++ b/src/plugins/qmljstools/qmljsmodelmanager.cpp @@ -18,10 +18,11 @@ #include #include #include +#include #include #include #include -#include +#include #include #include @@ -273,9 +274,9 @@ void ModelManager::delayedInitialization() connect(cppModelManager, &CppEditor::CppModelManager::documentUpdated, this, &ModelManagerInterface::maybeQueueCppQmlTypeUpdate, Qt::DirectConnection); - connect(SessionManager::instance(), &SessionManager::projectRemoved, + connect(ProjectManager::instance(), &ProjectManager::projectRemoved, this, &ModelManager::removeProjectInfo); - connect(SessionManager::instance(), &SessionManager::startupProjectChanged, + connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, this, &ModelManager::updateDefaultProjectInfo); ViewerContext qbsVContext; @@ -323,7 +324,7 @@ ModelManagerInterface::WorkingCopy ModelManager::workingCopyInternal() const void ModelManager::updateDefaultProjectInfo() { // needs to be performed in the ui thread - Project *currentProject = SessionManager::startupProject(); + Project *currentProject = ProjectManager::startupProject(); setDefaultProject(containsProject(currentProject) ? projectInfo(currentProject) : defaultProjectInfoForProject(currentProject, {}), diff --git a/src/plugins/qmljstools/qmljsrefactoringchanges.h b/src/plugins/qmljstools/qmljsrefactoringchanges.h index d16564832b6..33545e2bfc5 100644 --- a/src/plugins/qmljstools/qmljsrefactoringchanges.h +++ b/src/plugins/qmljstools/qmljsrefactoringchanges.h @@ -24,7 +24,7 @@ public: QmlJS::Document::Ptr qmljsDocument() const; /*! - \returns the offset in the document for the start position of the given + Returns the offset in the document for the start position of the given source location. */ unsigned startOf(const QmlJS::SourceLocation &loc) const; diff --git a/src/plugins/qmljstools/qmljstools.qbs b/src/plugins/qmljstools/qmljstools.qbs index 68c9a9dfc15..d13342e8dd2 100644 --- a/src/plugins/qmljstools/qmljstools.qbs +++ b/src/plugins/qmljstools/qmljstools.qbs @@ -54,9 +54,7 @@ QtcPlugin { "qmljstools.qrc" ] - Group { - name: "Tests" - condition: qtc.testsEnabled + QtcTestFiles { files: ["qmljstools_test.cpp"] } diff --git a/src/plugins/qmljstools/qmljstoolsplugin.cpp b/src/plugins/qmljstools/qmljstoolsplugin.cpp index 574c73190b5..88c72b29e64 100644 --- a/src/plugins/qmljstools/qmljstoolsplugin.cpp +++ b/src/plugins/qmljstools/qmljstoolsplugin.cpp @@ -38,7 +38,7 @@ public: QAction resetCodeModelAction{Tr::tr("Reset Code Model"), nullptr}; LocatorData locatorData; - FunctionFilter functionFilter{&locatorData}; + QmlJSFunctionsFilter functionsFilter{&locatorData}; QmlJSCodeStyleSettingsPage codeStyleSettingsPage; BasicBundleProvider basicBundleProvider; }; diff --git a/src/plugins/qmljstools/qmljstoolssettings.cpp b/src/plugins/qmljstools/qmljstoolssettings.cpp index 093fb676c25..caeb6dfa26f 100644 --- a/src/plugins/qmljstools/qmljstoolssettings.cpp +++ b/src/plugins/qmljstools/qmljstoolssettings.cpp @@ -71,45 +71,6 @@ QmlJSToolsSettings::QmlJSToolsSettings() QSettings *s = Core::ICore::settings(); m_globalCodeStyle->fromSettings(QLatin1String(QmlJSTools::Constants::QML_JS_SETTINGS_ID), s); - // legacy handling start (Qt Creator Version < 2.4) - const bool legacyTransformed = - s->value(QLatin1String("QmlJSTabPreferences/LegacyTransformed"), false).toBool(); - - if (!legacyTransformed) { - // creator 2.4 didn't mark yet the transformation (first run of creator 2.4) - - // we need to transform the settings only if at least one from - // below settings was already written - otherwise we use - // defaults like it would be the first run of creator 2.4 without stored settings - const QStringList groups = s->childGroups(); - const bool needTransform = groups.contains(QLatin1String("textTabPreferences")) || - groups.contains(QLatin1String("QmlJSTabPreferences")); - - if (needTransform) { - const QString currentFallback = s->value(QLatin1String("QmlJSTabPreferences/CurrentFallback")).toString(); - TabSettings legacyTabSettings; - if (currentFallback == QLatin1String("QmlJSGlobal")) { - // no delegate, global overwritten - Utils::fromSettings(QLatin1String("QmlJSTabPreferences"), - QString(), s, &legacyTabSettings); - } else { - // delegating to global - legacyTabSettings = TextEditorSettings::codeStyle()->currentTabSettings(); - } - - // create custom code style out of old settings - ICodeStylePreferences *oldCreator = pool->createCodeStyle( - "legacy", legacyTabSettings, QVariant(), Tr::tr("Old Creator")); - - // change the current delegate and save - m_globalCodeStyle->setCurrentDelegate(oldCreator); - m_globalCodeStyle->toSettings(QLatin1String(QmlJSTools::Constants::QML_JS_SETTINGS_ID), s); - } - // mark old settings as transformed - s->setValue(QLatin1String("QmlJSTabPreferences/LegacyTransformed"), true); - // legacy handling stop - } - // mimetypes to be handled TextEditorSettings::registerMimeTypeForLanguageId(Constants::QML_MIMETYPE, Constants::QML_JS_SETTINGS_ID); TextEditorSettings::registerMimeTypeForLanguageId(Constants::QMLUI_MIMETYPE, Constants::QML_JS_SETTINGS_ID); diff --git a/src/plugins/qmlpreview/qmlpreview.qbs b/src/plugins/qmlpreview/qmlpreview.qbs index 7b0f96dde76..e44f432c41a 100644 --- a/src/plugins/qmlpreview/qmlpreview.qbs +++ b/src/plugins/qmlpreview/qmlpreview.qbs @@ -38,9 +38,7 @@ QtcPlugin { ] } - Group { - name: "Unit tests" - condition: qtc.testsEnabled + QtcTestFiles { prefix: "tests/" files: [ "qmlpreviewclient_test.cpp", diff --git a/src/plugins/qmlpreview/qmlpreviewplugin.cpp b/src/plugins/qmlpreview/qmlpreviewplugin.cpp index 6175eac2a2f..8803475d8a6 100644 --- a/src/plugins/qmlpreview/qmlpreviewplugin.cpp +++ b/src/plugins/qmlpreview/qmlpreviewplugin.cpp @@ -25,10 +25,10 @@ #include #include #include +#include #include #include #include -#include #include #include @@ -150,15 +150,15 @@ QmlPreviewPluginPrivate::QmlPreviewPluginPrivate(QmlPreviewPlugin *parent) Constants::M_BUILDPROJECT); QAction *action = new QAction(Tr::tr("QML Preview"), this); action->setToolTip(Tr::tr("Preview changes to QML code live in your application.")); - action->setEnabled(SessionManager::startupProject() != nullptr); - connect(SessionManager::instance(), &SessionManager::startupProjectChanged, action, + action->setEnabled(ProjectManager::startupProject() != nullptr); + connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, action, &QAction::setEnabled); connect(action, &QAction::triggered, this, [this]() { if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current()) m_localeIsoCode = multiLanguageAspect->currentLocale(); bool skipDeploy = false; - const Kit *kit = SessionManager::startupTarget()->kit(); - if (SessionManager::startupTarget() && kit) + const Kit *kit = ProjectManager::startupTarget()->kit(); + if (ProjectManager::startupTarget() && kit) skipDeploy = kit->supportedPlatforms().contains(Android::Constants::ANDROID_DEVICE_TYPE) || DeviceTypeKitAspect::deviceTypeId(kit) == Android::Constants::ANDROID_DEVICE_TYPE; ProjectExplorerPlugin::runStartupProject(Constants::QML_PREVIEW_RUN_MODE, skipDeploy); diff --git a/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp b/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp index 57088ad19bf..ca43b07a42f 100644 --- a/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp +++ b/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include #include #include @@ -18,7 +18,7 @@ #include #include -#include +#include #include using namespace ProjectExplorer; @@ -88,11 +88,14 @@ QmlPreviewRunner::QmlPreviewRunner(RunControl *runControl, const QmlPreviewRunne if (!runControl->isRunning()) return; - this->connect(runControl, &RunControl::stopped, [this, runControl] { - auto rc = new RunControl(ProjectExplorer::Constants::QML_PREVIEW_RUN_MODE); - rc->copyDataFromRunControl(runControl); - ProjectExplorerPlugin::startRunControl(rc); - }); + this->connect(runControl, + &RunControl::stopped, + ProjectExplorerPlugin::instance(), + [runControl] { + auto rc = new RunControl(ProjectExplorer::Constants::QML_PREVIEW_RUN_MODE); + rc->copyDataFromRunControl(runControl); + ProjectExplorerPlugin::startRunControl(rc); + }); runControl->initiateStop(); }); @@ -124,7 +127,7 @@ QUrl QmlPreviewRunner::serverUrl() const QmlPreviewRunWorkerFactory::QmlPreviewRunWorkerFactory(QmlPreviewPlugin *plugin, const QmlPreviewRunnerSetting *runnerSettings) { - setProducer([this, plugin, runnerSettings](RunControl *runControl) { + setProducer([plugin, runnerSettings](RunControl *runControl) { auto runner = new QmlPreviewRunner(runControl, *runnerSettings); QObject::connect(plugin, &QmlPreviewPlugin::updatePreviews, runner, &QmlPreviewRunner::loadFile); diff --git a/src/plugins/qmlprofiler/qmlprofiler.qbs b/src/plugins/qmlprofiler/qmlprofiler.qbs index 5cc3604a07b..56a1e00cd6a 100644 --- a/src/plugins/qmlprofiler/qmlprofiler.qbs +++ b/src/plugins/qmlprofiler/qmlprofiler.qbs @@ -73,9 +73,7 @@ QtcPlugin { files: "qml/**" } - Group { - name: "Unit tests" - condition: qtc.testsEnabled + QtcTestFiles { prefix: "tests/" files: [ "debugmessagesmodel_test.cpp", "debugmessagesmodel_test.h", diff --git a/src/plugins/qmlprofiler/qmlprofilerdetailsrewriter.cpp b/src/plugins/qmlprofiler/qmlprofilerdetailsrewriter.cpp index 396c8305680..e9f0b1a7807 100644 --- a/src/plugins/qmlprofiler/qmlprofilerdetailsrewriter.cpp +++ b/src/plugins/qmlprofiler/qmlprofilerdetailsrewriter.cpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/plugins/qmlprofiler/qmlprofilerruncontrol.cpp b/src/plugins/qmlprofiler/qmlprofilerruncontrol.cpp index f5c45789d8f..41b67edda78 100644 --- a/src/plugins/qmlprofiler/qmlprofilerruncontrol.cpp +++ b/src/plugins/qmlprofiler/qmlprofilerruncontrol.cpp @@ -20,8 +20,8 @@ #include +#include #include -#include #include #include diff --git a/src/plugins/qmlprofiler/qmlprofilersettings.cpp b/src/plugins/qmlprofiler/qmlprofilersettings.cpp index 481a88ab323..1002479baae 100644 --- a/src/plugins/qmlprofiler/qmlprofilersettings.cpp +++ b/src/plugins/qmlprofiler/qmlprofilersettings.cpp @@ -17,24 +17,32 @@ using namespace Utils; -namespace QmlProfiler { -namespace Internal { +namespace QmlProfiler::Internal { -static QWidget *createQmlConfigWidget(QmlProfilerSettings *settings) +class QmlProfilerOptionsPageWidget : public Core::IOptionsPageWidget { - QmlProfilerSettings &s = *settings; - using namespace Layouting; +public: + explicit QmlProfilerOptionsPageWidget(QmlProfilerSettings *settings) + { + QmlProfilerSettings &s = *settings; - return Form { - s.flushEnabled, - s.flushInterval, - s.aggregateTraces - }.emerge(); -} + using namespace Layouting; + Form { + s.flushEnabled, br, + s.flushInterval, br, + s.aggregateTraces, br, + }.attachTo(this); + } + + void apply() final + { + QmlProfilerPlugin::globalSettings()->writeGlobalSettings(); + } +}; QmlProfilerSettings::QmlProfilerSettings() { - setConfigWidgetCreator([this] { return createQmlConfigWidget(this); }); + setConfigWidgetCreator([this] { return new QmlProfilerOptionsPageWidget(this); }); setSettingsGroup(Constants::ANALYZER); @@ -85,25 +93,9 @@ QmlProfilerOptionsPage::QmlProfilerOptionsPage() setCategory("T.Analyzer"); setDisplayCategory(::Debugger::Tr::tr("Analyzer")); setCategoryIconPath(Analyzer::Icons::SETTINGSCATEGORY_ANALYZER); + setWidgetCreator([] { + return new QmlProfilerOptionsPageWidget(QmlProfilerPlugin::globalSettings()); + }); } -QWidget *QmlProfilerOptionsPage::widget() -{ - // We cannot parent the widget to the options page as it expects a QWidget as parent - if (!m_widget) - m_widget = createQmlConfigWidget(QmlProfilerPlugin::globalSettings()); - return m_widget; -} - -void QmlProfilerOptionsPage::apply() -{ - QmlProfilerPlugin::globalSettings()->writeGlobalSettings(); -} - -void QmlProfilerOptionsPage::finish() -{ - delete m_widget; -} - -} // Internal -} // QmlProfiler +} // QmlProfiler::Internal diff --git a/src/plugins/qmlprofiler/qmlprofilersettings.h b/src/plugins/qmlprofiler/qmlprofilersettings.h index 3b4481cac88..14e35676215 100644 --- a/src/plugins/qmlprofiler/qmlprofilersettings.h +++ b/src/plugins/qmlprofiler/qmlprofilersettings.h @@ -7,15 +7,10 @@ #include -#include - -namespace QmlProfiler { -namespace Internal { +namespace QmlProfiler::Internal { class QmlProfilerSettings : public ProjectExplorer::ISettingsAspect { - Q_OBJECT - public: QmlProfilerSettings(); @@ -31,14 +26,6 @@ class QmlProfilerOptionsPage final : public Core::IOptionsPage { public: QmlProfilerOptionsPage(); - - QWidget *widget() override; - void apply() override; - void finish() override; - -private: - QPointer m_widget; }; -} // Internal -} // QmlProfiler +} // QmlProfiler::Internal diff --git a/src/plugins/qmlprofiler/qmlprofilertool.cpp b/src/plugins/qmlprofiler/qmlprofilertool.cpp index 579a870a4b0..f003d5f0e40 100644 --- a/src/plugins/qmlprofiler/qmlprofilertool.cpp +++ b/src/plugins/qmlprofiler/qmlprofilertool.cpp @@ -39,8 +39,8 @@ #include #include #include +#include #include -#include #include @@ -49,6 +49,7 @@ #include #include #include +#include #include #include @@ -190,14 +191,14 @@ QmlProfilerTool::QmlProfilerTool() d->m_displayFeaturesButton->setIcon(Utils::Icons::FILTER.icon()); d->m_displayFeaturesButton->setToolTip(Tr::tr("Hide or show event categories.")); d->m_displayFeaturesButton->setPopupMode(QToolButton::InstantPopup); - d->m_displayFeaturesButton->setProperty("noArrow", true); + d->m_displayFeaturesButton->setProperty(StyleHelper::C_NO_ARROW, true); d->m_displayFeaturesMenu = new QMenu(d->m_displayFeaturesButton); d->m_displayFeaturesButton->setMenu(d->m_displayFeaturesMenu); connect(d->m_displayFeaturesMenu, &QMenu::triggered, this, &QmlProfilerTool::toggleVisibleFeature); d->m_timeLabel = new QLabel(); - d->m_timeLabel->setProperty("panelwidget", true); + StyleHelper::setPanelWidget(d->m_timeLabel); d->m_timeLabel->setIndent(10); updateTimeDisplay(); connect(d->m_timeLabel, &QObject::destroyed, &d->m_recordingTimer, &QTimer::stop); @@ -540,7 +541,7 @@ ProjectExplorer::RunControl *QmlProfilerTool::attachToWaitingApplication() d->m_viewContainer->perspective()->select(); auto runControl = new RunControl(ProjectExplorer::Constants::QML_PROFILER_RUN_MODE); - runControl->copyDataFromRunConfiguration(SessionManager::startupRunConfiguration()); + runControl->copyDataFromRunConfiguration(ProjectManager::startupRunConfiguration()); auto profiler = new QmlProfilerRunner(runControl); profiler->setServerUrl(serverUrl); diff --git a/src/plugins/qmlprofiler/tests/qmlprofilerclientmanager_test.cpp b/src/plugins/qmlprofiler/tests/qmlprofilerclientmanager_test.cpp index b14b4962e7f..366a6028bb4 100644 --- a/src/plugins/qmlprofiler/tests/qmlprofilerclientmanager_test.cpp +++ b/src/plugins/qmlprofiler/tests/qmlprofilerclientmanager_test.cpp @@ -105,9 +105,9 @@ void QmlProfilerClientManagerTest::testConnectionFailure() QFETCH(QmlProfilerStateManager *, stateManager); QFETCH(QUrl, serverUrl); - QSignalSpy openedSpy(&clientManager, SIGNAL(connectionOpened())); - QSignalSpy closedSpy(&clientManager, SIGNAL(connectionClosed())); - QSignalSpy failedSpy(&clientManager, SIGNAL(connectionFailed())); + QSignalSpy openedSpy(&clientManager, &QmlProfilerClientManager::connectionOpened); + QSignalSpy closedSpy(&clientManager, &QmlProfilerClientManager::connectionClosed); + QSignalSpy failedSpy(&clientManager, &QmlProfilerClientManager::connectionFailed); QVERIFY(!clientManager.isConnected()); @@ -137,9 +137,9 @@ void QmlProfilerClientManagerTest::testConnectionFailure() void QmlProfilerClientManagerTest::testUnresponsiveTcp() { - QSignalSpy openedSpy(&clientManager, SIGNAL(connectionOpened())); - QSignalSpy closedSpy(&clientManager, SIGNAL(connectionClosed())); - QSignalSpy failedSpy(&clientManager, SIGNAL(connectionFailed())); + QSignalSpy openedSpy(&clientManager, &QmlProfilerClientManager::connectionOpened); + QSignalSpy closedSpy(&clientManager, &QmlProfilerClientManager::connectionClosed); + QSignalSpy failedSpy(&clientManager, &QmlProfilerClientManager::connectionFailed); QVERIFY(!clientManager.isConnected()); @@ -150,7 +150,7 @@ void QmlProfilerClientManagerTest::testUnresponsiveTcp() QTcpServer server; server.listen(QHostAddress(serverUrl.host()), serverUrl.port()); - QSignalSpy connectionSpy(&server, SIGNAL(newConnection())); + QSignalSpy connectionSpy(&server, &QTcpServer::newConnection); clientManager.connectToServer(serverUrl); @@ -165,9 +165,9 @@ void QmlProfilerClientManagerTest::testUnresponsiveTcp() void QmlProfilerClientManagerTest::testUnresponsiveLocal() { - QSignalSpy openedSpy(&clientManager, SIGNAL(connectionOpened())); - QSignalSpy closedSpy(&clientManager, SIGNAL(connectionClosed())); - QSignalSpy failedSpy(&clientManager, SIGNAL(connectionFailed())); + QSignalSpy openedSpy(&clientManager, &QmlProfilerClientManager::connectionOpened); + QSignalSpy closedSpy(&clientManager, &QmlProfilerClientManager::connectionClosed); + QSignalSpy failedSpy(&clientManager, &QmlProfilerClientManager::connectionFailed); QVERIFY(!clientManager.isConnected()); @@ -176,7 +176,7 @@ void QmlProfilerClientManagerTest::testUnresponsiveLocal() QUrl socketUrl = Utils::urlFromLocalSocket(); QLocalSocket socket; - QSignalSpy connectionSpy(&socket, SIGNAL(connected())); + QSignalSpy connectionSpy(&socket, &QLocalSocket::connected); clientManager.connectToServer(socketUrl); @@ -209,8 +209,8 @@ void QmlProfilerClientManagerTest::testResponsiveTcp() QUrl serverUrl = Utils::urlFromLocalHostAndFreePort(); - QSignalSpy openedSpy(&clientManager, SIGNAL(connectionOpened())); - QSignalSpy closedSpy(&clientManager, SIGNAL(connectionClosed())); + QSignalSpy openedSpy(&clientManager, &QmlProfilerClientManager::connectionOpened); + QSignalSpy closedSpy(&clientManager, &QmlProfilerClientManager::connectionClosed); QVERIFY(!clientManager.isConnected()); @@ -267,8 +267,8 @@ void QmlProfilerClientManagerTest::testResponsiveLocal() QUrl socketUrl = Utils::urlFromLocalSocket(); - QSignalSpy openedSpy(&clientManager, SIGNAL(connectionOpened())); - QSignalSpy closedSpy(&clientManager, SIGNAL(connectionClosed())); + QSignalSpy openedSpy(&clientManager, &QmlProfilerClientManager::connectionOpened); + QSignalSpy closedSpy(&clientManager, &QmlProfilerClientManager::connectionClosed); QVERIFY(!clientManager.isConnected()); @@ -320,9 +320,9 @@ void QmlProfilerClientManagerTest::testInvalidData() MessageHandler handler(&invalidHelloMessageHandler); Q_UNUSED(handler) - QSignalSpy openedSpy(&clientManager, SIGNAL(connectionOpened())); - QSignalSpy closedSpy(&clientManager, SIGNAL(connectionClosed())); - QSignalSpy failedSpy(&clientManager, SIGNAL(connectionFailed())); + QSignalSpy openedSpy(&clientManager, &QmlProfilerClientManager::connectionOpened); + QSignalSpy closedSpy(&clientManager, &QmlProfilerClientManager::connectionClosed); + QSignalSpy failedSpy(&clientManager, &QmlProfilerClientManager::connectionFailed); QVERIFY(!clientManager.isConnected()); @@ -365,8 +365,8 @@ void QmlProfilerClientManagerTest::testStopRecording() QmlProfilerClientManager clientManager; clientManager.setRetryInterval(10); clientManager.setMaximumRetries(10); - QSignalSpy openedSpy(&clientManager, SIGNAL(connectionOpened())); - QSignalSpy closedSpy(&clientManager, SIGNAL(connectionClosed())); + QSignalSpy openedSpy(&clientManager, &QmlProfilerClientManager::connectionOpened); + QSignalSpy closedSpy(&clientManager, &QmlProfilerClientManager::connectionClosed); QVERIFY(!clientManager.isConnected()); diff --git a/src/plugins/qmlprofiler/tests/qmlprofilerdetailsrewriter_test.cpp b/src/plugins/qmlprofiler/tests/qmlprofilerdetailsrewriter_test.cpp index e01b6ab5ef3..d71836319cc 100644 --- a/src/plugins/qmlprofiler/tests/qmlprofilerdetailsrewriter_test.cpp +++ b/src/plugins/qmlprofiler/tests/qmlprofilerdetailsrewriter_test.cpp @@ -3,15 +3,16 @@ #include "qmlprofilerdetailsrewriter_test.h" +#include #include #include -#include -#include -#include -#include -#include #include -#include +#include +#include +#include +#include +#include + #include #include @@ -157,15 +158,15 @@ void QmlProfilerDetailsRewriterTest::testPopulateFileFinder() // Test that the rewriter will populate from available projects if given nullptr as parameter. DummyProject *project1 = new DummyProject(":/nix.nix"); - ProjectExplorer::SessionManager::addProject(project1); + ProjectExplorer::ProjectManager::addProject(project1); DummyProject *project2 = new DummyProject(":/qmlprofiler/tests/Test.qml"); - ProjectExplorer::SessionManager::addProject(project2); + ProjectExplorer::ProjectManager::addProject(project2); m_rewriter.populateFileFinder(nullptr); QCOMPARE(m_rewriter.getLocalFile("Test.qml"), Utils::FilePath::fromString(":/qmlprofiler/tests/Test.qml")); - ProjectExplorer::SessionManager::removeProject(project1); - ProjectExplorer::SessionManager::removeProject(project2); + ProjectExplorer::ProjectManager::removeProject(project1); + ProjectExplorer::ProjectManager::removeProject(project2); } void QmlProfilerDetailsRewriterTest::seedRewriter() @@ -174,12 +175,11 @@ void QmlProfilerDetailsRewriterTest::seedRewriter() m_modelManager = new QmlJS::ModelManagerInterface(this); QString filename = ":/qmlprofiler/tests/Test.qml"; - QFutureInterface result; QmlJS::PathsAndLanguages lPaths; lPaths.maybeInsert( Utils::FilePath::fromString(QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath)), QmlJS::Dialect::Qml); - QmlJS::ModelManagerInterface::importScan(result, QmlJS::ModelManagerInterface::workingCopy(), + QmlJS::ModelManagerInterface::importScan(QmlJS::ModelManagerInterface::workingCopy(), lPaths, m_modelManager, false); QFile file(filename); @@ -196,11 +196,11 @@ void QmlProfilerDetailsRewriterTest::seedRewriter() ProjectExplorer::SysRootKitAspect::setSysRoot(kit.get(), "/nowhere"); DummyProject *project = new DummyProject(Utils::FilePath::fromString(filename)); - ProjectExplorer::SessionManager::addProject(project); + ProjectExplorer::ProjectManager::addProject(project); m_rewriter.populateFileFinder(project->addTargetForKit(kit.get())); - ProjectExplorer::SessionManager::removeProject(project); + ProjectExplorer::ProjectManager::removeProject(project); } } // namespace Internal diff --git a/src/plugins/qmlprojectmanager/CMakeLists.txt b/src/plugins/qmlprojectmanager/CMakeLists.txt index 21da457c214..ad71e3fbffb 100644 --- a/src/plugins/qmlprojectmanager/CMakeLists.txt +++ b/src/plugins/qmlprojectmanager/CMakeLists.txt @@ -1,7 +1,7 @@ add_qtc_plugin(QmlProjectManager - CONDITION TARGET Qt5::QuickWidgets + CONDITION TARGET Qt::QuickWidgets PLUGIN_CLASS QmlProjectPlugin - DEPENDS QmlJS Qt5::QuickWidgets Utils + DEPENDS QmlJS Qt::QuickWidgets Utils PLUGIN_DEPENDS Core ProjectExplorer QtSupport QmlDesignerBase SOURCES qmlprojectgen/qmlprojectgenerator.cpp qmlprojectgen/qmlprojectgenerator.h diff --git a/src/plugins/qmlprojectmanager/buildsystem/qmlbuildsystem.cpp b/src/plugins/qmlprojectmanager/buildsystem/qmlbuildsystem.cpp index 86645dc8c50..4ccb9ad48f0 100644 --- a/src/plugins/qmlprojectmanager/buildsystem/qmlbuildsystem.cpp +++ b/src/plugins/qmlprojectmanager/buildsystem/qmlbuildsystem.cpp @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include "qmlbuildsystem.h" -#include "qmlprojectconstants.h" +#include "../qmlprojectconstants.h" #include #include @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -26,7 +27,6 @@ #include #include #include -#include #include #include "projectitem/qmlprojectitem.h" @@ -37,6 +37,8 @@ #include "texteditor/textdocument.h" +#include + using namespace ProjectExplorer; namespace QmlProjectManager { diff --git a/src/plugins/qmlprojectmanager/cmakegen/cmakeprojectconverter.cpp b/src/plugins/qmlprojectmanager/cmakegen/cmakeprojectconverter.cpp index 99995bc2abe..21a538c7c51 100644 --- a/src/plugins/qmlprojectmanager/cmakegen/cmakeprojectconverter.cpp +++ b/src/plugins/qmlprojectmanager/cmakegen/cmakeprojectconverter.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + #include "cmakeprojectconverter.h" #include "cmakeprojectconverterdialog.h" #include "generatecmakelists.h" @@ -11,7 +12,7 @@ #include #include -#include +#include #include #include @@ -41,10 +42,10 @@ void CmakeProjectConverter::generateMenuEntry(QObject *parent) Core::Command *cmd = Core::ActionManager::registerAction(action, "QmlProject.ConvertToCmakeProject"); exportMenu->addAction(cmd, QmlProjectManager::Constants::G_EXPORT_CONVERT); - action->setEnabled(isProjectConvertable(ProjectExplorer::SessionManager::startupProject())); - QObject::connect(ProjectExplorer::SessionManager::instance(), - &ProjectExplorer::SessionManager::startupProjectChanged, [action]() { - action->setEnabled(isProjectConvertable(ProjectExplorer::SessionManager::startupProject())); + action->setEnabled(isProjectConvertable(ProjectExplorer::ProjectManager::startupProject())); + QObject::connect(ProjectExplorer::ProjectManager::instance(), + &ProjectExplorer::ProjectManager::startupProjectChanged, [action]() { + action->setEnabled(isProjectConvertable(ProjectExplorer::ProjectManager::startupProject())); }); } @@ -83,7 +84,7 @@ bool CmakeProjectConverter::isProjectCurrentFormat(const ProjectExplorer::Projec void CmakeProjectConverter::onConvertProject() { - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); const QmlProjectManager::QmlProject *qmlProject = qobject_cast(project); if (qmlProject) { diff --git a/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.cpp b/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.cpp index 40d634361e0..b1ce3aaf345 100644 --- a/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.cpp +++ b/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "generatecmakelists.h" + #include "generatecmakelistsconstants.h" #include "cmakegeneratordialog.h" #include "../qmlprojectmanagertr.h" @@ -10,9 +11,9 @@ #include #include -#include #include -#include +#include +#include #include #include @@ -86,7 +87,7 @@ const QString MENU_ITEM_GENERATE = Tr::tr("Generate CMake Build Files..."); const QmlBuildSystem *getBuildSystem() { - auto project = ProjectExplorer::SessionManager::startupProject(); + auto project = ProjectExplorer::ProjectManager::startupProject(); if (project && project->activeTarget() && project->activeTarget()->buildSystem()) { return qobject_cast( project->activeTarget()->buildSystem()); @@ -114,8 +115,8 @@ void generateMenuEntry(QObject *parent) exportMenu->addAction(cmd, QmlProjectManager::Constants::G_EXPORT_GENERATE); action->setEnabled(false); - QObject::connect(ProjectExplorer::SessionManager::instance(), - &ProjectExplorer::SessionManager::startupProjectChanged, + QObject::connect(ProjectExplorer::ProjectManager::instance(), + &ProjectExplorer::ProjectManager::startupProjectChanged, [action]() { if (auto buildSystem = getBuildSystem()) action->setEnabled(!buildSystem->qtForMCUs()); @@ -125,8 +126,7 @@ void generateMenuEntry(QObject *parent) void onGenerateCmakeLists() { trackUsage("generateCMakeProjectDialogOpened"); - - FilePath rootDir = ProjectExplorer::SessionManager::startupProject()->projectDirectory(); + FilePath rootDir = ProjectExplorer::ProjectManager::startupProject()->projectDirectory(); int projectDirErrors = isProjectCorrectlyFormed(rootDir); if (projectDirErrors != NoError) { @@ -340,7 +340,7 @@ const char ADD_SUBDIR[] = "add_subdirectory(%1)\n"; void CmakeFileGenerator::generateMainCmake(const FilePath &rootDir) { //TODO startupProject() may be a terrible way to try to get "current project". It's not necessarily the same thing at all. - QString projectName = ProjectExplorer::SessionManager::startupProject()->displayName(); + QString projectName = ProjectExplorer::ProjectManager::startupProject()->displayName(); QString appName = projectName + "App"; QString fileSection = ""; @@ -559,7 +559,7 @@ bool CmakeFileGenerator::isDirBlacklisted(const FilePath &dir) bool CmakeFileGenerator::includeFile(const FilePath &filePath) { if (m_checkFileIsInProject) { - ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); if (!project->isKnownFile(filePath)) return false; } diff --git a/src/plugins/qmlprojectmanager/projectfilecontenttools.cpp b/src/plugins/qmlprojectmanager/projectfilecontenttools.cpp index 8ae0efe3119..8cde4e2c222 100644 --- a/src/plugins/qmlprojectmanager/projectfilecontenttools.cpp +++ b/src/plugins/qmlprojectmanager/projectfilecontenttools.cpp @@ -18,7 +18,7 @@ QRegularExpression qdsVerRegexp(R"x(qdsVersion: "(.*)")x"); const Utils::FilePaths rootCmakeFiles(ProjectExplorer::Project *project) { if (!project) - project = ProjectExplorer::SessionManager::startupProject(); + project = ProjectExplorer::ProjectManager::startupProject(); if (!project) return {}; return project->projectDirectory().dirEntries({QList({"CMakeLists.txt"}), QDir::Files}); diff --git a/src/plugins/qmlprojectmanager/projectfilecontenttools.h b/src/plugins/qmlprojectmanager/projectfilecontenttools.h index 843912eb7c8..3c3fcb5847e 100644 --- a/src/plugins/qmlprojectmanager/projectfilecontenttools.h +++ b/src/plugins/qmlprojectmanager/projectfilecontenttools.h @@ -6,7 +6,7 @@ #include "qmlprojectmanager_global.h" #include -#include +#include #include diff --git a/src/plugins/qmlprojectmanager/qmlmainfileaspect.cpp b/src/plugins/qmlprojectmanager/qmlmainfileaspect.cpp index 6944f8376a4..5d328fd934a 100644 --- a/src/plugins/qmlprojectmanager/qmlmainfileaspect.cpp +++ b/src/plugins/qmlprojectmanager/qmlmainfileaspect.cpp @@ -55,7 +55,7 @@ QmlMainFileAspect::~QmlMainFileAspect() delete m_fileListCombo; } -void QmlMainFileAspect::addToLayout(Layouting::LayoutBuilder &builder) +void QmlMainFileAspect::addToLayout(Layouting::LayoutItem &parent) { QTC_ASSERT(!m_fileListCombo, delete m_fileListCombo); m_fileListCombo = new QComboBox; @@ -67,7 +67,7 @@ void QmlMainFileAspect::addToLayout(Layouting::LayoutBuilder &builder) this, &QmlMainFileAspect::updateFileComboBox); connect(m_fileListCombo, &QComboBox::activated, this, &QmlMainFileAspect::setMainScript); - builder.addItems({Tr::tr("Main QML file:"), m_fileListCombo.data()}); + parent.addItems({Tr::tr("Main QML file:"), m_fileListCombo.data()}); } void QmlMainFileAspect::toMap(QVariantMap &map) const diff --git a/src/plugins/qmlprojectmanager/qmlmainfileaspect.h b/src/plugins/qmlprojectmanager/qmlmainfileaspect.h index b71fa6784b8..3c75e4744a2 100644 --- a/src/plugins/qmlprojectmanager/qmlmainfileaspect.h +++ b/src/plugins/qmlprojectmanager/qmlmainfileaspect.h @@ -42,7 +42,7 @@ public: Utils::FilePath currentFile; }; - void addToLayout(Utils::Layouting::LayoutBuilder &builder) final; + void addToLayout(Layouting::LayoutItem &parent) final; void toMap(QVariantMap &map) const final; void fromMap(const QVariantMap &map) final; diff --git a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp index 4ca9f9aa626..db0001ac70f 100644 --- a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp +++ b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp @@ -10,8 +10,8 @@ #include #include +#include #include -#include #include static bool isMultilanguagePresent() @@ -114,7 +114,7 @@ void QmlMultiLanguageAspect::fromMap(const QVariantMap &map) QmlMultiLanguageAspect *QmlMultiLanguageAspect::current() { - if (auto project = ProjectExplorer::SessionManager::startupProject()) + if (auto project = ProjectExplorer::ProjectManager::startupProject()) return current(project); return {}; } diff --git a/src/plugins/qmlprojectmanager/qmlproject.cpp b/src/plugins/qmlprojectmanager/qmlproject.cpp index 4e919005c08..31fa3429d21 100644 --- a/src/plugins/qmlprojectmanager/qmlproject.cpp +++ b/src/plugins/qmlprojectmanager/qmlproject.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include #include @@ -22,6 +22,21 @@ #include "qmlprojectmanagerconstants.h" #include "qmlprojectmanagertr.h" #include "utils/algorithm.h" +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include using namespace Core; using namespace ProjectExplorer; @@ -37,15 +52,11 @@ QmlProject::QmlProject(const Utils::FilePath &fileName) setNeedsBuildConfigurations(false); setBuildSystemCreator([](Target *t) { return new QmlBuildSystem(t); }); - // FIXME: why checking this? - // this should not even be the case. if that's possible, then what? - // what are the follow-up actions? - if (!QmlProject::isQtDesignStudio()) - return; - - if (allowOnlySingleProject()) { - Core::EditorManager::closeAllDocuments(); - SessionManager::closeAllProjects(); + if (QmlProject::isQtDesignStudio()) { + if (allowOnlySingleProject()) { + EditorManager::closeAllDocuments(); + ProjectManager::closeAllProjects(); + } } connect(this, &QmlProject::anyParsingFinished, this, &QmlProject::parsingFinished); @@ -150,7 +161,7 @@ bool QmlProject::setKitWithVersion(const int qtMajorVersion, const QList } if (target) - SessionManager::setActiveTarget(this, target, SetActive::NoCascade); + target->project()->setActiveTarget(target, SetActive::NoCascade); return true; } @@ -237,7 +248,7 @@ bool QmlProject::allowOnlySingleProject() { QSettings *settings = Core::ICore::settings(); auto key = "QML/Designer/AllowMultipleProjects"; - return !settings->value(key, false).toBool(); + return !settings->value(QString::fromUtf8(key), false).toBool(); } } // namespace QmlProjectManager diff --git a/src/plugins/qmlprojectmanager/qmlprojectmanager.qbs b/src/plugins/qmlprojectmanager/qmlprojectmanager.qbs index d4f3b32cc37..9e6f3babd0b 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectmanager.qbs +++ b/src/plugins/qmlprojectmanager/qmlprojectmanager.qbs @@ -9,6 +9,7 @@ QtcPlugin { Depends { name: "Core" } Depends { name: "ProjectExplorer" } + Depends { name: "QmlDesignerBase" } Depends { name: "QtSupport" } Depends { name: "TextEditor" } @@ -25,7 +26,6 @@ QtcPlugin { "qmlprojectconstants.h", "qmlprojectmanager_global.h", "qmlprojectmanagertr.h", "qmlprojectmanagerconstants.h", - "qmlprojectnodes.cpp", "qmlprojectnodes.h", "qmlprojectplugin.cpp", "qmlprojectplugin.h", "qmlprojectrunconfiguration.cpp", "qmlprojectrunconfiguration.h", project.ide_source_tree + "/src/share/3rdparty/studiofonts/studiofonts.qrc" @@ -39,7 +39,7 @@ QtcPlugin { "qmlbuildsystem.cpp", "qmlbuildsystem.h", "projectitem/filefilteritems.cpp", "projectitem/filefilteritems.h", "projectitem/qmlprojectitem.cpp", "projectitem/qmlprojectitem.h", - "projectitem/converters.h", + "projectitem/converters.cpp", "projectitem/converters.h", "projectnode/qmlprojectnodes.cpp", "projectnode/qmlprojectnodes.h" ] } diff --git a/src/plugins/qmlprojectmanager/qmlprojectplugin.cpp b/src/plugins/qmlprojectmanager/qmlprojectplugin.cpp index 0a87ef9bc90..2876c3c366a 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectplugin.cpp +++ b/src/plugins/qmlprojectmanager/qmlprojectplugin.cpp @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include #include @@ -40,7 +40,7 @@ #include #include -#include +#include #include #include @@ -115,10 +115,10 @@ void QmlProjectPlugin::openQDS(const Utils::FilePath &fileName) qputenv(Constants::enviromentLaunchedQDS, "true"); //-a and -client arguments help to append project to open design studio application if (Utils::HostOsInfo::isMacHost()) - qdsStarted = Utils::QtcProcess::startDetached( + qdsStarted = Utils::Process::startDetached( {"/usr/bin/open", {"-a", qdsPath.path(), fileName.toString()}}); else - qdsStarted = Utils::QtcProcess::startDetached({qdsPath, {"-client", fileName.toString()}}); + qdsStarted = Utils::Process::startDetached({qdsPath, {"-client", fileName.toString()}}); if (!qdsStarted) { QMessageBox::warning(Core::ICore::dialogParent(), @@ -180,7 +180,7 @@ const Utils::FilePath findQmlProjectUpwards(const Utils::FilePath &folder) static bool findAndOpenProject(const Utils::FilePath &filePath) { ProjectExplorer::Project *project - = ProjectExplorer::SessionManager::projectForFile(filePath); + = ProjectExplorer::ProjectManager::projectForFile(filePath); if (project) { if (project->projectFilePath().suffix() == "qmlproject") { @@ -436,7 +436,7 @@ void QmlProjectPlugin::updateQmlLandingPageProjectInfo(const Utils::FilePath &pr Utils::FilePath QmlProjectPlugin::projectFilePath() { - auto project = ProjectExplorer::SessionManager::startupProject(); + auto project = ProjectExplorer::ProjectManager::startupProject(); const QmlProjectManager::QmlProject *qmlProject = qobject_cast(project); if (qmlProject) { return qmlProject->projectFilePath(); diff --git a/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp b/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp index 31a8a32ee69..1cce1650ce0 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp +++ b/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include #include @@ -34,7 +34,7 @@ #include #include #include -#include +#include #include #include @@ -289,7 +289,7 @@ void QmlProjectRunConfiguration::createQtVersionAspect() if (!newTarget) newTarget = project->addTargetForKit(kits.first()); - SessionManager::setActiveTarget(project, newTarget, SetActive::Cascade); + project->setActiveTarget(newTarget, SetActive::Cascade); /* Reset the aspect. We changed the target and this aspect should not change. */ m_qtversionAspect->blockSignals(true); diff --git a/src/plugins/qnx/CMakeLists.txt b/src/plugins/qnx/CMakeLists.txt index f6c5907b4e8..8d0c6751b3f 100644 --- a/src/plugins/qnx/CMakeLists.txt +++ b/src/plugins/qnx/CMakeLists.txt @@ -4,16 +4,11 @@ add_qtc_plugin(Qnx SOURCES qnx.qrc qnxanalyzesupport.cpp qnxanalyzesupport.h - qnxconfiguration.cpp qnxconfiguration.h - qnxconfigurationmanager.cpp qnxconfigurationmanager.h qnxconstants.h qnxdebugsupport.cpp qnxdebugsupport.h qnxdeployqtlibrariesdialog.cpp qnxdeployqtlibrariesdialog.h qnxdevice.cpp qnxdevice.h - qnxdeviceprocesslist.cpp qnxdeviceprocesslist.h - qnxdeviceprocesssignaloperation.cpp qnxdeviceprocesssignaloperation.h qnxdevicetester.cpp qnxdevicetester.h - qnxdevicewizard.cpp qnxdevicewizard.h qnxplugin.cpp qnxqtversion.cpp qnxqtversion.h qnxrunconfiguration.cpp qnxrunconfiguration.h diff --git a/src/plugins/qnx/qnx.qbs b/src/plugins/qnx/qnx.qbs index 0389f542a65..ad4eae91c83 100644 --- a/src/plugins/qnx/qnx.qbs +++ b/src/plugins/qnx/qnx.qbs @@ -20,24 +20,14 @@ QtcPlugin { "qnxtoolchain.h", "qnx.qrc", "qnxconstants.h", - "qnxconfiguration.cpp", - "qnxconfiguration.h", "qnxanalyzesupport.cpp", "qnxanalyzesupport.h", "qnxdebugsupport.cpp", "qnxdebugsupport.h", "qnxdevice.cpp", "qnxdevice.h", - "qnxdevicewizard.cpp", - "qnxdevicewizard.h", - "qnxdeviceprocesslist.cpp", - "qnxdeviceprocesslist.h", - "qnxdeviceprocesssignaloperation.cpp", - "qnxdeviceprocesssignaloperation.h", "qnxdevicetester.cpp", "qnxdevicetester.h", - "qnxconfigurationmanager.cpp", - "qnxconfigurationmanager.h", "qnxsettingspage.cpp", "qnxsettingspage.h", "qnxtr.h", diff --git a/src/plugins/qnx/qnxanalyzesupport.cpp b/src/plugins/qnx/qnxanalyzesupport.cpp index 29ea42b7cdb..4d5a73b2ab5 100644 --- a/src/plugins/qnx/qnxanalyzesupport.cpp +++ b/src/plugins/qnx/qnxanalyzesupport.cpp @@ -9,8 +9,7 @@ #include -#include -#include +#include #include diff --git a/src/plugins/qnx/qnxconfiguration.cpp b/src/plugins/qnx/qnxconfiguration.cpp deleted file mode 100644 index c1b0e498f0c..00000000000 --- a/src/plugins/qnx/qnxconfiguration.cpp +++ /dev/null @@ -1,468 +0,0 @@ -// Copyright (C) 2016 BlackBerry Limited. All rights reserved. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "qnxconfiguration.h" - -#include "qnxqtversion.h" -#include "qnxutils.h" -#include "qnxtoolchain.h" -#include "qnxtr.h" - -#include "debugger/debuggeritem.h" - -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include -#include -#include - -#include - -#include -#include -#include - -using namespace ProjectExplorer; -using namespace QtSupport; -using namespace Utils; -using namespace Debugger; - -namespace Qnx::Internal { - -const QLatin1String QNXEnvFileKey("EnvFile"); -const QLatin1String QNXVersionKey("QNXVersion"); -// For backward compatibility -const QLatin1String SdpEnvFileKey("NDKEnvFile"); - -const QLatin1String QNXConfiguration("QNX_CONFIGURATION"); -const QLatin1String QNXTarget("QNX_TARGET"); -const QLatin1String QNXHost("QNX_HOST"); - -QnxConfiguration::QnxConfiguration() = default; - -QnxConfiguration::QnxConfiguration(const FilePath &sdpEnvFile) -{ - setDefaultConfiguration(sdpEnvFile); - readInformation(); -} - -QnxConfiguration::QnxConfiguration(const QVariantMap &data) -{ - QString envFilePath = data.value(QNXEnvFileKey).toString(); - if (envFilePath.isEmpty()) - envFilePath = data.value(SdpEnvFileKey).toString(); - - m_version = QnxVersionNumber(data.value(QNXVersionKey).toString()); - - setDefaultConfiguration(FilePath::fromString(envFilePath)); - readInformation(); -} - -FilePath QnxConfiguration::envFile() const -{ - return m_envFile; -} - -FilePath QnxConfiguration::qnxTarget() const -{ - return m_qnxTarget; -} - -FilePath QnxConfiguration::qnxHost() const -{ - return m_qnxHost; -} - -FilePath QnxConfiguration::qccCompilerPath() const -{ - return m_qccCompiler; -} - -EnvironmentItems QnxConfiguration::qnxEnv() const -{ - return m_qnxEnv; -} - -QnxVersionNumber QnxConfiguration::version() const -{ - return m_version; -} - -QVariantMap QnxConfiguration::toMap() const -{ - QVariantMap data; - data.insert(QLatin1String(QNXEnvFileKey), m_envFile.toString()); - data.insert(QLatin1String(QNXVersionKey), m_version.toString()); - return data; -} - -bool QnxConfiguration::isValid() const -{ - return !m_qccCompiler.isEmpty() && !m_targets.isEmpty(); -} - -QString QnxConfiguration::displayName() const -{ - return m_configName; -} - -bool QnxConfiguration::activate() -{ - if (isActive()) - return true; - - if (!isValid()) { - QMessageBox::warning(Core::ICore::dialogParent(), Tr::tr("Cannot Set Up QNX Configuration"), - validationErrorMessage(), QMessageBox::Ok); - return false; - } - - for (const Target &target : std::as_const(m_targets)) - createTools(target); - - return true; -} - -void QnxConfiguration::deactivate() -{ - if (!isActive()) - return; - - const Toolchains toolChainsToRemove = - ToolChainManager::toolchains(Utils::equal(&ToolChain::compilerCommand, qccCompilerPath())); - - QList debuggersToRemove; - const QList debuggerItems = DebuggerItemManager::debuggers(); - for (const DebuggerItem &debuggerItem : debuggerItems) { - if (findTargetByDebuggerPath(debuggerItem.command())) - debuggersToRemove.append(debuggerItem); - } - - const QList kits = KitManager::kits(); - for (Kit *kit : kits) { - if (kit->isAutoDetected() - && DeviceTypeKitAspect::deviceTypeId(kit) == Constants::QNX_QNX_OS_TYPE - && toolChainsToRemove.contains(ToolChainKitAspect::cxxToolChain(kit))) { - KitManager::deregisterKit(kit); - } - } - - for (ToolChain *tc : toolChainsToRemove) - ToolChainManager::deregisterToolChain(tc); - - for (const DebuggerItem &debuggerItem : std::as_const(debuggersToRemove)) - DebuggerItemManager::deregisterDebugger(debuggerItem.id()); -} - -bool QnxConfiguration::isActive() const -{ - const bool hasToolChain = ToolChainManager::toolChain(Utils::equal(&ToolChain::compilerCommand, - qccCompilerPath())); - const bool hasDebugger = Utils::contains(DebuggerItemManager::debuggers(), [this](const DebuggerItem &di) { - return findTargetByDebuggerPath(di.command()); - }); - - return hasToolChain && hasDebugger; -} - -FilePath QnxConfiguration::sdpPath() const -{ - return envFile().parentDir(); -} - -QnxQtVersion *QnxConfiguration::qnxQtVersion(const Target &target) const -{ - const QtVersions versions = QtVersionManager::versions( - Utils::equal(&QtVersion::type, QString::fromLatin1(Constants::QNX_QNX_QT))); - for (QtVersion *version : versions) { - auto qnxQt = dynamic_cast(version); - if (qnxQt && qnxQt->sdpPath() == sdpPath()) { - const Abis abis = version->qtAbis(); - for (const Abi &qtAbi : abis) { - if ((qtAbi == target.m_abi) && (qnxQt->cpuDir() == target.cpuDir())) - return qnxQt; - } - } - } - return nullptr; -} - -QList QnxConfiguration::autoDetect(const QList &alreadyKnown) -{ - QList result; - - for (const Target &target : std::as_const(m_targets)) - result += findToolChain(alreadyKnown, target.m_abi); - - return result; -} - -void QnxConfiguration::createTools(const Target &target) -{ - QnxToolChainMap toolchainMap = createToolChain(target); - QVariant debuggerId = createDebugger(target); - createKit(target, toolchainMap, debuggerId); -} - -QVariant QnxConfiguration::createDebugger(const Target &target) -{ - Environment sysEnv = m_qnxHost.deviceEnvironment(); - sysEnv.modify(qnxEnvironmentItems()); - - Debugger::DebuggerItem debugger; - debugger.setCommand(target.m_debuggerPath); - debugger.reinitializeFromFile(nullptr, &sysEnv); - debugger.setUnexpandedDisplayName(Tr::tr("Debugger for %1 (%2)") - .arg(displayName()) - .arg(target.shortDescription())); - return Debugger::DebuggerItemManager::registerDebugger(debugger); -} - -QnxConfiguration::QnxToolChainMap QnxConfiguration::createToolChain(const Target &target) -{ - QnxToolChainMap toolChainMap; - - for (auto language : { ProjectExplorer::Constants::C_LANGUAGE_ID, - ProjectExplorer::Constants::CXX_LANGUAGE_ID}) { - auto toolChain = new QnxToolChain; - toolChain->setDetection(ToolChain::AutoDetection); - toolChain->setLanguage(language); - toolChain->setTargetAbi(target.m_abi); - toolChain->setDisplayName(Tr::tr("QCC for %1 (%2)") - .arg(displayName()) - .arg(target.shortDescription())); - toolChain->setSdpPath(sdpPath()); - toolChain->setCpuDir(target.cpuDir()); - toolChain->resetToolChain(qccCompilerPath()); - ToolChainManager::registerToolChain(toolChain); - - toolChainMap.insert({language, toolChain}); - } - - return toolChainMap; -} - -QList QnxConfiguration::findToolChain(const QList &alreadyKnown, - const Abi &abi) -{ - return Utils::filtered(alreadyKnown, [this, abi](ToolChain *tc) { - return tc->typeId() == Constants::QNX_TOOLCHAIN_ID - && tc->targetAbi() == abi - && tc->compilerCommand() == m_qccCompiler; - }); -} - -void QnxConfiguration::createKit(const Target &target, const QnxToolChainMap &toolChainMap, - const QVariant &debugger) -{ - QnxQtVersion *qnxQt = qnxQtVersion(target); // nullptr is ok. - - const auto init = [&](Kit *k) { - QtKitAspect::setQtVersion(k, qnxQt); - ToolChainKitAspect::setToolChain(k, toolChainMap.at(ProjectExplorer::Constants::C_LANGUAGE_ID)); - ToolChainKitAspect::setToolChain(k, toolChainMap.at(ProjectExplorer::Constants::CXX_LANGUAGE_ID)); - - if (debugger.isValid()) - DebuggerKitAspect::setDebugger(k, debugger); - - DeviceTypeKitAspect::setDeviceTypeId(k, Constants::QNX_QNX_OS_TYPE); - // TODO: Add sysroot? - - k->setUnexpandedDisplayName(Tr::tr("Kit for %1 (%2)") - .arg(displayName()) - .arg(target.shortDescription())); - - k->setAutoDetected(false); - k->setAutoDetectionSource(envFile().toString()); - k->setMutable(DeviceKitAspect::id(), true); - - k->setSticky(ToolChainKitAspect::id(), true); - k->setSticky(DeviceTypeKitAspect::id(), true); - k->setSticky(SysRootKitAspect::id(), true); - k->setSticky(DebuggerKitAspect::id(), true); - k->setSticky(QmakeProjectManager::Constants::KIT_INFORMATION_ID, true); - - EnvironmentKitAspect::setEnvironmentChanges(k, qnxEnvironmentItems()); - }; - - // add kit with device and qt version not sticky - KitManager::registerKit(init); -} - -QString QnxConfiguration::validationErrorMessage() const -{ - if (isValid()) - return {}; - - QStringList errorStrings - = {Tr::tr("The following errors occurred while activating the QNX configuration:")}; - if (m_qccCompiler.isEmpty()) - errorStrings << Tr::tr("- No GCC compiler found."); - if (m_targets.isEmpty()) - errorStrings << Tr::tr("- No targets found."); - return errorStrings.join('\n'); -} - -void QnxConfiguration::setVersion(const QnxVersionNumber &version) -{ - m_version = version; -} - -void QnxConfiguration::readInformation() -{ - const FilePath configPath = m_qnxConfiguration / "qconfig"; - if (!configPath.isDir()) - return; - - configPath.iterateDirectory([this, configPath](const FilePath &sdpFile) { - QFile xmlFile(sdpFile.toFSPathString()); - if (!xmlFile.open(QIODevice::ReadOnly)) - return IterationPolicy::Continue; - - QDomDocument doc; - if (!doc.setContent(&xmlFile)) // Skip error message - return IterationPolicy::Continue; - - QDomElement docElt = doc.documentElement(); - if (docElt.tagName() != QLatin1String("qnxSystemDefinition")) - return IterationPolicy::Continue; - - QDomElement childElt = docElt.firstChildElement(QLatin1String("installation")); - // The file contains only one installation node - if (childElt.isNull()) // The file contains only one base node - return IterationPolicy::Continue; - - FilePath host = configPath.withNewPath( - childElt.firstChildElement(QLatin1String("host")).text()).canonicalPath(); - if (m_qnxHost != host) - return IterationPolicy::Continue; - - FilePath target = configPath.withNewPath( - childElt.firstChildElement(QLatin1String("target")).text()).canonicalPath(); - if (m_qnxTarget != target) - return IterationPolicy::Continue; - - m_configName = childElt.firstChildElement(QLatin1String("name")).text(); - QString version = childElt.firstChildElement(QLatin1String("version")).text(); - setVersion(QnxVersionNumber(version)); - return IterationPolicy::Stop; - }, {{"*.xml"}, QDir::Files}); -} - -void QnxConfiguration::setDefaultConfiguration(const FilePath &envScript) -{ - QTC_ASSERT(!envScript.isEmpty(), return); - m_envFile = envScript; - m_qnxEnv = QnxUtils::qnxEnvironmentFromEnvFile(m_envFile); - for (const EnvironmentItem &item : std::as_const(m_qnxEnv)) { - if (item.name == QNXConfiguration) - m_qnxConfiguration = envScript.withNewPath(item.value).canonicalPath(); - else if (item.name == QNXTarget) - m_qnxTarget = envScript.withNewPath(item.value).canonicalPath(); - else if (item.name == QNXHost) - m_qnxHost = envScript.withNewPath(item.value).canonicalPath(); - } - - const FilePath qccPath = m_qnxHost.pathAppended("usr/bin/qcc").withExecutableSuffix(); - if (qccPath.exists()) - m_qccCompiler = qccPath; - - // Some fall back in case the qconfig dir with .xml files is not found later - if (m_configName.isEmpty()) - m_configName = QString("%1 - %2").arg(m_qnxHost.fileName(), m_qnxTarget.fileName()); - - updateTargets(); - assignDebuggersToTargets(); - - // Remove debuggerless targets. - Utils::erase(m_targets, [](const Target &target) { - if (target.m_debuggerPath.isEmpty()) - qWarning() << "No debugger found for" << target.m_path << "... discarded"; - return target.m_debuggerPath.isEmpty(); - }); -} - -EnvironmentItems QnxConfiguration::qnxEnvironmentItems() const -{ - Utils::EnvironmentItems envList; - envList.push_back(EnvironmentItem(QNXConfiguration, m_qnxConfiguration.path())); - envList.push_back(EnvironmentItem(QNXTarget, m_qnxTarget.path())); - envList.push_back(EnvironmentItem(QNXHost, m_qnxHost.path())); - - return envList; -} - -const QnxConfiguration::Target *QnxConfiguration::findTargetByDebuggerPath( - const FilePath &path) const -{ - const auto it = std::find_if(m_targets.begin(), m_targets.end(), - [path](const Target &target) { return target.m_debuggerPath == path; }); - return it == m_targets.end() ? nullptr : &(*it); -} - -void QnxConfiguration::updateTargets() -{ - m_targets.clear(); - const QList targets = QnxUtils::findTargets(m_qnxTarget); - for (const QnxTarget &target : targets) - m_targets.append(Target(target.m_abi, target.m_path)); -} - -void QnxConfiguration::assignDebuggersToTargets() -{ - const FilePath hostUsrBinDir = m_qnxHost.pathAppended("usr/bin"); - QString pattern = "nto*-gdb"; - if (m_qnxHost.osType() == Utils::OsTypeWindows) - pattern += ".exe"; - - const FilePaths debuggerNames = hostUsrBinDir.dirEntries({{pattern}, QDir::Files}); - Environment sysEnv = m_qnxHost.deviceEnvironment(); - sysEnv.modify(qnxEnvironmentItems()); - - for (const FilePath &debuggerPath : debuggerNames) { - DebuggerItem item; - item.setCommand(debuggerPath); - item.reinitializeFromFile(nullptr, &sysEnv); - bool found = false; - for (const Abi &abi : item.abis()) { - for (Target &target : m_targets) { - if (target.m_abi.isCompatibleWith(abi)) { - found = true; - - if (target.m_debuggerPath.isEmpty()) { - target.m_debuggerPath = debuggerPath; - } else { - qWarning() << debuggerPath << "has the same ABI as" << target.m_debuggerPath - << "... discarded"; - break; - } - } - } - } - if (!found) - qWarning() << "No target found for" << debuggerPath.toUserOutput() << "... discarded"; - } -} - -QString QnxConfiguration::Target::shortDescription() const -{ - return QnxUtils::cpuDirShortDescription(cpuDir()); -} - -QString QnxConfiguration::Target::cpuDir() const -{ - return m_path.fileName(); -} - -} // Qnx::Internal diff --git a/src/plugins/qnx/qnxconfiguration.h b/src/plugins/qnx/qnxconfiguration.h deleted file mode 100644 index 34056536c35..00000000000 --- a/src/plugins/qnx/qnxconfiguration.h +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (C) 2016 BlackBerry Limited. All rights reserved. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "qnxconstants.h" -#include "qnxversionnumber.h" - -#include -#include - -#include - -#include - -#include - -namespace ProjectExplorer -{ - class Kit; - class ToolChain; -} - -namespace Qnx::Internal { - -class QnxToolChain; -class QnxQtVersion; - -class QnxConfiguration -{ -public: - QnxConfiguration(); - QnxConfiguration(const Utils::FilePath &sdpEnvFile); - QnxConfiguration(const QVariantMap &data); - - Utils::FilePath envFile() const; - Utils::FilePath qnxTarget() const; - Utils::FilePath qnxHost() const; - Utils::FilePath qccCompilerPath() const; - Utils::EnvironmentItems qnxEnv() const; - QnxVersionNumber version() const; - QVariantMap toMap() const; - - bool isValid() const; - - QString displayName() const; - bool activate(); - void deactivate(); - bool isActive() const; - Utils::FilePath sdpPath() const; - - QList autoDetect( - const QList &alreadyKnown); - -private: - QList findToolChain( - const QList &alreadyKnown, - const ProjectExplorer::Abi &abi); - - QString validationErrorMessage() const; - - void setVersion(const QnxVersionNumber& version); - - void readInformation(); - - void setDefaultConfiguration(const Utils::FilePath &envScript); - - Utils::EnvironmentItems qnxEnvironmentItems() const; - - QString m_configName; - - Utils::FilePath m_envFile; - Utils::FilePath m_qnxConfiguration; - Utils::FilePath m_qnxTarget; - Utils::FilePath m_qnxHost; - Utils::FilePath m_qccCompiler; - Utils::EnvironmentItems m_qnxEnv; - QnxVersionNumber m_version; - - class Target - { - public: - Target(const ProjectExplorer::Abi &abi, const Utils::FilePath &path) - : m_abi(abi), m_path(path) - { - } - - QString shortDescription() const; - QString cpuDir() const; - - ProjectExplorer::Abi m_abi; - Utils::FilePath m_path; - Utils::FilePath m_debuggerPath; - }; - - QList m_targets; - - QnxQtVersion *qnxQtVersion(const Target &target) const; - - void createTools(const Target &target); - QVariant createDebugger(const Target &target); - - using QnxToolChainMap = std::map; - - QnxToolChainMap createToolChain(const Target &target); - void createKit(const Target &target, const QnxToolChainMap &toolChain, const QVariant &debugger); - - const Target *findTargetByDebuggerPath(const Utils::FilePath &path) const; - - void updateTargets(); - void assignDebuggersToTargets(); -}; - -} // Qnx::Internal diff --git a/src/plugins/qnx/qnxconfigurationmanager.cpp b/src/plugins/qnx/qnxconfigurationmanager.cpp deleted file mode 100644 index da585b69cf2..00000000000 --- a/src/plugins/qnx/qnxconfigurationmanager.cpp +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (C) 2016 BlackBerry Limited. All rights reserved. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "qnxconfigurationmanager.h" - -#include "qnxconfiguration.h" - -#include - -#include -#include - -using namespace Utils; - -namespace Qnx::Internal { - -const QLatin1String QNXConfigDataKey("QNXConfiguration."); -const QLatin1String QNXConfigCountKey("QNXConfiguration.Count"); -const QLatin1String QNXConfigsFileVersionKey("Version"); - -static FilePath qnxConfigSettingsFileName() -{ - return Core::ICore::userResourcePath("qnx/qnxconfigurations.xml"); -} - -static QnxConfigurationManager *m_instance = nullptr; - -QnxConfigurationManager::QnxConfigurationManager() -{ - m_instance = this; - m_writer = new PersistentSettingsWriter(qnxConfigSettingsFileName(), "QnxConfigurations"); - connect(Core::ICore::instance(), &Core::ICore::saveSettingsRequested, - this, &QnxConfigurationManager::saveConfigs); -} - -QnxConfigurationManager *QnxConfigurationManager::instance() -{ - return m_instance; -} - -QnxConfigurationManager::~QnxConfigurationManager() -{ - m_instance = nullptr; - qDeleteAll(m_configurations); - delete m_writer; -} - -QList QnxConfigurationManager::configurations() const -{ - return m_configurations; -} - -void QnxConfigurationManager::removeConfiguration(QnxConfiguration *config) -{ - if (m_configurations.removeAll(config)) { - delete config; - emit configurationsListUpdated(); - } -} - -bool QnxConfigurationManager::addConfiguration(QnxConfiguration *config) -{ - if (!config || !config->isValid()) - return false; - - for (QnxConfiguration *c : std::as_const(m_configurations)) { - if (c->envFile() == config->envFile()) - return false; - } - - m_configurations.append(config); - emit configurationsListUpdated(); - return true; -} - -QnxConfiguration *QnxConfigurationManager::configurationFromEnvFile(const FilePath &envFile) const -{ - for (QnxConfiguration *c : m_configurations) { - if (c->envFile() == envFile) - return c; - } - - return nullptr; -} - -void QnxConfigurationManager::saveConfigs() -{ - QTC_ASSERT(m_writer, return); - QVariantMap data; - data.insert(QLatin1String(QNXConfigsFileVersionKey), 1); - int count = 0; - for (QnxConfiguration *config : std::as_const(m_configurations)) { - QVariantMap tmp = config->toMap(); - if (tmp.isEmpty()) - continue; - - data.insert(QNXConfigDataKey + QString::number(count), tmp); - ++count; - } - - data.insert(QLatin1String(QNXConfigCountKey), count); - m_writer->save(data, Core::ICore::dialogParent()); -} - -void QnxConfigurationManager::restoreConfigurations() -{ - PersistentSettingsReader reader; - if (!reader.load(qnxConfigSettingsFileName())) - return; - - QVariantMap data = reader.restoreValues(); - int count = data.value(QNXConfigCountKey, 0).toInt(); - for (int i = 0; i < count; ++i) { - const QString key = QNXConfigDataKey + QString::number(i); - if (!data.contains(key)) - continue; - - const QVariantMap dMap = data.value(key).toMap(); - auto configuration = new QnxConfiguration(dMap); - addConfiguration(configuration); - } -} - -} // Qnx::Internal diff --git a/src/plugins/qnx/qnxconfigurationmanager.h b/src/plugins/qnx/qnxconfigurationmanager.h deleted file mode 100644 index bc0d02dd988..00000000000 --- a/src/plugins/qnx/qnxconfigurationmanager.h +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (C) 2016 BlackBerry Limited. All rights reserved. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -namespace Utils { class PersistentSettingsWriter; } - -namespace Qnx::Internal { - -class QnxConfiguration; -class QnxPlugin; - -class QnxConfigurationManager: public QObject -{ - Q_OBJECT -public: - QnxConfigurationManager(); - ~QnxConfigurationManager() override; - - void restoreConfigurations(); - - static QnxConfigurationManager *instance(); - QList configurations() const; - void removeConfiguration(QnxConfiguration *config); - bool addConfiguration(QnxConfiguration *config); - QnxConfiguration* configurationFromEnvFile(const Utils::FilePath &envFile) const; - -protected slots: - void saveConfigs(); - -signals: - void configurationsListUpdated(); - -private: - QList m_configurations; - Utils::PersistentSettingsWriter *m_writer; -}; - -} // Qnx::Internal diff --git a/src/plugins/qnx/qnxconstants.h b/src/plugins/qnx/qnxconstants.h index b70db8c87e1..3b8bdd26996 100644 --- a/src/plugins/qnx/qnxconstants.h +++ b/src/plugins/qnx/qnxconstants.h @@ -15,5 +15,6 @@ const char QNX_QNX_OS_TYPE[] = "QnxOsType"; // Also used for device type. const char QNX_TOOLCHAIN_ID[] = "Qnx.QccToolChain"; const char QNX_TMP_DIR[] = "/tmp"; // /var/run is root:root drwxr-xr-x +const char QNX_DIRECT_UPLOAD_STEP_ID[] ="Qnx.DirectUploadStep"; } // Qnx::Constants diff --git a/src/plugins/qnx/qnxdebugsupport.cpp b/src/plugins/qnx/qnxdebugsupport.cpp index cb3932daab5..919e9ef1fcc 100644 --- a/src/plugins/qnx/qnxdebugsupport.cpp +++ b/src/plugins/qnx/qnxdebugsupport.cpp @@ -21,8 +21,8 @@ #include #include #include +#include #include -#include #include #include @@ -33,9 +33,9 @@ #include #include #include +#include #include #include -#include #include #include @@ -223,7 +223,7 @@ void showAttachToProcessDialog() return; // FIXME: That should be somehow related to the selected kit. - auto runConfig = SessionManager::startupRunConfiguration(); + auto runConfig = ProjectManager::startupRunConfiguration(); const int pid = dlg.currentProcess().processId; // QString projectSourceDirectory = dlg.projectSource(); diff --git a/src/plugins/qnx/qnxdeployqtlibrariesdialog.cpp b/src/plugins/qnx/qnxdeployqtlibrariesdialog.cpp index 78d752efb07..a5cc9cdad86 100644 --- a/src/plugins/qnx/qnxdeployqtlibrariesdialog.cpp +++ b/src/plugins/qnx/qnxdeployqtlibrariesdialog.cpp @@ -16,9 +16,9 @@ #include #include #include +#include #include #include -#include #include #include @@ -31,8 +31,8 @@ using namespace ProjectExplorer; using namespace QtSupport; +using namespace Tasking; using namespace Utils; -using namespace Utils::Tasking; namespace Qnx::Internal { @@ -119,12 +119,12 @@ QList collectFilesToUpload(const DeployableFile &deployable) TaskItem QnxDeployQtLibrariesDialogPrivate::checkDirTask() { - const auto setupHandler = [this](QtcProcess &process) { + const auto setupHandler = [this](Process &process) { m_deployLogWindow->appendPlainText(Tr::tr("Checking existence of \"%1\"") .arg(fullRemoteDirectory())); process.setCommand({m_device->filePath("test"), {"-d", fullRemoteDirectory()}}); }; - const auto doneHandler = [this](const QtcProcess &process) { + const auto doneHandler = [this](const Process &process) { Q_UNUSED(process) const int answer = QMessageBox::question(q, q->windowTitle(), Tr::tr("The remote directory \"%1\" already exists.\n" @@ -133,7 +133,7 @@ TaskItem QnxDeployQtLibrariesDialogPrivate::checkDirTask() QMessageBox::Yes | QMessageBox::No); m_checkResult = answer == QMessageBox::Yes ? CheckResult::RemoveDir : CheckResult::Abort; }; - const auto errorHandler = [this](const QtcProcess &process) { + const auto errorHandler = [this](const Process &process) { if (process.result() != ProcessResult::FinishedWithError) { m_deployLogWindow->appendPlainText(Tr::tr("Connection failed: %1") .arg(process.errorString())); @@ -142,24 +142,24 @@ TaskItem QnxDeployQtLibrariesDialogPrivate::checkDirTask() } m_checkResult = CheckResult::SkipRemoveDir; }; - return Process(setupHandler, doneHandler, errorHandler); + return ProcessTask(setupHandler, doneHandler, errorHandler); } TaskItem QnxDeployQtLibrariesDialogPrivate::removeDirTask() { - const auto setupHandler = [this](QtcProcess &process) { + const auto setupHandler = [this](Process &process) { if (m_checkResult != CheckResult::RemoveDir) return TaskAction::StopWithDone; m_deployLogWindow->appendPlainText(Tr::tr("Removing \"%1\"").arg(fullRemoteDirectory())); process.setCommand({m_device->filePath("rm"), {"-rf", fullRemoteDirectory()}}); return TaskAction::Continue; }; - const auto errorHandler = [this](const QtcProcess &process) { + const auto errorHandler = [this](const Process &process) { QTC_ASSERT(process.exitCode() == 0, return); m_deployLogWindow->appendPlainText(Tr::tr("Connection failed: %1") .arg(process.errorString())); }; - return Process(setupHandler, {}, errorHandler); + return ProcessTask(setupHandler, {}, errorHandler); } TaskItem QnxDeployQtLibrariesDialogPrivate::uploadTask() @@ -193,16 +193,16 @@ TaskItem QnxDeployQtLibrariesDialogPrivate::uploadTask() const auto errorHandler = [this](const FileTransfer &transfer) { emitErrorMessage(transfer.resultData().m_errorString); }; - return Transfer(setupHandler, {}, errorHandler); + return FileTransferTask(setupHandler, {}, errorHandler); } TaskItem QnxDeployQtLibrariesDialogPrivate::chmodTask(const DeployableFile &file) { - const auto setupHandler = [=](QtcProcess &process) { + const auto setupHandler = [=](Process &process) { process.setCommand({m_device->filePath("chmod"), {"a+x", Utils::ProcessArgs::quoteArgUnix(file.remoteFilePath())}}); }; - const auto errorHandler = [=](const QtcProcess &process) { + const auto errorHandler = [=](const Process &process) { const QString error = process.errorString(); if (!error.isEmpty()) { emitWarningMessage(Tr::tr("Remote chmod failed for file \"%1\": %2") @@ -212,7 +212,7 @@ TaskItem QnxDeployQtLibrariesDialogPrivate::chmodTask(const DeployableFile &file .arg(file.remoteFilePath(), process.cleanedStdErr())); } }; - return Process(setupHandler, {}, errorHandler); + return ProcessTask(setupHandler, {}, errorHandler); } TaskItem QnxDeployQtLibrariesDialogPrivate::chmodTree() @@ -223,14 +223,14 @@ TaskItem QnxDeployQtLibrariesDialogPrivate::chmodTree() if (file.isExecutable()) filesToChmod << file; } - QList chmodList{optional, ParallelLimit(MaxConcurrentStatCalls)}; + QList chmodList{finishAllAndDone, parallelLimit(MaxConcurrentStatCalls)}; for (const DeployableFile &file : std::as_const(filesToChmod)) { QTC_ASSERT(file.isValid(), continue); chmodList.append(chmodTask(file)); } tree.setupRoot(chmodList); }; - return Tree{setupChmodHandler}; + return TaskTreeTask{setupChmodHandler}; } Group QnxDeployQtLibrariesDialogPrivate::deployRecipe() @@ -261,18 +261,18 @@ Group QnxDeployQtLibrariesDialogPrivate::deployRecipe() return TaskAction::Continue; }; const Group root { - OnGroupSetup(setupHandler), + onGroupSetup(setupHandler), Group { - optional, + finishAllAndDone, checkDirTask() }, Group { - OnGroupSetup(subGroupSetupHandler), + onGroupSetup(subGroupSetupHandler), removeDirTask(), uploadTask(), chmodTree() }, - OnGroupDone(doneHandler) + onGroupDone(doneHandler) }; return root; } diff --git a/src/plugins/qnx/qnxdevice.cpp b/src/plugins/qnx/qnxdevice.cpp index 0c7c99a5c39..306b87e861d 100644 --- a/src/plugins/qnx/qnxdevice.cpp +++ b/src/plugins/qnx/qnxdevice.cpp @@ -6,18 +6,21 @@ #include "qnxconstants.h" #include "qnxdeployqtlibrariesdialog.h" #include "qnxdevicetester.h" -#include "qnxdeviceprocesslist.h" -#include "qnxdeviceprocesssignaloperation.h" -#include "qnxdevicewizard.h" #include "qnxtr.h" -#include +#include + +#include + +#include +#include +#include #include +#include +#include #include -#include - -#include +#include using namespace ProjectExplorer; using namespace RemoteLinux; @@ -25,149 +28,91 @@ using namespace Utils; namespace Qnx::Internal { -class QnxProcessImpl final : public SshProcessInterface +static QString signalProcessByNameQnxCommandLine(const QString &filePath, int sig) +{ + QString executable = filePath; + return QString::fromLatin1("for PID in $(ps -f -o pid,comm | grep %1 | awk '/%1/ {print $1}'); " + "do " + "kill -%2 $PID; " + "done").arg(executable.replace(QLatin1String("/"), QLatin1String("\\/"))).arg(sig); +} + +class QnxDeviceProcessSignalOperation : public RemoteLinuxSignalOperation { public: - QnxProcessImpl(const LinuxDevice *linuxDevice); - ~QnxProcessImpl() { killIfRunning(); } + explicit QnxDeviceProcessSignalOperation(const IDeviceConstPtr &device) + : RemoteLinuxSignalOperation(device) + {} -private: - QString fullCommandLine(const CommandLine &commandLine) const final; - void handleSendControlSignal(Utils::ControlSignal controlSignal) final; + QString killProcessByNameCommandLine(const QString &filePath) const override + { + return QString::fromLatin1("%1; %2").arg(signalProcessByNameQnxCommandLine(filePath, 15), + signalProcessByNameQnxCommandLine(filePath, 9)); + } - const QString m_pidFile; + QString interruptProcessByNameCommandLine(const QString &filePath) const override + { + return signalProcessByNameQnxCommandLine(filePath, 2); + } }; -static std::atomic_int s_pidFileCounter = 1; - -QnxProcessImpl::QnxProcessImpl(const LinuxDevice *linuxDevice) - : SshProcessInterface(linuxDevice) - , m_pidFile(QString("%1/qtc.%2.pid").arg(Constants::QNX_TMP_DIR).arg(s_pidFileCounter.fetch_add(1))) +class QnxDevice final : public LinuxDevice { -} +public: + QnxDevice() + { + setDisplayType(Tr::tr("QNX")); + setDefaultDisplayName(Tr::tr("QNX Device")); + setOsType(OsTypeOtherUnix); + setupId(IDevice::ManuallyAdded); + setType(Constants::QNX_QNX_OS_TYPE); + setMachineType(IDevice::Hardware); + SshParameters sshParams; + sshParams.timeout = 10; + setSshParameters(sshParams); + setFreePorts(PortList::fromString("10000-10100")); -QString QnxProcessImpl::fullCommandLine(const CommandLine &commandLine) const -{ - QStringList args = ProcessArgs::splitArgs(commandLine.arguments()); - args.prepend(commandLine.executable().toString()); - const QString cmd = ProcessArgs::createUnixArgs(args).toString(); - - QString fullCommandLine = - "test -f /etc/profile && . /etc/profile ; " - "test -f $HOME/profile && . $HOME/profile ; "; - - if (!m_setup.m_workingDirectory.isEmpty()) - fullCommandLine += QString::fromLatin1("cd %1 ; ").arg( - ProcessArgs::quoteArg(m_setup.m_workingDirectory.toString())); - - const Environment env = m_setup.m_environment; - for (auto it = env.constBegin(); it != env.constEnd(); ++it) { - fullCommandLine += QString::fromLatin1("%1='%2' ") - .arg(env.key(it)).arg(env.expandedValueForKey(env.key(it))); + addDeviceAction({Tr::tr("Deploy Qt libraries..."), [](const IDevice::Ptr &device, QWidget *parent) { + QnxDeployQtLibrariesDialog dialog(device, parent); + dialog.exec(); + }}); } - fullCommandLine += QString::fromLatin1("%1 & echo $! > %2").arg(cmd).arg(m_pidFile); - - return fullCommandLine; -} - -void QnxProcessImpl::handleSendControlSignal(Utils::ControlSignal controlSignal) -{ - QTC_ASSERT(controlSignal != ControlSignal::KickOff, return); - const QString args = QString::fromLatin1("-%1 `cat %2`") - .arg(controlSignalToInt(controlSignal)).arg(m_pidFile); - CommandLine command = { "kill", args, CommandLine::Raw }; - // Note: This blocking call takes up to 2 ms for local remote. - runInShell(command); -} - -const char QnxVersionKey[] = "QnxVersion"; - -QnxDevice::QnxDevice() -{ - setDisplayType(Tr::tr("QNX")); - setDefaultDisplayName(Tr::tr("QNX Device")); - setOsType(OsTypeOtherUnix); - - addDeviceAction({Tr::tr("Deploy Qt libraries..."), [](const IDevice::Ptr &device, QWidget *parent) { - QnxDeployQtLibrariesDialog dialog(device, parent); - dialog.exec(); - }}); -} - -int QnxDevice::qnxVersion() const -{ - if (m_versionNumber == 0) - updateVersionNumber(); - - return m_versionNumber; -} - -void QnxDevice::updateVersionNumber() const -{ - QtcProcess versionNumberProcess; - - versionNumberProcess.setCommand({filePath("uname"), {"-r"}}); - versionNumberProcess.runBlocking(EventLoopMode::On); - - QByteArray output = versionNumberProcess.readAllRawStandardOutput(); - QString versionMessage = QString::fromLatin1(output); - const QRegularExpression versionNumberRegExp("(\\d+)\\.(\\d+)\\.(\\d+)"); - const QRegularExpressionMatch match = versionNumberRegExp.match(versionMessage); - if (match.hasMatch()) { - int major = match.captured(1).toInt(); - int minor = match.captured(2).toInt(); - int patch = match.captured(3).toInt(); - m_versionNumber = (major << 16)|(minor<<8)|(patch); + DeviceProcessSignalOperation::Ptr signalOperation() const final + { + return DeviceProcessSignalOperation::Ptr(new QnxDeviceProcessSignalOperation(sharedFromThis())); } -} -void QnxDevice::fromMap(const QVariantMap &map) + DeviceTester *createDeviceTester() const final { return new QnxDeviceTester; } +}; + +class QnxDeviceWizard : public Wizard { - m_versionNumber = map.value(QLatin1String(QnxVersionKey), 0).toInt(); - LinuxDevice::fromMap(map); -} +public: + QnxDeviceWizard() : Wizard(Core::ICore::dialogParent()) + { + setWindowTitle(Tr::tr("New QNX Device Configuration Setup")); -QVariantMap QnxDevice::toMap() const -{ - QVariantMap map(LinuxDevice::toMap()); - map.insert(QLatin1String(QnxVersionKey), m_versionNumber); - return map; -} + addPage(&m_setupPage); + addPage(&m_keyDeploymentPage); + addPage(&m_finalPage); + m_finalPage.setCommitPage(true); -PortsGatheringMethod QnxDevice::portsGatheringMethod() const -{ - return { - // TODO: The command is probably needlessly complicated because the parsing method - // used to be fixed. These two can now be matched to each other. - [this](QAbstractSocket::NetworkLayerProtocol protocol) -> CommandLine { - Q_UNUSED(protocol) - return {filePath("netstat"), {"-na"}}; - }, + m_device.reset(new QnxDevice); - &Port::parseFromNetstatOutput - }; -} + m_setupPage.setDevice(m_device); + m_keyDeploymentPage.setDevice(m_device); + } -DeviceProcessList *QnxDevice::createProcessListModel(QObject *parent) const -{ - return new QnxDeviceProcessList(sharedFromThis(), parent); -} + IDevice::Ptr device() const { return m_device; } -DeviceTester *QnxDevice::createDeviceTester() const -{ - return new QnxDeviceTester; -} +private: + GenericLinuxDeviceConfigurationWizardSetupPage m_setupPage; + GenericLinuxDeviceConfigurationWizardKeyDeploymentPage m_keyDeploymentPage; + GenericLinuxDeviceConfigurationWizardFinalPage m_finalPage; -Utils::ProcessInterface *QnxDevice::createProcessInterface() const -{ - return new QnxProcessImpl(this); -} - -DeviceProcessSignalOperation::Ptr QnxDevice::signalOperation() const -{ - return DeviceProcessSignalOperation::Ptr(new QnxDeviceProcessSignalOperation(sharedFromThis())); -} + LinuxDevice::Ptr m_device; +}; // Factory @@ -176,7 +121,8 @@ QnxDeviceFactory::QnxDeviceFactory() : IDeviceFactory(Constants::QNX_QNX_OS_TYPE setDisplayName(Tr::tr("QNX Device")); setCombinedIcon(":/qnx/images/qnxdevicesmall.png", ":/qnx/images/qnxdevice.png"); - setConstructionFunction(&QnxDevice::create); + setQuickCreationAllowed(true); + setConstructionFunction([] { return IDevice::Ptr(new QnxDevice); }); setCreator([] { QnxDeviceWizard wizard; if (wizard.exec() != QDialog::Accepted) diff --git a/src/plugins/qnx/qnxdevice.h b/src/plugins/qnx/qnxdevice.h index f4efa3e1f25..c4b484f478c 100644 --- a/src/plugins/qnx/qnxdevice.h +++ b/src/plugins/qnx/qnxdevice.h @@ -3,42 +3,10 @@ #pragma once -#include +#include namespace Qnx::Internal { -class QnxDevice final : public RemoteLinux::LinuxDevice -{ -public: - using Ptr = QSharedPointer; - using ConstPtr = QSharedPointer; - - static Ptr create() { return Ptr(new QnxDevice); } - - ProjectExplorer::PortsGatheringMethod portsGatheringMethod() const override; - ProjectExplorer::DeviceProcessList *createProcessListModel(QObject *parent) const override; - ProjectExplorer::DeviceProcessSignalOperation::Ptr signalOperation() const override; - - ProjectExplorer::DeviceTester *createDeviceTester() const override; - Utils::ProcessInterface *createProcessInterface() const override; - - int qnxVersion() const; - -protected: - void fromMap(const QVariantMap &map) final; - QVariantMap toMap() const final; - - QString interruptProcessByNameCommandLine(const QString &filePath) const; - QString killProcessByNameCommandLine(const QString &filePath) const; - -private: - QnxDevice(); - - void updateVersionNumber() const; - - mutable int m_versionNumber = 0; -}; - class QnxDeviceFactory final : public ProjectExplorer::IDeviceFactory { public: diff --git a/src/plugins/qnx/qnxdeviceprocesslist.cpp b/src/plugins/qnx/qnxdeviceprocesslist.cpp deleted file mode 100644 index 39735329934..00000000000 --- a/src/plugins/qnx/qnxdeviceprocesslist.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (C) 2016 BlackBerry Limited. All rights reserved. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "qnxdeviceprocesslist.h" - -#include -#include -#include -#include - -#include -#include - -using namespace Utils; - -namespace Qnx::Internal { - -QnxDeviceProcessList::QnxDeviceProcessList( - const ProjectExplorer::IDevice::ConstPtr &device, QObject *parent) - : ProjectExplorer::SshDeviceProcessList(device, parent) -{ -} - -QString QnxDeviceProcessList::listProcessesCommandLine() const -{ - return QLatin1String("pidin -F '%a %A {/%n}'"); -} - -QList QnxDeviceProcessList::buildProcessList(const QString &listProcessesReply) const -{ - QList processes; - QStringList lines = listProcessesReply.split(QLatin1Char('\n')); - if (lines.isEmpty()) - return processes; - - lines.pop_front(); // drop headers - const QRegularExpression re("\\s*(\\d+)\\s+(.*){(.*)}"); - - for (const QString &line : std::as_const(lines)) { - const QRegularExpressionMatch match = re.match(line); - if (match.hasMatch()) { - const QStringList captures = match.capturedTexts(); - if (captures.size() == 4) { - const int pid = captures[1].toInt(); - const QString args = captures[2]; - const QString exe = captures[3]; - ProcessInfo deviceProcess; - deviceProcess.processId = pid; - deviceProcess.executable = exe.trimmed(); - deviceProcess.commandLine = args.trimmed(); - processes.append(deviceProcess); - } - } - } - - return Utils::sorted(std::move(processes)); -} - -} // Qnx::Internal diff --git a/src/plugins/qnx/qnxdeviceprocesslist.h b/src/plugins/qnx/qnxdeviceprocesslist.h deleted file mode 100644 index 0e71ae7ab01..00000000000 --- a/src/plugins/qnx/qnxdeviceprocesslist.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (C) 2016 BlackBerry Limited. All rights reserved. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -namespace Qnx::Internal { - -class QnxDeviceProcessList : public ProjectExplorer::SshDeviceProcessList -{ -public: - explicit QnxDeviceProcessList( - const ProjectExplorer::IDeviceConstPtr &device, QObject *parent = nullptr); - -private: - QString listProcessesCommandLine() const override; - QList buildProcessList(const QString &listProcessesReply) const override; -}; - -} // Qnx::Internal diff --git a/src/plugins/qnx/qnxdeviceprocesssignaloperation.cpp b/src/plugins/qnx/qnxdeviceprocesssignaloperation.cpp deleted file mode 100644 index 56df01d8a9e..00000000000 --- a/src/plugins/qnx/qnxdeviceprocesssignaloperation.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (C) 2016 BlackBerry Limited. All rights reserved. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "qnxdeviceprocesssignaloperation.h" - -namespace Qnx::Internal { - -QnxDeviceProcessSignalOperation::QnxDeviceProcessSignalOperation( - const ProjectExplorer::IDeviceConstPtr &device) - : RemoteLinux::RemoteLinuxSignalOperation(device) -{ -} - -static QString signalProcessByNameQnxCommandLine(const QString &filePath, int sig) -{ - QString executable = filePath; - return QString::fromLatin1("for PID in $(ps -f -o pid,comm | grep %1 | awk '/%1/ {print $1}'); " - "do " - "kill -%2 $PID; " - "done").arg(executable.replace(QLatin1String("/"), QLatin1String("\\/"))).arg(sig); -} - -QString QnxDeviceProcessSignalOperation::killProcessByNameCommandLine( - const QString &filePath) const -{ - return QString::fromLatin1("%1; %2").arg(signalProcessByNameQnxCommandLine(filePath, 15), - signalProcessByNameQnxCommandLine(filePath, 9)); -} - -QString QnxDeviceProcessSignalOperation::interruptProcessByNameCommandLine( - const QString &filePath) const -{ - return signalProcessByNameQnxCommandLine(filePath, 2); -} - -} // Qnx::Internal diff --git a/src/plugins/qnx/qnxdeviceprocesssignaloperation.h b/src/plugins/qnx/qnxdeviceprocesssignaloperation.h deleted file mode 100644 index 271aea6a4e0..00000000000 --- a/src/plugins/qnx/qnxdeviceprocesssignaloperation.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (C) 2016 BlackBerry Limited. All rights reserved. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -namespace Qnx::Internal { - -class QnxDeviceProcessSignalOperation : public RemoteLinux::RemoteLinuxSignalOperation -{ -protected: - explicit QnxDeviceProcessSignalOperation(const ProjectExplorer::IDeviceConstPtr &device); - -private: - QString killProcessByNameCommandLine(const QString &filePath) const override; - QString interruptProcessByNameCommandLine(const QString &filePath) const override; - - friend class QnxDevice; -}; - -} // Qnx::Internal diff --git a/src/plugins/qnx/qnxdevicetester.cpp b/src/plugins/qnx/qnxdevicetester.cpp index fa60bfe69b6..cbead1aa3d6 100644 --- a/src/plugins/qnx/qnxdevicetester.cpp +++ b/src/plugins/qnx/qnxdevicetester.cpp @@ -4,87 +4,66 @@ #include "qnxdevicetester.h" #include "qnxconstants.h" -#include "qnxdevice.h" #include "qnxtr.h" -#include -#include +#include using namespace Utils; namespace Qnx::Internal { QnxDeviceTester::QnxDeviceTester(QObject *parent) - : ProjectExplorer::DeviceTester(parent) -{ - m_genericTester = new RemoteLinux::GenericLinuxDeviceTester(this); - connect(m_genericTester, &DeviceTester::progressMessage, - this, &DeviceTester::progressMessage); - connect(m_genericTester, &DeviceTester::errorMessage, - this, &DeviceTester::errorMessage); - connect(m_genericTester, &DeviceTester::finished, - this, &QnxDeviceTester::finished); -} + : RemoteLinux::GenericLinuxDeviceTester(parent) +{} -static QStringList versionSpecificCommandsToTest(int versionNumber) +void QnxDeviceTester::testDevice(const ProjectExplorer::IDevice::Ptr &device) { - if (versionNumber > 0x060500) - return {"slog2info"}; - return {}; -} + static const QStringList commandsToTest { + "awk", + "cat", + "cut", + "df", + "grep", + "kill", + "netstat", + "mkdir", + "print", + "printf", + "pidin", + "read", + "rm", + "sed", + "sleep", + "tail", + "uname", + "slog2info" + }; -void QnxDeviceTester::testDevice(const ProjectExplorer::IDevice::Ptr &deviceConfiguration) -{ - static const QStringList s_commandsToTest = {"awk", - "cat", - "cut", - "df", - "grep", - "kill", - "netstat", - "mkdir", - "print", - "printf", - "pidin", - "read", - "rm", - "sed", - "sleep", - "tail", - "uname"}; - m_deviceConfiguration = deviceConfiguration; - QnxDevice::ConstPtr qnxDevice = m_deviceConfiguration.dynamicCast(); - m_genericTester->setExtraCommandsToTest( - s_commandsToTest + versionSpecificCommandsToTest(qnxDevice->qnxVersion())); + setExtraCommandsToTest(commandsToTest); using namespace Tasking; - auto setupHandler = [this](QtcProcess &process) { + auto setupHandler = [device, this](Process &process) { emit progressMessage(Tr::tr("Checking that files can be created in %1...") .arg(Constants::QNX_TMP_DIR)); const QString pidFile = QString("%1/qtc_xxxx.pid").arg(Constants::QNX_TMP_DIR); - const CommandLine cmd(m_deviceConfiguration->filePath("/bin/sh"), + const CommandLine cmd(device->filePath("/bin/sh"), {"-c", QLatin1String("rm %1 > /dev/null 2>&1; echo ABC > %1 && rm %1").arg(pidFile)}); process.setCommand(cmd); }; - auto doneHandler = [this](const QtcProcess &) { + auto doneHandler = [this](const Process &) { emit progressMessage(Tr::tr("Files can be created in /var/run.") + '\n'); }; - auto errorHandler = [this](const QtcProcess &process) { + auto errorHandler = [this](const Process &process) { const QString message = process.result() == ProcessResult::StartFailed ? Tr::tr("An error occurred while checking that files can be created in %1.") .arg(Constants::QNX_TMP_DIR) + '\n' + process.errorString() : Tr::tr("Files cannot be created in %1.").arg(Constants::QNX_TMP_DIR); emit errorMessage(message + '\n'); }; - m_genericTester->setExtraTests({Process(setupHandler, doneHandler, errorHandler)}); + setExtraTests({ProcessTask(setupHandler, doneHandler, errorHandler)}); - m_genericTester->testDevice(deviceConfiguration); -} - -void QnxDeviceTester::stopTest() -{ - m_genericTester->stopTest(); + RemoteLinux::GenericLinuxDeviceTester::testDevice(device); } } // Qnx::Internal diff --git a/src/plugins/qnx/qnxdevicetester.h b/src/plugins/qnx/qnxdevicetester.h index 96f31f38416..f8fc4be8821 100644 --- a/src/plugins/qnx/qnxdevicetester.h +++ b/src/plugins/qnx/qnxdevicetester.h @@ -5,23 +5,14 @@ #include -namespace Qnx { -namespace Internal { +namespace Qnx::Internal { -class QnxDeviceTester : public ProjectExplorer::DeviceTester +class QnxDeviceTester : public RemoteLinux::GenericLinuxDeviceTester { - Q_OBJECT - public: explicit QnxDeviceTester(QObject *parent = nullptr); - void testDevice(const ProjectExplorer::IDevice::Ptr &deviceConfiguration) override; - void stopTest() override; - -private: - RemoteLinux::GenericLinuxDeviceTester *m_genericTester = nullptr; - ProjectExplorer::IDevice::ConstPtr m_deviceConfiguration; + void testDevice(const ProjectExplorer::IDevice::Ptr &device) override; }; -} // namespace Internal -} // namespace Qnx +} // Qnx::Internal diff --git a/src/plugins/qnx/qnxdevicewizard.cpp b/src/plugins/qnx/qnxdevicewizard.cpp deleted file mode 100644 index 1e7d1cfcdd6..00000000000 --- a/src/plugins/qnx/qnxdevicewizard.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (C) 2016 BlackBerry Limited. All rights reserved. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "qnxdevicewizard.h" - -#include "qnxconstants.h" -#include "qnxtr.h" - -#include -#include -#include - -using namespace ProjectExplorer; - -namespace Qnx::Internal { - -QnxDeviceWizard::QnxDeviceWizard(QWidget *parent) : - Utils::Wizard(parent) -{ - setWindowTitle(Tr::tr("New QNX Device Configuration Setup")); - - m_setupPage = new RemoteLinux::GenericLinuxDeviceConfigurationWizardSetupPage(this); - m_keyDeploymentPage - = new RemoteLinux::GenericLinuxDeviceConfigurationWizardKeyDeploymentPage(this); - m_finalPage = new RemoteLinux::GenericLinuxDeviceConfigurationWizardFinalPage(this); - - setPage(SetupPageId, m_setupPage); - setPage(KeyDeploymenPageId, m_keyDeploymentPage); - setPage(FinalPageId, m_finalPage); - m_finalPage->setCommitPage(true); - SshParameters sshParams; - sshParams.timeout = 10; - m_device = QnxDevice::create(); - m_device->setupId(IDevice::ManuallyAdded); - m_device->setType(Constants::QNX_QNX_OS_TYPE); - m_device->setMachineType(IDevice::Hardware); - m_device->setSshParameters(sshParams); - m_device->setFreePorts(Utils::PortList::fromString(QLatin1String("10000-10100"))); - m_setupPage->setDevice(m_device); - m_keyDeploymentPage->setDevice(m_device); -} - -IDevice::Ptr QnxDeviceWizard::device() -{ - return m_device; -} - -} // Qnx::Internal diff --git a/src/plugins/qnx/qnxdevicewizard.h b/src/plugins/qnx/qnxdevicewizard.h deleted file mode 100644 index ce30658ce11..00000000000 --- a/src/plugins/qnx/qnxdevicewizard.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (C) 2016 BlackBerry Limited. All rights reserved. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "qnxdevice.h" - -#include - -namespace RemoteLinux { -class GenericLinuxDeviceConfigurationWizardSetupPage; -class GenericLinuxDeviceConfigurationWizardKeyDeploymentPage; -class GenericLinuxDeviceConfigurationWizardFinalPage; -} - -namespace Qnx::Internal { - -class QnxDeviceWizard : public Utils::Wizard -{ -public: - explicit QnxDeviceWizard(QWidget *parent = nullptr); - - ProjectExplorer::IDevice::Ptr device(); - -private: - enum PageId { - SetupPageId, - KeyDeploymenPageId, - FinalPageId - }; - - RemoteLinux::GenericLinuxDeviceConfigurationWizardSetupPage *m_setupPage; - RemoteLinux::GenericLinuxDeviceConfigurationWizardKeyDeploymentPage *m_keyDeploymentPage; - RemoteLinux::GenericLinuxDeviceConfigurationWizardFinalPage *m_finalPage; - QnxDevice::Ptr m_device; -}; - -} // Qnx::Internal diff --git a/src/plugins/qnx/qnxplugin.cpp b/src/plugins/qnx/qnxplugin.cpp index c6664b9f600..09fd528086c 100644 --- a/src/plugins/qnx/qnxplugin.cpp +++ b/src/plugins/qnx/qnxplugin.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qnxanalyzesupport.h" -#include "qnxconfigurationmanager.h" #include "qnxconstants.h" #include "qnxdebugsupport.h" #include "qnxdevice.h" @@ -33,8 +32,6 @@ #include #include -#include -#include #include #include @@ -43,21 +40,12 @@ using namespace ProjectExplorer; namespace Qnx::Internal { -class QnxUploadStep : public RemoteLinux::GenericDirectUploadStep +class QnxDeployStepFactory : public BuildStepFactory { public: - QnxUploadStep(BuildStepList *bsl, Utils::Id id) : GenericDirectUploadStep(bsl, id, false) {} - static Utils::Id stepId() { return "Qnx.DirectUploadStep"; } -}; - -template -class GenericQnxDeployStepFactory : public BuildStepFactory -{ -public: - GenericQnxDeployStepFactory() + QnxDeployStepFactory(Utils::Id existingStepId, Utils::Id overrideId = {}) { - registerStep(Step::stepId()); - setDisplayName(Step::displayName()); + cloneStepCreator(existingStepId, overrideId); setSupportedConfiguration(Constants::QNX_QNX_DEPLOYCONFIGURATION_ID); setSupportedStepList(ProjectExplorer::Constants::BUILDSTEPS_DEPLOY); } @@ -78,8 +66,8 @@ public: return prj->deploymentKnowledge() == DeploymentKnowledge::Bad && prj->hasMakeInstallEquivalent(); }); - addInitialStep(DeviceCheckBuildStep::stepId()); - addInitialStep(QnxUploadStep::stepId()); + addInitialStep(ProjectExplorer::Constants::DEVICE_CHECK_STEP); + addInitialStep(Constants::QNX_DIRECT_UPLOAD_STEP_ID); } }; @@ -91,15 +79,14 @@ public: QAction *m_debugSeparator = nullptr; QAction m_attachToQnxApplication{Tr::tr("Attach to remote QNX application..."), nullptr}; - QnxConfigurationManager configurationManager; + QnxSettingsPage settingsPage; QnxQtVersionFactory qtVersionFactory; QnxDeviceFactory deviceFactory; QnxDeployConfigurationFactory deployConfigFactory; - GenericQnxDeployStepFactory directUploadDeployFactory; - GenericQnxDeployStepFactory makeInstallDeployFactory; - GenericQnxDeployStepFactory checkBuildDeployFactory; + QnxDeployStepFactory directUploadDeployFactory{RemoteLinux::Constants::DirectUploadStepId, + Constants::QNX_DIRECT_UPLOAD_STEP_ID}; + QnxDeployStepFactory makeInstallStepFactory{RemoteLinux::Constants::MakeInstallStepId}; QnxRunConfigurationFactory runConfigFactory; - QnxSettingsPage settingsPage; QnxToolChainFactory toolChainFactory; SimpleTargetRunnerFactory runWorkerFactory{{runConfigFactory.runConfigurationId()}}; QnxDebugWorkerFactory debugWorkerFactory; @@ -123,10 +110,6 @@ private: void QnxPlugin::extensionsInitialized() { - // Can't do yet as not all devices are around. - connect(DeviceManager::instance(), &DeviceManager::devicesLoaded, - &d->configurationManager, &QnxConfigurationManager::restoreConfigurations); - // Attach support connect(&d->m_attachToQnxApplication, &QAction::triggered, this, &showAttachToProcessDialog); diff --git a/src/plugins/qnx/qnxsettingspage.cpp b/src/plugins/qnx/qnxsettingspage.cpp index 0d354037735..992ba9b87d3 100644 --- a/src/plugins/qnx/qnxsettingspage.cpp +++ b/src/plugins/qnx/qnxsettingspage.cpp @@ -3,29 +3,512 @@ #include "qnxsettingspage.h" -#include "qnxconfiguration.h" -#include "qnxconfigurationmanager.h" +#include "qnxqtversion.h" +#include "qnxtoolchain.h" #include "qnxtr.h" +#include "qnxutils.h" +#include "qnxversionnumber.h" #include +#include +#include +#include + +#include #include +#include +#include +#include +#include +#include #include +#include +#include + +#include #include +#include +#include #include #include +#include +#include +#include #include #include #include #include +using namespace ProjectExplorer; +using namespace QtSupport; using namespace Utils; +using namespace Debugger; namespace Qnx::Internal { +const QLatin1String QNXEnvFileKey("EnvFile"); +const QLatin1String QNXVersionKey("QNXVersion"); +// For backward compatibility +const QLatin1String SdpEnvFileKey("NDKEnvFile"); + +const QLatin1String QNXConfiguration("QNX_CONFIGURATION"); +const QLatin1String QNXTarget("QNX_TARGET"); +const QLatin1String QNXHost("QNX_HOST"); + +const QLatin1String QNXConfigDataKey("QNXConfiguration."); +const QLatin1String QNXConfigCountKey("QNXConfiguration.Count"); +const QLatin1String QNXConfigsFileVersionKey("Version"); + +static FilePath qnxConfigSettingsFileName() +{ + return Core::ICore::userResourcePath("qnx/qnxconfigurations.xml"); +} + +class QnxConfiguration +{ +public: + QnxConfiguration() = default; + explicit QnxConfiguration(const FilePath &envFile) { m_envFile = envFile; } + + void fromMap(const QVariantMap &data) + { + QString envFilePath = data.value(QNXEnvFileKey).toString(); + if (envFilePath.isEmpty()) + envFilePath = data.value(SdpEnvFileKey).toString(); + + m_version = QnxVersionNumber(data.value(QNXVersionKey).toString()); + m_envFile = FilePath::fromString(envFilePath); + } + + QVariantMap toMap() const + { + QVariantMap data; + data.insert(QLatin1String(QNXEnvFileKey), m_envFile.toString()); + data.insert(QLatin1String(QNXVersionKey), m_version.toString()); + return data; + } + + bool isValid() const + { + return !m_qccCompiler.isEmpty() && !m_targets.isEmpty(); + } + + bool isActive() const + { + const bool hasToolChain = ToolChainManager::toolChain(Utils::equal(&ToolChain::compilerCommand, + m_qccCompiler)); + const bool hasDebugger = Utils::contains(DebuggerItemManager::debuggers(), [this](const DebuggerItem &di) { + return findTargetByDebuggerPath(di.command()); + }); + return hasToolChain && hasDebugger; + } + + void activate(); + void deactivate(); + + void ensureContents() const; + void mutableEnsureContents(); + + QString architectureNames() const + { + return transform(m_targets, &QnxTarget::shortDescription).join(", "); + } + + EnvironmentItems qnxEnvironmentItems() const; + + QnxQtVersion *qnxQtVersion(const QnxTarget &target) const; + + void createKit(const QnxTarget &target); + QVariant createDebugger(const QnxTarget &target); + Toolchains createToolChains(const QnxTarget &target); + + const QnxTarget *findTargetByDebuggerPath(const Utils::FilePath &path) const; + + bool m_hasContents = false; + QString m_configName; + + FilePath m_envFile; + FilePath m_qnxConfiguration; + FilePath m_qnxTarget; + FilePath m_qnxHost; + FilePath m_qccCompiler; + EnvironmentItems m_qnxEnv; + QnxVersionNumber m_version; + + QList m_targets; +}; + +void QnxConfiguration::activate() +{ + ensureContents(); + + if (!isValid()) { + QStringList errorStrings + = {Tr::tr("The following errors occurred while activating the QNX configuration:")}; + if (m_qccCompiler.isEmpty()) + errorStrings << Tr::tr("- No GCC compiler found."); + if (m_targets.isEmpty()) + errorStrings << Tr::tr("- No targets found."); + const QString msg = errorStrings.join('\n'); + + QMessageBox::warning(Core::ICore::dialogParent(), Tr::tr("Cannot Set Up QNX Configuration"), + msg, QMessageBox::Ok); + return; + } + + for (const QnxTarget &target : std::as_const(m_targets)) + createKit(target); +} + +void QnxConfiguration::deactivate() +{ + QTC_ASSERT(isActive(), return); + + const Toolchains toolChainsToRemove = + ToolChainManager::toolchains(Utils::equal(&ToolChain::compilerCommand, m_qccCompiler)); + + QList debuggersToRemove; + const QList debuggerItems = DebuggerItemManager::debuggers(); + for (const DebuggerItem &debuggerItem : debuggerItems) { + if (findTargetByDebuggerPath(debuggerItem.command())) + debuggersToRemove.append(debuggerItem); + } + + const QList kits = KitManager::kits(); + for (Kit *kit : kits) { + if (kit->isAutoDetected() + && DeviceTypeKitAspect::deviceTypeId(kit) == Constants::QNX_QNX_OS_TYPE + && toolChainsToRemove.contains(ToolChainKitAspect::cxxToolChain(kit))) { + KitManager::deregisterKit(kit); + } + } + + for (ToolChain *tc : toolChainsToRemove) + ToolChainManager::deregisterToolChain(tc); + + for (const DebuggerItem &debuggerItem : std::as_const(debuggersToRemove)) + DebuggerItemManager::deregisterDebugger(debuggerItem.id()); +} + +QnxQtVersion *QnxConfiguration::qnxQtVersion(const QnxTarget &target) const +{ + const QtVersions versions = QtVersionManager::versions( + Utils::equal(&QtVersion::type, QString::fromLatin1(Constants::QNX_QNX_QT))); + for (QtVersion *version : versions) { + auto qnxQt = dynamic_cast(version); + if (qnxQt && qnxQt->sdpPath() == m_envFile.parentDir()) { + const Abis abis = version->qtAbis(); + for (const Abi &qtAbi : abis) { + if (qtAbi == target.m_abi && qnxQt->cpuDir() == target.cpuDir()) + return qnxQt; + } + } + } + return nullptr; +} + +QVariant QnxConfiguration::createDebugger(const QnxTarget &target) +{ + Environment sysEnv = m_qnxHost.deviceEnvironment(); + sysEnv.modify(qnxEnvironmentItems()); + + Debugger::DebuggerItem debugger; + debugger.setCommand(target.m_debuggerPath); + debugger.reinitializeFromFile(nullptr, &sysEnv); + debugger.setUnexpandedDisplayName(Tr::tr("Debugger for %1 (%2)") + .arg(m_configName) + .arg(target.shortDescription())); + return Debugger::DebuggerItemManager::registerDebugger(debugger); +} + +Toolchains QnxConfiguration::createToolChains(const QnxTarget &target) +{ + Toolchains toolChains; + + for (const Id language : {ProjectExplorer::Constants::C_LANGUAGE_ID, + ProjectExplorer::Constants::CXX_LANGUAGE_ID}) { + auto toolChain = new QnxToolChain; + toolChain->setDetection(ToolChain::ManualDetection); + toolChain->setLanguage(language); + toolChain->setTargetAbi(target.m_abi); + toolChain->setDisplayName(Tr::tr("QCC for %1 (%2)") + .arg(m_configName) + .arg(target.shortDescription())); + toolChain->setSdpPath(m_envFile.parentDir()); + toolChain->setCpuDir(target.cpuDir()); + toolChain->resetToolChain(m_qccCompiler); + ToolChainManager::registerToolChain(toolChain); + + toolChains.append(toolChain); + } + + return toolChains; +} + +void QnxConfiguration::createKit(const QnxTarget &target) +{ + Toolchains toolChains = createToolChains(target); + QVariant debugger = createDebugger(target); + + QnxQtVersion *qnxQt = qnxQtVersion(target); // nullptr is ok. + + const auto init = [&](Kit *k) { + QtKitAspect::setQtVersion(k, qnxQt); + ToolChainKitAspect::setToolChain(k, toolChains[0]); + ToolChainKitAspect::setToolChain(k, toolChains[1]); + + if (debugger.isValid()) + DebuggerKitAspect::setDebugger(k, debugger); + + DeviceTypeKitAspect::setDeviceTypeId(k, Constants::QNX_QNX_OS_TYPE); + // TODO: Add sysroot? + + k->setUnexpandedDisplayName(Tr::tr("Kit for %1 (%2)") + .arg(m_configName) + .arg(target.shortDescription())); + + k->setAutoDetected(false); + k->setAutoDetectionSource(m_envFile.toString()); + k->setMutable(DeviceKitAspect::id(), true); + + k->setSticky(ToolChainKitAspect::id(), true); + k->setSticky(DeviceTypeKitAspect::id(), true); + k->setSticky(SysRootKitAspect::id(), true); + k->setSticky(DebuggerKitAspect::id(), true); + k->setSticky(QmakeProjectManager::Constants::KIT_INFORMATION_ID, true); + + EnvironmentKitAspect::setEnvironmentChanges(k, qnxEnvironmentItems()); + }; + + // add kit with device and qt version not sticky + KitManager::registerKit(init); +} + +void QnxConfiguration::ensureContents() const +{ + if (!m_hasContents) + const_cast(this)->mutableEnsureContents(); +} + +void QnxConfiguration::mutableEnsureContents() +{ + QTC_ASSERT(!m_envFile.isEmpty(), return); + m_hasContents = true; + + m_qnxEnv = QnxUtils::qnxEnvironmentFromEnvFile(m_envFile); + for (const EnvironmentItem &item : std::as_const(m_qnxEnv)) { + if (item.name == QNXConfiguration) + m_qnxConfiguration = m_envFile.withNewPath(item.value).canonicalPath(); + else if (item.name == QNXTarget) + m_qnxTarget = m_envFile.withNewPath(item.value).canonicalPath(); + else if (item.name == QNXHost) + m_qnxHost = m_envFile.withNewPath(item.value).canonicalPath(); + } + + const FilePath qccPath = m_qnxHost.pathAppended("usr/bin/qcc").withExecutableSuffix(); + if (qccPath.exists()) + m_qccCompiler = qccPath; + + // Some fallback in case the qconfig dir with .xml files is not found later. + if (m_configName.isEmpty()) + m_configName = QString("%1 - %2").arg(m_qnxHost.fileName(), m_qnxTarget.fileName()); + + m_targets = QnxUtils::findTargets(m_qnxTarget); + + // Assign debuggers. + const FilePath hostUsrBinDir = m_qnxHost.pathAppended("usr/bin"); + QString pattern = "nto*-gdb"; + if (m_qnxHost.osType() == Utils::OsTypeWindows) + pattern += ".exe"; + + const FilePaths debuggerNames = hostUsrBinDir.dirEntries({{pattern}, QDir::Files}); + Environment sysEnv = m_qnxHost.deviceEnvironment(); + sysEnv.modify(qnxEnvironmentItems()); + + for (const FilePath &debuggerPath : debuggerNames) { + DebuggerItem item; + item.setCommand(debuggerPath); + item.reinitializeFromFile(nullptr, &sysEnv); + bool found = false; + for (const Abi &abi : item.abis()) { + for (QnxTarget &target : m_targets) { + if (target.m_abi.isCompatibleWith(abi)) { + found = true; + + if (target.m_debuggerPath.isEmpty()) { + target.m_debuggerPath = debuggerPath; + } else { + qWarning() << debuggerPath << "has the same ABI as" << target.m_debuggerPath + << "... discarded"; + break; + } + } + } + } + if (!found) + qWarning() << "No target found for" << debuggerPath.toUserOutput() << "... discarded"; + } + + // Remove debuggerless targets. + Utils::erase(m_targets, [](const QnxTarget &target) { + if (target.m_debuggerPath.isEmpty()) + qWarning() << "No debugger found for" << target.m_path << "... discarded"; + return target.m_debuggerPath.isEmpty(); + }); + + const FilePath configPath = m_qnxConfiguration / "qconfig"; + if (!configPath.isDir()) + return; + + configPath.iterateDirectory([this, configPath](const FilePath &sdpFile) { + QFile xmlFile(sdpFile.toFSPathString()); + if (!xmlFile.open(QIODevice::ReadOnly)) + return IterationPolicy::Continue; + + QDomDocument doc; + if (!doc.setContent(&xmlFile)) // Skip error message + return IterationPolicy::Continue; + + QDomElement docElt = doc.documentElement(); + if (docElt.tagName() != QLatin1String("qnxSystemDefinition")) + return IterationPolicy::Continue; + + QDomElement childElt = docElt.firstChildElement(QLatin1String("installation")); + // The file contains only one installation node + if (childElt.isNull()) // The file contains only one base node + return IterationPolicy::Continue; + + FilePath host = configPath.withNewPath( + childElt.firstChildElement(QLatin1String("host")).text()).canonicalPath(); + if (m_qnxHost != host) + return IterationPolicy::Continue; + + FilePath target = configPath.withNewPath( + childElt.firstChildElement(QLatin1String("target")).text()).canonicalPath(); + if (m_qnxTarget != target) + return IterationPolicy::Continue; + + m_configName = childElt.firstChildElement(QLatin1String("name")).text(); + QString version = childElt.firstChildElement(QLatin1String("version")).text(); + m_version = QnxVersionNumber(version); + return IterationPolicy::Stop; + }, {{"*.xml"}, QDir::Files}); +} + +EnvironmentItems QnxConfiguration::qnxEnvironmentItems() const +{ + ensureContents(); + return { + {QNXConfiguration, m_qnxConfiguration.path()}, + {QNXTarget, m_qnxTarget.path()}, + {QNXHost, m_qnxHost.path()} + }; +} + +const QnxTarget *QnxConfiguration::findTargetByDebuggerPath( + const FilePath &path) const +{ + const auto it = std::find_if(m_targets.begin(), m_targets.end(), + [path](const QnxTarget &target) { return target.m_debuggerPath == path; }); + return it == m_targets.end() ? nullptr : &(*it); +} + + +// QnxSettingsPagePrivate + +class QnxSettingsPagePrivate : public QObject +{ +public: + QnxSettingsPagePrivate() + { + connect(Core::ICore::instance(), &Core::ICore::saveSettingsRequested, + this, &QnxSettingsPagePrivate::saveConfigs); + // Can't do yet as not all devices are around. + connect(DeviceManager::instance(), &DeviceManager::devicesLoaded, + this, &QnxSettingsPagePrivate::restoreConfigurations); + } + + void saveConfigs() + { + QVariantMap data; + data.insert(QLatin1String(QNXConfigsFileVersionKey), 1); + int count = 0; + for (const QnxConfiguration &config : std::as_const(m_configurations)) { + QVariantMap tmp = config.toMap(); + if (tmp.isEmpty()) + continue; + + data.insert(QNXConfigDataKey + QString::number(count), tmp); + ++count; + } + + data.insert(QLatin1String(QNXConfigCountKey), count); + m_writer.save(data, Core::ICore::dialogParent()); + } + + void restoreConfigurations() + { + PersistentSettingsReader reader; + if (!reader.load(qnxConfigSettingsFileName())) + return; + + QVariantMap data = reader.restoreValues(); + int count = data.value(QNXConfigCountKey, 0).toInt(); + for (int i = 0; i < count; ++i) { + const QString key = QNXConfigDataKey + QString::number(i); + if (!data.contains(key)) + continue; + + QnxConfiguration config; + config.fromMap(data.value(key).toMap()); + m_configurations[config.m_envFile] = config; + } + } + + QnxConfiguration *configurationFromEnvFile(const FilePath &envFile) + { + auto it = m_configurations.find(envFile); + return it == m_configurations.end() ? nullptr : &*it; + } + + QHash m_configurations; + PersistentSettingsWriter m_writer{qnxConfigSettingsFileName(), "QnxConfigurations"}; +}; + +static QnxSettingsPagePrivate *dd = nullptr; + + +// QnxSettingsWidget + +class ArchitecturesList final : public QWidget +{ +public: + void setConfiguration(const FilePath &envFile) + { + m_envFile = envFile; + delete layout(); + + QnxConfiguration *config = dd->configurationFromEnvFile(envFile); + if (!config) + return; + + auto l = new QHBoxLayout(this); + for (const QnxTarget &target : config->m_targets) { + auto button = new QPushButton(tr("Create Kit for %1").arg(target.cpuDir())); + connect(button, &QPushButton::clicked, this, [config, target] { + config->createKit(target); + }); + l->addWidget(button); + } + } + + FilePath m_envFile; +}; + class QnxSettingsWidget final : public Core::IOptionsPageWidget { public: @@ -42,10 +525,10 @@ public: public: bool operator ==(const ConfigState &cs) const { - return config == cs.config && state == cs.state; + return envFile == cs.envFile && state == cs.state; } - QnxConfiguration *config; + FilePath envFile; State state; }; @@ -53,73 +536,69 @@ public: void addConfiguration(); void removeConfiguration(); - void generateKits(bool checked); void updateInformation(); void populateConfigsCombo(); - void setConfigState(QnxConfiguration *config, State state); + void setConfigState(const FilePath &envFile, State state); private: QComboBox *m_configsCombo = new QComboBox; - QCheckBox *m_generateKitsCheckBox = new QCheckBox(Tr::tr("Generate kits")); QLabel *m_configName = new QLabel; QLabel *m_configVersion = new QLabel; QLabel *m_configHost = new QLabel; QLabel *m_configTarget = new QLabel; + QLabel *m_compiler = new QLabel; + QLabel *m_architectures = new QLabel; + + ArchitecturesList *m_kitCreation = new ArchitecturesList; - QnxConfigurationManager *m_qnxConfigManager = QnxConfigurationManager::instance(); QList m_changedConfigs; }; QnxSettingsWidget::QnxSettingsWidget() { - auto addButton = new QPushButton(Tr::tr("Add...")); - auto removeButton = new QPushButton(Tr::tr("Remove")); - using namespace Layouting; Row { Column { m_configsCombo, - Row { m_generateKitsCheckBox, st }, Group { title(Tr::tr("Configuration Information:")), Form { Tr::tr("Name:"), m_configName, br, Tr::tr("Version:"), m_configVersion, br, Tr::tr("Host:"), m_configHost, br, - Tr::tr("Target:"), m_configTarget + Tr::tr("Target:"), m_configTarget, br, + Tr::tr("Compiler:"), m_compiler, br, + Tr::tr("Architectures:"), m_architectures } }, + Row { m_kitCreation, st }, st }, Column { - addButton, - removeButton, + PushButton { + text(Tr::tr("Add...")), + onClicked([this] { addConfiguration(); }, this) + }, + PushButton { + text(Tr::tr("Remove")), + onClicked([this] { removeConfiguration(); }, this) + }, st } }.attachTo(this); populateConfigsCombo(); - connect(addButton, &QAbstractButton::clicked, - this, &QnxSettingsWidget::addConfiguration); - connect(removeButton, &QAbstractButton::clicked, - this, &QnxSettingsWidget::removeConfiguration); + connect(m_configsCombo, &QComboBox::currentIndexChanged, this, &QnxSettingsWidget::updateInformation); - connect(m_generateKitsCheckBox, &QAbstractButton::toggled, - this, &QnxSettingsWidget::generateKits); - connect(m_qnxConfigManager, &QnxConfigurationManager::configurationsListUpdated, - this, &QnxSettingsWidget::populateConfigsCombo); - connect(QtSupport::QtVersionManager::instance(), - &QtSupport::QtVersionManager::qtVersionsChanged, - this, &QnxSettingsWidget::updateInformation); } void QnxSettingsWidget::addConfiguration() { QString filter; - if (Utils::HostOsInfo::isWindowsHost()) + if (HostOsInfo::isWindowsHost()) filter = "*.bat file"; else filter = "*.sh file"; @@ -129,82 +608,85 @@ void QnxSettingsWidget::addConfiguration() if (envFile.isEmpty()) return; - QnxConfiguration *config = new QnxConfiguration(envFile); - if (m_qnxConfigManager->configurations().contains(config) || !config->isValid()) { + if (dd->m_configurations.contains(envFile)) { QMessageBox::warning(Core::ICore::dialogParent(), Tr::tr("Warning"), - Tr::tr("Configuration already exists or is invalid.")); - delete config; + Tr::tr("Configuration already exists.")); return; } - setConfigState(config, Added); - m_configsCombo->addItem(config->displayName(), - QVariant::fromValue(static_cast(config))); + // Temporary to be able to check + QnxConfiguration config(envFile); + config.ensureContents(); + if (!config.isValid()) { + QMessageBox::warning(Core::ICore::dialogParent(), + Tr::tr("Warning"), + Tr::tr("Configuration is not valid.")); + return; + } + + setConfigState(envFile, Added); + m_configsCombo->addItem(config.m_configName, QVariant::fromValue(envFile)); } void QnxSettingsWidget::removeConfiguration() { - const int currentIndex = m_configsCombo->currentIndex(); - auto config = static_cast( - m_configsCombo->itemData(currentIndex).value()); + const FilePath envFile = m_configsCombo->currentData().value(); + QTC_ASSERT(!envFile.isEmpty(), return); - if (!config) - return; + QnxConfiguration *config = dd->configurationFromEnvFile(envFile); + QTC_ASSERT(config, return); + + config->ensureContents(); QMessageBox::StandardButton button = QMessageBox::question(Core::ICore::dialogParent(), Tr::tr("Remove QNX Configuration"), - Tr::tr("Are you sure you want to remove:\n %1?").arg(config->displayName()), + Tr::tr("Are you sure you want to remove:\n %1?") + .arg(config->m_configName), QMessageBox::Yes | QMessageBox::No); if (button == QMessageBox::Yes) { - setConfigState(config, Removed); - m_configsCombo->removeItem(currentIndex); + setConfigState(envFile, Removed); + m_configsCombo->removeItem(m_configsCombo->currentIndex()); + updateInformation(); } } -void QnxSettingsWidget::generateKits(bool checked) -{ - const int currentIndex = m_configsCombo->currentIndex(); - auto config = static_cast( - m_configsCombo->itemData(currentIndex).value()); - if (!config) - return; - - setConfigState(config, checked ? Activated : Deactivated); -} - void QnxSettingsWidget::updateInformation() { - const int currentIndex = m_configsCombo->currentIndex(); + const FilePath envFile = m_configsCombo->currentData().value(); - auto config = static_cast( - m_configsCombo->itemData(currentIndex).value()); - - // update the checkbox - m_generateKitsCheckBox->setEnabled(config ? config->isValid() : false); - m_generateKitsCheckBox->setChecked(config ? config->isActive() : false); - - // update information - m_configName->setText(config ? config->displayName() : QString()); - m_configVersion->setText(config ? config->version().toString() : QString()); - m_configHost->setText(config ? config->qnxHost().toString() : QString()); - m_configTarget->setText(config ? config->qnxTarget().toString() : QString()); + if (QnxConfiguration *config = dd->configurationFromEnvFile(envFile)) { + config->ensureContents(); + m_configName->setText(config->m_configName); + m_configVersion->setText(config->m_version.toString()); + m_configHost->setText(config->m_qnxHost.toString()); + m_configTarget->setText(config->m_qnxTarget.toString()); + m_compiler->setText(config->m_qccCompiler.toUserOutput()); + m_architectures->setText(config->architectureNames()); + m_kitCreation->setConfiguration(envFile); + } else { + m_configName->setText({}); + m_configVersion->setText({}); + m_configHost->setText({}); + m_compiler->setText({}); + m_architectures->setText({}); + m_kitCreation->setConfiguration({}); + } } void QnxSettingsWidget::populateConfigsCombo() { m_configsCombo->clear(); - const QList configList = m_qnxConfigManager->configurations(); - for (QnxConfiguration *config : configList) { - m_configsCombo->addItem(config->displayName(), - QVariant::fromValue(static_cast(config))); + for (const QnxConfiguration &config : std::as_const(dd->m_configurations)) { + config.ensureContents(); + m_configsCombo->addItem(config.m_configName, QVariant::fromValue(config.m_envFile)); } updateInformation(); } -void QnxSettingsWidget::setConfigState(QnxConfiguration *config, State state) +void QnxSettingsWidget::setConfigState(const FilePath &envFile, State state) { State stateToRemove = Activated; switch (state) { @@ -223,45 +705,79 @@ void QnxSettingsWidget::setConfigState(QnxConfiguration *config, State state) } for (const ConfigState &configState : std::as_const(m_changedConfigs)) { - if (configState.config == config && configState.state == stateToRemove) + if (configState.envFile == envFile && configState.state == stateToRemove) m_changedConfigs.removeAll(configState); } - m_changedConfigs.append(ConfigState{config, state}); + m_changedConfigs.append(ConfigState{envFile, state}); } void QnxSettingsWidget::apply() { for (const ConfigState &configState : std::as_const(m_changedConfigs)) { switch (configState.state) { - case Activated : - configState.config->activate(); + case Activated: { + QnxConfiguration *config = dd->configurationFromEnvFile(configState.envFile); + QTC_ASSERT(config, break); + config->activate(); break; - case Deactivated: - configState.config->deactivate(); + } + case Deactivated: { + QnxConfiguration *config = dd->configurationFromEnvFile(configState.envFile); + QTC_ASSERT(config, break); + config->deactivate(); break; - case Added: - m_qnxConfigManager->addConfiguration(configState.config); + } + case Added: { + QnxConfiguration config(configState.envFile); + config.ensureContents(); + dd->m_configurations.insert(configState.envFile, config); break; + } case Removed: - configState.config->deactivate(); - m_qnxConfigManager->removeConfiguration(configState.config); + QnxConfiguration *config = dd->configurationFromEnvFile(configState.envFile); + QTC_ASSERT(config, break); + config->deactivate(); + dd->m_configurations.remove(configState.envFile); break; } } m_changedConfigs.clear(); + populateConfigsCombo(); } - // QnxSettingsPage +QList QnxSettingsPage::autoDetect(const QList &alreadyKnown) +{ + QList result; + for (const QnxConfiguration &config : std::as_const(dd->m_configurations)) { + config.ensureContents(); + for (const QnxTarget &target : std::as_const(config.m_targets)) { + result += Utils::filtered(alreadyKnown, [config, target](ToolChain *tc) { + return tc->typeId() == Constants::QNX_TOOLCHAIN_ID + && tc->targetAbi() == target.m_abi + && tc->compilerCommand() == config.m_qccCompiler; + }); + } + } + return result; +} + QnxSettingsPage::QnxSettingsPage() { setId("DD.Qnx Configuration"); setDisplayName(Tr::tr("QNX")); setCategory(ProjectExplorer::Constants::DEVICE_SETTINGS_CATEGORY); setWidgetCreator([] { return new QnxSettingsWidget; }); + + dd = new QnxSettingsPagePrivate; +} + +QnxSettingsPage::~QnxSettingsPage() +{ + delete dd; } } // Qnx::Internal diff --git a/src/plugins/qnx/qnxsettingspage.h b/src/plugins/qnx/qnxsettingspage.h index cdf6f1ba860..f127d5f35e4 100644 --- a/src/plugins/qnx/qnxsettingspage.h +++ b/src/plugins/qnx/qnxsettingspage.h @@ -5,12 +5,18 @@ #include +namespace ProjectExplorer { class ToolChain; } + namespace Qnx::Internal { class QnxSettingsPage final : public Core::IOptionsPage { public: QnxSettingsPage(); + ~QnxSettingsPage(); + + static QList autoDetect( + const QList &alreadyKnown); }; } // Qnx::Internal diff --git a/src/plugins/qnx/qnxtoolchain.cpp b/src/plugins/qnx/qnxtoolchain.cpp index 773efc0c740..8176b3d17c9 100644 --- a/src/plugins/qnx/qnxtoolchain.cpp +++ b/src/plugins/qnx/qnxtoolchain.cpp @@ -3,9 +3,8 @@ #include "qnxtoolchain.h" -#include "qnxconfiguration.h" -#include "qnxconfigurationmanager.h" #include "qnxconstants.h" +#include "qnxsettingspage.h" #include "qnxtr.h" #include "qnxutils.h" @@ -54,7 +53,7 @@ static Abis detectTargetAbis(const FilePath &sdpPath) const EnvironmentItems environment = QnxUtils::qnxEnvironment(sdpPath); for (const EnvironmentItem &item : environment) { if (item.name == QLatin1String("QNX_TARGET")) - qnxTarget = FilePath::fromString(item.value); + qnxTarget = sdpPath.withNewPath(item.value); } } @@ -222,10 +221,7 @@ Toolchains QnxToolChainFactory::autoDetect(const ToolchainDetector &detector) co if (detector.device) return {}; - Toolchains tcs; - const auto configurations = QnxConfigurationManager::instance()->configurations(); - for (QnxConfiguration *configuration : configurations) - tcs += configuration->autoDetect(detector.alreadyKnown); + Toolchains tcs = QnxSettingsPage::autoDetect(detector.alreadyKnown); return tcs; } diff --git a/src/plugins/qnx/qnxutils.cpp b/src/plugins/qnx/qnxutils.cpp index 697e95bebc6..ba2c41b3453 100644 --- a/src/plugins/qnx/qnxutils.cpp +++ b/src/plugins/qnx/qnxutils.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include @@ -16,6 +16,15 @@ using namespace Utils; namespace Qnx::Internal { +QnxTarget::QnxTarget(const Utils::FilePath &path, const ProjectExplorer::Abi &abi) : + m_path(path), m_abi(abi) +{} + +QString QnxTarget::shortDescription() const +{ + return QnxUtils::cpuDirShortDescription(cpuDir()); +} + QString QnxUtils::cpuDirFromAbi(const Abi &abi) { if (abi.os() != Abi::OS::QnxOS) @@ -89,7 +98,7 @@ EnvironmentItems QnxUtils::qnxEnvironmentFromEnvFile(const FilePath &filePath) tmpFile->writeFileContents(content.toUtf8()); // running wrapper script - QtcProcess process; + Process process; if (isWindows) process.setCommand({filePath.withNewPath("cmd.exe"), {"/C", tmpFile->path()}}); else diff --git a/src/plugins/qnx/qnxutils.h b/src/plugins/qnx/qnxutils.h index 4f523953e46..2fea51ccc63 100644 --- a/src/plugins/qnx/qnxutils.h +++ b/src/plugins/qnx/qnxutils.h @@ -14,12 +14,14 @@ namespace Qnx::Internal { class QnxTarget { public: - QnxTarget(const Utils::FilePath &path, const ProjectExplorer::Abi &abi) : - m_path(path), m_abi(abi) - { - } + QnxTarget(const Utils::FilePath &path, const ProjectExplorer::Abi &abi); + + QString shortDescription() const; + QString cpuDir() const { return m_path.fileName(); } + Utils::FilePath m_path; ProjectExplorer::Abi m_abi; + Utils::FilePath m_debuggerPath; }; namespace QnxUtils { diff --git a/src/plugins/qnx/slog2inforunner.cpp b/src/plugins/qnx/slog2inforunner.cpp index 58509782c5b..77700804b21 100644 --- a/src/plugins/qnx/slog2inforunner.cpp +++ b/src/plugins/qnx/slog2inforunner.cpp @@ -3,13 +3,13 @@ #include "slog2inforunner.h" -#include "qnxdevice.h" #include "qnxtr.h" +#include #include +#include #include -#include #include @@ -31,50 +31,47 @@ Slog2InfoRunner::Slog2InfoRunner(RunControl *runControl) void Slog2InfoRunner::start() { - using namespace Utils::Tasking; + using namespace Tasking; QTC_CHECK(!m_taskTree); - const auto testStartHandler = [this](QtcProcess &process) { + const auto testStartHandler = [this](Process &process) { process.setCommand({device()->filePath("slog2info"), {}}); }; - const auto testDoneHandler = [this](const QtcProcess &) { + const auto testDoneHandler = [this](const Process &) { m_found = true; }; - const auto testErrorHandler = [this](const QtcProcess &) { - QnxDevice::ConstPtr qnxDevice = device().dynamicCast(); - if (qnxDevice && qnxDevice->qnxVersion() > 0x060500) { - appendMessage(Tr::tr("Warning: \"slog2info\" is not found on the device, " - "debug output not available."), ErrorMessageFormat); - } + const auto testErrorHandler = [this](const Process &) { + appendMessage(Tr::tr("Warning: \"slog2info\" is not found on the device, " + "debug output not available."), ErrorMessageFormat); }; - const auto launchTimeStartHandler = [this](QtcProcess &process) { + const auto launchTimeStartHandler = [this](Process &process) { process.setCommand({device()->filePath("date"), "+\"%d %H:%M:%S\"", CommandLine::Raw}); }; - const auto launchTimeDoneHandler = [this](const QtcProcess &process) { + const auto launchTimeDoneHandler = [this](const Process &process) { QTC_CHECK(!m_applicationId.isEmpty()); QTC_CHECK(m_found); m_launchDateTime = QDateTime::fromString(process.cleanedStdOut().trimmed(), "dd HH:mm:ss"); }; - const auto logStartHandler = [this](QtcProcess &process) { + const auto logStartHandler = [this](Process &process) { process.setCommand({device()->filePath("slog2info"), {"-w"}}); - connect(&process, &QtcProcess::readyReadStandardOutput, this, [&] { + connect(&process, &Process::readyReadStandardOutput, this, [&] { processLogInput(QString::fromLatin1(process.readAllRawStandardOutput())); }); - connect(&process, &QtcProcess::readyReadStandardError, this, [&] { + connect(&process, &Process::readyReadStandardError, this, [&] { appendMessage(QString::fromLatin1(process.readAllRawStandardError()), StdErrFormat); }); }; - const auto logErrorHandler = [this](const QtcProcess &process) { + const auto logErrorHandler = [this](const Process &process) { appendMessage(Tr::tr("Cannot show slog2info output. Error: %1").arg(process.errorString()), StdErrFormat); }; - const Tasking::Group root { - Process(testStartHandler, testDoneHandler, testErrorHandler), - Process(launchTimeStartHandler, launchTimeDoneHandler), - Process(logStartHandler, {}, logErrorHandler) + const Group root { + ProcessTask(testStartHandler, testDoneHandler, testErrorHandler), + ProcessTask(launchTimeStartHandler, launchTimeDoneHandler), + ProcessTask(logStartHandler, {}, logErrorHandler) }; m_taskTree.reset(new TaskTree(root)); diff --git a/src/plugins/qnx/slog2inforunner.h b/src/plugins/qnx/slog2inforunner.h index e89b80b6975..83d8d293336 100644 --- a/src/plugins/qnx/slog2inforunner.h +++ b/src/plugins/qnx/slog2inforunner.h @@ -7,7 +7,7 @@ #include -namespace Utils { class TaskTree; } +namespace Tasking { class TaskTree; } namespace Qnx::Internal { @@ -33,7 +33,7 @@ private: bool m_currentLogs = false; QString m_remainingData; - std::unique_ptr m_taskTree; + std::unique_ptr m_taskTree; }; } // Qnx::Internal diff --git a/src/plugins/qtsupport/baseqtversion.cpp b/src/plugins/qtsupport/baseqtversion.cpp index 52461fae505..179649c53b8 100644 --- a/src/plugins/qtsupport/baseqtversion.cpp +++ b/src/plugins/qtsupport/baseqtversion.cpp @@ -21,7 +21,8 @@ #include #include #include -#include +#include +#include #include #include #include @@ -32,8 +33,8 @@ #include #include #include +#include #include -#include #include #include @@ -1252,7 +1253,7 @@ void QtVersionPrivate::updateVersionInfo() m_qmakeIsExecutable = true; auto fileProperty = [this](const QByteArray &name) { - return FilePath::fromUserInput(qmakeProperty(name)).onDevice(m_qmakeCommand); + return m_qmakeCommand.withNewPath(qmakeProperty(name)).cleanPath(); }; m_data.prefix = fileProperty("QT_INSTALL_PREFIX"); @@ -1543,10 +1544,10 @@ void QtVersion::populateQmlFileFinder(FileInProjectFinder *finder, const Target // ... else try the session manager's global startup project ... if (!startupProject) - startupProject = SessionManager::startupProject(); + startupProject = ProjectManager::startupProject(); // ... and if that is null, use the first project available. - const QList projects = SessionManager::projects(); + const QList projects = ProjectManager::projects(); QTC_CHECK(projects.isEmpty() || startupProject); FilePath projectDirectory; @@ -1683,7 +1684,7 @@ static QByteArray runQmakeQuery(const FilePath &binary, const Environment &env, // Prevent e.g. qmake 4.x on MinGW to show annoying errors about missing dll's. WindowsCrashDialogBlocker crashDialogBlocker; - QtcProcess process; + Process process; process.setEnvironment(env); process.setCommand({binary, {"-query"}}); process.start(); @@ -1773,7 +1774,7 @@ FilePath QtVersionPrivate::mkspecDirectoryFromVersionInfo(const QHash &versionInfo, diff --git a/src/plugins/qtsupport/codegensettingspage.cpp b/src/plugins/qtsupport/codegensettingspage.cpp index 8e0a3cfca08..4a30e5f969f 100644 --- a/src/plugins/qtsupport/codegensettingspage.cpp +++ b/src/plugins/qtsupport/codegensettingspage.cpp @@ -39,12 +39,10 @@ private: CodeGenSettingsPageWidget::CodeGenSettingsPageWidget() { - resize(340, 232); - CodeGenSettings parameters; parameters.fromSettings(Core::ICore::settings()); - using namespace Utils::Layouting; + using namespace Layouting; m_ptrAggregationRadioButton = new QRadioButton(Tr::tr("Aggregation as a pointer member")); m_ptrAggregationRadioButton->setChecked diff --git a/src/plugins/qtsupport/exampleslistmodel.cpp b/src/plugins/qtsupport/exampleslistmodel.cpp index d3cf3535dd9..5beb9c80a35 100644 --- a/src/plugins/qtsupport/exampleslistmodel.cpp +++ b/src/plugins/qtsupport/exampleslistmodel.cpp @@ -4,7 +4,6 @@ #include "exampleslistmodel.h" #include "examplesparser.h" -#include "qtsupporttr.h" #include #include @@ -29,6 +28,7 @@ #include #include +#include #include #include @@ -38,6 +38,8 @@ using namespace Utils; namespace QtSupport { namespace Internal { +static QLoggingCategory log = QLoggingCategory("qtc.examples", QtWarningMsg); + static bool debugExamples() { return qtcEnvironmentVariableIsSet("QTC_DEBUG_EXAMPLESMODEL"); @@ -64,16 +66,16 @@ int ExampleSetModel::readCurrentIndexFromSettings() const ExampleSetModel::ExampleSetModel() { + if (debugExamples() && !log().isDebugEnabled()) + log().setEnabled(QtDebugMsg, true); // read extra example sets settings QSettings *settings = Core::ICore::settings(); const QStringList list = settings->value("Help/InstalledExamples", QStringList()).toStringList(); - if (debugExamples()) - qWarning() << "Reading Help/InstalledExamples from settings:" << list; + qCDebug(log) << "Reading Help/InstalledExamples from settings:" << list; for (const QString &item : list) { const QStringList &parts = item.split(QLatin1Char('|')); if (parts.size() < 3) { - if (debugExamples()) - qWarning() << "Item" << item << "has less than 3 parts (separated by '|'):" << parts; + qCDebug(log) << "Item" << item << "has less than 3 parts (separated by '|'):" << parts; continue; } ExtraExampleSet set; @@ -82,15 +84,13 @@ ExampleSetModel::ExampleSetModel() set.examplesPath = parts.at(2); QFileInfo fi(set.manifestPath); if (!fi.isDir() || !fi.isReadable()) { - if (debugExamples()) - qWarning() << "Manifest path " << set.manifestPath << "is not a readable directory, ignoring"; + qCDebug(log) << "Manifest path " << set.manifestPath + << "is not a readable directory, ignoring"; continue; } - if (debugExamples()) { - qWarning() << "Adding examples set displayName=" << set.displayName - << ", manifestPath=" << set.manifestPath - << ", examplesPath=" << set.examplesPath; - } + qCDebug(log) << "Adding examples set displayName=" << set.displayName + << ", manifestPath=" << set.manifestPath + << ", examplesPath=" << set.examplesPath; if (!Utils::anyOf(m_extraExampleSets, [&set](const ExtraExampleSet &s) { return FilePath::fromString(s.examplesPath).cleanPath() == FilePath::fromString(set.examplesPath).cleanPath() @@ -98,8 +98,8 @@ ExampleSetModel::ExampleSetModel() == FilePath::fromString(set.manifestPath).cleanPath(); })) { m_extraExampleSets.append(set); - } else if (debugExamples()) { - qWarning() << "Not adding, because example set with same directories exists"; + } else { + qCDebug(log) << "Not adding, because example set with same directories exists"; } } m_extraExampleSets += pluginRegisteredExampleSets(); @@ -138,11 +138,9 @@ void ExampleSetModel::recreateModel(const QtVersions &qtVersions) if (extraManifestDirs.contains(version->docsPath())) { m_extraExampleSets[extraManifestDirs.value(version->docsPath())].qtVersion = version->qtVersion(); - if (debugExamples()) { - qWarning() << "Not showing Qt version because manifest path is already added " - "through InstalledExamples settings:" - << version->displayName(); - } + qCDebug(log) << "Not showing Qt version because manifest path is already added " + "through InstalledExamples settings:" + << version->displayName(); continue; } auto newItem = new QStandardItem(); @@ -255,7 +253,8 @@ static QPixmap fetchPixmapAndUpdatePixmapCache(const QString &url) img.convertTo(QImage::Format_RGB32); const int dpr = qApp->devicePixelRatio(); // boundedTo -> don't scale thumbnails up - const QSize scaledSize = Core::ListModel::defaultImageSize.boundedTo(img.size()) * dpr; + const QSize scaledSize = + WelcomePageHelpers::GridItemImageSize.boundedTo(img.size()) * dpr; pixmap = QPixmap::fromImage( img.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation)); pixmap.setDevicePixelRatio(dpr); @@ -318,65 +317,14 @@ static bool isValidExampleOrDemo(ExampleItem *item) } if (!ok) { item->tags.append(QLatin1String("broken")); - if (debugExamples()) - qWarning() << QString::fromLatin1("ERROR: Item \"%1\" broken: %2").arg(item->name, reason); + qCDebug(log) << QString::fromLatin1("ERROR: Item \"%1\" broken: %2").arg(item->name, reason); } - if (debugExamples() && item->description.isEmpty()) - qWarning() << QString::fromLatin1("WARNING: Item \"%1\" has no description").arg(item->name); + if (item->description.isEmpty()) + qCDebug(log) << QString::fromLatin1("WARNING: Item \"%1\" has no description") + .arg(item->name); return ok || debugExamples(); } -static bool sortByHighlightedAndName(ExampleItem *first, ExampleItem *second) -{ - if (first->isHighlighted && !second->isHighlighted) - return true; - if (!first->isHighlighted && second->isHighlighted) - return false; - return first->name.compare(second->name, Qt::CaseInsensitive) < 0; -} - -static QList>> getCategories( - const QList &items, bool sortIntoCategories) -{ - static const QString otherDisplayName = Tr::tr("Other", "Category for all other examples"); - const bool useCategories = sortIntoCategories - || qtcEnvironmentVariableIsSet("QTC_USE_EXAMPLE_CATEGORIES"); - QList other; - QMap> categoryMap; - if (useCategories) { - for (ExampleItem *item : items) { - const QStringList itemCategories = item->metaData.value("category"); - for (const QString &category : itemCategories) - categoryMap[category].append(item); - if (itemCategories.isEmpty()) - other.append(item); - } - } - QList>> categories; - if (categoryMap.isEmpty()) { - // The example set doesn't define categories. Consider the "highlighted" ones as "featured" - QList featured; - QList allOther; - std::tie(featured, allOther) = Utils::partition(items, [](ExampleItem *i) { - return i->isHighlighted; - }); - if (!featured.isEmpty()) - categories.append({Tr::tr("Featured", "Category for highlighted examples"), featured}); - if (!allOther.isEmpty()) - categories.append({otherDisplayName, allOther}); - } else { - const auto end = categoryMap.constKeyValueEnd(); - for (auto it = categoryMap.constKeyValueBegin(); it != end; ++it) - categories.append(*it); - if (!other.isEmpty()) - categories.append({otherDisplayName, other}); - } - const auto end = categories.end(); - for (auto it = categories.begin(); it != end; ++it) - sort(it->second, sortByHighlightedAndName); - return categories; -} - void ExamplesViewController::updateExamples() { QString examplesInstallPath; @@ -391,10 +339,8 @@ void ExamplesViewController::updateExamples() QList items; for (const QString &exampleSource : sources) { const auto manifest = FilePath::fromUserInput(exampleSource); - if (debugExamples()) { - qWarning() << QString::fromLatin1("Reading file \"%1\"...") - .arg(manifest.absoluteFilePath().toUserOutput()); - } + qCDebug(log) << QString::fromLatin1("Reading file \"%1\"...") + .arg(manifest.absoluteFilePath().toUserOutput()); const expected_str> result = parseExamples(manifest, @@ -402,10 +348,8 @@ void ExamplesViewController::updateExamples() FilePath::fromUserInput(demosInstallPath), m_isExamples); if (!result) { - if (debugExamples()) { - qWarning() << "ERROR: Could not read examples from" << exampleSource << ":" - << result.error(); - } + qCDebug(log) << "ERROR: Could not read examples from" << exampleSource << ":" + << result.error(); continue; } items += filtered(*result, isValidExampleOrDemo); @@ -423,10 +367,10 @@ void ExamplesViewController::updateExamples() } const bool sortIntoCategories = qtVersion >= *minQtVersionForCategories; - const QList>> sections + const QList>> sections = getCategories(items, sortIntoCategories); for (int i = 0; i < sections.size(); ++i) { - m_view->addSection({sections.at(i).first, i}, + m_view->addSection(sections.at(i).first, static_container_cast(sections.at(i).second)); } } @@ -535,7 +479,7 @@ QStringList ExampleSetModel::exampleSources(QString *examplesInstallPath, const QStringList demosPattern(QLatin1String("demos-manifest.xml")); QFileInfoList fis; const QFileInfoList subDirs = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); - for (QFileInfo subDir : subDirs) { + for (const QFileInfo &subDir : subDirs) { fis << QDir(subDir.absoluteFilePath()).entryInfoList(examplesPattern); fis << QDir(subDir.absoluteFilePath()).entryInfoList(demosPattern); } diff --git a/src/plugins/qtsupport/exampleslistmodel.h b/src/plugins/qtsupport/exampleslistmodel.h index c444f9bc1e7..f0465655b79 100644 --- a/src/plugins/qtsupport/exampleslistmodel.h +++ b/src/plugins/qtsupport/exampleslistmodel.h @@ -31,7 +31,7 @@ public: // qtVersion is set by recreateModel for extra sets that correspond to actual Qt versions. // This is needed for the decision to show categories or not based on the Qt version // (which is not ideal). - QVersionNumber qtVersion; + QVersionNumber qtVersion = {}; }; static QVector pluginRegisteredExampleSets(); diff --git a/src/plugins/qtsupport/examplesparser.cpp b/src/plugins/qtsupport/examplesparser.cpp index 5fd3161777c..84469ca2c4d 100644 --- a/src/plugins/qtsupport/examplesparser.cpp +++ b/src/plugins/qtsupport/examplesparser.cpp @@ -3,7 +3,10 @@ #include "examplesparser.h" +#include "qtsupporttr.h" + #include +#include #include #include @@ -24,8 +27,8 @@ static FilePath relativeOrInstallPath(const FilePath &path, return relativeResolvedPath; if (installResolvedPath.exists()) return installResolvedPath; - // doesn't exist, just return relative - return relativeResolvedPath; + // doesn't exist, return the preferred resolved install path + return installResolvedPath; } static QString fixStringForTags(const QString &string) @@ -298,4 +301,68 @@ expected_str> parseExamples(const QByteArray &manifestData, return items; } +static bool sortByHighlightedAndName(ExampleItem *first, ExampleItem *second) +{ + if (first->isHighlighted && !second->isHighlighted) + return true; + if (!first->isHighlighted && second->isHighlighted) + return false; + return first->name.compare(second->name, Qt::CaseInsensitive) < 0; +} + +QList>> getCategories(const QList &items, + bool sortIntoCategories) +{ + static const QString otherDisplayName = Tr::tr("Other", "Category for all other examples"); + const bool useCategories = sortIntoCategories + || qtcEnvironmentVariableIsSet("QTC_USE_EXAMPLE_CATEGORIES"); + QList other; + QMap> categoryMap; + if (useCategories) { + // Append copies of the items and delete the original ones, + // because items might be added to multiple categories and that needs individual items + for (ExampleItem *item : items) { + const QStringList itemCategories = Utils::filteredUnique( + item->metaData.value("category")); + for (const QString &category : itemCategories) + categoryMap[category].append(new ExampleItem(*item)); + if (itemCategories.isEmpty()) + other.append(new ExampleItem(*item)); + } + } + QList>> categories; + if (categoryMap.isEmpty()) { + // If we tried sorting into categories, but none were defined, we copied the items + // into "other", which we don't use here. Get rid of them again. + qDeleteAll(other); + // The example set doesn't define categories. Consider the "highlighted" ones as "featured" + QList featured; + QList allOther; + std::tie(featured, allOther) = Utils::partition(items, [](ExampleItem *i) { + return i->isHighlighted; + }); + if (!featured.isEmpty()) { + categories.append( + {{Tr::tr("Featured", "Category for highlighted examples"), 0}, featured}); + } + if (!allOther.isEmpty()) + categories.append({{otherDisplayName, 1}, allOther}); + } else { + // All original items have been copied into a category or other, delete. + qDeleteAll(items); + int index = 0; + const auto end = categoryMap.constKeyValueEnd(); + for (auto it = categoryMap.constKeyValueBegin(); it != end; ++it) { + categories.append({{it->first, index, /*maxRows=*/index == 0 ? 2 : 1}, it->second}); + ++index; + } + if (!other.isEmpty()) + categories.append({{otherDisplayName, index, /*maxRows=*/1}, other}); + } + const auto end = categories.end(); + for (auto it = categories.begin(); it != end; ++it) + sort(it->second, sortByHighlightedAndName); + return categories; +} + } // namespace QtSupport::Internal diff --git a/src/plugins/qtsupport/examplesparser.h b/src/plugins/qtsupport/examplesparser.h index 2d1afa54838..54efaf58875 100644 --- a/src/plugins/qtsupport/examplesparser.h +++ b/src/plugins/qtsupport/examplesparser.h @@ -13,7 +13,7 @@ namespace QtSupport::Internal { enum InstructionalType { Example = 0, Demo, Tutorial }; -class QTSUPPORT_EXPORT ExampleItem : public Core::ListItem +class QTSUPPORT_TEST_EXPORT ExampleItem : public Core::ListItem { public: Utils::FilePath projectPath; @@ -31,19 +31,22 @@ public: QHash metaData; }; -QTSUPPORT_EXPORT Utils::expected_str> parseExamples( +QTSUPPORT_TEST_EXPORT Utils::expected_str> parseExamples( const Utils::FilePath &manifest, const Utils::FilePath &examplesInstallPath, const Utils::FilePath &demosInstallPath, bool examples); -QTSUPPORT_EXPORT Utils::expected_str> parseExamples( +QTSUPPORT_TEST_EXPORT Utils::expected_str> parseExamples( const QByteArray &manifestData, const Utils::FilePath &manifestPath, const Utils::FilePath &examplesInstallPath, const Utils::FilePath &demosInstallPath, bool examples); +QTSUPPORT_TEST_EXPORT QList>> getCategories( + const QList &items, bool sortIntoCategories); + } // namespace QtSupport::Internal Q_DECLARE_METATYPE(QtSupport::Internal::ExampleItem *) diff --git a/src/plugins/qtsupport/externaleditors.cpp b/src/plugins/qtsupport/externaleditors.cpp index bed8b7618fa..dffd730d285 100644 --- a/src/plugins/qtsupport/externaleditors.cpp +++ b/src/plugins/qtsupport/externaleditors.cpp @@ -8,8 +8,8 @@ #include #include #include +#include #include -#include #include @@ -19,8 +19,8 @@ #include #include #include +#include #include -#include #include #include @@ -181,7 +181,7 @@ static bool getEditorLaunchData(const CommandForQtVersion &commandForQtVersion, // As fallback check PATH data->workingDirectory.clear(); QVector qtVersionsToCheck; // deduplicated after being filled - if (const Project *project = SessionManager::projectForFile(filePath)) { + if (const Project *project = ProjectManager::projectForFile(filePath)) { data->workingDirectory = project->projectDirectory(); // active kit if (const Target *target = project->activeTarget()) { @@ -224,7 +224,7 @@ static bool startEditorProcess(const LaunchData &data, QString *errorMessage) if (debug) qDebug() << Q_FUNC_INFO << '\n' << data.binary << data.arguments << data.workingDirectory; qint64 pid = 0; - if (!QtcProcess::startDetached({FilePath::fromString(data.binary), data.arguments}, data.workingDirectory, &pid)) { + if (!Process::startDetached({FilePath::fromString(data.binary), data.arguments}, data.workingDirectory, &pid)) { *errorMessage = msgStartFailed(data.binary, data.arguments); return false; } diff --git a/src/plugins/qtsupport/gettingstartedwelcomepage.cpp b/src/plugins/qtsupport/gettingstartedwelcomepage.cpp index bf19233c211..7bc7192185e 100644 --- a/src/plugins/qtsupport/gettingstartedwelcomepage.cpp +++ b/src/plugins/qtsupport/gettingstartedwelcomepage.cpp @@ -280,8 +280,8 @@ public: // for macOS dark mode pal.setColor(QPalette::Text, Utils::creatorTheme()->color(Theme::Welcome_TextColor)); exampleSetSelector->setPalette(pal); - exampleSetSelector->setMinimumWidth(Core::ListItemDelegate::GridItemWidth); - exampleSetSelector->setMaximumWidth(Core::ListItemDelegate::GridItemWidth); + exampleSetSelector->setMinimumWidth(Core::WelcomePageHelpers::GridItemWidth); + exampleSetSelector->setMaximumWidth(Core::WelcomePageHelpers::GridItemWidth); exampleSetSelector->setModel(s_exampleSetModel); exampleSetSelector->setCurrentIndex(s_exampleSetModel->selectedExampleSet()); connect(exampleSetSelector, diff --git a/src/plugins/qtsupport/qscxmlcgenerator.cpp b/src/plugins/qtsupport/qscxmlcgenerator.cpp index 85c467281d3..7cb67f6171e 100644 --- a/src/plugins/qtsupport/qscxmlcgenerator.cpp +++ b/src/plugins/qtsupport/qscxmlcgenerator.cpp @@ -91,7 +91,7 @@ bool QScxmlcGenerator::prepareToRun(const QByteArray &sourceContents) return true; } -FileNameToContentsHash QScxmlcGenerator::handleProcessFinished(Utils::QtcProcess *process) +FileNameToContentsHash QScxmlcGenerator::handleProcessFinished(Utils::Process *process) { Q_UNUSED(process) const Utils::FilePath wd = workingDirectory(); diff --git a/src/plugins/qtsupport/qscxmlcgenerator.h b/src/plugins/qtsupport/qscxmlcgenerator.h index 633fb1231bf..342db3cb096 100644 --- a/src/plugins/qtsupport/qscxmlcgenerator.h +++ b/src/plugins/qtsupport/qscxmlcgenerator.h @@ -23,7 +23,7 @@ protected: private: Utils::FilePath tmpFile() const; - ProjectExplorer::FileNameToContentsHash handleProcessFinished(Utils::QtcProcess *process) override; + ProjectExplorer::FileNameToContentsHash handleProcessFinished(Utils::Process *process) override; bool prepareToRun(const QByteArray &sourceContents) override; ProjectExplorer::Tasks parseIssues(const QByteArray &processStderr) override; diff --git a/src/plugins/qtsupport/qtbuildaspects.cpp b/src/plugins/qtsupport/qtbuildaspects.cpp index 95f2c8d4321..210ea58ee5a 100644 --- a/src/plugins/qtsupport/qtbuildaspects.cpp +++ b/src/plugins/qtsupport/qtbuildaspects.cpp @@ -30,13 +30,13 @@ QmlDebuggingAspect::QmlDebuggingAspect(BuildConfiguration *buildConfig) setValue(ProjectExplorerPlugin::buildPropertiesSettings().qmlDebugging.value()); } -void QmlDebuggingAspect::addToLayout(Layouting::LayoutBuilder &builder) +void QmlDebuggingAspect::addToLayout(Layouting::LayoutItem &parent) { - SelectionAspect::addToLayout(builder); + SelectionAspect::addToLayout(parent); const auto warningLabel = createSubWidget(QString(), InfoLabel::Warning); warningLabel->setElideMode(Qt::ElideNone); warningLabel->setVisible(false); - builder.addRow({{}, warningLabel}); + parent.addRow({{}, warningLabel}); const auto changeHandler = [this, warningLabel] { QString warningText; QTC_ASSERT(m_buildConfig, return); @@ -67,13 +67,13 @@ QtQuickCompilerAspect::QtQuickCompilerAspect(BuildConfiguration *buildConfig) setValue(ProjectExplorerPlugin::buildPropertiesSettings().qtQuickCompiler.value()); } -void QtQuickCompilerAspect::addToLayout(Layouting::LayoutBuilder &builder) +void QtQuickCompilerAspect::addToLayout(Layouting::LayoutItem &parent) { - SelectionAspect::addToLayout(builder); + SelectionAspect::addToLayout(parent); const auto warningLabel = createSubWidget(QString(), InfoLabel::Warning); warningLabel->setElideMode(Qt::ElideNone); warningLabel->setVisible(false); - builder.addRow({{}, warningLabel}); + parent.addRow({{}, warningLabel}); const auto changeHandler = [this, warningLabel] { QString warningText; QTC_ASSERT(m_buildConfig, return); diff --git a/src/plugins/qtsupport/qtbuildaspects.h b/src/plugins/qtsupport/qtbuildaspects.h index e66fe9bbceb..e5e0b3332e6 100644 --- a/src/plugins/qtsupport/qtbuildaspects.h +++ b/src/plugins/qtsupport/qtbuildaspects.h @@ -18,7 +18,7 @@ class QTSUPPORT_EXPORT QmlDebuggingAspect : public Utils::TriStateAspect public: explicit QmlDebuggingAspect(ProjectExplorer::BuildConfiguration *buildConfig); - void addToLayout(Utils::Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; private: const ProjectExplorer::BuildConfiguration *m_buildConfig = nullptr; @@ -32,7 +32,7 @@ public: QtQuickCompilerAspect(ProjectExplorer::BuildConfiguration *buildConfig); private: - void addToLayout(Utils::Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; const ProjectExplorer::BuildConfiguration *m_buildConfig = nullptr; }; diff --git a/src/plugins/qtsupport/qtkitinformation.cpp b/src/plugins/qtsupport/qtkitinformation.cpp index 2489ce5839b..7fab3fbbcca 100644 --- a/src/plugins/qtsupport/qtkitinformation.cpp +++ b/src/plugins/qtsupport/qtkitinformation.cpp @@ -60,11 +60,11 @@ public: private: void makeReadOnly() final { m_combo->setEnabled(false); } - void addToLayout(Layouting::LayoutBuilder &builder) + void addToLayout(Layouting::LayoutItem &parent) { addMutableAction(m_combo); - builder.addItem(m_combo); - builder.addItem(m_manageButton); + parent.addItem(m_combo); + parent.addItem(m_manageButton); } void refresh() final diff --git a/src/plugins/qtsupport/qtoptionspage.cpp b/src/plugins/qtsupport/qtoptionspage.cpp index 25a9f5f3a78..92b439ea1a2 100644 --- a/src/plugins/qtsupport/qtoptionspage.cpp +++ b/src/plugins/qtsupport/qtoptionspage.cpp @@ -232,8 +232,6 @@ QtOptionsPageWidget::QtOptionsPageWidget() , m_warningVersionIcon(Utils::Icons::WARNING.icon()) , m_configurationWidget(nullptr) { - resize(446, 450); - m_qtdirList = new QTreeView(this); m_qtdirList->setObjectName("qtDirList"); m_qtdirList->setUniformRowHeights(true); @@ -261,15 +259,16 @@ QtOptionsPageWidget::QtOptionsPageWidget() m_errorLabel = new QLabel; - using namespace Utils::Layouting; + using namespace Layouting; auto versionInfoWidget = new QWidget; // clang-format off Form { Tr::tr("Name:"), m_nameEdit, br, Tr::tr("qmake path:"), Row { m_qmakePath, m_editPathPushButton }, br, - Span(2, m_errorLabel) - }.attachTo(versionInfoWidget, WithoutMargins); + Span(2, m_errorLabel), + noMargin + }.attachTo(versionInfoWidget); // clang-format on m_formLayout = qobject_cast(versionInfoWidget->layout()); diff --git a/src/plugins/qtsupport/qtsupport_global.h b/src/plugins/qtsupport/qtsupport_global.h index aca20f5aa27..60c9bf0c128 100644 --- a/src/plugins/qtsupport/qtsupport_global.h +++ b/src/plugins/qtsupport/qtsupport_global.h @@ -12,3 +12,15 @@ #else # define QTSUPPORT_EXPORT Q_DECL_IMPORT #endif + +#if defined(WITH_TESTS) +# if defined(QTSUPPORT_LIBRARY) +# define QTSUPPORT_TEST_EXPORT Q_DECL_EXPORT +# elif defined(QTSUPPORT_STATIC_LIBRARY) +# define QTSUPPORT_TEST_EXPORT +# else +# define QTSUPPORT_TEST_EXPORT Q_DECL_IMPORT +# endif +#else +# define QTSUPPORT_TEST_EXPORT +#endif diff --git a/src/plugins/qtsupport/qtsupportplugin.cpp b/src/plugins/qtsupport/qtsupportplugin.cpp index c54c1bfc232..41c88d18e36 100644 --- a/src/plugins/qtsupport/qtsupportplugin.cpp +++ b/src/plugins/qtsupport/qtsupportplugin.cpp @@ -24,8 +24,8 @@ #include #include #include +#include #include -#include #include #include @@ -33,7 +33,7 @@ #include #include #include -#include +#include using namespace Core; using namespace Utils; @@ -76,7 +76,7 @@ static void processRunnerCallback(ProcessData *data) { FilePath rootPath = FilePath::fromString(data->deviceRoot); - QtcProcess proc; + Process proc; proc.setProcessChannelMode(data->processChannelMode); proc.setCommand({rootPath.withNewPath("/bin/sh"), {QString("-c"), data->command}}); proc.setWorkingDirectory(FilePath::fromString(data->workingDirectory)); @@ -170,7 +170,7 @@ void QtSupportPlugin::extensionsInitialized() }); static const auto activeQtVersion = []() -> const QtVersion * { - ProjectExplorer::Project *project = SessionManager::startupProject(); + ProjectExplorer::Project *project = ProjectManager::startupProject(); if (!project || !project->activeTarget()) return nullptr; return QtKitAspect::qtVersion(project->activeTarget()->kit()); @@ -197,7 +197,7 @@ void QtSupportPlugin::extensionsInitialized() expander->registerVariable( "ActiveProject::QT_HOST_LIBEXECS", - Tr::tr("Full path to the libexec bin directory of the Qt version in the active kit " + Tr::tr("Full path to the libexec directory of the Qt version in the active kit " "of the active project."), []() { const QtVersion *const qt = activeQtVersion(); @@ -208,7 +208,7 @@ void QtSupportPlugin::extensionsInitialized() const FilePath filePath = item.filePath(); if (filePath.isEmpty()) return links; - const Project *project = SessionManager::projectForFile(filePath); + const Project *project = ProjectManager::projectForFile(filePath); Target *target = project ? project->activeTarget() : nullptr; QtVersion *qt = target ? QtKitAspect::qtVersion(target->kit()) : nullptr; if (!qt) diff --git a/src/plugins/qtsupport/qtversionmanager.cpp b/src/plugins/qtsupport/qtversionmanager.cpp index c5a0f7b315e..eedc79e6864 100644 --- a/src/plugins/qtsupport/qtversionmanager.cpp +++ b/src/plugins/qtsupport/qtversionmanager.cpp @@ -5,7 +5,6 @@ #include "baseqtversion.h" #include "exampleslistmodel.h" -#include "qtkitinformation.h" #include "qtsupportconstants.h" #include "qtversionfactory.h" @@ -22,8 +21,8 @@ #include #include #include +#include #include -#include #include #include @@ -369,7 +368,7 @@ static void saveQtVersions() // Executes qtchooser with arguments in a process and returns its output static QList runQtChooser(const QString &qtchooser, const QStringList &arguments) { - QtcProcess p; + Process p; p.setCommand({FilePath::fromString(qtchooser), arguments}); p.start(); p.waitForFinished(); diff --git a/src/plugins/qtsupport/uicgenerator.cpp b/src/plugins/qtsupport/uicgenerator.cpp index 36c109e7b4f..f1ff905b831 100644 --- a/src/plugins/qtsupport/uicgenerator.cpp +++ b/src/plugins/qtsupport/uicgenerator.cpp @@ -2,20 +2,21 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "uicgenerator.h" + #include "baseqtversion.h" #include "qtkitinformation.h" +#include #include #include -#include +#include #include -#include -#include -#include -#include #include +#include +#include +#include using namespace ProjectExplorer; @@ -48,7 +49,7 @@ QStringList UicGenerator::arguments() const return {"-p"}; } -FileNameToContentsHash UicGenerator::handleProcessFinished(Utils::QtcProcess *process) +FileNameToContentsHash UicGenerator::handleProcessFinished(Utils::Process *process) { FileNameToContentsHash result; if (process->exitStatus() != QProcess::NormalExit && process->exitCode() != 0) diff --git a/src/plugins/qtsupport/uicgenerator.h b/src/plugins/qtsupport/uicgenerator.h index 1d9344634a3..d68b6814c22 100644 --- a/src/plugins/qtsupport/uicgenerator.h +++ b/src/plugins/qtsupport/uicgenerator.h @@ -18,7 +18,7 @@ public: protected: Utils::FilePath command() const override; QStringList arguments() const override; - ProjectExplorer::FileNameToContentsHash handleProcessFinished(Utils::QtcProcess *process) override; + ProjectExplorer::FileNameToContentsHash handleProcessFinished(Utils::Process *process) override; }; class UicGeneratorFactory : public ProjectExplorer::ExtraCompilerFactory diff --git a/src/plugins/remotelinux/CMakeLists.txt b/src/plugins/remotelinux/CMakeLists.txt index 08581c2f67f..f81f75350ac 100644 --- a/src/plugins/remotelinux/CMakeLists.txt +++ b/src/plugins/remotelinux/CMakeLists.txt @@ -5,7 +5,6 @@ add_qtc_plugin(RemoteLinux abstractremotelinuxdeploystep.cpp abstractremotelinuxdeploystep.h customcommanddeploystep.cpp customcommanddeploystep.h deploymenttimeinfo.cpp deploymenttimeinfo.h - genericdirectuploadservice.cpp genericdirectuploadservice.h genericdirectuploadstep.cpp genericdirectuploadstep.h genericlinuxdeviceconfigurationwidget.cpp genericlinuxdeviceconfigurationwidget.h genericlinuxdeviceconfigurationwizard.cpp genericlinuxdeviceconfigurationwizard.h @@ -29,7 +28,6 @@ add_qtc_plugin(RemoteLinux remotelinuxtr.h rsyncdeploystep.cpp rsyncdeploystep.h sshkeycreationdialog.cpp sshkeycreationdialog.h - sshprocessinterface.h tarpackagecreationstep.cpp tarpackagecreationstep.h tarpackagedeploystep.cpp tarpackagedeploystep.h ) diff --git a/src/plugins/remotelinux/abstractremotelinuxdeploystep.cpp b/src/plugins/remotelinux/abstractremotelinuxdeploystep.cpp index 7e7e3d9a19a..afd4e0e96a2 100644 --- a/src/plugins/remotelinux/abstractremotelinuxdeploystep.cpp +++ b/src/plugins/remotelinux/abstractremotelinuxdeploystep.cpp @@ -12,149 +12,74 @@ #include #include +#include + #include -#include #include #include using namespace ProjectExplorer; +using namespace Tasking; using namespace Utils; namespace RemoteLinux { namespace Internal { -class AbstractRemoteLinuxDeployServicePrivate -{ -public: - IDevice::ConstPtr deviceConfiguration; - QPointer target; - - DeploymentTimeInfo deployTimes; - std::unique_ptr m_taskTree; -}; - class AbstractRemoteLinuxDeployStepPrivate { public: bool hasError; std::function internalInit; std::function runPreparer; - AbstractRemoteLinuxDeployService *deployService = nullptr; + + DeploymentTimeInfo deployTimes; + std::unique_ptr m_taskTree; }; } // Internal using namespace Internal; -AbstractRemoteLinuxDeployService::AbstractRemoteLinuxDeployService(QObject *parent) - : QObject(parent), d(new AbstractRemoteLinuxDeployServicePrivate) -{ -} +AbstractRemoteLinuxDeployStep::AbstractRemoteLinuxDeployStep(BuildStepList *bsl, Id id) + : BuildStep(bsl, id), d(new AbstractRemoteLinuxDeployStepPrivate) +{} -AbstractRemoteLinuxDeployService::~AbstractRemoteLinuxDeployService() +AbstractRemoteLinuxDeployStep::~AbstractRemoteLinuxDeployStep() { delete d; } -const Target *AbstractRemoteLinuxDeployService::target() const +IDevice::ConstPtr AbstractRemoteLinuxDeployStep::deviceConfiguration() const { - return d->target; + return DeviceKitAspect::device(kit()); } -const Kit *AbstractRemoteLinuxDeployService::kit() const -{ - return d->target ? d->target->kit() : nullptr; -} - -IDevice::ConstPtr AbstractRemoteLinuxDeployService::deviceConfiguration() const -{ - return d->deviceConfiguration; -} - -void AbstractRemoteLinuxDeployService::saveDeploymentTimeStamp(const DeployableFile &deployableFile, +void AbstractRemoteLinuxDeployStep::saveDeploymentTimeStamp(const DeployableFile &deployableFile, const QDateTime &remoteTimestamp) { d->deployTimes.saveDeploymentTimeStamp(deployableFile, kit(), remoteTimestamp); } -bool AbstractRemoteLinuxDeployService::hasLocalFileChanged( +bool AbstractRemoteLinuxDeployStep::hasLocalFileChanged( const DeployableFile &deployableFile) const { return d->deployTimes.hasLocalFileChanged(deployableFile, kit()); } -bool AbstractRemoteLinuxDeployService::hasRemoteFileChanged( +bool AbstractRemoteLinuxDeployStep::hasRemoteFileChanged( const DeployableFile &deployableFile, const QDateTime &remoteTimestamp) const { return d->deployTimes.hasRemoteFileChanged(deployableFile, kit(), remoteTimestamp); } -void AbstractRemoteLinuxDeployService::setTarget(Target *target) -{ - d->target = target; - d->deviceConfiguration = DeviceKitAspect::device(kit()); -} - -void AbstractRemoteLinuxDeployService::start() -{ - QTC_ASSERT(!d->m_taskTree, return); - - const CheckResult check = isDeploymentPossible(); - if (!check) { - emit errorMessage(check.errorMessage()); - emit finished(); - return; - } - - if (!isDeploymentNecessary()) { - emit progressMessage(Tr::tr("No deployment action necessary. Skipping.")); - emit finished(); - return; - } - - d->m_taskTree.reset(new TaskTree(deployRecipe())); - const auto endHandler = [this] { - d->m_taskTree.release()->deleteLater(); - emit finished(); - }; - connect(d->m_taskTree.get(), &TaskTree::done, this, endHandler); - connect(d->m_taskTree.get(), &TaskTree::errorOccurred, this, endHandler); - d->m_taskTree->start(); -} - -void AbstractRemoteLinuxDeployService::stop() -{ - if (!d->m_taskTree) - return; - d->m_taskTree.reset(); - emit finished(); -} - -CheckResult AbstractRemoteLinuxDeployService::isDeploymentPossible() const +CheckResult AbstractRemoteLinuxDeployStep::isDeploymentPossible() const { if (!deviceConfiguration()) return CheckResult::failure(Tr::tr("No device configuration set.")); return CheckResult::success(); } -QVariantMap AbstractRemoteLinuxDeployService::exportDeployTimes() const -{ - return d->deployTimes.exportDeployTimes(); -} - -void AbstractRemoteLinuxDeployService::importDeployTimes(const QVariantMap &map) -{ - d->deployTimes.importDeployTimes(map); -} - - - -AbstractRemoteLinuxDeployStep::AbstractRemoteLinuxDeployStep(BuildStepList *bsl, Utils::Id id) - : BuildStep(bsl, id), d(new Internal::AbstractRemoteLinuxDeployStepPrivate) -{ -} - void AbstractRemoteLinuxDeployStep::setInternalInitializer(const std::function &init) { d->internalInit = init; @@ -165,36 +90,23 @@ void AbstractRemoteLinuxDeployStep::setRunPreparer(const std::function d->runPreparer = prep; } -void AbstractRemoteLinuxDeployStep::setDeployService(AbstractRemoteLinuxDeployService *service) -{ - d->deployService = service; -} - -AbstractRemoteLinuxDeployStep::~AbstractRemoteLinuxDeployStep() -{ - delete d->deployService; - delete d; -} - bool AbstractRemoteLinuxDeployStep::fromMap(const QVariantMap &map) { if (!BuildStep::fromMap(map)) return false; - d->deployService->importDeployTimes(map); + d->deployTimes.importDeployTimes(map); return true; } QVariantMap AbstractRemoteLinuxDeployStep::toMap() const { QVariantMap map = BuildStep::toMap(); - map.insert(d->deployService->exportDeployTimes()); + map.insert(d->deployTimes.exportDeployTimes()); return map; } bool AbstractRemoteLinuxDeployStep::init() { - d->deployService->setTarget(target()); - QTC_ASSERT(d->internalInit, return false); const CheckResult canDeploy = d->internalInit(); if (!canDeploy) { @@ -209,21 +121,31 @@ void AbstractRemoteLinuxDeployStep::doRun() if (d->runPreparer) d->runPreparer(); - connect(d->deployService, &AbstractRemoteLinuxDeployService::errorMessage, - this, &AbstractRemoteLinuxDeployStep::handleErrorMessage); - connect(d->deployService, &AbstractRemoteLinuxDeployService::progressMessage, - this, &AbstractRemoteLinuxDeployStep::handleProgressMessage); - connect(d->deployService, &AbstractRemoteLinuxDeployService::warningMessage, - this, &AbstractRemoteLinuxDeployStep::handleWarningMessage); - connect(d->deployService, &AbstractRemoteLinuxDeployService::stdOutData, - this, &AbstractRemoteLinuxDeployStep::handleStdOutData); - connect(d->deployService, &AbstractRemoteLinuxDeployService::stdErrData, - this, &AbstractRemoteLinuxDeployStep::handleStdErrData); - connect(d->deployService, &AbstractRemoteLinuxDeployService::finished, - this, &AbstractRemoteLinuxDeployStep::handleFinished); - d->hasError = false; - d->deployService->start(); + + QTC_ASSERT(!d->m_taskTree, return); + + const CheckResult check = isDeploymentPossible(); + if (!check) { + addErrorMessage(check.errorMessage()); + handleFinished(); + return; + } + + if (!isDeploymentNecessary()) { + addProgressMessage(Tr::tr("No deployment action necessary. Skipping.")); + handleFinished(); + return; + } + + d->m_taskTree.reset(new TaskTree(deployRecipe())); + const auto endHandler = [this] { + d->m_taskTree.release()->deleteLater(); + handleFinished(); + }; + connect(d->m_taskTree.get(), &TaskTree::done, this, endHandler); + connect(d->m_taskTree.get(), &TaskTree::errorOccurred, this, endHandler); + d->m_taskTree->start(); } void AbstractRemoteLinuxDeployStep::doCancel() @@ -234,22 +156,26 @@ void AbstractRemoteLinuxDeployStep::doCancel() emit addOutput(Tr::tr("User requests deployment to stop; cleaning up."), OutputFormat::NormalMessage); d->hasError = true; - d->deployService->stop(); + + if (!d->m_taskTree) + return; + d->m_taskTree.reset(); + handleFinished(); } -void AbstractRemoteLinuxDeployStep::handleProgressMessage(const QString &message) +void AbstractRemoteLinuxDeployStep::addProgressMessage(const QString &message) { emit addOutput(message, OutputFormat::NormalMessage); } -void AbstractRemoteLinuxDeployStep::handleErrorMessage(const QString &message) +void AbstractRemoteLinuxDeployStep::addErrorMessage(const QString &message) { emit addOutput(message, OutputFormat::ErrorMessage); emit addTask(DeploymentTask(Task::Error, message), 1); // TODO correct? d->hasError = true; } -void AbstractRemoteLinuxDeployStep::handleWarningMessage(const QString &message) +void AbstractRemoteLinuxDeployStep::addWarningMessage(const QString &message) { emit addOutput(message, OutputFormat::ErrorMessage); emit addTask(DeploymentTask(Task::Warning, message), 1); // TODO correct? @@ -261,7 +187,7 @@ void AbstractRemoteLinuxDeployStep::handleFinished() emit addOutput(Tr::tr("Deploy step failed."), OutputFormat::ErrorMessage); else emit addOutput(Tr::tr("Deploy step finished."), OutputFormat::NormalMessage); - disconnect(d->deployService, nullptr, this, nullptr); + emit finished(!d->hasError); } @@ -275,4 +201,14 @@ void AbstractRemoteLinuxDeployStep::handleStdErrData(const QString &data) emit addOutput(data, OutputFormat::Stderr, DontAppendNewline); } +bool AbstractRemoteLinuxDeployStep::isDeploymentNecessary() const +{ + return true; +} + +Group AbstractRemoteLinuxDeployStep::deployRecipe() +{ + return {}; +} + } // namespace RemoteLinux diff --git a/src/plugins/remotelinux/abstractremotelinuxdeploystep.h b/src/plugins/remotelinux/abstractremotelinuxdeploystep.h index 67855064bb0..b0af97f5d2b 100644 --- a/src/plugins/remotelinux/abstractremotelinuxdeploystep.h +++ b/src/plugins/remotelinux/abstractremotelinuxdeploystep.h @@ -8,55 +8,14 @@ #include #include -#include #include -namespace ProjectExplorer { -class DeployableFile; -class Kit; -class Target; -} - -namespace Utils::Tasking { class Group; } +namespace ProjectExplorer { class DeployableFile; } +namespace Tasking { class Group; } namespace RemoteLinux { -class AbstractRemoteLinuxDeployService; -class CheckResult; - namespace Internal { class AbstractRemoteLinuxDeployStepPrivate; } -namespace Internal { class AbstractRemoteLinuxDeployServicePrivate; } - -class REMOTELINUX_EXPORT AbstractRemoteLinuxDeployStep : public ProjectExplorer::BuildStep -{ - Q_OBJECT - -public: - ~AbstractRemoteLinuxDeployStep() override; - -protected: - bool fromMap(const QVariantMap &map) override; - QVariantMap toMap() const override; - bool init() override; - void doRun() final; - void doCancel() override; - - explicit AbstractRemoteLinuxDeployStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id); - - void setInternalInitializer(const std::function &init); - void setRunPreparer(const std::function &prep); - void setDeployService(AbstractRemoteLinuxDeployService *service); - -private: - void handleProgressMessage(const QString &message); - void handleErrorMessage(const QString &message); - void handleWarningMessage(const QString &message); - void handleFinished(); - void handleStdOutData(const QString &data); - void handleStdErrData(const QString &data); - - Internal::AbstractRemoteLinuxDeployStepPrivate *d; -}; class REMOTELINUX_EXPORT CheckResult { @@ -74,36 +33,28 @@ private: QString m_error; }; -class REMOTELINUX_EXPORT AbstractRemoteLinuxDeployService : public QObject +class REMOTELINUX_EXPORT AbstractRemoteLinuxDeployStep : public ProjectExplorer::BuildStep { - Q_OBJECT - Q_DISABLE_COPY(AbstractRemoteLinuxDeployService) public: - explicit AbstractRemoteLinuxDeployService(QObject *parent = nullptr); - ~AbstractRemoteLinuxDeployService() override; + explicit AbstractRemoteLinuxDeployStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id); + ~AbstractRemoteLinuxDeployStep() override; - void setTarget(ProjectExplorer::Target *bc); - - void start(); - void stop(); - - QVariantMap exportDeployTimes() const; - void importDeployTimes(const QVariantMap &map); + ProjectExplorer::IDeviceConstPtr deviceConfiguration() const; virtual CheckResult isDeploymentPossible() const; -signals: - void errorMessage(const QString &message); - void progressMessage(const QString &message); - void warningMessage(const QString &message); - void stdOutData(const QString &data); - void stdErrData(const QString &data); - void finished(); // Used by Qnx. + void handleStdOutData(const QString &data); + void handleStdErrData(const QString &data); protected: - const ProjectExplorer::Target *target() const; - const ProjectExplorer::Kit *kit() const; - ProjectExplorer::IDeviceConstPtr deviceConfiguration() const; + bool fromMap(const QVariantMap &map) override; + QVariantMap toMap() const override; + bool init() override; + void doRun() final; + void doCancel() override; + + void setInternalInitializer(const std::function &init); + void setRunPreparer(const std::function &prep); void saveDeploymentTimeStamp(const ProjectExplorer::DeployableFile &deployableFile, const QDateTime &remoteTimestamp); @@ -111,11 +62,17 @@ protected: bool hasRemoteFileChanged(const ProjectExplorer::DeployableFile &deployableFile, const QDateTime &remoteTimestamp) const; -private: - virtual bool isDeploymentNecessary() const = 0; - virtual Utils::Tasking::Group deployRecipe() = 0; + void addProgressMessage(const QString &message); + void addErrorMessage(const QString &message); + void addWarningMessage(const QString &message); - Internal::AbstractRemoteLinuxDeployServicePrivate * const d; + void handleFinished(); + +private: + virtual bool isDeploymentNecessary() const; + virtual Tasking::Group deployRecipe(); + + Internal::AbstractRemoteLinuxDeployStepPrivate *d; }; } // RemoteLinux diff --git a/src/plugins/remotelinux/customcommanddeploystep.cpp b/src/plugins/remotelinux/customcommanddeploystep.cpp index b7abdd83044..32076d34f27 100644 --- a/src/plugins/remotelinux/customcommanddeploystep.cpp +++ b/src/plugins/remotelinux/customcommanddeploystep.cpp @@ -11,95 +11,79 @@ #include #include -#include +#include using namespace ProjectExplorer; +using namespace Tasking; using namespace Utils; -using namespace Utils::Tasking; namespace RemoteLinux::Internal { -class CustomCommandDeployService : public AbstractRemoteLinuxDeployService -{ -public: - void setCommandLine(const QString &commandLine); - CheckResult isDeploymentPossible() const final; - -protected: - Group deployRecipe() final; - -private: - bool isDeploymentNecessary() const final { return true; } - - QString m_commandLine; -}; - -void CustomCommandDeployService::setCommandLine(const QString &commandLine) -{ - m_commandLine = commandLine; -} - -CheckResult CustomCommandDeployService::isDeploymentPossible() const -{ - if (m_commandLine.isEmpty()) - return CheckResult::failure(Tr::tr("No command line given.")); - - return AbstractRemoteLinuxDeployService::isDeploymentPossible(); -} - -Group CustomCommandDeployService::deployRecipe() -{ - const auto setupHandler = [this](QtcProcess &process) { - emit progressMessage(Tr::tr("Starting remote command \"%1\"...").arg(m_commandLine)); - process.setCommand({deviceConfiguration()->filePath("/bin/sh"), - {"-c", m_commandLine}}); - QtcProcess *proc = &process; - connect(proc, &QtcProcess::readyReadStandardOutput, this, [this, proc] { - emit stdOutData(proc->readAllStandardOutput()); - }); - connect(proc, &QtcProcess::readyReadStandardError, this, [this, proc] { - emit stdErrData(proc->readAllStandardError()); - }); - }; - const auto doneHandler = [this](const QtcProcess &) { - emit progressMessage(Tr::tr("Remote command finished successfully.")); - }; - const auto errorHandler = [this](const QtcProcess &process) { - if (process.error() != QProcess::UnknownError - || process.exitStatus() != QProcess::NormalExit) { - emit errorMessage(Tr::tr("Remote process failed: %1").arg(process.errorString())); - } else if (process.exitCode() != 0) { - emit errorMessage(Tr::tr("Remote process finished with exit code %1.") - .arg(process.exitCode())); - } - }; - return Group { Process(setupHandler, doneHandler, errorHandler) }; -} - class CustomCommandDeployStep : public AbstractRemoteLinuxDeployStep { public: CustomCommandDeployStep(BuildStepList *bsl, Id id) : AbstractRemoteLinuxDeployStep(bsl, id) { - auto service = new CustomCommandDeployService; - setDeployService(service); - auto commandLine = addAspect(); commandLine->setSettingsKey("RemoteLinuxCustomCommandDeploymentStep.CommandLine"); commandLine->setLabelText(Tr::tr("Command line:")); commandLine->setDisplayStyle(StringAspect::LineEditDisplay); commandLine->setHistoryCompleter("RemoteLinuxCustomCommandDeploymentStep.History"); - setInternalInitializer([service, commandLine] { - service->setCommandLine(commandLine->value().trimmed()); - return service->isDeploymentPossible(); + setInternalInitializer([this, commandLine] { + m_commandLine = commandLine->value().trimmed(); + return isDeploymentPossible(); }); addMacroExpander(); } + + CheckResult isDeploymentPossible() const final; + +private: + Group deployRecipe() final; + + QString m_commandLine; }; +CheckResult CustomCommandDeployStep::isDeploymentPossible() const +{ + if (m_commandLine.isEmpty()) + return CheckResult::failure(Tr::tr("No command line given.")); + + return AbstractRemoteLinuxDeployStep::isDeploymentPossible(); +} + +Group CustomCommandDeployStep::deployRecipe() +{ + const auto setupHandler = [this](Process &process) { + addProgressMessage(Tr::tr("Starting remote command \"%1\"...").arg(m_commandLine)); + process.setCommand({deviceConfiguration()->filePath("/bin/sh"), + {"-c", m_commandLine}}); + Process *proc = &process; + connect(proc, &Process::readyReadStandardOutput, this, [this, proc] { + handleStdOutData(proc->readAllStandardOutput()); + }); + connect(proc, &Process::readyReadStandardError, this, [this, proc] { + handleStdErrData(proc->readAllStandardError()); + }); + }; + const auto doneHandler = [this](const Process &) { + addProgressMessage(Tr::tr("Remote command finished successfully.")); + }; + const auto errorHandler = [this](const Process &process) { + if (process.error() != QProcess::UnknownError + || process.exitStatus() != QProcess::NormalExit) { + addErrorMessage(Tr::tr("Remote process failed: %1").arg(process.errorString())); + } else if (process.exitCode() != 0) { + addErrorMessage(Tr::tr("Remote process finished with exit code %1.") + .arg(process.exitCode())); + } + }; + return Group { ProcessTask(setupHandler, doneHandler, errorHandler) }; +} + // CustomCommandDeployStepFactory diff --git a/src/plugins/remotelinux/filesystemaccess_test.cpp b/src/plugins/remotelinux/filesystemaccess_test.cpp index fcc8477974a..2edb78fa7b3 100644 --- a/src/plugins/remotelinux/filesystemaccess_test.cpp +++ b/src/plugins/remotelinux/filesystemaccess_test.cpp @@ -9,7 +9,11 @@ #include #include #include +#include +#include +#include #include +#include #include #include @@ -86,6 +90,45 @@ void FileSystemAccessTest::initTestCase() QVERIFY(!filePath.exists()); QVERIFY(filePath.createDir()); QVERIFY(filePath.exists()); + + const QString streamerLocalDir("streamerLocalDir"); + const QString streamerRemoteDir("streamerRemoteDir"); + const QString sourceDir("source"); + const QString destDir("dest"); + const QString localDir("local"); + const QString remoteDir("remote"); + const FilePath localRoot; + const FilePath remoteRoot = m_device->rootPath(); + const FilePath localTempDir = *localRoot.tmpDir(); + const FilePath remoteTempDir = *remoteRoot.tmpDir(); + m_localStreamerDir = localTempDir / streamerLocalDir; + m_remoteStreamerDir = remoteTempDir / streamerRemoteDir; + m_localSourceDir = m_localStreamerDir / sourceDir; + m_remoteSourceDir = m_remoteStreamerDir / sourceDir; + m_localDestDir = m_localStreamerDir / destDir; + m_remoteDestDir = m_remoteStreamerDir / destDir; + m_localLocalDestDir = m_localDestDir / localDir; + m_localRemoteDestDir = m_localDestDir / remoteDir; + m_remoteLocalDestDir = m_remoteDestDir / localDir; + m_remoteRemoteDestDir = m_remoteDestDir / remoteDir; + + QVERIFY(m_localSourceDir.createDir()); + QVERIFY(m_remoteSourceDir.createDir()); + QVERIFY(m_localDestDir.createDir()); + QVERIFY(m_remoteDestDir.createDir()); + QVERIFY(m_localLocalDestDir.createDir()); + QVERIFY(m_localRemoteDestDir.createDir()); + QVERIFY(m_remoteLocalDestDir.createDir()); + QVERIFY(m_remoteRemoteDestDir.createDir()); + + QVERIFY(m_localSourceDir.exists()); + QVERIFY(m_remoteSourceDir.exists()); + QVERIFY(m_localDestDir.exists()); + QVERIFY(m_remoteDestDir.exists()); + QVERIFY(m_localLocalDestDir.exists()); + QVERIFY(m_localRemoteDestDir.exists()); + QVERIFY(m_remoteLocalDestDir.exists()); + QVERIFY(m_remoteRemoteDestDir.exists()); } void FileSystemAccessTest::cleanupTestCase() @@ -94,6 +137,14 @@ void FileSystemAccessTest::cleanupTestCase() return; QVERIFY(baseFilePath().exists()); QVERIFY(baseFilePath().removeRecursively()); + + QVERIFY(m_localStreamerDir.removeRecursively()); + QVERIFY(m_remoteStreamerDir.removeRecursively()); + + QVERIFY(!m_localStreamerDir.exists()); + QVERIFY(!m_remoteStreamerDir.exists()); + + FileStreamerManager::stopAll(); } void FileSystemAccessTest::testCreateRemoteFile_data() @@ -102,13 +153,13 @@ void FileSystemAccessTest::testCreateRemoteFile_data() QTest::newRow("Spaces") << QByteArray("Line with spaces"); QTest::newRow("Newlines") << QByteArray("Some \n\n newlines \n"); - QTest::newRow("Carriage return") << QByteArray("Line with carriage \r return"); + QTest::newRow("CarriageReturn") << QByteArray("Line with carriage \r return"); QTest::newRow("Tab") << QByteArray("Line with \t tab"); QTest::newRow("Apostrophe") << QByteArray("Line with apostrophe's character"); - QTest::newRow("Quotation marks") << QByteArray("Line with \"quotation marks\""); - QTest::newRow("Backslash 1") << QByteArray("Line with \\ backslash"); - QTest::newRow("Backslash 2") << QByteArray("Line with \\\" backslash"); - QTest::newRow("Command output") << QByteArray("The date is: $(date +%D)"); + QTest::newRow("QuotationMarks") << QByteArray("Line with \"quotation marks\""); + QTest::newRow("Backslash1") << QByteArray("Line with \\ backslash"); + QTest::newRow("Backslash2") << QByteArray("Line with \\\" backslash"); + QTest::newRow("CommandOutput") << QByteArray("The date is: $(date +%D)"); const int charSize = sizeof(char) * 0x100; QByteArray charString(charSize, Qt::Uninitialized); @@ -132,6 +183,21 @@ void FileSystemAccessTest::testCreateRemoteFile() QVERIFY(!testFilePath.exists()); } +void FileSystemAccessTest::testWorkingDirectory() +{ + const FilePath dir = baseFilePath() / "testdir with space and 'various' \"quotes\" here"; + QVERIFY(dir.ensureWritableDir()); + Process proc; + proc.setCommand({"pwd", {}}); + proc.setWorkingDirectory(dir); + proc.start(); + QVERIFY(proc.waitForFinished()); + const QString out = proc.readAllStandardOutput().trimmed(); + QCOMPARE(out, dir.path()); + const QString err = proc.readAllStandardOutput(); + QVERIFY(err.isEmpty()); +} + void FileSystemAccessTest::testDirStatus() { FilePath filePath = baseFilePath(); @@ -201,6 +267,8 @@ void FileSystemAccessTest::testFileTransfer_data() QTest::addColumn("fileTransferMethod"); QTest::addRow("Sftp") << FileTransferMethod::Sftp; + // TODO: By default rsync doesn't support creating target directories, + // needs to be done manually - see RsyncDeployService. // QTest::addRow("Rsync") << FileTransferMethod::Rsync; } @@ -282,5 +350,287 @@ void FileSystemAccessTest::testFileTransfer() QVERIFY2(remoteDir.removeRecursively(&errorString), qPrintable(errorString)); } +void FileSystemAccessTest::testFileStreamer_data() +{ + QTest::addColumn("fileName"); + QTest::addColumn("data"); + + const QByteArray spaces("Line with spaces"); + const QByteArray newlines("Some \n\n newlines \n"); + const QByteArray carriageReturn("Line with carriage \r return"); + const QByteArray tab("Line with \t tab"); + const QByteArray apostrophe("Line with apostrophe's character"); + const QByteArray quotationMarks("Line with \"quotation marks\""); + const QByteArray backslash1("Line with \\ backslash"); + const QByteArray backslash2("Line with \\\" backslash"); + const QByteArray commandOutput("The date is: $(date +%D)"); + + const int charSize = sizeof(char) * 0x100; + QByteArray charString(charSize, Qt::Uninitialized); + char *data = charString.data(); + for (int c = 0; c < charSize; ++c) + data[c] = c; + + const int bigSize = 1024 * 1024; // = 256 * 1024 * 1024 = 268.435.456 bytes + QByteArray bigString; + for (int i = 0; i < bigSize; ++i) + bigString += charString; + + QTest::newRow("Spaces") << QString("spaces") << spaces; + QTest::newRow("Newlines") << QString("newlines") << newlines; + QTest::newRow("CarriageReturn") << QString("carriageReturn") << carriageReturn; + QTest::newRow("Tab") << QString("tab") << tab; + QTest::newRow("Apostrophe") << QString("apostrophe") << apostrophe; + QTest::newRow("QuotationMarks") << QString("quotationMarks") << quotationMarks; + QTest::newRow("Backslash1") << QString("backslash1") << backslash1; + QTest::newRow("Backslash2") << QString("backslash2") << backslash2; + QTest::newRow("CommandOutput") << QString("commandOutput") << commandOutput; + QTest::newRow("AllCharacters") << QString("charString") << charString; + QTest::newRow("BigString") << QString("bigString") << bigString; +} + +void FileSystemAccessTest::testFileStreamer() +{ + QTC_SCOPED_TIMER("testFileStreamer"); + + QFETCH(QString, fileName); + QFETCH(QByteArray, data); + + const FilePath localSourcePath = m_localSourceDir / fileName; + const FilePath remoteSourcePath = m_remoteSourceDir / fileName; + const FilePath localLocalDestPath = m_localDestDir / "local" / fileName; + const FilePath localRemoteDestPath = m_localDestDir / "remote" / fileName; + const FilePath remoteLocalDestPath = m_remoteDestDir / "local" / fileName; + const FilePath remoteRemoteDestPath = m_remoteDestDir / "remote" / fileName; + + localSourcePath.removeFile(); + remoteSourcePath.removeFile(); + localLocalDestPath.removeFile(); + localRemoteDestPath.removeFile(); + remoteLocalDestPath.removeFile(); + remoteRemoteDestPath.removeFile(); + + QVERIFY(!localSourcePath.exists()); + QVERIFY(!remoteSourcePath.exists()); + QVERIFY(!localLocalDestPath.exists()); + QVERIFY(!localRemoteDestPath.exists()); + QVERIFY(!remoteLocalDestPath.exists()); + QVERIFY(!remoteRemoteDestPath.exists()); + + std::optional localData; + std::optional remoteData; + std::optional localLocalData; + std::optional localRemoteData; + std::optional remoteLocalData; + std::optional remoteRemoteData; + + using namespace Tasking; + + const auto localWriter = [&] { + const auto setup = [&](FileStreamer &streamer) { + streamer.setStreamMode(StreamMode::Writer); + streamer.setDestination(localSourcePath); + streamer.setWriteData(data); + }; + return FileStreamerTask(setup); + }; + const auto remoteWriter = [&] { + const auto setup = [&](FileStreamer &streamer) { + streamer.setStreamMode(StreamMode::Writer); + streamer.setDestination(remoteSourcePath); + streamer.setWriteData(data); + }; + return FileStreamerTask(setup); + }; + const auto localReader = [&] { + const auto setup = [&](FileStreamer &streamer) { + streamer.setStreamMode(StreamMode::Reader); + streamer.setSource(localSourcePath); + }; + const auto onDone = [&](const FileStreamer &streamer) { + localData = streamer.readData(); + }; + return FileStreamerTask(setup, onDone); + }; + const auto remoteReader = [&] { + const auto setup = [&](FileStreamer &streamer) { + streamer.setStreamMode(StreamMode::Reader); + streamer.setSource(remoteSourcePath); + }; + const auto onDone = [&](const FileStreamer &streamer) { + remoteData = streamer.readData(); + }; + return FileStreamerTask(setup, onDone); + }; + const auto transfer = [](const FilePath &source, const FilePath &dest, + std::optional *result) { + const auto setupTransfer = [=](FileStreamer &streamer) { + streamer.setSource(source); + streamer.setDestination(dest); + }; + const auto setupReader = [=](FileStreamer &streamer) { + streamer.setStreamMode(StreamMode::Reader); + streamer.setSource(dest); + }; + const auto onReaderDone = [result](const FileStreamer &streamer) { + *result = streamer.readData(); + }; + const Group root { + FileStreamerTask(setupTransfer), + FileStreamerTask(setupReader, onReaderDone) + }; + return root; + }; + + // In total: 5 local reads, 3 local writes, 5 remote reads, 3 remote writes + const Group root { + Group { + parallel, + localWriter(), + remoteWriter() + }, + Group { + parallel, + localReader(), + remoteReader() + }, + Group { + parallel, + transfer(localSourcePath, localLocalDestPath, &localLocalData), + transfer(remoteSourcePath, localRemoteDestPath, &localRemoteData), + transfer(localSourcePath, remoteLocalDestPath, &remoteLocalData), + transfer(remoteSourcePath, remoteRemoteDestPath, &remoteRemoteData), + } + }; + + using namespace std::chrono_literals; + QVERIFY(TaskTree::runBlocking(root, 10000ms)); + + QVERIFY(localData); + QCOMPARE(*localData, data); + QVERIFY(remoteData); + QCOMPARE(*remoteData, data); + + QVERIFY(localLocalData); + QCOMPARE(*localLocalData, data); + QVERIFY(localRemoteData); + QCOMPARE(*localRemoteData, data); + QVERIFY(remoteLocalData); + QCOMPARE(*remoteLocalData, data); + QVERIFY(remoteRemoteData); + QCOMPARE(*remoteRemoteData, data); +} + +void FileSystemAccessTest::testFileStreamerManager_data() +{ + testFileStreamer_data(); +} + +void FileSystemAccessTest::testFileStreamerManager() +{ + QTC_SCOPED_TIMER("testFileStreamerManager"); + + QFETCH(QString, fileName); + QFETCH(QByteArray, data); + + const FilePath localSourcePath = m_localSourceDir / fileName; + const FilePath remoteSourcePath = m_remoteSourceDir / fileName; + const FilePath localLocalDestPath = m_localDestDir / "local" / fileName; + const FilePath localRemoteDestPath = m_localDestDir / "remote" / fileName; + const FilePath remoteLocalDestPath = m_remoteDestDir / "local" / fileName; + const FilePath remoteRemoteDestPath = m_remoteDestDir / "remote" / fileName; + + localSourcePath.removeFile(); + remoteSourcePath.removeFile(); + localLocalDestPath.removeFile(); + localRemoteDestPath.removeFile(); + remoteLocalDestPath.removeFile(); + remoteRemoteDestPath.removeFile(); + + QVERIFY(!localSourcePath.exists()); + QVERIFY(!remoteSourcePath.exists()); + QVERIFY(!localLocalDestPath.exists()); + QVERIFY(!localRemoteDestPath.exists()); + QVERIFY(!remoteLocalDestPath.exists()); + QVERIFY(!remoteRemoteDestPath.exists()); + + std::optional localData; + std::optional remoteData; + std::optional localLocalData; + std::optional localRemoteData; + std::optional remoteLocalData; + std::optional remoteRemoteData; + + QEventLoop eventLoop1; + QEventLoop *loop = &eventLoop1; + int counter = 0; + int *hitCount = &counter; + + const auto writeAndRead = [hitCount, loop, data](const FilePath &destination, + std::optional *result) { + const auto onWrite = [hitCount, loop, destination, result] + (const expected_str &writeResult) { + QVERIFY(writeResult); + const auto onRead = [hitCount, loop, result] + (const expected_str &readResult) { + QVERIFY(readResult); + *result = *readResult; + ++(*hitCount); + if (*hitCount == 2) + loop->quit(); + }; + FileStreamerManager::read(destination, onRead); + }; + FileStreamerManager::write(destination, data, onWrite); + }; + + writeAndRead(localSourcePath, &localData); + writeAndRead(remoteSourcePath, &remoteData); + loop->exec(); + + QVERIFY(localData); + QCOMPARE(*localData, data); + QVERIFY(remoteData); + QCOMPARE(*remoteData, data); + + QEventLoop eventLoop2; + loop = &eventLoop2; + counter = 0; + + const auto transferAndRead = [hitCount, loop, data](const FilePath &source, + const FilePath &destination, + std::optional *result) { + const auto onTransfer = [hitCount, loop, destination, result] + (const expected_str &transferResult) { + QVERIFY(transferResult); + const auto onRead = [hitCount, loop, result] + (const expected_str &readResult) { + QVERIFY(readResult); + *result = *readResult; + ++(*hitCount); + if (*hitCount == 4) + loop->quit(); + }; + FileStreamerManager::read(destination, onRead); + }; + FileStreamerManager::copy(source, destination, onTransfer); + }; + + transferAndRead(localSourcePath, localLocalDestPath, &localLocalData); + transferAndRead(remoteSourcePath, localRemoteDestPath, &localRemoteData); + transferAndRead(localSourcePath, remoteLocalDestPath, &remoteLocalData); + transferAndRead(remoteSourcePath, remoteRemoteDestPath, &remoteRemoteData); + loop->exec(); + + QVERIFY(localLocalData); + QCOMPARE(*localLocalData, data); + QVERIFY(localRemoteData); + QCOMPARE(*localRemoteData, data); + QVERIFY(remoteLocalData); + QCOMPARE(*remoteLocalData, data); + QVERIFY(remoteRemoteData); + QCOMPARE(*remoteRemoteData, data); +} + } // Internal } // RemoteLinux diff --git a/src/plugins/remotelinux/filesystemaccess_test.h b/src/plugins/remotelinux/filesystemaccess_test.h index 9684cbc9264..087eabbcad9 100644 --- a/src/plugins/remotelinux/filesystemaccess_test.h +++ b/src/plugins/remotelinux/filesystemaccess_test.h @@ -4,6 +4,7 @@ #pragma once #include +#include namespace RemoteLinux { namespace Internal { @@ -23,11 +24,16 @@ private slots: void testCreateRemoteFile_data(); void testCreateRemoteFile(); + void testWorkingDirectory(); void testDirStatus(); void testBytesAvailable(); void testFileActions(); void testFileTransfer_data(); void testFileTransfer(); + void testFileStreamer_data(); + void testFileStreamer(); + void testFileStreamerManager_data(); + void testFileStreamerManager(); void cleanupTestCase(); @@ -35,6 +41,16 @@ private: TestLinuxDeviceFactory m_testLinuxDeviceFactory; bool m_skippedAtWhole = false; ProjectExplorer::IDeviceConstPtr m_device; + Utils::FilePath m_localStreamerDir; + Utils::FilePath m_remoteStreamerDir; + Utils::FilePath m_localSourceDir; + Utils::FilePath m_remoteSourceDir; + Utils::FilePath m_localDestDir; + Utils::FilePath m_remoteDestDir; + Utils::FilePath m_localLocalDestDir; + Utils::FilePath m_localRemoteDestDir; + Utils::FilePath m_remoteLocalDestDir; + Utils::FilePath m_remoteRemoteDestDir; }; } // Internal diff --git a/src/plugins/remotelinux/genericdirectuploadservice.cpp b/src/plugins/remotelinux/genericdirectuploadservice.cpp deleted file mode 100644 index 6cd3c799376..00000000000 --- a/src/plugins/remotelinux/genericdirectuploadservice.cpp +++ /dev/null @@ -1,314 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "genericdirectuploadservice.h" - -#include "remotelinuxtr.h" - -#include -#include -#include - -#include -#include -#include -#include - -#include -#include - -using namespace ProjectExplorer; -using namespace Utils; -using namespace Utils::Tasking; - -namespace RemoteLinux { -namespace Internal { - -const int MaxConcurrentStatCalls = 10; - -struct UploadStorage -{ - QList filesToUpload; -}; - -class GenericDirectUploadServicePrivate -{ -public: - GenericDirectUploadServicePrivate(GenericDirectUploadService *service) : q(service) {} - - QDateTime timestampFromStat(const DeployableFile &file, QtcProcess *statProc); - - using FilesToStat = std::function(UploadStorage *)>; - using StatEndHandler - = std::function; - TaskItem statTask(UploadStorage *storage, const DeployableFile &file, - StatEndHandler statEndHandler); - TaskItem statTree(const TreeStorage &storage, FilesToStat filesToStat, - StatEndHandler statEndHandler); - TaskItem uploadTask(const TreeStorage &storage); - TaskItem chmodTask(const DeployableFile &file); - TaskItem chmodTree(const TreeStorage &storage); - - GenericDirectUploadService *q = nullptr; - IncrementalDeployment incremental = IncrementalDeployment::NotSupported; - bool ignoreMissingFiles = false; - QList deployableFiles; -}; - -QList collectFilesToUpload(const DeployableFile &deployable) -{ - QList collected; - FilePath localFile = deployable.localFilePath(); - if (localFile.isDir()) { - const FilePaths files = localFile.dirEntries(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); - const QString remoteDir = deployable.remoteDirectory() + '/' + localFile.fileName(); - for (const FilePath &localFilePath : files) - collected.append(collectFilesToUpload(DeployableFile(localFilePath, remoteDir))); - } else { - collected << deployable; - } - return collected; -} - -} // namespace Internal - -using namespace Internal; - -GenericDirectUploadService::GenericDirectUploadService(QObject *parent) - : AbstractRemoteLinuxDeployService(parent), d(new GenericDirectUploadServicePrivate(this)) -{ -} - -GenericDirectUploadService::~GenericDirectUploadService() -{ - delete d; -} - -void GenericDirectUploadService::setDeployableFiles(const QList &deployableFiles) -{ - d->deployableFiles = deployableFiles; -} - -void GenericDirectUploadService::setIncrementalDeployment(IncrementalDeployment incremental) -{ - d->incremental = incremental; -} - -void GenericDirectUploadService::setIgnoreMissingFiles(bool ignoreMissingFiles) -{ - d->ignoreMissingFiles = ignoreMissingFiles; -} - -bool GenericDirectUploadService::isDeploymentNecessary() const -{ - QList collected; - for (int i = 0; i < d->deployableFiles.count(); ++i) - collected.append(collectFilesToUpload(d->deployableFiles.at(i))); - - QTC_CHECK(collected.size() >= d->deployableFiles.size()); - d->deployableFiles = collected; - return !d->deployableFiles.isEmpty(); -} - -QDateTime GenericDirectUploadServicePrivate::timestampFromStat(const DeployableFile &file, - QtcProcess *statProc) -{ - bool succeeded = false; - QString error; - if (statProc->error() == QProcess::FailedToStart) { - error = Tr::tr("Failed to start \"stat\": %1").arg(statProc->errorString()); - } else if (statProc->exitStatus() == QProcess::CrashExit) { - error = Tr::tr("\"stat\" crashed."); - } else if (statProc->exitCode() != 0) { - error = Tr::tr("\"stat\" failed with exit code %1: %2") - .arg(statProc->exitCode()).arg(statProc->cleanedStdErr()); - } else { - succeeded = true; - } - if (!succeeded) { - emit q->warningMessage(Tr::tr("Failed to retrieve remote timestamp for file \"%1\". " - "Incremental deployment will not work. Error message was: %2") - .arg(file.remoteFilePath(), error)); - return {}; - } - const QByteArray output = statProc->readAllRawStandardOutput().trimmed(); - const QString warningString(Tr::tr("Unexpected stat output for remote file \"%1\": %2") - .arg(file.remoteFilePath()).arg(QString::fromUtf8(output))); - if (!output.startsWith(file.remoteFilePath().toUtf8())) { - emit q->warningMessage(warningString); - return {}; - } - const QByteArrayList columns = output.mid(file.remoteFilePath().toUtf8().size() + 1).split(' '); - if (columns.size() < 14) { // Normal Linux stat: 16 columns in total, busybox stat: 15 columns - emit q->warningMessage(warningString); - return {}; - } - bool isNumber; - const qint64 secsSinceEpoch = columns.at(11).toLongLong(&isNumber); - if (!isNumber) { - emit q->warningMessage(warningString); - return {}; - } - return QDateTime::fromSecsSinceEpoch(secsSinceEpoch); -} - -TaskItem GenericDirectUploadServicePrivate::statTask(UploadStorage *storage, - const DeployableFile &file, - StatEndHandler statEndHandler) -{ - const auto setupHandler = [=](QtcProcess &process) { - // We'd like to use --format=%Y, but it's not supported by busybox. - process.setCommand({q->deviceConfiguration()->filePath("stat"), - {"-t", Utils::ProcessArgs::quoteArgUnix(file.remoteFilePath())}}); - }; - const auto endHandler = [=](const QtcProcess &process) { - QtcProcess *proc = const_cast(&process); - const QDateTime timestamp = timestampFromStat(file, proc); - statEndHandler(storage, file, timestamp); - }; - return Process(setupHandler, endHandler, endHandler); -} - -TaskItem GenericDirectUploadServicePrivate::statTree(const TreeStorage &storage, - FilesToStat filesToStat, StatEndHandler statEndHandler) -{ - const auto setupHandler = [=](TaskTree &tree) { - UploadStorage *storagePtr = storage.activeStorage(); - const QList files = filesToStat(storagePtr); - QList statList{optional, ParallelLimit(MaxConcurrentStatCalls)}; - for (const DeployableFile &file : std::as_const(files)) { - QTC_ASSERT(file.isValid(), continue); - statList.append(statTask(storagePtr, file, statEndHandler)); - } - tree.setupRoot({statList}); - }; - return Tree(setupHandler); -} - -TaskItem GenericDirectUploadServicePrivate::uploadTask(const TreeStorage &storage) -{ - const auto setupHandler = [this, storage](FileTransfer &transfer) { - if (storage->filesToUpload.isEmpty()) { - emit q->progressMessage(Tr::tr("No files need to be uploaded.")); - return TaskAction::StopWithDone; - } - emit q->progressMessage(Tr::tr("%n file(s) need to be uploaded.", "", - storage->filesToUpload.size())); - FilesToTransfer files; - for (const DeployableFile &file : std::as_const(storage->filesToUpload)) { - if (!file.localFilePath().exists()) { - const QString message = Tr::tr("Local file \"%1\" does not exist.") - .arg(file.localFilePath().toUserOutput()); - if (ignoreMissingFiles) { - emit q->warningMessage(message); - continue; - } - emit q->errorMessage(message); - return TaskAction::StopWithError; - } - files.append({file.localFilePath(), - q->deviceConfiguration()->filePath(file.remoteFilePath())}); - } - if (files.isEmpty()) { - emit q->progressMessage(Tr::tr("No files need to be uploaded.")); - return TaskAction::StopWithDone; - } - transfer.setFilesToTransfer(files); - QObject::connect(&transfer, &FileTransfer::progress, - q, &GenericDirectUploadService::progressMessage); - return TaskAction::Continue; - }; - const auto errorHandler = [this](const FileTransfer &transfer) { - emit q->errorMessage(transfer.resultData().m_errorString); - }; - - return Transfer(setupHandler, {}, errorHandler); -} - -TaskItem GenericDirectUploadServicePrivate::chmodTask(const DeployableFile &file) -{ - const auto setupHandler = [=](QtcProcess &process) { - process.setCommand({q->deviceConfiguration()->filePath("chmod"), - {"a+x", Utils::ProcessArgs::quoteArgUnix(file.remoteFilePath())}}); - }; - const auto errorHandler = [=](const QtcProcess &process) { - const QString error = process.errorString(); - if (!error.isEmpty()) { - emit q->warningMessage(Tr::tr("Remote chmod failed for file \"%1\": %2") - .arg(file.remoteFilePath(), error)); - } else if (process.exitCode() != 0) { - emit q->warningMessage(Tr::tr("Remote chmod failed for file \"%1\": %2") - .arg(file.remoteFilePath(), process.cleanedStdErr())); - } - }; - return Process(setupHandler, {}, errorHandler); -} - -TaskItem GenericDirectUploadServicePrivate::chmodTree(const TreeStorage &storage) -{ - const auto setupChmodHandler = [=](TaskTree &tree) { - QList filesToChmod; - for (const DeployableFile &file : std::as_const(storage->filesToUpload)) { - if (file.isExecutable()) - filesToChmod << file; - } - QList chmodList{optional, ParallelLimit(MaxConcurrentStatCalls)}; - for (const DeployableFile &file : std::as_const(filesToChmod)) { - QTC_ASSERT(file.isValid(), continue); - chmodList.append(chmodTask(file)); - } - tree.setupRoot({chmodList}); - }; - return Tree(setupChmodHandler); -} - -Group GenericDirectUploadService::deployRecipe() -{ - const auto preFilesToStat = [this](UploadStorage *storage) { - QList filesToStat; - for (const DeployableFile &file : std::as_const(d->deployableFiles)) { - if (d->incremental != IncrementalDeployment::Enabled || hasLocalFileChanged(file)) { - storage->filesToUpload.append(file); - continue; - } - if (d->incremental == IncrementalDeployment::NotSupported) - continue; - filesToStat << file; - } - return filesToStat; - }; - const auto preStatEndHandler = [this](UploadStorage *storage, const DeployableFile &file, - const QDateTime ×tamp) { - if (!timestamp.isValid() || hasRemoteFileChanged(file, timestamp)) - storage->filesToUpload.append(file); - }; - - const auto postFilesToStat = [this](UploadStorage *storage) { - return d->incremental == IncrementalDeployment::NotSupported - ? QList() : storage->filesToUpload; - }; - const auto postStatEndHandler = [this](UploadStorage *storage, const DeployableFile &file, - const QDateTime ×tamp) { - Q_UNUSED(storage) - if (timestamp.isValid()) - saveDeploymentTimeStamp(file, timestamp); - }; - const auto doneHandler = [this] { - emit progressMessage(Tr::tr("All files successfully deployed.")); - }; - - const TreeStorage storage; - const Group root { - Storage(storage), - d->statTree(storage, preFilesToStat, preStatEndHandler), - d->uploadTask(storage), - Group { - d->chmodTree(storage), - d->statTree(storage, postFilesToStat, postStatEndHandler) - }, - OnGroupDone(doneHandler) - }; - return root; -} - -} //namespace RemoteLinux diff --git a/src/plugins/remotelinux/genericdirectuploadservice.h b/src/plugins/remotelinux/genericdirectuploadservice.h deleted file mode 100644 index 31799f063ad..00000000000 --- a/src/plugins/remotelinux/genericdirectuploadservice.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "remotelinux_export.h" - -#include "abstractremotelinuxdeploystep.h" - -#include - -namespace ProjectExplorer { class DeployableFile; } - -namespace RemoteLinux { -namespace Internal { class GenericDirectUploadServicePrivate; } - -enum class IncrementalDeployment { Enabled, Disabled, NotSupported }; - -class REMOTELINUX_EXPORT GenericDirectUploadService : public AbstractRemoteLinuxDeployService -{ - Q_OBJECT -public: - GenericDirectUploadService(QObject *parent = nullptr); - ~GenericDirectUploadService(); - - void setDeployableFiles(const QList &deployableFiles); - void setIncrementalDeployment(IncrementalDeployment incremental); - void setIgnoreMissingFiles(bool ignoreMissingFiles); - -private: - bool isDeploymentNecessary() const final; - Utils::Tasking::Group deployRecipe() final; - - friend class Internal::GenericDirectUploadServicePrivate; - Internal::GenericDirectUploadServicePrivate * const d; -}; - -} //namespace RemoteLinux diff --git a/src/plugins/remotelinux/genericdirectuploadstep.cpp b/src/plugins/remotelinux/genericdirectuploadstep.cpp index 2b778e2b9c8..6074eaabaaf 100644 --- a/src/plugins/remotelinux/genericdirectuploadstep.cpp +++ b/src/plugins/remotelinux/genericdirectuploadstep.cpp @@ -2,69 +2,325 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "genericdirectuploadstep.h" +#include "abstractremotelinuxdeploystep.h" -#include "genericdirectuploadservice.h" #include "remotelinux_constants.h" #include "remotelinuxtr.h" +#include #include -#include +#include +#include #include +#include + +#include +#include +#include +#include + +#include using namespace ProjectExplorer; +using namespace Tasking; using namespace Utils; -namespace RemoteLinux { +namespace RemoteLinux::Internal { -GenericDirectUploadStep::GenericDirectUploadStep(BuildStepList *bsl, Utils::Id id, - bool offerIncrementalDeployment) - : AbstractRemoteLinuxDeployStep(bsl, id) +const int MaxConcurrentStatCalls = 10; + +struct UploadStorage { - auto service = new GenericDirectUploadService; - setDeployService(service); + QList filesToUpload; +}; - BoolAspect *incremental = nullptr; - if (offerIncrementalDeployment) { - incremental = addAspect(); +enum class IncrementalDeployment { Enabled, Disabled, NotSupported }; + +class GenericDirectUploadStep : public AbstractRemoteLinuxDeployStep +{ +public: + GenericDirectUploadStep(ProjectExplorer::BuildStepList *bsl, Id id) + : AbstractRemoteLinuxDeployStep(bsl, id) + { + auto incremental = addAspect(); incremental->setSettingsKey("RemoteLinux.GenericDirectUploadStep.Incremental"); incremental->setLabel(Tr::tr("Incremental deployment"), BoolAspect::LabelPlacement::AtCheckBox); incremental->setValue(true); incremental->setDefaultValue(true); + + auto ignoreMissingFiles = addAspect(); + ignoreMissingFiles->setSettingsKey("RemoteLinux.GenericDirectUploadStep.IgnoreMissingFiles"); + ignoreMissingFiles->setLabel(Tr::tr("Ignore missing files"), + BoolAspect::LabelPlacement::AtCheckBox); + ignoreMissingFiles->setValue(false); + + setInternalInitializer([this, incremental, ignoreMissingFiles] { + m_incremental = incremental->value() + ? IncrementalDeployment::Enabled : IncrementalDeployment::Disabled; + m_ignoreMissingFiles = ignoreMissingFiles->value(); + return isDeploymentPossible(); + }); + + setRunPreparer([this] { + m_deployableFiles = target()->deploymentData().allFiles(); + }); } - auto ignoreMissingFiles = addAspect(); - ignoreMissingFiles->setSettingsKey("RemoteLinux.GenericDirectUploadStep.IgnoreMissingFiles"); - ignoreMissingFiles->setLabel(Tr::tr("Ignore missing files"), - BoolAspect::LabelPlacement::AtCheckBox); - ignoreMissingFiles->setValue(false); + bool isDeploymentNecessary() const final; + Group deployRecipe() final; - setInternalInitializer([incremental, ignoreMissingFiles, service] { - if (incremental) { - service->setIncrementalDeployment(incremental->value() - ? IncrementalDeployment::Enabled : IncrementalDeployment::Disabled); - } else { - service->setIncrementalDeployment(IncrementalDeployment::NotSupported); + QDateTime timestampFromStat(const DeployableFile &file, Process *statProc); + + using FilesToStat = std::function(UploadStorage *)>; + using StatEndHandler + = std::function; + TaskItem statTask(UploadStorage *storage, const DeployableFile &file, + StatEndHandler statEndHandler); + TaskItem statTree(const TreeStorage &storage, FilesToStat filesToStat, + StatEndHandler statEndHandler); + TaskItem uploadTask(const TreeStorage &storage); + TaskItem chmodTask(const DeployableFile &file); + TaskItem chmodTree(const TreeStorage &storage); + + IncrementalDeployment m_incremental = IncrementalDeployment::NotSupported; + bool m_ignoreMissingFiles = false; + mutable QList m_deployableFiles; +}; + +static QList collectFilesToUpload(const DeployableFile &deployable) +{ + QList collected; + FilePath localFile = deployable.localFilePath(); + if (localFile.isDir()) { + const FilePaths files = localFile.dirEntries(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + const QString remoteDir = deployable.remoteDirectory() + '/' + localFile.fileName(); + for (const FilePath &localFilePath : files) + collected.append(collectFilesToUpload(DeployableFile(localFilePath, remoteDir))); + } else { + collected << deployable; + } + return collected; +} + +bool GenericDirectUploadStep::isDeploymentNecessary() const +{ + QList collected; + for (int i = 0; i < m_deployableFiles.count(); ++i) + collected.append(collectFilesToUpload(m_deployableFiles.at(i))); + + QTC_CHECK(collected.size() >= m_deployableFiles.size()); + m_deployableFiles = collected; + return !m_deployableFiles.isEmpty(); +} + +QDateTime GenericDirectUploadStep::timestampFromStat(const DeployableFile &file, + Process *statProc) +{ + bool succeeded = false; + QString error; + if (statProc->error() == QProcess::FailedToStart) { + error = Tr::tr("Failed to start \"stat\": %1").arg(statProc->errorString()); + } else if (statProc->exitStatus() == QProcess::CrashExit) { + error = Tr::tr("\"stat\" crashed."); + } else if (statProc->exitCode() != 0) { + error = Tr::tr("\"stat\" failed with exit code %1: %2") + .arg(statProc->exitCode()).arg(statProc->cleanedStdErr()); + } else { + succeeded = true; + } + if (!succeeded) { + addWarningMessage(Tr::tr("Failed to retrieve remote timestamp for file \"%1\". " + "Incremental deployment will not work. Error message was: %2") + .arg(file.remoteFilePath(), error)); + return {}; + } + const QByteArray output = statProc->readAllRawStandardOutput().trimmed(); + const QString warningString(Tr::tr("Unexpected stat output for remote file \"%1\": %2") + .arg(file.remoteFilePath()).arg(QString::fromUtf8(output))); + if (!output.startsWith(file.remoteFilePath().toUtf8())) { + addWarningMessage(warningString); + return {}; + } + const QByteArrayList columns = output.mid(file.remoteFilePath().toUtf8().size() + 1).split(' '); + if (columns.size() < 14) { // Normal Linux stat: 16 columns in total, busybox stat: 15 columns + addWarningMessage(warningString); + return {}; + } + bool isNumber; + const qint64 secsSinceEpoch = columns.at(11).toLongLong(&isNumber); + if (!isNumber) { + addWarningMessage(warningString); + return {}; + } + return QDateTime::fromSecsSinceEpoch(secsSinceEpoch); +} + +TaskItem GenericDirectUploadStep::statTask(UploadStorage *storage, + const DeployableFile &file, + StatEndHandler statEndHandler) +{ + const auto setupHandler = [=](Process &process) { + // We'd like to use --format=%Y, but it's not supported by busybox. + process.setCommand({deviceConfiguration()->filePath("stat"), + {"-t", Utils::ProcessArgs::quoteArgUnix(file.remoteFilePath())}}); + }; + const auto endHandler = [=](const Process &process) { + Process *proc = const_cast(&process); + const QDateTime timestamp = timestampFromStat(file, proc); + statEndHandler(storage, file, timestamp); + }; + return ProcessTask(setupHandler, endHandler, endHandler); +} + +TaskItem GenericDirectUploadStep::statTree(const TreeStorage &storage, + FilesToStat filesToStat, StatEndHandler statEndHandler) +{ + const auto setupHandler = [=](TaskTree &tree) { + UploadStorage *storagePtr = storage.activeStorage(); + const QList files = filesToStat(storagePtr); + QList statList{finishAllAndDone, parallelLimit(MaxConcurrentStatCalls)}; + for (const DeployableFile &file : std::as_const(files)) { + QTC_ASSERT(file.isValid(), continue); + statList.append(statTask(storagePtr, file, statEndHandler)); } - service->setIgnoreMissingFiles(ignoreMissingFiles->value()); - return service->isDeploymentPossible(); - }); - - setRunPreparer([this, service] { - service->setDeployableFiles(target()->deploymentData().allFiles()); - }); + tree.setupRoot({statList}); + }; + return TaskTreeTask(setupHandler); } -GenericDirectUploadStep::~GenericDirectUploadStep() = default; - -Utils::Id GenericDirectUploadStep::stepId() +TaskItem GenericDirectUploadStep::uploadTask(const TreeStorage &storage) { - return Constants::DirectUploadStepId; + const auto setupHandler = [this, storage](FileTransfer &transfer) { + if (storage->filesToUpload.isEmpty()) { + addProgressMessage(Tr::tr("No files need to be uploaded.")); + return TaskAction::StopWithDone; + } + addProgressMessage(Tr::tr("%n file(s) need to be uploaded.", "", + storage->filesToUpload.size())); + FilesToTransfer files; + for (const DeployableFile &file : std::as_const(storage->filesToUpload)) { + if (!file.localFilePath().exists()) { + const QString message = Tr::tr("Local file \"%1\" does not exist.") + .arg(file.localFilePath().toUserOutput()); + if (m_ignoreMissingFiles) { + addWarningMessage(message); + continue; + } + addErrorMessage(message); + return TaskAction::StopWithError; + } + files.append({file.localFilePath(), + deviceConfiguration()->filePath(file.remoteFilePath())}); + } + if (files.isEmpty()) { + addProgressMessage(Tr::tr("No files need to be uploaded.")); + return TaskAction::StopWithDone; + } + transfer.setFilesToTransfer(files); + QObject::connect(&transfer, &FileTransfer::progress, + this, &GenericDirectUploadStep::addProgressMessage); + return TaskAction::Continue; + }; + const auto errorHandler = [this](const FileTransfer &transfer) { + addErrorMessage(transfer.resultData().m_errorString); + }; + + return FileTransferTask(setupHandler, {}, errorHandler); } -QString GenericDirectUploadStep::displayName() +TaskItem GenericDirectUploadStep::chmodTask(const DeployableFile &file) { - return Tr::tr("Upload files via SFTP"); + const auto setupHandler = [=](Process &process) { + process.setCommand({deviceConfiguration()->filePath("chmod"), + {"a+x", Utils::ProcessArgs::quoteArgUnix(file.remoteFilePath())}}); + }; + const auto errorHandler = [=](const Process &process) { + const QString error = process.errorString(); + if (!error.isEmpty()) { + addWarningMessage(Tr::tr("Remote chmod failed for file \"%1\": %2") + .arg(file.remoteFilePath(), error)); + } else if (process.exitCode() != 0) { + addWarningMessage(Tr::tr("Remote chmod failed for file \"%1\": %2") + .arg(file.remoteFilePath(), process.cleanedStdErr())); + } + }; + return ProcessTask(setupHandler, {}, errorHandler); } -} //namespace RemoteLinux +TaskItem GenericDirectUploadStep::chmodTree(const TreeStorage &storage) +{ + const auto setupChmodHandler = [=](TaskTree &tree) { + QList filesToChmod; + for (const DeployableFile &file : std::as_const(storage->filesToUpload)) { + if (file.isExecutable()) + filesToChmod << file; + } + QList chmodList{finishAllAndDone, parallelLimit(MaxConcurrentStatCalls)}; + for (const DeployableFile &file : std::as_const(filesToChmod)) { + QTC_ASSERT(file.isValid(), continue); + chmodList.append(chmodTask(file)); + } + tree.setupRoot({chmodList}); + }; + return TaskTreeTask(setupChmodHandler); +} + +Group GenericDirectUploadStep::deployRecipe() +{ + const auto preFilesToStat = [this](UploadStorage *storage) { + QList filesToStat; + for (const DeployableFile &file : std::as_const(m_deployableFiles)) { + if (m_incremental != IncrementalDeployment::Enabled || hasLocalFileChanged(file)) { + storage->filesToUpload.append(file); + continue; + } + if (m_incremental == IncrementalDeployment::NotSupported) + continue; + filesToStat << file; + } + return filesToStat; + }; + const auto preStatEndHandler = [this](UploadStorage *storage, const DeployableFile &file, + const QDateTime ×tamp) { + if (!timestamp.isValid() || hasRemoteFileChanged(file, timestamp)) + storage->filesToUpload.append(file); + }; + + const auto postFilesToStat = [this](UploadStorage *storage) { + return m_incremental == IncrementalDeployment::NotSupported + ? QList() : storage->filesToUpload; + }; + const auto postStatEndHandler = [this](UploadStorage *storage, const DeployableFile &file, + const QDateTime ×tamp) { + Q_UNUSED(storage) + if (timestamp.isValid()) + saveDeploymentTimeStamp(file, timestamp); + }; + const auto doneHandler = [this] { + addProgressMessage(Tr::tr("All files successfully deployed.")); + }; + + const TreeStorage storage; + const Group root { + Storage(storage), + statTree(storage, preFilesToStat, preStatEndHandler), + uploadTask(storage), + Group { + chmodTree(storage), + statTree(storage, postFilesToStat, postStatEndHandler) + }, + onGroupDone(doneHandler) + }; + return root; +} + +// Factory + +GenericDirectUploadStepFactory::GenericDirectUploadStepFactory() +{ + registerStep(Constants::DirectUploadStepId); + setDisplayName(Tr::tr("Upload files via SFTP")); +} + +} // RemoteLinux::Internal diff --git a/src/plugins/remotelinux/genericdirectuploadstep.h b/src/plugins/remotelinux/genericdirectuploadstep.h index 0c2fb1b67d8..3e825caf5c6 100644 --- a/src/plugins/remotelinux/genericdirectuploadstep.h +++ b/src/plugins/remotelinux/genericdirectuploadstep.h @@ -3,23 +3,14 @@ #pragma once -#include "remotelinux_export.h" +#include -#include "abstractremotelinuxdeploystep.h" +namespace RemoteLinux::Internal { -namespace RemoteLinux { - -class REMOTELINUX_EXPORT GenericDirectUploadStep : public AbstractRemoteLinuxDeployStep +class GenericDirectUploadStepFactory : public ProjectExplorer::BuildStepFactory { - Q_OBJECT - public: - GenericDirectUploadStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id, - bool offerIncrementalDeployment = true); - ~GenericDirectUploadStep() override; - - static Utils::Id stepId(); - static QString displayName(); + GenericDirectUploadStepFactory(); }; -} //namespace RemoteLinux +} // RemoteLinux::Internal diff --git a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.cpp b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.cpp index 50c553437f6..da7b6138dea 100644 --- a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.cpp +++ b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.cpp @@ -3,9 +3,11 @@ #include "genericlinuxdeviceconfigurationwidget.h" +#include "remotelinux_constants.h" #include "remotelinuxtr.h" #include "sshkeycreationdialog.h" +#include #include #include @@ -16,6 +18,7 @@ #include #include +#include #include #include #include @@ -32,14 +35,13 @@ GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget( const IDevice::Ptr &device) : IDeviceWidget(device) { - resize(556, 309); - m_defaultAuthButton = new QRadioButton(Tr::tr("Default"), this); m_keyButton = new QRadioButton(Tr::tr("Specific &key")); - m_hostLineEdit = new QLineEdit(this); + m_hostLineEdit = new FancyLineEdit(this); m_hostLineEdit->setPlaceholderText(Tr::tr("IP or host name of the device")); + m_hostLineEdit->setHistoryCompleter("HostName"); m_sshPortSpinBox = new QSpinBox(this); m_sshPortSpinBox->setMinimum(0); @@ -48,8 +50,9 @@ GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget( m_hostKeyCheckBox = new QCheckBox(Tr::tr("&Check host key")); - m_portsLineEdit = new QLineEdit(this); + m_portsLineEdit = new FancyLineEdit(this); m_portsLineEdit->setToolTip(Tr::tr("You can enter lists and ranges like this: '1024,1026-1028,1030'.")); + m_portsLineEdit->setHistoryCompleter("PortRange"); m_portsWarningLabel = new QLabel(this); @@ -59,7 +62,8 @@ GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget( m_timeoutSpinBox->setValue(1000); m_timeoutSpinBox->setSuffix(Tr::tr("s")); - m_userLineEdit = new QLineEdit(this); + m_userLineEdit = new FancyLineEdit(this); + m_userLineEdit->setHistoryCompleter("UserName"); m_keyLabel = new QLabel(Tr::tr("Private key file:")); @@ -74,11 +78,28 @@ GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget( m_gdbServerLineEdit->setExpectedKind(PathChooser::ExistingCommand); m_gdbServerLineEdit->setPlaceholderText(hint); m_gdbServerLineEdit->setToolTip(hint); + m_gdbServerLineEdit->setHistoryCompleter("GdbServer"); + m_gdbServerLineEdit->setAllowPathFromDevice(true); m_qmlRuntimeLineEdit = new PathChooser(this); m_qmlRuntimeLineEdit->setExpectedKind(PathChooser::ExistingCommand); m_qmlRuntimeLineEdit->setPlaceholderText(hint); m_qmlRuntimeLineEdit->setToolTip(hint); + m_qmlRuntimeLineEdit->setHistoryCompleter("QmlRuntime"); + m_qmlRuntimeLineEdit->setAllowPathFromDevice(true); + + m_sourceProfileCheckBox = + new QCheckBox(Tr::tr("Source %1 and %2").arg("/etc/profile").arg("$HOME/.profile")); + + m_linkDeviceComboBox = new QComboBox; + m_linkDeviceComboBox->addItem(Tr::tr("Direct"), QVariant()); + + auto dm = DeviceManager::instance(); + const int dmCount = dm->deviceCount(); + for (int i = 0; i < dmCount; ++i) { + IDevice::ConstPtr dev = dm->deviceAt(i); + m_linkDeviceComboBox->addItem(dev->displayName(), dev->id().toSetting()); + } auto sshPortLabel = new QLabel(Tr::tr("&SSH port:")); sshPortLabel->setBuddy(m_sshPortSpinBox); @@ -93,7 +114,9 @@ GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget( Tr::tr("&Username:"), m_userLineEdit, st, br, m_keyLabel, m_keyFileLineEdit, createKeyButton, br, Tr::tr("GDB server executable:"), m_gdbServerLineEdit, br, - Tr::tr("QML runtime executable:"), m_qmlRuntimeLineEdit, br + Tr::tr("QML runtime executable:"), m_qmlRuntimeLineEdit, br, + QString(), m_sourceProfileCheckBox, br, + Tr::tr("Access via:"), m_linkDeviceComboBox }.attachTo(this); connect(m_hostLineEdit, &QLineEdit::editingFinished, @@ -124,6 +147,10 @@ GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget( this, &GenericLinuxDeviceConfigurationWidget::qmlRuntimeEditingFinished); connect(m_hostKeyCheckBox, &QCheckBox::toggled, this, &GenericLinuxDeviceConfigurationWidget::hostKeyCheckingChanged); + connect(m_sourceProfileCheckBox, &QCheckBox::toggled, + this, &GenericLinuxDeviceConfigurationWidget::sourceProfileCheckingChanged); + connect(m_linkDeviceComboBox, &QComboBox::currentIndexChanged, + this, &GenericLinuxDeviceConfigurationWidget::linkDeviceChanged); initGui(); } @@ -214,6 +241,17 @@ void GenericLinuxDeviceConfigurationWidget::hostKeyCheckingChanged(bool doCheck) device()->setSshParameters(sshParams); } +void GenericLinuxDeviceConfigurationWidget::sourceProfileCheckingChanged(bool doCheck) +{ + device()->setExtraData(Constants::SourceProfile, doCheck); +} + +void GenericLinuxDeviceConfigurationWidget::linkDeviceChanged(int index) +{ + const QVariant deviceId = m_linkDeviceComboBox->itemData(index); + device()->setExtraData(Constants::LinkDevice, deviceId); +} + void GenericLinuxDeviceConfigurationWidget::updateDeviceFromUi() { hostNameEditingFinished(); @@ -223,6 +261,10 @@ void GenericLinuxDeviceConfigurationWidget::updateDeviceFromUi() keyFileEditingFinished(); handleFreePortsChanged(); gdbServerEditingFinished(); + sshPortEditingFinished(); + timeoutEditingFinished(); + sourceProfileCheckingChanged(m_sourceProfileCheckBox->isChecked()); + linkDeviceChanged(m_linkDeviceComboBox->currentIndex()); qmlRuntimeEditingFinished(); } @@ -261,6 +303,17 @@ void GenericLinuxDeviceConfigurationWidget::initGui() m_hostLineEdit->setEnabled(!device()->isAutoDetected()); m_sshPortSpinBox->setEnabled(!device()->isAutoDetected()); m_hostKeyCheckBox->setChecked(sshParams.hostKeyCheckingMode != SshHostKeyCheckingNone); + m_sourceProfileCheckBox->setChecked(device()->extraData(Constants::SourceProfile).toBool()); + Id linkDeviceId = Id::fromSetting(device()->extraData(Constants::LinkDevice)); + auto dm = DeviceManager::instance(); + int found = -1; + for (int i = 0, n = dm->deviceCount(); i < n; ++i) { + if (dm->deviceAt(i)->id() == linkDeviceId) { + found = i; + break; + } + } + m_linkDeviceComboBox->setCurrentIndex(found + 1); // There's the "Direct" entry first. m_hostLineEdit->setText(sshParams.host()); m_sshPortSpinBox->setValue(sshParams.port()); diff --git a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.h b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.h index 4e00cedcb62..aba251b969e 100644 --- a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.h +++ b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.h @@ -7,13 +7,14 @@ QT_BEGIN_NAMESPACE class QCheckBox; +class QComboBox; class QLabel; -class QLineEdit; class QRadioButton; class QSpinBox; QT_END_NAMESPACE namespace Utils { +class FancyLineEdit; class FilePath; class PathChooser; } // Utils @@ -42,6 +43,8 @@ private: void setPrivateKey(const Utils::FilePath &path); void createNewKey(); void hostKeyCheckingChanged(bool doCheck); + void sourceProfileCheckingChanged(bool doCheck); + void linkDeviceChanged(int index); void updateDeviceFromUi() override; void updatePortsWarningLabel(); @@ -50,17 +53,19 @@ private: QRadioButton *m_defaultAuthButton; QLabel *m_keyLabel; QRadioButton *m_keyButton; - QLineEdit *m_hostLineEdit; + Utils::FancyLineEdit *m_hostLineEdit; QSpinBox *m_sshPortSpinBox; QCheckBox *m_hostKeyCheckBox; - QLineEdit *m_portsLineEdit; + Utils::FancyLineEdit *m_portsLineEdit; QLabel *m_portsWarningLabel; - QLineEdit *m_userLineEdit; + Utils::FancyLineEdit *m_userLineEdit; QSpinBox *m_timeoutSpinBox; Utils::PathChooser *m_keyFileLineEdit; QLabel *m_machineTypeValueLabel; Utils::PathChooser *m_gdbServerLineEdit; Utils::PathChooser *m_qmlRuntimeLineEdit; + QCheckBox *m_sourceProfileCheckBox; + QComboBox *m_linkDeviceComboBox; }; } // RemoteLinux::Internal diff --git a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizard.cpp b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizard.cpp index 4de31d898de..7bd047a590e 100644 --- a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizard.cpp +++ b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizard.cpp @@ -11,7 +11,6 @@ #include #include -#include #include using namespace ProjectExplorer; @@ -45,13 +44,6 @@ GenericLinuxDeviceConfigurationWizard::GenericLinuxDeviceConfigurationWizard(QWi setPage(Internal::FinalPageId, &d->finalPage); d->finalPage.setCommitPage(true); d->device = LinuxDevice::create(); - d->device->setupId(IDevice::ManuallyAdded, Utils::Id()); - d->device->setType(Constants::GenericLinuxOsType); - d->device->setMachineType(IDevice::Hardware); - d->device->setFreePorts(Utils::PortList::fromString(QLatin1String("10000-10100"))); - SshParameters sshParams; - sshParams.timeout = 10; - d->device->setSshParameters(sshParams); d->setupPage.setDevice(d->device); d->keyDeploymentPage.setDevice(d->device); } diff --git a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardpages.cpp b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardpages.cpp index 89e4c2e4838..0a34683bdeb 100644 --- a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardpages.cpp +++ b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardpages.cpp @@ -9,14 +9,15 @@ #include +#include #include +#include #include #include #include #include #include -#include #include #include @@ -29,10 +30,10 @@ namespace Internal { class GenericLinuxDeviceConfigurationWizardSetupPagePrivate { public: - QLineEdit *nameLineEdit; - QLineEdit *hostNameLineEdit; + FancyLineEdit *nameLineEdit; + FancyLineEdit *hostNameLineEdit; QSpinBox *sshPortSpinBox; - QLineEdit *userNameLineEdit; + FancyLineEdit *userNameLineEdit; LinuxDevice::Ptr device; }; @@ -52,10 +53,16 @@ GenericLinuxDeviceConfigurationWizardSetupPage::GenericLinuxDeviceConfigurationW setTitle(Tr::tr("Connection")); setWindowTitle(Tr::tr("WizardPage")); - d->nameLineEdit = new QLineEdit(this); - d->hostNameLineEdit = new QLineEdit(this); + d->nameLineEdit = new FancyLineEdit(this); + d->nameLineEdit->setHistoryCompleter("DeviceName"); + + d->hostNameLineEdit = new FancyLineEdit(this); + d->hostNameLineEdit->setHistoryCompleter("HostName"); + d->sshPortSpinBox = new QSpinBox(this); - d->userNameLineEdit = new QLineEdit(this); + + d->userNameLineEdit = new FancyLineEdit(this); + d->userNameLineEdit->setHistoryCompleter("UserName"); using namespace Layouting; Form { @@ -97,7 +104,9 @@ bool GenericLinuxDeviceConfigurationWizardSetupPage::validatePage() { d->device->setDisplayName(configurationName()); SshParameters sshParams = d->device->sshParameters(); - sshParams.url = url(); + sshParams.setHost(d->hostNameLineEdit->text().trimmed()); + sshParams.setUserName(d->userNameLineEdit->text().trimmed()); + sshParams.setPort(d->sshPortSpinBox->value()); d->device->setSshParameters(sshParams); return true; } @@ -107,15 +116,6 @@ QString GenericLinuxDeviceConfigurationWizardSetupPage::configurationName() cons return d->nameLineEdit->text().trimmed(); } -QUrl GenericLinuxDeviceConfigurationWizardSetupPage::url() const -{ - QUrl url; - url.setHost(d->hostNameLineEdit->text().trimmed()); - url.setUserName(d->userNameLineEdit->text().trimmed()); - url.setPort(d->sshPortSpinBox->value()); - return url; -} - void GenericLinuxDeviceConfigurationWizardSetupPage::setDevice(const LinuxDevice::Ptr &device) { d->device = device; diff --git a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardpages.h b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardpages.h index 6e5e00119ad..2e2d5298913 100644 --- a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardpages.h +++ b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardpages.h @@ -32,7 +32,6 @@ private: bool validatePage() override; QString configurationName() const; - QUrl url() const; Internal::GenericLinuxDeviceConfigurationWizardSetupPagePrivate * const d; }; @@ -64,7 +63,7 @@ class REMOTELINUX_EXPORT GenericLinuxDeviceConfigurationWizardFinalPage final : { Q_OBJECT public: - GenericLinuxDeviceConfigurationWizardFinalPage(QWidget *parent); + GenericLinuxDeviceConfigurationWizardFinalPage(QWidget *parent = nullptr); ~GenericLinuxDeviceConfigurationWizardFinalPage() override; protected: diff --git a/src/plugins/remotelinux/killappstep.cpp b/src/plugins/remotelinux/killappstep.cpp index 03473d95a23..d3ec72e8895 100644 --- a/src/plugins/remotelinux/killappstep.cpp +++ b/src/plugins/remotelinux/killappstep.cpp @@ -15,15 +15,26 @@ #include using namespace ProjectExplorer; +using namespace Tasking; using namespace Utils; -using namespace Utils::Tasking; namespace RemoteLinux::Internal { -class KillAppService : public AbstractRemoteLinuxDeployService +class KillAppStep : public AbstractRemoteLinuxDeployStep { public: - void setRemoteExecutable(const FilePath &filePath) { m_remoteExecutable = filePath; } + KillAppStep(BuildStepList *bsl, Id id) : AbstractRemoteLinuxDeployStep(bsl, id) + { + setWidgetExpandedByDefault(false); + + setInternalInitializer([this] { + Target * const theTarget = target(); + QTC_ASSERT(theTarget, return CheckResult::failure()); + RunConfiguration * const rc = theTarget->activeRunConfiguration(); + m_remoteExecutable = rc ? rc->runnable().command.executable() : FilePath(); + return CheckResult::success(); + }); + } private: bool isDeploymentNecessary() const final { return !m_remoteExecutable.isEmpty(); } @@ -32,44 +43,23 @@ private: FilePath m_remoteExecutable; }; -Group KillAppService::deployRecipe() +Group KillAppStep::deployRecipe() { const auto setupHandler = [this](DeviceProcessKiller &killer) { killer.setProcessPath(m_remoteExecutable); - emit progressMessage(Tr::tr("Trying to kill \"%1\" on remote device...") - .arg(m_remoteExecutable.path())); + addProgressMessage(Tr::tr("Trying to kill \"%1\" on remote device...") + .arg(m_remoteExecutable.path())); }; const auto doneHandler = [this](const DeviceProcessKiller &) { - emit progressMessage(Tr::tr("Remote application killed.")); + addProgressMessage(Tr::tr("Remote application killed.")); }; const auto errorHandler = [this](const DeviceProcessKiller &) { - emit progressMessage(Tr::tr("Failed to kill remote application. " + addProgressMessage(Tr::tr("Failed to kill remote application. " "Assuming it was not running.")); }; - return Group { Killer(setupHandler, doneHandler, errorHandler) }; + return Group { DeviceProcessKillerTask(setupHandler, doneHandler, errorHandler) }; } -class KillAppStep : public AbstractRemoteLinuxDeployStep -{ -public: - KillAppStep(BuildStepList *bsl, Id id) : AbstractRemoteLinuxDeployStep(bsl, id) - { - auto service = new Internal::KillAppService; - setDeployService(service); - - setWidgetExpandedByDefault(false); - - setInternalInitializer([this, service] { - Target * const theTarget = target(); - QTC_ASSERT(theTarget, return CheckResult::failure()); - RunConfiguration * const rc = theTarget->activeRunConfiguration(); - const FilePath remoteExe = rc ? rc->runnable().command.executable() : FilePath(); - service->setRemoteExecutable(remoteExe); - return CheckResult::success(); - }); - } -}; - KillAppStepFactory::KillAppStepFactory() { registerStep(Constants::KillAppStepId); diff --git a/src/plugins/remotelinux/linuxdevice.cpp b/src/plugins/remotelinux/linuxdevice.cpp index 5d0d2d6b92e..cc44dddb3c4 100644 --- a/src/plugins/remotelinux/linuxdevice.cpp +++ b/src/plugins/remotelinux/linuxdevice.cpp @@ -11,14 +11,14 @@ #include "remotelinux_constants.h" #include "remotelinuxsignaloperation.h" #include "remotelinuxtr.h" -#include "sshprocessinterface.h" #include #include +#include #include #include -#include +#include #include #include @@ -28,9 +28,10 @@ #include #include #include +#include +#include #include #include -#include #include #include @@ -51,9 +52,6 @@ namespace RemoteLinux { const QByteArray s_pidMarker = "__qtc"; -const char Delimiter0[] = "x--"; -const char Delimiter1[] = "---"; - static Q_LOGGING_CATEGORY(linuxDeviceLog, "qtc.remotelinux.device", QtWarningMsg); #define DEBUG(x) qCDebug(linuxDeviceLog) << x << '\n' @@ -94,7 +92,7 @@ private: QStringList connectionArgs(const FilePath &binary) const; const SshParameters m_sshParameters; - std::unique_ptr m_masterProcess; + std::unique_ptr m_masterProcess; std::unique_ptr m_masterSocketDir; QTimer m_timer; int m_ref = 0; @@ -160,11 +158,11 @@ void SshSharedConnection::connectToHost() return; } - m_masterProcess.reset(new QtcProcess); + m_masterProcess.reset(new Process); SshParameters::setupSshEnvironment(m_masterProcess.get()); m_timer.setSingleShot(true); connect(&m_timer, &QTimer::timeout, this, &SshSharedConnection::autoDestructRequested); - connect(m_masterProcess.get(), &QtcProcess::readyReadStandardOutput, this, [this] { + connect(m_masterProcess.get(), &Process::readyReadStandardOutput, this, [this] { const QByteArray reply = m_masterProcess->readAllRawStandardOutput(); if (reply == "\n") emitConnected(); @@ -172,7 +170,7 @@ void SshSharedConnection::connectToHost() }); // TODO: in case of refused connection we are getting the following on stdErr: // ssh: connect to host 127.0.0.1 port 22: Connection refused\r\n - connect(m_masterProcess.get(), &QtcProcess::done, this, [this] { + connect(m_masterProcess.get(), &Process::done, this, [this] { const ProcessResult result = m_masterProcess->result(); const ProcessResultData resultData = m_masterProcess->resultData(); if (result == ProcessResult::StartFailed) { @@ -276,77 +274,6 @@ private: IDevice::ConstPtr m_device; }; -static QString visualizeNull(QString s) -{ - return s.replace(QLatin1Char('\0'), QLatin1String("")); -} - -class LinuxDeviceProcessList : public SshDeviceProcessList -{ -public: - LinuxDeviceProcessList(const IDevice::ConstPtr &device, QObject *parent) - : SshDeviceProcessList(device, parent) - { - } - -private: - QString listProcessesCommandLine() const override - { - return QString::fromLatin1( - "for dir in `ls -d /proc/[0123456789]*`; do " - "test -d $dir || continue;" // Decrease the likelihood of a race condition. - "echo $dir;" - "cat $dir/cmdline;echo;" // cmdline does not end in newline - "cat $dir/stat;" - "readlink $dir/exe;" - "printf '%1''%2';" - "done").arg(QLatin1String(Delimiter0)).arg(QLatin1String(Delimiter1)); - } - - QList buildProcessList(const QString &listProcessesReply) const override - { - QList processes; - const QStringList lines = listProcessesReply.split(QString::fromLatin1(Delimiter0) - + QString::fromLatin1(Delimiter1), Qt::SkipEmptyParts); - for (const QString &line : lines) { - const QStringList elements = line.split(QLatin1Char('\n')); - if (elements.count() < 4) { - qDebug("%s: Expected four list elements, got %d. Line was '%s'.", Q_FUNC_INFO, - int(elements.count()), qPrintable(visualizeNull(line))); - continue; - } - bool ok; - const int pid = elements.first().mid(6).toInt(&ok); - if (!ok) { - qDebug("%s: Expected number in %s. Line was '%s'.", Q_FUNC_INFO, - qPrintable(elements.first()), qPrintable(visualizeNull(line))); - continue; - } - QString command = elements.at(1); - command.replace(QLatin1Char('\0'), QLatin1Char(' ')); - if (command.isEmpty()) { - const QString &statString = elements.at(2); - const int openParenPos = statString.indexOf(QLatin1Char('(')); - const int closedParenPos = statString.indexOf(QLatin1Char(')'), openParenPos); - if (openParenPos == -1 || closedParenPos == -1) - continue; - command = QLatin1Char('[') - + statString.mid(openParenPos + 1, closedParenPos - openParenPos - 1) - + QLatin1Char(']'); - } - - ProcessInfo process; - process.processId = pid; - process.commandLine = command; - process.executable = elements.at(3); - processes.append(process); - } - - return Utils::sorted(std::move(processes)); - } -}; - - // LinuxDevicePrivate class ShellThreadHandler; @@ -382,11 +309,13 @@ public: Environment getEnvironment(); void invalidateEnvironmentCache(); + void checkOsType(); + void queryOsType(std::function run); + LinuxDevice *q = nullptr; QThread m_shellThread; ShellThreadHandler *m_handler = nullptr; mutable QMutex m_shellMutex; - QList m_terminals; LinuxDeviceFileAccess m_fileAccess{this}; QReadWriteLock m_environmentCacheLock; @@ -410,11 +339,8 @@ Environment LinuxDevicePrivate::getEnvironment() if (m_environmentCache.has_value()) return m_environmentCache.value(); - QtcProcess getEnvProc; - getEnvProc.setCommand({FilePath("env").onDevice(q->rootPath()), {}}); - Environment inEnv; - inEnv.setCombineWithDeviceEnvironment(false); - getEnvProc.setEnvironment(inEnv); + Process getEnvProc; + getEnvProc.setCommand({q->filePath("env"), {}}); getEnvProc.runBlocking(); const QString remoteOutput = getEnvProc.cleanedStdOut(); @@ -437,10 +363,8 @@ Environment LinuxDeviceFileAccess::deviceEnvironment() const class SshProcessInterfacePrivate : public QObject { - Q_OBJECT - public: - SshProcessInterfacePrivate(SshProcessInterface *sshInterface, LinuxDevicePrivate *devicePrivate); + SshProcessInterfacePrivate(SshProcessInterface *sshInterface, const IDevice::ConstPtr &device); void start(); @@ -463,47 +387,32 @@ public: // as this object is alive. IDevice::ConstPtr m_device; std::unique_ptr m_connectionHandle; - QtcProcess m_process; - LinuxDevicePrivate *m_devicePrivate = nullptr; + Process m_process; QString m_socketFilePath; SshParameters m_sshParameters; + bool m_connecting = false; bool m_killed = false; ProcessResultData m_result; + + QByteArray m_output; + QByteArray m_error; + bool m_pidParsed = false; + bool m_useConnectionSharing = false; }; -SshProcessInterface::SshProcessInterface(const LinuxDevice *linuxDevice) - : d(new SshProcessInterfacePrivate(this, linuxDevice->d)) -{ -} +SshProcessInterface::SshProcessInterface(const IDevice::ConstPtr &device) + : d(new SshProcessInterfacePrivate(this, device)) +{} SshProcessInterface::~SshProcessInterface() { + killIfRunning(); delete d; } -void SshProcessInterface::handleStarted(qint64 processId) -{ - emitStarted(processId); -} - -void SshProcessInterface::handleDone(const ProcessResultData &resultData) -{ - emit done(resultData); -} - -void SshProcessInterface::handleReadyReadStandardOutput(const QByteArray &outputData) -{ - emit readyRead(outputData, {}); -} - -void SshProcessInterface::handleReadyReadStandardError(const QByteArray &errorData) -{ - emit readyRead({}, errorData); -} - void SshProcessInterface::emitStarted(qint64 processId) { d->m_processId = processId; @@ -525,7 +434,7 @@ qint64 SshProcessInterface::processId() const bool SshProcessInterface::runInShell(const CommandLine &command, const QByteArray &data) { - QtcProcess process; + Process process; CommandLine cmd = {d->m_device->filePath("/bin/sh"), {"-c"}}; QString tmp; ProcessArgs::addArg(&tmp, command.executable().path()); @@ -556,7 +465,7 @@ void SshProcessInterface::sendControlSignal(ControlSignal controlSignal) d->m_process.closeWriteChannel(); return; } - if (d->m_process.usesTerminal()) { + if (d->m_process.usesTerminal() || d->m_process.ptyData().has_value()) { switch (controlSignal) { case ControlSignal::Terminate: d->m_process.terminate(); break; case ControlSignal::Kill: d->m_process.kill(); break; @@ -569,17 +478,7 @@ void SshProcessInterface::sendControlSignal(ControlSignal controlSignal) handleSendControlSignal(controlSignal); } -LinuxProcessInterface::LinuxProcessInterface(const LinuxDevice *linuxDevice) - : SshProcessInterface(linuxDevice) -{ -} - -LinuxProcessInterface::~LinuxProcessInterface() -{ - killIfRunning(); -} - -void LinuxProcessInterface::handleSendControlSignal(ControlSignal controlSignal) +void SshProcessInterface::handleSendControlSignal(ControlSignal controlSignal) { QTC_ASSERT(controlSignal != ControlSignal::KickOff, return); QTC_ASSERT(controlSignal != ControlSignal::CloseWriteChannel, return); @@ -592,75 +491,58 @@ void LinuxProcessInterface::handleSendControlSignal(ControlSignal controlSignal) runInShell(command); } -QString LinuxProcessInterface::fullCommandLine(const CommandLine &commandLine) const +void SshProcessInterfacePrivate::handleStarted() { - CommandLine cmd; + const qint64 processId = m_process.usesTerminal() ? m_process.processId() : 0; - if (!commandLine.isEmpty()) { - const QStringList rcFilesToSource = {"/etc/profile", "$HOME/.profile"}; - for (const QString &filePath : rcFilesToSource) { - cmd.addArgs({"test", "-f", filePath}); - cmd.addArgs("&&", CommandLine::Raw); - cmd.addArgs({".", filePath}); - cmd.addArgs(";", CommandLine::Raw); - } - } - - if (!m_setup.m_workingDirectory.isEmpty()) { - cmd.addArgs({"cd", m_setup.m_workingDirectory.path()}); - cmd.addArgs("&&", CommandLine::Raw); - } - - if (m_setup.m_terminalMode == TerminalMode::Off) - cmd.addArgs(QString("echo ") + s_pidMarker + "$$" + s_pidMarker + " && ", CommandLine::Raw); - - const Environment &env = m_setup.m_environment; - for (auto it = env.constBegin(); it != env.constEnd(); ++it) - cmd.addArgs(env.key(it) + "='" + env.expandedValueForKey(env.key(it)) + '\'', CommandLine::Raw); - - if (m_setup.m_terminalMode == TerminalMode::Off) - cmd.addArg("exec"); - - if (!commandLine.isEmpty()) - cmd.addCommandLineAsArgs(commandLine, CommandLine::Raw); - return cmd.arguments(); -} - -void LinuxProcessInterface::handleStarted(qint64 processId) -{ // Don't emit started() when terminal is off, // it's being done later inside handleReadyReadStandardOutput(). - if (m_setup.m_terminalMode == TerminalMode::Off) + if (q->m_setup.m_terminalMode == TerminalMode::Off && !q->m_setup.m_ptyData) return; m_pidParsed = true; - emitStarted(processId); + q->emitStarted(processId); } -void LinuxProcessInterface::handleDone(const ProcessResultData &resultData) +void SshProcessInterfacePrivate::handleDone() { - ProcessResultData finalData = resultData; + if (m_connectionHandle) // TODO: should it disconnect from signals first? + m_connectionHandle.release()->deleteLater(); + + ProcessResultData finalData = m_process.resultData(); if (!m_pidParsed) { finalData.m_error = QProcess::FailedToStart; finalData.m_errorString = Utils::joinStrings({finalData.m_errorString, QString::fromLocal8Bit(m_error)}, '\n'); } - emit done(finalData); + emit q->done(finalData); } -void LinuxProcessInterface::handleReadyReadStandardOutput(const QByteArray &outputData) +void SshProcessInterfacePrivate::handleReadyReadStandardOutput() { + // By default this forwards readyRead immediately, but only buffers the + // output in case the start signal is not emitted yet. + // In case the pid can be parsed now, the delayed started() is + // emitted, and any previously buffered output emitted now. + const QByteArray outputData = m_process.readAllRawStandardOutput(); + if (m_pidParsed) { - emit readyRead(outputData, {}); + emit q->readyRead(outputData, {}); return; } m_output.append(outputData); static const QByteArray endMarker = s_pidMarker + '\n'; - const int endMarkerOffset = m_output.indexOf(endMarker); - if (endMarkerOffset == -1) - return; + int endMarkerLength = endMarker.length(); + int endMarkerOffset = m_output.indexOf(endMarker); + if (endMarkerOffset == -1) { + static const QByteArray endMarker = s_pidMarker + "\r\n"; + endMarkerOffset = m_output.indexOf(endMarker); + endMarkerLength = endMarker.length(); + if (endMarkerOffset == -1) + return; + } const int startMarkerOffset = m_output.indexOf(s_pidMarker); if (startMarkerOffset == endMarkerOffset) // Only theoretically possible. return; @@ -670,59 +552,109 @@ void LinuxProcessInterface::handleReadyReadStandardOutput(const QByteArray &outp const qint64 processId = pidString.toLongLong(); // We don't want to show output from e.g. /etc/profile. - m_output = m_output.mid(endMarkerOffset + endMarker.length()); + m_output = m_output.mid(endMarkerOffset + endMarkerLength); - emitStarted(processId); + q->emitStarted(processId); if (!m_output.isEmpty() || !m_error.isEmpty()) - emit readyRead(m_output, m_error); + emit q->readyRead(m_output, m_error); m_output.clear(); m_error.clear(); } -void LinuxProcessInterface::handleReadyReadStandardError(const QByteArray &errorData) +void SshProcessInterfacePrivate::handleReadyReadStandardError() { + // By default forwards readyRead immediately, but buffers it in + // case the start signal is not emitted yet. + const QByteArray errorData = m_process.readAllRawStandardError(); + if (m_pidParsed) { - emit readyRead({}, errorData); + emit q->readyRead({}, errorData); return; } m_error.append(errorData); } SshProcessInterfacePrivate::SshProcessInterfacePrivate(SshProcessInterface *sshInterface, - LinuxDevicePrivate *devicePrivate) + const IDevice::ConstPtr &device) : QObject(sshInterface) , q(sshInterface) - , m_device(devicePrivate->q->sharedFromThis()) + , m_device(device) , m_process(this) - , m_devicePrivate(devicePrivate) { - connect(&m_process, &QtcProcess::started, this, &SshProcessInterfacePrivate::handleStarted); - connect(&m_process, &QtcProcess::done, this, &SshProcessInterfacePrivate::handleDone); - connect(&m_process, &QtcProcess::readyReadStandardOutput, + connect(&m_process, &Process::started, this, &SshProcessInterfacePrivate::handleStarted); + connect(&m_process, &Process::done, this, &SshProcessInterfacePrivate::handleDone); + connect(&m_process, &Process::readyReadStandardOutput, this, &SshProcessInterfacePrivate::handleReadyReadStandardOutput); - connect(&m_process, &QtcProcess::readyReadStandardError, + connect(&m_process, &Process::readyReadStandardError, this, &SshProcessInterfacePrivate::handleReadyReadStandardError); } void SshProcessInterfacePrivate::start() { clearForStart(); + m_sshParameters = m_device->sshParameters(); + + const Id linkDeviceId = Id::fromSetting(m_device->extraData(Constants::LinkDevice)); + if (const IDevice::ConstPtr linkDevice = DeviceManager::instance()->find(linkDeviceId)) { + CommandLine cmd{linkDevice->filePath("ssh")}; + if (!m_sshParameters.userName().isEmpty()) { + cmd.addArg("-l"); + cmd.addArg(m_sshParameters.userName()); + } + cmd.addArg(m_sshParameters.host()); + + const bool useTerminal = q->m_setup.m_terminalMode != TerminalMode::Off + || q->m_setup.m_ptyData; + if (useTerminal) + cmd.addArg("-tt"); + + const CommandLine full = q->m_setup.m_commandLine; + if (!full.isEmpty()) { // Empty is ok in case of opening a terminal. + CommandLine inner; + const QString wd = q->m_setup.m_workingDirectory.path(); + if (!wd.isEmpty()) + inner.addCommandLineWithAnd({"cd", {wd}}); + if (!useTerminal) { + const QString pidArg = QString("%1\\$\\$%1").arg(QString::fromLatin1(s_pidMarker)); + inner.addCommandLineWithAnd({"echo", pidArg, CommandLine::Raw}); + } + inner.addCommandLineWithAnd(full); + cmd.addCommandLineAsSingleArg(inner); + } + + m_process.setProcessImpl(q->m_setup.m_processImpl); + m_process.setProcessMode(q->m_setup.m_processMode); + m_process.setTerminalMode(q->m_setup.m_terminalMode); + m_process.setPtyData(q->m_setup.m_ptyData); + m_process.setReaperTimeout(q->m_setup.m_reaperTimeout); + m_process.setWriteData(q->m_setup.m_writeData); + m_process.setCreateConsoleOnWindows(q->m_setup.m_createConsoleOnWindows); + m_process.setExtraData(q->m_setup.m_extraData); + + m_process.setCommand(cmd); + m_process.start(); + return; + } + + m_useConnectionSharing = SshSettings::connectionSharingEnabled(); - m_sshParameters = m_devicePrivate->q->sshParameters(); // TODO: Do we really need it for master process? m_sshParameters.x11DisplayName = q->m_setup.m_extraData.value("Ssh.X11ForwardToDisplay").toString(); - if (SshSettings::connectionSharingEnabled()) { + if (m_useConnectionSharing) { m_connecting = true; - m_connectionHandle.reset(new SshConnectionHandle(m_devicePrivate->q->sharedFromThis())); + m_connectionHandle.reset(new SshConnectionHandle(m_device)); m_connectionHandle->setParent(this); connect(m_connectionHandle.get(), &SshConnectionHandle::connected, this, &SshProcessInterfacePrivate::handleConnected); connect(m_connectionHandle.get(), &SshConnectionHandle::disconnected, this, &SshProcessInterfacePrivate::handleDisconnected); - m_devicePrivate->attachToSharedConnection(m_connectionHandle.get(), m_sshParameters); + auto linuxDevice = m_device.dynamicCast(); + QTC_ASSERT(linuxDevice, handleDone(); return); + linuxDevice->connectionAccess() + ->attachToSharedConnection(m_connectionHandle.get(), m_sshParameters); } else { doStart(); } @@ -749,34 +681,6 @@ void SshProcessInterfacePrivate::handleDisconnected(const ProcessResultData &res emit q->done(resultData); // TODO: don't emit done() on process finished afterwards } -void SshProcessInterfacePrivate::handleStarted() -{ - const qint64 processId = m_process.usesTerminal() ? m_process.processId() : 0; - // By default emits started signal, Linux impl doesn't emit it when terminal is off. - q->handleStarted(processId); -} - -void SshProcessInterfacePrivate::handleDone() -{ - if (m_connectionHandle) // TODO: should it disconnect from signals first? - m_connectionHandle.release()->deleteLater(); - q->handleDone(m_process.resultData()); -} - -void SshProcessInterfacePrivate::handleReadyReadStandardOutput() -{ - // By default emits signal. LinuxProcessImpl does custom parsing for processId - // and emits delayed start() - only when terminal is off. - q->handleReadyReadStandardOutput(m_process.readAllRawStandardOutput()); -} - -void SshProcessInterfacePrivate::handleReadyReadStandardError() -{ - // By default emits signal. LinuxProcessImpl buffers the error channel until - // it emits delayed start() - only when terminal is off. - q->handleReadyReadStandardError(m_process.readAllRawStandardError()); -} - void SshProcessInterfacePrivate::clearForStart() { m_result = {}; @@ -787,8 +691,11 @@ void SshProcessInterfacePrivate::doStart() m_process.setProcessImpl(q->m_setup.m_processImpl); m_process.setProcessMode(q->m_setup.m_processMode); m_process.setTerminalMode(q->m_setup.m_terminalMode); + m_process.setPtyData(q->m_setup.m_ptyData); m_process.setReaperTimeout(q->m_setup.m_reaperTimeout); m_process.setWriteData(q->m_setup.m_writeData); + m_process.setCreateConsoleOnWindows(q->m_setup.m_createConsoleOnWindows); + // TODO: what about other fields from m_setup? SshParameters::setupSshEnvironment(&m_process); if (!m_sshParameters.x11DisplayName.isEmpty()) { @@ -798,32 +705,72 @@ void SshProcessInterfacePrivate::doStart() env.set("DISPLAY", m_sshParameters.x11DisplayName); m_process.setControlEnvironment(env); } + m_process.setExtraData(q->m_setup.m_extraData); m_process.setCommand(fullLocalCommandLine()); m_process.start(); } CommandLine SshProcessInterfacePrivate::fullLocalCommandLine() const { - CommandLine cmd{SshSettings::sshFilePath()}; + const FilePath sshBinary = SshSettings::sshFilePath(); + const bool useTerminal = q->m_setup.m_terminalMode != TerminalMode::Off || q->m_setup.m_ptyData; + const bool usePidMarker = !useTerminal; + const bool sourceProfile = m_device->extraData(Constants::SourceProfile).toBool(); + const bool useX = !m_sshParameters.x11DisplayName.isEmpty(); - if (!m_sshParameters.x11DisplayName.isEmpty()) + CommandLine cmd{sshBinary}; + + if (useX) cmd.addArg("-X"); - if (q->m_setup.m_terminalMode != TerminalMode::Off) + if (useTerminal) cmd.addArg("-tt"); cmd.addArg("-q"); - QStringList options = m_sshParameters.connectionOptions(SshSettings::sshFilePath()); + cmd.addArgs(m_sshParameters.connectionOptions(sshBinary)); if (!m_socketFilePath.isEmpty()) - options << "-o" << ("ControlPath=" + m_socketFilePath); - options << m_sshParameters.host(); - cmd.addArgs(options); + cmd.addArgs({"-o", "ControlPath=" + m_socketFilePath}); - CommandLine remoteWithLocalPath = q->m_setup.m_commandLine; - FilePath executable = FilePath::fromParts({}, {}, remoteWithLocalPath.executable().path()); - remoteWithLocalPath.setExecutable(executable); + cmd.addArg(m_sshParameters.host()); + + CommandLine commandLine = q->m_setup.m_commandLine; + FilePath executable = FilePath::fromParts({}, {}, commandLine.executable().path()); + commandLine.setExecutable(executable); + + CommandLine inner; + + if (!commandLine.isEmpty() && sourceProfile) { + const QStringList rcFilesToSource = {"/etc/profile", "$HOME/.profile"}; + for (const QString &filePath : rcFilesToSource) { + inner.addArgs({"test", "-f", filePath}); + inner.addArgs("&&", CommandLine::Raw); + inner.addArgs({".", filePath}); + inner.addArgs(";", CommandLine::Raw); + } + } + + const FilePath &workingDirectory = q->m_setup.m_workingDirectory; + if (!workingDirectory.isEmpty()) { + inner.addArgs({"cd", workingDirectory.path()}); + inner.addArgs("&&", CommandLine::Raw); + } + + if (usePidMarker) + inner.addArgs(QString("echo ") + s_pidMarker + "$$" + s_pidMarker + " && ", CommandLine::Raw); + + const Environment &env = q->m_setup.m_environment; + env.forEachEntry([&](const QString &key, const QString &value, bool) { + inner.addArgs(key + "='" + env.expandVariables(value) + '\'', CommandLine::Raw); + }); + + if (!useTerminal && !commandLine.isEmpty()) + inner.addArg("exec"); + + if (!commandLine.isEmpty()) + inner.addCommandLineAsArgs(commandLine, CommandLine::Raw); + + cmd.addArg(inner.arguments()); - cmd.addArg(q->fullCommandLine(remoteWithLocalPath)); return cmd; } @@ -848,7 +795,7 @@ class ShellThreadHandler : public QObject } private: - void setupShellProcess(QtcProcess *shellProcess) override + void setupShellProcess(Process *shellProcess) override { SshParameters::setupSshEnvironment(shellProcess); shellProcess->setCommand(m_cmdLine); @@ -857,7 +804,7 @@ class ShellThreadHandler : public QObject CommandLine createFallbackCommand(const CommandLine &cmdLine) override { CommandLine result = cmdLine; - result.setExecutable(cmdLine.executable().onDevice(m_devicePath)); + result.setExecutable(m_devicePath.withNewMappedPath(cmdLine.executable())); // Needed? return result; } @@ -995,9 +942,16 @@ LinuxDevice::LinuxDevice() { setFileAccess(&d->m_fileAccess); setDisplayType(Tr::tr("Remote Linux")); - setDefaultDisplayName(Tr::tr("Remote Linux Device")); setOsType(OsTypeLinux); + setupId(IDevice::ManuallyAdded, Utils::Id()); + setType(Constants::GenericLinuxOsType); + setMachineType(IDevice::Hardware); + setFreePorts(PortList::fromString(QLatin1String("10000-10100"))); + SshParameters sshParams; + sshParams.timeout = 10; + setSshParameters(sshParams); + addDeviceAction({Tr::tr("Deploy Public Key..."), [](const IDevice::Ptr &device, QWidget *parent) { if (auto d = PublicKeyDeploymentDialog::createDialog(device, parent)) { d->exec(); @@ -1006,45 +960,31 @@ LinuxDevice::LinuxDevice() }}); setOpenTerminal([this](const Environment &env, const FilePath &workingDir) { - QtcProcess * const proc = new QtcProcess; - d->m_terminals.append(proc); - QObject::connect(proc, &QtcProcess::done, proc, [this, proc] { - if (proc->error() != QProcess::UnknownError) { - const QString errorString = proc->errorString(); - QString message; - if (proc->error() == QProcess::FailedToStart) - message = Tr::tr("Error starting remote shell."); - else if (errorString.isEmpty()) - message = Tr::tr("Error running remote shell."); - else - message = Tr::tr("Error running remote shell: %1").arg(errorString); - Core::MessageManager::writeDisrupting(message); - } - proc->deleteLater(); - d->m_terminals.removeOne(proc); - }); + Process proc; - // We recreate the same way that QtcProcess uses to create the actual environment. - const Environment finalEnv = (!env.hasChanges() && env.combineWithDeviceEnvironment()) - ? d->getEnvironment() - : env; // If we will not set any environment variables, we can leave out the shell executable // as the "ssh ..." call will automatically launch the default shell if there are // no arguments. But if we will set environment variables, we need to explicitly // specify the shell executable. - const QString shell = finalEnv.hasChanges() ? finalEnv.value_or("SHELL", "/bin/sh") - : QString(); + const QString shell = env.hasChanges() ? env.value_or("SHELL", "/bin/sh") : QString(); - proc->setCommand({filePath(shell), {}}); - proc->setTerminalMode(TerminalMode::On); - proc->setEnvironment(env); - proc->setWorkingDirectory(workingDir); - proc->start(); + proc.setCommand({filePath(shell), {}}); + proc.setTerminalMode(TerminalMode::Detached); + proc.setEnvironment(env); + proc.setWorkingDirectory(workingDir); + proc.start(); }); addDeviceAction({Tr::tr("Open Remote Shell"), [](const IDevice::Ptr &device, QWidget *) { device->openTerminal(Environment(), FilePath()); }}); + setQmlRunCommand(filePath("qml")); +} + +void LinuxDevice::_setOsType(Utils::OsType osType) +{ + qCDebug(linuxDeviceLog) << "Setting OS type to" << osType << "for" << displayName(); + IDevice::setOsType(osType); } LinuxDevice::~LinuxDevice() @@ -1057,38 +997,9 @@ IDeviceWidget *LinuxDevice::createWidget() return new Internal::GenericLinuxDeviceConfigurationWidget(sharedFromThis()); } -bool LinuxDevice::canAutoDetectPorts() const -{ - return true; -} - -PortsGatheringMethod LinuxDevice::portsGatheringMethod() const -{ - return { - [this](QAbstractSocket::NetworkLayerProtocol protocol) -> CommandLine { - // We might encounter the situation that protocol is given IPv6 - // but the consumer of the free port information decides to open - // an IPv4(only) port. As a result the next IPv6 scan will - // report the port again as open (in IPv6 namespace), while the - // same port in IPv4 namespace might still be blocked, and - // re-use of this port fails. - // GDBserver behaves exactly like this. - - Q_UNUSED(protocol) - - // /proc/net/tcp* covers /proc/net/tcp and /proc/net/tcp6 - return {filePath("sed"), - "-e 's/.*: [[:xdigit:]]*:\\([[:xdigit:]]\\{4\\}\\).*/\\1/g' /proc/net/tcp*", - CommandLine::Raw}; - }, - - &Port::parseFromSedOutput - }; -} - DeviceProcessList *LinuxDevice::createProcessListModel(QObject *parent) const { - return new LinuxDeviceProcessList(sharedFromThis(), parent); + return new ProcessList(sharedFromThis(), parent); } DeviceTester *LinuxDevice::createDeviceTester() const @@ -1127,7 +1038,7 @@ bool LinuxDevice::handlesFile(const FilePath &filePath) const ProcessInterface *LinuxDevice::createProcessInterface() const { - return new LinuxProcessInterface(this); + return new SshProcessInterface(sharedFromThis()); } LinuxDevicePrivate::LinuxDevicePrivate(LinuxDevice *parent) @@ -1142,7 +1053,6 @@ LinuxDevicePrivate::LinuxDevicePrivate(LinuxDevice *parent) LinuxDevicePrivate::~LinuxDevicePrivate() { - qDeleteAll(m_terminals); auto closeShell = [this] { m_shellThread.quit(); m_shellThread.wait(); @@ -1153,6 +1063,23 @@ LinuxDevicePrivate::~LinuxDevicePrivate() QMetaObject::invokeMethod(&m_shellThread, closeShell, Qt::BlockingQueuedConnection); } +void LinuxDevicePrivate::queryOsType(std::function runInShell) +{ + const RunResult result = runInShell({"uname", {"-s"}, OsType::OsTypeLinux}); + if (result.exitCode != 0) + q->_setOsType(OsTypeOtherUnix); + const QString osName = QString::fromUtf8(result.stdOut).trimmed(); + if (osName == "Darwin") + q->_setOsType(OsTypeMac); + if (osName == "Linux") + q->_setOsType(OsTypeLinux); +} + +void LinuxDevicePrivate::checkOsType() +{ + queryOsType([this](const CommandLine &cmd) { return runInShell(cmd); }); +} + // Call me with shell mutex locked bool LinuxDevicePrivate::setupShell() { @@ -1166,6 +1093,10 @@ bool LinuxDevicePrivate::setupShell() QMetaObject::invokeMethod(m_handler, [this, sshParameters] { return m_handler->start(sshParameters); }, Qt::BlockingQueuedConnection, &ok); + + if (ok) { + queryOsType([this](const CommandLine &cmd) { return m_handler->runInShell(cmd); }); + } return ok; } @@ -1208,13 +1139,9 @@ static FilePaths dirsToCreate(const FilesToTransfer &files) return sorted(std::move(dirs)); } -static QByteArray transferCommand(const FileTransferDirection direction, bool link) +static QByteArray transferCommand(bool link) { - if (direction == FileTransferDirection::Upload) - return link ? "ln -s" : "put"; - if (direction == FileTransferDirection::Download) - return "get"; - return {}; + return link ? "ln -s" : "put"; } class SshTransferInterface : public FileTransferInterface @@ -1222,21 +1149,20 @@ class SshTransferInterface : public FileTransferInterface Q_OBJECT protected: - SshTransferInterface(const FileTransferSetupData &setup, LinuxDevicePrivate *devicePrivate) + SshTransferInterface(const FileTransferSetupData &setup, const IDevice::ConstPtr &device) : FileTransferInterface(setup) - , m_device(devicePrivate->q->sharedFromThis()) - , m_devicePrivate(devicePrivate) + , m_device(device) , m_process(this) { - m_direction = m_setup.m_files.isEmpty() ? FileTransferDirection::Invalid - : m_setup.m_files.first().direction(); SshParameters::setupSshEnvironment(&m_process); - connect(&m_process, &QtcProcess::readyReadStandardOutput, this, [this] { + connect(&m_process, &Process::readyReadStandardOutput, this, [this] { emit progress(QString::fromLocal8Bit(m_process.readAllRawStandardOutput())); }); - connect(&m_process, &QtcProcess::done, this, &SshTransferInterface::doneImpl); + connect(&m_process, &Process::done, this, &SshTransferInterface::doneImpl); } + IDevice::ConstPtr device() const { return m_device; } + bool handleError() { ProcessResultData resultData = m_process.resultData(); @@ -1270,10 +1196,9 @@ protected: } QString host() const { return m_sshParameters.host(); } - QString userAtHost() const { return m_sshParameters.userName() + '@' + m_sshParameters.host(); } + QString userAtHost() const { return m_sshParameters.userAtHost(); } - QtcProcess &process() { return m_process; } - FileTransferDirection direction() const { return m_direction; } + Process &process() { return m_process; } private: virtual void startImpl() = 0; @@ -1282,7 +1207,11 @@ private: void start() final { m_sshParameters = displayless(m_device->sshParameters()); - if (SshSettings::connectionSharingEnabled()) { + const Id linkDeviceId = Id::fromSetting(m_device->extraData(Constants::LinkDevice)); + const auto linkDevice = DeviceManager::instance()->find(linkDeviceId); + const bool useConnectionSharing = !linkDevice && SshSettings::connectionSharingEnabled(); + + if (useConnectionSharing) { m_connecting = true; m_connectionHandle.reset(new SshConnectionHandle(m_device)); m_connectionHandle->setParent(this); @@ -1290,7 +1219,10 @@ private: this, &SshTransferInterface::handleConnected); connect(m_connectionHandle.get(), &SshConnectionHandle::disconnected, this, &SshTransferInterface::handleDisconnected); - m_devicePrivate->attachToSharedConnection(m_connectionHandle.get(), m_sshParameters); + auto linuxDevice = m_device.dynamicCast(); + QTC_ASSERT(linuxDevice, startFailed("No Linux device"); return); + linuxDevice->connectionAccess() + ->attachToSharedConnection(m_connectionHandle.get(), m_sshParameters); } else { startImpl(); } @@ -1318,28 +1250,33 @@ private: } IDevice::ConstPtr m_device; - LinuxDevicePrivate *m_devicePrivate = nullptr; SshParameters m_sshParameters; - FileTransferDirection m_direction = FileTransferDirection::Invalid; // helper // ssh shared connection related std::unique_ptr m_connectionHandle; QString m_socketFilePath; bool m_connecting = false; - QtcProcess m_process; + Process m_process; }; class SftpTransferImpl : public SshTransferInterface { public: - SftpTransferImpl(const FileTransferSetupData &setup, LinuxDevicePrivate *devicePrivate) - : SshTransferInterface(setup, devicePrivate) { } + SftpTransferImpl(const FileTransferSetupData &setup, const IDevice::ConstPtr &device) + : SshTransferInterface(setup, device) + {} private: void startImpl() final { - const FilePath sftpBinary = SshSettings::sftpFilePath(); + FilePath sftpBinary = SshSettings::sftpFilePath(); + + // This is a hack. We only test the last hop here. + const Id linkDeviceId = Id::fromSetting(device()->extraData(Constants::LinkDevice)); + if (const auto linkDevice = DeviceManager::instance()->find(linkDeviceId)) + sftpBinary = linkDevice->filePath(sftpBinary.fileName()).searchInPath(); + if (!sftpBinary.exists()) { startFailed(Tr::tr("\"sftp\" binary \"%1\" does not exist.") .arg(sftpBinary.toUserOutput())); @@ -1349,35 +1286,26 @@ private: QByteArray batchData; const FilePaths dirs = dirsToCreate(m_setup.m_files); - for (const FilePath &dir : dirs) { - if (direction() == FileTransferDirection::Upload) { - batchData += "-mkdir " + ProcessArgs::quoteArgUnix(dir.path()).toLocal8Bit() + '\n'; - } else if (direction() == FileTransferDirection::Download) { - if (!QDir::root().mkpath(dir.path())) { - startFailed(Tr::tr("Failed to create local directory \"%1\".") - .arg(QDir::toNativeSeparators(dir.path()))); - return; - } - } - } + for (const FilePath &dir : dirs) + batchData += "-mkdir " + ProcessArgs::quoteArgUnix(dir.path()).toLocal8Bit() + '\n'; for (const FileToTransfer &file : m_setup.m_files) { FilePath sourceFileOrLinkTarget = file.m_source; bool link = false; - if (direction() == FileTransferDirection::Upload) { - const QFileInfo fi(file.m_source.toFileInfo()); - if (fi.isSymLink()) { - link = true; - batchData += "-rm " + ProcessArgs::quoteArgUnix( - file.m_target.path()).toLocal8Bit() + '\n'; - // see QTBUG-5817. - sourceFileOrLinkTarget = - sourceFileOrLinkTarget.withNewPath(fi.dir().relativeFilePath(fi.symLinkTarget())); - } - } - batchData += transferCommand(direction(), link) + ' ' - + ProcessArgs::quoteArgUnix(sourceFileOrLinkTarget.path()).toLocal8Bit() + ' ' - + ProcessArgs::quoteArgUnix(file.m_target.path()).toLocal8Bit() + '\n'; + + const QFileInfo fi(file.m_source.toFileInfo()); + if (fi.isSymLink()) { + link = true; + batchData += "-rm " + ProcessArgs::quoteArgUnix( + file.m_target.path()).toLocal8Bit() + '\n'; + // see QTBUG-5817. + sourceFileOrLinkTarget = + sourceFileOrLinkTarget.withNewPath(fi.dir().relativeFilePath(fi.symLinkTarget())); + } + + batchData += transferCommand(link) + ' ' + + ProcessArgs::quoteArgUnix(sourceFileOrLinkTarget.path()).toLocal8Bit() + ' ' + + ProcessArgs::quoteArgUnix(file.m_target.path()).toLocal8Bit() + '\n'; } process().setCommand({sftpBinary, fullConnectionOptions() << "-b" << "-" << host()}); process().setWriteData(batchData); @@ -1390,8 +1318,8 @@ private: class RsyncTransferImpl : public SshTransferInterface { public: - RsyncTransferImpl(const FileTransferSetupData &setup, LinuxDevicePrivate *devicePrivate) - : SshTransferInterface(setup, devicePrivate) + RsyncTransferImpl(const FileTransferSetupData &setup, const IDevice::ConstPtr &device) + : SshTransferInterface(setup, device) { } private: @@ -1442,8 +1370,7 @@ private: if (!HostOsInfo::isWindowsHost()) return file; - QString localFilePath = direction() == FileTransferDirection::Upload - ? file.m_source.path() : file.m_target.path(); + QString localFilePath = file.m_source.path(); localFilePath = '/' + localFilePath.at(0) + localFilePath.mid(2); if (anyOf(options, [](const QString &opt) { return opt.contains("cygwin", Qt::CaseInsensitive); })) { @@ -1451,30 +1378,19 @@ private: } FileToTransfer fixedFile = file; - if (direction() == FileTransferDirection::Upload) - fixedFile.m_source = fixedFile.m_source.withNewPath(localFilePath); - else - fixedFile.m_target = fixedFile.m_target.withNewPath(localFilePath); + fixedFile.m_source = fixedFile.m_source.withNewPath(localFilePath); return fixedFile; } QPair fixPaths(const FileToTransfer &file, const QString &remoteHost) const { - FilePath localPath; - FilePath remotePath; - if (direction() == FileTransferDirection::Upload) { - localPath = file.m_source; - remotePath = file.m_target; - } else { - remotePath = file.m_source; - localPath = file.m_target; - } + FilePath localPath = file.m_source; + FilePath remotePath = file.m_target; const QString local = (localPath.isDir() && localPath.path().back() != '/') ? localPath.path() + '/' : localPath.path(); const QString remote = remoteHost + ':' + remotePath.path(); - return direction() == FileTransferDirection::Upload ? qMakePair(local, remote) - : qMakePair(remote, local); + return qMakePair(local, remote); } int m_currentIndex = 0; @@ -1483,7 +1399,7 @@ private: class GenericTransferImpl : public FileTransferInterface { public: - GenericTransferImpl(const FileTransferSetupData &setup, LinuxDevicePrivate *) + GenericTransferImpl(const FileTransferSetupData &setup) : FileTransferInterface(setup) {} @@ -1525,8 +1441,9 @@ private: .arg(m_currentIndex) .arg(m_fileCount) .arg(source.toUserOutput(), target.toUserOutput())); - if (!source.copyFile(target)) { - result.m_errorString = Tr::tr("Failed."); + expected_str copyResult = source.copyFile(target); + if (!copyResult) { + result.m_errorString = Tr::tr("Failed: %1").arg(copyResult.error()); result.m_exitCode = -1; // Random pick emit done(result); return; @@ -1545,14 +1462,24 @@ FileTransferInterface *LinuxDevice::createFileTransferInterface( const FileTransferSetupData &setup) const { switch (setup.m_method) { - case FileTransferMethod::Sftp: return new SftpTransferImpl(setup, d); - case FileTransferMethod::Rsync: return new RsyncTransferImpl(setup, d); - case FileTransferMethod::GenericCopy: return new GenericTransferImpl(setup, d); + case FileTransferMethod::Sftp: return new SftpTransferImpl(setup, sharedFromThis()); + case FileTransferMethod::Rsync: return new RsyncTransferImpl(setup, sharedFromThis()); + case FileTransferMethod::GenericCopy: return new GenericTransferImpl(setup); } QTC_CHECK(false); return {}; } +LinuxDevicePrivate *LinuxDevice::connectionAccess() const +{ + return d; +} + +void LinuxDevice::checkOsType() +{ + d->checkOsType(); +} + namespace Internal { // Factory @@ -1563,6 +1490,7 @@ LinuxDeviceFactory::LinuxDeviceFactory() setDisplayName(Tr::tr("Remote Linux Device")); setIcon(QIcon()); setConstructionFunction(&LinuxDevice::create); + setQuickCreationAllowed(true); setCreator([] { GenericLinuxDeviceConfigurationWizard wizard(Core::ICore::dialogParent()); if (wizard.exec() != QDialog::Accepted) diff --git a/src/plugins/remotelinux/linuxdevice.h b/src/plugins/remotelinux/linuxdevice.h index ac65d6e89c9..2094b426b4c 100644 --- a/src/plugins/remotelinux/linuxdevice.h +++ b/src/plugins/remotelinux/linuxdevice.h @@ -22,8 +22,6 @@ public: ProjectExplorer::IDeviceWidget *createWidget() override; - bool canAutoDetectPorts() const override; - ProjectExplorer::PortsGatheringMethod portsGatheringMethod() const override; bool canCreateProcessModel() const override { return true; } ProjectExplorer::DeviceProcessList *createProcessListModel(QObject *parent) const override; bool hasDeviceTester() const override { return true; } @@ -41,12 +39,16 @@ public: ProjectExplorer::FileTransferInterface *createFileTransferInterface( const ProjectExplorer::FileTransferSetupData &setup) const override; + class LinuxDevicePrivate *connectionAccess() const; + void checkOsType() override; + protected: LinuxDevice(); + void _setOsType(Utils::OsType osType); + class LinuxDevicePrivate *d; - friend class SshProcessInterface; - friend class SshTransferInterface; + friend class LinuxDevicePrivate; }; namespace Internal { diff --git a/src/plugins/remotelinux/linuxdevicetester.cpp b/src/plugins/remotelinux/linuxdevicetester.cpp index 3c34a638202..37aea361123 100644 --- a/src/plugins/remotelinux/linuxdevicetester.cpp +++ b/src/plugins/remotelinux/linuxdevicetester.cpp @@ -10,20 +10,21 @@ #include #include +#include #include #include -#include +#include using namespace ProjectExplorer; +using namespace Tasking; using namespace Utils; -using namespace Utils::Tasking; namespace RemoteLinux { namespace Internal { struct TransferStorage { - bool sftpWorks = false; + bool useGenericCopy = false; }; class GenericLinuxDeviceTesterPrivate @@ -33,7 +34,7 @@ public: QStringList commandsToTest() const; - TaskItem echoTask() const; + TaskItem echoTask(const QString &contents) const; TaskItem unameTask() const; TaskItem gathererTask() const; TaskItem transferTask(FileTransferMethod method, @@ -49,8 +50,6 @@ public: QList m_extraTests; }; -static const char s_echoContents[] = "Hello Remote World!"; - QStringList GenericLinuxDeviceTesterPrivate::commandsToTest() const { static const QStringList s_commandsToTest = {"base64", @@ -91,48 +90,49 @@ QStringList GenericLinuxDeviceTesterPrivate::commandsToTest() const return commands; } -TaskItem GenericLinuxDeviceTesterPrivate::echoTask() const +TaskItem GenericLinuxDeviceTesterPrivate::echoTask(const QString &contents) const { - const auto setup = [this](QtcProcess &process) { + const auto setup = [this, contents](Process &process) { emit q->progressMessage(Tr::tr("Sending echo to device...")); - process.setCommand({m_device->filePath("echo"), {s_echoContents}}); + process.setCommand({m_device->filePath("echo"), {contents}}); }; - const auto done = [this](const QtcProcess &process) { - const QString reply = process.cleanedStdOut().chopped(1); // Remove trailing '\n' - if (reply != s_echoContents) - emit q->errorMessage(Tr::tr("Device replied to echo with unexpected contents.") + '\n'); + const auto done = [this, contents](const Process &process) { + const QString reply = Utils::chopIfEndsWith(process.cleanedStdOut(), '\n'); + if (reply != contents) + emit q->errorMessage(Tr::tr("Device replied to echo with unexpected contents: \"%1\"") + .arg(reply) + '\n'); else emit q->progressMessage(Tr::tr("Device replied to echo with expected contents.") + '\n'); }; - const auto error = [this](const QtcProcess &process) { + const auto error = [this](const Process &process) { const QString stdErrOutput = process.cleanedStdErr(); if (!stdErrOutput.isEmpty()) emit q->errorMessage(Tr::tr("echo failed: %1").arg(stdErrOutput) + '\n'); else emit q->errorMessage(Tr::tr("echo failed.") + '\n'); }; - return Process(setup, done, error); + return ProcessTask(setup, done, error); } TaskItem GenericLinuxDeviceTesterPrivate::unameTask() const { - const auto setup = [this](QtcProcess &process) { + const auto setup = [this](Process &process) { emit q->progressMessage(Tr::tr("Checking kernel version...")); process.setCommand({m_device->filePath("uname"), {"-rsm"}}); }; - const auto done = [this](const QtcProcess &process) { + const auto done = [this](const Process &process) { emit q->progressMessage(process.cleanedStdOut()); }; - const auto error = [this](const QtcProcess &process) { + const auto error = [this](const Process &process) { const QString stdErrOutput = process.cleanedStdErr(); if (!stdErrOutput.isEmpty()) emit q->errorMessage(Tr::tr("uname failed: %1").arg(stdErrOutput) + '\n'); else emit q->errorMessage(Tr::tr("uname failed.") + '\n'); }; - return Tasking::Group { - optional, - Process(setup, done, error) + return Group { + finishAllAndDone, + ProcessTask(setup, done, error) }; } @@ -154,9 +154,14 @@ TaskItem GenericLinuxDeviceTesterPrivate::gathererTask() const } }; const auto error = [this](const DeviceUsedPortsGatherer &gatherer) { - emit q->errorMessage(Tr::tr("Error gathering ports: %1").arg(gatherer.errorString()) + '\n'); + emit q->errorMessage(Tr::tr("Error gathering ports: %1").arg(gatherer.errorString()) + '\n' + + Tr::tr("Some tools will not work out of the box.\n")); + }; + + return Group { + finishAllAndDone, + DeviceUsedPortsGathererTask(setup, done, error) }; - return PortGatherer(setup, done, error); } TaskItem GenericLinuxDeviceTesterPrivate::transferTask(FileTransferMethod method, @@ -173,8 +178,10 @@ TaskItem GenericLinuxDeviceTesterPrivate::transferTask(FileTransferMethod method emit q->progressMessage(Tr::tr("\"%1\" is functional.\n").arg(methodName)); if (method == FileTransferMethod::Rsync) m_device->setExtraData(Constants::SupportsRSync, true); + else if (method == FileTransferMethod::Sftp) + m_device->setExtraData(Constants::SupportsSftp, true); else - storage->sftpWorks = true; + storage->useGenericCopy = true; }; const auto error = [this, method, storage](const FileTransfer &transfer) { const QString methodName = FileTransfer::transferMethodName(method); @@ -189,28 +196,36 @@ TaskItem GenericLinuxDeviceTesterPrivate::transferTask(FileTransferMethod method .arg(methodName).arg(resultData.m_exitCode).arg(resultData.m_errorString); } emit q->errorMessage(error); - if (method == FileTransferMethod::Rsync) { + if (method == FileTransferMethod::Rsync) m_device->setExtraData(Constants::SupportsRSync, false); - if (!storage->sftpWorks) - return; + else if (method == FileTransferMethod::Sftp) + m_device->setExtraData(Constants::SupportsSftp, false); + + const QVariant supportsRSync = m_device->extraData(Constants::SupportsRSync); + const QVariant supportsSftp = m_device->extraData(Constants::SupportsSftp); + if (supportsRSync.isValid() && !supportsRSync.toBool() + && supportsSftp.isValid() && !supportsSftp.toBool()) { + const QString generic = FileTransfer::transferMethodName(FileTransferMethod::GenericCopy); const QString sftp = FileTransfer::transferMethodName(FileTransferMethod::Sftp); - const QString rsync = methodName; + const QString rsync = FileTransfer::transferMethodName(FileTransferMethod::Rsync); emit q->progressMessage(Tr::tr("\"%1\" will be used for deployment, because \"%2\" " - "is not available.\n").arg(sftp, rsync)); + "and \"%3\" are not available.\n") + .arg(generic, sftp, rsync)); } }; - return TransferTest(setup, done, error); + return FileTransferTestTask(setup, done, error); } TaskItem GenericLinuxDeviceTesterPrivate::transferTasks() const { TreeStorage storage; - return Tasking::Group { + return Group { continueOnDone, Storage(storage), + transferTask(FileTransferMethod::GenericCopy, storage), transferTask(FileTransferMethod::Sftp, storage), transferTask(FileTransferMethod::Rsync, storage), - OnGroupError([this] { emit q->errorMessage(Tr::tr("Deployment to this device will not " + onGroupError([this] { emit q->errorMessage(Tr::tr("Deployment to this device will not " "work out of the box.\n")); }) }; @@ -218,34 +233,34 @@ TaskItem GenericLinuxDeviceTesterPrivate::transferTasks() const TaskItem GenericLinuxDeviceTesterPrivate::commandTask(const QString &commandName) const { - const auto setup = [this, commandName](QtcProcess &process) { + const auto setup = [this, commandName](Process &process) { emit q->progressMessage(Tr::tr("%1...").arg(commandName)); CommandLine command{m_device->filePath("/bin/sh"), {"-c"}}; command.addArgs(QLatin1String("\"command -v %1\"").arg(commandName), CommandLine::Raw); process.setCommand(command); }; - const auto done = [this, commandName](const QtcProcess &) { + const auto done = [this, commandName](const Process &) { emit q->progressMessage(Tr::tr("%1 found.").arg(commandName)); }; - const auto error = [this, commandName](const QtcProcess &process) { + const auto error = [this, commandName](const Process &process) { const QString message = process.result() == ProcessResult::StartFailed ? Tr::tr("An error occurred while checking for %1.").arg(commandName) + '\n' + process.errorString() : Tr::tr("%1 not found.").arg(commandName); emit q->errorMessage(message); }; - return Process(setup, done, error); + return ProcessTask(setup, done, error); } TaskItem GenericLinuxDeviceTesterPrivate::commandTasks() const { QList tasks {continueOnError}; - tasks.append(OnGroupSetup([this] { + tasks.append(onGroupSetup([this] { emit q->progressMessage(Tr::tr("Checking if required commands are available...")); })); for (const QString &commandName : commandsToTest()) tasks.append(commandTask(commandName)); - return Tasking::Group {tasks}; + return Group {tasks}; } } // namespace Internal @@ -281,7 +296,8 @@ void GenericLinuxDeviceTester::testDevice(const IDevice::Ptr &deviceConfiguratio }; QList taskItems = { - d->echoTask(), + d->echoTask("Hello"), // No quoting necessary + d->echoTask("Hello Remote World!"), // Checks quoting, too. d->unameTask(), d->gathererTask(), d->transferTasks() @@ -289,8 +305,8 @@ void GenericLinuxDeviceTester::testDevice(const IDevice::Ptr &deviceConfiguratio if (!d->m_extraTests.isEmpty()) taskItems << Group { d->m_extraTests }; taskItems << d->commandTasks() - << OnGroupDone(std::bind(allFinished, TestSuccess)) - << OnGroupError(std::bind(allFinished, TestFailure)); + << onGroupDone(std::bind(allFinished, TestSuccess)) + << onGroupError(std::bind(allFinished, TestFailure)); d->m_taskTree.reset(new TaskTree(taskItems)); d->m_taskTree->start(); diff --git a/src/plugins/remotelinux/linuxdevicetester.h b/src/plugins/remotelinux/linuxdevicetester.h index 5c1210f9a14..3fda1ecb3a2 100644 --- a/src/plugins/remotelinux/linuxdevicetester.h +++ b/src/plugins/remotelinux/linuxdevicetester.h @@ -7,7 +7,7 @@ #include -namespace Utils::Tasking { class TaskItem; } +namespace Tasking { class TaskItem; } namespace RemoteLinux { @@ -22,7 +22,7 @@ public: ~GenericLinuxDeviceTester() override; void setExtraCommandsToTest(const QStringList &extraCommands); - void setExtraTests(const QList &extraTests); + void setExtraTests(const QList &extraTests); void testDevice(const ProjectExplorer::IDevice::Ptr &deviceConfiguration) override; void stopTest() override; diff --git a/src/plugins/remotelinux/linuxprocessinterface.h b/src/plugins/remotelinux/linuxprocessinterface.h index 949c5934200..e2a8e82fc8c 100644 --- a/src/plugins/remotelinux/linuxprocessinterface.h +++ b/src/plugins/remotelinux/linuxprocessinterface.h @@ -5,32 +5,35 @@ #include "remotelinux_export.h" -#include "sshprocessinterface.h" +#include "linuxdevice.h" + +#include namespace RemoteLinux { -class LinuxDevice; class SshProcessInterfacePrivate; -class REMOTELINUX_EXPORT LinuxProcessInterface : public SshProcessInterface +class REMOTELINUX_EXPORT SshProcessInterface : public Utils::ProcessInterface { public: - LinuxProcessInterface(const LinuxDevice *linuxDevice); - ~LinuxProcessInterface(); + explicit SshProcessInterface(const ProjectExplorer::IDevice::ConstPtr &device); + ~SshProcessInterface(); + +protected: + void emitStarted(qint64 processId); + void killIfRunning(); + qint64 processId() const; + bool runInShell(const Utils::CommandLine &command, const QByteArray &data = {}); private: - void handleSendControlSignal(Utils::ControlSignal controlSignal) override; + virtual void handleSendControlSignal(Utils::ControlSignal controlSignal); - void handleStarted(qint64 processId) final; - void handleDone(const Utils::ProcessResultData &resultData) final; - void handleReadyReadStandardOutput(const QByteArray &outputData) final; - void handleReadyReadStandardError(const QByteArray &errorData) final; + void start() final; + qint64 write(const QByteArray &data) final; + void sendControlSignal(Utils::ControlSignal controlSignal) final; - QString fullCommandLine(const Utils::CommandLine &commandLine) const final; - - QByteArray m_output; - QByteArray m_error; - bool m_pidParsed = false; + friend class SshProcessInterfacePrivate; + SshProcessInterfacePrivate *d = nullptr; }; } // namespace RemoteLinux diff --git a/src/plugins/remotelinux/makeinstallstep.cpp b/src/plugins/remotelinux/makeinstallstep.cpp index ea7b6364586..42f0467c947 100644 --- a/src/plugins/remotelinux/makeinstallstep.cpp +++ b/src/plugins/remotelinux/makeinstallstep.cpp @@ -20,8 +20,8 @@ #include #include +#include #include -#include #include #include @@ -70,10 +70,9 @@ MakeInstallStep::MakeInstallStep(BuildStepList *parent, Id id) : MakeStep(parent connect(makeAspect, &ExecutableAspect::changed, this, &MakeInstallStep::updateCommandFromAspect); - const auto installRootAspect = addAspect(); + const auto installRootAspect = addAspect(); installRootAspect->setId(InstallRootAspectId); installRootAspect->setSettingsKey(InstallRootAspectId); - installRootAspect->setDisplayStyle(StringAspect::PathChooserDisplay); installRootAspect->setExpectedKind(PathChooser::Directory); installRootAspect->setLabelText(Tr::tr("Install root:")); installRootAspect->setFilePath(rootPath); @@ -124,16 +123,6 @@ MakeInstallStep::MakeInstallStep(BuildStepList *parent, Id id) : MakeStep(parent }); } -Utils::Id MakeInstallStep::stepId() -{ - return Constants::MakeInstallStepId; -} - -QString MakeInstallStep::displayName() -{ - return Tr::tr("Install into temporary host directory"); -} - QWidget *MakeInstallStep::createConfigWidget() { // Note: this intentionally skips the MakeStep::createConfigWidget() level. @@ -145,7 +134,7 @@ bool MakeInstallStep::init() if (!MakeStep::init()) return false; - const FilePath rootDir = installRoot().onDevice(makeCommand()); + const FilePath rootDir = makeCommand().withNewPath(installRoot().path()); // FIXME: Needed? if (rootDir.isEmpty()) { emit addTask(BuildSystemTask(Task::Error, Tr::tr("You must provide an install root."))); return false; @@ -172,12 +161,10 @@ bool MakeInstallStep::init() const MakeInstallCommand cmd = buildSystem()->makeInstallCommand(rootDir); if (cmd.environment.hasChanges()) { Environment env = processParameters()->environment(); - for (auto it = cmd.environment.constBegin(); it != cmd.environment.constEnd(); ++it) { - if (cmd.environment.isEnabled(it)) { - const QString key = cmd.environment.key(it); - env.set(key, cmd.environment.expandedValueForKey(key)); - } - } + cmd.environment.forEachEntry([&](const QString &key, const QString &value, bool enabled) { + if (enabled) + env.set(key, cmd.environment.expandVariables(value)); + }); processParameters()->setEnvironment(env); } m_noInstallTarget = false; @@ -193,7 +180,7 @@ bool MakeInstallStep::init() void MakeInstallStep::finish(ProcessResult result) { if (isSuccess(result)) { - const FilePath rootDir = installRoot().onDevice(makeCommand()); + const FilePath rootDir = makeCommand().withNewPath(installRoot().path()); // FIXME: Needed? m_deploymentData = DeploymentData(); m_deploymentData.setLocalInstallRoot(rootDir); @@ -251,11 +238,8 @@ void MakeInstallStep::updateArgsFromAspect() void MakeInstallStep::updateFullCommandLine() { - // FIXME: Only executable? - static_cast(aspect(FullCommandLineAspectId))->setValue( - QDir::toNativeSeparators( - ProcessArgs::quoteArg(makeExecutable().toString())) - + ' ' + userArguments()); + CommandLine cmd{makeExecutable(), userArguments(), CommandLine::Raw}; + static_cast(aspect(FullCommandLineAspectId))->setValue(cmd.toUserOutput()); } void MakeInstallStep::updateFromCustomCommandLineAspect() @@ -263,7 +247,7 @@ void MakeInstallStep::updateFromCustomCommandLineAspect() const StringAspect * const aspect = customCommandLineAspect(); if (!aspect->isChecked()) return; - const QStringList tokens = ProcessArgs::splitArgs(aspect->value()); + const QStringList tokens = ProcessArgs::splitArgs(aspect->value(), HostOsInfo::hostOs()); setMakeCommand(tokens.isEmpty() ? FilePath() : FilePath::fromString(tokens.first())); setUserArguments(ProcessArgs::joinArgs(tokens.mid(1))); } @@ -283,4 +267,12 @@ bool MakeInstallStep::fromMap(const QVariantMap &map) return true; } -} // namespace RemoteLinux +// Factory + +MakeInstallStepFactory::MakeInstallStepFactory() +{ + registerStep(Constants::MakeInstallStepId); + setDisplayName(Tr::tr("Install into temporary host directory")); +} + +} // RemoteLinux diff --git a/src/plugins/remotelinux/makeinstallstep.h b/src/plugins/remotelinux/makeinstallstep.h index ec2fc3985a2..3be2d5df979 100644 --- a/src/plugins/remotelinux/makeinstallstep.h +++ b/src/plugins/remotelinux/makeinstallstep.h @@ -16,9 +16,6 @@ class REMOTELINUX_EXPORT MakeInstallStep : public ProjectExplorer::MakeStep public: MakeInstallStep(ProjectExplorer::BuildStepList *parent, Utils::Id id); - static Utils::Id stepId(); - static QString displayName(); - private: bool fromMap(const QVariantMap &map) override; QWidget *createConfigWidget() override; @@ -41,4 +38,11 @@ private: bool m_isCmakeProject = false; }; +class REMOTELINUX_EXPORT MakeInstallStepFactory + : public ProjectExplorer::BuildStepFactory +{ +public: + MakeInstallStepFactory(); +}; + } // namespace RemoteLinux diff --git a/src/plugins/remotelinux/publickeydeploymentdialog.cpp b/src/plugins/remotelinux/publickeydeploymentdialog.cpp index fcff8efd44d..c7b4a548103 100644 --- a/src/plugins/remotelinux/publickeydeploymentdialog.cpp +++ b/src/plugins/remotelinux/publickeydeploymentdialog.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include #include @@ -23,7 +23,7 @@ namespace Internal { class PublicKeyDeploymentDialogPrivate { public: - QtcProcess m_process; + Process m_process; bool m_done; }; } // namespace Internal; @@ -56,7 +56,7 @@ PublicKeyDeploymentDialog::PublicKeyDeploymentDialog(const IDevice::ConstPtr &de setValue(0); connect(this, &PublicKeyDeploymentDialog::canceled, this, [this] { d->m_done ? accept() : reject(); }); - connect(&d->m_process, &QtcProcess::done, this, [this] { + connect(&d->m_process, &Process::done, this, [this] { const bool succeeded = d->m_process.result() == ProcessResult::FinishedWithSuccess; QString finalMessage; if (!succeeded) { diff --git a/src/plugins/remotelinux/remotelinux.qbs b/src/plugins/remotelinux/remotelinux.qbs index 7881cab47de..03a6afad10f 100644 --- a/src/plugins/remotelinux/remotelinux.qbs +++ b/src/plugins/remotelinux/remotelinux.qbs @@ -19,8 +19,6 @@ Project { "deploymenttimeinfo.h", "customcommanddeploystep.cpp", "customcommanddeploystep.h", - "genericdirectuploadservice.cpp", - "genericdirectuploadservice.h", "genericdirectuploadstep.cpp", "genericdirectuploadstep.h", "genericlinuxdeviceconfigurationwidget.cpp", @@ -62,7 +60,6 @@ Project { "rsyncdeploystep.h", "sshkeycreationdialog.cpp", "sshkeycreationdialog.h", - "sshprocessinterface.h", "tarpackagecreationstep.cpp", "tarpackagecreationstep.h", "tarpackagedeploystep.cpp", @@ -70,9 +67,7 @@ Project { "images/embeddedtarget.png", ] - Group { - name: "Tests" - condition: qtc.testsEnabled + QtcTestFiles { files: [ "filesystemaccess_test.cpp", "filesystemaccess_test.h", diff --git a/src/plugins/remotelinux/remotelinux_constants.h b/src/plugins/remotelinux/remotelinux_constants.h index 31a97588281..9c428650c73 100644 --- a/src/plugins/remotelinux/remotelinux_constants.h +++ b/src/plugins/remotelinux/remotelinux_constants.h @@ -19,7 +19,10 @@ const char RsyncDeployStepId[] = "RemoteLinux.RsyncDeployStep"; const char CustomCommandDeployStepId[] = "RemoteLinux.GenericRemoteLinuxCustomCommandDeploymentStep"; const char KillAppStepId[] = "RemoteLinux.KillAppStep"; -const char SupportsRSync[] = "RemoteLinux.SupportsRSync"; +const char SupportsRSync[] = "RemoteLinux.SupportsRSync"; +const char SupportsSftp[] = "RemoteLinux.SupportsSftp"; +const char SourceProfile[] = "RemoteLinux.SourceProfile"; +const char LinkDevice[] = "RemoteLinux.LinkDevice"; const char RunConfigId[] = "RemoteLinuxRunConfiguration:"; const char CustomRunConfigId[] = "RemoteLinux.CustomRunConfig"; diff --git a/src/plugins/remotelinux/remotelinuxcustomrunconfiguration.cpp b/src/plugins/remotelinux/remotelinuxcustomrunconfiguration.cpp index 36e4b0bda24..fd6f75c0761 100644 --- a/src/plugins/remotelinux/remotelinuxcustomrunconfiguration.cpp +++ b/src/plugins/remotelinux/remotelinuxcustomrunconfiguration.cpp @@ -41,10 +41,9 @@ RemoteLinuxCustomRunConfiguration::RemoteLinuxCustomRunConfiguration(Target *tar exeAspect->setHistoryCompleter("RemoteLinux.CustomExecutable.History"); exeAspect->setExpectedKind(PathChooser::Any); - auto symbolsAspect = addAspect(); + auto symbolsAspect = addAspect(); symbolsAspect->setSettingsKey("RemoteLinux.CustomRunConfig.LocalExecutable"); symbolsAspect->setLabelText(Tr::tr("Local executable:")); - symbolsAspect->setDisplayStyle(SymbolFileAspect::PathChooserDisplay); addAspect(macroExpander()); addAspect(macroExpander(), envAspect); diff --git a/src/plugins/remotelinux/remotelinuxdeployconfiguration.cpp b/src/plugins/remotelinux/remotelinuxdeployconfiguration.cpp index 8cacd63ff91..94bfbfb981f 100644 --- a/src/plugins/remotelinux/remotelinuxdeployconfiguration.cpp +++ b/src/plugins/remotelinux/remotelinuxdeployconfiguration.cpp @@ -3,19 +3,38 @@ #include "remotelinuxdeployconfiguration.h" -#include "makeinstallstep.h" #include "remotelinux_constants.h" #include "remotelinuxtr.h" +#include #include #include #include +#include #include using namespace ProjectExplorer; namespace RemoteLinux::Internal { +FileTransferMethod defaultTransferMethod(Kit *kit) +{ + auto runDevice = DeviceKitAspect::device(kit); + auto buildDevice = BuildDeviceKitAspect::device(kit); + + if (runDevice != buildDevice) { + // FIXME: That's not the full truth, we need support from the build + // device, too. + if (runDevice && runDevice->extraData(Constants::SupportsRSync).toBool()) + return FileTransferMethod::Rsync; + } + + if (runDevice && runDevice->extraData(Constants::SupportsSftp).toBool()) + return FileTransferMethod::Sftp; + + return FileTransferMethod::GenericCopy; +} + RemoteLinuxDeployConfigurationFactory::RemoteLinuxDeployConfigurationFactory() { setConfigBaseId(RemoteLinux::Constants::DeployToGenericLinux); @@ -32,31 +51,23 @@ RemoteLinuxDeployConfigurationFactory::RemoteLinuxDeployConfigurationFactory() setPostRestore([needsMakeInstall](DeployConfiguration *dc, const QVariantMap &map) { // 4.9 -> 4.10. See QTCREATORBUG-22689. if (map.value("_checkMakeInstall").toBool() && needsMakeInstall(dc->target())) { - auto step = new MakeInstallStep(dc->stepList(), MakeInstallStep::stepId()); - dc->stepList()->insertStep(0, step); + dc->stepList()->insertStep(0, Constants::MakeInstallStepId); } }); addInitialStep(Constants::MakeInstallStepId, needsMakeInstall); addInitialStep(Constants::KillAppStepId); - // Todo: Check: Instead of having two different steps here, have one + // Todo: Check: Instead of having three different steps here, have one // and shift the logic into the implementation there? addInitialStep(Constants::RsyncDeployStepId, [](Target *target) { - auto runDevice = DeviceKitAspect::device(target->kit()); - auto buildDevice = BuildDeviceKitAspect::device(target->kit()); - if (runDevice == buildDevice) - return false; - // FIXME: That's not the full truth, we need support from the build - // device, too. - return runDevice && runDevice->extraData(Constants::SupportsRSync).toBool(); + return defaultTransferMethod(target->kit()) == FileTransferMethod::Rsync; }); addInitialStep(Constants::DirectUploadStepId, [](Target *target) { - auto runDevice = DeviceKitAspect::device(target->kit()); - auto buildDevice = BuildDeviceKitAspect::device(target->kit()); - if (runDevice == buildDevice) - return true; - return runDevice && !runDevice->extraData(Constants::SupportsRSync).toBool(); + return defaultTransferMethod(target->kit()) == FileTransferMethod::Sftp; + }); + addInitialStep(ProjectExplorer::Constants::COPY_FILE_STEP, [](Target *target) { + return defaultTransferMethod(target->kit()) == FileTransferMethod::GenericCopy; }); } diff --git a/src/plugins/remotelinux/remotelinuxenvironmentaspect.cpp b/src/plugins/remotelinux/remotelinuxenvironmentaspect.cpp index cc13e51273d..1be0624490f 100644 --- a/src/plugins/remotelinux/remotelinuxenvironmentaspect.cpp +++ b/src/plugins/remotelinux/remotelinuxenvironmentaspect.cpp @@ -40,7 +40,7 @@ public: connect(target, &Target::kitChanged, [aspect] { aspect->setRemoteEnvironment({}); }); - connect(fetchButton, &QPushButton::clicked, this, [this, aspect, target] { + connect(fetchButton, &QPushButton::clicked, this, [aspect, target] { if (IDevice::ConstPtr device = DeviceKitAspect::device(target->kit())) { DeviceFileAccess *access = device->fileAccess(); QTC_ASSERT(access, return); diff --git a/src/plugins/remotelinux/remotelinuxplugin.cpp b/src/plugins/remotelinux/remotelinuxplugin.cpp index 73a30b27e3f..b3af1fb873d 100644 --- a/src/plugins/remotelinux/remotelinuxplugin.cpp +++ b/src/plugins/remotelinux/remotelinuxplugin.cpp @@ -34,16 +34,14 @@ using namespace Utils; namespace RemoteLinux { namespace Internal { -template -class GenericDeployStepFactory : public ProjectExplorer::BuildStepFactory +template +class RemoteLinuxDeployStepFactory : public Factory { public: - GenericDeployStepFactory() + RemoteLinuxDeployStepFactory() { - registerStep(Step::stepId()); - setDisplayName(Step::displayName()); - setSupportedConfiguration(RemoteLinux::Constants::DeployToGenericLinux); - setSupportedStepList(ProjectExplorer::Constants::BUILDSTEPS_DEPLOY); + Factory::setSupportedConfiguration(RemoteLinux::Constants::DeployToGenericLinux); + Factory::setSupportedStepList(ProjectExplorer::Constants::BUILDSTEPS_DEPLOY); } }; @@ -56,11 +54,11 @@ public: RemoteLinuxDeployConfigurationFactory deployConfigurationFactory; TarPackageCreationStepFactory tarPackageCreationStepFactory; TarPackageDeployStepFactory tarPackageDeployStepFactory; - GenericDeployStepFactory genericDirectUploadStepFactory; - GenericDeployStepFactory rsyncDeployStepFactory; + RemoteLinuxDeployStepFactory genericDirectUploadStepFactory; + RemoteLinuxDeployStepFactory rsyncDeployStepFactory; CustomCommandDeployStepFactory customCommandDeployStepFactory; KillAppStepFactory killAppStepFactory; - GenericDeployStepFactory makeInstallStepFactory; + RemoteLinuxDeployStepFactory makeInstallStepFactory; RemoteLinuxRunWorkerFactory runWorkerFactory; RemoteLinuxDebugWorkerFactory debugWorkerFactory; RemoteLinuxQmlToolingWorkerFactory qmlToolingWorkerFactory; diff --git a/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp b/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp index 3e28f24af49..42e80aae41b 100644 --- a/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp +++ b/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -58,18 +59,17 @@ RemoteLinuxRunConfiguration::RemoteLinuxRunConfiguration(Target *target, Id id) envAspect, &EnvironmentAspect::environmentChanged); setUpdater([this, target, exeAspect, symbolsAspect, libAspect] { - BuildTargetInfo bti = buildTargetInfo(); - const FilePath localExecutable = bti.targetFilePath; - DeployableFile depFile = target->deploymentData().deployableForLocalFile(localExecutable); - - if (depFile.localFilePath().needsDevice()) // a full remote build - exeAspect->setExecutable(depFile.localFilePath()); - else - exeAspect->setExecutable(FilePath::fromString(depFile.remoteFilePath())); - symbolsAspect->setFilePath(localExecutable); - const IDeviceConstPtr buildDevice = BuildDeviceKitAspect::device(target->kit()); const IDeviceConstPtr runDevice = DeviceKitAspect::device(target->kit()); + QTC_ASSERT(buildDevice, return); + QTC_ASSERT(runDevice, return); + const BuildTargetInfo bti = buildTargetInfo(); + const FilePath localExecutable = bti.targetFilePath; + const DeploymentData deploymentData = target->deploymentData(); + const DeployableFile depFile = deploymentData.deployableForLocalFile(localExecutable); + + exeAspect->setExecutable(runDevice->filePath(depFile.remoteFilePath())); + symbolsAspect->setFilePath(localExecutable); libAspect->setEnabled(buildDevice == runDevice); }); diff --git a/src/plugins/remotelinux/remotelinuxsignaloperation.cpp b/src/plugins/remotelinux/remotelinuxsignaloperation.cpp index e073acde057..816e0e38696 100644 --- a/src/plugins/remotelinux/remotelinuxsignaloperation.cpp +++ b/src/plugins/remotelinux/remotelinuxsignaloperation.cpp @@ -7,8 +7,8 @@ #include #include +#include #include -#include using namespace ProjectExplorer; using namespace Utils; @@ -30,8 +30,8 @@ static QString signalProcessGroupByPidCommandLine(qint64 pid, int signal) void RemoteLinuxSignalOperation::run(const QString &command) { QTC_ASSERT(!m_process, return); - m_process.reset(new QtcProcess); - connect(m_process.get(), &QtcProcess::done, this, &RemoteLinuxSignalOperation::runnerDone); + m_process.reset(new Process); + connect(m_process.get(), &Process::done, this, &RemoteLinuxSignalOperation::runnerDone); m_process->setCommand({m_device->filePath("/bin/sh"), {"-c", command}}); m_process->start(); diff --git a/src/plugins/remotelinux/remotelinuxsignaloperation.h b/src/plugins/remotelinux/remotelinuxsignaloperation.h index d3e840e0d3d..00967c9414c 100644 --- a/src/plugins/remotelinux/remotelinuxsignaloperation.h +++ b/src/plugins/remotelinux/remotelinuxsignaloperation.h @@ -32,7 +32,7 @@ private: void run(const QString &command); const ProjectExplorer::IDeviceConstPtr m_device; - std::unique_ptr m_process; + std::unique_ptr m_process; friend class LinuxDevice; }; diff --git a/src/plugins/remotelinux/rsyncdeploystep.cpp b/src/plugins/remotelinux/rsyncdeploystep.cpp index b20eaf90499..84eb47b8cc1 100644 --- a/src/plugins/remotelinux/rsyncdeploystep.cpp +++ b/src/plugins/remotelinux/rsyncdeploystep.cpp @@ -16,21 +16,21 @@ #include #include +#include #include -#include using namespace ProjectExplorer; +using namespace Tasking; using namespace Utils; -using namespace Utils::Tasking; namespace RemoteLinux { -class RsyncDeployService : public AbstractRemoteLinuxDeployService +// RsyncDeployStep + +class RsyncDeployStep : public AbstractRemoteLinuxDeployStep { public: - void setDeployableFiles(const QList &files); - void setIgnoreMissingFiles(bool ignore) { m_ignoreMissingFiles = ignore; } - void setFlags(const QString &flags) { m_flags = flags; } + RsyncDeployStep(BuildStepList *bsl, Id id); private: bool isDeploymentNecessary() const final; @@ -43,84 +43,9 @@ private: QString m_flags; }; -void RsyncDeployService::setDeployableFiles(const QList &files) -{ - m_files.clear(); - for (const DeployableFile &f : files) - m_files.append({f.localFilePath(), deviceConfiguration()->filePath(f.remoteFilePath())}); -} - -bool RsyncDeployService::isDeploymentNecessary() const -{ - if (m_ignoreMissingFiles) - Utils::erase(m_files, [](const FileToTransfer &file) { return !file.m_source.exists(); }); - return !m_files.empty(); -} - -TaskItem RsyncDeployService::mkdirTask() -{ - const auto setupHandler = [this](QtcProcess &process) { - QStringList remoteDirs; - for (const FileToTransfer &file : std::as_const(m_files)) - remoteDirs << file.m_target.parentDir().path(); - remoteDirs.sort(); - remoteDirs.removeDuplicates(); - process.setCommand({deviceConfiguration()->filePath("mkdir"), - QStringList("-p") + remoteDirs}); - connect(&process, &QtcProcess::readyReadStandardError, this, [this, proc = &process] { - emit stdErrData(QString::fromLocal8Bit(proc->readAllRawStandardError())); - }); - }; - const auto errorHandler = [this](const QtcProcess &process) { - QString finalMessage = process.errorString(); - const QString stdErr = process.cleanedStdErr(); - if (!stdErr.isEmpty()) { - if (!finalMessage.isEmpty()) - finalMessage += '\n'; - finalMessage += stdErr; - } - emit errorMessage(Tr::tr("Deploy via rsync: failed to create remote directories:") - + '\n' + finalMessage); - }; - return Process(setupHandler, {}, errorHandler); -} - -TaskItem RsyncDeployService::transferTask() -{ - const auto setupHandler = [this](FileTransfer &transfer) { - transfer.setTransferMethod(FileTransferMethod::Rsync); - transfer.setRsyncFlags(m_flags); - transfer.setFilesToTransfer(m_files); - connect(&transfer, &FileTransfer::progress, - this, &AbstractRemoteLinuxDeployService::stdOutData); - }; - const auto errorHandler = [this](const FileTransfer &transfer) { - const ProcessResultData result = transfer.resultData(); - if (result.m_error == QProcess::FailedToStart) { - emit errorMessage(Tr::tr("rsync failed to start: %1").arg(result.m_errorString)); - } else if (result.m_exitStatus == QProcess::CrashExit) { - emit errorMessage(Tr::tr("rsync crashed.")); - } else if (result.m_exitCode != 0) { - emit errorMessage(Tr::tr("rsync failed with exit code %1.").arg(result.m_exitCode) - + "\n" + result.m_errorString); - } - }; - return Transfer(setupHandler, {}, errorHandler); -} - -Group RsyncDeployService::deployRecipe() -{ - return Group { mkdirTask(), transferTask() }; -} - -// RsyncDeployStep - RsyncDeployStep::RsyncDeployStep(BuildStepList *bsl, Id id) : AbstractRemoteLinuxDeployStep(bsl, id) { - auto service = new RsyncDeployService; - setDeployService(service); - auto flags = addAspect(); flags->setDisplayStyle(StringAspect::LineEditDisplay); flags->setSettingsKey("RemoteLinux.RsyncDeployStep.Flags"); @@ -133,33 +58,95 @@ RsyncDeployStep::RsyncDeployStep(BuildStepList *bsl, Id id) BoolAspect::LabelPlacement::InExtraLabel); ignoreMissingFiles->setValue(false); - setInternalInitializer([this, service, flags, ignoreMissingFiles] { + setInternalInitializer([this, ignoreMissingFiles, flags] { if (BuildDeviceKitAspect::device(kit()) == DeviceKitAspect::device(kit())) { // rsync transfer on the same device currently not implemented // and typically not wanted. return CheckResult::failure( Tr::tr("rsync is only supported for transfers between different devices.")); } - service->setIgnoreMissingFiles(ignoreMissingFiles->value()); - service->setFlags(flags->value()); - return service->isDeploymentPossible(); + m_ignoreMissingFiles = ignoreMissingFiles->value(); + m_flags = flags->value(); + return isDeploymentPossible(); }); - setRunPreparer([this, service] { - service->setDeployableFiles(target()->deploymentData().allFiles()); + setRunPreparer([this] { + const QList files = target()->deploymentData().allFiles(); + m_files.clear(); + for (const DeployableFile &f : files) + m_files.append({f.localFilePath(), deviceConfiguration()->filePath(f.remoteFilePath())}); }); } -RsyncDeployStep::~RsyncDeployStep() = default; - -Utils::Id RsyncDeployStep::stepId() +bool RsyncDeployStep::isDeploymentNecessary() const { - return Constants::RsyncDeployStepId; + if (m_ignoreMissingFiles) + Utils::erase(m_files, [](const FileToTransfer &file) { return !file.m_source.exists(); }); + return !m_files.empty(); } -QString RsyncDeployStep::displayName() +TaskItem RsyncDeployStep::mkdirTask() { - return Tr::tr("Deploy files via rsync"); + const auto setupHandler = [this](Process &process) { + QStringList remoteDirs; + for (const FileToTransfer &file : std::as_const(m_files)) + remoteDirs << file.m_target.parentDir().path(); + remoteDirs.sort(); + remoteDirs.removeDuplicates(); + process.setCommand({deviceConfiguration()->filePath("mkdir"), + QStringList("-p") + remoteDirs}); + connect(&process, &Process::readyReadStandardError, this, [this, proc = &process] { + handleStdErrData(QString::fromLocal8Bit(proc->readAllRawStandardError())); + }); + }; + const auto errorHandler = [this](const Process &process) { + QString finalMessage = process.errorString(); + const QString stdErr = process.cleanedStdErr(); + if (!stdErr.isEmpty()) { + if (!finalMessage.isEmpty()) + finalMessage += '\n'; + finalMessage += stdErr; + } + addErrorMessage(Tr::tr("Deploy via rsync: failed to create remote directories:") + + '\n' + finalMessage); + }; + return ProcessTask(setupHandler, {}, errorHandler); +} + +TaskItem RsyncDeployStep::transferTask() +{ + const auto setupHandler = [this](FileTransfer &transfer) { + transfer.setTransferMethod(FileTransferMethod::Rsync); + transfer.setRsyncFlags(m_flags); + transfer.setFilesToTransfer(m_files); + connect(&transfer, &FileTransfer::progress, + this, &AbstractRemoteLinuxDeployStep::handleStdOutData); + }; + const auto errorHandler = [this](const FileTransfer &transfer) { + const ProcessResultData result = transfer.resultData(); + if (result.m_error == QProcess::FailedToStart) { + addErrorMessage(Tr::tr("rsync failed to start: %1").arg(result.m_errorString)); + } else if (result.m_exitStatus == QProcess::CrashExit) { + addErrorMessage(Tr::tr("rsync crashed.")); + } else if (result.m_exitCode != 0) { + addErrorMessage(Tr::tr("rsync failed with exit code %1.").arg(result.m_exitCode) + + "\n" + result.m_errorString); + } + }; + return FileTransferTask(setupHandler, {}, errorHandler); +} + +Group RsyncDeployStep::deployRecipe() +{ + return Group { mkdirTask(), transferTask() }; +} + +// Factory + +RsyncDeployStepFactory::RsyncDeployStepFactory() +{ + registerStep(Constants::RsyncDeployStepId); + setDisplayName(Tr::tr("Deploy files via rsync")); } } // RemoteLinux diff --git a/src/plugins/remotelinux/rsyncdeploystep.h b/src/plugins/remotelinux/rsyncdeploystep.h index a0f87905311..7450d84fcb6 100644 --- a/src/plugins/remotelinux/rsyncdeploystep.h +++ b/src/plugins/remotelinux/rsyncdeploystep.h @@ -5,18 +5,15 @@ #include "remotelinux_export.h" -#include "abstractremotelinuxdeploystep.h" +#include namespace RemoteLinux { -class REMOTELINUX_EXPORT RsyncDeployStep : public AbstractRemoteLinuxDeployStep +class REMOTELINUX_EXPORT RsyncDeployStepFactory + : public ProjectExplorer::BuildStepFactory { public: - RsyncDeployStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id); - ~RsyncDeployStep() override; - - static Utils::Id stepId(); - static QString displayName(); + RsyncDeployStepFactory(); }; } // namespace RemoteLinux diff --git a/src/plugins/remotelinux/sshkeycreationdialog.cpp b/src/plugins/remotelinux/sshkeycreationdialog.cpp index e0128d2a2ad..441edab8898 100644 --- a/src/plugins/remotelinux/sshkeycreationdialog.cpp +++ b/src/plugins/remotelinux/sshkeycreationdialog.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include @@ -109,7 +109,7 @@ void SshKeyCreationDialog::generateKeys() } const QString keyTypeString = QLatin1String(m_rsa->isChecked() ? "rsa": "ecdsa"); QApplication::setOverrideCursor(Qt::BusyCursor); - QtcProcess keygen; + Process keygen; const QStringList args{"-t", keyTypeString, "-b", m_comboBox->currentText(), "-N", QString(), "-f", privateKeyFilePath().path()}; QString errorMsg; diff --git a/src/plugins/remotelinux/sshprocessinterface.h b/src/plugins/remotelinux/sshprocessinterface.h deleted file mode 100644 index 9d67d6a4e67..00000000000 --- a/src/plugins/remotelinux/sshprocessinterface.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "remotelinux_export.h" - -#include - -namespace RemoteLinux { - -class LinuxDevice; -class SshProcessInterfacePrivate; - -class REMOTELINUX_EXPORT SshProcessInterface : public Utils::ProcessInterface -{ -public: - SshProcessInterface(const LinuxDevice *linuxDevice); - ~SshProcessInterface(); - -protected: - void emitStarted(qint64 processId); - // To be called from leaf destructor. - // Can't call it from SshProcessInterface destructor as it calls virtual method. - void killIfRunning(); - qint64 processId() const; - bool runInShell(const Utils::CommandLine &command, const QByteArray &data = {}); - -private: - virtual void handleStarted(qint64 processId); - virtual void handleDone(const Utils::ProcessResultData &resultData); - virtual void handleReadyReadStandardOutput(const QByteArray &outputData); - virtual void handleReadyReadStandardError(const QByteArray &errorData); - virtual void handleSendControlSignal(Utils::ControlSignal controlSignal) = 0; - - virtual QString fullCommandLine(const Utils::CommandLine &commandLine) const = 0; - - void start() final; - qint64 write(const QByteArray &data) final; - void sendControlSignal(Utils::ControlSignal controlSignal) final; - - friend class SshProcessInterfacePrivate; - SshProcessInterfacePrivate *d = nullptr; -}; - -} // namespace RemoteLinux diff --git a/src/plugins/remotelinux/tarpackagecreationstep.cpp b/src/plugins/remotelinux/tarpackagecreationstep.cpp index 0d53d36ff55..7c577812da1 100644 --- a/src/plugins/remotelinux/tarpackagecreationstep.cpp +++ b/src/plugins/remotelinux/tarpackagecreationstep.cpp @@ -13,8 +13,8 @@ #include #include +#include #include -#include #include #include @@ -74,9 +74,9 @@ private: bool isPackagingNeeded() const; void deployFinished(bool success); void addNeededDeploymentFiles(const DeployableFile &deployable, const Kit *kit); - void doPackage(QFutureInterface &fi, const Utils::FilePath &tarFilePath, + void doPackage(QPromise &promise, const Utils::FilePath &tarFilePath, bool ignoreMissingFiles); - bool appendFile(QFutureInterface &fi, QFile &tarFile, const QFileInfo &fileInfo, + bool appendFile(QPromise &promise, QFile &tarFile, const QFileInfo &fileInfo, const QString &remoteFilePath, const Utils::FilePath &tarFilePath, bool ignoreMissingFiles); @@ -94,7 +94,6 @@ private: TarPackageCreationStep::TarPackageCreationStep(BuildStepList *bsl, Id id) : BuildStep(bsl, id) { - m_synchronizer.setCancelOnWait(true); connect(target(), &Target::deploymentDataChanged, this, [this] { m_deploymentDataModified = true; }); @@ -167,7 +166,7 @@ void TarPackageCreationStep::doRun() connect(BuildManager::instance(), &BuildManager::buildQueueFinished, this, &TarPackageCreationStep::deployFinished); }); - auto future = Utils::runAsync(&TarPackageCreationStep::doPackage, this, + auto future = Utils::asyncRun(&TarPackageCreationStep::doPackage, this, m_tarFilePath, m_ignoreMissingFilesAspect->value()); watcher->setFuture(future); m_synchronizer.addFuture(future); @@ -271,7 +270,7 @@ void TarPackageCreationStep::addNeededDeploymentFiles( } } -void TarPackageCreationStep::doPackage(QFutureInterface &fi, const FilePath &tarFilePath, +void TarPackageCreationStep::doPackage(QPromise &promise, const FilePath &tarFilePath, bool ignoreMissingFiles) { // TODO: Optimization: Only package changed files @@ -280,7 +279,7 @@ void TarPackageCreationStep::doPackage(QFutureInterface &fi, const FilePat if (!tarFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { raiseError(Tr::tr("Error: tar file %1 cannot be opened (%2).") .arg(tarFilePath.toUserOutput(), tarFile.errorString())); - fi.reportResult(false); + promise.addResult(false); return; } @@ -291,10 +290,10 @@ void TarPackageCreationStep::doPackage(QFutureInterface &fi, const FilePat continue; } QFileInfo fileInfo = d.localFilePath().toFileInfo(); - if (!appendFile(fi, tarFile, fileInfo, + if (!appendFile(promise, tarFile, fileInfo, d.remoteDirectory() + QLatin1Char('/') + fileInfo.fileName(), tarFilePath, ignoreMissingFiles)) { - fi.reportResult(false); + promise.addResult(false); return; } } @@ -303,10 +302,10 @@ void TarPackageCreationStep::doPackage(QFutureInterface &fi, const FilePat if (tarFile.write(eofIndicator) != eofIndicator.length()) { raiseError(Tr::tr("Error writing tar file \"%1\": %2.") .arg(QDir::toNativeSeparators(tarFile.fileName()), tarFile.errorString())); - fi.reportResult(false); + promise.addResult(false); return; } - fi.reportResult(true); + promise.addResult(true); } static bool setFilePath(TarFileHeader &header, const QByteArray &filePath) @@ -388,7 +387,7 @@ static bool writeHeader(QFile &tarFile, const QFileInfo &fileInfo, const QString return true; } -bool TarPackageCreationStep::appendFile(QFutureInterface &fi, +bool TarPackageCreationStep::appendFile(QPromise &promise, QFile &tarFile, const QFileInfo &fileInfo, const QString &remoteFilePath, @@ -406,7 +405,7 @@ bool TarPackageCreationStep::appendFile(QFutureInterface &fi, for (const QString &fileName : files) { const QString thisLocalFilePath = dir.path() + QLatin1Char('/') + fileName; const QString thisRemoteFilePath = remoteFilePath + QLatin1Char('/') + fileName; - if (!appendFile(fi, tarFile, QFileInfo(thisLocalFilePath), thisRemoteFilePath, + if (!appendFile(promise, tarFile, QFileInfo(thisLocalFilePath), thisRemoteFilePath, tarFilePath, ignoreMissingFiles)) { return false; } @@ -437,7 +436,7 @@ bool TarPackageCreationStep::appendFile(QFutureInterface &fi, while (!file.atEnd() && file.error() == QFile::NoError && tarFile.error() == QFile::NoError) { const QByteArray data = file.read(chunkSize); tarFile.write(data); - if (fi.isCanceled()) + if (promise.isCanceled()) return false; } if (file.error() != QFile::NoError) { diff --git a/src/plugins/remotelinux/tarpackagedeploystep.cpp b/src/plugins/remotelinux/tarpackagedeploystep.cpp index ee276666ac8..6b50ca4dced 100644 --- a/src/plugins/remotelinux/tarpackagedeploystep.cpp +++ b/src/plugins/remotelinux/tarpackagedeploystep.cpp @@ -12,95 +12,15 @@ #include #include +#include #include -#include using namespace ProjectExplorer; +using namespace Tasking; using namespace Utils; -using namespace Utils::Tasking; namespace RemoteLinux::Internal { -class TarPackageDeployService : public AbstractRemoteLinuxDeployService -{ -public: - void setPackageFilePath(const FilePath &filePath); - -private: - QString remoteFilePath() const; - bool isDeploymentNecessary() const final; - Group deployRecipe() final; - TaskItem uploadTask(); - TaskItem installTask(); - - FilePath m_packageFilePath; -}; - -void TarPackageDeployService::setPackageFilePath(const FilePath &filePath) -{ - m_packageFilePath = filePath; -} - -QString TarPackageDeployService::remoteFilePath() const -{ - return QLatin1String("/tmp/") + m_packageFilePath.fileName(); -} - -bool TarPackageDeployService::isDeploymentNecessary() const -{ - return hasLocalFileChanged(DeployableFile(m_packageFilePath, {})); -} - -TaskItem TarPackageDeployService::uploadTask() -{ - const auto setupHandler = [this](FileTransfer &transfer) { - const FilesToTransfer files {{m_packageFilePath, - deviceConfiguration()->filePath(remoteFilePath())}}; - transfer.setFilesToTransfer(files); - connect(&transfer, &FileTransfer::progress, - this, &TarPackageDeployService::progressMessage); - emit progressMessage(Tr::tr("Uploading package to device...")); - }; - const auto doneHandler = [this](const FileTransfer &) { - emit progressMessage(Tr::tr("Successfully uploaded package file.")); - }; - const auto errorHandler = [this](const FileTransfer &transfer) { - const ProcessResultData result = transfer.resultData(); - emit errorMessage(result.m_errorString); - }; - return Transfer(setupHandler, doneHandler, errorHandler); -} - -TaskItem TarPackageDeployService::installTask() -{ - const auto setupHandler = [this](QtcProcess &process) { - const QString cmdLine = QLatin1String("cd / && tar xvf ") + remoteFilePath() - + " && (rm " + remoteFilePath() + " || :)"; - process.setCommand({deviceConfiguration()->filePath("/bin/sh"), {"-c", cmdLine}}); - QtcProcess *proc = &process; - connect(proc, &QtcProcess::readyReadStandardOutput, this, [this, proc] { - emit stdOutData(proc->readAllStandardOutput()); - }); - connect(proc, &QtcProcess::readyReadStandardError, this, [this, proc] { - emit stdErrData(proc->readAllStandardError()); - }); - emit progressMessage(Tr::tr("Installing package to device...")); - }; - const auto doneHandler = [this](const QtcProcess &) { - saveDeploymentTimeStamp(DeployableFile(m_packageFilePath, {}), {}); - emit progressMessage(Tr::tr("Successfully installed package file.")); - }; - const auto errorHandler = [this](const QtcProcess &process) { - emit errorMessage(Tr::tr("Installing package failed.") + process.errorString()); - }; - return Process(setupHandler, doneHandler, errorHandler); -} - -Group TarPackageDeployService::deployRecipe() -{ - return Group { uploadTask(), installTask() }; -} - // TarPackageDeployStep class TarPackageDeployStep : public AbstractRemoteLinuxDeployStep @@ -109,12 +29,9 @@ public: TarPackageDeployStep(BuildStepList *bsl, Id id) : AbstractRemoteLinuxDeployStep(bsl, id) { - auto service = new TarPackageDeployService; - setDeployService(service); - setWidgetExpandedByDefault(false); - setInternalInitializer([this, service] { + setInternalInitializer([this] { const BuildStep *tarCreationStep = nullptr; for (BuildStep *step : deployConfiguration()->stepList()->steps()) { @@ -128,14 +45,81 @@ public: if (!tarCreationStep) return CheckResult::failure(Tr::tr("No tarball creation step found.")); - const FilePath tarFile = - FilePath::fromVariant(tarCreationStep->data(Constants::TarPackageFilePathId)); - service->setPackageFilePath(tarFile); - return service->isDeploymentPossible(); + m_packageFilePath = + FilePath::fromVariant(tarCreationStep->data(Constants::TarPackageFilePathId)); + return isDeploymentPossible(); }); } + +private: + QString remoteFilePath() const; + bool isDeploymentNecessary() const final; + Group deployRecipe() final; + TaskItem uploadTask(); + TaskItem installTask(); + + FilePath m_packageFilePath; }; +QString TarPackageDeployStep::remoteFilePath() const +{ + return QLatin1String("/tmp/") + m_packageFilePath.fileName(); +} + +bool TarPackageDeployStep::isDeploymentNecessary() const +{ + return hasLocalFileChanged(DeployableFile(m_packageFilePath, {})); +} + +TaskItem TarPackageDeployStep::uploadTask() +{ + const auto setupHandler = [this](FileTransfer &transfer) { + const FilesToTransfer files {{m_packageFilePath, + deviceConfiguration()->filePath(remoteFilePath())}}; + transfer.setFilesToTransfer(files); + connect(&transfer, &FileTransfer::progress, this, &TarPackageDeployStep::addProgressMessage); + addProgressMessage(Tr::tr("Uploading package to device...")); + }; + const auto doneHandler = [this](const FileTransfer &) { + addProgressMessage(Tr::tr("Successfully uploaded package file.")); + }; + const auto errorHandler = [this](const FileTransfer &transfer) { + const ProcessResultData result = transfer.resultData(); + addErrorMessage(result.m_errorString); + }; + return FileTransferTask(setupHandler, doneHandler, errorHandler); +} + +TaskItem TarPackageDeployStep::installTask() +{ + const auto setupHandler = [this](Process &process) { + const QString cmdLine = QLatin1String("cd / && tar xvf ") + remoteFilePath() + + " && (rm " + remoteFilePath() + " || :)"; + process.setCommand({deviceConfiguration()->filePath("/bin/sh"), {"-c", cmdLine}}); + Process *proc = &process; + connect(proc, &Process::readyReadStandardOutput, this, [this, proc] { + handleStdOutData(proc->readAllStandardOutput()); + }); + connect(proc, &Process::readyReadStandardError, this, [this, proc] { + handleStdErrData(proc->readAllStandardError()); + }); + addProgressMessage(Tr::tr("Installing package to device...")); + }; + const auto doneHandler = [this](const Process &) { + saveDeploymentTimeStamp(DeployableFile(m_packageFilePath, {}), {}); + addProgressMessage(Tr::tr("Successfully installed package file.")); + }; + const auto errorHandler = [this](const Process &process) { + addErrorMessage(Tr::tr("Installing package failed.") + process.errorString()); + }; + return ProcessTask(setupHandler, doneHandler, errorHandler); +} + +Group TarPackageDeployStep::deployRecipe() +{ + return Group { uploadTask(), installTask() }; +} + // TarPackageDeployStepFactory diff --git a/src/plugins/resourceeditor/qrceditor/qrceditor.cpp b/src/plugins/resourceeditor/qrceditor/qrceditor.cpp index 86679c52b74..e08f52340fd 100644 --- a/src/plugins/resourceeditor/qrceditor/qrceditor.cpp +++ b/src/plugins/resourceeditor/qrceditor/qrceditor.cpp @@ -43,7 +43,7 @@ QrcEditor::QrcEditor(RelativeResourceModel *model, QWidget *parent) m_languageLabel = new QLabel(Tr::tr("Language:")); m_languageText = new QLineEdit; - using namespace Utils::Layouting; + using namespace Layouting; Column { Row { addPrefixButton, diff --git a/src/plugins/resourceeditor/resourcenode.cpp b/src/plugins/resourceeditor/resourcenode.cpp index fa0e3b5e4a2..d402e3ed89e 100644 --- a/src/plugins/resourceeditor/resourcenode.cpp +++ b/src/plugins/resourceeditor/resourcenode.cpp @@ -268,9 +268,7 @@ static void compressTree(FolderNode *n) compressable->compress(); return; } - const QList childFolders = n->folderNodes(); - for (FolderNode * const c : childFolders) - compressTree(c); + n->forEachFolderNode([](FolderNode *c) { compressTree(c); }); } void ResourceTopLevelNode::addInternalNodes() diff --git a/src/plugins/scxmleditor/CMakeLists.txt b/src/plugins/scxmleditor/CMakeLists.txt index fef49a90827..358c407233c 100644 --- a/src/plugins/scxmleditor/CMakeLists.txt +++ b/src/plugins/scxmleditor/CMakeLists.txt @@ -42,6 +42,7 @@ add_qtc_plugin(ScxmlEditor plugin_interface/baseitem.cpp plugin_interface/baseitem.h plugin_interface/connectableitem.cpp plugin_interface/connectableitem.h plugin_interface/cornergrabberitem.cpp plugin_interface/cornergrabberitem.h + plugin_interface/eventitem.cpp plugin_interface/eventitem.h plugin_interface/finalstateitem.cpp plugin_interface/finalstateitem.h plugin_interface/genericscxmlplugin.cpp plugin_interface/genericscxmlplugin.h plugin_interface/graphicsitemprovider.h diff --git a/src/plugins/scxmleditor/common/colorpicker.cpp b/src/plugins/scxmleditor/common/colorpicker.cpp index 04e415224bf..88f4d6d22a7 100644 --- a/src/plugins/scxmleditor/common/colorpicker.cpp +++ b/src/plugins/scxmleditor/common/colorpicker.cpp @@ -34,8 +34,8 @@ ColorPicker::ColorPicker(const QString &key, QWidget *parent) m_lastUsedColorContainer = new QHBoxLayout(lastUsedColorContainer); m_lastUsedColorContainer->setContentsMargins(0, 0, 0, 0); - using namespace Utils::Layouting; - Grid colorGrid; + using namespace Layouting; + Grid colorGrid{noMargin}; for (int i = 0; i < colors.count(); ++i) { QWidget *button = createButton(colors[i]); colorGrid.addItem(button); @@ -46,7 +46,7 @@ ColorPicker::ColorPicker(const QString &key, QWidget *parent) QSizePolicy::MinimumExpanding, QSizePolicy::Preferred)); } - colorGrid.attachTo(basicColorContentFrame, WithoutMargins); + colorGrid.attachTo(basicColorContentFrame); Column { Tr::tr("Basic Colors"), basicColorContentFrame, diff --git a/src/plugins/scxmleditor/common/colorsettings.cpp b/src/plugins/scxmleditor/common/colorsettings.cpp index c2ad9f22ce2..1419b481bc5 100644 --- a/src/plugins/scxmleditor/common/colorsettings.cpp +++ b/src/plugins/scxmleditor/common/colorsettings.cpp @@ -36,7 +36,7 @@ ColorSettings::ColorSettings(QWidget *parent) s->value(Constants::C_SETTINGS_COLORSETTINGS_CURRENTCOLORTHEME).toString()); selectTheme(m_comboColorThemes->currentIndex()); - using namespace Utils::Layouting; + using namespace Layouting; Column { Row { m_comboColorThemes, @@ -44,7 +44,8 @@ ColorSettings::ColorSettings(QWidget *parent) removeTheme, }, m_colorThemeView, - }.attachTo(this, WithoutMargins); + noMargin + }.attachTo(this); connect(m_comboColorThemes, &QComboBox::currentIndexChanged, this, &ColorSettings::selectTheme); diff --git a/src/plugins/scxmleditor/common/colorthemedialog.cpp b/src/plugins/scxmleditor/common/colorthemedialog.cpp index 00f224d1f98..2f2b209b779 100644 --- a/src/plugins/scxmleditor/common/colorthemedialog.cpp +++ b/src/plugins/scxmleditor/common/colorthemedialog.cpp @@ -21,7 +21,7 @@ ColorThemeDialog::ColorThemeDialog(QWidget *parent) QDialogButtonBox::Cancel | QDialogButtonBox::Apply); - using namespace Utils::Layouting; + using namespace Layouting; Column { m_colorSettings, buttonBox, diff --git a/src/plugins/scxmleditor/common/navigatorslider.cpp b/src/plugins/scxmleditor/common/navigatorslider.cpp index 5f3fc345e1f..ab3738bc06e 100644 --- a/src/plugins/scxmleditor/common/navigatorslider.cpp +++ b/src/plugins/scxmleditor/common/navigatorslider.cpp @@ -29,13 +29,15 @@ NavigatorSlider::NavigatorSlider(QWidget *parent) btn->setAutoRepeatInterval(10); } - using namespace Utils::Layouting; + using namespace Layouting; Row { + spacing(0), zoomOut, m_slider, zoomIn, Space(20), - }.setSpacing(0).attachTo(this, WithoutMargins); + noMargin, + }.attachTo(this); connect(zoomOut, &QToolButton::clicked, this, &NavigatorSlider::zoomOut); connect(zoomIn, &QToolButton::clicked, this, &NavigatorSlider::zoomIn); diff --git a/src/plugins/scxmleditor/common/search.cpp b/src/plugins/scxmleditor/common/search.cpp index acfc5cda131..f4d7e4bbc3f 100644 --- a/src/plugins/scxmleditor/common/search.cpp +++ b/src/plugins/scxmleditor/common/search.cpp @@ -45,11 +45,13 @@ Search::Search(QWidget *parent) m_searchView->setModel(m_proxyModel); m_searchView->setFrameShape(QFrame::NoFrame); - using namespace Utils::Layouting; + using namespace Layouting; Column { + spacing(0), m_searchEdit, m_searchView, - }.setSpacing(0).attachTo(this, WithoutMargins); + noMargin + }.attachTo(this); connect(m_searchEdit, &Utils::FancyLineEdit::textChanged, this, &Search::setSearchText); connect(m_searchView, &TableView::pressed, this, &Search::rowActivated); diff --git a/src/plugins/scxmleditor/common/shapestoolbox.cpp b/src/plugins/scxmleditor/common/shapestoolbox.cpp index 988c762c347..82def88379a 100644 --- a/src/plugins/scxmleditor/common/shapestoolbox.cpp +++ b/src/plugins/scxmleditor/common/shapestoolbox.cpp @@ -29,10 +29,12 @@ ShapesToolbox::ShapesToolbox(QWidget *parent) m_shapeGroupsLayout->setContentsMargins(0, 0, 0, 0); m_shapeGroupsLayout->setSpacing(0); - using namespace Utils::Layouting; + using namespace Layouting; Column { + spacing(0), scrollArea, - }.setSpacing(0).attachTo(this, WithoutMargins); + noMargin, + }.attachTo(this); } void ShapesToolbox::setUIFactory(ScxmlEditor::PluginInterface::ScxmlUiFactory *factory) diff --git a/src/plugins/scxmleditor/common/stateproperties.cpp b/src/plugins/scxmleditor/common/stateproperties.cpp index 32d6e43f642..f7a884837ea 100644 --- a/src/plugins/scxmleditor/common/stateproperties.cpp +++ b/src/plugins/scxmleditor/common/stateproperties.cpp @@ -106,7 +106,6 @@ void StateProperties::createUi() m_currentTagName = new QLabel; auto propertiesToolBar = new QToolBar; - propertiesToolBar->setMinimumHeight(24); propertiesToolBar->addWidget(titleLabel); propertiesToolBar->addWidget(m_currentTagName); diff --git a/src/plugins/scxmleditor/common/stateview.cpp b/src/plugins/scxmleditor/common/stateview.cpp index 1ee99034417..517f53bd3ec 100644 --- a/src/plugins/scxmleditor/common/stateview.cpp +++ b/src/plugins/scxmleditor/common/stateview.cpp @@ -31,15 +31,19 @@ StateView::StateView(StateItem *state, QWidget *parent) m_graphicsView = new GraphicsView; - using namespace Utils::Layouting; + using namespace Layouting; Row { PushButton{ text("Back"), onClicked([this] { closeView(); }, this) }, stateNameLabel, - }.attachTo(titleBar, WithoutMargins); + noMargin + }.attachTo(titleBar); Column { - titleBar, m_graphicsView - }.setSpacing(0).attachTo(this, WithoutMargins); + spacing(0), + titleBar, + m_graphicsView, + noMargin, + }.attachTo(this); initScene(); } diff --git a/src/plugins/scxmleditor/common/statistics.cpp b/src/plugins/scxmleditor/common/statistics.cpp index 06c62f70b2a..cff7d337c37 100644 --- a/src/plugins/scxmleditor/common/statistics.cpp +++ b/src/plugins/scxmleditor/common/statistics.cpp @@ -135,13 +135,14 @@ Statistics::Statistics(QWidget *parent) m_statisticsView->setAlternatingRowColors(true); m_statisticsView->setSortingEnabled(true); - using namespace Utils::Layouting; + using namespace Layouting; Grid { Tr::tr("File"), m_fileNameLabel, br, Tr::tr("Time"), m_timeLabel, br, Tr::tr("Max. levels"), m_levels, br, - Span(2, m_statisticsView), br - }.attachTo(this, WithoutMargins); + Span(2, m_statisticsView), br, + noMargin + }.attachTo(this); } void Statistics::setDocument(ScxmlDocument *doc) diff --git a/src/plugins/scxmleditor/common/statisticsdialog.cpp b/src/plugins/scxmleditor/common/statisticsdialog.cpp index 7cfb6cd4637..d9a20dd81de 100644 --- a/src/plugins/scxmleditor/common/statisticsdialog.cpp +++ b/src/plugins/scxmleditor/common/statisticsdialog.cpp @@ -21,7 +21,7 @@ StatisticsDialog::StatisticsDialog(QWidget *parent) m_statistics = new Statistics; auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok); - using namespace Utils::Layouting; + using namespace Layouting; Column { m_statistics, buttonBox, diff --git a/src/plugins/scxmleditor/plugin_interface/baseitem.cpp b/src/plugins/scxmleditor/plugin_interface/baseitem.cpp index 861094321aa..7d87a68f91c 100644 --- a/src/plugins/scxmleditor/plugin_interface/baseitem.cpp +++ b/src/plugins/scxmleditor/plugin_interface/baseitem.cpp @@ -41,7 +41,8 @@ BaseItem::~BaseItem() void BaseItem::checkParentBoundingRect() { BaseItem *parentBaseItem = this->parentBaseItem(); - if (parentBaseItem && type() >= InitialStateType && !parentBaseItem->blockUpdates()) { + if ((parentBaseItem && type() >= InitialStateType && !parentBaseItem->blockUpdates()) + || (parentBaseItem && type() == StateWarningType)) { auto parentStateItem = qgraphicsitem_cast(parentBaseItem); if (parentStateItem && (parentStateItem->type() >= StateType)) parentStateItem->updateBoundingRect(); diff --git a/src/plugins/scxmleditor/plugin_interface/baseitem.h b/src/plugins/scxmleditor/plugin_interface/baseitem.h index 5c992c632b6..42f09ca3893 100644 --- a/src/plugins/scxmleditor/plugin_interface/baseitem.h +++ b/src/plugins/scxmleditor/plugin_interface/baseitem.h @@ -93,6 +93,7 @@ public: ScxmlUiFactory *uiFactory() const; virtual void updateUIProperties(); + virtual void addChild(ScxmlTag */*tag*/) {}; protected: virtual void updatePolygon(); diff --git a/src/plugins/scxmleditor/plugin_interface/eventitem.cpp b/src/plugins/scxmleditor/plugin_interface/eventitem.cpp new file mode 100644 index 00000000000..f61517f7cf6 --- /dev/null +++ b/src/plugins/scxmleditor/plugin_interface/eventitem.cpp @@ -0,0 +1,108 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "eventitem.h" + +#include +#include +#include +#include +#include + +namespace ScxmlEditor { + +namespace PluginInterface { + +EventItem::EventItem(const QPointF &pos, BaseItem *parent) + : BaseItem(parent) +{ + m_eventNameItem = new TextItem(this); + m_eventNameItem->setParentItem(this); + QFont serifFont("Times", 13, QFont::Normal); + m_eventNameItem->setFont(serifFont); + + QString color = editorInfo("fontColor"); + m_eventNameItem->setDefaultTextColor(color.isEmpty() ? QColor(Qt::black) : QColor(color)); + + setPos(pos); + m_eventNameItem->setTextInteractionFlags(Qt::NoTextInteraction); + setItemBoundingRect(m_eventNameItem->boundingRect()); +} + +void EventItem::updateAttributes() +{ + QString text = " " + tag()->tagName(); + if (tag()->attributeNames().size() > 0) { + for (int i = 0; i < tag()->attributeNames().size(); ++i) + if (tag()->attributeNames().at(i) == "event") { + if (tag()->attributeValues().size() > i) + text += " / " + tag()->attributeValues().at(i); + break; + } + } + m_eventNameItem->setText(text); + setItemBoundingRect(m_eventNameItem->boundingRect()); +} + +OnEntryExitItem::OnEntryExitItem(BaseItem *parent) + : BaseItem(parent) +{ + m_eventNameItem = new TextItem(this); + m_eventNameItem->setParentItem(this); + QFont serifFont("Times", 13, QFont::Normal); + m_eventNameItem->setFont(serifFont); + m_eventNameItem->setDefaultTextColor(Qt::black); + m_eventNameItem->setTextInteractionFlags(Qt::NoTextInteraction); +} + +void OnEntryExitItem::updateAttributes() +{ + QString text = tag()->tagName(); + + m_eventNameItem->setText(text); + setItemBoundingRect(childBoundingRect()); + checkParentBoundingRect(); +} + +void OnEntryExitItem::finalizeCreation() +{ + auto children = tag()->allChildren(); + auto pos = m_eventNameItem->boundingRect().bottomLeft(); + for (auto child : children) { + EventItem *item = new EventItem(pos, this); + item->setTag(child); + item->updateAttributes(); + pos = item->pos() + item->boundingRect().bottomLeft(); + } + + setItemBoundingRect(childBoundingRect()); +} + +void OnEntryExitItem::addChild(ScxmlTag *tag) +{ + auto pos = childBoundingRect().bottomLeft(); + EventItem *item = new EventItem(pos, this); + item->setTag(tag); + item->updateAttributes(); + + setItemBoundingRect(childBoundingRect()); + checkParentBoundingRect(); +} + +QRectF OnEntryExitItem::childBoundingRect() const +{ + QRectF r = m_eventNameItem->boundingRect(); + + const QList children = childItems(); + + for (const QGraphicsItem *child : children) { + QRectF br = child->boundingRect(); + QPointF p = child->pos() + br.topLeft(); + br.moveTopLeft(p); + r = r.united(br); + } + return r; +} + +} // namespace PluginInterface +} // namespace ScxmlEditor diff --git a/src/plugins/scxmleditor/plugin_interface/eventitem.h b/src/plugins/scxmleditor/plugin_interface/eventitem.h new file mode 100644 index 00000000000..cf8ff3a9e34 --- /dev/null +++ b/src/plugins/scxmleditor/plugin_interface/eventitem.h @@ -0,0 +1,41 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "baseitem.h" +#include "textitem.h" + +namespace ScxmlEditor::PluginInterface { + +class EventItem : public BaseItem +{ +public: + explicit EventItem(const QPointF &pos = QPointF(), BaseItem *parent = nullptr); + + void updateAttributes() override; + void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override {} + +private: + TextItem *m_eventNameItem; +}; + +class OnEntryExitItem : public BaseItem +{ +public: + explicit OnEntryExitItem(BaseItem *parent = nullptr); + + int type() const override { return StateWarningType; } + + void updateAttributes() override; + void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override {} + void finalizeCreation() override; + void addChild(ScxmlTag *tag) override; + + QRectF childBoundingRect() const; + +private: + TextItem *m_eventNameItem; +}; + +} // namespace ScxmlEditor::PluginInterface diff --git a/src/plugins/scxmleditor/plugin_interface/graphicsscene.cpp b/src/plugins/scxmleditor/plugin_interface/graphicsscene.cpp index fb0e8252db9..94bd8b41edb 100644 --- a/src/plugins/scxmleditor/plugin_interface/graphicsscene.cpp +++ b/src/plugins/scxmleditor/plugin_interface/graphicsscene.cpp @@ -467,8 +467,7 @@ void GraphicsScene::endTagChange(ScxmlDocument::TagChange change, ScxmlTag *tag, break; } case ScxmlDocument::TagChangeParent: { - auto childItem = qobject_cast(findItem(tag)); - + auto childItem = findItem(tag); if (childItem) { QTC_ASSERT(tag, break); BaseItem *newParentItem = findItem(tag->parentTag()); @@ -485,8 +484,11 @@ void GraphicsScene::endTagChange(ScxmlDocument::TagChange change, ScxmlTag *tag, childItem->setParentItem(newParentItem); childItem->updateUIProperties(); - childItem->updateTransitions(true); - childItem->updateTransitionAttributes(true); + if (auto childConItem = qobject_cast(findItem(tag))) { + childConItem->updateTransitions(true); + childConItem->updateTransitionAttributes(true); + } + childItem->checkWarnings(); childItem->checkInitial(); if (newParentItem) { @@ -495,6 +497,8 @@ void GraphicsScene::endTagChange(ScxmlDocument::TagChange change, ScxmlTag *tag, newParentItem->checkWarnings(); newParentItem->checkOverlapping(); newParentItem->updateUIProperties(); + if (auto newConItem = qobject_cast(newParentItem)) + newConItem->updateBoundingRect(); } if (oldParentItem) @@ -549,6 +553,9 @@ void GraphicsScene::endTagChange(ScxmlDocument::TagChange change, ScxmlTag *tag, } if (parentItem) { + if (childItem == nullptr) + parentItem->addChild(childTag); + parentItem->updateAttributes(); parentItem->updateUIProperties(); parentItem->checkInitial(); diff --git a/src/plugins/scxmleditor/plugin_interface/stateitem.cpp b/src/plugins/scxmleditor/plugin_interface/stateitem.cpp index dd1015f9d40..e53bd8c894e 100644 --- a/src/plugins/scxmleditor/plugin_interface/stateitem.cpp +++ b/src/plugins/scxmleditor/plugin_interface/stateitem.cpp @@ -1,19 +1,18 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "finalstateitem.h" +#include "stateitem.h" + +#include "eventitem.h" #include "graphicsitemprovider.h" #include "graphicsscene.h" #include "idwarningitem.h" #include "imageprovider.h" -#include "initialstateitem.h" -#include "parallelitem.h" #include "sceneutils.h" #include "scxmleditorconstants.h" #include "scxmleditortr.h" #include "scxmltagutils.h" #include "scxmluifactory.h" -#include "stateitem.h" #include "statewarningitem.h" #include "textitem.h" #include "transitionitem.h" @@ -28,6 +27,7 @@ #include #include #include +#include using namespace ScxmlEditor::PluginInterface; @@ -171,6 +171,7 @@ void StateItem::updateBoundingRect() // Check if we need to increase parent boundingrect if (!r2.isNull()) { + positionOnExitItems(); QRectF r = boundingRect(); QRectF r3 = r.united(r2); @@ -244,7 +245,6 @@ void StateItem::transitionCountChanged() QRectF StateItem::childItemsBoundingRect() const { QRectF r; - QRectF rr = boundingRect(); QList children = childItems(); for (int i = 0; i < children.count(); ++i) { @@ -256,15 +256,26 @@ QRectF StateItem::childItemsBoundingRect() const } } + if (m_onEntryItem) { + QRectF br = m_onEntryItem->childBoundingRect(); + QPointF p = m_onEntryItem->pos() + br.topLeft(); + br.moveTopLeft(p); + r = r.united(br); + } + + if (m_onExitItem) { + QRectF br = m_onExitItem->childBoundingRect(); + QPointF p = m_onExitItem->pos() + br.topLeft(); + br.moveTopLeft(p); + r = r.united(br); + } + if (m_transitionRect.isValid()) { r.setLeft(r.left() - m_transitionRect.width()); r.setHeight(qMax(r.height(), m_transitionRect.height())); r.moveBottom(qMax(r.bottom(), m_transitionRect.bottom())); } - if (!r.isNull()) - r.adjust(-20, -(rr.height() * 0.06 + 40), 20, 20); - return r; } @@ -418,11 +429,18 @@ void StateItem::updatePolygon() << m_drawingRect.bottomLeft() << m_drawingRect.topLeft(); - m_titleRect = QRectF(m_drawingRect.left(), m_drawingRect.top(), m_drawingRect.width(), TEXT_ITEM_HEIGHT + m_drawingRect.height() * 0.06); + m_titleRect = QRectF(m_drawingRect.left(), + m_drawingRect.top(), + m_drawingRect.width(), + TEXT_ITEM_HEIGHT + m_drawingRect.height() * 0.06); QFont f = m_stateNameItem->font(); f.setPixelSize(m_titleRect.height() * 0.65); m_stateNameItem->setFont(f); + if (m_onEntryItem) + m_onEntryItem->setPos(m_titleRect.x(), m_titleRect.bottom()); + positionOnExitItems(); + updateTextPositions(); } @@ -517,13 +535,41 @@ void StateItem::init(ScxmlTag *tag, BaseItem *parentItem, bool initChildren, boo if (newItem) { newItem->init(child, this, initChildren, blockUpdates); newItem->finalizeCreation(); - } + } else + addChild(child); } } if (blockUpdates) setBlockUpdates(false); } + +void StateItem::addChild(ScxmlTag *child) +{ + if (child->tagName() == "onentry") { + OnEntryExitItem *item = new OnEntryExitItem(this); + m_onEntryItem = item; + item->setTag(child); + item->finalizeCreation(); + item->updateAttributes(); + m_onEntryItem->setPos(m_titleRect.x(), m_titleRect.bottom()); + } else if (child->tagName() == "onexit") { + OnEntryExitItem *item = new OnEntryExitItem(this); + m_onExitItem = item; + item->setTag(child); + item->finalizeCreation(); + item->updateAttributes(); + positionOnExitItems(); + } +} + +void StateItem::positionOnExitItems() +{ + int offset = m_onEntryItem ? m_onEntryItem->boundingRect().height() : 0; + if (m_onExitItem) + m_onExitItem->setPos(m_titleRect.x(), m_titleRect.bottom() + offset); +} + QString StateItem::itemId() const { return m_stateNameItem ? m_stateNameItem->toPlainText() : QString(); diff --git a/src/plugins/scxmleditor/plugin_interface/stateitem.h b/src/plugins/scxmleditor/plugin_interface/stateitem.h index 585992f040e..beaebe9ebd7 100644 --- a/src/plugins/scxmleditor/plugin_interface/stateitem.h +++ b/src/plugins/scxmleditor/plugin_interface/stateitem.h @@ -4,7 +4,9 @@ #pragma once #include "connectableitem.h" +#include "textitem.h" #include +#include QT_FORWARD_DECLARE_CLASS(QGraphicsSceneMouseEvent) @@ -16,6 +18,7 @@ class TransitionItem; class TextItem; class IdWarningItem; class StateWarningItem; +class OnEntryExitItem; /** * @brief The StateItem class represents the SCXML-State. @@ -49,6 +52,8 @@ public: QRectF childItemsBoundingRect() const; void connectToParent(BaseItem *parentItem) override; + void addChild(ScxmlTag *child) override; + protected: void updatePolygon() override; void transitionsChanged() override; @@ -69,6 +74,7 @@ private: void updateTextPositions(); void checkParentBoundingRect(); void checkWarningItems(); + void positionOnExitItems(); TextItem *m_stateNameItem; StateWarningItem *m_stateWarningItem = nullptr; @@ -76,6 +82,8 @@ private: QPen m_pen; bool m_initial = false; bool m_parallelState = false; + QPointer m_onEntryItem; + QPointer m_onExitItem; QImage m_backgroundImage; }; diff --git a/src/plugins/scxmleditor/scxmleditor.qbs b/src/plugins/scxmleditor/scxmleditor.qbs index c110b92f28d..39340d8764c 100644 --- a/src/plugins/scxmleditor/scxmleditor.qbs +++ b/src/plugins/scxmleditor/scxmleditor.qbs @@ -93,6 +93,7 @@ QtcPlugin { "baseitem.cpp", "baseitem.h", "connectableitem.cpp", "connectableitem.h", "cornergrabberitem.cpp", "cornergrabberitem.h", + "eventitem.cpp", "eventitem.h", "finalstateitem.cpp", "finalstateitem.h", "genericscxmlplugin.cpp", "genericscxmlplugin.h", "graphicsitemprovider.h", diff --git a/src/plugins/scxmleditor/scxmleditordocument.cpp b/src/plugins/scxmleditor/scxmleditordocument.cpp index 9c84910b2dd..e10c8759084 100644 --- a/src/plugins/scxmleditor/scxmleditordocument.cpp +++ b/src/plugins/scxmleditor/scxmleditordocument.cpp @@ -6,7 +6,7 @@ #include "scxmleditorconstants.h" #include -#include +#include #include #include #include diff --git a/src/plugins/silversearcher/CMakeLists.txt b/src/plugins/silversearcher/CMakeLists.txt index 7cc12ebb294..2af582d4a3e 100644 --- a/src/plugins/silversearcher/CMakeLists.txt +++ b/src/plugins/silversearcher/CMakeLists.txt @@ -2,12 +2,12 @@ add_qtc_plugin(SilverSearcher PLUGIN_DEPENDS Core TextEditor SOURCES findinfilessilversearcher.cpp findinfilessilversearcher.h - silversearcheroutputparser.cpp silversearcheroutputparser.h + silversearcherparser.cpp silversearcherparser.h silversearcherplugin.cpp silversearcherplugin.h silversearchertr.h ) extend_qtc_plugin(SilverSearcher CONDITION WITH_TESTS SOURCES - outputparser_test.cpp outputparser_test.h + silversearcherparser_test.cpp silversearcherparser_test.h ) diff --git a/src/plugins/silversearcher/findinfilessilversearcher.cpp b/src/plugins/silversearcher/findinfilessilversearcher.cpp index b2887cd0a91..6cf428bb64f 100644 --- a/src/plugins/silversearcher/findinfilessilversearcher.cpp +++ b/src/plugins/silversearcher/findinfilessilversearcher.cpp @@ -2,37 +2,27 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "findinfilessilversearcher.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "silversearcheroutputparser.h" +#include "silversearcherparser.h" #include "silversearchertr.h" +#include +#include +#include +#include + #include #include #include #include using namespace Core; +using namespace SilverSearcher; using namespace TextEditor; using namespace Utils; namespace { -const QLatin1String silverSearcherName("Silver Searcher"); - -using FutureInterfaceType = QFutureInterface; - -const QString metacharacters = "+()^$.{}[]|\\"; - -const QString SearchOptionsString = "SearchOptionsString"; +const QLatin1String s_metaCharacters = QLatin1String("+()^$.{}[]|\\"); +const QLatin1String s_searchOptionsString = QLatin1String("SearchOptionsString"); class SilverSearcherSearchOptions { @@ -40,96 +30,80 @@ public: QString searchOptions; }; -QString convertWildcardToRegex(const QString &wildcard) +static QString convertWildcardToRegex(const QString &wildcard) { QString regex; const int wildcardSize = wildcard.size(); regex.append('^'); for (int i = 0; i < wildcardSize; ++i) { const QChar ch = wildcard[i]; - if (ch == '*') { + if (ch == '*') regex.append(".*"); - } else if (ch == '?') { + else if (ch == '?') regex.append('.'); - } else if (metacharacters.indexOf(ch) != -1) { - regex.append('\\'); + else if (s_metaCharacters.indexOf(ch) != -1) + regex.append('\\' + ch); + else regex.append(ch); - } else { - regex.append(ch); - } } regex.append('$'); - return regex; } -bool isSilverSearcherAvailable() +static bool isSilverSearcherAvailable() { - QtcProcess silverSearcherProcess; + Process silverSearcherProcess; silverSearcherProcess.setCommand({"ag", {"--version"}}); silverSearcherProcess.start(); - if (silverSearcherProcess.waitForFinished(1000)) { - if (silverSearcherProcess.cleanedStdOut().contains("ag version")) - return true; - } - - return false; + return silverSearcherProcess.waitForFinished(1000) + && silverSearcherProcess.cleanedStdOut().contains("ag version"); } -void runSilverSeacher(FutureInterfaceType &fi, FileFindParameters parameters) +static void runSilverSeacher(QPromise &promise, + const FileFindParameters ¶meters) { - ProgressTimer progress(fi, 5); - const FilePath directory = FilePath::fromUserInput(parameters.additionalParameters.toString()); - QStringList arguments = {"--parallel", "--ackmate"}; + const auto setupProcess = [parameters](Process &process) { + const FilePath directory + = FilePath::fromUserInput(parameters.additionalParameters.toString()); + QStringList arguments = {"--parallel", "--ackmate"}; - if (parameters.flags & FindCaseSensitively) - arguments << "-s"; - else - arguments << "-i"; + if (parameters.flags & FindCaseSensitively) + arguments << "-s"; + else + arguments << "-i"; - if (parameters.flags & FindWholeWords) - arguments << "-w"; + if (parameters.flags & FindWholeWords) + arguments << "-w"; - if (!(parameters.flags & FindRegularExpression)) - arguments << "-Q"; + if (!(parameters.flags & FindRegularExpression)) + arguments << "-Q"; - for (const QString &filter : std::as_const(parameters.exclusionFilters)) - arguments << "--ignore" << filter; + for (const QString &filter : std::as_const(parameters.exclusionFilters)) + arguments << "--ignore" << filter; - QString nameFiltersAsRegex; - for (const QString &filter : std::as_const(parameters.nameFilters)) - nameFiltersAsRegex += QString("(%1)|").arg(convertWildcardToRegex(filter)); - nameFiltersAsRegex.remove(nameFiltersAsRegex.length() - 1, 1); + QString nameFiltersAsRegExp; + for (const QString &filter : std::as_const(parameters.nameFilters)) + nameFiltersAsRegExp += QString("(%1)|").arg(convertWildcardToRegex(filter)); + nameFiltersAsRegExp.remove(nameFiltersAsRegExp.length() - 1, 1); - arguments << "-G" << nameFiltersAsRegex; + arguments << "-G" << nameFiltersAsRegExp; - SilverSearcherSearchOptions params = parameters.searchEngineParameters - .value(); - if (!params.searchOptions.isEmpty()) - arguments << params.searchOptions.split(' '); + const SilverSearcherSearchOptions params = parameters.searchEngineParameters + .value(); + if (!params.searchOptions.isEmpty()) + arguments << params.searchOptions.split(' '); - arguments << "--" << parameters.text << directory.normalizedPathName().toString(); + arguments << "--" << parameters.text << directory.normalizedPathName().toString(); + process.setCommand({"ag", arguments}); + }; - QtcProcess process; - process.setCommand({"ag", arguments}); - process.start(); - if (process.waitForFinished()) { - QRegularExpression regexp; - if (parameters.flags & FindRegularExpression) { - const QRegularExpression::PatternOptions patternOptions - = (parameters.flags & FindCaseSensitively) - ? QRegularExpression::NoPatternOption - : QRegularExpression::CaseInsensitiveOption; - regexp.setPattern(parameters.text); - regexp.setPatternOptions(patternOptions); - } - SilverSearcher::SilverSearcherOutputParser parser(process.cleanedStdOut(), regexp); - FileSearchResultList items = parser.parse(); - if (!items.isEmpty()) - fi.reportResult(items); - } else { - fi.reportCanceled(); - } + FilePath lastFilePath; + const auto outputParser = [&lastFilePath](const QFuture &future, const QString &input, + const std::optional ®Exp) { + return SilverSearcher::parse(future, input, regExp, &lastFilePath); + }; + + TextEditor::searchInProcessOutput(promise, parameters, setupProcess, outputParser); } } // namespace @@ -154,6 +128,7 @@ FindInFilesSilverSearcher::FindInFilesSilverSearcher(QObject *parent) QTC_ASSERT(findInFiles, return); findInFiles->addSearchEngine(this); + // TODO: Make disabled by default and run isSilverSearcherAvailable asynchronously setEnabled(isSilverSearcherAvailable()); if (!isEnabled()) { QLabel *label = new QLabel(Tr::tr("Silver Searcher is not available on the system.")); @@ -162,10 +137,6 @@ FindInFilesSilverSearcher::FindInFilesSilverSearcher(QObject *parent) } } -FindInFilesSilverSearcher::~FindInFilesSilverSearcher() -{ -} - QVariant FindInFilesSilverSearcher::parameters() const { SilverSearcherSearchOptions silverSearcherSearchOptions; @@ -175,12 +146,12 @@ QVariant FindInFilesSilverSearcher::parameters() const QString FindInFilesSilverSearcher::title() const { - return silverSearcherName; + return "Silver Searcher"; } QString FindInFilesSilverSearcher::toolTip() const { - return QString(); + return {}; } QWidget *FindInFilesSilverSearcher::widget() const @@ -190,13 +161,13 @@ QWidget *FindInFilesSilverSearcher::widget() const void FindInFilesSilverSearcher::writeSettings(QSettings *settings) const { - settings->setValue(SearchOptionsString, m_searchOptionsLineEdit->text()); + settings->setValue(s_searchOptionsString, m_searchOptionsLineEdit->text()); } -QFuture FindInFilesSilverSearcher::executeSearch( +QFuture FindInFilesSilverSearcher::executeSearch( const FileFindParameters ¶meters, BaseFileFind * /*baseFileFind*/) { - return Utils::runAsync(runSilverSeacher, parameters); + return Utils::asyncRun(runSilverSeacher, parameters); } IEditor *FindInFilesSilverSearcher::openEditor(const SearchResultItem & /*item*/, @@ -207,7 +178,7 @@ IEditor *FindInFilesSilverSearcher::openEditor(const SearchResultItem & /*item*/ void FindInFilesSilverSearcher::readSettings(QSettings *settings) { - m_searchOptionsLineEdit->setText(settings->value(SearchOptionsString).toString()); + m_searchOptionsLineEdit->setText(settings->value(s_searchOptionsString).toString()); } } // namespace SilverSearcher diff --git a/src/plugins/silversearcher/findinfilessilversearcher.h b/src/plugins/silversearcher/findinfilessilversearcher.h index 3a5363d7137..bc5e224c21f 100644 --- a/src/plugins/silversearcher/findinfilessilversearcher.h +++ b/src/plugins/silversearcher/findinfilessilversearcher.h @@ -3,10 +3,9 @@ #pragma once -#include #include -#include +#include #include @@ -14,6 +13,8 @@ QT_BEGIN_NAMESPACE class QLineEdit; QT_END_NAMESPACE +namespace Core { class IFindSupport; } + namespace SilverSearcher { class FindInFilesSilverSearcher : public TextEditor::SearchEngine @@ -22,7 +23,6 @@ class FindInFilesSilverSearcher : public TextEditor::SearchEngine public: explicit FindInFilesSilverSearcher(QObject *parent); - ~FindInFilesSilverSearcher() override; // TextEditor::FileFindExtension QString title() const override; @@ -31,9 +31,9 @@ public: QVariant parameters() const override; void readSettings(QSettings *settings) override; void writeSettings(QSettings *settings) const override; - QFuture executeSearch( + QFuture executeSearch( const TextEditor::FileFindParameters ¶meters, TextEditor::BaseFileFind *) override; - Core::IEditor *openEditor(const Core::SearchResultItem &item, + Core::IEditor *openEditor(const Utils::SearchResultItem &item, const TextEditor::FileFindParameters ¶meters) override; private: diff --git a/src/plugins/silversearcher/outputparser_test.cpp b/src/plugins/silversearcher/outputparser_test.cpp deleted file mode 100644 index a9185b31568..00000000000 --- a/src/plugins/silversearcher/outputparser_test.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "outputparser_test.h" -#include "silversearcheroutputparser.h" - -#include - -using namespace Utils; - -namespace SilverSearcher { -namespace Internal { - -void OutputParserTest::test_data() -{ - QTest::addColumn("parserOutput"); - QTest::addColumn("results"); - - QTest::addRow("nothing") << QString("\n") << FileSearchResultList(); - QTest::addRow("oneFileOneMatch") - << QString(":/file/path/to/filename.h\n" - "1;1 5:match\n") - << FileSearchResultList({{"/file/path/to/filename.h", 1, "match", 1, 5, {}}}); - QTest::addRow("multipleFilesWithOneMatch") - << QString(":/file/path/to/filename1.h\n" - "1;1 5:match\n" - "\n" - ":/file/path/to/filename2.h\n" - "2;2 5: match\n") - << FileSearchResultList({{"/file/path/to/filename1.h", 1, "match", 1, 5, {}}, - {"/file/path/to/filename2.h", 2, " match", 2, 5, {}}}); - QTest::addRow("oneFileMultipleMatches") - << QString(":/file/path/to/filename.h\n" - "1;1 5,7 5:match match\n") - << FileSearchResultList({{"/file/path/to/filename.h", 1, "match match", 1, 5, {}}, - {"/file/path/to/filename.h", 1, "match match", 7, 5, {}}}); - QTest::addRow("multipleFilesWithMultipleMatches") - << QString(":/file/path/to/filename1.h\n" - "1;1 5,7 5:match match\n" - "\n" - ":/file/path/to/filename2.h\n" - "2;2 5,8 5: match match\n") - << FileSearchResultList({{"/file/path/to/filename1.h", 1, "match match", 1, 5, {}}, - {"/file/path/to/filename1.h", 1, "match match", 7, 5, {}}, - {"/file/path/to/filename2.h", 2, " match match", 2, 5, {}}, - {"/file/path/to/filename2.h", 2, " match match", 8, 5, {}}}); -} - -void OutputParserTest::test() -{ - QFETCH(QString, parserOutput); - QFETCH(FileSearchResultList, results); - SilverSearcher::SilverSearcherOutputParser ssop(parserOutput); - const FileSearchResultList items = ssop.parse(); - QCOMPARE(items, results); -} - -} // namespace Internal -} // namespace SilverSearcher diff --git a/src/plugins/silversearcher/silversearcher.qbs b/src/plugins/silversearcher/silversearcher.qbs index f099858f6e3..08a25367aad 100644 --- a/src/plugins/silversearcher/silversearcher.qbs +++ b/src/plugins/silversearcher/silversearcher.qbs @@ -9,16 +9,14 @@ QtcPlugin { files: [ "findinfilessilversearcher.cpp", "findinfilessilversearcher.h", - "silversearcheroutputparser.cpp", "silversearcheroutputparser.h", + "silversearcherparser.cpp", "silversearcherparser.h", "silversearcherplugin.cpp", "silversearcherplugin.h", ] - Group { - name: "Tests" - condition: qtc.testsEnabled + QtcTestFiles { files: [ - "outputparser_test.cpp", - "outputparser_test.h", + "silversearcherparser_test.cpp", + "silversearcherparser_test.h", ] } } diff --git a/src/plugins/silversearcher/silversearcheroutputparser.cpp b/src/plugins/silversearcher/silversearcheroutputparser.cpp deleted file mode 100644 index 9088310c3fc..00000000000 --- a/src/plugins/silversearcher/silversearcheroutputparser.cpp +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (C) 2017 Przemyslaw Gorszkowski . -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "silversearcheroutputparser.h" - -#include - -namespace SilverSearcher { - -SilverSearcherOutputParser::SilverSearcherOutputParser( - const QString &output, const QRegularExpression ®exp) - : output(output) - , regexp(regexp) - , outputSize(output.size()) -{ - hasRegexp = !regexp.pattern().isEmpty(); -} - -Utils::FileSearchResultList SilverSearcherOutputParser::parse() -{ - while (index < outputSize - 1) { - if (output[index] == '\n') { - ++index; - continue; - } - parseFilePath(); - while (index < outputSize && output[index] != '\n') { - parseLineNumber(); - if (index >= outputSize - 1) - break; - int matches = parseMatches(); - if (index >= outputSize - 1) - break; - parseText(); - for (int i = 0; i < matches; ++i) - items[items.size() - i - 1].matchingLine = item.matchingLine; - } - } - - return items; -} - -bool SilverSearcherOutputParser::parseFilePath() -{ - int startIndex = ++index; - while (index < outputSize && output[index] != '\n') - ++index; - item.fileName = Utils::FilePath::fromString(QString(output.data() + startIndex, index - startIndex)); - ++index; - return true; -} - -bool SilverSearcherOutputParser::parseLineNumber() -{ - int startIndex = index; - while (index < outputSize && output[++index] != ';') { } - - item.lineNumber = QString(output.data() + startIndex, index - startIndex).toInt(); - ++index; - return true; -} - -bool SilverSearcherOutputParser::parseMatchIndex() -{ - int startIndex = index; - while (index < outputSize && output[++index] != ' ') { } - - item.matchStart = QString(output.data() + startIndex, index - startIndex).toInt(); - ++index; - return true; -} - -bool SilverSearcherOutputParser::parseMatchLength() -{ - int startIndex = index; - while (index < outputSize && output[++index] != ':' && output[index] != ',') { } - - item.matchLength = QString(output.data() + startIndex, index - startIndex).toInt(); - return true; -} - -int SilverSearcherOutputParser::parseMatches() -{ - int matches = 1; - const int colon = output.indexOf(':', index); - QString text; - if (colon != -1) { - const int textStart = colon + 1; - const int newline = output.indexOf('\n', textStart); - text = output.mid(textStart, newline >= 0 ? newline - textStart : -1); - } - while (index < outputSize && output[index] != ':') { - if (output[index] == ',') { - ++matches; - ++index; - } - parseMatchIndex(); - parseMatchLength(); - if (hasRegexp) { - const QString part = QString(text.mid(item.matchStart, item.matchLength)); - item.regexpCapturedTexts = regexp.match(part).capturedTexts(); - } - items << item; - } - - ++index; - return matches; -} - -bool SilverSearcherOutputParser::parseText() -{ - int startIndex = index; - while (index < outputSize && output[++index] != '\n') { } - item.matchingLine = QString(output.data() + startIndex, index - startIndex); - ++index; - return true; -} - -} // namespace SilverSearcher diff --git a/src/plugins/silversearcher/silversearcheroutputparser.h b/src/plugins/silversearcher/silversearcheroutputparser.h deleted file mode 100644 index 187e463938c..00000000000 --- a/src/plugins/silversearcher/silversearcheroutputparser.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (C) 2017 Przemyslaw Gorszkowski . -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include -#include - -#include -#include - -namespace SilverSearcher { - -class SilverSearcherOutputParser -{ -public: - SilverSearcherOutputParser(const QString &output, const QRegularExpression ®exp = {}); - - Utils::FileSearchResultList parse(); - -private: - int parseMatches(); - bool parseMatchLength(); - bool parseMatchIndex(); - bool parseLineNumber(); - bool parseFilePath(); - bool parseText(); - - QString output; - QRegularExpression regexp; - bool hasRegexp = false; - int outputSize = 0; - int index = 0; - Utils::FileSearchResult item; - Utils::FileSearchResultList items; -}; - -} // namespace SilverSearcher diff --git a/src/plugins/silversearcher/silversearcherparser.cpp b/src/plugins/silversearcher/silversearcherparser.cpp new file mode 100644 index 00000000000..1a2f5e55e87 --- /dev/null +++ b/src/plugins/silversearcher/silversearcherparser.cpp @@ -0,0 +1,157 @@ +// Copyright (C) 2017 Przemyslaw Gorszkowski . +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "silversearcherparser.h" + +using namespace Utils; + +namespace SilverSearcher { + +/* +// Example output (searching for "ab"): + +:/home/jarek/dev/creator-master/src/libs/qmlpuppetcommunication/container/imagecontainer.cpp +149;60 2: static const bool dontUseSharedMemory = qEnvironmentVariableIsSet("DESIGNER_DONT_USE_SHARED_MEMORY"); +198;65 2: qCInfo(imageContainerDebug()) << Q_FUNC_INFO << "Not able to create image:" << imageWidth << imageHeight << imageFormat; + +:/home/jarek/dev/creator-master/src/libs/qmlpuppetcommunication/container/propertyabstractcontainer.cpp +4;18 2:#include "propertyabstractcontainer.h" +10;8 2,35 2:PropertyAbstractContainer::PropertyAbstractContainer() +*/ + +static QStringView nextLine(QStringView *remainingInput) +{ + const int newLinePos = remainingInput->indexOf('\n'); + if (newLinePos < 0) { + QStringView ret = *remainingInput; + *remainingInput = QStringView(); + return ret; + } + QStringView ret = remainingInput->left(newLinePos); + *remainingInput = remainingInput->mid(newLinePos + 1); + return ret; +} + +static bool parseNumber(QStringView numberString, int *number) +{ + for (const auto &c : numberString) { + if (!c.isDigit()) + return false; + } + bool ok = false; + int parsedNumber = numberString.toInt(&ok); + if (ok) + *number = parsedNumber; + return ok; +} + +static bool parseLineNumber(QStringView *remainingInput, int *lineNumber) +{ + const int lineNumberDelimiterPos = remainingInput->indexOf(';'); + if (lineNumberDelimiterPos < 0) + return false; + + if (!parseNumber(remainingInput->left(lineNumberDelimiterPos), lineNumber)) + return false; + + *remainingInput = remainingInput->mid(lineNumberDelimiterPos + 1); + return true; +} + +static bool parseLineHit(QStringView hitString, QPair *hit) +{ + const int hitSpaceDelimiterPos = hitString.indexOf(' '); + if (hitSpaceDelimiterPos < 0) + return false; + + int hitStart = -1; + if (!parseNumber(hitString.left(hitSpaceDelimiterPos), &hitStart)) + return false; + + int hitLength = -1; + if (!parseNumber(hitString.mid(hitSpaceDelimiterPos + 1), &hitLength)) + return false; + + *hit = {hitStart, hitLength}; + return true; +} + +static bool parseLineHits(QStringView *remainingInput, QList> *hits) +{ + const int hitsDelimiterPos = remainingInput->indexOf(':'); + if (hitsDelimiterPos < 0) + return false; + + const QStringView hitsString = remainingInput->left(hitsDelimiterPos); + const QList hitStrings = hitsString.split(',', Qt::SkipEmptyParts); + for (const auto hitString : hitStrings) { + QPair hit; + if (!parseLineHit(hitString, &hit)) + return false; + hits->append(hit); + } + *remainingInput = remainingInput->mid(hitsDelimiterPos + 1); + return true; +} + +SearchResultItems parse(const QFuture &future, const QString &input, + const std::optional ®Exp, FilePath *lastFilePath) +{ + QTC_ASSERT(lastFilePath, return {}); + SearchResultItems items; + + QStringView remainingInput(input); + while (true) { + if (future.isCanceled()) + return {}; + + if (remainingInput.isEmpty()) + break; + + const QStringView filePathLine = nextLine(&remainingInput); + if (filePathLine.isEmpty()) { + *lastFilePath = {}; // Clear the parser state + continue; + } + + if (filePathLine.startsWith(':')) + *lastFilePath = FilePath::fromPathPart(filePathLine.mid(1)); + + while (true) { + QStringView hitLine = nextLine(&remainingInput); + if (hitLine.isEmpty()) + break; + int lineNumber = -1; + if (!parseLineNumber(&hitLine, &lineNumber)) + break; + + // List of pairs + QList> hits; + if (!parseLineHits(&hitLine, &hits)) + break; + + SearchResultItem item; + item.setFilePath(*lastFilePath); + item.setDisplayText(hitLine.toString()); + item.setUseTextEditorFont(true); + for (const QPair &hit : hits) { + item.setMainRange(lineNumber, hit.first, hit.second); + item.setUserData( + regExp ? regExp->match(hitLine.mid(hit.first, hit.second)).capturedTexts() + : QVariant()); + items.append(item); + } + } + } + return items; +} + +SearchResultItems parse(const QString &input, const std::optional ®Exp) +{ + QPromise promise; + promise.start(); + FilePath dummy; + return SilverSearcher::parse(promise.future(), input, regExp, &dummy); +} + +} // namespace SilverSearcher diff --git a/src/plugins/silversearcher/silversearcherparser.h b/src/plugins/silversearcher/silversearcherparser.h new file mode 100644 index 00000000000..67b3e124f42 --- /dev/null +++ b/src/plugins/silversearcher/silversearcherparser.h @@ -0,0 +1,22 @@ +// Copyright (C) 2017 Przemyslaw Gorszkowski . +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +#include +#include + +namespace Utils { class FilePath; } + +namespace SilverSearcher { + +Utils::SearchResultItems parse(const QFuture &future, const QString &input, + const std::optional ®Exp, + Utils::FilePath *lastFilePath); + +Utils::SearchResultItems parse(const QString &input, + const std::optional ®Exp = {}); + +} // namespace SilverSearcher diff --git a/src/plugins/silversearcher/silversearcherparser_test.cpp b/src/plugins/silversearcher/silversearcherparser_test.cpp new file mode 100644 index 00000000000..b23002e191e --- /dev/null +++ b/src/plugins/silversearcher/silversearcherparser_test.cpp @@ -0,0 +1,69 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "silversearcherparser.h" +#include "silversearcherparser_test.h" + +#include + +using namespace Utils; + +namespace SilverSearcher { +namespace Internal { + +SearchResultItem searchResult(const FilePath &fileName, const QString &matchingLine, + int lineNumber, int matchStart, int matchLength) +{ + SearchResultItem result; + result.setFilePath(fileName); + result.setLineText(matchingLine); + result.setMainRange(lineNumber, matchStart, matchLength); + result.setUseTextEditorFont(true); + return result; +} + +void OutputParserTest::test_data() +{ + QTest::addColumn("input"); + QTest::addColumn("results"); + + QTest::addRow("nothing") << QString("\n") << SearchResultItems(); + QTest::addRow("oneFileOneMatch") + << QString(":/file/path/to/filename.h\n" + "1;1 5:match\n") + << SearchResultItems{searchResult("/file/path/to/filename.h", "match", 1, 1, 5)}; + QTest::addRow("multipleFilesWithOneMatch") + << QString(":/file/path/to/filename1.h\n" + "1;1 5:match\n" + "\n" + ":/file/path/to/filename2.h\n" + "2;2 5: match\n") + << SearchResultItems{searchResult("/file/path/to/filename1.h", "match", 1, 1, 5), + searchResult("/file/path/to/filename2.h", " match", 2, 2, 5)}; + QTest::addRow("oneFileMultipleMatches") + << QString(":/file/path/to/filename.h\n" + "1;1 5,7 5:match match\n") + << SearchResultItems{searchResult("/file/path/to/filename.h", "match match", 1, 1, 5), + searchResult("/file/path/to/filename.h", "match match", 1, 7, 5)}; + QTest::addRow("multipleFilesWithMultipleMatches") + << QString(":/file/path/to/filename1.h\n" + "1;1 5,7 5:match match\n" + "\n" + ":/file/path/to/filename2.h\n" + "2;2 5,8 5: match match\n") + << SearchResultItems{searchResult("/file/path/to/filename1.h", "match match", 1, 1, 5), + searchResult("/file/path/to/filename1.h", "match match", 1, 7, 5), + searchResult("/file/path/to/filename2.h", " match match", 2, 2, 5), + searchResult("/file/path/to/filename2.h", " match match", 2, 8, 5)}; +} + +void OutputParserTest::test() +{ + QFETCH(QString, input); + QFETCH(SearchResultItems, results); + const SearchResultItems items = SilverSearcher::parse(input); + QCOMPARE(items, results); +} + +} // namespace Internal +} // namespace SilverSearcher diff --git a/src/plugins/silversearcher/outputparser_test.h b/src/plugins/silversearcher/silversearcherparser_test.h similarity index 100% rename from src/plugins/silversearcher/outputparser_test.h rename to src/plugins/silversearcher/silversearcherparser_test.h diff --git a/src/plugins/silversearcher/silversearcherplugin.cpp b/src/plugins/silversearcher/silversearcherplugin.cpp index e16ed4a3498..1f143407d78 100644 --- a/src/plugins/silversearcher/silversearcherplugin.cpp +++ b/src/plugins/silversearcher/silversearcherplugin.cpp @@ -1,9 +1,9 @@ // Copyright (C) 2017 Przemyslaw Gorszkowski . // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "silversearcherplugin.h" #include "findinfilessilversearcher.h" -#include "outputparser_test.h" +#include "silversearcherparser_test.h" +#include "silversearcherplugin.h" namespace SilverSearcher::Internal { diff --git a/src/plugins/squish/deletesymbolicnamedialog.cpp b/src/plugins/squish/deletesymbolicnamedialog.cpp index 44a3b507d58..dbb6804fb5a 100644 --- a/src/plugins/squish/deletesymbolicnamedialog.cpp +++ b/src/plugins/squish/deletesymbolicnamedialog.cpp @@ -63,7 +63,7 @@ DeleteSymbolicNameDialog::DeleteSymbolicNameDialog(const QString &symbolicName, updateDetailsLabel(symbolicName); populateSymbolicNamesList(names); - using namespace Utils::Layouting; + using namespace Layouting; Column { m_detailsLabel, diff --git a/src/plugins/squish/images/picker.png b/src/plugins/squish/images/picker.png new file mode 100644 index 00000000000..1e9a13e8a19 Binary files /dev/null and b/src/plugins/squish/images/picker.png differ diff --git a/src/plugins/squish/images/picker@2x.png b/src/plugins/squish/images/picker@2x.png new file mode 100644 index 00000000000..390926b891a Binary files /dev/null and b/src/plugins/squish/images/picker@2x.png differ diff --git a/src/plugins/squish/objectsmapdocument.cpp b/src/plugins/squish/objectsmapdocument.cpp index eb7266ddc43..1e0b30766da 100644 --- a/src/plugins/squish/objectsmapdocument.cpp +++ b/src/plugins/squish/objectsmapdocument.cpp @@ -10,7 +10,7 @@ #include "squishtr.h" #include -#include +#include #include @@ -195,7 +195,7 @@ Core::IDocument::OpenResult ObjectsMapDocument::openImpl(QString *error, text = reader.data(); } else { - const Utils::FilePath base = SquishPlugin::squishSettings()->squishPath.filePath(); + const Utils::FilePath base = SquishPlugin::squishSettings()->squishPath(); if (base.isEmpty()) { if (error) error->append(Tr::tr("Incomplete Squish settings. " @@ -209,7 +209,7 @@ Core::IDocument::OpenResult ObjectsMapDocument::openImpl(QString *error, return OpenResult::ReadError; } - Utils::QtcProcess objectMapReader; + Utils::Process objectMapReader; objectMapReader.setCommand({exe, {"--scriptMap", "--mode", "read", "--scriptedObjectMapPath", realFileName.toUserOutput()}}); objectMapReader.setCodec(QTextCodec::codecForName("UTF-8")); @@ -233,14 +233,14 @@ bool ObjectsMapDocument::writeFile(const Utils::FilePath &fileName) const } // otherwise we need the objectmaptool to write the scripted object map again - const Utils::FilePath base = SquishPlugin::squishSettings()->squishPath.filePath(); + const Utils::FilePath base = SquishPlugin::squishSettings()->squishPath(); if (base.isEmpty()) return false; const Utils::FilePath exe = base.pathAppended("lib/exec/objectmaptool").withExecutableSuffix(); if (!exe.isExecutableFile()) return false; - Utils::QtcProcess objectMapWriter; + Utils::Process objectMapWriter; objectMapWriter.setCommand({exe, {"--scriptMap", "--mode", "write", "--scriptedObjectMapPath", fileName.toUserOutput()}}); objectMapWriter.setWriteData(contents()); diff --git a/src/plugins/squish/objectsmapeditorwidget.cpp b/src/plugins/squish/objectsmapeditorwidget.cpp index 7e215d0d77f..e689a5a2962 100644 --- a/src/plugins/squish/objectsmapeditorwidget.cpp +++ b/src/plugins/squish/objectsmapeditorwidget.cpp @@ -88,7 +88,7 @@ void ObjectsMapEditorWidget::initUi() m_stackedLayout->addWidget(validPropertiesWidget); m_stackedLayout->addWidget(invalidPropertiesWidget); - using namespace Utils::Layouting; + using namespace Layouting; Row { m_propertiesTree, diff --git a/src/plugins/squish/opensquishsuitesdialog.cpp b/src/plugins/squish/opensquishsuitesdialog.cpp index f36d402dcab..c32e9d0a39b 100644 --- a/src/plugins/squish/opensquishsuitesdialog.cpp +++ b/src/plugins/squish/opensquishsuitesdialog.cpp @@ -40,7 +40,7 @@ OpenSquishSuitesDialog::OpenSquishSuitesDialog(QWidget *parent) m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Open); m_buttonBox->button(QDialogButtonBox::Open)->setEnabled(false); - using namespace Utils::Layouting; + using namespace Layouting; Column { new QLabel(Tr::tr("Base directory:")), diff --git a/src/plugins/squish/squish.qrc b/src/plugins/squish/squish.qrc index 1a62ee1c1c6..e55323c3cc7 100644 --- a/src/plugins/squish/squish.qrc +++ b/src/plugins/squish/squish.qrc @@ -8,6 +8,8 @@ images/jumpTo@2x.png images/data.png images/data@2x.png + images/picker.png + images/picker@2x.png wizard/suite/wizard.json diff --git a/src/plugins/squish/squishfilehandler.cpp b/src/plugins/squish/squishfilehandler.cpp index 956f9488227..8a726cb9baa 100644 --- a/src/plugins/squish/squishfilehandler.cpp +++ b/src/plugins/squish/squishfilehandler.cpp @@ -15,8 +15,10 @@ #include #include #include +#include + #include -#include + #include #include #include @@ -31,6 +33,8 @@ #include #include +using namespace Core; + namespace Squish { namespace Internal { @@ -51,7 +55,7 @@ public: QWidget *widget = new QWidget(this); auto buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); - using namespace Utils::Layouting; + using namespace Layouting; Form { label, &aut, br, arguments, @@ -93,8 +97,7 @@ SquishFileHandler::SquishFileHandler(QObject *parent) : QObject(parent) { m_instance = this; - auto sessionManager = ProjectExplorer::SessionManager::instance(); - connect(sessionManager, &ProjectExplorer::SessionManager::sessionLoaded, + connect(SessionManager::instance(), &SessionManager::sessionLoaded, this, &SquishFileHandler::onSessionLoaded); } @@ -258,7 +261,7 @@ void SquishFileHandler::openTestSuites() } } emit suitesOpened(); - ProjectExplorer::SessionManager::setValue(SK_OpenSuites, suitePathsAsStringList()); + SessionManager::setValue(SK_OpenSuites, suitePathsAsStringList()); } void SquishFileHandler::openTestSuite(const Utils::FilePath &suiteConfPath, bool isReopen) @@ -288,7 +291,7 @@ void SquishFileHandler::openTestSuite(const Utils::FilePath &suiteConfPath, bool m_suites.insert(suiteName, suiteConfPath); emit testTreeItemCreated(item); } - ProjectExplorer::SessionManager::setValue(SK_OpenSuites, suitePathsAsStringList()); + SessionManager::setValue(SK_OpenSuites, suitePathsAsStringList()); } static void closeOpenedEditorsFor(const Utils::FilePath &filePath, bool askAboutModifiedEditors) @@ -310,13 +313,13 @@ void SquishFileHandler::closeTestSuite(const QString &suiteName) // TODO remove file watcher m_suites.remove(suiteName); emit suiteTreeItemRemoved(suiteName); - ProjectExplorer::SessionManager::setValue(SK_OpenSuites, suitePathsAsStringList()); + SessionManager::setValue(SK_OpenSuites, suitePathsAsStringList()); } void SquishFileHandler::closeAllTestSuites() { closeAllInternal(); - ProjectExplorer::SessionManager::setValue(SK_OpenSuites, suitePathsAsStringList()); + SessionManager::setValue(SK_OpenSuites, suitePathsAsStringList()); } void SquishFileHandler::deleteTestCase(const QString &suiteName, const QString &testCaseName) @@ -538,7 +541,7 @@ void SquishFileHandler::onSessionLoaded() // remove currently opened "silently" (without storing into session) closeAllInternal(); - const QVariant variant = ProjectExplorer::SessionManager::value(SK_OpenSuites); + const QVariant variant = SessionManager::value(SK_OpenSuites); const Utils::FilePaths suitePaths = Utils::transform(variant.toStringList(), &Utils::FilePath::fromString); diff --git a/src/plugins/squish/squishnavigationwidget.cpp b/src/plugins/squish/squishnavigationwidget.cpp index ce965013987..cc9ec76b317 100644 --- a/src/plugins/squish/squishnavigationwidget.cpp +++ b/src/plugins/squish/squishnavigationwidget.cpp @@ -185,7 +185,7 @@ void SquishNavigationWidget::contextMenuEvent(QContextMenuEvent *event) QAction *closeAllSuites = new QAction(Tr::tr("Close All Test Suites"), &menu); menu.addAction(closeAllSuites); - connect(closeAllSuites, &QAction::triggered, this, [this] { + connect(closeAllSuites, &QAction::triggered, this, [] { if (SquishMessages::simpleQuestion(Tr::tr("Close All Test Suites"), Tr::tr("Close all test suites?" /*"\nThis will close all related files as well."*/)) @@ -296,14 +296,14 @@ void SquishNavigationWidget::onRemoveAllSharedFolderTriggered() void SquishNavigationWidget::onRecordTestCase(const QString &suiteName, const QString &testCase) { - QDialogButtonBox::StandardButton pressed = Utils::CheckableMessageBox::doNotAskAgainQuestion( - Core::ICore::dialogParent(), - Tr::tr("Record Test Case"), - Tr::tr("Do you want to record over the test case \"%1\"? The existing content will " - "be overwritten by the recorded script.").arg(testCase), - Core::ICore::settings(), - "RecordWithoutApproval"); - if (pressed != QDialogButtonBox::Yes) + QMessageBox::StandardButton pressed = Utils::CheckableMessageBox::question( + Core::ICore::dialogParent(), + Tr::tr("Record Test Case"), + Tr::tr("Do you want to record over the test case \"%1\"? The existing content will " + "be overwritten by the recorded script.") + .arg(testCase), + QString("RecordWithoutApproval")); + if (pressed != QMessageBox::Yes) return; SquishFileHandler::instance()->recordTestCase(suiteName, testCase); @@ -314,7 +314,7 @@ void SquishNavigationWidget::onNewTestCaseTriggered(const QModelIndex &index) auto settings = SquishPlugin::squishSettings(); QTC_ASSERT(settings, return); - if (!settings->squishPath.filePath().pathAppended("scriptmodules").exists()) { + if (!settings->squishPath().pathAppended("scriptmodules").exists()) { SquishMessages::criticalMessage(Tr::tr("Set up a valid Squish path to be able to create " "a new test case.\n(Edit > Preferences > Squish)")); return; diff --git a/src/plugins/squish/squishoutputpane.cpp b/src/plugins/squish/squishoutputpane.cpp index 2b2d90d1652..8c8047f47f4 100644 --- a/src/plugins/squish/squishoutputpane.cpp +++ b/src/plugins/squish/squishoutputpane.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -305,17 +306,20 @@ void SquishOutputPane::clearOldResults() void SquishOutputPane::createToolButtons() { m_expandAll = new QToolButton(m_treeView); + Utils::StyleHelper::setPanelWidget(m_expandAll); m_expandAll->setIcon(Utils::Icons::EXPAND_TOOLBAR.icon()); m_expandAll->setToolTip(Tr::tr("Expand All")); m_collapseAll = new QToolButton(m_treeView); + Utils::StyleHelper::setPanelWidget(m_collapseAll); m_collapseAll->setIcon(Utils::Icons::COLLAPSE_TOOLBAR.icon()); m_collapseAll->setToolTip(Tr::tr("Collapse All")); m_filterButton = new QToolButton(m_treeView); + Utils::StyleHelper::setPanelWidget(m_filterButton); m_filterButton->setIcon(Utils::Icons::FILTER.icon()); m_filterButton->setToolTip(Tr::tr("Filter Test Results")); - m_filterButton->setProperty("noArrow", true); + m_filterButton->setProperty(Utils::StyleHelper::C_NO_ARROW, true); m_filterButton->setAutoRaise(true); m_filterButton->setPopupMode(QToolButton::InstantPopup); m_filterMenu = new QMenu(m_filterButton); diff --git a/src/plugins/squish/squishperspective.cpp b/src/plugins/squish/squishperspective.cpp index fca93ce35db..1b762c71a41 100644 --- a/src/plugins/squish/squishperspective.cpp +++ b/src/plugins/squish/squishperspective.cpp @@ -26,10 +26,13 @@ namespace Squish { namespace Internal { -enum class IconType { StopRecord, Play, Pause, StepIn, StepOver, StepReturn, Stop }; +enum class IconType { StopRecord, Play, Pause, StepIn, StepOver, StepReturn, Stop, Inspect }; static QIcon iconForType(IconType type) { + static const Utils::Icon inspectIcon({{":/squish/images/picker.png", + Utils::Theme::IconsBaseColor}}); + switch (type) { case IconType::StopRecord: return Debugger::Icons::RECORD_ON.icon(); @@ -45,6 +48,8 @@ static QIcon iconForType(IconType type) return Debugger::Icons::STEP_OUT_TOOLBAR.icon(); case IconType::Stop: return Utils::Icons::STOP_SMALL.icon(); + case IconType::Inspect: + return inspectIcon.icon(); } return QIcon(); } @@ -91,6 +96,79 @@ QVariant LocalsItem::data(int column, int role) const return TreeItem::data(column, role); } +QVariant InspectedObjectItem::data(int column, int role) const +{ + if (role == Qt::DisplayRole || role == Qt::ToolTipRole) { + switch (column) { + case 0: return value; + case 1: return type; + } + } + return TreeItem::data(column, role); +} + +QVariant InspectedPropertyItem::data(int column, int role) const +{ + if (role ==Qt::DisplayRole || role == Qt::ToolTipRole) { + switch (column) { + case 0: return name; + case 1: return value; + } + } + return TreeItem::data(column, role); +} + +void InspectedPropertyItem::parseAndUpdateChildren() +{ + const char open = '{'; + const char close = '}'; + const char delimiter =','; + const char eq = '='; + + if (!value.startsWith(open) || !value.endsWith(close)) // only parse multi-property content + return; + + int start = 1; + int end = value.size() - 1; + do { + int endOfName = value.indexOf(eq, start); + QTC_ASSERT(endOfName != -1, return); + int innerStart = endOfName + 2; + QTC_ASSERT(innerStart < end, return); + const QString name = value.mid(start, endOfName - start).trimmed(); + if (value.at(innerStart) != open) { + int endOfItemValue = value.indexOf(delimiter, innerStart); + if (endOfItemValue == -1) + endOfItemValue = end; + const QString content = value.mid(innerStart, endOfItemValue - innerStart).trimmed(); + appendChild(new InspectedPropertyItem(name, content)); + start = endOfItemValue + 1; + } else { + int openedBraces = 1; + // advance until item's content is complete + int pos = innerStart; + do { + if (++pos > end) + break; + if (value.at(pos) == open) { + ++openedBraces; + continue; + } + if (value.at(pos) == close) { + --openedBraces; + if (openedBraces == 0) + break; + } + } while (pos < end); + ++pos; + QTC_ASSERT(pos < end, return); + const QString content = value.mid(innerStart, pos - innerStart).trimmed(); + appendChild(new InspectedPropertyItem(name, content)); + start = pos + 1; + } + } while (start < end); +} + class SquishControlBar : public QDialog { public: @@ -209,10 +287,14 @@ void SquishPerspective::initPerspective() m_stepOutAction->setEnabled(false); m_stopAction = Debugger::createStopAction(); m_stopAction->setEnabled(false); + m_inspectAction = new QAction(this); + m_inspectAction->setIcon(iconForType(IconType::Inspect)); + m_inspectAction->setToolTip(Tr::tr("Inspect")); + m_inspectAction->setEnabled(false); - QVBoxLayout *mainLayout = new QVBoxLayout; - mainLayout->setContentsMargins(0, 0, 0, 0); - mainLayout->setSpacing(1); + QVBoxLayout *localsMainLayout = new QVBoxLayout; + localsMainLayout->setContentsMargins(0, 0, 0, 0); + localsMainLayout->setSpacing(1); m_localsModel.setHeader({Tr::tr("Name"), Tr::tr("Type"), Tr::tr("Value")}); auto localsView = new Utils::TreeView; @@ -220,11 +302,45 @@ void SquishPerspective::initPerspective() localsView->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); localsView->setModel(&m_localsModel); localsView->setRootIsDecorated(true); - mainLayout->addWidget(localsView); - QWidget *mainWidget = new QWidget; - mainWidget->setObjectName("SquishLocalsView"); - mainWidget->setWindowTitle(Tr::tr("Squish Locals")); - mainWidget->setLayout(mainLayout); + localsMainLayout->addWidget(localsView); + QWidget *localsWidget = new QWidget; + localsWidget->setObjectName("SquishLocalsView"); + localsWidget->setWindowTitle(Tr::tr("Squish Locals")); + localsWidget->setLayout(localsMainLayout); + + QVBoxLayout *objectsMainLayout = new QVBoxLayout; + objectsMainLayout->setContentsMargins(0, 0, 0, 0); + objectsMainLayout->setSpacing(1); + + m_objectsModel.setHeader({Tr::tr("Object"), Tr::tr("Type")}); + m_objectsView = new Utils::TreeView; + m_objectsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_objectsView->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_objectsView->setModel(&m_objectsModel); + m_objectsView->setRootIsDecorated(true); + objectsMainLayout->addWidget(m_objectsView); + + QWidget *objectWidget = new QWidget; + objectWidget->setObjectName("SquishObjectsView"); + objectWidget->setWindowTitle(Tr::tr("Squish Objects")); + objectWidget->setLayout(objectsMainLayout); + + QVBoxLayout *propertiesMainLayout = new QVBoxLayout; + propertiesMainLayout->setContentsMargins(0, 0, 0, 0); + propertiesMainLayout->setSpacing(1); + + m_propertiesModel.setHeader({Tr::tr("Property"), Tr::tr("Value")}); + auto propertiesView = new Utils::TreeView; + propertiesView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + propertiesView->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + propertiesView->setModel(&m_propertiesModel); + propertiesView->setRootIsDecorated(true); + propertiesMainLayout->addWidget(propertiesView); + + QWidget *propertiesWidget = new QWidget; + propertiesWidget->setObjectName("SquishPropertiesView"); + propertiesWidget->setWindowTitle(Tr::tr("Squish Object Properties")); + propertiesWidget->setLayout(propertiesMainLayout); addToolBarAction(m_pausePlayAction); addToolBarAction(m_stepInAction); @@ -232,10 +348,14 @@ void SquishPerspective::initPerspective() addToolBarAction(m_stepOutAction); addToolBarAction(m_stopAction); addToolbarSeparator(); + addToolBarAction(m_inspectAction); + addToolbarSeparator(); m_status = new QLabel; addToolBarWidget(m_status); - addWindow(mainWidget, Perspective::AddToTab, nullptr, true, Qt::RightDockWidgetArea); + addWindow(objectWidget, Perspective::SplitVertical, nullptr); + addWindow(propertiesWidget, Perspective::SplitHorizontal, objectWidget); + addWindow(localsWidget, Perspective::AddToTab, nullptr, true, Qt::RightDockWidgetArea); connect(m_pausePlayAction, &QAction::triggered, this, &SquishPerspective::onPausePlayTriggered); connect(m_stepInAction, &QAction::triggered, this, [this] { @@ -250,6 +370,10 @@ void SquishPerspective::initPerspective() connect(m_stopAction, &QAction::triggered, this, &SquishPerspective::onStopTriggered); connect(m_stopRecordAction, &QAction::triggered, this, &SquishPerspective::onStopRecordTriggered); + connect(m_inspectAction, &QAction::triggered, this, [this]{ + m_inspectAction->setEnabled(false); + emit inspectTriggered(); + }); connect(SquishTools::instance(), &SquishTools::localsUpdated, this, &SquishPerspective::onLocalsUpdated); @@ -262,6 +386,35 @@ void SquishPerspective::initPerspective() SquishTools::instance()->requestExpansion(item->name); } }); + + connect(SquishTools::instance(), &SquishTools::objectPicked, + this, &SquishPerspective::onObjectPicked); + connect(SquishTools::instance(), &SquishTools::updateChildren, + this, &SquishPerspective::onUpdateChildren); + connect(SquishTools::instance(), &SquishTools::propertiesFetched, + this, &SquishPerspective::onPropertiesFetched); + connect(SquishTools::instance(), &SquishTools::autIdRetrieved, + this, [this]{ + m_autIdKnown = true; + m_inspectAction->setEnabled(true); + }); + connect(m_objectsView, &QTreeView::expanded, this, [this](const QModelIndex &idx) { + InspectedObjectItem *item = m_objectsModel.itemForIndex(idx); + if (QTC_GUARD(item)) { + if (item->expanded) + return; + item->expanded = true; + SquishTools::instance()->requestExpansionForObject(item->fullName); + } + }); + connect(m_objectsView->selectionModel(), &QItemSelectionModel::currentChanged, + this, [this](const QModelIndex ¤t){ + m_propertiesModel.clear(); + InspectedObjectItem *item = m_objectsModel.itemForIndex(current); + if (!item) + return; + SquishTools::instance()->requestPropertiesForObject(item->fullName); + }); } void SquishPerspective::onStopTriggered() @@ -269,6 +422,8 @@ void SquishPerspective::onStopTriggered() m_stopRecordAction->setEnabled(false); m_pausePlayAction->setEnabled(false); m_stopAction->setEnabled(false); + m_inspectAction->setEnabled(false); + m_autIdKnown = false; emit stopRequested(); } @@ -277,6 +432,8 @@ void SquishPerspective::onStopRecordTriggered() m_stopRecordAction->setEnabled(false); m_pausePlayAction->setEnabled(false); m_stopAction->setEnabled(false); + m_inspectAction->setEnabled(false); + m_autIdKnown = false; emit stopRecordRequested(); } @@ -326,6 +483,60 @@ void SquishPerspective::onLocalsUpdated(const QString &output) } } +void SquishPerspective::onObjectPicked(const QString &output) +{ + // "+{container=':o_QQuickView' text='2' type='Text' unnamed='1' visible='true'}\tQQuickText_QML_1" + static const QRegularExpression regex("^(?[-+])(?\\{.*\\})\t(?.+)$"); + const QRegularExpressionMatch match = regex.match(output); + if (!match.hasMatch()) + return; + const QString content = match.captured("content"); + m_objectsModel.clear(); + InspectedObjectItem *parent = m_objectsModel.rootItem(); + InspectedObjectItem *obj = new InspectedObjectItem(content, match.captured("type")); + obj->fullName = content; + if (match.captured("exp") == "+") + obj->appendChild(new InspectedObjectItem); // add pseudo child + parent->appendChild(obj); + m_inspectAction->setEnabled(true); + const QModelIndex idx = m_objectsModel.indexForItem(obj); + if (idx.isValid()) + m_objectsView->setCurrentIndex(idx); +} + +void SquishPerspective::onUpdateChildren(const QString &name, const QStringList &children) +{ + InspectedObjectItem *item = m_objectsModel.findNonRootItem([name](InspectedObjectItem *it) { + return it->fullName == name; + }); + if (!item) + return; + + item->removeChildren(); // remove former dummy child + static const QRegularExpression regex("(?[-+])(?.+)\t(?.+)"); + for (const QString &child : children) { + const QRegularExpressionMatch match = regex.match(child); + QTC_ASSERT(match.hasMatch(), continue); + const QString symbolicName = match.captured("symbolicName"); + auto childItem = new InspectedObjectItem(symbolicName, match.captured("type")); + childItem->fullName = name + '.' + symbolicName; + childItem->appendChild(new InspectedObjectItem); // add dummy child + item->appendChild(childItem); + } +} + +void SquishPerspective::onPropertiesFetched(const QStringList &properties) +{ + static const QRegularExpression regex("(?.+)=(?[-+])(?.*)"); + + for (const QString &line : properties) { + const QRegularExpressionMatch match = regex.match(line); + QTC_ASSERT(match.hasMatch(), continue); + auto item = new InspectedPropertyItem(match.captured("name"), match.captured("content")); + m_propertiesModel.rootItem()->appendChild(item); + } +} + void SquishPerspective::updateStatus(const QString &status) { m_status->setText(status); @@ -360,6 +571,12 @@ void SquishPerspective::destroyControlBar() m_controlBar = nullptr; } +void SquishPerspective::resetAutId() +{ + m_autIdKnown = false; + m_inspectAction->setEnabled(false); +} + void SquishPerspective::setPerspectiveMode(PerspectiveMode mode) { if (m_mode == mode) // ignore @@ -376,6 +593,7 @@ void SquishPerspective::setPerspectiveMode(PerspectiveMode mode) m_stepOverAction->setEnabled(false); m_stepOutAction->setEnabled(false); m_stopAction->setEnabled(true); + m_inspectAction->setEnabled(false); break; case Recording: m_stopRecordAction->setEnabled(true); @@ -386,6 +604,7 @@ void SquishPerspective::setPerspectiveMode(PerspectiveMode mode) m_stepOverAction->setEnabled(false); m_stepOutAction->setEnabled(false); m_stopAction->setEnabled(true); + m_inspectAction->setEnabled(false); break; case Interrupted: m_pausePlayAction->setEnabled(true); @@ -395,6 +614,7 @@ void SquishPerspective::setPerspectiveMode(PerspectiveMode mode) m_stepOverAction->setEnabled(true); m_stepOutAction->setEnabled(true); m_stopAction->setEnabled(true); + m_inspectAction->setEnabled(m_autIdKnown); break; case Configuring: case Querying: @@ -407,7 +627,10 @@ void SquishPerspective::setPerspectiveMode(PerspectiveMode mode) m_stepOverAction->setEnabled(false); m_stepOutAction->setEnabled(false); m_stopAction->setEnabled(false); + m_inspectAction->setEnabled(false); m_localsModel.clear(); + m_objectsModel.clear(); + m_propertiesModel.clear(); break; default: break; diff --git a/src/plugins/squish/squishperspective.h b/src/plugins/squish/squishperspective.h index 1e5c4aa515f..b1a89294332 100644 --- a/src/plugins/squish/squishperspective.h +++ b/src/plugins/squish/squishperspective.h @@ -9,6 +9,8 @@ #include +namespace Utils { class TreeView; } + namespace Squish { namespace Internal { @@ -26,6 +28,32 @@ public: bool expanded = false; }; +class InspectedObjectItem : public Utils::TreeItem +{ +public: + InspectedObjectItem() = default; + InspectedObjectItem(const QString &v, const QString &t) : value(v), type(t) {} + QVariant data(int column, int role) const override; + QString value; + QString type; + QString fullName; // FIXME this might be non-unique + bool expanded = false; +}; + +class InspectedPropertyItem : public Utils::TreeItem +{ +public: + InspectedPropertyItem() = default; + InspectedPropertyItem(const QString &n, const QString &v) + : name(n), value(v) { parseAndUpdateChildren(); } + QVariant data(int column, int role) const override; + QString name; + QString value; + bool expanded = false; +private: + void parseAndUpdateChildren(); +}; + class SquishPerspective : public Utils::Perspective { Q_OBJECT @@ -41,18 +69,23 @@ public: void showControlBar(SquishXmlOutputHandler *xmlOutputHandler); void destroyControlBar(); + void resetAutId(); signals: void stopRequested(); void stopRecordRequested(); void interruptRequested(); void runRequested(StepMode mode); + void inspectTriggered(); private: void onStopTriggered(); void onStopRecordTriggered(); void onPausePlayTriggered(); void onLocalsUpdated(const QString &output); + void onObjectPicked(const QString &output); + void onUpdateChildren(const QString &name, const QStringList &children); + void onPropertiesFetched(const QStringList &properties); QAction *m_stopRecordAction = nullptr; QAction *m_pausePlayAction = nullptr; @@ -60,10 +93,15 @@ private: QAction *m_stepOverAction = nullptr; QAction *m_stepOutAction = nullptr; QAction *m_stopAction = nullptr; + QAction *m_inspectAction = nullptr; QLabel *m_status = nullptr; class SquishControlBar *m_controlBar = nullptr; Utils::TreeModel m_localsModel; + Utils::TreeModel m_objectsModel; + Utils::TreeModel m_propertiesModel; + Utils::TreeView *m_objectsView = nullptr; PerspectiveMode m_mode = NoMode; + bool m_autIdKnown = false; friend class SquishControlBar; }; diff --git a/src/plugins/squish/squishplugin.cpp b/src/plugins/squish/squishplugin.cpp index f72f70df788..caeb787de69 100644 --- a/src/plugins/squish/squishplugin.cpp +++ b/src/plugins/squish/squishplugin.cpp @@ -43,7 +43,6 @@ public: bool initializeGlobalScripts(); SquishSettings m_squishSettings; - SquishSettingsPage m_settingsPage{&m_squishSettings}; SquishTestTreeModel m_treeModel; SquishNavigationWidgetFactory m_navigationWidgetFactory; ObjectsMapEditorFactory m_objectsMapEditorFactory; @@ -57,7 +56,6 @@ SquishPluginPrivate::SquishPluginPrivate() { qRegisterMetaType("SquishResultItem*"); - m_squishSettings.readSettings(ICore::settings()); m_outputPane = SquishOutputPane::instance(); m_squishTools = new SquishTools; initializeMenuEntries(); @@ -109,7 +107,7 @@ bool SquishPluginPrivate::initializeGlobalScripts() QTC_ASSERT(dd->m_squishTools, return false); SquishFileHandler::instance()->setSharedFolders({}); - const Utils::FilePath squishserver = dd->m_squishSettings.squishPath.filePath().pathAppended( + const Utils::FilePath squishserver = dd->m_squishSettings.squishPath().pathAppended( Utils::HostOsInfo::withExecutableSuffix("bin/squishserver")); if (!squishserver.isExecutableFile()) return false; @@ -134,8 +132,7 @@ void SquishPlugin::initialize() bool SquishPlugin::delayedInitialize() { - - connect(&dd->m_squishSettings, &SquishSettings::squishPathChanged, + connect(&dd->m_squishSettings.squishPath, &Utils::BaseAspect::changed, dd, &SquishPluginPrivate::initializeGlobalScripts); return dd->initializeGlobalScripts(); diff --git a/src/plugins/squish/squishprocessbase.cpp b/src/plugins/squish/squishprocessbase.cpp index 23d3a49aa70..744ea506c12 100644 --- a/src/plugins/squish/squishprocessbase.cpp +++ b/src/plugins/squish/squishprocessbase.cpp @@ -8,9 +8,9 @@ namespace Squish::Internal { SquishProcessBase::SquishProcessBase(QObject *parent) : QObject(parent) { - connect(&m_process, &Utils::QtcProcess::readyReadStandardError, + connect(&m_process, &Utils::Process::readyReadStandardError, this, &SquishProcessBase::onErrorOutput); - connect(&m_process, &Utils::QtcProcess::done, + connect(&m_process, &Utils::Process::done, this, &SquishProcessBase::onDone); } diff --git a/src/plugins/squish/squishprocessbase.h b/src/plugins/squish/squishprocessbase.h index 088eca8a73b..6582c95062b 100644 --- a/src/plugins/squish/squishprocessbase.h +++ b/src/plugins/squish/squishprocessbase.h @@ -5,7 +5,7 @@ #include "squishconstants.h" -#include +#include #include @@ -36,7 +36,7 @@ protected: virtual void onDone() {} virtual void onErrorOutput() {} - Utils::QtcProcess m_process; + Utils::Process m_process; private: SquishProcessState m_state = Idle; diff --git a/src/plugins/squish/squishrunnerprocess.cpp b/src/plugins/squish/squishrunnerprocess.cpp index 3fa6474e269..ca1e330a7c3 100644 --- a/src/plugins/squish/squishrunnerprocess.cpp +++ b/src/plugins/squish/squishrunnerprocess.cpp @@ -6,6 +6,7 @@ #include "squishtr.h" #include +#include #include @@ -13,6 +14,14 @@ Q_LOGGING_CATEGORY(runnerLOG, "qtc.squish.squishrunner", QtWarningMsg) namespace Squish::Internal { +static QString maskedArgument(const QString &originalArg) +{ + QString masked = originalArg; + masked.replace('\\', "\\\\"); + masked.replace(' ', "\\x20"); + return masked; +} + SquishRunnerProcess::SquishRunnerProcess(QObject *parent) : SquishProcessBase{parent} { @@ -36,6 +45,10 @@ void SquishRunnerProcess::setupProcess(RunnerMode mode) case Record: m_process.setProcessMode(Utils::ProcessMode::Writer); break; + case Inspect: + m_process.setProcessMode(Utils::ProcessMode::Writer); + m_process.setStdOutLineCallback([this](const QString &line) { onInspectorOutput(line); }); + break; } } @@ -44,6 +57,8 @@ void SquishRunnerProcess::start(const Utils::CommandLine &cmdline, const Utils:: QTC_ASSERT(m_process.state() == QProcess::NotRunning, return); m_licenseIssues = false; m_autId = 0; + m_outputMode = SingleLine; + m_multiLineContent.clear(); SquishProcessBase::start(cmdline, env); } @@ -130,11 +145,75 @@ void SquishRunnerProcess::onStdOutput(const QString &lineIn) isPrompt = true; m_autId = line.mid(7).toInt(); qCInfo(runnerLOG) << "AUT ID set" << m_autId << "(" << line << ")"; + emit autIdRetrieved(); } if (isPrompt) emit interrupted(fileName, fileLine, fileColumn); } +void SquishRunnerProcess::handleMultiLineOutput(OutputMode mode) +{ + Utils::ExecuteOnDestruction atExit([this]{ + m_multiLineContent.clear(); + m_context.clear(); + }); + + if (mode == MultiLineProperties) { + emit propertiesFetched(m_multiLineContent); + } else if (mode == MultiLineChildren) { + emit updateChildren(m_context, m_multiLineContent); + } +} + +void SquishRunnerProcess::onInspectorOutput(const QString &lineIn) +{ + QString line = lineIn; + line.chop(1); // line has a newline + if (line.startsWith("SSPY:")) + line = line.mid(5); + if (line.isEmpty()) // we have a prompt, that's fine + return; + + if (m_outputMode != SingleLine) { + const OutputMode originalMode = m_outputMode; + if (line.startsWith("@end")) { + m_outputMode = SingleLine; + if (!QTC_GUARD(line.mid(6).chopped(1) == m_context)) { // messed up output + m_multiLineContent.clear(); + m_context.clear(); + return; + } + } else { + m_multiLineContent.append(line); + } + if (m_outputMode == SingleLine) // we reached the @end + handleMultiLineOutput(originalMode); + return; + } + if (line == "@ready") + return; + if (line.startsWith("@picked: ")) { + const QString value = line.mid(9); + emit objectPicked(value); + return; + } + if (line.startsWith("@startprop")) { + m_outputMode = MultiLineProperties; + m_context = line.mid(12).chopped(1); + return; + } + if (line.startsWith("@startobj")) { + m_outputMode = MultiLineChildren; + m_context = line.mid(11).chopped(1); + return; + } + if (line.contains("license acquisition")) { + emit logOutputReceived("Inspect: " + line); + return; + } +// qDebug() << "unhandled" << line; +} + static QString cmdToString(SquishRunnerProcess::RunnerCommand cmd) { switch (cmd) { @@ -142,6 +221,7 @@ static QString cmdToString(SquishRunnerProcess::RunnerCommand cmd) case SquishRunnerProcess::EndRecord: return "endrecord\n"; case SquishRunnerProcess::Exit: return "exit\n"; case SquishRunnerProcess::Next: return "next\n"; + case SquishRunnerProcess::Pick: return "pick\n"; case SquishRunnerProcess::PrintVariables: return "print variables\n"; case SquishRunnerProcess::Return: return "return\n"; case SquishRunnerProcess::Step: return "step\n"; @@ -161,6 +241,16 @@ void SquishRunnerProcess::requestExpanded(const QString &variableName) m_process.write("print variables +" + variableName + "\n"); } +void SquishRunnerProcess::requestListObject(const QString &value) +{ + m_process.write("list objects " + maskedArgument(value) + "\n"); +} + +void SquishRunnerProcess::requestListProperties(const QString &value) +{ + m_process.write("list properties " + maskedArgument(value) + "\n"); +} + // FIXME: add/removal of breakpoints while debugging not handled yet // FIXME: enabled state of breakpoints Utils::Links SquishRunnerProcess::setBreakpoints(const QString &scriptExtension) @@ -180,8 +270,7 @@ Utils::Links SquishRunnerProcess::setBreakpoints(const QString &scriptExtension) continue; // mask backslashes and spaces - fileName.replace('\\', "\\\\"); - fileName.replace(' ', "\\x20"); + fileName = maskedArgument(fileName); auto line = gb->data(BreakpointLineColumn, Qt::DisplayRole).toInt(); QString cmd = "break "; cmd.append(fileName); diff --git a/src/plugins/squish/squishrunnerprocess.h b/src/plugins/squish/squishrunnerprocess.h index b1c98220c17..2d2ae4efd34 100644 --- a/src/plugins/squish/squishrunnerprocess.h +++ b/src/plugins/squish/squishrunnerprocess.h @@ -15,8 +15,8 @@ class SquishRunnerProcess : public SquishProcessBase { Q_OBJECT public: - enum RunnerCommand { Continue, EndRecord, Exit, Next, PrintVariables, Return, Step }; - enum RunnerMode { Run, StartAut, QueryServer, Record }; + enum RunnerCommand { Continue, EndRecord, Exit, Next, Pick, PrintVariables, Return, Step }; + enum RunnerMode { Run, StartAut, QueryServer, Record, Inspect }; enum RunnerError { InvalidSocket, MappedAutMissing }; explicit SquishRunnerProcess(QObject *parent = nullptr); @@ -33,6 +33,8 @@ public: void writeCommand(RunnerCommand cmd); void requestExpanded(const QString &variableName); + void requestListObject(const QString &value); + void requestListProperties(const QString &value); Utils::Links setBreakpoints(const QString &scriptExtension); bool lastRunHadLicenseIssues() const { return m_licenseIssues; } @@ -43,16 +45,27 @@ signals: void runnerFinished(); void interrupted(const QString &fileName, int line, int column); void localsUpdated(const QString &output); + void propertiesFetched(const QStringList &properties); + void objectPicked(const QString &output); + void updateChildren(const QString &name, const QStringList &children); void runnerError(RunnerError error); + void autIdRetrieved(); protected: void onDone() override; void onErrorOutput() override; private: + enum OutputMode { SingleLine, MultiLineChildren, MultiLineProperties }; + void onStdOutput(const QString &line); + void handleMultiLineOutput(OutputMode mode); + void onInspectorOutput(const QString &line); Utils::FilePath m_currentTestCasePath; + QStringList m_multiLineContent; + QString m_context; + OutputMode m_outputMode = SingleLine; int m_autId = 0; bool m_licenseIssues = false; std::optional m_mode; diff --git a/src/plugins/squish/squishserverprocess.cpp b/src/plugins/squish/squishserverprocess.cpp index 4d7bb2ef97f..9e4b9741433 100644 --- a/src/plugins/squish/squishserverprocess.cpp +++ b/src/plugins/squish/squishserverprocess.cpp @@ -10,7 +10,7 @@ namespace Squish::Internal { SquishServerProcess::SquishServerProcess(QObject *parent) : SquishProcessBase(parent) { - connect(&m_process, &Utils::QtcProcess::readyReadStandardOutput, + connect(&m_process, &Utils::Process::readyReadStandardOutput, this, &SquishServerProcess::onStandardOutput); } @@ -25,7 +25,7 @@ void SquishServerProcess::start(const Utils::CommandLine &commandLine, void SquishServerProcess::stop() { if (m_process.state() != QProcess::NotRunning && m_serverPort > 0) { - Utils::QtcProcess serverKiller; + Utils::Process serverKiller; QStringList args; args << "--stop" << "--port" << QString::number(m_serverPort); serverKiller.setCommand({m_process.commandLine().executable(), args}); diff --git a/src/plugins/squish/squishsettings.cpp b/src/plugins/squish/squishsettings.cpp index 144e3046083..f7758a844d3 100644 --- a/src/plugins/squish/squishsettings.cpp +++ b/src/plugins/squish/squishsettings.cpp @@ -33,13 +33,17 @@ namespace Internal { SquishSettings::SquishSettings() { + setId("A.Squish.General"); + setDisplayName(Tr::tr("General")); + setCategory(Constants::SQUISH_SETTINGS_CATEGORY); + setDisplayCategory("Squish"); + setCategoryIcon(Icon({{":/squish/images/settingscategory_squish.png", + Theme::PanelTextColorDark}}, Icon::Tint)); setSettingsGroup("Squish"); setAutoApply(false); - registerAspect(&squishPath); squishPath.setSettingsKey("SquishPath"); squishPath.setLabelText(Tr::tr("Squish path:")); - squishPath.setDisplayStyle(StringAspect::PathChooserDisplay); squishPath.setExpectedKind(PathChooser::ExistingDirectory); squishPath.setPlaceHolderText(Tr::tr("Path to Squish installation")); squishPath.setValidationFunction([this](FancyLineEdit *edit, QString *error) { @@ -54,37 +58,30 @@ SquishSettings::SquishSettings() return valid; }); - registerAspect(&licensePath); licensePath.setSettingsKey("LicensePath"); licensePath.setLabelText(Tr::tr("License path:")); - licensePath.setDisplayStyle(StringAspect::PathChooserDisplay); licensePath.setExpectedKind(PathChooser::ExistingDirectory); - registerAspect(&local); local.setSettingsKey("Local"); local.setLabel(Tr::tr("Local Server")); local.setDefaultValue(true); - registerAspect(&serverHost); serverHost.setSettingsKey("ServerHost"); serverHost.setLabelText(Tr::tr("Server host:")); serverHost.setDisplayStyle(StringAspect::LineEditDisplay); serverHost.setDefaultValue("localhost"); serverHost.setEnabled(false); - registerAspect(&serverPort); serverPort.setSettingsKey("ServerPort"); serverPort.setLabel(Tr::tr("Server Port")); serverPort.setRange(1, 65535); serverPort.setDefaultValue(9999); serverPort.setEnabled(false); - registerAspect(&verbose); verbose.setSettingsKey("Verbose"); verbose.setLabel(Tr::tr("Verbose log")); verbose.setDefaultValue(false); - registerAspect(&minimizeIDE); minimizeIDE.setSettingsKey("MinimizeIDE"); minimizeIDE.setLabel(Tr::tr("Minimize IDE")); minimizeIDE.setToolTip(Tr::tr("Minimize IDE automatically while running or recording test cases.")); @@ -94,13 +91,24 @@ SquishSettings::SquishSettings() serverHost.setEnabled(!checked); serverPort.setEnabled(!checked); }); - connect(&squishPath, &Utils::StringAspect::valueChanged, - this, &SquishSettings::squishPathChanged); + + setLayouter([this] { + using namespace Layouting; + return Form { + squishPath, br, + licensePath, br, + local, serverHost, serverPort, br, + verbose, br, + minimizeIDE, br, + }; + }); + + readSettings(); } Utils::FilePath SquishSettings::scriptsPath(Language language) const { - Utils::FilePath scripts = squishPath.filePath().pathAppended("scriptmodules"); + Utils::FilePath scripts = squishPath().pathAppended("scriptmodules"); switch (language) { case Language::Python: scripts = scripts.pathAppended("python"); break; case Language::Perl: scripts = scripts.pathAppended("perl"); break; @@ -112,35 +120,8 @@ Utils::FilePath SquishSettings::scriptsPath(Language language) const return scripts.isReadableDir() ? scripts : Utils::FilePath(); } -SquishSettingsPage::SquishSettingsPage(SquishSettings *settings) -{ - setId("A.Squish.General"); - setDisplayName(Tr::tr("General")); - setCategory(Constants::SQUISH_SETTINGS_CATEGORY); - setDisplayCategory("Squish"); - setCategoryIcon(Icon({{":/squish/images/settingscategory_squish.png", - Theme::PanelTextColorDark}}, Icon::Tint)); - - setSettings(settings); - - setLayouter([settings](QWidget *widget) { - SquishSettings &s = *settings; - using namespace Layouting; - - Grid grid { - s.squishPath, br, - s.licensePath, br, - Span {2, Row { s.local, s.serverHost, s.serverPort } }, br, - s.verbose, br, - s.minimizeIDE, br, - }; - Column { Row { grid }, st }.attachTo(widget); - }); -} - SquishServerSettings::SquishServerSettings() { - registerAspect(&autTimeout); autTimeout.setLabel(Tr::tr("Maximum startup time:")); autTimeout.setToolTip(Tr::tr("Specifies how many seconds Squish should wait for a reply from the " "AUT directly after starting it.")); @@ -148,7 +129,6 @@ SquishServerSettings::SquishServerSettings() autTimeout.setSuffix("s"); autTimeout.setDefaultValue(20); - registerAspect(&responseTimeout); responseTimeout.setLabel(Tr::tr("Maximum response time:")); responseTimeout.setToolTip(Tr::tr("Specifies how many seconds Squish should wait for a reply from " "the hooked up AUT before raising a timeout error.")); @@ -156,7 +136,6 @@ SquishServerSettings::SquishServerSettings() responseTimeout.setDefaultValue(300); responseTimeout.setSuffix("s"); - registerAspect(&postMortemWaitTime); postMortemWaitTime.setLabel(Tr::tr("Maximum post-mortem wait time:")); postMortemWaitTime.setToolTip(Tr::tr("Specifies how many seconds Squish should wait after the the " "first AUT process has exited.")); @@ -164,7 +143,6 @@ SquishServerSettings::SquishServerSettings() postMortemWaitTime.setDefaultValue(1500); postMortemWaitTime.setSuffix("ms"); - registerAspect(&animatedCursor); animatedCursor.setLabel(Tr::tr("Animate mouse cursor:")); animatedCursor.setDefaultValue(true); } @@ -251,10 +229,10 @@ void SquishServerSettings::setFromXmlOutput(const QString &output) autPaths = newSettings.autPaths; attachableAuts = newSettings.attachableAuts; licensedToolkits = newSettings.licensedToolkits; - autTimeout.setValue(newSettings.autTimeout.value()); - postMortemWaitTime.setValue(newSettings.postMortemWaitTime.value()); - responseTimeout.setValue(newSettings.responseTimeout.value()); - animatedCursor.setValue(newSettings.animatedCursor.value()); + autTimeout.setValue(newSettings.autTimeout()); + postMortemWaitTime.setValue(newSettings.postMortemWaitTime()); + responseTimeout.setValue(newSettings.responseTimeout()); + animatedCursor.setValue(newSettings.animatedCursor()); } class SquishServerItem : public TreeItem @@ -386,10 +364,10 @@ SquishServerSettingsWidget::SquishServerSettingsWidget(QWidget *parent) using namespace Layouting; Form grid { &m_applicationsView, br, - &m_serverSettings.autTimeout, - &m_serverSettings.responseTimeout, - &m_serverSettings.postMortemWaitTime, - &m_serverSettings.animatedCursor, + &m_serverSettings.autTimeout, br, + &m_serverSettings.responseTimeout, br, + &m_serverSettings.postMortemWaitTime, br, + &m_serverSettings.animatedCursor, br, }; Column buttonCol { add, @@ -626,14 +604,14 @@ QList SquishServerSettingsWidget::toConfigChangeArguments() const result.append({"addAppPath", path}); } - if (m_originalSettings.autTimeout.value() != m_serverSettings.autTimeout.value()) - result.append({"setAUTTimeout", QString::number(m_serverSettings.autTimeout.value())}); - if (m_originalSettings.responseTimeout.value() != m_serverSettings.responseTimeout.value()) - result.append({"setResponseTimeout", QString::number(m_serverSettings.responseTimeout.value())}); - if (m_originalSettings.postMortemWaitTime.value() != m_serverSettings.postMortemWaitTime.value()) - result.append({"setAUTPostMortemTimeout", QString::number(m_serverSettings.postMortemWaitTime.value())}); - if (m_originalSettings.animatedCursor.value() != m_serverSettings.animatedCursor.value()) - result.append({"setCursorAnimation", m_serverSettings.animatedCursor.value() ? QString("on") : QString("off")}); + if (m_originalSettings.autTimeout() != m_serverSettings.autTimeout()) + result.append({"setAUTTimeout", QString::number(m_serverSettings.autTimeout())}); + if (m_originalSettings.responseTimeout() != m_serverSettings.responseTimeout()) + result.append({"setResponseTimeout", QString::number(m_serverSettings.responseTimeout())}); + if (m_originalSettings.postMortemWaitTime() != m_serverSettings.postMortemWaitTime()) + result.append({"setAUTPostMortemTimeout", QString::number(m_serverSettings.postMortemWaitTime())}); + if (m_originalSettings.animatedCursor() != m_serverSettings.animatedCursor()) + result.append({"setCursorAnimation", m_serverSettings.animatedCursor() ? QString("on") : QString("off")}); return result; } diff --git a/src/plugins/squish/squishsettings.h b/src/plugins/squish/squishsettings.h index 23380521e69..0e25a4215e3 100644 --- a/src/plugins/squish/squishsettings.h +++ b/src/plugins/squish/squishsettings.h @@ -5,17 +5,10 @@ #include -#include - #include #include -QT_BEGIN_NAMESPACE -class QSettings; -QT_END_NAMESPACE - -namespace Squish { -namespace Internal { +namespace Squish::Internal { enum class Language; @@ -30,36 +23,26 @@ public: QMap attachableAuts; // name, host:port QStringList autPaths; // absolute path QStringList licensedToolkits; - Utils::IntegerAspect autTimeout; - Utils::IntegerAspect responseTimeout; - Utils::IntegerAspect postMortemWaitTime; - Utils::BoolAspect animatedCursor; + Utils::IntegerAspect autTimeout{this}; + Utils::IntegerAspect responseTimeout{this}; + Utils::IntegerAspect postMortemWaitTime{this}; + Utils::BoolAspect animatedCursor{this}; }; -class SquishSettings : public Utils::AspectContainer +class SquishSettings : public Core::PagedSettings { - Q_OBJECT public: SquishSettings(); Utils::FilePath scriptsPath(Language language) const; - Utils::StringAspect squishPath; - Utils::StringAspect licensePath; - Utils::StringAspect serverHost; - Utils::IntegerAspect serverPort; - Utils::BoolAspect local; - Utils::BoolAspect verbose; - Utils::BoolAspect minimizeIDE; - -signals: - void squishPathChanged(); -}; - -class SquishSettingsPage final : public Core::IOptionsPage -{ -public: - SquishSettingsPage(SquishSettings *settings); + Utils::FilePathAspect squishPath{this}; + Utils::FilePathAspect licensePath{this}; + Utils::StringAspect serverHost{this}; + Utils::IntegerAspect serverPort{this}; + Utils::BoolAspect local{this}; + Utils::BoolAspect verbose{this}; + Utils::BoolAspect minimizeIDE{this}; }; class SquishServerSettingsDialog : public QDialog @@ -71,5 +54,4 @@ private: void configWriteFailed(QProcess::ProcessError error); }; -} // namespace Internal -} // namespace Squish +} // Squish::Internal diff --git a/src/plugins/squish/squishtesttreeview.cpp b/src/plugins/squish/squishtesttreeview.cpp index 65e4c15dd93..d01ab0440f5 100644 --- a/src/plugins/squish/squishtesttreeview.cpp +++ b/src/plugins/squish/squishtesttreeview.cpp @@ -166,7 +166,6 @@ static bool copyScriptTemplates(const SuiteConf &suiteConf, const Utils::FilePat const SquishSettings *s = SquishPlugin::squishSettings(); QTC_ASSERT(s, return false); // copy template files - const Utils::FilePath squishPath = s->squishPath.filePath(); bool ok = destination.ensureWritableDir(); QTC_ASSERT(ok, return false); diff --git a/src/plugins/squish/squishtools.cpp b/src/plugins/squish/squishtools.cpp index bc09e2fa6cb..21450953068 100644 --- a/src/plugins/squish/squishtools.cpp +++ b/src/plugins/squish/squishtools.cpp @@ -133,6 +133,8 @@ SquishTools::SquishTools(QObject *parent) this, &SquishTools::stopRecorder); connect(&m_perspective, &SquishPerspective::runRequested, this, &SquishTools::onRunnerRunRequested); + connect(&m_perspective, &SquishPerspective::inspectTriggered, + this, &SquishTools::onInspectTriggered); } SquishTools::~SquishTools() @@ -167,7 +169,7 @@ struct SquishToolsSettings { const SquishSettings *squishSettings = SquishPlugin::squishSettings(); QTC_ASSERT(squishSettings, return); - squishPath = squishSettings->squishPath.filePath(); + squishPath = squishSettings->squishPath(); if (!squishPath.isEmpty()) { const FilePath squishBin(squishPath.pathAppended("bin")); @@ -179,12 +181,12 @@ struct SquishToolsSettings HostOsInfo::withExecutableSuffix("processcomm")).absoluteFilePath(); } - isLocalServer = squishSettings->local.value(); - serverHost = squishSettings->serverHost.value(); - serverPort = squishSettings->serverPort.value(); - verboseLog = squishSettings->verbose.value(); - licenseKeyPath = squishSettings->licensePath.filePath(); - minimizeIDE = squishSettings->minimizeIDE.value(); + isLocalServer = squishSettings->local(); + serverHost = squishSettings->serverHost(); + serverPort = squishSettings->serverPort(); + verboseLog = squishSettings->verbose(); + licenseKeyPath = squishSettings->licensePath(); + minimizeIDE = squishSettings->minimizeIDE(); } }; @@ -440,6 +442,7 @@ void SquishTools::onRunnerStopped() m_request = ServerStopRequested; qCInfo(LOG) << "Stopping server from RunnerStopped (query)"; stopSquishServer(); + return; } else if (m_request == RecordTestRequested) { if (m_secondaryRunner && m_secondaryRunner->isRunning()) { stopRecorder(); @@ -448,7 +451,12 @@ void SquishTools::onRunnerStopped() qCInfo(LOG) << "Stopping server from RunnerStopped (startaut)"; stopSquishServer(); } - } else if (m_testCases.isEmpty() || (m_squishRunnerState == RunnerState::Canceled)) { + return; + } + // below only normal run of test case(s) + exitAndResetSecondaryRunner(); + + if (m_testCases.isEmpty() || (m_squishRunnerState == RunnerState::Canceled)) { m_request = ServerStopRequested; qCInfo(LOG) << "Stopping server from RunnerStopped"; stopSquishServer(); @@ -613,6 +621,52 @@ void SquishTools::setupAndStartRecorder() m_secondaryRunner->start(cmd, squishEnvironment()); } +void SquishTools::setupAndStartInspector() +{ + QTC_ASSERT(m_primaryRunner && m_primaryRunner->autId() != 0, return); + QTC_ASSERT(!m_secondaryRunner, return); + + QStringList args; + if (!toolsSettings.isLocalServer) + args << "--host" << toolsSettings.serverHost; + args << "--port" << QString::number(m_serverProcess.port()); + args << "--debugLog" << "alpw"; // TODO make this configurable? + args << "--inspect"; + args << "--suitedir" << m_suitePath.toUserOutput(); + args << "--autid" << QString::number(m_primaryRunner->autId()); + + m_secondaryRunner = new SquishRunnerProcess(this); + m_secondaryRunner->setupProcess(SquishRunnerProcess::Inspect); + const CommandLine cmd = {toolsSettings.runnerPath, args}; + connect(m_secondaryRunner, &SquishRunnerProcess::logOutputReceived, + this, &SquishTools::logOutputReceived); + connect(m_secondaryRunner, &SquishRunnerProcess::objectPicked, + this, &SquishTools::objectPicked); + connect(m_secondaryRunner, &SquishRunnerProcess::updateChildren, + this, &SquishTools::updateChildren); + connect(m_secondaryRunner, &SquishRunnerProcess::propertiesFetched, + this, &SquishTools::propertiesFetched); + qCDebug(LOG) << "Inspector starting:" << cmd.toUserOutput(); + m_secondaryRunner->start(cmd, squishEnvironment()); +} + +void SquishTools::exitAndResetSecondaryRunner() +{ + m_perspective.resetAutId(); + if (m_secondaryRunner) { + m_secondaryRunner->writeCommand(SquishRunnerProcess::Exit); + m_secondaryRunner->deleteLater(); + m_secondaryRunner = nullptr; + } +} + +void SquishTools::onInspectTriggered() +{ + QTC_ASSERT(m_primaryRunner, return); + QTC_ASSERT(m_secondaryRunner, return); + m_secondaryRunner->writeCommand(SquishRunnerProcess::Pick); +} + void SquishTools::stopRecorder() { QTC_ASSERT(m_secondaryRunner && m_secondaryRunner->isRunning(), return); @@ -865,6 +919,7 @@ void SquishTools::handlePrompt(const QString &fileName, int line, int column) case RunnerState::CancelRequested: case RunnerState::CancelRequestedWhileInterrupted: logAndChangeRunnerState(RunnerState::Canceled); + exitAndResetSecondaryRunner(); m_primaryRunner->writeCommand(SquishRunnerProcess::Exit); clearLocationMarker(); break; @@ -885,6 +940,9 @@ void SquishTools::handlePrompt(const QString &fileName, int line, int column) const FilePath filePath = FilePath::fromUserInput(fileName); Core::EditorManager::openEditorAt({filePath, line, column}); updateLocationMarker(filePath, line); + // looks like we need to start inspector while being interrupted? + if (!m_secondaryRunner && m_primaryRunner->autId() !=0) + setupAndStartInspector(); } } else { // it's just some output coming from the server if (m_squishRunnerState == RunnerState::Interrupted && !m_requestVarsTimer) { @@ -909,6 +967,24 @@ void SquishTools::requestExpansion(const QString &name) m_primaryRunner->requestExpanded(name); } +void SquishTools::requestExpansionForObject(const QString &value) +{ + QTC_ASSERT(m_primaryRunner, return); + if (m_squishRunnerState != RunnerState::Interrupted) + return; + QTC_ASSERT(m_secondaryRunner, return); + m_secondaryRunner->requestListObject(value); +} + +void SquishTools::requestPropertiesForObject(const QString &value) +{ + QTC_ASSERT(m_primaryRunner, return); + if (m_squishRunnerState != RunnerState::Interrupted) + return; + QTC_ASSERT(m_secondaryRunner, return); + m_secondaryRunner->requestListProperties(value); +} + bool SquishTools::shutdown() { QTC_ASSERT(!m_shutdownInitiated, return true); @@ -1040,7 +1116,7 @@ void SquishTools::interruptRunner() QTC_ASSERT(m_primaryRunner, return); qint64 processId = m_primaryRunner->processId(); const CommandLine cmd(toolsSettings.processComPath, {QString::number(processId), "break"}); - QtcProcess process; + Process process; process.setCommand(cmd); process.start(); process.waitForFinished(); @@ -1056,7 +1132,7 @@ void SquishTools::terminateRunner() QTC_ASSERT(m_primaryRunner, return); qint64 processId = m_primaryRunner->processId(); const CommandLine cmd(toolsSettings.processComPath, {QString::number(processId), "terminate"}); - QtcProcess process; + Process process; process.setCommand(cmd); process.start(); process.waitForFinished(); @@ -1198,10 +1274,12 @@ bool SquishTools::setupRunnerPath() void SquishTools::setupAndStartSquishRunnerProcess(const Utils::CommandLine &cmdLine) { QTC_ASSERT(m_primaryRunner, return); - // avoid crashes on fast re-usage of QtcProcess + // avoid crashes on fast re-usage of Process m_primaryRunner->closeProcess(); if (m_request == RunTestRequested) { + connect(m_primaryRunner, &SquishRunnerProcess::autIdRetrieved, + this, &SquishTools::autIdRetrieved); // set up the file system watcher for being able to read the results.xml file m_resultsFileWatcher = new QFileSystemWatcher; // on 2nd run this directory exists and won't emit changes, so use the current subdirectory diff --git a/src/plugins/squish/squishtools.h b/src/plugins/squish/squishtools.h index 19b57a7cb42..099850f5a7c 100644 --- a/src/plugins/squish/squishtools.h +++ b/src/plugins/squish/squishtools.h @@ -9,7 +9,7 @@ #include "suiteconf.h" #include -#include +#include #include #include @@ -60,10 +60,13 @@ public: void requestSetSharedFolders(const Utils::FilePaths &sharedFolders); void writeServerSettingsChanges(const QList &changes); void requestExpansion(const QString &name); + void requestExpansionForObject(const QString &value); + void requestPropertiesForObject(const QString &value); bool shutdown(); signals: + void autIdRetrieved(); void logOutputReceived(const QString &output); void squishTestRunStarted(); void squishTestRunFinished(); @@ -71,6 +74,9 @@ signals: void configChangesFailed(QProcess::ProcessError error); void configChangesWritten(); void localsUpdated(const QString &output); + void objectPicked(const QString &output); + void updateChildren(const QString &realName, const QStringList &children); + void propertiesFetched(const QStringList &properties); void shutdownFinished(); private: @@ -103,6 +109,9 @@ private: void stopSquishServer(); void startSquishRunner(); void setupAndStartRecorder(); + void setupAndStartInspector(); + void exitAndResetSecondaryRunner(); + void onInspectTriggered(); void stopRecorder(); void queryServer(RunnerQuery query); void executeRunnerQuery(); diff --git a/src/plugins/squish/squishwizardpages.cpp b/src/plugins/squish/squishwizardpages.cpp index 118c3217f2d..70003cb2bf4 100644 --- a/src/plugins/squish/squishwizardpages.cpp +++ b/src/plugins/squish/squishwizardpages.cpp @@ -48,7 +48,6 @@ bool SquishToolkitsPageFactory::validateData(Utils::Id typeId, const QVariant &, SquishToolkitsPage::SquishToolkitsPage() { - resize(400, 300); setTitle(Tr::tr("Create New Squish Test Suite")); auto layout = new QVBoxLayout(this); @@ -113,7 +112,7 @@ bool SquishToolkitsPage::handleReject() void SquishToolkitsPage::delayedInitialize() { const auto s = SquishPlugin::squishSettings(); - const Utils::FilePath server = s->squishPath.filePath().pathAppended( + const Utils::FilePath server = s->squishPath().pathAppended( Utils::HostOsInfo::withExecutableSuffix("bin/squishserver")); if (server.isExecutableFile()) fetchServerSettings(); @@ -173,7 +172,6 @@ bool SquishScriptLanguagePageFactory::validateData(Utils::Id typeId, const QVari SquishScriptLanguagePage::SquishScriptLanguagePage() { - resize(400, 300); setTitle(Tr::tr("Create New Squish Test Suite")); auto layout = new QHBoxLayout(this); @@ -229,7 +227,6 @@ bool SquishAUTPageFactory::validateData(Utils::Id typeId, const QVariant &, QStr SquishAUTPage::SquishAUTPage() { - resize(400, 300); auto layout = new QVBoxLayout(this); m_autCombo = new QComboBox(this); layout->addWidget(m_autCombo); diff --git a/src/plugins/studiowelcome/qdsnewdialog.cpp b/src/plugins/studiowelcome/qdsnewdialog.cpp index c3f8f443729..4bd6293c19a 100644 --- a/src/plugins/studiowelcome/qdsnewdialog.cpp +++ b/src/plugins/studiowelcome/qdsnewdialog.cpp @@ -88,10 +88,10 @@ QdsNewDialog::QdsNewDialog(QWidget *parent) m_dialog->installEventFilter(this); - QObject::connect(&m_wizard, &WizardHandler::wizardCreationFailed, this, [this]() { + QObject::connect(&m_wizard, &WizardHandler::wizardCreationFailed, this, [this] { QMessageBox::critical(m_dialog, tr("New Project"), tr("Failed to initialize data.")); reject(); - delete this; + deleteLater(); }); QObject::connect(m_styleModel.data(), &StyleModel::modelAboutToBeReset, this, [this]() { diff --git a/src/plugins/studiowelcome/studiowelcomeplugin.cpp b/src/plugins/studiowelcome/studiowelcomeplugin.cpp index c53ec48c43c..b7a4725f814 100644 --- a/src/plugins/studiowelcome/studiowelcomeplugin.cpp +++ b/src/plugins/studiowelcome/studiowelcomeplugin.cpp @@ -103,7 +103,7 @@ static StudioWelcomePlugin *s_pluginInstance = nullptr; static Utils::FilePath getMainUiFileWithFallback() { - auto project = ProjectExplorer::SessionManager::startupProject(); + auto project = ProjectExplorer::ProjectManager::startupProject(); if (!project) return {}; @@ -492,8 +492,7 @@ private: void StudioWelcomePlugin::closeSplashScreen() { - Utils::CheckableMessageBox::doNotAskAgain(Core::ICore::settings(), - DO_NOT_SHOW_SPLASHSCREEN_AGAIN_KEY); + Utils::CheckableDecider(DO_NOT_SHOW_SPLASHSCREEN_AGAIN_KEY).doNotAskAgain(); if (!s_viewWindow.isNull()) s_viewWindow->deleteLater(); @@ -541,8 +540,7 @@ static bool showSplashScreen() return true; } - return Utils::CheckableMessageBox::shouldAskAgain(Core::ICore::settings(), - DO_NOT_SHOW_SPLASHSCREEN_AGAIN_KEY); + return Utils::CheckableDecider(DO_NOT_SHOW_SPLASHSCREEN_AGAIN_KEY).shouldAskAgain(); } void StudioWelcomePlugin::extensionsInitialized() @@ -669,10 +667,7 @@ bool StudioWelcomePlugin::delayedInitialize() const Utils::FilePath qmlPath = version->qmlPath(); importPaths.maybeInsert(qmlPath, QmlJS::Dialect::QmlQtQuick2); - QFutureInterface result; - - QmlJS::ModelManagerInterface::importScan(result, - QmlJS::ModelManagerInterface::workingCopy(), + QmlJS::ModelManagerInterface::importScan(QmlJS::ModelManagerInterface::workingCopy(), importPaths, modelManager, false); diff --git a/src/plugins/studiowelcome/stylemodel.cpp b/src/plugins/studiowelcome/stylemodel.cpp index 79669ff2a5f..520129f20d4 100644 --- a/src/plugins/studiowelcome/stylemodel.cpp +++ b/src/plugins/studiowelcome/stylemodel.cpp @@ -3,8 +3,8 @@ #include "stylemodel.h" -#include "utils/algorithm.h" -#include "utils/qtcassert.h" +#include +#include #include diff --git a/src/plugins/studiowelcome/wizardhandler.cpp b/src/plugins/studiowelcome/wizardhandler.cpp index ee20518e604..d6d750d39eb 100644 --- a/src/plugins/studiowelcome/wizardhandler.cpp +++ b/src/plugins/studiowelcome/wizardhandler.cpp @@ -11,8 +11,8 @@ #include -#include "utils/wizard.h" #include +#include using namespace StudioWelcome; diff --git a/src/plugins/subversion/subversionclient.cpp b/src/plugins/subversion/subversionclient.cpp index f496054da67..ac664a69b88 100644 --- a/src/plugins/subversion/subversionclient.cpp +++ b/src/plugins/subversion/subversionclient.cpp @@ -12,8 +12,7 @@ #include #include #include -#include -#include +#include #include #include @@ -35,27 +34,22 @@ using namespace VcsBase; namespace Subversion { namespace Internal { -static SubversionSettings *s_settings = nullptr; - class SubversionLogConfig : public VcsBaseEditorConfig { Q_OBJECT public: - SubversionLogConfig(SubversionSettings &settings, QToolBar *toolBar) : - VcsBaseEditorConfig(toolBar) + explicit SubversionLogConfig(QToolBar *toolBar) + : VcsBaseEditorConfig(toolBar) { mapSetting(addToggleButton("--verbose", Tr::tr("Verbose"), Tr::tr("Show files changed in each revision")), - &settings.logVerbose); + &settings().logVerbose); } }; -SubversionClient::SubversionClient(SubversionSettings *settings) : VcsBaseClient(settings) +SubversionClient::SubversionClient() : VcsBaseClient(&Internal::settings()) { - s_settings = settings; - setLogConfigCreator([settings](QToolBar *toolBar) { - return new SubversionLogConfig(*settings, toolBar); - }); + setLogConfigCreator([](QToolBar *toolBar) { return new SubversionLogConfig(toolBar); }); } bool SubversionClient::doCommit(const FilePath &repositoryRoot, @@ -102,11 +96,11 @@ Id SubversionClient::vcsEditorKind(VcsCommandTag cmd) const // Add authorization options to the command line arguments. CommandLine &operator<<(Utils::CommandLine &command, SubversionClient::AddAuthOptions) { - if (!s_settings->hasAuthentication()) + if (!settings().hasAuthentication()) return command; - const QString userName = s_settings->userName.value(); - const QString password = s_settings->password.value(); + const QString userName = settings().userName(); + const QString password = settings().password(); if (userName.isEmpty()) return command; @@ -171,7 +165,7 @@ SubversionDiffEditorController::SubversionDiffEditorController(IDocument *docume const TreeStorage diffInputStorage = inputStorage(); - const auto setupDescription = [this](QtcProcess &process) { + const auto setupDescription = [this](Process &process) { if (m_changeNumber == 0) return TaskAction::StopWithDone; setupCommand(process, {"log", "-r", QString::number(m_changeNumber)}); @@ -181,14 +175,14 @@ SubversionDiffEditorController::SubversionDiffEditorController(IDocument *docume setDescription(Tr::tr("Waiting for data...")); return TaskAction::Continue; }; - const auto onDescriptionDone = [this](const QtcProcess &process) { + const auto onDescriptionDone = [this](const Process &process) { setDescription(process.cleanedStdOut()); }; - const auto onDescriptionError = [this](const QtcProcess &) { + const auto onDescriptionError = [this](const Process &) { setDescription({}); }; - const auto setupDiff = [this](QtcProcess &process) { + const auto setupDiff = [this](Process &process) { QStringList args = QStringList{"diff"} << "--internal-diff"; if (ignoreWhitespace()) args << "-x" << "-uw"; @@ -202,19 +196,19 @@ SubversionDiffEditorController::SubversionDiffEditorController(IDocument *docume command << SubversionClient::AddAuthOptions(); process.setCommand(command); }; - const auto onDiffDone = [diffInputStorage](const QtcProcess &process) { - *diffInputStorage.activeStorage() = process.cleanedStdOut(); + const auto onDiffDone = [diffInputStorage](const Process &process) { + *diffInputStorage = process.cleanedStdOut(); }; const Group root { Storage(diffInputStorage), parallel, Group { - optional, - Process(setupDescription, onDescriptionDone, onDescriptionError) + finishAllAndDone, + ProcessTask(setupDescription, onDescriptionDone, onDescriptionError) }, Group { - Process(setupDiff, onDiffDone), + ProcessTask(setupDiff, onDiffDone), postProcessTask() } }; @@ -240,13 +234,13 @@ void SubversionDiffEditorController::setChangeNumber(int changeNumber) SubversionDiffEditorController *SubversionClient::findOrCreateDiffEditor(const QString &documentId, const FilePath &source, const QString &title, const FilePath &workingDirectory) { - auto &settings = static_cast(this->settings()); + SubversionSettings &settings = Internal::settings(); IDocument *document = DiffEditorController::findOrCreateDocument(documentId, title); auto controller = qobject_cast( DiffEditorController::controller(document)); if (!controller) { controller = new SubversionDiffEditorController(document); - controller->setVcsBinary(settings.binaryPath.filePath()); + controller->setVcsBinary(settings.binaryPath()); controller->setProcessEnvironment(processEnvironment()); controller->setWorkingDirectory(workingDirectory); } @@ -278,7 +272,7 @@ void SubversionClient::log(const FilePath &workingDir, const std::function &addAuthOptions) { auto &settings = static_cast(this->settings()); - const int logCount = settings.logCount.value(); + const int logCount = settings.logCount(); QStringList svnExtraOptions = extraOptions; if (logCount > 0) svnExtraOptions << QLatin1String("-l") << QString::number(logCount); diff --git a/src/plugins/subversion/subversionclient.h b/src/plugins/subversion/subversionclient.h index d3beec2b4c9..b2ce0f90616 100644 --- a/src/plugins/subversion/subversionclient.h +++ b/src/plugins/subversion/subversionclient.h @@ -18,7 +18,7 @@ class SubversionClient : public VcsBase::VcsBaseClient Q_OBJECT public: - SubversionClient(SubversionSettings *settings); + SubversionClient(); bool doCommit(const Utils::FilePath &repositoryRoot, const QStringList &files, diff --git a/src/plugins/subversion/subversionplugin.cpp b/src/plugins/subversion/subversionplugin.cpp index 1eedd616065..e584d981818 100644 --- a/src/plugins/subversion/subversionplugin.cpp +++ b/src/plugins/subversion/subversionplugin.cpp @@ -289,8 +289,6 @@ private: QAction *m_menuAction = nullptr; - SubversionSettingsPage m_settingsPage{&m_settings}; - public: VcsSubmitEditorFactory submitEditorFactory { submitParameters, @@ -358,7 +356,7 @@ SubversionPluginPrivate::SubversionPluginPrivate() { dd = this; - m_client = new SubversionClient(&m_settings); + m_client = new SubversionClient(); setTopicCache(new SubversionTopicCache(this)); @@ -524,7 +522,7 @@ SubversionPluginPrivate::SubversionPluginPrivate() subversionMenu->addAction(command); m_commandLocator->appendCommand(command); - connect(&m_settings, &AspectContainer::applied, this, &IVersionControl::configurationChanged); + connect(&settings(), &AspectContainer::applied, this, &IVersionControl::configurationChanged); } bool SubversionPluginPrivate::isVcsDirectory(const FilePath &fileName) const @@ -640,7 +638,7 @@ void SubversionPluginPrivate::revertAll() QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) return; // NoteL: Svn "revert ." doesn not work. - CommandLine args{m_settings.binaryPath.filePath(), {"revert"}}; + CommandLine args{settings().binaryPath(), {"revert"}}; args << SubversionClient::AddAuthOptions(); args << QLatin1String("--recursive") << state.topLevel().toString(); const auto revertResponse = runSvn(state.topLevel(), args, RunFlags::ShowStdOut); @@ -657,7 +655,7 @@ void SubversionPluginPrivate::revertCurrentFile() const VcsBasePluginState state = currentState(); QTC_ASSERT(state.hasFile(), return); - CommandLine diffArgs{m_settings.binaryPath.filePath(), {"diff"}}; + CommandLine diffArgs{settings().binaryPath(), {"diff"}}; diffArgs << SubversionClient::AddAuthOptions(); diffArgs << SubversionClient::escapeFile(state.relativeCurrentFile()); @@ -675,7 +673,7 @@ void SubversionPluginPrivate::revertCurrentFile() FileChangeBlocker fcb(state.currentFile()); // revert - CommandLine args{m_settings.binaryPath.filePath(), {"revert"}}; + CommandLine args{settings().binaryPath(), {"revert"}}; args << SubversionClient::AddAuthOptions(); args << SubversionClient::escapeFile(state.relativeCurrentFile()); @@ -736,7 +734,7 @@ void SubversionPluginPrivate::startCommit(const FilePath &workingDir, const QStr return; } - CommandLine args{m_settings.binaryPath.filePath(), {"status"}}; + CommandLine args{settings().binaryPath(), {"status"}}; args << SubversionClient::AddAuthOptions(); args << SubversionClient::escapeFiles(files); @@ -815,7 +813,7 @@ void SubversionPluginPrivate::svnStatus(const FilePath &workingDir, const QStrin { const VcsBasePluginState state = currentState(); QTC_ASSERT(state.hasTopLevel(), return); - CommandLine args{m_settings.binaryPath.filePath(), {"status"}}; + CommandLine args{settings().binaryPath(), {"status"}}; args << SubversionClient::AddAuthOptions(); if (!relativePath.isEmpty()) args << SubversionClient::escapeFile(relativePath); @@ -841,7 +839,7 @@ void SubversionPluginPrivate::updateProject() void SubversionPluginPrivate::svnUpdate(const FilePath &workingDir, const QString &relativePath) { - CommandLine args{m_settings.binaryPath.filePath(), {"update"}}; + CommandLine args{settings().binaryPath(), {"update"}}; args << SubversionClient::AddAuthOptions(); args << Constants::NON_INTERACTIVE_OPTION; if (!relativePath.isEmpty()) @@ -865,9 +863,9 @@ void SubversionPluginPrivate::vcsAnnotateHelper(const FilePath &workingDir, cons const FilePath source = VcsBaseEditor::getSource(workingDir, file); QTextCodec *codec = VcsBaseEditor::getCodec(source); - CommandLine args{m_settings.binaryPath.filePath(), {"annotate"}}; + CommandLine args{settings().binaryPath(), {"annotate"}}; args << SubversionClient::AddAuthOptions(); - if (m_settings.spaceIgnorantAnnotation.value()) + if (settings().spaceIgnorantAnnotation.value()) args << "-x" << "-uw"; if (!revision.isEmpty()) args << "-r" << revision; @@ -949,10 +947,10 @@ CommandResult SubversionPluginPrivate::runSvn(const FilePath &workingDir, const CommandLine &command, RunFlags flags, QTextCodec *outputCodec, int timeoutMutiplier) const { - if (m_settings.binaryPath.value().isEmpty()) + if (settings().binaryPath().isEmpty()) return CommandResult(ProcessResult::StartFailed, Tr::tr("No subversion executable specified.")); - const int timeoutS = m_settings.timeout.value() * timeoutMutiplier; + const int timeoutS = settings().timeout() * timeoutMutiplier; return m_client->vcsSynchronousExec(workingDir, command, flags, timeoutS, outputCodec); } @@ -1008,7 +1006,7 @@ QString SubversionPluginPrivate::synchronousTopic(const FilePath &repository) co bool SubversionPluginPrivate::vcsAdd(const FilePath &workingDir, const QString &rawFileName) { const QString file = QDir::toNativeSeparators(SubversionClient::escapeFile(rawFileName)); - CommandLine args{m_settings.binaryPath.filePath()}; + CommandLine args{settings().binaryPath()}; args << "add" << SubversionClient::AddAuthOptions() << "--parents" << file; return runSvn(workingDir, args, RunFlags::ShowStdOut).result() == ProcessResult::FinishedWithSuccess; @@ -1018,7 +1016,7 @@ bool SubversionPluginPrivate::vcsDelete(const FilePath &workingDir, const QStrin { const QString file = QDir::toNativeSeparators(SubversionClient::escapeFile(rawFileName)); - CommandLine args{m_settings.binaryPath.filePath()}; + CommandLine args{settings().binaryPath()}; args << "delete" << SubversionClient::AddAuthOptions() << "--force" << file; return runSvn(workingDir, args, RunFlags::ShowStdOut).result() @@ -1027,7 +1025,7 @@ bool SubversionPluginPrivate::vcsDelete(const FilePath &workingDir, const QStrin bool SubversionPluginPrivate::vcsMove(const FilePath &workingDir, const QString &from, const QString &to) { - CommandLine args{m_settings.binaryPath.filePath(), {"move"}}; + CommandLine args{settings().binaryPath(), {"move"}}; args << SubversionClient::AddAuthOptions() << QDir::toNativeSeparators(SubversionClient::escapeFile(from)) << QDir::toNativeSeparators(SubversionClient::escapeFile(to)); @@ -1040,7 +1038,7 @@ bool SubversionPluginPrivate::vcsCheckout(const FilePath &directory, const QByte QUrl tempUrl = QUrl::fromEncoded(url); const QString username = tempUrl.userName(); const QString password = tempUrl.password(); - CommandLine args{m_settings.binaryPath.filePath(), {"checkout"}}; + CommandLine args{settings().binaryPath(), {"checkout"}}; args << Constants::NON_INTERACTIVE_OPTION; if (!username.isEmpty()) { @@ -1087,7 +1085,7 @@ bool SubversionPluginPrivate::managesDirectory(const FilePath &directory, FilePa bool SubversionPluginPrivate::managesFile(const FilePath &workingDirectory, const QString &fileName) const { - CommandLine args{m_settings.binaryPath.filePath()}; + CommandLine args{settings().binaryPath()}; args << "status" << SubversionClient::AddAuthOptions() << QDir::toNativeSeparators(SubversionClient::escapeFile(fileName)); const QString output = runSvn(workingDirectory, args).cleanedStdOut(); @@ -1126,7 +1124,7 @@ bool SubversionPluginPrivate::isVcsFileOrDirectory(const FilePath &filePath) con bool SubversionPluginPrivate::isConfigured() const { - const FilePath binary = m_settings.binaryPath.filePath(); + const FilePath binary = settings().binaryPath(); if (binary.isEmpty()) return false; QFileInfo fi = binary.toFileInfo(); @@ -1189,7 +1187,7 @@ VcsCommand *SubversionPluginPrivate::createInitialCheckoutCommand(const QString const QString &localName, const QStringList &extraArgs) { - CommandLine args{m_settings.binaryPath.filePath()}; + CommandLine args{settings().binaryPath()}; args << "checkout"; args << SubversionClient::AddAuthOptions(); args << Subversion::Constants::NON_INTERACTIVE_OPTION << extraArgs << url << localName; diff --git a/src/plugins/subversion/subversionsettings.cpp b/src/plugins/subversion/subversionsettings.cpp index 5a275129124..9b9225ccae7 100644 --- a/src/plugins/subversion/subversionsettings.cpp +++ b/src/plugins/subversion/subversionsettings.cpp @@ -3,124 +3,105 @@ #include "subversionsettings.h" -#include "subversionclient.h" -#include "subversionplugin.h" #include "subversiontr.h" -#include -#include - -#include #include #include #include #include -#include - using namespace Utils; using namespace VcsBase; -namespace Subversion { -namespace Internal { +namespace Subversion::Internal { -// SubversionSettings +static SubversionSettings *theSettings; + +SubversionSettings &settings() +{ + return *theSettings; +} SubversionSettings::SubversionSettings() { - setAutoApply(false); + theSettings = this; + + setId(VcsBase::Constants::VCS_ID_SUBVERSION); + setDisplayName(Tr::tr("Subversion")); + setCategory(VcsBase::Constants::VCS_SETTINGS_CATEGORY); setSettingsGroup("Subversion"); - registerAspect(&binaryPath); - binaryPath.setDisplayStyle(StringAspect::PathChooserDisplay); binaryPath.setExpectedKind(PathChooser::ExistingCommand); binaryPath.setHistoryCompleter("Subversion.Command.History"); binaryPath.setDefaultValue("svn" QTC_HOST_EXE_SUFFIX); binaryPath.setDisplayName(Tr::tr("Subversion Command")); binaryPath.setLabelText(Tr::tr("Subversion command:")); - registerAspect(&useAuthentication); useAuthentication.setSettingsKey("Authentication"); - useAuthentication.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel); + useAuthentication.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBox); - registerAspect(&userName); userName.setSettingsKey("User"); userName.setDisplayStyle(StringAspect::LineEditDisplay); userName.setLabelText(Tr::tr("Username:")); - registerAspect(&password); password.setSettingsKey("Password"); password.setDisplayStyle(StringAspect::LineEditDisplay); password.setLabelText(Tr::tr("Password:")); - registerAspect(&spaceIgnorantAnnotation); spaceIgnorantAnnotation.setSettingsKey("SpaceIgnorantAnnotation"); spaceIgnorantAnnotation.setDefaultValue(true); spaceIgnorantAnnotation.setLabelText(Tr::tr("Ignore whitespace changes in annotation")); - registerAspect(&diffIgnoreWhiteSpace); diffIgnoreWhiteSpace.setSettingsKey("DiffIgnoreWhiteSpace"); - registerAspect(&logVerbose); logVerbose.setSettingsKey("LogVerbose"); - registerAspect(&logCount); logCount.setDefaultValue(1000); logCount.setLabelText(Tr::tr("Log count:")); - registerAspect(&timeout); timeout.setLabelText(Tr::tr("Timeout:")); timeout.setSuffix(Tr::tr("s")); QObject::connect(&useAuthentication, &BaseAspect::changed, this, [this] { - userName.setEnabled(useAuthentication.value()); - password.setEnabled(useAuthentication.value()); + userName.setEnabled(useAuthentication()); + password.setEnabled(useAuthentication()); }); -} -bool SubversionSettings::hasAuthentication() const -{ - return useAuthentication.value() && !userName.value().isEmpty(); -} - -SubversionSettingsPage::SubversionSettingsPage(SubversionSettings *settings) -{ - setId(VcsBase::Constants::VCS_ID_SUBVERSION); - setDisplayName(Tr::tr("Subversion")); - setCategory(VcsBase::Constants::VCS_SETTINGS_CATEGORY); - setSettings(settings); - - setLayouter([settings](QWidget *widget) { - SubversionSettings &s = *settings; + setLayouter([this] { using namespace Layouting; - Column { + return Column { Group { title(Tr::tr("Configuration")), - Column { s.binaryPath } + Column { binaryPath } }, Group { - title(Tr::tr("Authentication"), &s.useAuthentication), + title(Tr::tr("Authentication")), + useAuthentication.groupChecker(), Form { - s.userName, - s.password, + userName, br, + password, } }, Group { title(Tr::tr("Miscellaneous")), Column { - Row { s.logCount, s.timeout, st }, - s.spaceIgnorantAnnotation, + Row { logCount, timeout, st }, + spaceIgnorantAnnotation, } }, st - }.attachTo(widget); + }; }); } -} // Internal -} // Subversion +bool SubversionSettings::hasAuthentication() const +{ + return useAuthentication() && !userName().isEmpty(); +} + +} // Subversion::Internal diff --git a/src/plugins/subversion/subversionsettings.h b/src/plugins/subversion/subversionsettings.h index 01e30916956..c5bcb8e9cf5 100644 --- a/src/plugins/subversion/subversionsettings.h +++ b/src/plugins/subversion/subversionsettings.h @@ -3,11 +3,9 @@ #pragma once -#include #include -namespace Subversion { -namespace Internal { +namespace Subversion::Internal { class SubversionSettings : public VcsBase::VcsBaseSettings { @@ -16,18 +14,13 @@ public: bool hasAuthentication() const; - Utils::BoolAspect useAuthentication; - Utils::StringAspect password; - Utils::BoolAspect spaceIgnorantAnnotation; - Utils::BoolAspect diffIgnoreWhiteSpace; - Utils::BoolAspect logVerbose; + Utils::BoolAspect useAuthentication{this}; + Utils::StringAspect password{this}; + Utils::BoolAspect spaceIgnorantAnnotation{this}; + Utils::BoolAspect diffIgnoreWhiteSpace{this}; + Utils::BoolAspect logVerbose{this}; }; -class SubversionSettingsPage final : public Core::IOptionsPage -{ -public: - explicit SubversionSettingsPage(SubversionSettings *settings); -}; +SubversionSettings &settings(); -} // namespace Internal -} // namespace Subversion +} // Subversion::Internal diff --git a/src/plugins/terminal/CMakeLists.txt b/src/plugins/terminal/CMakeLists.txt new file mode 100644 index 00000000000..69da526cdc0 --- /dev/null +++ b/src/plugins/terminal/CMakeLists.txt @@ -0,0 +1,23 @@ + +add_qtc_plugin(Terminal + PLUGIN_DEPENDS Core ProjectExplorer + DEPENDS libvterm + SOURCES + celliterator.cpp celliterator.h + glyphcache.cpp glyphcache.h + keys.cpp keys.h + scrollback.cpp scrollback.h + shellintegration.cpp shellintegration.h + shellmodel.cpp shellmodel.h + terminal.qrc + terminalconstants.h + terminalicons.h + terminalpane.cpp terminalpane.h + terminalplugin.cpp + terminalprocessimpl.cpp terminalprocessimpl.h + terminalsearch.cpp terminalsearch.h + terminalsettings.cpp terminalsettings.h + terminalsurface.cpp terminalsurface.h + terminaltr.h + terminalwidget.cpp terminalwidget.h +) diff --git a/src/plugins/terminal/Terminal.json.in b/src/plugins/terminal/Terminal.json.in new file mode 100644 index 00000000000..5640012c081 --- /dev/null +++ b/src/plugins/terminal/Terminal.json.in @@ -0,0 +1,18 @@ +{ + \"Name\" : \"Terminal\", + \"Version\" : \"$$QTCREATOR_VERSION\", + \"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\", + \"Vendor\" : \"The Qt Company Ltd\", + \"Copyright\" : \"(C) $$QTCREATOR_COPYRIGHT_YEAR The Qt Company Ltd\", + \"License\" : [ \"Commercial Usage\", + \"\", + \"Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt 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.\", + \"\", + \"GNU General Public License Usage\", + \"\", + \"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html.\" + ], + \"Description\" : \"Terminal window.\", + \"Url\" : \"http://www.qt.io\", + $$dependencyList +} diff --git a/src/plugins/terminal/celliterator.cpp b/src/plugins/terminal/celliterator.cpp new file mode 100644 index 00000000000..91a70f76ea3 --- /dev/null +++ b/src/plugins/terminal/celliterator.cpp @@ -0,0 +1,94 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "celliterator.h" + +#include "terminalsurface.h" + +#include + +namespace Terminal::Internal { + +CellIterator::CellIterator(const TerminalSurface *surface, QPoint pos) + : CellIterator(surface, pos.x() + (pos.y() * surface->liveSize().width())) +{} + +CellIterator::CellIterator(const TerminalSurface *surface, int pos) + : m_state(State::INSIDE) + , m_surface(surface) +{ + m_maxpos = surface->fullSize().width() * (surface->fullSize().height()) - 1; + m_pos = qMax(0, qMin(m_maxpos + 1, pos)); + + if (m_pos == 0) { + m_state = State::BEGIN; + } else if (m_pos == m_maxpos + 1) { + m_state = State::END; + } + + if (m_state != State::END) + updateChar(); +} + +CellIterator::CellIterator(const TerminalSurface *surface) + : m_state(State::END) + , m_surface(surface) +{ + m_maxpos = surface->fullSize().width() * (surface->fullSize().height()) - 1; + m_pos = m_maxpos + 1; +} + +QPoint CellIterator::gridPos() const +{ + return m_surface->posToGrid(m_pos); +} + +bool CellIterator::updateChar() +{ + QPoint cell = m_surface->posToGrid(m_pos); + m_char = m_surface->fetchCharAt(cell.x(), cell.y()); + return m_char != 0; +} + +CellIterator &CellIterator::operator-=(int n) +{ + if (n == 0) + return *this; + + if (m_pos - n < 0) + throw new std::runtime_error("-= n too big!"); + + m_pos -= n; + + while (!updateChar() && m_pos > 0 && m_skipZeros) + m_pos--; + + m_state = State::INSIDE; + + if (m_pos == 0) { + m_state = State::BEGIN; + } + + return *this; +} + +CellIterator &CellIterator::operator+=(int n) +{ + if (n == 0) + return *this; + + if (m_pos + n < m_maxpos + 1) { + m_state = State::INSIDE; + m_pos += n; + while (!updateChar() && m_pos < (m_maxpos + 1) && m_skipZeros) + m_pos++; + + if (m_pos == m_maxpos + 1) + m_state = State::END; + } else { + *this = m_surface->end(); + } + return *this; +} + +} // namespace Terminal::Internal diff --git a/src/plugins/terminal/celliterator.h b/src/plugins/terminal/celliterator.h new file mode 100644 index 00000000000..c246aaa3114 --- /dev/null +++ b/src/plugins/terminal/celliterator.h @@ -0,0 +1,97 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +#include + +namespace Terminal::Internal { + +class TerminalSurface; + +class CellIterator +{ +public: + using iterator_category = std::bidirectional_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = std::u32string::value_type; + using pointer = std::u32string::value_type *; + // We need to return copies for std::reverse_iterator to work + using reference = std::u32string::value_type; + + enum class State { BEGIN, INSIDE, END } m_state{}; + +public: + CellIterator(const TerminalSurface *surface, QPoint pos); + CellIterator(const TerminalSurface *surface, int pos); + CellIterator(const TerminalSurface *surface); + +public: + QPoint gridPos() const; + +public: + CellIterator &operator-=(int n); + CellIterator &operator+=(int n); + + reference operator*() const { return m_char; } + pointer operator->() { return &m_char; } + + CellIterator &operator++() { return *this += 1; } + CellIterator operator++(int) + { + CellIterator tmp = *this; + ++(*this); + return tmp; + } + + CellIterator &operator--() { return *this -= 1; } + CellIterator operator--(int) + { + CellIterator tmp = *this; + --(*this); + return tmp; + } + + bool operator!=(const CellIterator &other) const + { + if (other.m_state != m_state) + return true; + + if (other.m_pos != m_pos) + return true; + + return false; + } + + bool operator==(const CellIterator &other) const { return !operator!=(other); } + + CellIterator operator-(int n) const + { + CellIterator result = *this; + result -= n; + return result; + } + + CellIterator operator+(int n) const + { + CellIterator result = *this; + result += n; + return result; + } + + int position() const { return m_pos; } + void setSkipZeros(bool skipZeros) { m_skipZeros = skipZeros; } + +private: + bool updateChar(); + + const TerminalSurface *m_surface{nullptr}; + int m_pos{-1}; + int m_maxpos{-1}; + bool m_skipZeros{false}; + mutable std::u32string::value_type m_char; +}; + +} // namespace Terminal::Internal diff --git a/src/plugins/terminal/glyphcache.cpp b/src/plugins/terminal/glyphcache.cpp new file mode 100644 index 00000000000..72a0fd7b9d1 --- /dev/null +++ b/src/plugins/terminal/glyphcache.cpp @@ -0,0 +1,48 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "glyphcache.h" + +#include + +namespace Terminal::Internal { + +size_t qHash(const GlyphCacheKey &key, size_t seed = 0) +{ + return qHash(key.font, seed) ^ qHash(key.text, seed); +} + +GlyphCache &GlyphCache::instance() +{ + static GlyphCache cache(5000); + return cache; +} + +const QGlyphRun *GlyphCache::get(const QFont &font, const QString &text) +{ + GlyphCacheKey key{font, text}; + if (auto *run = object(key)) + return run; + + QTextLayout layout; + + layout.setText(text); + layout.setFont(font); + + layout.beginLayout(); + layout.createLine().setNumColumns(std::numeric_limits::max()); + layout.endLayout(); + + if (layout.lineCount() > 0) { + const auto &line = layout.lineAt(0); + const auto runs = line.glyphRuns(); + if (!runs.isEmpty()) { + QGlyphRun *run = new QGlyphRun(layout.lineAt(0).glyphRuns().first()); + insert(key, run); + return run; + } + } + return nullptr; +} + +} // namespace Terminal::Internal diff --git a/src/plugins/terminal/glyphcache.h b/src/plugins/terminal/glyphcache.h new file mode 100644 index 00000000000..60701098f5f --- /dev/null +++ b/src/plugins/terminal/glyphcache.h @@ -0,0 +1,34 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include +#include +#include +#include + +namespace Terminal::Internal { + +struct GlyphCacheKey +{ + QFont font; + QString text; + + bool operator==(const GlyphCacheKey &other) const + { + return font == other.font && text == other.text; + } +}; + +class GlyphCache : public QCache +{ +public: + using QCache::QCache; + + static GlyphCache &instance(); + + const QGlyphRun *get(const QFont &font, const QString &text); +}; + +} // namespace Terminal::Internal diff --git a/src/plugins/terminal/images/settingscategory_terminal.png b/src/plugins/terminal/images/settingscategory_terminal.png new file mode 100644 index 00000000000..6e8c7167787 Binary files /dev/null and b/src/plugins/terminal/images/settingscategory_terminal.png differ diff --git a/src/plugins/terminal/images/settingscategory_terminal@2x.png b/src/plugins/terminal/images/settingscategory_terminal@2x.png new file mode 100644 index 00000000000..71e292d8bc1 Binary files /dev/null and b/src/plugins/terminal/images/settingscategory_terminal@2x.png differ diff --git a/src/plugins/terminal/images/terminal.png b/src/plugins/terminal/images/terminal.png new file mode 100644 index 00000000000..0a1dd311ec8 Binary files /dev/null and b/src/plugins/terminal/images/terminal.png differ diff --git a/src/plugins/terminal/images/terminal@2x.png b/src/plugins/terminal/images/terminal@2x.png new file mode 100644 index 00000000000..da36b36721c Binary files /dev/null and b/src/plugins/terminal/images/terminal@2x.png differ diff --git a/src/plugins/terminal/keys.cpp b/src/plugins/terminal/keys.cpp new file mode 100644 index 00000000000..f6a7a91b13d --- /dev/null +++ b/src/plugins/terminal/keys.cpp @@ -0,0 +1,83 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "keys.h" + +namespace Terminal::Internal { + +VTermModifier qtModifierToVTerm(Qt::KeyboardModifiers mod) +{ + int ret = VTERM_MOD_NONE; + + if (mod & Qt::ShiftModifier) + ret |= VTERM_MOD_SHIFT; + + if (mod & Qt::AltModifier) + ret |= VTERM_MOD_ALT; + +#ifdef Q_OS_DARWIN + if (mod & Qt::MetaModifier) + ret |= VTERM_MOD_CTRL; +#else + if (mod & Qt::ControlModifier) + ret |= VTERM_MOD_CTRL; +#endif + + return static_cast(ret); +} + +VTermKey qtKeyToVTerm(Qt::Key key, bool keypad) +{ + if (key >= Qt::Key_F1 && key <= Qt::Key_F35) + return static_cast(VTERM_KEY_FUNCTION_0 + key - Qt::Key_F1 + 1); + + switch (key) { + case Qt::Key_Return: + return VTERM_KEY_ENTER; + case Qt::Key_Tab: + return VTERM_KEY_TAB; + case Qt::Key_Backspace: + return VTERM_KEY_BACKSPACE; + case Qt::Key_Escape: + return VTERM_KEY_ESCAPE; + case Qt::Key_Up: + return VTERM_KEY_UP; + case Qt::Key_Down: + return VTERM_KEY_DOWN; + case Qt::Key_Left: + return VTERM_KEY_LEFT; + case Qt::Key_Right: + return VTERM_KEY_RIGHT; + case Qt::Key_Insert: + return VTERM_KEY_INS; + case Qt::Key_Delete: + return VTERM_KEY_DEL; + case Qt::Key_Home: + return VTERM_KEY_HOME; + case Qt::Key_End: + return VTERM_KEY_END; + case Qt::Key_PageUp: + return VTERM_KEY_PAGEUP; + case Qt::Key_PageDown: + return VTERM_KEY_PAGEDOWN; + case Qt::Key_multiply: + return keypad ? VTERM_KEY_KP_MULT : VTERM_KEY_NONE; + case Qt::Key_Plus: + return keypad ? VTERM_KEY_KP_PLUS : VTERM_KEY_NONE; + case Qt::Key_Comma: + return keypad ? VTERM_KEY_KP_COMMA : VTERM_KEY_NONE; + case Qt::Key_Minus: + return keypad ? VTERM_KEY_KP_MINUS : VTERM_KEY_NONE; + case Qt::Key_Period: + return keypad ? VTERM_KEY_KP_PERIOD : VTERM_KEY_NONE; + case Qt::Key_Slash: + return keypad ? VTERM_KEY_KP_DIVIDE : VTERM_KEY_NONE; + case Qt::Key_Enter: + return keypad ? VTERM_KEY_KP_ENTER : VTERM_KEY_NONE; + case Qt::Key_Equal: + return keypad ? VTERM_KEY_KP_EQUAL : VTERM_KEY_NONE; + default: + return VTERM_KEY_NONE; + } +} +} // namespace Terminal::Internal diff --git a/src/plugins/terminal/keys.h b/src/plugins/terminal/keys.h new file mode 100644 index 00000000000..f3df9330013 --- /dev/null +++ b/src/plugins/terminal/keys.h @@ -0,0 +1,15 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +#include + +namespace Terminal::Internal { + +VTermKey qtKeyToVTerm(Qt::Key key, bool keypad); +VTermModifier qtModifierToVTerm(Qt::KeyboardModifiers mod); + +} // namespace Terminal::Internal diff --git a/src/plugins/terminal/scrollback.cpp b/src/plugins/terminal/scrollback.cpp new file mode 100644 index 00000000000..e22d5fa2436 --- /dev/null +++ b/src/plugins/terminal/scrollback.cpp @@ -0,0 +1,61 @@ +// Copyright (c) 2020, Justin Bronder +// Copied and modified from: https://github.com/jsbronder/sff +// SPDX-License-Identifier: BSD-3-Clause + +#include "scrollback.h" + +#include +#include +#include + +namespace Terminal::Internal { + +Scrollback::Line::Line(int cols, const VTermScreenCell *cells) + : m_cols(cols) + , m_cells(std::make_unique(cols)) +{ + memcpy(m_cells.get(), cells, cols * sizeof(cells[0])); +} + +const VTermScreenCell *Scrollback::Line::cell(int i) const +{ + assert(i >= 0 && i < m_cols); + return &m_cells[i]; +} + +Scrollback::Scrollback(size_t capacity) + : m_capacity(capacity) +{} + +void Scrollback::emplace(int cols, const VTermScreenCell *cells) +{ + m_deque.emplace_front(cols, cells); + while (m_deque.size() > m_capacity) { + m_deque.pop_back(); + } +} + +void Scrollback::popto(int cols, VTermScreenCell *cells) +{ + const Line &sbl = m_deque.front(); + + int ncells = cols; + if (ncells > sbl.cols()) + ncells = sbl.cols(); + + memcpy(cells, sbl.cells(), sizeof(cells[0]) * ncells); + for (size_t i = ncells; i < static_cast(cols); ++i) { + cells[i].chars[0] = '\0'; + cells[i].width = 1; + cells[i].bg = cells[ncells - 1].bg; + } + + m_deque.pop_front(); +} + +void Scrollback::clear() +{ + m_deque.clear(); +} + +} // namespace Terminal::Internal diff --git a/src/plugins/terminal/scrollback.h b/src/plugins/terminal/scrollback.h new file mode 100644 index 00000000000..9ca71eec615 --- /dev/null +++ b/src/plugins/terminal/scrollback.h @@ -0,0 +1,57 @@ +// Copyright (c) 2020, Justin Bronder +// Copied and modified from: https://github.com/jsbronder/sff +// SPDX-License-Identifier: BSD-3-Clause + +#pragma once + +#include + +#include +#include +#include + +#include +#include + +namespace Terminal::Internal { + +class Scrollback +{ +public: + class Line + { + public: + Line(int cols, const VTermScreenCell *cells); + Line(Line &&other) = default; + Line() = delete; + + int cols() const { return m_cols; }; + const VTermScreenCell *cell(int i) const; + const VTermScreenCell *cells() const { return &m_cells[0]; }; + + private: + int m_cols; + std::unique_ptr m_cells; + }; + +public: + Scrollback(size_t capacity); + Scrollback() = delete; + + int capacity() const { return static_cast(m_capacity); }; + int size() const { return static_cast(m_deque.size()); }; + + const Line &line(size_t index) const { return m_deque.at(index); }; + const std::deque &lines() const { return m_deque; }; + + void emplace(int cols, const VTermScreenCell *cells); + void popto(int cols, VTermScreenCell *cells); + + void clear(); + +private: + size_t m_capacity; + std::deque m_deque; +}; + +} // namespace Terminal::Internal diff --git a/src/plugins/terminal/shellintegration.cpp b/src/plugins/terminal/shellintegration.cpp new file mode 100644 index 00000000000..d8e26f94ce5 --- /dev/null +++ b/src/plugins/terminal/shellintegration.cpp @@ -0,0 +1,164 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "shellintegration.h" + +#include +#include +#include + +#include + +Q_LOGGING_CATEGORY(integrationLog, "qtc.terminal.shellintegration", QtWarningMsg) + +using namespace Utils; + +namespace Terminal { + +struct FileToCopy +{ + FilePath source; + QString destName; +}; + +// clang-format off +struct +{ + struct + { + FilePath rcFile{":/terminal/shellintegrations/shellintegration-bash.sh"}; + } bash; + struct + { + QList files{ + {":/terminal/shellintegrations/shellintegration-env.zsh", ".zshenv"}, + {":/terminal/shellintegrations/shellintegration-login.zsh", ".zlogin"}, + {":/terminal/shellintegrations/shellintegration-profile.zsh", ".zprofile"}, + {":/terminal/shellintegrations/shellintegration-rc.zsh", ".zshrc"} + }; + } zsh; + struct + { + FilePath script{":/terminal/shellintegrations/shellintegration.ps1"}; + } pwsh; + struct + { + FilePath script{":/terminal/shellintegrations/shellintegration-clink.lua"}; + } clink; + +} filesToCopy; +// clang-format on + +bool ShellIntegration::canIntegrate(const Utils::CommandLine &cmdLine) +{ + if (cmdLine.executable().needsDevice()) + return false; // TODO: Allow integration for remote shells + + if (cmdLine.executable().baseName() == "zsh") + return true; + + if (!cmdLine.arguments().isEmpty() && cmdLine.arguments() != "-l") + return false; + + if (cmdLine.executable().baseName() == "bash") + return true; + + if (cmdLine.executable().baseName() == "pwsh" + || cmdLine.executable().baseName() == "powershell") { + return true; + } + + if (cmdLine.executable().baseName() == "cmd") + return true; + + return false; +} + +void ShellIntegration::onOsc(int cmd, const VTermStringFragment &fragment) +{ + QString d = QString::fromLocal8Bit(fragment.str, fragment.len); + const auto [command, data] = Utils::splitAtFirst(d, ';'); + + if (cmd == 1337) { + const auto [key, value] = Utils::splitAtFirst(command, '='); + if (key == QStringView(u"CurrentDir")) + emit currentDirChanged(FilePath::fromUserInput(value.toString()).path()); + + } else if (cmd == 7) { + emit currentDirChanged(FilePath::fromUserInput(d).path()); + } else if (cmd == 133) { + qCDebug(integrationLog) << "OSC 133:" << data; + } else if (cmd == 633 && command.length() == 1) { + if (command[0] == 'E') { + CommandLine cmdLine = CommandLine::fromUserInput(data.toString()); + emit commandChanged(cmdLine); + } else if (command[0] == 'D') { + emit commandChanged({}); + } else if (command[0] == 'P') { + const auto [key, value] = Utils::splitAtFirst(data, '='); + if (key == QStringView(u"Cwd")) + emit currentDirChanged(value.toString()); + } + } +} + +void ShellIntegration::prepareProcess(Utils::Process &process) +{ + Environment env = process.environment().hasChanges() ? process.environment() + : Environment::systemEnvironment(); + CommandLine cmd = process.commandLine(); + + if (!canIntegrate(cmd)) + return; + + env.set("VSCODE_INJECTION", "1"); + env.set("TERM_PROGRAM", "vscode"); + + if (cmd.executable().baseName() == "bash") { + const FilePath rcPath = filesToCopy.bash.rcFile; + const FilePath tmpRc = FilePath::fromUserInput( + m_tempDir.filePath(filesToCopy.bash.rcFile.fileName())); + rcPath.copyFile(tmpRc); + + CommandLine newCmd = {cmd.executable(), {"--init-file", tmpRc.nativePath()}}; + + if (cmd.arguments() == "-l") + newCmd.addArg("-l"); + + cmd = newCmd; + } else if (cmd.executable().baseName() == "zsh") { + for (const FileToCopy &file : filesToCopy.zsh.files) { + const auto copyResult = file.source.copyFile( + FilePath::fromUserInput(m_tempDir.filePath(file.destName))); + QTC_ASSERT_EXPECTED(copyResult, return); + } + + const Utils::FilePath originalZdotDir = FilePath::fromUserInput( + env.value_or("ZDOTDIR", QDir::homePath())); + + env.set("ZDOTDIR", m_tempDir.path()); + env.set("USER_ZDOTDIR", originalZdotDir.nativePath()); + } else if (cmd.executable().baseName() == "pwsh" + || cmd.executable().baseName() == "powershell") { + const FilePath rcPath = filesToCopy.pwsh.script; + const FilePath tmpRc = FilePath::fromUserInput( + m_tempDir.filePath(filesToCopy.pwsh.script.fileName())); + rcPath.copyFile(tmpRc); + + cmd.addArgs(QString("-noexit -command try { . \"%1\" } catch {}{1}").arg(tmpRc.nativePath()), + CommandLine::Raw); + } else if (cmd.executable().baseName() == "cmd") { + const FilePath rcPath = filesToCopy.clink.script; + const FilePath tmpRc = FilePath::fromUserInput( + m_tempDir.filePath(filesToCopy.clink.script.fileName())); + rcPath.copyFile(tmpRc); + + env.set("CLINK_HISTORY_LABEL", "QtCreator"); + env.appendOrSet("CLINK_PATH", tmpRc.parentDir().nativePath(), ";"); + } + + process.setCommand(cmd); + process.setEnvironment(env); +} + +} // namespace Terminal diff --git a/src/plugins/terminal/shellintegration.h b/src/plugins/terminal/shellintegration.h new file mode 100644 index 00000000000..a4a813c8a65 --- /dev/null +++ b/src/plugins/terminal/shellintegration.h @@ -0,0 +1,34 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH +// Qt-GPL-exception-1.0 + +#pragma once + +#include +#include + +#include + +#include + +namespace Terminal { + +class ShellIntegration : public QObject +{ + Q_OBJECT +public: + static bool canIntegrate(const Utils::CommandLine &cmdLine); + + void onOsc(int cmd, const VTermStringFragment &fragment); + + void prepareProcess(Utils::Process &process); + +signals: + void commandChanged(const Utils::CommandLine &command); + void currentDirChanged(const QString &dir); + +private: + QTemporaryDir m_tempDir; +}; + +} // namespace Terminal diff --git a/src/plugins/terminal/shellintegrations/shellintegration-bash.sh b/src/plugins/terminal/shellintegrations/shellintegration-bash.sh new file mode 100755 index 00000000000..7db188be08e --- /dev/null +++ b/src/plugins/terminal/shellintegrations/shellintegration-bash.sh @@ -0,0 +1,252 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# SPDX-License-Identifier: MIT + + +# Prevent the script recursing when setting up +if [[ -n "$VSCODE_SHELL_INTEGRATION" ]]; then + builtin return +fi + +VSCODE_SHELL_INTEGRATION=1 + +# Run relevant rc/profile only if shell integration has been injected, not when run manually +if [ "$VSCODE_INJECTION" == "1" ]; then + if [ -z "$VSCODE_SHELL_LOGIN" ]; then + if [ -r ~/.bashrc ]; then + . ~/.bashrc + fi + else + # Imitate -l because --init-file doesn't support it: + # run the first of these files that exists + if [ -r /etc/profile ]; then + . /etc/profile + fi + # exceute the first that exists + if [ -r ~/.bash_profile ]; then + . ~/.bash_profile + elif [ -r ~/.bash_login ]; then + . ~/.bash_login + elif [ -r ~/.profile ]; then + . ~/.profile + fi + builtin unset VSCODE_SHELL_LOGIN + + # Apply any explicit path prefix (see #99878) + if [ -n "$VSCODE_PATH_PREFIX" ]; then + export PATH=$VSCODE_PATH_PREFIX$PATH + builtin unset VSCODE_PATH_PREFIX + fi + fi + builtin unset VSCODE_INJECTION +fi + +if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then + builtin return +fi + +__vsc_get_trap() { + # 'trap -p DEBUG' outputs a shell command like `trap -- '…shellcode…' DEBUG`. + # The terms are quoted literals, but are not guaranteed to be on a single line. + # (Consider a trap like $'echo foo\necho \'bar\''). + # To parse, we splice those terms into an expression capturing them into an array. + # This preserves the quoting of those terms: when we `eval` that expression, they are preserved exactly. + # This is different than simply exploding the string, which would split everything on IFS, oblivious to quoting. + builtin local -a terms + builtin eval "terms=( $(trap -p "${1:-DEBUG}") )" + # |________________________| + # | + # \-------------------*--------------------/ + # terms=( trap -- '…arbitrary shellcode…' DEBUG ) + # |____||__| |_____________________| |_____| + # | | | | + # 0 1 2 3 + # | + # \--------*----/ + builtin printf '%s' "${terms[2]:-}" +} + +# The property (P) and command (E) codes embed values which require escaping. +# Backslashes are doubled. Non-alphanumeric characters are converted to escaped hex. +__vsc_escape_value() { + # Process text byte by byte, not by codepoint. + builtin local LC_ALL=C str="${1}" i byte token out='' + + for (( i=0; i < "${#str}"; ++i )); do + byte="${str:$i:1}" + + # Escape backslashes and semi-colons + if [ "$byte" = "\\" ]; then + token="\\\\" + elif [ "$byte" = ";" ]; then + token="\\x3b" + else + token="$byte" + fi + + out+="$token" + done + + builtin printf '%s\n' "${out}" +} + +# Send the IsWindows property if the environment looks like Windows +if [[ "$(uname -s)" =~ ^CYGWIN*|MINGW*|MSYS* ]]; then + builtin printf '\e]633;P;IsWindows=True\a' +fi + +# Allow verifying $BASH_COMMAND doesn't have aliases resolved via history when the right HISTCONTROL +# configuration is used +if [[ "$HISTCONTROL" =~ .*(erasedups|ignoreboth|ignoredups).* ]]; then + __vsc_history_verify=0 +else + __vsc_history_verify=1 +fi + +__vsc_initialized=0 +__vsc_original_PS1="$PS1" +__vsc_original_PS2="$PS2" +__vsc_custom_PS1="" +__vsc_custom_PS2="" +__vsc_in_command_execution="1" +__vsc_current_command="" + +__vsc_prompt_start() { + builtin printf '\e]633;A\a' +} + +__vsc_prompt_end() { + builtin printf '\e]633;B\a' +} + +__vsc_update_cwd() { + builtin printf '\e]633;P;Cwd=%s\a' "$(__vsc_escape_value "$PWD")" +} + +__vsc_command_output_start() { + builtin printf '\e]633;C\a' + builtin printf '\e]633;E;%s\a' "$(__vsc_escape_value "${__vsc_current_command}")" +} + +__vsc_continuation_start() { + builtin printf '\e]633;F\a' +} + +__vsc_continuation_end() { + builtin printf '\e]633;G\a' +} + +__vsc_command_complete() { + if [ "$__vsc_current_command" = "" ]; then + builtin printf '\e]633;D\a' + else + builtin printf '\e]633;D;%s\a' "$__vsc_status" + fi + __vsc_update_cwd +} +__vsc_update_prompt() { + # in command execution + if [ "$__vsc_in_command_execution" = "1" ]; then + # Wrap the prompt if it is not yet wrapped, if the PS1 changed this this was last set it + # means the user re-exported the PS1 so we should re-wrap it + if [[ "$__vsc_custom_PS1" == "" || "$__vsc_custom_PS1" != "$PS1" ]]; then + __vsc_original_PS1=$PS1 + __vsc_custom_PS1="\[$(__vsc_prompt_start)\]$__vsc_original_PS1\[$(__vsc_prompt_end)\]" + PS1="$__vsc_custom_PS1" + fi + if [[ "$__vsc_custom_PS2" == "" || "$__vsc_custom_PS2" != "$PS2" ]]; then + __vsc_original_PS2=$PS2 + __vsc_custom_PS2="\[$(__vsc_continuation_start)\]$__vsc_original_PS2\[$(__vsc_continuation_end)\]" + PS2="$__vsc_custom_PS2" + fi + __vsc_in_command_execution="0" + fi +} + +__vsc_precmd() { + __vsc_command_complete "$__vsc_status" + __vsc_current_command="" + __vsc_update_prompt +} + +__vsc_preexec() { + __vsc_initialized=1 + if [[ ! "$BASH_COMMAND" =~ ^__vsc_prompt* ]]; then + # Use history if it's available to verify the command as BASH_COMMAND comes in with aliases + # resolved + if [ "$__vsc_history_verify" = "1" ]; then + __vsc_current_command="$(builtin history 1 | sed 's/ *[0-9]* *//')" + else + __vsc_current_command=$BASH_COMMAND + fi + else + __vsc_current_command="" + fi + __vsc_command_output_start +} + +# Debug trapping/preexec inspired by starship (ISC) +if [[ -n "${bash_preexec_imported:-}" ]]; then + __vsc_preexec_only() { + if [ "$__vsc_in_command_execution" = "0" ]; then + __vsc_in_command_execution="1" + __vsc_preexec + fi + } + precmd_functions+=(__vsc_prompt_cmd) + preexec_functions+=(__vsc_preexec_only) +else + __vsc_dbg_trap="$(__vsc_get_trap DEBUG)" + + if [[ -z "$__vsc_dbg_trap" ]]; then + __vsc_preexec_only() { + if [ "$__vsc_in_command_execution" = "0" ]; then + __vsc_in_command_execution="1" + __vsc_preexec + fi + } + trap '__vsc_preexec_only "$_"' DEBUG + elif [[ "$__vsc_dbg_trap" != '__vsc_preexec "$_"' && "$__vsc_dbg_trap" != '__vsc_preexec_all "$_"' ]]; then + __vsc_preexec_all() { + if [ "$__vsc_in_command_execution" = "0" ]; then + __vsc_in_command_execution="1" + builtin eval "${__vsc_dbg_trap}" + __vsc_preexec + fi + } + trap '__vsc_preexec_all "$_"' DEBUG + fi +fi + +__vsc_update_prompt + +__vsc_restore_exit_code() { + return "$1" +} + +__vsc_prompt_cmd_original() { + __vsc_status="$?" + __vsc_restore_exit_code "${__vsc_status}" + # Evaluate the original PROMPT_COMMAND similarly to how bash would normally + # See https://unix.stackexchange.com/a/672843 for technique + for cmd in "${__vsc_original_prompt_command[@]}"; do + eval "${cmd:-}" + done + __vsc_precmd +} + +__vsc_prompt_cmd() { + __vsc_status="$?" + __vsc_precmd +} + +# PROMPT_COMMAND arrays and strings seem to be handled the same (handling only the first entry of +# the array?) +__vsc_original_prompt_command=$PROMPT_COMMAND + +if [[ -z "${bash_preexec_imported:-}" ]]; then + if [[ -n "$__vsc_original_prompt_command" && "$__vsc_original_prompt_command" != "__vsc_prompt_cmd" ]]; then + PROMPT_COMMAND=__vsc_prompt_cmd_original + else + PROMPT_COMMAND=__vsc_prompt_cmd + fi +fi diff --git a/src/plugins/terminal/shellintegrations/shellintegration-clink.lua b/src/plugins/terminal/shellintegrations/shellintegration-clink.lua new file mode 100644 index 00000000000..ff9d5f3facc --- /dev/null +++ b/src/plugins/terminal/shellintegrations/shellintegration-clink.lua @@ -0,0 +1,52 @@ +-- Copyright (c) 2023 Chris Antos +-- SPDX-License-Identifier: MIT + +-- luacheck: globals vscode_shell_integration NONL +if vscode_shell_integration == nil then + vscode_shell_integration = true +end + +if not vscode_shell_integration then + return +end + +local function is_vscode() + local term_program = os.getenv("term_program") or "" + if term_program:lower() == "vscode" then + return true + end +end + +local function send_context() + if is_vscode() then + local codes = "" + codes = codes .. "\027]633;D;" .. os.geterrorlevel() .. "\a" -- send command exit code + codes = codes .. "\027]633;P;Cwd=" .. os.getcwd() .. "\a" -- send cwd as title + clink.print(codes, NONL) + end +end + +local p = clink.promptfilter(-999) + +function p:filter() -- luacheck: no unused + -- Nothing to do here, but the filter function must be defined. +end + +function p:surround() -- luacheck: no unused + if is_vscode() then + local pre, suf + local rpre, rsuf + + -- ESC codes surrounding prompt string + pre = "\027]633;A\a" -- copied from shellIntegration-rc.zsh + suf = "\027]633;B\a" -- copied from shellIntegration-rc.zsh + + -- ESC codes surrounding right side prompt string + rpre = "\027]633;H\a" -- copied from shellIntegration-rc.zsh + rsuf = "\027]633;I\a" -- copied from shellIntegration-rc.zsh + + return pre, suf, rpre, rsuf + end +end + +clink.onbeginedit(send_context) diff --git a/src/plugins/terminal/shellintegrations/shellintegration-env.zsh b/src/plugins/terminal/shellintegrations/shellintegration-env.zsh new file mode 100644 index 00000000000..3c890539aeb --- /dev/null +++ b/src/plugins/terminal/shellintegrations/shellintegration-env.zsh @@ -0,0 +1,15 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# SPDX-License-Identifier: MIT + +if [[ -f $USER_ZDOTDIR/.zshenv ]]; then + VSCODE_ZDOTDIR=$ZDOTDIR + ZDOTDIR=$USER_ZDOTDIR + + # prevent recursion + if [[ $USER_ZDOTDIR != $VSCODE_ZDOTDIR ]]; then + . $USER_ZDOTDIR/.zshenv + fi + + USER_ZDOTDIR=$ZDOTDIR + ZDOTDIR=$VSCODE_ZDOTDIR +fi diff --git a/src/plugins/terminal/shellintegrations/shellintegration-login.zsh b/src/plugins/terminal/shellintegrations/shellintegration-login.zsh new file mode 100644 index 00000000000..37ff5439790 --- /dev/null +++ b/src/plugins/terminal/shellintegrations/shellintegration-login.zsh @@ -0,0 +1,7 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# SPDX-License-Identifier: MIT + +ZDOTDIR=$USER_ZDOTDIR +if [[ $options[norcs] = off && -o "login" && -f $ZDOTDIR/.zlogin ]]; then + . $ZDOTDIR/.zlogin +fi diff --git a/src/plugins/terminal/shellintegrations/shellintegration-profile.zsh b/src/plugins/terminal/shellintegrations/shellintegration-profile.zsh new file mode 100644 index 00000000000..724e1f28790 --- /dev/null +++ b/src/plugins/terminal/shellintegrations/shellintegration-profile.zsh @@ -0,0 +1,15 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# SPDX-License-Identifier: MIT + +if [[ $options[norcs] = off && -o "login" && -f $USER_ZDOTDIR/.zprofile ]]; then + VSCODE_ZDOTDIR=$ZDOTDIR + ZDOTDIR=$USER_ZDOTDIR + . $USER_ZDOTDIR/.zprofile + ZDOTDIR=$VSCODE_ZDOTDIR + + # Apply any explicit path prefix (see #99878) + if (( ${+VSCODE_PATH_PREFIX} )); then + export PATH=$VSCODE_PATH_PREFIX$PATH + fi + builtin unset VSCODE_PATH_PREFIX +fi diff --git a/src/plugins/terminal/shellintegrations/shellintegration-rc.zsh b/src/plugins/terminal/shellintegrations/shellintegration-rc.zsh new file mode 100644 index 00000000000..df4109131a9 --- /dev/null +++ b/src/plugins/terminal/shellintegrations/shellintegration-rc.zsh @@ -0,0 +1,160 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# SPDX-License-Identifier: MIT + +builtin autoload -Uz add-zsh-hook + +# Prevent the script recursing when setting up +if [ -n "$VSCODE_SHELL_INTEGRATION" ]; then + ZDOTDIR=$USER_ZDOTDIR + builtin return +fi + +# This variable allows the shell to both detect that VS Code's shell integration is enabled as well +# as disable it by unsetting the variable. +VSCODE_SHELL_INTEGRATION=1 + +# By default, zsh will set the $HISTFILE to the $ZDOTDIR location automatically. In the case of the +# shell integration being injected, this means that the terminal will use a different history file +# to other terminals. To fix this issue, set $HISTFILE back to the default location before ~/.zshrc +# is called as that may depend upon the value. +if [[ "$VSCODE_INJECTION" == "1" ]]; then + HISTFILE=$USER_ZDOTDIR/.zsh_history +fi + +# Only fix up ZDOTDIR if shell integration was injected (not manually installed) and has not been called yet +if [[ "$VSCODE_INJECTION" == "1" ]]; then + if [[ $options[norcs] = off && -f $USER_ZDOTDIR/.zshrc ]]; then + VSCODE_ZDOTDIR=$ZDOTDIR + ZDOTDIR=$USER_ZDOTDIR + # A user's custom HISTFILE location might be set when their .zshrc file is sourced below + . $USER_ZDOTDIR/.zshrc + fi +fi + +# Shell integration was disabled by the shell, exit without warning assuming either the shell has +# explicitly disabled shell integration as it's incompatible or it implements the protocol. +if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then + builtin return +fi + +# The property (P) and command (E) codes embed values which require escaping. +# Backslashes are doubled. Non-alphanumeric characters are converted to escaped hex. +__vsc_escape_value() { + builtin emulate -L zsh + + # Process text byte by byte, not by codepoint. + builtin local LC_ALL=C str="$1" i byte token out='' + + for (( i = 0; i < ${#str}; ++i )); do + byte="${str:$i:1}" + + # Escape backslashes and semi-colons + if [ "$byte" = "\\" ]; then + token="\\\\" + elif [ "$byte" = ";" ]; then + token="\\x3b" + else + token="$byte" + fi + + out+="$token" + done + + builtin print -r "$out" +} + +__vsc_in_command_execution="1" +__vsc_current_command="" + +__vsc_prompt_start() { + builtin printf '\e]633;A\a' +} + +__vsc_prompt_end() { + builtin printf '\e]633;B\a' +} + +__vsc_update_cwd() { + builtin printf '\e]633;P;Cwd=%s\a' "$(__vsc_escape_value "${PWD}")" +} + +__vsc_command_output_start() { + builtin printf '\e]633;C\a' + builtin printf '\e]633;E;%s\a' "${__vsc_current_command}" +} + +__vsc_continuation_start() { + builtin printf '\e]633;F\a' +} + +__vsc_continuation_end() { + builtin printf '\e]633;G\a' +} + +__vsc_right_prompt_start() { + builtin printf '\e]633;H\a' +} + +__vsc_right_prompt_end() { + builtin printf '\e]633;I\a' +} + +__vsc_command_complete() { + if [[ "$__vsc_current_command" == "" ]]; then + builtin printf '\e]633;D\a' + else + builtin printf '\e]633;D;%s\a' "$__vsc_status" + fi + __vsc_update_cwd +} + +if [[ -o NOUNSET ]]; then + if [ -z "${RPROMPT-}" ]; then + RPROMPT="" + fi +fi +__vsc_update_prompt() { + __vsc_prior_prompt="$PS1" + __vsc_prior_prompt2="$PS2" + __vsc_in_command_execution="" + PS1="%{$(__vsc_prompt_start)%}$PS1%{$(__vsc_prompt_end)%}" + PS2="%{$(__vsc_continuation_start)%}$PS2%{$(__vsc_continuation_end)%}" + if [ -n "$RPROMPT" ]; then + __vsc_prior_rprompt="$RPROMPT" + RPROMPT="%{$(__vsc_right_prompt_start)%}$RPROMPT%{$(__vsc_right_prompt_end)%}" + fi +} + +__vsc_precmd() { + local __vsc_status="$?" + if [ -z "${__vsc_in_command_execution-}" ]; then + # not in command execution + __vsc_command_output_start + fi + + __vsc_command_complete "$__vsc_status" + __vsc_current_command="" + + # in command execution + if [ -n "$__vsc_in_command_execution" ]; then + # non null + __vsc_update_prompt + fi +} + +__vsc_preexec() { + PS1="$__vsc_prior_prompt" + PS2="$__vsc_prior_prompt2" + if [ -n "$RPROMPT" ]; then + RPROMPT="$__vsc_prior_rprompt" + fi + __vsc_in_command_execution="1" + __vsc_current_command=$2 + __vsc_command_output_start +} +add-zsh-hook precmd __vsc_precmd +add-zsh-hook preexec __vsc_preexec + +if [[ $options[login] = off && $USER_ZDOTDIR != $VSCODE_ZDOTDIR ]]; then + ZDOTDIR=$USER_ZDOTDIR +fi diff --git a/src/plugins/terminal/shellintegrations/shellintegration.fish b/src/plugins/terminal/shellintegrations/shellintegration.fish new file mode 100644 index 00000000000..7495bab3f40 --- /dev/null +++ b/src/plugins/terminal/shellintegrations/shellintegration.fish @@ -0,0 +1,122 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# SPDX-License-Identifier: MIT +# +# Visual Studio Code terminal integration for fish +# +# Manual installation: +# +# (1) Add the following to the end of `$__fish_config_dir/config.fish`: +# +# string match -q "$TERM_PROGRAM" "vscode" +# and . (code --locate-shell-integration-path fish) +# +# (2) Restart fish. + +# Don't run in scripts, other terminals, or more than once per session. +status is-interactive +and string match --quiet "$TERM_PROGRAM" "vscode" +and ! set --query VSCODE_SHELL_INTEGRATION +or exit + +set --global VSCODE_SHELL_INTEGRATION 1 + +# Apply any explicit path prefix (see #99878) +if status --is-login; and set -q VSCODE_PATH_PREFIX + fish_add_path -p $VSCODE_PATH_PREFIX +end +set -e VSCODE_PATH_PREFIX + +# Helper function +function __vsc_esc -d "Emit escape sequences for VS Code shell integration" + builtin printf "\e]633;%s\a" (string join ";" $argv) +end + +# Sent right before executing an interactive command. +# Marks the beginning of command output. +function __vsc_cmd_executed --on-event fish_preexec + __vsc_esc C + __vsc_esc E (__vsc_escape_value "$argv") + + # Creates a marker to indicate a command was run. + set --global _vsc_has_cmd +end + + +# Escape a value for use in the 'P' ("Property") or 'E' ("Command Line") sequences. +# Backslashes are doubled and non-alphanumeric characters are hex encoded. +function __vsc_escape_value + # Escape backslashes and semi-colons + echo $argv \ + | string replace --all '\\' '\\\\' \ + | string replace --all ';' '\\x3b' \ + ; +end + +# Sent right after an interactive command has finished executing. +# Marks the end of command output. +function __vsc_cmd_finished --on-event fish_postexec + __vsc_esc D $status +end + +# Sent when a command line is cleared or reset, but no command was run. +# Marks the cleared line with neither success nor failure. +function __vsc_cmd_clear --on-event fish_cancel + __vsc_esc D +end + +# Sent whenever a new fish prompt is about to be displayed. +# Updates the current working directory. +function __vsc_update_cwd --on-event fish_prompt + __vsc_esc P Cwd=(__vsc_escape_value "$PWD") + + # If a command marker exists, remove it. + # Otherwise, the commandline is empty and no command was run. + if set --query _vsc_has_cmd + set --erase _vsc_has_cmd + else + __vsc_cmd_clear + end +end + +# Sent at the start of the prompt. +# Marks the beginning of the prompt (and, implicitly, a new line). +function __vsc_fish_prompt_start + __vsc_esc A +end + +# Sent at the end of the prompt. +# Marks the beginning of the user's command input. +function __vsc_fish_cmd_start + __vsc_esc B +end + +function __vsc_fish_has_mode_prompt -d "Returns true if fish_mode_prompt is defined and not empty" + functions fish_mode_prompt | string match -rvq '^ *(#|function |end$|$)' +end + +# Preserve the user's existing prompt, to wrap in our escape sequences. +functions --copy fish_prompt __vsc_fish_prompt + +# Preserve and wrap fish_mode_prompt (which appears to the left of the regular +# prompt), but only if it's not defined as an empty function (which is the +# officially documented way to disable that feature). +if __vsc_fish_has_mode_prompt + functions --copy fish_mode_prompt __vsc_fish_mode_prompt + + function fish_mode_prompt + __vsc_fish_prompt_start + __vsc_fish_mode_prompt + end + + function fish_prompt + __vsc_fish_prompt + __vsc_fish_cmd_start + end +else + # No fish_mode_prompt, so put everything in fish_prompt. + function fish_prompt + __vsc_fish_prompt_start + __vsc_fish_prompt + __vsc_fish_cmd_start + end +end diff --git a/src/plugins/terminal/shellintegrations/shellintegration.ps1 b/src/plugins/terminal/shellintegrations/shellintegration.ps1 new file mode 100644 index 00000000000..4fd978a8844 --- /dev/null +++ b/src/plugins/terminal/shellintegrations/shellintegration.ps1 @@ -0,0 +1,158 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# SPDX-License-Identifier: MIT + +# Prevent installing more than once per session +if (Test-Path variable:global:__VSCodeOriginalPrompt) { + return; +} + +# Disable shell integration when the language mode is restricted +if ($ExecutionContext.SessionState.LanguageMode -ne "FullLanguage") { + return; +} + +$Global:__VSCodeOriginalPrompt = $function:Prompt + +$Global:__LastHistoryId = -1 + +function Global:__VSCode-Escape-Value([string]$value) { + # NOTE: In PowerShell v6.1+, this can be written `$value -replace '…', { … }` instead of `[regex]::Replace`. + # Replace any non-alphanumeric characters. + [regex]::Replace($value, '[\\\n;]', { param($match) + # Encode the (ascii) matches as `\x` + -Join ( + [System.Text.Encoding]::UTF8.GetBytes($match.Value) | ForEach-Object { '\x{0:x2}' -f $_ } + ) + }) +} + +function Global:Prompt() { + $FakeCode = [int]!$global:? + # NOTE: We disable strict mode for the scope of this function because it unhelpfully throws an + # error when $LastHistoryEntry is null, and is not otherwise useful. + Set-StrictMode -Off + $LastHistoryEntry = Get-History -Count 1 + # Skip finishing the command if the first command has not yet started + if ($Global:__LastHistoryId -ne -1) { + if ($LastHistoryEntry.Id -eq $Global:__LastHistoryId) { + # Don't provide a command line or exit code if there was no history entry (eg. ctrl+c, enter on no command) + $Result = "$([char]0x1b)]633;E`a" + $Result += "$([char]0x1b)]633;D`a" + } else { + # Command finished command line + # OSC 633 ; A ; ST + $Result = "$([char]0x1b)]633;E;" + # Sanitize the command line to ensure it can get transferred to the terminal and can be parsed + # correctly. This isn't entirely safe but good for most cases, it's important for the Pt parameter + # to only be composed of _printable_ characters as per the spec. + if ($LastHistoryEntry.CommandLine) { + $CommandLine = $LastHistoryEntry.CommandLine + } else { + $CommandLine = "" + } + $Result += $(__VSCode-Escape-Value $CommandLine) + $Result += "`a" + # Command finished exit code + # OSC 633 ; D [; ] ST + $Result += "$([char]0x1b)]633;D;$FakeCode`a" + } + } + # Prompt started + # OSC 633 ; A ST + $Result += "$([char]0x1b)]633;A`a" + # Current working directory + # OSC 633 ; = ST + $Result += if($pwd.Provider.Name -eq 'FileSystem'){"$([char]0x1b)]633;P;Cwd=$(__VSCode-Escape-Value $pwd.ProviderPath)`a"} + # Before running the original prompt, put $? back to what it was: + if ($FakeCode -ne 0) { + Write-Error "failure" -ea ignore + } + # Run the original prompt + $Result += $Global:__VSCodeOriginalPrompt.Invoke() + # Write command started + $Result += "$([char]0x1b)]633;B`a" + $Global:__LastHistoryId = $LastHistoryEntry.Id + return $Result +} + +# Only send the command executed sequence when PSReadLine is loaded, if not shell integration should +# still work thanks to the command line sequence +if (Get-Module -Name PSReadLine) { + $__VSCodeOriginalPSConsoleHostReadLine = $function:PSConsoleHostReadLine + function Global:PSConsoleHostReadLine { + $tmp = $__VSCodeOriginalPSConsoleHostReadLine.Invoke() + # Write command executed sequence directly to Console to avoid the new line from Write-Host + [Console]::Write("$([char]0x1b)]633;C`a") + $tmp + } +} + +# Set IsWindows property +[Console]::Write("$([char]0x1b)]633;P;IsWindows=$($IsWindows)`a") + +# Set always on key handlers which map to default VS Code keybindings +function Set-MappedKeyHandler { + param ([string[]] $Chord, [string[]]$Sequence) + try { + $Handler = Get-PSReadLineKeyHandler -Chord $Chord | Select-Object -First 1 + } catch [System.Management.Automation.ParameterBindingException] { + # PowerShell 5.1 ships with PSReadLine 2.0.0 which does not have -Chord, + # so we check what's bound and filter it. + $Handler = Get-PSReadLineKeyHandler -Bound | Where-Object -FilterScript { $_.Key -eq $Chord } | Select-Object -First 1 + } + if ($Handler) { + Set-PSReadLineKeyHandler -Chord $Sequence -Function $Handler.Function + } +} + +function Set-MappedKeyHandlers { + Set-MappedKeyHandler -Chord Ctrl+Spacebar -Sequence 'F12,a' + Set-MappedKeyHandler -Chord Alt+Spacebar -Sequence 'F12,b' + Set-MappedKeyHandler -Chord Shift+Enter -Sequence 'F12,c' + Set-MappedKeyHandler -Chord Shift+End -Sequence 'F12,d' + + # Conditionally enable suggestions + if ($env:VSCODE_SUGGEST -eq '1') { + Remove-Item Env:VSCODE_SUGGEST + + # VS Code send completions request (may override Ctrl+Spacebar) + Set-PSReadLineKeyHandler -Chord 'F12,e' -ScriptBlock { + Send-Completions + } + + # Suggest trigger characters + Set-PSReadLineKeyHandler -Chord "-" -ScriptBlock { + [Microsoft.PowerShell.PSConsoleReadLine]::Insert("-") + Send-Completions + } + } +} + +function Send-Completions { + $commandLine = "" + $cursorIndex = 0 + # TODO: Since fuzzy matching exists, should completions be provided only for character after the + # last space and then filter on the client side? That would let you trigger ctrl+space + # anywhere on a word and have full completions available + [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$commandLine, [ref]$cursorIndex) + $completionPrefix = $commandLine + + # Get completions + $result = "`e]633;Completions" + if ($completionPrefix.Length -gt 0) { + # Get and send completions + $completions = TabExpansion2 -inputScript $completionPrefix -cursorColumn $cursorIndex + if ($null -ne $completions.CompletionMatches) { + $result += ";$($completions.ReplacementIndex);$($completions.ReplacementLength);$($cursorIndex);" + $result += $completions.CompletionMatches | ConvertTo-Json -Compress + } + } + $result += "`a" + + Write-Host -NoNewLine $result +} + +# Register key handlers if PSReadLine is available +if (Get-Module -Name PSReadLine) { + Set-MappedKeyHandlers +} diff --git a/src/plugins/terminal/shellmodel.cpp b/src/plugins/terminal/shellmodel.cpp new file mode 100644 index 00000000000..b4cf53a1e99 --- /dev/null +++ b/src/plugins/terminal/shellmodel.cpp @@ -0,0 +1,110 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "shellmodel.h" + +#include +#include +#include + +#include +#include + +namespace Terminal::Internal { + +using namespace Utils; + +FilePaths availableShells() +{ + if (Utils::HostOsInfo::isWindowsHost()) { + FilePaths shells; + + FilePath comspec = FilePath::fromUserInput(qtcEnvironmentVariable("COMSPEC")); + shells << comspec; + + if (comspec.fileName() != "cmd.exe") { + FilePath cmd = FilePath::fromUserInput(QStandardPaths::findExecutable("cmd.exe")); + shells << cmd; + } + + FilePath powershell = FilePath::fromUserInput( + QStandardPaths::findExecutable("powershell.exe")); + if (powershell.exists()) + shells << powershell; + + FilePath bash = FilePath::fromUserInput(QStandardPaths::findExecutable("bash.exe")); + if (bash.exists()) + shells << bash; + + FilePath git_bash = FilePath::fromUserInput(QStandardPaths::findExecutable("git.exe")); + if (git_bash.exists()) + shells << git_bash.parentDir().parentDir().pathAppended("usr/bin/bash.exe"); + + FilePath msys2_bash = FilePath::fromUserInput(QStandardPaths::findExecutable("msys2.exe")); + if (msys2_bash.exists()) + shells << msys2_bash.parentDir().pathAppended("usr/bin/bash.exe"); + + return shells; + } else { + FilePath shellsFile = FilePath::fromString("/etc/shells"); + const auto shellFileContent = shellsFile.fileContents(); + QTC_ASSERT_EXPECTED(shellFileContent, return {}); + + QString shellFileContentString = QString::fromUtf8(*shellFileContent); + + // Filter out comments ... + const QStringList lines + = Utils::filtered(shellFileContentString.split('\n', Qt::SkipEmptyParts), + [](const QString &line) { return !line.trimmed().startsWith('#'); }); + + // Convert lines to file paths ... + const FilePaths shells = Utils::transform(lines, [](const QString &line) { + return FilePath::fromUserInput(line.trimmed()); + }); + + // ... and filter out non-existing shells. + return Utils::filtered(shells, [](const FilePath &shell) { return shell.exists(); }); + } +} + +struct ShellModelPrivate +{ + QList localShells; +}; + +ShellModel::ShellModel(QObject *parent) + : QObject(parent) + , d(new ShellModelPrivate()) +{ + QFileIconProvider iconProvider; + + const FilePaths shells = availableShells(); + for (const FilePath &shell : shells) { + ShellModelItem item; + item.icon = iconProvider.icon(shell.toFileInfo()); + item.name = shell.toUserOutput(); + item.openParameters.shellCommand = {shell, {}}; + d->localShells << item; + } +} + +ShellModel::~ShellModel() = default; + +QList ShellModel::local() const +{ + return d->localShells; +} + +QList ShellModel::remote() const +{ + const auto deviceCmds = Utils::Terminal::Hooks::instance().getTerminalCommandsForDevicesHook()(); + + const QList deviceItems = Utils::transform( + deviceCmds, [](const Utils::Terminal::NameAndCommandLine &item) -> ShellModelItem { + return ShellModelItem{item.name, {}, {item.commandLine, std::nullopt, std::nullopt}}; + }); + + return deviceItems; +} + +} // namespace Terminal::Internal diff --git a/src/plugins/terminal/shellmodel.h b/src/plugins/terminal/shellmodel.h new file mode 100644 index 00000000000..272f3fcd394 --- /dev/null +++ b/src/plugins/terminal/shellmodel.h @@ -0,0 +1,37 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +#include +#include +#include + +#include + +namespace Terminal::Internal { +struct ShellModelPrivate; + +struct ShellModelItem +{ + QString name; + QIcon icon; + Utils::Terminal::OpenTerminalParameters openParameters; +}; + +class ShellModel : public QObject +{ +public: + ShellModel(QObject *parent = nullptr); + ~ShellModel(); + + QList local() const; + QList remote() const; + +private: + std::unique_ptr d; +}; + +} // namespace Terminal::Internal diff --git a/src/plugins/terminal/terminal.qbs b/src/plugins/terminal/terminal.qbs new file mode 100644 index 00000000000..300c1acf19d --- /dev/null +++ b/src/plugins/terminal/terminal.qbs @@ -0,0 +1,43 @@ +import qbs 1.0 + +QtcPlugin { + name: "Terminal" + + Depends { name: "Core" } + Depends { name: "ProjectExplorer" } + Depends { name: "vterm" } + Depends { name: "ptyqt" } + + files: [ + "celliterator.cpp", + "celliterator.h", + "glyphcache.cpp", + "glyphcache.h", + "keys.cpp", + "keys.h", + "scrollback.cpp", + "scrollback.h", + "shellmodel.cpp", + "shellmodel.h", + "shellintegration.cpp", + "shellintegration.h", + "terminal.qrc", + "terminalconstants.h", + "terminalicons.h", + "terminalpane.cpp", + "terminalpane.h", + "terminalplugin.cpp", + "terminalprocessimpl.cpp", + "terminalprocessimpl.h", + "terminalsearch.cpp", + "terminalsearch.h", + "terminalsettings.cpp", + "terminalsettings.h", + "terminalsurface.cpp", + "terminalsurface.h", + "terminaltr.h", + "terminalwidget.cpp", + "terminalwidget.h", + ] +} + diff --git a/src/plugins/terminal/terminal.qrc b/src/plugins/terminal/terminal.qrc new file mode 100644 index 00000000000..63c28169dfd --- /dev/null +++ b/src/plugins/terminal/terminal.qrc @@ -0,0 +1,16 @@ + + + images/settingscategory_terminal.png + images/settingscategory_terminal@2x.png + images/terminal.png + images/terminal@2x.png + shellintegrations/shellintegration-bash.sh + shellintegrations/shellintegration-env.zsh + shellintegrations/shellintegration-login.zsh + shellintegrations/shellintegration-profile.zsh + shellintegrations/shellintegration-rc.zsh + shellintegrations/shellintegration.fish + shellintegrations/shellintegration.ps1 + shellintegrations/shellintegration-clink.lua + + diff --git a/src/plugins/terminal/terminalconstants.h b/src/plugins/terminal/terminalconstants.h new file mode 100644 index 00000000000..99700475e60 --- /dev/null +++ b/src/plugins/terminal/terminalconstants.h @@ -0,0 +1,19 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +namespace Terminal::Constants { +constexpr char NEWTERMINAL[] = "Terminal.NewTerminal"; +constexpr char NEXTTERMINAL[] = "Terminal.NextTerminal"; +constexpr char PREVTERMINAL[] = "Terminal.PrevTerminal"; +constexpr char MINMAX[] = "Terminal.MinMax"; + +constexpr char COPY[] = "Terminal.Copy"; +constexpr char PASTE[] = "Terminal.Paste"; +constexpr char CLEARSELECTION[] = "Terminal.ClearSelection"; +constexpr char MOVECURSORWORDLEFT[] = "Terminal.MoveCursorWordLeft"; +constexpr char MOVECURSORWORDRIGHT[] = "Terminal.MoveCursorWordRight"; +constexpr char CLEAR_TERMINAL[] = "Terminal.ClearTerminal"; + +} // namespace Terminal::Constants diff --git a/src/plugins/terminal/terminalicons.h b/src/plugins/terminal/terminalicons.h new file mode 100644 index 00000000000..ca503f50f96 --- /dev/null +++ b/src/plugins/terminal/terminalicons.h @@ -0,0 +1,18 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace Terminal { + +static Utils::Icon NEW_TERMINAL_ICON( + {{":/terminal/images/terminal.png", Utils::Theme::IconsBaseColor}, + {":/utils/images/iconoverlay_add_small.png", Utils::Theme::IconsRunToolBarColor}}); + +static Utils::Icon CLOSE_TERMINAL_ICON( + {{":/terminal/images/terminal.png", Utils::Theme::IconsBaseColor}, + {":/utils/images/iconoverlay_close_small.png", Utils::Theme::IconsStopToolBarColor}}); + +} // namespace Terminal diff --git a/src/plugins/terminal/terminalpane.cpp b/src/plugins/terminal/terminalpane.cpp new file mode 100644 index 00000000000..7e9cae7539e --- /dev/null +++ b/src/plugins/terminal/terminalpane.cpp @@ -0,0 +1,394 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "terminalpane.h" + +#include "shellmodel.h" +#include "terminalconstants.h" +#include "terminalicons.h" +#include "terminalsettings.h" +#include "terminaltr.h" +#include "terminalwidget.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace Terminal { + +using namespace Utils; +using namespace Utils::Terminal; +using namespace Core; + +TerminalPane::TerminalPane(QObject *parent) + : IOutputPane(parent) + , m_context("Terminal.Pane", Core::Constants::C_GLOBAL_CUTOFF) +{ + setupContext(m_context, &m_tabWidget); + setZoomButtonsEnabled(true); + + connect(this, &IOutputPane::zoomInRequested, this, [this] { + if (currentTerminal()) + currentTerminal()->zoomIn(); + }); + connect(this, &IOutputPane::zoomOutRequested, this, [this] { + if (currentTerminal()) + currentTerminal()->zoomOut(); + }); + + initActions(); + + m_newTerminalButton = new QToolButton(); + m_newTerminalButton->setDefaultAction(&newTerminal); + + m_closeTerminalButton = new QToolButton(); + m_closeTerminalButton->setDefaultAction(&closeTerminal); + + m_openSettingsButton = new QToolButton(); + m_openSettingsButton->setToolTip(Tr::tr("Configure...")); + m_openSettingsButton->setIcon(Icons::SETTINGS_TOOLBAR.icon()); + + connect(m_openSettingsButton, &QToolButton::clicked, m_openSettingsButton, []() { + ICore::showOptionsDialog("Terminal.General"); + }); + + const auto updateEscButton = [this] { + m_escSettingButton->setChecked(TerminalSettings::instance().sendEscapeToTerminal()); + static const QString escKey + = QKeySequence(Qt::Key_Escape).toString(QKeySequence::NativeText); + static const QString shiftEsc = QKeySequence( + QKeyCombination(Qt::ShiftModifier, Qt::Key_Escape)) + .toString(QKeySequence::NativeText); + if (TerminalSettings::instance().sendEscapeToTerminal.value()) { + m_escSettingButton->setText(escKey); + m_escSettingButton->setToolTip(Tr::tr("Sending ESC to terminal instead of Qt Creator")); + } else { + m_escSettingButton->setText(shiftEsc); + m_escSettingButton->setToolTip(Tr::tr("Press %1 to send ESC to terminal").arg(shiftEsc)); + } + }; + + m_escSettingButton = new QToolButton(); + m_escSettingButton->setCheckable(true); + + updateEscButton(); + + connect(m_escSettingButton, &QToolButton::toggled, this, [this] { + TerminalSettings::instance().sendEscapeToTerminal.setValue(m_escSettingButton->isChecked()); + TerminalSettings::instance().writeSettings(ICore::settings()); + }); + + connect(&TerminalSettings::instance(), &TerminalSettings::applied, this, updateEscButton); +} + +TerminalPane::~TerminalPane() {} + +static std::optional startupProjectDirectory() +{ + const ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject(); + if (!project) + return std::nullopt; + + return project->projectDirectory(); +} + +void TerminalPane::openTerminal(const OpenTerminalParameters ¶meters) +{ + OpenTerminalParameters parametersCopy{parameters}; + if (!m_isVisible) + emit showPage(IOutputPane::ModeSwitch); + + if (!parametersCopy.workingDirectory) { + const std::optional projectDir = startupProjectDirectory(); + if (projectDir) { + if (!parametersCopy.shellCommand + || parametersCopy.shellCommand->executable().ensureReachable(*projectDir)) { + parametersCopy.workingDirectory = *projectDir; + } + } + } + + const auto terminalWidget = new TerminalWidget(&m_tabWidget, parametersCopy); + m_tabWidget.setCurrentIndex(m_tabWidget.addTab(terminalWidget, Tr::tr("Terminal"))); + setupTerminalWidget(terminalWidget); + + m_tabWidget.currentWidget()->setFocus(); + + emit navigateStateUpdate(); +} + +void TerminalPane::addTerminal(TerminalWidget *terminal, const QString &title) +{ + if (!m_isVisible) + emit showPage(IOutputPane::ModeSwitch); + m_tabWidget.setCurrentIndex(m_tabWidget.addTab(terminal, title)); + setupTerminalWidget(terminal); + + emit navigateStateUpdate(); +} + +void TerminalPane::ensureVisible(TerminalWidget *terminal) +{ + if (!m_isVisible) + emit showPage(IOutputPane::ModeSwitch); + m_tabWidget.setCurrentWidget(terminal); + terminal->setFocus(); +} + +TerminalWidget *TerminalPane::stoppedTerminalWithId(Id identifier) const +{ + for (int i = 0; i < m_tabWidget.count(); ++i) { + const auto terminal = qobject_cast(m_tabWidget.widget(i)); + if (terminal && terminal->processState() == QProcess::NotRunning + && terminal->identifier() == identifier) + return terminal; + } + + return nullptr; +} + +QWidget *TerminalPane::outputWidget(QWidget *parent) +{ + Q_UNUSED(parent) + if (!m_widgetInitialized) { + m_widgetInitialized = true; + m_tabWidget.setTabBarAutoHide(false); + m_tabWidget.setDocumentMode(true); + m_tabWidget.setTabsClosable(true); + m_tabWidget.setMovable(true); + + connect(&m_tabWidget, &QTabWidget::tabCloseRequested, this, [this](int index) { + removeTab(index); + }); + + connect(&m_tabWidget, &QTabWidget::currentChanged, this, [this](int index) { + if (auto widget = m_tabWidget.widget(index)) + widget->setFocus(); + else + emit hidePage(); + }); + } + + return &m_tabWidget; +} + +TerminalWidget *TerminalPane::currentTerminal() const +{ + return static_cast(m_tabWidget.currentWidget()); +} + +void TerminalPane::removeTab(int index) +{ + delete m_tabWidget.widget(index); + emit navigateStateUpdate(); +} + +void TerminalPane::setupTerminalWidget(TerminalWidget *terminal) +{ + if (!terminal) + return; + + const auto setTabText = [this, terminal]() { + const int index = m_tabWidget.indexOf(terminal); + const FilePath cwd = terminal->cwd(); + + const QString exe = terminal->currentCommand().isEmpty() + ? terminal->shellName() + : terminal->currentCommand().executable().fileName(); + + if (cwd.isEmpty()) + m_tabWidget.setTabText(index, exe); + else + m_tabWidget.setTabText(index, exe + " - " + cwd.fileName()); + }; + + connect(terminal, &TerminalWidget::started, this, setTabText); + connect(terminal, &TerminalWidget::cwdChanged, this, setTabText); + connect(terminal, &TerminalWidget::commandChanged, this, setTabText); + + if (!terminal->shellName().isEmpty()) + setTabText(); +} + +void TerminalPane::initActions() +{ + createShellMenu(); + + newTerminal.setText(Tr::tr("New Terminal")); + newTerminal.setIcon(NEW_TERMINAL_ICON.icon()); + newTerminal.setToolTip(Tr::tr("Create a new Terminal.")); + newTerminal.setMenu(&m_shellMenu); + + nextTerminal.setText(Tr::tr("Next Terminal")); + prevTerminal.setText(Tr::tr("Previous Terminal")); + + closeTerminal.setIcon(CLOSE_TERMINAL_ICON.icon()); + closeTerminal.setToolTip(Tr::tr("Close the current Terminal.")); + + using namespace Constants; + + ActionManager::registerAction(&newTerminal, NEWTERMINAL, m_context) + ->setDefaultKeySequences({QKeySequence( + HostOsInfo::isMacHost() ? QLatin1String("Ctrl+T") : QLatin1String("Ctrl+Shift+T"))}); + + ActionManager::registerAction(&nextTerminal, NEXTTERMINAL, m_context) + ->setDefaultKeySequences( + {QKeySequence("Alt+Tab"), + QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+Shift+[") + : QLatin1String("Ctrl+PgUp"))}); + + ActionManager::registerAction(&prevTerminal, PREVTERMINAL, m_context) + ->setDefaultKeySequences( + {QKeySequence("Alt+Shift+Tab"), + QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+Shift+]") + : QLatin1String("Ctrl+PgDown"))}); + + m_minMax = TerminalWidget::unlockGlobalAction("Coreplugin.OutputPane.minmax", m_context); + m_locate = TerminalWidget::unlockGlobalAction(Core::Constants::LOCATE, m_context); + + connect(&newTerminal, &QAction::triggered, this, [this] { openTerminal({}); }); + connect(&closeTerminal, &QAction::triggered, this, [this] { + removeTab(m_tabWidget.currentIndex()); + }); + connect(&nextTerminal, &QAction::triggered, this, [this] { + if (canNavigate()) + goToNext(); + }); + connect(&prevTerminal, &QAction::triggered, this, [this] { + if (canPrevious()) + goToPrev(); + }); +} + +void TerminalPane::createShellMenu() +{ + const Internal::ShellModel *shellModel = new Internal::ShellModel(&m_shellMenu); + + connect(&m_shellMenu, &QMenu::aboutToShow, &m_shellMenu, [shellModel, this] { + m_shellMenu.clear(); + + const auto addItems = [this](const QList &items) { + for (const Internal::ShellModelItem &item : items) { + QAction *action = new QAction(item.icon, item.name, &m_shellMenu); + + connect(action, &QAction::triggered, action, [item, this]() { + openTerminal(item.openParameters); + }); + + m_shellMenu.addAction(action); + } + }; + + addItems(shellModel->local()); + m_shellMenu.addSection(Tr::tr("Devices")); + addItems(shellModel->remote()); + }); +} + +QList TerminalPane::toolBarWidgets() const +{ + QList widgets = IOutputPane::toolBarWidgets(); + widgets.prepend(m_newTerminalButton); + widgets.prepend(m_closeTerminalButton); + + return widgets << m_openSettingsButton << m_escSettingButton; +} + +QString TerminalPane::displayName() const +{ + return Tr::tr("Terminal"); +} + +int TerminalPane::priorityInStatusBar() const +{ + return 50; +} + +void TerminalPane::clearContents() +{ + if (const auto t = currentTerminal()) + t->clearContents(); +} + +void TerminalPane::visibilityChanged(bool visible) +{ + if (m_isVisible == visible) + return; + + m_isVisible = visible; + + if (visible && m_tabWidget.count() == 0) + openTerminal({}); + + IOutputPane::visibilityChanged(visible); +} + +void TerminalPane::setFocus() +{ + if (const auto t = currentTerminal()) + t->setFocus(); +} + +bool TerminalPane::hasFocus() const +{ + if (const auto t = currentTerminal()) + return t->hasFocus(); + + return false; +} + +bool TerminalPane::canFocus() const +{ + return true; +} + +bool TerminalPane::canNavigate() const +{ + return true; +} + +bool TerminalPane::canNext() const +{ + return m_tabWidget.count() > 1; +} + +bool TerminalPane::canPrevious() const +{ + return m_tabWidget.count() > 1; +} + +void TerminalPane::goToNext() +{ + int nextIndex = m_tabWidget.currentIndex() + 1; + if (nextIndex >= m_tabWidget.count()) + nextIndex = 0; + + m_tabWidget.setCurrentIndex(nextIndex); + emit navigateStateUpdate(); +} + +void TerminalPane::goToPrev() +{ + int prevIndex = m_tabWidget.currentIndex() - 1; + if (prevIndex < 0) + prevIndex = m_tabWidget.count() - 1; + + m_tabWidget.setCurrentIndex(prevIndex); + emit navigateStateUpdate(); +} + +} // namespace Terminal diff --git a/src/plugins/terminal/terminalpane.h b/src/plugins/terminal/terminalpane.h new file mode 100644 index 00000000000..21f15168f26 --- /dev/null +++ b/src/plugins/terminal/terminalpane.h @@ -0,0 +1,82 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "terminalwidget.h" + +#include + +#include + +#include +#include +#include +#include + +namespace Terminal { + +class TerminalWidget; + +class TerminalPane : public Core::IOutputPane +{ + Q_OBJECT +public: + TerminalPane(QObject *parent = nullptr); + ~TerminalPane() override; + + QWidget *outputWidget(QWidget *parent) override; + QList toolBarWidgets() const override; + QString displayName() const override; + int priorityInStatusBar() const override; + void clearContents() override; + void visibilityChanged(bool visible) override; + void setFocus() override; + bool hasFocus() const override; + bool canFocus() const override; + bool canNavigate() const override; + bool canNext() const override; + bool canPrevious() const override; + void goToNext() override; + void goToPrev() override; + + void openTerminal(const Utils::Terminal::OpenTerminalParameters ¶meters); + void addTerminal(TerminalWidget *terminal, const QString &title); + + TerminalWidget *stoppedTerminalWithId(Utils::Id identifier) const; + + void ensureVisible(TerminalWidget *terminal); + +private: + TerminalWidget *currentTerminal() const; + + void removeTab(int index); + void setupTerminalWidget(TerminalWidget *terminal); + void initActions(); + void createShellMenu(); + +private: + QTabWidget m_tabWidget; + + QToolButton *m_newTerminalButton{nullptr}; + QToolButton *m_closeTerminalButton{nullptr}; + QToolButton *m_openSettingsButton{nullptr}; + QToolButton *m_escSettingButton{nullptr}; + + UnlockedGlobalAction m_minMax; + UnlockedGlobalAction m_locate; + + QAction newTerminal; + QAction nextTerminal; + QAction prevTerminal; + QAction closeTerminal; + + QMenu m_shellMenu; + + Core::Context m_context; + + bool m_widgetInitialized{false}; + bool m_isVisible{false}; +}; + +} // namespace Terminal diff --git a/src/plugins/terminal/terminalplugin.cpp b/src/plugins/terminal/terminalplugin.cpp new file mode 100644 index 00000000000..38b58b1722f --- /dev/null +++ b/src/plugins/terminal/terminalplugin.cpp @@ -0,0 +1,87 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "terminalpane.h" +#include "terminalprocessimpl.h" +#include "terminalsettings.h" +#include "terminalwidget.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +namespace Terminal::Internal { + +class TerminalPlugin final : public ExtensionSystem::IPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Terminal.json") + +public: + TerminalPlugin() = default; + + ~TerminalPlugin() final + { + ExtensionSystem::PluginManager::removeObject(m_terminalPane); + delete m_terminalPane; + m_terminalPane = nullptr; + } + + void initialize() final { addManaged(); } + + void extensionsInitialized() final + { + m_terminalPane = new TerminalPane; + ExtensionSystem::PluginManager::addObject(m_terminalPane); + + TerminalWidget::initActions(); + + auto enable = [this] { + Utils::Terminal::Hooks::instance() + .addCallbackSet("Internal", + {[this](const Utils::Terminal::OpenTerminalParameters &p) { + m_terminalPane->openTerminal(p); + }, + [this] { return new TerminalProcessImpl(m_terminalPane); }}); + }; + + auto disable = [] { Utils::Terminal::Hooks::instance().removeCallbackSet("Internal"); }; + + static bool isEnabled = false; + auto settingsChanged = [enable, disable] { + if (isEnabled != TerminalSettings::instance().enableTerminal()) { + isEnabled = TerminalSettings::instance().enableTerminal(); + if (isEnabled) + enable(); + else + disable(); + } + }; + + QObject::connect(&TerminalSettings::instance(), + &Utils::AspectContainer::applied, + this, + settingsChanged); + + settingsChanged(); + } + +private: + TerminalPane *m_terminalPane{nullptr}; +}; + +} // namespace Terminal::Internal + +#include "terminalplugin.moc" diff --git a/src/plugins/terminal/terminalprocessimpl.cpp b/src/plugins/terminal/terminalprocessimpl.cpp new file mode 100644 index 00000000000..9a7f7e7dcd6 --- /dev/null +++ b/src/plugins/terminal/terminalprocessimpl.cpp @@ -0,0 +1,82 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "terminalprocessimpl.h" +#include "terminalwidget.h" + +#include + +#include +#include +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(terminalProcessLog, "qtc.terminal.stubprocess", QtDebugMsg) + +using namespace Utils; +using namespace Utils::Terminal; + +namespace Terminal { + +class ProcessStubCreator : public StubCreator +{ +public: + ProcessStubCreator(TerminalProcessImpl *interface, TerminalPane *terminalPane) + : m_terminalPane(terminalPane) + , m_process(interface) + , m_interface(interface) + {} + + expected_str startStubProcess(const ProcessSetupData &setup) override + { + if (QApplication::activeModalWidget()) { + m_fallbackStubCreator = std::make_unique(m_interface); + return m_fallbackStubCreator->startStubProcess(setup); + } + + const Id id = Id::fromString(setup.m_commandLine.executable().toUserOutput()); + + TerminalWidget *terminal = m_terminalPane->stoppedTerminalWithId(id); + + const OpenTerminalParameters openParameters{setup.m_commandLine, + std::nullopt, + std::nullopt, + ExitBehavior::Keep, + id}; + + if (!terminal) { + terminal = new TerminalWidget(nullptr, openParameters); + + terminal->setShellName(setup.m_commandLine.executable().fileName()); + m_terminalPane->addTerminal(terminal, "App"); + } else { + terminal->restart(openParameters); + } + + m_terminalPane->ensureVisible(terminal); + + connect(terminal, &TerminalWidget::destroyed, m_process, [process = m_process] { + if (process->inferiorProcessId()) + process->emitFinished(-1, QProcess::CrashExit); + }); + + return 0; + } + + TerminalPane *m_terminalPane; + TerminalProcessImpl *m_process; + TerminalInterface *m_interface; + std::unique_ptr m_fallbackStubCreator; +}; + +TerminalProcessImpl::TerminalProcessImpl(TerminalPane *terminalPane) + : TerminalInterface(false) +{ + auto creator = new ProcessStubCreator(this, terminalPane); + creator->moveToThread(qApp->thread()); + setStubCreator(creator); +} + +} // namespace Terminal diff --git a/src/plugins/terminal/terminalprocessimpl.h b/src/plugins/terminal/terminalprocessimpl.h new file mode 100644 index 00000000000..f77f418281c --- /dev/null +++ b/src/plugins/terminal/terminalprocessimpl.h @@ -0,0 +1,18 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "terminalpane.h" + +#include + +namespace Terminal { + +class TerminalProcessImpl : public Utils::TerminalInterface +{ +public: + TerminalProcessImpl(TerminalPane *terminalPane); +}; + +} // namespace Terminal diff --git a/src/plugins/terminal/terminalsearch.cpp b/src/plugins/terminal/terminalsearch.cpp new file mode 100644 index 00000000000..69a412b1948 --- /dev/null +++ b/src/plugins/terminal/terminalsearch.cpp @@ -0,0 +1,274 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "terminalsearch.h" + +#include +#include +#include + +#include + +Q_LOGGING_CATEGORY(terminalSearchLog, "qtc.terminal.search", QtWarningMsg) + +using namespace std::chrono_literals; + +namespace Terminal { + +using namespace Terminal::Internal; + +constexpr std::chrono::milliseconds debounceInterval = 100ms; + +TerminalSearch::TerminalSearch(TerminalSurface *surface) + : m_surface(surface) +{ + m_debounceTimer.setInterval(debounceInterval); + m_debounceTimer.setSingleShot(true); + + connect(surface, &TerminalSurface::invalidated, this, &TerminalSearch::updateHits); + connect(&m_debounceTimer, &QTimer::timeout, this, &TerminalSearch::debouncedUpdateHits); +} + +void TerminalSearch::setCurrentSelection(std::optional selection) +{ + m_currentSelection = selection; +} + +void TerminalSearch::setSearchString(const QString &searchString, Core::FindFlags findFlags) +{ + if (m_currentSearchString != searchString || m_findFlags != findFlags) { + m_currentSearchString = searchString; + m_findFlags = findFlags; + updateHits(); + } +} + +void TerminalSearch::nextHit() +{ + if (m_hits.isEmpty()) + return; + + m_currentHit = (m_currentHit + 1) % m_hits.size(); + emit currentHitChanged(); +} + +void TerminalSearch::previousHit() +{ + if (m_hits.isEmpty()) + return; + + m_currentHit = (m_currentHit - 1 + m_hits.size()) % m_hits.size(); + emit currentHitChanged(); +} + +void TerminalSearch::updateHits() +{ + if (!m_hits.isEmpty()) { + m_hits.clear(); + m_currentHit = -1; + emit hitsChanged(); + emit currentHitChanged(); + } + + m_debounceTimer.start(); +} + +bool isSpace(char32_t a, char32_t b) +{ + if (a == std::numeric_limits::max()) + return std::isspace(b); + else if (b == std::numeric_limits::max()) + return std::isspace(a); + + return false; +} + +QList TerminalSearch::search() +{ + QList hits; + + std::function compare; + + if (m_findFlags.testFlag(Core::FindFlag::FindCaseSensitively)) { + compare = [](char32_t a, char32_t b) { return a == b || isSpace(a, b); }; + } else { + compare = [](char32_t a, char32_t b) { + return std::tolower(a) == std::tolower(b) || isSpace(a, b); + }; + } + + if (!m_currentSearchString.isEmpty()) { + const QList asUcs4 = m_currentSearchString.toUcs4(); + std::u32string searchString(asUcs4.begin(), asUcs4.end()); + + if (m_findFlags.testFlag(Core::FindFlag::FindWholeWords)) { + searchString.push_back(std::numeric_limits::max()); + searchString.insert(searchString.begin(), std::numeric_limits::max()); + } + + Internal::CellIterator it = m_surface->begin(); + while (it != m_surface->end()) { + it = std::search(it, m_surface->end(), searchString.begin(), searchString.end(), compare); + + if (it != m_surface->end()) { + auto hit = SearchHit{it.position(), + static_cast(it.position() + searchString.size())}; + if (m_findFlags.testFlag(Core::FindFlag::FindWholeWords)) { + hit.start++; + hit.end--; + } + hits << hit; + it += m_currentSearchString.size(); + } + } + } + return hits; +} + +QList TerminalSearch::searchRegex() +{ + QList hits; + + QString allText; + allText.reserve(1000); + + // Contains offsets at which there are characters > 2 bytes + QList adjustTable; + + for (auto it = m_surface->begin(); it != m_surface->end(); ++it) { + auto chs = QChar::fromUcs4(*it); + if (chs.size() > 1) + adjustTable << (allText.size()); + allText += chs; + } + + QRegularExpression re(m_currentSearchString, + m_findFlags.testFlag(Core::FindFlag::FindCaseSensitively) + ? QRegularExpression::NoPatternOption + : QRegularExpression::CaseInsensitiveOption); + + QRegularExpressionMatchIterator it = re.globalMatch(allText); + int adjust = 0; + auto itAdjust = adjustTable.begin(); + while (it.hasNext()) { + QRegularExpressionMatch match = it.next(); + int s = match.capturedStart(); + int e = match.capturedEnd(); + + // Update 'adjust' to account for characters > 2 bytes + if (itAdjust != adjustTable.end()) { + while (s > *itAdjust && itAdjust != adjustTable.end()) { + adjust++; + itAdjust++; + } + s -= adjust; + while (e > *itAdjust && itAdjust != adjustTable.end()) { + adjust++; + itAdjust++; + } + e -= adjust; + } + hits << SearchHit{s, e}; + } + + return hits; +} + +void TerminalSearch::debouncedUpdateHits() +{ + QElapsedTimer t; + t.start(); + + m_currentHit = -1; + + const bool regex = m_findFlags.testFlag(Core::FindFlag::FindRegularExpression); + + QList hits = regex ? searchRegex() : search(); + + if (hits != m_hits) { + m_currentHit = -1; + if (m_currentSelection) + m_currentHit = hits.indexOf(*m_currentSelection); + + if (m_currentHit == -1 && !hits.isEmpty()) + m_currentHit = 0; + + m_hits = hits; + emit hitsChanged(); + emit currentHitChanged(); + emit changed(); + } + if (!m_currentSearchString.isEmpty()) + qCDebug(terminalSearchLog) << "Search took" << t.elapsed() << "ms"; +} + +Core::FindFlags TerminalSearch::supportedFindFlags() const +{ + return Core::FindFlag::FindCaseSensitively | Core::FindFlag::FindBackward + | Core::FindFlag::FindRegularExpression | Core::FindFlag::FindWholeWords; +} + +void TerminalSearch::resetIncrementalSearch() +{ + m_currentSelection.reset(); +} + +void TerminalSearch::clearHighlights() +{ + setSearchString("", {}); +} + +QString TerminalSearch::currentFindString() const +{ + if (m_currentSelection) + return m_currentSelection->text; + else + return m_currentSearchString; +} + +QString TerminalSearch::completedFindString() const +{ + return {}; +} + +Core::IFindSupport::Result TerminalSearch::findIncremental(const QString &txt, + Core::FindFlags findFlags) +{ + if (txt == m_currentSearchString) { + if (m_debounceTimer.isActive()) + return Result::NotYetFound; + else if (m_hits.isEmpty()) + return Result::NotFound; + else + return Result::Found; + } + + setSearchString(txt, findFlags); + return Result::NotYetFound; +} + +Core::IFindSupport::Result TerminalSearch::findStep(const QString &txt, Core::FindFlags findFlags) +{ + if (txt == m_currentSearchString) { + if (m_debounceTimer.isActive()) + return Result::NotYetFound; + else if (m_hits.isEmpty()) + return Result::NotFound; + + if (findFlags.testFlag(Core::FindFlag::FindBackward)) + previousHit(); + else + nextHit(); + + return Result::Found; + } + + return findIncremental(txt, findFlags); +} + +void TerminalSearch::highlightAll(const QString &txt, Core::FindFlags findFlags) +{ + setSearchString(txt, findFlags); +} + +} // namespace Terminal diff --git a/src/plugins/terminal/terminalsearch.h b/src/plugins/terminal/terminalsearch.h new file mode 100644 index 00000000000..3daa05c04d2 --- /dev/null +++ b/src/plugins/terminal/terminalsearch.h @@ -0,0 +1,82 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "terminalsurface.h" + +#include +#include + +#include + +namespace Terminal { + +struct SearchHit +{ + int start{-1}; + int end{-1}; + + bool operator!=(const SearchHit &other) const + { + return start != other.start || end != other.end; + } + bool operator==(const SearchHit &other) const { return !operator!=(other); } +}; + +struct SearchHitWithText : SearchHit +{ + QString text; +}; + +class TerminalSearch : public Core::IFindSupport +{ + Q_OBJECT +public: + TerminalSearch(Internal::TerminalSurface *surface); + + void setCurrentSelection(std::optional selection); + void setSearchString(const QString &searchString, Core::FindFlags findFlags); + void nextHit(); + void previousHit(); + + const QList &hits() const { return m_hits; } + SearchHit currentHit() const + { + return m_currentHit >= 0 ? m_hits.at(m_currentHit) : SearchHit{}; + } + +public: + bool supportsReplace() const override { return false; } + Core::FindFlags supportedFindFlags() const override; + void resetIncrementalSearch() override; + void clearHighlights() override; + QString currentFindString() const override; + QString completedFindString() const override; + Result findIncremental(const QString &txt, Core::FindFlags findFlags) override; + Result findStep(const QString &txt, Core::FindFlags findFlags) override; + + void highlightAll(const QString &, Core::FindFlags) override; + +signals: + void hitsChanged(); + void currentHitChanged(); + +protected: + void updateHits(); + void debouncedUpdateHits(); + QList search(); + QList searchRegex(); + +private: + std::optional m_currentSelection; + QString m_currentSearchString; + Core::FindFlags m_findFlags; + Internal::TerminalSurface *m_surface; + + int m_currentHit{-1}; + QList m_hits; + QTimer m_debounceTimer; +}; + +} // namespace Terminal diff --git a/src/plugins/terminal/terminalsettings.cpp b/src/plugins/terminal/terminalsettings.cpp new file mode 100644 index 00000000000..06903c4d4c5 --- /dev/null +++ b/src/plugins/terminal/terminalsettings.cpp @@ -0,0 +1,591 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "terminalsettings.h" + +#include "terminaltr.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Utils; + +namespace Terminal { + +static QString defaultFontFamily() +{ + if (HostOsInfo::isMacHost()) + return QLatin1String("Menlo"); + + if (Utils::HostOsInfo::isAnyUnixHost()) + return QLatin1String("Monospace"); + + return QLatin1String("Consolas"); +} + +static int defaultFontSize() +{ + if (Utils::HostOsInfo::isMacHost()) + return 12; + if (Utils::HostOsInfo::isAnyUnixHost()) + return 9; + return 10; +} + +static QString defaultShell() +{ + if (HostOsInfo::isWindowsHost()) + return qtcEnvironmentVariable("COMSPEC"); + + QString defaultShell = qtcEnvironmentVariable("SHELL"); + if (FilePath::fromUserInput(defaultShell).isExecutableFile()) + return defaultShell; + + Utils::FilePath shPath = Utils::Environment::systemEnvironment().searchInPath("sh"); + return shPath.nativePath(); +} + +void setupColor(TerminalSettings *settings, + ColorAspect &color, + const QString &label, + const QColor &defaultColor) +{ + color.setSettingsKey(label); + color.setDefaultValue(defaultColor); + color.setToolTip(Tr::tr("The color used for %1.").arg(label)); + + settings->registerAspect(&color); +} + +static expected_str loadXdefaults(const FilePath &path) +{ + const expected_str readResult = path.fileContents(); + if (!readResult) + return make_unexpected(readResult.error()); + + QRegularExpression re(R"(.*\*(color[0-9]{1,2}|foreground|background):\s*(#[0-9a-f]{6}))"); + + for (const QByteArray &line : readResult->split('\n')) { + if (line.trimmed().startsWith('!')) + continue; + + const auto match = re.match(QString::fromUtf8(line)); + if (match.hasMatch()) { + const QString colorName = match.captured(1); + const QColor color(match.captured(2)); + if (colorName == "foreground") { + TerminalSettings::instance().foregroundColor.setVolatileValue(color); + } else if (colorName == "background") { + TerminalSettings::instance().backgroundColor.setVolatileValue(color); + } else { + const int colorIndex = colorName.mid(5).toInt(); + if (colorIndex >= 0 && colorIndex < 16) + TerminalSettings::instance().colors[colorIndex].setVolatileValue(color); + } + } + } + + return {}; +} + +static expected_str loadItermColors(const FilePath &path) +{ + QFile f(path.toFSPathString()); + const bool opened = f.open(QIODevice::ReadOnly); + if (!opened) + return make_unexpected(Tr::tr("Failed to open file")); + + QXmlStreamReader reader(&f); + while (!reader.atEnd() && reader.readNextStartElement()) { + if (reader.name() == u"plist") { + while (!reader.atEnd() && reader.readNextStartElement()) { + if (reader.name() == u"dict") { + QString colorName; + while (!reader.atEnd() && reader.readNextStartElement()) { + if (reader.name() == u"key") { + colorName = reader.readElementText(); + } else if (reader.name() == u"dict") { + QColor color; + int component = 0; + while (!reader.atEnd() && reader.readNextStartElement()) { + if (reader.name() == u"key") { + const auto &text = reader.readElementText(); + if (text == u"Red Component") + component = 0; + else if (text == u"Green Component") + component = 1; + else if (text == u"Blue Component") + component = 2; + else if (text == u"Alpha Component") + component = 3; + } else if (reader.name() == u"real") { + // clang-format off + switch (component) { + case 0: color.setRedF(reader.readElementText().toDouble()); break; + case 1: color.setGreenF(reader.readElementText().toDouble()); break; + case 2: color.setBlueF(reader.readElementText().toDouble()); break; + case 3: color.setAlphaF(reader.readElementText().toDouble()); break; + } + // clang-format on + } else { + reader.skipCurrentElement(); + } + } + + if (colorName.startsWith("Ansi")) { + const auto c = colorName.mid(5, 2); + const int colorIndex = c.toInt(); + if (colorIndex >= 0 && colorIndex < 16) + TerminalSettings::instance().colors[colorIndex].setVolatileValue( + color); + } else if (colorName == "Foreground Color") { + TerminalSettings::instance().foregroundColor.setVolatileValue(color); + } else if (colorName == "Background Color") { + TerminalSettings::instance().backgroundColor.setVolatileValue(color); + } else if (colorName == "Selection Color") { + TerminalSettings::instance().selectionColor.setVolatileValue(color); + } + } + } + } + } + break; + } + } + if (reader.hasError()) + return make_unexpected(reader.errorString()); + + return {}; +} + +static expected_str loadVsCodeColors(const FilePath &path) +{ + const expected_str readResult = path.fileContents(); + if (!readResult) + return make_unexpected(readResult.error()); + + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(*readResult, &error); + if (error.error != QJsonParseError::NoError) + return make_unexpected(Tr::tr("JSON parsing error: \"%1\", at offset: %2") + .arg(error.errorString()) + .arg(error.offset)); + + const QJsonObject root = doc.object(); + const auto itColors = root.find("colors"); + if (itColors == root.end()) + return make_unexpected(Tr::tr("No colors found")); + + const QJsonObject colors = itColors->toObject(); + + // clang-format off + const QList> colorKeys = { + qMakePair(u"editor.background", &TerminalSettings::instance().backgroundColor), + qMakePair(u"terminal.foreground", &TerminalSettings::instance().foregroundColor), + qMakePair(u"terminal.selectionBackground", &TerminalSettings::instance().selectionColor), + + qMakePair(u"terminal.ansiBlack", &TerminalSettings::instance().colors[0]), + qMakePair(u"terminal.ansiBrightBlack", &TerminalSettings::instance().colors[8]), + + qMakePair(u"terminal.ansiRed", &TerminalSettings::instance().colors[1]), + qMakePair(u"terminal.ansiBrightRed", &TerminalSettings::instance().colors[9]), + + qMakePair(u"terminal.ansiGreen", &TerminalSettings::instance().colors[2]), + qMakePair(u"terminal.ansiBrightGreen", &TerminalSettings::instance().colors[10]), + + qMakePair(u"terminal.ansiYellow", &TerminalSettings::instance().colors[3]), + qMakePair(u"terminal.ansiBrightYellow", &TerminalSettings::instance().colors[11]), + + qMakePair(u"terminal.ansiBlue", &TerminalSettings::instance().colors[4]), + qMakePair(u"terminal.ansiBrightBlue", &TerminalSettings::instance().colors[12]), + + qMakePair(u"terminal.ansiMagenta", &TerminalSettings::instance().colors[5]), + qMakePair(u"terminal.ansiBrightMagenta", &TerminalSettings::instance().colors[13]), + + qMakePair(u"terminal.ansiCyan", &TerminalSettings::instance().colors[6]), + qMakePair(u"terminal.ansiBrightCyan", &TerminalSettings::instance().colors[14]), + + qMakePair(u"terminal.ansiWhite", &TerminalSettings::instance().colors[7]), + qMakePair(u"terminal.ansiBrightWhite", &TerminalSettings::instance().colors[15]) + }; + // clang-format on + + for (const auto &pair : colorKeys) { + const auto it = colors.find(pair.first); + if (it != colors.end()) { + const QString colorString = it->toString(); + if (colorString.startsWith("#")) { + QColor color(colorString.mid(0, 7)); + if (colorString.size() > 7) { + int alpha = colorString.mid(7).toInt(nullptr, 16); + color.setAlpha(alpha); + } + if (color.isValid()) + pair.second->setVolatileValue(color); + } + } + } + + return {}; +} + +static expected_str loadKonsoleColorScheme(const FilePath &path) +{ + QSettings settings(path.toFSPathString(), QSettings::IniFormat); + + auto parseColor = [](const QStringList &parts) -> expected_str { + if (parts.size() != 3 && parts.size() != 4) + return make_unexpected(Tr::tr("Invalid color format")); + int alpha = parts.size() == 4 ? parts[3].toInt() : 255; + return QColor(parts[0].toInt(), parts[1].toInt(), parts[2].toInt(), alpha); + }; + + // clang-format off + const QList> colorKeys = { + qMakePair(QLatin1String("Background/Color"), &TerminalSettings::instance().backgroundColor), + qMakePair(QLatin1String("Foreground/Color"), &TerminalSettings::instance().foregroundColor), + + qMakePair(QLatin1String("Color0/Color"), &TerminalSettings::instance().colors[0]), + qMakePair(QLatin1String("Color0Intense/Color"), &TerminalSettings::instance().colors[8]), + + qMakePair(QLatin1String("Color1/Color"), &TerminalSettings::instance().colors[1]), + qMakePair(QLatin1String("Color1Intense/Color"), &TerminalSettings::instance().colors[9]), + + qMakePair(QLatin1String("Color2/Color"), &TerminalSettings::instance().colors[2]), + qMakePair(QLatin1String("Color2Intense/Color"), &TerminalSettings::instance().colors[10]), + + qMakePair(QLatin1String("Color3/Color"), &TerminalSettings::instance().colors[3]), + qMakePair(QLatin1String("Color3Intense/Color"), &TerminalSettings::instance().colors[11]), + + qMakePair(QLatin1String("Color4/Color"), &TerminalSettings::instance().colors[4]), + qMakePair(QLatin1String("Color4Intense/Color"), &TerminalSettings::instance().colors[12]), + + qMakePair(QLatin1String("Color5/Color"), &TerminalSettings::instance().colors[5]), + qMakePair(QLatin1String("Color5Intense/Color"), &TerminalSettings::instance().colors[13]), + + qMakePair(QLatin1String("Color6/Color"), &TerminalSettings::instance().colors[6]), + qMakePair(QLatin1String("Color6Intense/Color"), &TerminalSettings::instance().colors[14]), + + qMakePair(QLatin1String("Color7/Color"), &TerminalSettings::instance().colors[7]), + qMakePair(QLatin1String("Color7Intense/Color"), &TerminalSettings::instance().colors[15]) + }; + // clang-format on + + for (const auto &colorKey : colorKeys) { + if (settings.contains(colorKey.first)) { + const auto color = parseColor(settings.value(colorKey.first).toStringList()); + if (!color) + return make_unexpected(color.error()); + + colorKey.second->setVolatileValue(*color); + } + } + + return {}; +} + +static expected_str loadXFCE4ColorScheme(const FilePath &path) +{ + expected_str arr = path.fileContents(); + if (!arr) + return make_unexpected(arr.error()); + + arr->replace(';', ','); + + QTemporaryFile f; + f.open(); + f.write(*arr); + f.close(); + + QSettings settings(f.fileName(), QSettings::IniFormat); + + // clang-format off + const QList> colorKeys = { + qMakePair(QLatin1String("Scheme/ColorBackground"), &TerminalSettings::instance().backgroundColor), + qMakePair(QLatin1String("Scheme/ColorForeground"), &TerminalSettings::instance().foregroundColor), + }; + // clang-format on + + for (const auto &colorKey : colorKeys) { + if (settings.contains(colorKey.first)) { + colorKey.second->setVolatileValue(QColor(settings.value(colorKey.first).toString())); + } + } + + QStringList colors = settings.value(QLatin1String("Scheme/ColorPalette")).toStringList(); + int i = 0; + for (const auto &color : colors) { + TerminalSettings::instance().colors[i++].setVolatileValue(QColor(color)); + } + + return {}; +} + +static expected_str loadColorScheme(const FilePath &path) +{ + if (path.endsWith("Xdefaults")) + return loadXdefaults(path); + else if (path.suffix() == "itermcolors") + return loadItermColors(path); + else if (path.suffix() == "json") + return loadVsCodeColors(path); + else if (path.suffix() == "colorscheme") + return loadKonsoleColorScheme(path); + else if (path.suffix() == "theme" || path.completeSuffix() == "theme.txt") + return loadXFCE4ColorScheme(path); + + return make_unexpected(Tr::tr("Unknown color scheme format")); +} + +static TerminalSettings *s_instance; + +TerminalSettings &TerminalSettings::instance() +{ + return *s_instance; +} + +TerminalSettings::TerminalSettings() +{ + s_instance = this; + + setSettingsGroup("Terminal"); + setId("Terminal.General"); + setDisplayName("Terminal"); + setCategory("ZY.Terminal"); + setDisplayCategory("Terminal"); + setCategoryIconPath(":/terminal/images/settingscategory_terminal.png"); + + enableTerminal.setSettingsKey("EnableTerminal"); + enableTerminal.setLabelText(Tr::tr("Use internal terminal")); + enableTerminal.setToolTip( + Tr::tr("If enabled, use the internal terminal when \"Run In Terminal\" is " + "enabled and for \"Open Terminal here\".")); + enableTerminal.setDefaultValue(true); + + font.setSettingsKey("FontFamily"); + font.setLabelText(Tr::tr("Family:")); + font.setHistoryCompleter("Terminal.Fonts.History"); + font.setToolTip(Tr::tr("The font family used in the terminal.")); + font.setDefaultValue(defaultFontFamily()); + + fontSize.setSettingsKey("FontSize"); + fontSize.setLabelText(Tr::tr("Size:")); + fontSize.setToolTip(Tr::tr("The font size used in the terminal. (in points)")); + fontSize.setDefaultValue(defaultFontSize()); + fontSize.setRange(1, 100); + + allowBlinkingCursor.setSettingsKey("AllowBlinkingCursor"); + allowBlinkingCursor.setLabelText(Tr::tr("Allow blinking cursor")); + allowBlinkingCursor.setToolTip(Tr::tr("Allow the cursor to blink.")); + allowBlinkingCursor.setDefaultValue(false); + + shell.setSettingsKey("ShellPath"); + shell.setLabelText(Tr::tr("Shell path:")); + shell.setExpectedKind(PathChooser::ExistingCommand); + shell.setHistoryCompleter("Terminal.Shell.History"); + shell.setToolTip(Tr::tr("The shell executable to be started.")); + shell.setDefaultValue(defaultShell()); + + shellArguments.setSettingsKey("ShellArguments"); + shellArguments.setLabelText(Tr::tr("Shell arguments:")); + shellArguments.setDisplayStyle(StringAspect::LineEditDisplay); + shellArguments.setHistoryCompleter("Terminal.Shell.History"); + shellArguments.setToolTip(Tr::tr("The arguments to be passed to the shell.")); + if (!HostOsInfo::isWindowsHost()) + shellArguments.setDefaultValue(QString("-l")); + + sendEscapeToTerminal.setSettingsKey("SendEscapeToTerminal"); + sendEscapeToTerminal.setLabelText(Tr::tr("Send escape key to terminal")); + sendEscapeToTerminal.setToolTip( + Tr::tr("If enabled, pressing the escape key will send it to the terminal " + "instead of closing the terminal.")); + sendEscapeToTerminal.setDefaultValue(false); + + audibleBell.setSettingsKey("AudibleBell"); + audibleBell.setLabelText(Tr::tr("Audible bell")); + audibleBell.setToolTip(Tr::tr("If enabled, the terminal will beep when a bell " + "character is received.")); + audibleBell.setDefaultValue(true); + + setupColor(this, + foregroundColor, + "Foreground", + Utils::creatorTheme()->color(Theme::TerminalForeground)); + setupColor(this, + backgroundColor, + "Background", + Utils::creatorTheme()->color(Theme::TerminalBackground)); + setupColor(this, + selectionColor, + "Selection", + Utils::creatorTheme()->color(Theme::TerminalSelection)); + + setupColor(this, + findMatchColor, + "Find matches", + Utils::creatorTheme()->color(Theme::TerminalFindMatch)); + + setupColor(this, colors[0], "0", Utils::creatorTheme()->color(Theme::TerminalAnsi0)); + setupColor(this, colors[8], "8", Utils::creatorTheme()->color(Theme::TerminalAnsi8)); + + setupColor(this, colors[1], "1", Utils::creatorTheme()->color(Theme::TerminalAnsi1)); + setupColor(this, colors[9], "9", Utils::creatorTheme()->color(Theme::TerminalAnsi9)); + + setupColor(this, colors[2], "2", Utils::creatorTheme()->color(Theme::TerminalAnsi2)); + setupColor(this, colors[10], "10", Utils::creatorTheme()->color(Theme::TerminalAnsi10)); + + setupColor(this, colors[3], "3", Utils::creatorTheme()->color(Theme::TerminalAnsi3)); + setupColor(this, colors[11], "11", Utils::creatorTheme()->color(Theme::TerminalAnsi11)); + + setupColor(this, colors[4], "4", Utils::creatorTheme()->color(Theme::TerminalAnsi4)); + setupColor(this, colors[12], "12", Utils::creatorTheme()->color(Theme::TerminalAnsi12)); + + setupColor(this, colors[5], "5", Utils::creatorTheme()->color(Theme::TerminalAnsi5)); + setupColor(this, colors[13], "13", Utils::creatorTheme()->color(Theme::TerminalAnsi13)); + + setupColor(this, colors[6], "6", Utils::creatorTheme()->color(Theme::TerminalAnsi6)); + setupColor(this, colors[14], "14", Utils::creatorTheme()->color(Theme::TerminalAnsi14)); + + setupColor(this, colors[7], "7", Utils::creatorTheme()->color(Theme::TerminalAnsi7)); + setupColor(this, colors[15], "15", Utils::creatorTheme()->color(Theme::TerminalAnsi15)); + + setLayouter([this] { + using namespace Layouting; + + QFontComboBox *fontComboBox = new QFontComboBox; + fontComboBox->setFontFilters(QFontComboBox::MonospacedFonts); + fontComboBox->setCurrentFont(font()); + + connect(fontComboBox, &QFontComboBox::currentFontChanged, this, [this](const QFont &f) { + font.setValue(f.family()); + }); + + auto loadThemeButton = new QPushButton(Tr::tr("Load Theme...")); + auto resetTheme = new QPushButton(Tr::tr("Reset Theme")); + + connect(loadThemeButton, &QPushButton::clicked, this, [] { + const FilePath path = FileUtils::getOpenFilePath( + Core::ICore::dialogParent(), + "Open Theme", + {}, + "All Scheme formats (*.itermcolors *.json *.colorscheme *.theme *.theme.txt);;" + "Xdefaults (.Xdefaults Xdefaults);;" + "iTerm Color Schemes(*.itermcolors);;" + "VS Code Color Schemes(*.json);;" + "Konsole Color Schemes(*.colorscheme);;" + "XFCE4 Terminal Color Schemes(*.theme *.theme.txt);;" + "All files (*)", + nullptr, + {}, + true, + false); + + if (path.isEmpty()) + return; + + const expected_str result = loadColorScheme(path); + if (!result) + QMessageBox::warning(Core::ICore::dialogParent(), Tr::tr("Error"), result.error()); + }); + + connect(resetTheme, &QPushButton::clicked, this, [this] { + foregroundColor.setVolatileValue(foregroundColor.defaultValue()); + backgroundColor.setVolatileValue(backgroundColor.defaultValue()); + selectionColor.setVolatileValue(selectionColor.defaultValue()); + + for (ColorAspect &color : colors) + color.setVolatileValue(color.defaultValue()); + }); + +// FIXME: Implement and use a Layouting::DropArea item + +// DropSupport *dropSupport = new DropSupport; +// connect(dropSupport, +// &DropSupport::filesDropped, +// this, +// [this](const QList &files) { +// if (files.size() != 1) +// return; + +// const expected_str result = loadColorScheme(files.at(0).filePath); +// if (!result) +// QMessageBox::warning(Core::ICore::dialogParent(), Tr::tr("Error"), result.error()); +// }); + + // clang-format off + return Column { + Group { + title(Tr::tr("General")), + Column { + enableTerminal, st, + sendEscapeToTerminal, st, + audibleBell, st, + allowBlinkingCursor, st, + }, + }, + Group { + title(Tr::tr("Font")), + Row { + font.labelText(), fontComboBox, Space(20), + fontSize, st, + }, + }, + Group { + title(Tr::tr("Colors")), + Column { + Row { + Tr::tr("Foreground"), foregroundColor, st, + Tr::tr("Background"), backgroundColor, st, + Tr::tr("Selection"), selectionColor, st, + Tr::tr("Find match"), findMatchColor, st, + }, + Row { + colors[0], colors[1], + colors[2], colors[3], + colors[4], colors[5], + colors[6], colors[7] + }, + Row { + colors[8], colors[9], + colors[10], colors[11], + colors[12], colors[13], + colors[14], colors[15] + }, + Row { + loadThemeButton, resetTheme, st, + } + }, + }, + Group { + title(Tr::tr("Default Shell")), + Column { + shell, + shellArguments, + }, + }, + st, + }; + // clang-format on + }); + + readSettings(); +} + +} // Terminal diff --git a/src/plugins/terminal/terminalsettings.h b/src/plugins/terminal/terminalsettings.h new file mode 100644 index 00000000000..4550bbce2a2 --- /dev/null +++ b/src/plugins/terminal/terminalsettings.h @@ -0,0 +1,37 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace Terminal { + +class TerminalSettings : public Core::PagedSettings +{ +public: + TerminalSettings(); + + static TerminalSettings &instance(); + + Utils::BoolAspect enableTerminal{this}; + + Utils::StringAspect font{this}; + Utils::IntegerAspect fontSize{this}; + Utils::FilePathAspect shell{this}; + Utils::StringAspect shellArguments{this}; + + Utils::ColorAspect foregroundColor; + Utils::ColorAspect backgroundColor; + Utils::ColorAspect selectionColor; + Utils::ColorAspect findMatchColor; + + Utils::ColorAspect colors[16]; + + Utils::BoolAspect allowBlinkingCursor{this}; + + Utils::BoolAspect sendEscapeToTerminal{this}; + Utils::BoolAspect audibleBell{this}; +}; + +} // Terminal diff --git a/src/plugins/terminal/terminalsurface.cpp b/src/plugins/terminal/terminalsurface.cpp new file mode 100644 index 00000000000..ea994302318 --- /dev/null +++ b/src/plugins/terminal/terminalsurface.cpp @@ -0,0 +1,531 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "terminalsurface.h" + +#include "keys.h" +#include "scrollback.h" + +#include + +#include + +#include + +namespace Terminal::Internal { + +Q_LOGGING_CATEGORY(log, "qtc.terminal.surface", QtWarningMsg); + +QColor toQColor(const VTermColor &c) +{ + return QColor(qRgb(c.rgb.red, c.rgb.green, c.rgb.blue)); +}; + +struct TerminalSurfacePrivate +{ + TerminalSurfacePrivate(TerminalSurface *surface, + const QSize &initialGridSize, + ShellIntegration *shellIntegration) + : m_vterm(vterm_new(initialGridSize.height(), initialGridSize.width()), vterm_free) + , m_vtermScreen(vterm_obtain_screen(m_vterm.get())) + , m_scrollback(std::make_unique(5000)) + , m_shellIntegration(shellIntegration) + , q(surface) + {} + + void init() + { + vterm_set_utf8(m_vterm.get(), true); + + static auto writeToPty = [](const char *s, size_t len, void *user) { + auto p = static_cast(user); + emit p->q->writeToPty(QByteArray(s, static_cast(len))); + }; + + vterm_output_set_callback(m_vterm.get(), writeToPty, this); + + memset(&m_vtermScreenCallbacks, 0, sizeof(m_vtermScreenCallbacks)); + + m_vtermScreenCallbacks.damage = [](VTermRect rect, void *user) { + auto p = static_cast(user); + p->invalidate(rect); + return 1; + }; + m_vtermScreenCallbacks.sb_pushline = [](int cols, const VTermScreenCell *cells, void *user) { + auto p = static_cast(user); + return p->sb_pushline(cols, cells); + }; + m_vtermScreenCallbacks.sb_popline = [](int cols, VTermScreenCell *cells, void *user) { + auto p = static_cast(user); + return p->sb_popline(cols, cells); + }; + m_vtermScreenCallbacks.settermprop = [](VTermProp prop, VTermValue *val, void *user) { + auto p = static_cast(user); + return p->setTerminalProperties(prop, val); + }; + m_vtermScreenCallbacks.movecursor = + [](VTermPos pos, VTermPos oldpos, int visible, void *user) { + auto p = static_cast(user); + return p->movecursor(pos, oldpos, visible); + }; + m_vtermScreenCallbacks.sb_clear = [](void *user) { + auto p = static_cast(user); + return p->sb_clear(); + }; + m_vtermScreenCallbacks.bell = [](void *user) { + auto p = static_cast(user); + emit p->q->bell(); + return 1; + }; + + vterm_screen_set_callbacks(m_vtermScreen, &m_vtermScreenCallbacks, this); + vterm_screen_set_damage_merge(m_vtermScreen, VTERM_DAMAGE_SCROLL); + vterm_screen_enable_altscreen(m_vtermScreen, true); + + memset(&m_vtermStateFallbacks, 0, sizeof(m_vtermStateFallbacks)); + + m_vtermStateFallbacks.osc = [](int cmd, VTermStringFragment fragment, void *user) { + auto p = static_cast(user); + return p->osc(cmd, fragment); + }; + + VTermState *vts = vterm_obtain_state(m_vterm.get()); + vterm_state_set_unrecognised_fallbacks(vts, &m_vtermStateFallbacks, this); + vterm_state_set_bold_highbright(vts, true); + + VTermColor fg; + VTermColor bg; + vterm_color_indexed(&fg, ColorIndex::Foreground); + vterm_color_indexed(&bg, ColorIndex::Background); + vterm_state_set_default_colors(vts, &fg, &bg); + + for (int i = 0; i < 16; ++i) { + VTermColor col; + vterm_color_indexed(&col, i); + vterm_state_set_palette_color(vts, i, &col); + } + + vterm_screen_reset(m_vtermScreen, 1); + } + + QSize liveSize() const + { + int rows; + int cols; + vterm_get_size(m_vterm.get(), &rows, &cols); + + return QSize(cols, rows); + } + + std::variant toVariantColor(const VTermColor &color) + { + if (color.type & VTERM_COLOR_DEFAULT_BG) + return ColorIndex::Background; + else if (color.type & VTERM_COLOR_DEFAULT_FG) + return ColorIndex::Foreground; + else if (color.type & VTERM_COLOR_INDEXED) { + if (color.indexed.idx >= 16) { + VTermColor c = color; + vterm_state_convert_color_to_rgb(vterm_obtain_state(m_vterm.get()), &c); + return toQColor(c); + } + return color.indexed.idx; + } else if (color.type == VTERM_COLOR_RGB) + return toQColor(color); + else + return -1; + } + + TerminalCell toCell(const VTermScreenCell &cell) + { + TerminalCell result; + result.width = cell.width; + result.text = QString::fromUcs4(cell.chars); + + const VTermColor *bg = &cell.bg; + const VTermColor *fg = &cell.fg; + + if (static_cast(cell.attrs.reverse)) + std::swap(fg, bg); + + result.backgroundColor = toVariantColor(*bg); + result.foregroundColor = toVariantColor(*fg); + + result.bold = cell.attrs.bold; + result.strikeOut = cell.attrs.strike; + + if (cell.attrs.underline > 0) { + result.underlineStyle = QTextCharFormat::NoUnderline; + switch (cell.attrs.underline) { + case VTERM_UNDERLINE_SINGLE: + result.underlineStyle = QTextCharFormat::SingleUnderline; + break; + case VTERM_UNDERLINE_DOUBLE: + // TODO: Double underline + result.underlineStyle = QTextCharFormat::SingleUnderline; + break; + case VTERM_UNDERLINE_CURLY: + result.underlineStyle = QTextCharFormat::WaveUnderline; + break; + case VTERM_UNDERLINE_DASHED: + result.underlineStyle = QTextCharFormat::DashUnderline; + break; + case VTERM_UNDERLINE_DOTTED: + result.underlineStyle = QTextCharFormat::DotLine; + break; + } + } + + result.strikeOut = cell.attrs.strike; + + return result; + } + + // Callbacks from vterm + void invalidate(VTermRect rect) + { + if (!m_altscreen) { + rect.start_row += m_scrollback->size(); + rect.end_row += m_scrollback->size(); + } + + emit q->invalidated( + QRect{QPoint{rect.start_col, rect.start_row}, QPoint{rect.end_col, rect.end_row - 1}}); + } + + int sb_pushline(int cols, const VTermScreenCell *cells) + { + m_scrollback->emplace(cols, cells); + emit q->fullSizeChanged(q->fullSize()); + return 1; + } + + int sb_popline(int cols, VTermScreenCell *cells) + { + if (m_scrollback->size() == 0) + return 0; + + m_scrollback->popto(cols, cells); + emit q->fullSizeChanged(q->fullSize()); + return 1; + } + + int sb_clear() + { + m_scrollback->clear(); + emit q->fullSizeChanged(q->fullSize()); + return 1; + } + + int osc(int cmd, const VTermStringFragment &fragment) + { + if (m_shellIntegration) + m_shellIntegration->onOsc(cmd, fragment); + + return 1; + } + + int setTerminalProperties(VTermProp prop, VTermValue *val) + { + switch (prop) { + case VTERM_PROP_CURSORVISIBLE: { + Cursor old = q->cursor(); + m_cursor.visible = val->boolean; + q->cursorChanged(old, q->cursor()); + break; + } + case VTERM_PROP_CURSORBLINK: { + Cursor old = q->cursor(); + m_cursor.blink = val->boolean; + emit q->cursorChanged(old, q->cursor()); + break; + } + case VTERM_PROP_CURSORSHAPE: { + Cursor old = q->cursor(); + m_cursor.shape = (Cursor::Shape) val->number; + emit q->cursorChanged(old, q->cursor()); + break; + } + case VTERM_PROP_ICONNAME: + break; + case VTERM_PROP_TITLE: + break; + case VTERM_PROP_ALTSCREEN: + m_altscreen = val->boolean; + emit q->altscreenChanged(m_altscreen); + break; + case VTERM_PROP_MOUSE: + qCDebug(log) << "Ignoring VTERM_PROP_MOUSE" << val->number; + break; + case VTERM_PROP_REVERSE: + qCDebug(log) << "Ignoring VTERM_PROP_REVERSE" << val->boolean; + break; + case VTERM_N_PROPS: + break; + } + return 1; + } + int movecursor(VTermPos pos, VTermPos oldpos, int visible) + { + Q_UNUSED(oldpos); + Cursor oldCursor = q->cursor(); + m_cursor.position = {pos.col, pos.row}; + m_cursor.visible = visible > 0; + q->cursorChanged(oldCursor, q->cursor()); + return 1; + } + + const VTermScreenCell *cellAt(int x, int y) + { + QTC_ASSERT(y >= 0 && x >= 0, return nullptr); + QTC_ASSERT(y < q->fullSize().height() && x < liveSize().width(), return nullptr); + + if (!m_altscreen && y < m_scrollback->size()) { + const auto &sbl = m_scrollback->line((m_scrollback->size() - 1) - y); + if (x < sbl.cols()) { + return sbl.cell(x); + } + return nullptr; + } + + if (!m_altscreen) + y -= m_scrollback->size(); + + static VTermScreenCell refCell{}; + VTermPos vtp{y, x}; + vterm_screen_get_cell(m_vtermScreen, vtp, &refCell); + + return &refCell; + } + + std::unique_ptr m_vterm; + VTermScreen *m_vtermScreen; + VTermScreenCallbacks m_vtermScreenCallbacks; + VTermStateFallbacks m_vtermStateFallbacks; + + Cursor m_cursor; + QString m_currentCommand; + + bool m_altscreen{false}; + + std::unique_ptr m_scrollback; + + ShellIntegration *m_shellIntegration{nullptr}; + + TerminalSurface *q; +}; + +TerminalSurface::TerminalSurface(QSize initialGridSize, ShellIntegration *shellIntegration) + : d(std::make_unique(this, initialGridSize, shellIntegration)) +{ + d->init(); +} + +TerminalSurface::~TerminalSurface() = default; + +int TerminalSurface::cellWidthAt(int x, int y) const +{ + const VTermScreenCell *cell = d->cellAt(x, y); + if (!cell) + return 0; + return cell->width; +} + +QSize TerminalSurface::liveSize() const +{ + return d->liveSize(); +} + +QSize TerminalSurface::fullSize() const +{ + if (d->m_altscreen) + return liveSize(); + return QSize{d->liveSize().width(), d->liveSize().height() + d->m_scrollback->size()}; +} + +std::u32string::value_type TerminalSurface::fetchCharAt(int x, int y) const +{ + const VTermScreenCell *cell = d->cellAt(x, y); + if (!cell) + return 0; + + if (cell->width == 0) + return 0; + + QString s = QString::fromUcs4(cell->chars, 6).normalized(QString::NormalizationForm_C); + const QList ucs4 = s.toUcs4(); + return std::u32string(ucs4.begin(), ucs4.end()).front(); +} + +TerminalCell TerminalSurface::fetchCell(int x, int y) const +{ + static TerminalCell emptyCell{1, + {}, + {}, + false, + ColorIndex::Foreground, + ColorIndex::Background, + QTextCharFormat::NoUnderline, + false}; + + QTC_ASSERT(y >= 0, return emptyCell); + QTC_ASSERT(y < fullSize().height() && x < fullSize().width(), return emptyCell); + + const VTermScreenCell *refCell = d->cellAt(x, y); + if (!refCell) + return emptyCell; + + return d->toCell(*refCell); +} + +void TerminalSurface::clearAll() +{ + // Fake a scrollback clearing + QByteArray data{"\x1b[3J"}; + vterm_input_write(d->m_vterm.get(), data.constData(), data.size()); + + // Send Ctrl+L which will clear the screen + emit writeToPty(QByteArray("\f")); +} + +void TerminalSurface::resize(QSize newSize) +{ + vterm_set_size(d->m_vterm.get(), newSize.height(), newSize.width()); +} + +QPoint TerminalSurface::posToGrid(int pos) const +{ + return {pos % d->liveSize().width(), pos / d->liveSize().width()}; +} +int TerminalSurface::gridToPos(QPoint gridPos) const +{ + return gridPos.y() * d->liveSize().width() + gridPos.x(); +} + +void TerminalSurface::dataFromPty(const QByteArray &data) +{ + vterm_input_write(d->m_vterm.get(), data.constData(), data.size()); + vterm_screen_flush_damage(d->m_vtermScreen); +} + +void TerminalSurface::flush() +{ + vterm_screen_flush_damage(d->m_vtermScreen); +} + +void TerminalSurface::pasteFromClipboard(const QString &clipboardText) +{ + if (clipboardText.isEmpty()) + return; + + vterm_keyboard_start_paste(d->m_vterm.get()); + for (unsigned int ch : clipboardText.toUcs4()) + vterm_keyboard_unichar(d->m_vterm.get(), ch, VTERM_MOD_NONE); + vterm_keyboard_end_paste(d->m_vterm.get()); + + if (!d->m_altscreen) { + emit unscroll(); + } +} + +void TerminalSurface::sendKey(Qt::Key key) +{ + if (key == Qt::Key_Escape) + vterm_keyboard_key(d->m_vterm.get(), VTERM_KEY_ESCAPE, VTERM_MOD_NONE); +} + +void TerminalSurface::sendKey(const QString &text) +{ + for (const unsigned int ch : text.toUcs4()) + vterm_keyboard_unichar(d->m_vterm.get(), ch, VTERM_MOD_NONE); +} + +void TerminalSurface::sendKey(QKeyEvent *event) +{ + bool keypad = event->modifiers() & Qt::KeypadModifier; + VTermModifier mod = Internal::qtModifierToVTerm(event->modifiers()); + VTermKey key = Internal::qtKeyToVTerm(Qt::Key(event->key()), keypad); + + if (key != VTERM_KEY_NONE) { + if (mod == VTERM_MOD_SHIFT && (key == VTERM_KEY_ESCAPE || key == VTERM_KEY_BACKSPACE)) + mod = VTERM_MOD_NONE; + + vterm_keyboard_key(d->m_vterm.get(), key, mod); + } else if (event->text().length() == 1) { + // This maps to delete word and is way to easy to mistakenly type + // if (event->key() == Qt::Key_Space && mod == VTERM_MOD_SHIFT) + // mod = VTERM_MOD_NONE; + + // Per https://github.com/justinmk/neovim/commit/317d5ca7b0f92ef42de989b3556ca9503f0a3bf6 + // libvterm prefers we send the full keycode rather than sending the + // ctrl modifier. This helps with ncurses applications which otherwise + // do not recognize ctrl+ and in the shell for getting common control characters + // like ctrl+i for tab or ctrl+j for newline. + + // Workaround for "ALT+SHIFT+/" (\ on german mac keyboards) + if (mod == (VTERM_MOD_SHIFT | VTERM_MOD_ALT) && event->key() == Qt::Key_Slash) { + mod = VTERM_MOD_NONE; + } + + vterm_keyboard_unichar(d->m_vterm.get(), event->text().toUcs4()[0], VTERM_MOD_NONE); + } else if (mod == VTERM_MOD_CTRL && event->key() >= Qt::Key_A && event->key() < Qt::Key_Z) { + vterm_keyboard_unichar(d->m_vterm.get(), 'a' + (event->key() - Qt::Key_A), mod); + } +} + +Cursor TerminalSurface::cursor() const +{ + Cursor cursor = d->m_cursor; + if (!d->m_altscreen) + cursor.position.setY(cursor.position.y() + d->m_scrollback->size()); + + return cursor; +} + +ShellIntegration *TerminalSurface::shellIntegration() const +{ + return d->m_shellIntegration; +} + +CellIterator TerminalSurface::begin() const +{ + auto res = CellIterator(this, {0, 0}); + res.m_state = CellIterator::State::BEGIN; + return res; +} + +CellIterator TerminalSurface::end() const +{ + return CellIterator(this); +} + +std::reverse_iterator TerminalSurface::rbegin() const +{ + return std::make_reverse_iterator(end()); +} + +std::reverse_iterator TerminalSurface::rend() const +{ + return std::make_reverse_iterator(begin()); +} + +CellIterator TerminalSurface::iteratorAt(QPoint pos) const +{ + return CellIterator(this, pos); +} +CellIterator TerminalSurface::iteratorAt(int pos) const +{ + return CellIterator(this, pos); +} + +std::reverse_iterator TerminalSurface::rIteratorAt(QPoint pos) const +{ + return std::make_reverse_iterator(iteratorAt(pos)); +} + +std::reverse_iterator TerminalSurface::rIteratorAt(int pos) const +{ + return std::make_reverse_iterator(iteratorAt(pos)); +} + +} // namespace Terminal::Internal diff --git a/src/plugins/terminal/terminalsurface.h b/src/plugins/terminal/terminalsurface.h new file mode 100644 index 00000000000..a6fc7425d48 --- /dev/null +++ b/src/plugins/terminal/terminalsurface.h @@ -0,0 +1,111 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "celliterator.h" +#include "shellintegration.h" + +#include +#include +#include + +#include + +namespace Terminal::Internal { + +class Scrollback; + +struct TerminalSurfacePrivate; + +enum ColorIndex { Foreground = 16, Background = 17 }; + +struct TerminalCell +{ + int width; + QString text; + bool bold{false}; + bool italic{false}; + std::variant foregroundColor; + std::variant backgroundColor; + QTextCharFormat::UnderlineStyle underlineStyle{QTextCharFormat::NoUnderline}; + bool strikeOut{false}; +}; + +struct Cursor +{ + enum class Shape { + Block = 1, + Underline, + LeftBar, + }; + QPoint position; + bool visible; + Shape shape; + bool blink{false}; +}; + +class TerminalSurface : public QObject +{ + Q_OBJECT; + +public: + TerminalSurface(QSize initialGridSize, ShellIntegration *shellIntegration); + ~TerminalSurface(); + +public: + CellIterator begin() const; + CellIterator end() const; + std::reverse_iterator rbegin() const; + std::reverse_iterator rend() const; + + CellIterator iteratorAt(QPoint pos) const; + CellIterator iteratorAt(int pos) const; + + std::reverse_iterator rIteratorAt(QPoint pos) const; + std::reverse_iterator rIteratorAt(int pos) const; + +public: + void clearAll(); + + void resize(QSize newSize); + + TerminalCell fetchCell(int x, int y) const; + std::u32string::value_type fetchCharAt(int x, int y) const; + int cellWidthAt(int x, int y) const; + + QSize liveSize() const; + QSize fullSize() const; + + QPoint posToGrid(int pos) const; + int gridToPos(QPoint gridPos) const; + + void dataFromPty(const QByteArray &data); + void flush(); + + void pasteFromClipboard(const QString &text); + + void sendKey(Qt::Key key); + void sendKey(QKeyEvent *event); + void sendKey(const QString &text); + + int invertedScrollOffset() const; + + Cursor cursor() const; + + ShellIntegration *shellIntegration() const; + +signals: + void writeToPty(const QByteArray &data); + void invalidated(QRect grid); + void fullSizeChanged(QSize newSize); + void cursorChanged(Cursor oldCursor, Cursor newCursor); + void altscreenChanged(bool altScreen); + void unscroll(); + void bell(); + +private: + std::unique_ptr d; +}; + +} // namespace Terminal::Internal diff --git a/src/plugins/terminal/terminaltr.h b/src/plugins/terminal/terminaltr.h new file mode 100644 index 00000000000..b645284833c --- /dev/null +++ b/src/plugins/terminal/terminaltr.h @@ -0,0 +1,15 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace Terminal { + +struct Tr +{ + Q_DECLARE_TR_FUNCTIONS(QtC::Terminal) +}; + +} // namespace Terminal diff --git a/src/plugins/terminal/terminalwidget.cpp b/src/plugins/terminal/terminalwidget.cpp new file mode 100644 index 00000000000..5027d390294 --- /dev/null +++ b/src/plugins/terminal/terminalwidget.cpp @@ -0,0 +1,1568 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "terminalwidget.h" +#include "glyphcache.h" +#include "terminalconstants.h" +#include "terminalsettings.h" +#include "terminalsurface.h" +#include "terminaltr.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(terminalLog, "qtc.terminal", QtWarningMsg) +Q_LOGGING_CATEGORY(selectionLog, "qtc.terminal.selection", QtWarningMsg) +Q_LOGGING_CATEGORY(paintLog, "qtc.terminal.paint", QtWarningMsg) + +using namespace Utils; +using namespace Utils::Terminal; +using namespace Core; + +namespace Terminal { + +namespace ColorIndex { +enum Indices { + Foreground = Internal::ColorIndex::Foreground, + Background = Internal::ColorIndex::Background, + Selection, + FindMatch, +}; +} + +using namespace std::chrono_literals; + +// Minimum time between two refreshes. (30fps) +static constexpr std::chrono::milliseconds minRefreshInterval = 1s / 30; + +TerminalWidget::TerminalWidget(QWidget *parent, const OpenTerminalParameters &openParameters) + : QAbstractScrollArea(parent) + , m_context(Utils::Id("TerminalWidget_").withSuffix((size_t) this)) + , m_openParameters(openParameters) + , m_lastFlush(std::chrono::system_clock::now()) + , m_lastDoubleClick(std::chrono::system_clock::now()) +{ + auto contextObj = new IContext(this); + contextObj->setWidget(this); + contextObj->setContext(m_context); + ICore::addContextObject(contextObj); + + setupSurface(); + setupFont(); + setupColors(); + setupActions(); + + m_cursorBlinkTimer.setInterval(750); + m_cursorBlinkTimer.setSingleShot(false); + + connect(&m_cursorBlinkTimer, &QTimer::timeout, this, [this]() { + if (hasFocus()) + m_cursorBlinkState = !m_cursorBlinkState; + else + m_cursorBlinkState = true; + updateViewportRect(gridToViewport(QRect{m_cursor.position, m_cursor.position})); + }); + + setAttribute(Qt::WA_InputMethodEnabled); + setAttribute(Qt::WA_MouseTracking); + setAcceptDrops(true); + + setCursor(Qt::IBeamCursor); + + setViewportMargins(1, 1, 1, 1); + + setFocus(); + setFocusPolicy(Qt::StrongFocus); + + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + + m_flushDelayTimer.setSingleShot(true); + m_flushDelayTimer.setInterval(minRefreshInterval); + + connect(&m_flushDelayTimer, &QTimer::timeout, this, [this]() { flushVTerm(true); }); + + m_scrollTimer.setSingleShot(false); + m_scrollTimer.setInterval(1s / 2); + connect(&m_scrollTimer, &QTimer::timeout, this, [this] { + if (m_scrollDirection < 0) + verticalScrollBar()->triggerAction(QAbstractSlider::SliderSingleStepSub); + else if (m_scrollDirection > 0) + verticalScrollBar()->triggerAction(QAbstractSlider::SliderSingleStepAdd); + }); + + connect(&TerminalSettings::instance(), &AspectContainer::applied, this, [this] { + // Setup colors first, as setupFont will redraw the screen. + setupColors(); + setupFont(); + configBlinkTimer(); + }); + + m_aggregate = new Aggregation::Aggregate(this); + m_aggregate->add(this); + m_aggregate->add(m_search.get()); +} + +void TerminalWidget::setupPty() +{ + m_process = std::make_unique(); + + CommandLine shellCommand = m_openParameters.shellCommand.value_or( + CommandLine{TerminalSettings::instance().shell.filePath(), + TerminalSettings::instance().shellArguments.value(), + CommandLine::Raw}); + + Environment env = m_openParameters.environment.value_or( + shellCommand.executable().deviceEnvironment()); + + // For git bash on Windows + env.prependOrSetPath(shellCommand.executable().parentDir()); + if (env.hasKey("CLINK_NOAUTORUN")) + env.unset("CLINK_NOAUTORUN"); + + m_process->setProcessMode(ProcessMode::Writer); + m_process->setPtyData(Utils::Pty::Data()); + m_process->setCommand(shellCommand); + if (m_openParameters.workingDirectory.has_value()) + m_process->setWorkingDirectory(*m_openParameters.workingDirectory); + m_process->setEnvironment(env); + + if (m_surface->shellIntegration()) { + m_surface->shellIntegration()->prepareProcess(*m_process.get()); + } + + connect(m_process.get(), &Process::readyReadStandardOutput, this, [this]() { + onReadyRead(false); + }); + + connect(m_process.get(), &Process::done, this, [this] { + if (m_process) { + if (m_process->exitCode() != 0) { + QByteArray msg = QString("\r\n\033[31mProcess exited with code: %1") + .arg(m_process->exitCode()) + .toUtf8(); + + if (!m_process->errorString().isEmpty()) + msg += QString(" (%1)").arg(m_process->errorString()).toUtf8(); + + m_surface->dataFromPty(msg); + + return; + } + } + + if (m_openParameters.m_exitBehavior == ExitBehavior::Restart) { + QMetaObject::invokeMethod( + this, + [this] { + m_process.reset(); + setupSurface(); + setupPty(); + }, + Qt::QueuedConnection); + } + + if (m_openParameters.m_exitBehavior == ExitBehavior::Close) + deleteLater(); + + if (m_openParameters.m_exitBehavior == ExitBehavior::Keep) { + QByteArray msg = QString("\r\nProcess exited with code: %1") + .arg(m_process ? m_process->exitCode() : -1) + .toUtf8(); + + m_surface->dataFromPty(msg); + } + }); + + connect(m_process.get(), &Process::started, this, [this] { + if (m_shellName.isEmpty()) + m_shellName = m_process->commandLine().executable().fileName(); + if (HostOsInfo::isWindowsHost() && m_shellName.endsWith(QTC_WIN_EXE_SUFFIX)) + m_shellName.chop(QStringLiteral(QTC_WIN_EXE_SUFFIX).size()); + + applySizeChange(); + emit started(m_process->processId()); + }); + + m_process->start(); +} + +void TerminalWidget::setupFont() +{ + QFont f; + f.setFixedPitch(true); + f.setFamily(TerminalSettings::instance().font.value()); + f.setPointSize(TerminalSettings::instance().fontSize.value()); + + setFont(f); +} + +void TerminalWidget::setupColors() +{ + // Check if the colors have changed. + std::array newColors; + for (int i = 0; i < 16; ++i) { + newColors[i] = TerminalSettings::instance().colors[i].value(); + } + newColors[ColorIndex::Background] = TerminalSettings::instance().backgroundColor.value(); + newColors[ColorIndex::Foreground] = TerminalSettings::instance().foregroundColor.value(); + newColors[ColorIndex::Selection] = TerminalSettings::instance().selectionColor.value(); + newColors[ColorIndex::FindMatch] = TerminalSettings::instance().findMatchColor.value(); + + if (m_currentColors == newColors) + return; + + m_currentColors = newColors; + + updateViewport(); + update(); +} + +void TerminalWidget::setupActions() +{ + ActionManager::registerAction(&m_copy, Constants::COPY, m_context); + ActionManager::registerAction(&m_paste, Constants::PASTE, m_context); + ActionManager::registerAction(&m_close, Core::Constants::CLOSE, m_context); + ActionManager::registerAction(&m_clearTerminal, Constants::CLEAR_TERMINAL, m_context); + ActionManager::registerAction(&m_clearSelection, Constants::CLEARSELECTION, m_context); + ActionManager::registerAction(&m_moveCursorWordLeft, Constants::MOVECURSORWORDLEFT, m_context); + ActionManager::registerAction(&m_moveCursorWordRight, Constants::MOVECURSORWORDRIGHT, m_context); + + connect(&m_copy, &QAction::triggered, this, &TerminalWidget::copyToClipboard); + connect(&m_paste, &QAction::triggered, this, &TerminalWidget::pasteFromClipboard); + connect(&m_close, &QAction::triggered, this, &TerminalWidget::closeTerminal); + connect(&m_clearTerminal, &QAction::triggered, this, &TerminalWidget::clearContents); + connect(&m_clearSelection, &QAction::triggered, this, &TerminalWidget::clearSelection); + connect(&m_moveCursorWordLeft, &QAction::triggered, this, &TerminalWidget::moveCursorWordLeft); + connect(&m_moveCursorWordRight, &QAction::triggered, this, &TerminalWidget::moveCursorWordRight); + + m_exit = unlockGlobalAction(Core::Constants::EXIT, m_context); + m_options = unlockGlobalAction(Core::Constants::OPTIONS, m_context); + m_settings = unlockGlobalAction("Preferences.Terminal.General", m_context); + m_findInDocument = unlockGlobalAction(Core::Constants::FIND_IN_DOCUMENT, m_context); +} + +void TerminalWidget::closeTerminal() +{ + deleteLater(); +} + +void TerminalWidget::writeToPty(const QByteArray &data) +{ + if (m_process && m_process->isRunning()) + m_process->writeRaw(data); +} + +void TerminalWidget::setupSurface() +{ + m_shellIntegration.reset(new ShellIntegration()); + m_surface = std::make_unique(QSize{80, 60}, m_shellIntegration.get()); + m_search = TerminalSearchPtr(new TerminalSearch(m_surface.get()), [this](TerminalSearch *p) { + m_aggregate->remove(p); + delete p; + }); + + connect(m_search.get(), &TerminalSearch::hitsChanged, this, &TerminalWidget::updateViewport); + connect(m_search.get(), &TerminalSearch::currentHitChanged, this, [this] { + SearchHit hit = m_search->currentHit(); + if (hit.start >= 0) { + setSelection(Selection{hit.start, hit.end, true}, hit != m_lastSelectedHit); + m_lastSelectedHit = hit; + } + }); + + connect(m_surface.get(), + &Internal::TerminalSurface::writeToPty, + this, + &TerminalWidget::writeToPty); + connect(m_surface.get(), &Internal::TerminalSurface::fullSizeChanged, this, [this] { + updateScrollBars(); + }); + connect(m_surface.get(), + &Internal::TerminalSurface::invalidated, + this, + [this](const QRect &rect) { + setSelection(std::nullopt); + updateViewportRect(gridToViewport(rect)); + verticalScrollBar()->setValue(m_surface->fullSize().height()); + }); + connect(m_surface.get(), + &Internal::TerminalSurface::cursorChanged, + this, + [this](const Internal::Cursor &oldCursor, const Internal::Cursor &newCursor) { + int startX = oldCursor.position.x(); + int endX = newCursor.position.x(); + + if (startX > endX) + std::swap(startX, endX); + + int startY = oldCursor.position.y(); + int endY = newCursor.position.y(); + if (startY > endY) + std::swap(startY, endY); + + m_cursor = newCursor; + + updateViewportRect( + gridToViewport(QRect{QPoint{startX, startY}, QPoint{endX, endY}})); + configBlinkTimer(); + }); + connect(m_surface.get(), &Internal::TerminalSurface::altscreenChanged, this, [this] { + updateScrollBars(); + if (!setSelection(std::nullopt)) + updateViewport(); + }); + connect(m_surface.get(), &Internal::TerminalSurface::unscroll, this, [this] { + verticalScrollBar()->setValue(verticalScrollBar()->maximum()); + }); + connect(m_surface.get(), &Internal::TerminalSurface::bell, this, [] { + if (TerminalSettings::instance().audibleBell.value()) + QApplication::beep(); + }); + + if (m_shellIntegration) { + connect(m_shellIntegration.get(), + &ShellIntegration::commandChanged, + this, + [this](const CommandLine &command) { + m_currentCommand = command; + emit commandChanged(m_currentCommand); + }); + connect(m_shellIntegration.get(), + &ShellIntegration::currentDirChanged, + this, + [this](const QString ¤tDir) { + m_cwd = FilePath::fromUserInput(currentDir); + emit cwdChanged(m_cwd); + }); + } +} + +void TerminalWidget::configBlinkTimer() +{ + bool shouldRun = m_cursor.visible && m_cursor.blink && hasFocus() + && TerminalSettings::instance().allowBlinkingCursor.value(); + if (shouldRun != m_cursorBlinkTimer.isActive()) { + if (shouldRun) + m_cursorBlinkTimer.start(); + else + m_cursorBlinkTimer.stop(); + } +} + +QColor TerminalWidget::toQColor(std::variant color) const +{ + if (std::holds_alternative(color)) { + int idx = std::get(color); + if (idx >= 0 && idx < 18) + return m_currentColors[idx]; + + return m_currentColors[ColorIndex::Background]; + } + return std::get(color); +} + +void TerminalWidget::updateCopyState() +{ + if (!hasFocus()) + return; + + m_copy.setEnabled(m_selection.has_value()); +} + +void TerminalWidget::setFont(const QFont &font) +{ + m_font = font; + + QFontMetricsF qfm{m_font}; + const qreal w = [qfm]() -> qreal { + if (HostOsInfo::isMacHost()) + return qfm.maxWidth(); + return qfm.averageCharWidth(); + }(); + + qCInfo(terminalLog) << font.family() << font.pointSize() << w << viewport()->size(); + + m_cellSize = {w, (double) qCeil(qfm.height())}; + + QAbstractScrollArea::setFont(m_font); + + if (m_process) { + applySizeChange(); + } +} + +void TerminalWidget::copyToClipboard() +{ + QTC_ASSERT(m_selection.has_value(), return); + + QString text = textFromSelection(); + + qCDebug(selectionLog) << "Copied to clipboard: " << text; + + setClipboardAndSelection(text); +} + +void TerminalWidget::pasteFromClipboard() +{ + QClipboard *clipboard = QApplication::clipboard(); + const QString clipboardText = clipboard->text(QClipboard::Clipboard); + + if (clipboardText.isEmpty()) + return; + + m_surface->pasteFromClipboard(clipboardText); +} + +void TerminalWidget::copyLinkToClipboard() +{ + if (m_linkSelection) + setClipboardAndSelection(m_linkSelection->link.targetFilePath.toUserOutput()); +} + +void TerminalWidget::clearSelection() +{ + setSelection(std::nullopt); + m_surface->sendKey(Qt::Key_Escape); +} + +void TerminalWidget::zoomIn() +{ + m_font.setPointSize(m_font.pointSize() + 1); + setFont(m_font); +} + +void TerminalWidget::zoomOut() +{ + m_font.setPointSize(qMax(m_font.pointSize() - 1, 1)); + setFont(m_font); +} + +void TerminalWidget::moveCursorWordLeft() +{ + writeToPty("\x1b\x62"); +} + +void TerminalWidget::moveCursorWordRight() +{ + writeToPty("\x1b\x66"); +} + +void TerminalWidget::clearContents() +{ + m_surface->clearAll(); +} + +void TerminalWidget::onReadyRead(bool forceFlush) +{ + QByteArray data = m_process->readAllRawStandardOutput(); + + m_surface->dataFromPty(data); + + flushVTerm(forceFlush); +} + +void TerminalWidget::flushVTerm(bool force) +{ + const std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); + const std::chrono::milliseconds timeSinceLastFlush + = std::chrono::duration_cast(now - m_lastFlush); + + const bool shouldFlushImmediately = timeSinceLastFlush > minRefreshInterval; + if (force || shouldFlushImmediately) { + if (m_flushDelayTimer.isActive()) + m_flushDelayTimer.stop(); + + m_lastFlush = now; + m_surface->flush(); + return; + } + + if (!m_flushDelayTimer.isActive()) { + const std::chrono::milliseconds timeToNextFlush = (minRefreshInterval - timeSinceLastFlush); + m_flushDelayTimer.start(timeToNextFlush.count()); + } +} + +QString TerminalWidget::textFromSelection() const +{ + if (!m_selection) + return {}; + + Internal::CellIterator it = m_surface->iteratorAt(m_selection->start); + Internal::CellIterator end = m_surface->iteratorAt(m_selection->end); + + QTC_ASSERT(it.position() < end.position(), return {}); + + std::u32string s; + bool previousWasZero = false; + for (; it != end; ++it) { + if (it.gridPos().x() == 0 && !s.empty() && previousWasZero) + s += U'\n'; + + if (*it != 0) { + previousWasZero = false; + s += *it; + } else { + previousWasZero = true; + } + } + + return QString::fromUcs4(s.data(), static_cast(s.size())); +} + +bool TerminalWidget::setSelection(const std::optional &selection, bool scroll) +{ + qCDebug(selectionLog) << "setSelection" << selection.has_value(); + if (selection.has_value()) + qCDebug(selectionLog) << "start:" << selection->start << "end:" << selection->end + << "final:" << selection->final; + + if (selectionLog().isDebugEnabled()) + updateViewport(); + + if (selection == m_selection) + return false; + + m_selection = selection; + + updateCopyState(); + + if (m_selection && m_selection->final) { + qCDebug(selectionLog) << "Copy enabled:" << selection.has_value(); + QString text = textFromSelection(); + + QClipboard *clipboard = QApplication::clipboard(); + if (clipboard->supportsSelection()) { + qCDebug(selectionLog) << "Selection set to clipboard: " << text; + clipboard->setText(text, QClipboard::Selection); + } + + if (scroll) { + QPoint start = m_surface->posToGrid(m_selection->start); + QPoint end = m_surface->posToGrid(m_selection->end); + QRect viewRect = gridToViewport(QRect{start, end}); + if (viewRect.y() >= viewport()->height() || viewRect.y() < 0) { + // Selection is outside of the viewport, scroll to it. + verticalScrollBar()->setValue(start.y()); + } + } + + m_search->setCurrentSelection(SearchHitWithText{{selection->start, selection->end}, text}); + } + + if (!selectionLog().isDebugEnabled()) + updateViewport(); + + return true; +} + +void TerminalWidget::setShellName(const QString &shellName) +{ + m_shellName = shellName; +} + +QString TerminalWidget::shellName() const +{ + return m_shellName; +} + +FilePath TerminalWidget::cwd() const +{ + return m_cwd; +} + +CommandLine TerminalWidget::currentCommand() const +{ + return m_currentCommand; +} + +std::optional TerminalWidget::identifier() const +{ + return m_openParameters.identifier; +} + +QProcess::ProcessState TerminalWidget::processState() const +{ + if (m_process) + return m_process->state(); + + return QProcess::NotRunning; +} + +void TerminalWidget::restart(const OpenTerminalParameters &openParameters) +{ + QTC_ASSERT(!m_process || !m_process->isRunning(), return); + m_openParameters = openParameters; + + m_process.reset(); + setupSurface(); + setupPty(); +} + +QPoint TerminalWidget::viewportToGlobal(QPoint p) const +{ + int y = p.y() - topMargin(); + const double offset = verticalScrollBar()->value() * m_cellSize.height(); + y += offset; + + return {p.x(), y}; +} + +QPoint TerminalWidget::globalToViewport(QPoint p) const +{ + int y = p.y() + topMargin(); + const double offset = verticalScrollBar()->value() * m_cellSize.height(); + y -= offset; + + return {p.x(), y}; +} + +QPoint TerminalWidget::globalToGrid(QPointF p) const +{ + return QPoint(p.x() / m_cellSize.width(), p.y() / m_cellSize.height()); +} + +QPointF TerminalWidget::gridToGlobal(QPoint p, bool bottom, bool right) const +{ + QPointF result = QPointF(p.x() * m_cellSize.width(), p.y() * m_cellSize.height()); + if (bottom || right) + result += {right ? m_cellSize.width() : 0, bottom ? m_cellSize.height() : 0}; + return result; +} + +qreal TerminalWidget::topMargin() const +{ + return viewport()->size().height() - (m_surface->liveSize().height() * m_cellSize.height()); +} + +static QPixmap generateWavyPixmap(qreal maxRadius, const QPen &pen) +{ + const qreal radiusBase = qMax(qreal(1), maxRadius); + const qreal pWidth = pen.widthF(); + + const QString key = QLatin1String("WaveUnderline-") % pen.color().name() + % QString::number(int(radiusBase), 16) + % QString::number(int(pWidth), 16); + + QPixmap pixmap; + if (QPixmapCache::find(key, &pixmap)) + return pixmap; + + const qreal halfPeriod = qMax(qreal(2), qreal(radiusBase * 1.61803399)); // the golden ratio + const int width = qCeil(100 / (2 * halfPeriod)) * (2 * halfPeriod); + const qreal radius = qFloor(radiusBase * 2) / 2.; + + QPainterPath path; + + qreal xs = 0; + qreal ys = radius; + + while (xs < width) { + xs += halfPeriod; + ys = -ys; + path.quadTo(xs - halfPeriod / 2, ys, xs, 0); + } + + pixmap = QPixmap(width, radius * 2); + pixmap.fill(Qt::transparent); + { + QPen wavePen = pen; + wavePen.setCapStyle(Qt::SquareCap); + + // This is to protect against making the line too fat, as happens on macOS + // due to it having a rather thick width for the regular underline. + const qreal maxPenWidth = .8 * radius; + if (wavePen.widthF() > maxPenWidth) + wavePen.setWidthF(maxPenWidth); + + QPainter imgPainter(&pixmap); + imgPainter.setPen(wavePen); + imgPainter.setRenderHint(QPainter::Antialiasing); + imgPainter.translate(0, radius); + imgPainter.drawPath(path); + } + + QPixmapCache::insert(key, pixmap); + + return pixmap; +} + +// Copied from qpainter.cpp +static void drawTextItemDecoration(QPainter &painter, + const QPointF &pos, + QTextCharFormat::UnderlineStyle underlineStyle, + QTextItem::RenderFlags flags, + qreal width, + const QColor &underlineColor, + const QRawFont &font) +{ + if (underlineStyle == QTextCharFormat::NoUnderline + && !(flags & (QTextItem::StrikeOut | QTextItem::Overline))) + return; + + const QPen oldPen = painter.pen(); + const QBrush oldBrush = painter.brush(); + painter.setBrush(Qt::NoBrush); + QPen pen = oldPen; + pen.setStyle(Qt::SolidLine); + pen.setWidthF(font.lineThickness()); + pen.setCapStyle(Qt::FlatCap); + + QLineF line(qFloor(pos.x()), pos.y(), qFloor(pos.x() + width), pos.y()); + + const qreal underlineOffset = font.underlinePosition(); + + /*if (underlineStyle == QTextCharFormat::SpellCheckUnderline) { + QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme(); + if (theme) + underlineStyle = QTextCharFormat::UnderlineStyle( + theme->themeHint(QPlatformTheme::SpellCheckUnderlineStyle).toInt()); + if (underlineStyle == QTextCharFormat::SpellCheckUnderline) // still not resolved + underlineStyle = QTextCharFormat::WaveUnderline; + }*/ + + if (underlineStyle == QTextCharFormat::WaveUnderline) { + painter.save(); + painter.translate(0, pos.y() + 1); + qreal maxHeight = font.descent() - qreal(1); + + QColor uc = underlineColor; + if (uc.isValid()) + pen.setColor(uc); + + // Adapt wave to underlineOffset or pen width, whatever is larger, to make it work on all platforms + const QPixmap wave = generateWavyPixmap(qMin(qMax(underlineOffset, pen.widthF()), + maxHeight / qreal(2.)), + pen); + const int descent = qFloor(maxHeight); + + painter.setBrushOrigin(painter.brushOrigin().x(), 0); + painter.fillRect(pos.x(), 0, qCeil(width), qMin(wave.height(), descent), wave); + painter.restore(); + } else if (underlineStyle != QTextCharFormat::NoUnderline) { + // Deliberately ceil the offset to avoid the underline coming too close to + // the text above it, but limit it to stay within descent. + qreal adjustedUnderlineOffset = std::ceil(underlineOffset) + 0.5; + if (underlineOffset <= font.descent()) + adjustedUnderlineOffset = qMin(adjustedUnderlineOffset, font.descent() - qreal(0.5)); + const qreal underlinePos = pos.y() + adjustedUnderlineOffset; + QColor uc = underlineColor; + if (uc.isValid()) + pen.setColor(uc); + + pen.setStyle((Qt::PenStyle)(underlineStyle)); + painter.setPen(pen); + QLineF underline(line.x1(), underlinePos, line.x2(), underlinePos); + painter.drawLine(underline); + } + + pen.setStyle(Qt::SolidLine); + pen.setColor(oldPen.color()); + + if (flags & QTextItem::StrikeOut) { + QLineF strikeOutLine = line; + strikeOutLine.translate(0., -font.ascent() / 3.); + QColor uc = underlineColor; + if (uc.isValid()) + pen.setColor(uc); + painter.setPen(pen); + painter.drawLine(strikeOutLine); + } + + if (flags & QTextItem::Overline) { + QLineF overline = line; + overline.translate(0., -font.ascent()); + QColor uc = underlineColor; + if (uc.isValid()) + pen.setColor(uc); + painter.setPen(pen); + painter.drawLine(overline); + } + + painter.setPen(oldPen); + painter.setBrush(oldBrush); +} + +bool TerminalWidget::paintFindMatches(QPainter &p, + QList::const_iterator &it, + const QRectF &cellRect, + const QPoint gridPos) const +{ + if (it == m_search->hits().constEnd()) + return false; + + const int pos = m_surface->gridToPos(gridPos); + while (it != m_search->hits().constEnd()) { + if (pos < it->start) + return false; + + if (pos >= it->end) { + ++it; + continue; + } + break; + } + + if (it == m_search->hits().constEnd()) + return false; + + p.fillRect(cellRect, m_currentColors[ColorIndex::FindMatch]); + + return true; +} + +bool TerminalWidget::paintSelection(QPainter &p, const QRectF &cellRect, const QPoint gridPos) const +{ + bool isInSelection = false; + const int pos = m_surface->gridToPos(gridPos); + + if (m_selection) + isInSelection = pos >= m_selection->start && pos < m_selection->end; + + if (isInSelection) + p.fillRect(cellRect, m_currentColors[ColorIndex::Selection]); + + return isInSelection; +} + +int TerminalWidget::paintCell(QPainter &p, + const QRectF &cellRect, + QPoint gridPos, + const Internal::TerminalCell &cell, + QFont &f, + QList::const_iterator &searchIt) const +{ + bool paintBackground = !paintSelection(p, cellRect, gridPos) + && !paintFindMatches(p, searchIt, cellRect, gridPos); + + bool isDefaultBg = std::holds_alternative(cell.backgroundColor) + && std::get(cell.backgroundColor) == 17; + + if (paintBackground && !isDefaultBg) + p.fillRect(cellRect, toQColor(cell.backgroundColor)); + + p.setPen(toQColor(cell.foregroundColor)); + + f.setBold(cell.bold); + f.setItalic(cell.italic); + + if (!cell.text.isEmpty()) { + const auto r = Internal::GlyphCache::instance().get(f, cell.text); + + if (r) { + const auto brSize = r->boundingRect().size(); + QPointF brOffset; + if (brSize.width() > cellRect.size().width()) + brOffset.setX(-(brSize.width() - cellRect.size().width()) / 2.0); + if (brSize.height() > cellRect.size().height()) + brOffset.setY(-(brSize.height() - cellRect.size().height()) / 2.0); + + QPointF finalPos = cellRect.topLeft() + brOffset; + + p.drawGlyphRun(finalPos, *r); + + bool tempLink = false; + if (m_linkSelection) { + int chPos = m_surface->gridToPos(gridPos); + tempLink = chPos >= m_linkSelection->start && chPos < m_linkSelection->end; + } + if (cell.underlineStyle != QTextCharFormat::NoUnderline || cell.strikeOut || tempLink) { + QTextItem::RenderFlags flags; + //flags.setFlag(QTextItem::RenderFlag::Underline, cell.format.fontUnderline()); + flags.setFlag(QTextItem::StrikeOut, cell.strikeOut); + finalPos.setY(finalPos.y() + r->rawFont().ascent()); + drawTextItemDecoration(p, + finalPos, + tempLink ? QTextCharFormat::DashUnderline + : cell.underlineStyle, + flags, + cellRect.size().width(), + {}, + r->rawFont()); + } + } + } + + return cell.width; +} + +void TerminalWidget::paintCursor(QPainter &p) const +{ + if (!m_process || !m_process->isRunning()) + return; + + auto cursor = m_surface->cursor(); + + const bool blinkState = !cursor.blink || m_cursorBlinkState + || !TerminalSettings::instance().allowBlinkingCursor.value(); + + if (cursor.visible && blinkState) { + const int cursorCellWidth = m_surface->cellWidthAt(cursor.position.x(), cursor.position.y()); + + QRectF cursorRect = QRectF(gridToGlobal(cursor.position), + gridToGlobal({cursor.position.x() + cursorCellWidth, + cursor.position.y()}, + true)) + .toAlignedRect(); + + cursorRect.adjust(1, 1, -1, -1); + + QPen pen(Qt::white, 0, Qt::SolidLine); + p.setPen(pen); + + if (hasFocus()) { + QPainter::CompositionMode oldMode = p.compositionMode(); + p.setCompositionMode(QPainter::RasterOp_NotDestination); + switch (cursor.shape) { + case Internal::Cursor::Shape::Block: + p.fillRect(cursorRect, p.pen().brush()); + break; + case Internal::Cursor::Shape::Underline: + p.drawLine(cursorRect.bottomLeft(), cursorRect.bottomRight()); + break; + case Internal::Cursor::Shape::LeftBar: + p.drawLine(cursorRect.topLeft(), cursorRect.bottomLeft()); + break; + } + p.setCompositionMode(oldMode); + } else { + p.drawRect(cursorRect); + } + } +} + +void TerminalWidget::paintPreedit(QPainter &p) const +{ + auto cursor = m_surface->cursor(); + if (!m_preEditString.isEmpty()) { + QRectF rect = QRectF(gridToGlobal(cursor.position), + gridToGlobal({cursor.position.x(), cursor.position.y()}, true, true)); + + p.fillRect(rect, QColor::fromRgb(0, 0, 0)); + p.setPen(Qt::white); + p.drawText(rect, m_preEditString); + } +} + +void TerminalWidget::paintCells(QPainter &p, QPaintEvent *event) const +{ + QFont f = m_font; + + const int scrollOffset = verticalScrollBar()->value(); + + const int maxRow = m_surface->fullSize().height(); + const int startRow = qFloor((qreal) event->rect().y() / m_cellSize.height()) + scrollOffset; + const int endRow = qMin(maxRow, + qCeil((event->rect().y() + event->rect().height()) / m_cellSize.height()) + + scrollOffset); + + QList::const_iterator searchIt + = std::lower_bound(m_search->hits().constBegin(), + m_search->hits().constEnd(), + startRow, + [this](const SearchHit &hit, int value) { + return m_surface->posToGrid(hit.start).y() < value; + }); + + for (int cellY = startRow; cellY < endRow; ++cellY) { + for (int cellX = 0; cellX < m_surface->liveSize().width();) { + const auto cell = m_surface->fetchCell(cellX, cellY); + + QRectF cellRect(gridToGlobal({cellX, cellY}), + QSizeF{m_cellSize.width() * cell.width, m_cellSize.height()}); + + int numCells = paintCell(p, cellRect, {cellX, cellY}, cell, f, searchIt); + + cellX += numCells; + } + } +} + +void TerminalWidget::paintDebugSelection(QPainter &p, const Selection &selection) const +{ + auto s = globalToViewport(gridToGlobal(m_surface->posToGrid(selection.start)).toPoint()); + const auto e = globalToViewport( + gridToGlobal(m_surface->posToGrid(selection.end), true).toPoint()); + + p.setPen(QPen(Qt::green, 1, Qt::DashLine)); + p.drawLine(s.x(), 0, s.x(), height()); + p.drawLine(0, s.y(), width(), s.y()); + + p.setPen(QPen(Qt::red, 1, Qt::DashLine)); + + p.drawLine(e.x(), 0, e.x(), height()); + p.drawLine(0, e.y(), width(), e.y()); +} + +void TerminalWidget::paintEvent(QPaintEvent *event) +{ + QElapsedTimer t; + t.start(); + event->accept(); + QPainter p(viewport()); + + p.save(); + + if (paintLog().isDebugEnabled()) + p.fillRect(event->rect(), QColor::fromRgb(rand() % 60, rand() % 60, rand() % 60)); + else + p.fillRect(event->rect(), m_currentColors[ColorIndex::Background]); + + int scrollOffset = verticalScrollBar()->value(); + int offset = -(scrollOffset * m_cellSize.height()); + + qreal margin = topMargin(); + + p.translate(QPointF{0.0, offset + margin}); + + paintCells(p, event); + paintCursor(p); + paintPreedit(p); + + p.restore(); + + p.fillRect(QRectF{{0, 0}, QSizeF{(qreal) width(), topMargin()}}, + m_currentColors[ColorIndex::Background]); + + if (selectionLog().isDebugEnabled()) { + if (m_selection) + paintDebugSelection(p, *m_selection); + if (m_linkSelection) + paintDebugSelection(p, *m_linkSelection); + } + + if (paintLog().isDebugEnabled()) { + QToolTip::showText(this->mapToGlobal(QPoint(width() - 200, 0)), + QString("Paint: %1ms").arg(t.elapsed())); + } +} + +void TerminalWidget::keyPressEvent(QKeyEvent *event) +{ + // Don't blink during typing + if (m_cursorBlinkTimer.isActive()) { + m_cursorBlinkTimer.start(); + m_cursorBlinkState = true; + } + + if (event->key() == Qt::Key_Escape) { + bool sendToTerminal = TerminalSettings::instance().sendEscapeToTerminal.value(); + bool send = false; + if (sendToTerminal && event->modifiers() == Qt::NoModifier) + send = true; + else if (!sendToTerminal && event->modifiers() == Qt::ShiftModifier) + send = true; + + if (send) { + event->setModifiers(Qt::NoModifier); + m_surface->sendKey(event); + return; + } + + if (m_selection) + m_clearSelection.trigger(); + else { + QAction *returnAction = ActionManager::command(Core::Constants::S_RETURNTOEDITOR) + ->actionForContext(Core::Constants::C_GLOBAL); + QTC_ASSERT(returnAction, return); + returnAction->trigger(); + } + return; + } + + if (event->key() == Qt::Key_Control) { + if (!m_linkSelection.has_value() && checkLinkAt(mapFromGlobal(QCursor::pos()))) { + setCursor(Qt::PointingHandCursor); + } + } + + event->accept(); + + m_surface->sendKey(event); +} + +void TerminalWidget::keyReleaseEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Control && m_linkSelection.has_value()) { + m_linkSelection.reset(); + updateCopyState(); + setCursor(Qt::IBeamCursor); + updateViewport(); + } +} + +void TerminalWidget::applySizeChange() +{ + QSize newLiveSize = { + qFloor((qreal) (viewport()->size().width()) / (qreal) m_cellSize.width()), + qFloor((qreal) (viewport()->size().height()) / m_cellSize.height()), + }; + + if (newLiveSize.height() <= 0) + newLiveSize.setHeight(1); + + if (newLiveSize.width() <= 0) + newLiveSize.setWidth(1); + + if (m_process && m_process->ptyData()) + m_process->ptyData()->resize(newLiveSize); + + m_surface->resize(newLiveSize); + flushVTerm(true); +} + +void TerminalWidget::updateScrollBars() +{ + int scrollSize = m_surface->fullSize().height() - m_surface->liveSize().height(); + verticalScrollBar()->setRange(0, scrollSize); + verticalScrollBar()->setValue(verticalScrollBar()->maximum()); + updateViewport(); +} + +void TerminalWidget::resizeEvent(QResizeEvent *event) +{ + event->accept(); + + // If increasing in size, we'll trigger libvterm to call sb_popline in + // order to pull lines out of the history. This will cause the scrollback + // to decrease in size which reduces the size of the verticalScrollBar. + // That will trigger a scroll offset increase which we want to ignore. + m_ignoreScroll = true; + + applySizeChange(); + + setSelection(std::nullopt); + m_ignoreScroll = false; +} + +QRect TerminalWidget::gridToViewport(QRect rect) const +{ + int offset = verticalScrollBar()->value(); + + int startRow = rect.y() - offset; + int numRows = rect.height(); + int numCols = rect.width(); + + QRectF r{rect.x() * m_cellSize.width(), + startRow * m_cellSize.height(), + numCols * m_cellSize.width(), + numRows * m_cellSize.height()}; + + r.translate(0, topMargin()); + + return r.toAlignedRect(); +} + +void TerminalWidget::updateViewport() +{ + viewport()->update(); +} + +void TerminalWidget::updateViewportRect(const QRect &rect) +{ + viewport()->update(rect); +} + +void TerminalWidget::wheelEvent(QWheelEvent *event) +{ + verticalScrollBar()->event(event); +} + +void TerminalWidget::focusInEvent(QFocusEvent *) +{ + updateViewport(); + configBlinkTimer(); + updateCopyState(); +} +void TerminalWidget::focusOutEvent(QFocusEvent *) +{ + updateViewport(); + configBlinkTimer(); +} + +void TerminalWidget::inputMethodEvent(QInputMethodEvent *event) +{ + m_preEditString = event->preeditString(); + + if (event->commitString().isEmpty()) { + updateViewport(); + return; + } + + m_surface->sendKey(event->commitString()); +} + +void TerminalWidget::mousePressEvent(QMouseEvent *event) +{ + m_scrollDirection = 0; + + m_activeMouseSelect.start = viewportToGlobal(event->pos()); + + if (event->button() == Qt::LeftButton && event->modifiers() & Qt::ControlModifier) { + if (m_linkSelection) { + if (event->modifiers() & Qt::ShiftModifier) { + copyLinkToClipboard(); + return; + } + + if (m_linkSelection->link.targetFilePath.scheme().toString().startsWith("http")) { + QDesktopServices::openUrl(m_linkSelection->link.targetFilePath.toUrl()); + return; + } + + if (m_linkSelection->link.targetFilePath.isDir()) + Core::FileUtils::showInFileSystemView(m_linkSelection->link.targetFilePath); + else + EditorManager::openEditorAt(m_linkSelection->link); + } + return; + } + + if (event->button() == Qt::LeftButton) { + if (std::chrono::system_clock::now() - m_lastDoubleClick < 500ms) { + m_selectLineMode = true; + const Selection newSelection{m_surface->gridToPos( + {0, m_surface->posToGrid(m_selection->start).y()}), + m_surface->gridToPos( + {m_surface->liveSize().width(), + m_surface->posToGrid(m_selection->end).y()}), + false}; + setSelection(newSelection); + } else { + m_selectLineMode = false; + int pos = m_surface->gridToPos(globalToGrid(viewportToGlobal(event->pos()))); + setSelection(Selection{pos, pos, false}); + } + event->accept(); + updateViewport(); + } else if (event->button() == Qt::RightButton) { + if (event->modifiers() & Qt::ShiftModifier) { + QMenu *contextMenu = new QMenu(this); + QAction *configureAction = new QAction(contextMenu); + configureAction->setText(Tr::tr("Configure...")); + connect(configureAction, &QAction::triggered, this, [] { + ICore::showOptionsDialog("Terminal.General"); + }); + + contextMenu->addAction(ActionManager::command(Constants::COPY)->action()); + contextMenu->addAction(ActionManager::command(Constants::PASTE)->action()); + contextMenu->addSeparator(); + contextMenu->addAction(ActionManager::command(Constants::CLEAR_TERMINAL)->action()); + contextMenu->addSeparator(); + contextMenu->addAction(configureAction); + + contextMenu->popup(event->globalPos()); + } else if (m_selection) { + copyToClipboard(); + setSelection(std::nullopt); + } else { + pasteFromClipboard(); + } + } else if (event->button() == Qt::MiddleButton) { + QClipboard *clipboard = QApplication::clipboard(); + if (clipboard->supportsSelection()) { + const QString selectionText = clipboard->text(QClipboard::Selection); + if (!selectionText.isEmpty()) + m_surface->pasteFromClipboard(selectionText); + } else { + m_surface->pasteFromClipboard(textFromSelection()); + } + } +} +void TerminalWidget::mouseMoveEvent(QMouseEvent *event) +{ + if (m_selection && event->buttons() & Qt::LeftButton) { + Selection newSelection = *m_selection; + int scrollVelocity = 0; + if (event->pos().y() < 0) { + scrollVelocity = (event->pos().y()); + } else if (event->pos().y() > viewport()->height()) { + scrollVelocity = (event->pos().y() - viewport()->height()); + } + + if ((scrollVelocity != 0) != m_scrollTimer.isActive()) { + if (scrollVelocity != 0) + m_scrollTimer.start(); + else + m_scrollTimer.stop(); + } + + m_scrollDirection = scrollVelocity; + + if (m_scrollTimer.isActive() && scrollVelocity != 0) { + const std::chrono::milliseconds scrollInterval = 1000ms / qAbs(scrollVelocity); + if (m_scrollTimer.intervalAsDuration() != scrollInterval) + m_scrollTimer.setInterval(scrollInterval); + } + + QPoint posBoundedToViewport = event->pos(); + posBoundedToViewport.setX(qBound(0, posBoundedToViewport.x(), viewport()->width())); + + int start = m_surface->gridToPos(globalToGrid(m_activeMouseSelect.start)); + int newEnd = m_surface->gridToPos(globalToGrid(viewportToGlobal(posBoundedToViewport))); + + if (start > newEnd) { + std::swap(start, newEnd); + } + if (start < 0) + start = 0; + + if (m_selectLineMode) { + newSelection.start = m_surface->gridToPos({0, m_surface->posToGrid(start).y()}); + newSelection.end = m_surface->gridToPos( + {m_surface->liveSize().width(), m_surface->posToGrid(newEnd).y()}); + } else { + newSelection.start = start; + newSelection.end = newEnd; + } + + setSelection(newSelection); + } else if (event->modifiers() & Qt::ControlModifier) { + checkLinkAt(event->pos()); + } else if (m_linkSelection) { + m_linkSelection.reset(); + updateCopyState(); + updateViewport(); + } + + if (m_linkSelection) { + setCursor(Qt::PointingHandCursor); + } else { + setCursor(Qt::IBeamCursor); + } +} + +bool TerminalWidget::checkLinkAt(const QPoint &pos) +{ + const TextAndOffsets hit = textAt(pos); + + if (hit.text.size() > 0) { + QString t = QString::fromUcs4(hit.text.c_str(), hit.text.size()).trimmed(); + t = chopIfEndsWith(t, ':'); + + if (!t.isEmpty()) { + if (t.startsWith("~/")) + t = QDir::homePath() + t.mid(1); + + Link link = Link::fromString(t, true); + + if (!link.targetFilePath.isEmpty() && !link.targetFilePath.isAbsolutePath()) + link.targetFilePath = m_cwd.pathAppended(link.targetFilePath.path()); + + if (link.hasValidTarget() + && (link.targetFilePath.scheme().toString().startsWith("http") + || link.targetFilePath.exists())) { + const LinkSelection newSelection = LinkSelection{{hit.start, hit.end}, link}; + if (!m_linkSelection || *m_linkSelection != newSelection) { + m_linkSelection = newSelection; + updateViewport(); + updateCopyState(); + } + return true; + } + } + } + + if (m_linkSelection) { + m_linkSelection.reset(); + updateCopyState(); + updateViewport(); + } + return false; +} + +void TerminalWidget::mouseReleaseEvent(QMouseEvent *event) +{ + m_scrollTimer.stop(); + + if (m_selection && event->button() == Qt::LeftButton) { + if (m_selection->end - m_selection->start == 0) + setSelection(std::nullopt); + else + setSelection(Selection{m_selection->start, m_selection->end, true}); + } +} + +TerminalWidget::TextAndOffsets TerminalWidget::textAt(const QPoint &pos) const +{ + auto it = m_surface->iteratorAt(globalToGrid(viewportToGlobal(pos))); + auto itRev = m_surface->rIteratorAt(globalToGrid(viewportToGlobal(pos))); + + std::u32string whiteSpaces = U" \t\x00a0"; + + const bool inverted = whiteSpaces.find(*it) != std::u32string::npos || *it == 0; + + auto predicate = [inverted, whiteSpaces](const std::u32string::value_type &ch) { + if (inverted) + return ch != 0 && whiteSpaces.find(ch) == std::u32string::npos; + else + return ch == 0 || whiteSpaces.find(ch) != std::u32string::npos; + }; + + auto itRight = std::find_if(it, m_surface->end(), predicate); + auto itLeft = std::find_if(itRev, m_surface->rend(), predicate); + + std::u32string text; + std::copy(itLeft.base(), it, std::back_inserter(text)); + std::copy(it, itRight, std::back_inserter(text)); + std::transform(text.begin(), text.end(), text.begin(), [](const char32_t &ch) { + return ch == 0 ? U' ' : ch; + }); + + return {(itLeft.base()).position(), itRight.position(), text}; +} + +void TerminalWidget::mouseDoubleClickEvent(QMouseEvent *event) +{ + const auto hit = textAt(event->pos()); + + setSelection(Selection{hit.start, hit.end, true}); + + m_lastDoubleClick = std::chrono::system_clock::now(); + + event->accept(); +} + +void TerminalWidget::dragEnterEvent(QDragEnterEvent *event) +{ + if (event->mimeData()->hasUrls()) { + event->setDropAction(Qt::CopyAction); + event->accept(); + } +} + +void TerminalWidget::dropEvent(QDropEvent *event) +{ + QString urls = Utils::transform(event->mimeData()->urls(), [](const QUrl &url) { + return QString("\"%1\"").arg(url.toDisplayString(QUrl::PreferLocalFile)); + }).join(" "); + + writeToPty(urls.toUtf8()); + event->setDropAction(Qt::CopyAction); + event->accept(); +} + +void TerminalWidget::showEvent(QShowEvent *event) +{ + Q_UNUSED(event); + + if (!m_process) + setupPty(); + + QAbstractScrollArea::showEvent(event); +} + +bool TerminalWidget::event(QEvent *event) +{ + if (event->type() == QEvent::Paint) { + QPainter p(this); + p.fillRect(QRect(QPoint(0, 0), size()), m_currentColors[ColorIndex::Background]); + return true; + } + + if (event->type() == QEvent::KeyPress) { + QKeyEvent *k = (QKeyEvent *) event; + keyPressEvent(k); + return true; + } + if (event->type() == QEvent::KeyRelease) { + QKeyEvent *k = (QKeyEvent *) event; + keyReleaseEvent(k); + return true; + } + + return QAbstractScrollArea::event(event); +} + +void TerminalWidget::initActions() +{ + Core::Context context(Utils::Id("TerminalWidget")); + + static QAction copy; + static QAction paste; + static QAction clearSelection; + static QAction clearTerminal; + static QAction moveCursorWordLeft; + static QAction moveCursorWordRight; + static QAction close; + + copy.setText(Tr::tr("Copy")); + paste.setText(Tr::tr("Paste")); + clearSelection.setText(Tr::tr("Clear Selection")); + clearTerminal.setText(Tr::tr("Clear Terminal")); + moveCursorWordLeft.setText(Tr::tr("Move Cursor Word Left")); + moveCursorWordRight.setText(Tr::tr("Move Cursor Word Right")); + close.setText(Tr::tr("Close Terminal")); + + ActionManager::registerAction(©, Constants::COPY, context) + ->setDefaultKeySequences({QKeySequence( + HostOsInfo::isMacHost() ? QLatin1String("Ctrl+C") : QLatin1String("Ctrl+Shift+C"))}); + + ActionManager::registerAction(&paste, Constants::PASTE, context) + ->setDefaultKeySequences({QKeySequence( + HostOsInfo::isMacHost() ? QLatin1String("Ctrl+V") : QLatin1String("Ctrl+Shift+V"))}); + + ActionManager::registerAction(&clearSelection, Constants::CLEARSELECTION, context); + + ActionManager::registerAction(&moveCursorWordLeft, Constants::MOVECURSORWORDLEFT, context) + ->setDefaultKeySequences({QKeySequence("Alt+Left")}); + + ActionManager::registerAction(&moveCursorWordRight, Constants::MOVECURSORWORDRIGHT, context) + ->setDefaultKeySequences({QKeySequence("Alt+Right")}); + + ActionManager::registerAction(&clearTerminal, Constants::CLEAR_TERMINAL, context); +} + +UnlockedGlobalAction TerminalWidget::unlockGlobalAction(const Utils::Id &commandId, + const Context &context) +{ + QAction *srcAction = ActionManager::command(commandId)->actionForContext( + Core::Constants::C_GLOBAL); + + ProxyAction *proxy = ProxyAction::proxyActionWithIcon(srcAction, srcAction->icon()); + ActionManager::registerAction(proxy, commandId, context); + + UnlockedGlobalAction registeredAction(proxy, [commandId](QAction *a) { + ActionManager::unregisterAction(a, commandId); + delete a; + }); + + return registeredAction; +} + +} // namespace Terminal diff --git a/src/plugins/terminal/terminalwidget.h b/src/plugins/terminal/terminalwidget.h new file mode 100644 index 00000000000..4b82e4355a1 --- /dev/null +++ b/src/plugins/terminal/terminalwidget.h @@ -0,0 +1,256 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "terminalsearch.h" +#include "terminalsurface.h" + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +namespace Terminal { + +using UnlockedGlobalAction = std::unique_ptr>; + +class TerminalWidget : public QAbstractScrollArea +{ + friend class CellIterator; + Q_OBJECT +public: + TerminalWidget(QWidget *parent = nullptr, + const Utils::Terminal::OpenTerminalParameters &openParameters = {}); + + void setFont(const QFont &font); + + void copyToClipboard(); + void pasteFromClipboard(); + void copyLinkToClipboard(); + + void clearSelection(); + + void zoomIn(); + void zoomOut(); + + void moveCursorWordLeft(); + void moveCursorWordRight(); + + void clearContents(); + + void closeTerminal(); + + TerminalSearch *search() { return m_search.get(); } + + struct Selection + { + int start; + int end; + bool final{false}; + + bool operator!=(const Selection &other) const + { + return start != other.start || end != other.end || final != other.final; + } + + bool operator==(const Selection &other) const { return !operator!=(other); } + }; + + struct LinkSelection : public Selection + { + Utils::Link link; + + bool operator!=(const LinkSelection &other) const + { + return link != other.link || Selection::operator!=(other); + } + }; + + void setShellName(const QString &shellName); + QString shellName() const; + + Utils::FilePath cwd() const; + Utils::CommandLine currentCommand() const; + std::optional identifier() const; + QProcess::ProcessState processState() const; + + void restart(const Utils::Terminal::OpenTerminalParameters &openParameters); + + static void initActions(); + + [[nodiscard]] static UnlockedGlobalAction unlockGlobalAction(const Utils::Id &commandId, + const Core::Context &context); + +signals: + void started(qint64 pid); + void cwdChanged(const Utils::FilePath &cwd); + void commandChanged(const Utils::CommandLine &cmd); + +protected: + void paintEvent(QPaintEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + void wheelEvent(QWheelEvent *event) override; + void focusInEvent(QFocusEvent *event) override; + void focusOutEvent(QFocusEvent *event) override; + void inputMethodEvent(QInputMethodEvent *event) override; + + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; + + void dragEnterEvent(QDragEnterEvent *event) override; + void dropEvent(QDropEvent *event) override; + + void showEvent(QShowEvent *event) override; + + bool event(QEvent *event) override; + +protected: + void onReadyRead(bool forceFlush); + void setupSurface(); + void setupFont(); + void setupPty(); + void setupColors(); + void setupActions(); + + void writeToPty(const QByteArray &data); + + int paintCell(QPainter &p, + const QRectF &cellRect, + QPoint gridPos, + const Internal::TerminalCell &cell, + QFont &f, + QList::const_iterator &searchIt) const; + void paintCells(QPainter &painter, QPaintEvent *event) const; + void paintCursor(QPainter &painter) const; + void paintPreedit(QPainter &painter) const; + bool paintFindMatches(QPainter &painter, + QList::const_iterator &searchIt, + const QRectF &cellRect, + const QPoint gridPos) const; + bool paintSelection(QPainter &painter, const QRectF &cellRect, const QPoint gridPos) const; + void paintDebugSelection(QPainter &painter, const Selection &selection) const; + + qreal topMargin() const; + + QPoint viewportToGlobal(QPoint p) const; + QPoint globalToViewport(QPoint p) const; + QPoint globalToGrid(QPointF p) const; + QPointF gridToGlobal(QPoint p, bool bottom = false, bool right = false) const; + QRect gridToViewport(QRect rect) const; + + void updateViewport(); + void updateViewportRect(const QRect &rect); + + int textLineFromPixel(int y) const; + std::optional textPosFromPoint(const QTextLayout &textLayout, QPoint p) const; + + std::optional selectionToFormatRange( + TerminalWidget::Selection selection, const QTextLayout &layout, int rowOffset) const; + + bool checkLinkAt(const QPoint &pos); + + struct TextAndOffsets + { + int start; + int end; + std::u32string text; + }; + + TextAndOffsets textAt(const QPoint &pos) const; + + void applySizeChange(); + void updateScrollBars(); + + void flushVTerm(bool force); + + bool setSelection(const std::optional &selection, bool scroll = true); + QString textFromSelection() const; + + void configBlinkTimer(); + + QColor toQColor(std::variant color) const; + + void updateCopyState(); + +private: + Core::Context m_context; + std::unique_ptr m_process; + std::unique_ptr m_surface; + std::unique_ptr m_shellIntegration; + + QString m_shellName; + Utils::Id m_identifier; + + QFont m_font; + QSizeF m_cellSize; + + bool m_ignoreScroll{false}; + + QString m_preEditString; + + std::optional m_selection; + std::optional m_linkSelection; + + struct + { + QPoint start; + QPoint end; + } m_activeMouseSelect; + + QTimer m_flushDelayTimer; + + QTimer m_scrollTimer; + int m_scrollDirection{0}; + + std::array m_currentColors; + + Utils::Terminal::OpenTerminalParameters m_openParameters; + + std::chrono::system_clock::time_point m_lastFlush; + std::chrono::system_clock::time_point m_lastDoubleClick; + bool m_selectLineMode{false}; + + Internal::Cursor m_cursor; + QTimer m_cursorBlinkTimer; + bool m_cursorBlinkState{true}; + + Utils::FilePath m_cwd; + Utils::CommandLine m_currentCommand; + + using TerminalSearchPtr = std::unique_ptr>; + TerminalSearchPtr m_search; + + Aggregation::Aggregate *m_aggregate{nullptr}; + SearchHit m_lastSelectedHit{}; + + QAction m_copy; + QAction m_paste; + QAction m_clearSelection; + QAction m_clearTerminal; + QAction m_moveCursorWordLeft; + QAction m_moveCursorWordRight; + QAction m_close; + + UnlockedGlobalAction m_findInDocument; + UnlockedGlobalAction m_exit; + UnlockedGlobalAction m_options; + UnlockedGlobalAction m_settings; +}; + +} // namespace Terminal diff --git a/src/plugins/terminal/tests/colors b/src/plugins/terminal/tests/colors new file mode 100755 index 00000000000..a1910d45cc7 --- /dev/null +++ b/src/plugins/terminal/tests/colors @@ -0,0 +1,112 @@ +#!/usr/bin/python3 +# Source: https://gist.github.com/lilydjwg/fdeaf79e921c2f413f44b6f613f6ad53 + +from functools import partial + + +def colors16(): + for bold in [0, 1]: + for i in range(30, 38): + for j in range(40, 48): + print(f'\x1b[{bold};{i};{j}m {bold};{i};{j} |\x1b[0m', end='') + print() + print() + + for bold in [0, 1]: + for i in range(90, 98): + for j in range(100, 108): + print(f'\x1b[{bold};{i};{j}m {bold};{i};{j} |\x1b[0m', end='') + print() + print() + + +def color1(c, n=0): + print(f'\x1b[{n};38;5;{c}m{c:4}\x1b[0m', end='') + + +def color1_sep(c): + if (c - 15) % 18 == 0: + print() + + +def color2(c): + print(f'\x1b[48;5;{c}m \x1b[0m', end='') + + +def color2_sep(c): + if (c - 15) % 36 == 0: + print() + elif (c - 15) % 6 == 0: + print(' ', end='') + + +def colors256(color, sepfunc): + for i in range(0, 8): + color(i) + print() + for i in range(8, 16): + color(i) + print('\n') + + for i in range(16, 232): + color(i) + sepfunc(i) + print() + + for i in range(232, 256): + color(i) + print('\n') + + +def colors_gradient(): + s = '/\\' * 40 + for col in range(0, 77): + r = 255 - col * 255 // 76 + g = col * 510 // 76 + b = col * 255 // 76 + if g > 255: + g = 510 - g + print( + f'\x1b[48;2;{r};{g};{b}m\x1b[38;2;{255-r};{255-g};{255-b}m{s[col]}\x1b[0m', end='') + print() + + +def other_attributes(): + for i in range(0, 10): + print(f' \x1b[{i}mSGR {i:2}\x1b[m', end=' ') + print(' \x1b[53mSGR 53\x1b[m', end=' ') # overline + print('\n') + # https://askubuntu.com/a/985386/235132 + for i in range(1, 6): + print(f' \x1b[4:{i}mSGR 4:{i}\x1b[m', end=' ') + print(' \x1b[21mSGR 21\x1b[m', end=' ') + + print( + ' \x1b[4:3m\x1b[58;2;135;0;255mtruecolor underline\x1b[59m\x1b[4:0m', end=' ') + print(' \x1b]8;;https://askubuntu.com/a/985386/235132\x1b\\hyperlink\x1b]8;;\x1b\\') + + +if __name__ == '__main__': + print('basic 16 colors, foreground & background:\n') + colors16() + + print('256 colors:\n') + colors256(color1, color1_sep) + + print('256 colors, bold:\n') + colors256(partial(color1, n=1), color1_sep) + + print('256 colors, dim:\n') + colors256(partial(color1, n=2), color1_sep) + + print('256 colors, bold dim:\n') + colors256(partial(color1, n='1;2'), color1_sep) + + print('256 colors, solid background:\n') + colors256(color2, color2_sep) + + print('true colors gradient:\n') + colors_gradient() + + print('other attributes:\n') + other_attributes() diff --git a/src/plugins/terminal/tests/cursor/bar b/src/plugins/terminal/tests/cursor/bar new file mode 100755 index 00000000000..a7bd99b55dd --- /dev/null +++ b/src/plugins/terminal/tests/cursor/bar @@ -0,0 +1,3 @@ +#!/bin/sh + +echo -e -n "\x1b[\x36 q" # Steady bar diff --git a/src/plugins/terminal/tests/cursor/blinkbar b/src/plugins/terminal/tests/cursor/blinkbar new file mode 100755 index 00000000000..0acf6179d9c --- /dev/null +++ b/src/plugins/terminal/tests/cursor/blinkbar @@ -0,0 +1,3 @@ +#!/bin/sh + +echo -e -n "\x1b[\x35 q" # Blinking bar diff --git a/src/plugins/terminal/tests/cursor/blinkblock b/src/plugins/terminal/tests/cursor/blinkblock new file mode 100755 index 00000000000..c536c83b8b7 --- /dev/null +++ b/src/plugins/terminal/tests/cursor/blinkblock @@ -0,0 +1,3 @@ +#!/bin/sh + +echo -e -n "\x1b[\x31 q" # Blinking block (default) diff --git a/src/plugins/terminal/tests/cursor/blinkunderline b/src/plugins/terminal/tests/cursor/blinkunderline new file mode 100755 index 00000000000..745d9e2a29c --- /dev/null +++ b/src/plugins/terminal/tests/cursor/blinkunderline @@ -0,0 +1,3 @@ +#!/bin/sh + +echo -e -n "\x1b[\x33 q" # Blinking underline diff --git a/src/plugins/terminal/tests/cursor/block b/src/plugins/terminal/tests/cursor/block new file mode 100755 index 00000000000..421df3b8c5a --- /dev/null +++ b/src/plugins/terminal/tests/cursor/block @@ -0,0 +1,3 @@ +#!/bin/sh + +echo -e -n "\x1b[\x32 q" # Steady block diff --git a/src/plugins/terminal/tests/cursor/underline b/src/plugins/terminal/tests/cursor/underline new file mode 100755 index 00000000000..7638b5358d0 --- /dev/null +++ b/src/plugins/terminal/tests/cursor/underline @@ -0,0 +1,3 @@ +#!/bin/sh + +echo -e -n "\x1b[\x34 q" # Steady underline diff --git a/src/plugins/terminal/tests/decoration b/src/plugins/terminal/tests/decoration new file mode 100755 index 00000000000..a584d460927 --- /dev/null +++ b/src/plugins/terminal/tests/decoration @@ -0,0 +1,9 @@ +#!/bin/sh + +printf "\e[1mbold\e[0m\n" +printf "\e[3mitalic\e[0m\n" +printf "\e[3m\e[1mbold italic\e[0m\n" +printf "\e[4munderline (abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ)\e[0m\n" +printf "\e[9mstrikethrough\e[0m\n" +printf "\e[31mHello World\e[0m\n" +printf "\x1B[31mHello World\e[0m\n" diff --git a/src/plugins/terminal/tests/filenames b/src/plugins/terminal/tests/filenames new file mode 100755 index 00000000000..6a4e33e3ede --- /dev/null +++ b/src/plugins/terminal/tests/filenames @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +echo "Home:" +echo "~/" + +FULLPATH=$(readlink -f "$0") + +echo "This file:" +echo "$FULLPATH" + +echo "This file, this line:" +echo "$FULLPATH:11" + +echo "This file, this line, this word:" +echo "$FULLPATH:14:34" + +echo "This file, with an error message:" +echo "$FULLPATH:18:23: error: C++ requires a type specifier for all declarations" + +echo "A link: http://google.com" +echo "Another link: https://www.qt.io" +echo "Another one: https://codereview.qt-project.org/c/qt-creator/qt-creator/+/464740" diff --git a/src/plugins/terminal/tests/integration b/src/plugins/terminal/tests/integration new file mode 100755 index 00000000000..ac17432cb66 --- /dev/null +++ b/src/plugins/terminal/tests/integration @@ -0,0 +1,70 @@ +#!/bin/bash + +echo "Testing integration response, best start this from a terminal that has no builtin integration" +echo "e.g. 'sh'" +echo + +echo -e "\033[1m ⎆ Current dir should have changed to '/Some/Dir/Here'\033[0m" +printf "\033]7;file:///Some/Dir/Here\033\\" + +read -p " ⎆ Press enter to continue " -n1 -s +echo +echo + +echo -e "\033[1m ⎆ Current dir should have changed to '/Some/Other/Dir/Here'\033[0m" +printf "\033]1337;CurrentDir=/Some/Other/Dir/Here\033\\" + +read -p " ⎆ Press enter to continue " -n1 -s +echo +echo + + +echo -e "\033[1m ⎆ Current dir should have changed to '/VSCode/dir/with space'\033[0m" +printf "\033]633P;Cwd=/VSCode/dir/with space\033\\" + +read -p " ⎆ Press enter to continue " -n1 -s +echo +echo + +echo -e "\033[1m ⎆ The current process should have changed to 'test'\033[0m" +printf "\033]633E;test with arguments\033\\" + +read -p " ⎆ Press enter to continue " -n1 -s +echo +echo + +echo -e "\033[1m ⎆ The current process should have changed to 'test with space'\033[0m" +printf "\033]633E;'test with space'\033\\" + +read -p " ⎆ Press enter to continue " -n1 -s +echo +echo + +echo -e "\033[1m ⎆ The current process should have changed to 'test with space v2'\033[0m" +printf "\033]633E;\"test with space v2\"\033\\" + +read -p " ⎆ Press enter to continue " -n1 -s +echo +echo + +echo -e "\033[1m ⎆ The current process should have changed to 'test with space v3'\033[0m" +printf "\033]633E;\"./test/test with space v3\" -argument\033\\" + +read -p " ⎆ Press enter to continue " -n1 -s +echo +echo + +echo -e "\033[1m ⎆ The current process should have changed to 'cat'\033[0m" +printf "\033]633E;cat /dev/random | base64 -argument\033\\" + +read -p " ⎆ Press enter to continue " -n1 -s +echo +echo + +echo -e "\033[1m ⎆ The current process should have changed to 'cat me'\033[0m" +printf "\033]633E;cat\\ me args \033\\" + +read -p " ⎆ Press enter to continue " -n1 -s +echo +echo + diff --git a/src/plugins/texteditor/CMakeLists.txt b/src/plugins/texteditor/CMakeLists.txt index 01636fe7544..048cd40b1d5 100644 --- a/src/plugins/texteditor/CMakeLists.txt +++ b/src/plugins/texteditor/CMakeLists.txt @@ -70,6 +70,7 @@ add_qtc_plugin(TextEditor ioutlinewidget.h linenumberfilter.cpp linenumberfilter.h marginsettings.cpp marginsettings.h + markdowneditor.cpp markdowneditor.h outlinefactory.cpp outlinefactory.h plaintexteditorfactory.cpp plaintexteditorfactory.h quickfix.cpp quickfix.h @@ -112,5 +113,7 @@ add_qtc_plugin(TextEditor extend_qtc_plugin(TextEditor CONDITION WITH_TESTS - SOURCES texteditor_test.cpp + SOURCES + codeassist/codeassist_test.cpp codeassist/codeassist_test.h + texteditor_test.cpp ) diff --git a/src/plugins/texteditor/basefilefind.cpp b/src/plugins/texteditor/basefilefind.cpp index 29cccc6c10d..99f37d05eac 100644 --- a/src/plugins/texteditor/basefilefind.cpp +++ b/src/plugins/texteditor/basefilefind.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -38,6 +39,98 @@ using namespace Utils; using namespace Core; namespace TextEditor { + +static std::optional regExpFromParameters(const FileFindParameters ¶meters) +{ + if (!(parameters.flags & FindRegularExpression)) + return {}; + + const QRegularExpression::PatternOptions options = parameters.flags & FindCaseSensitively + ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption; + QRegularExpression regExp; + regExp.setPattern(parameters.text); + regExp.setPatternOptions(options); + return regExp; +} + +void searchInProcessOutput(QPromise &promise, + const FileFindParameters ¶meters, + const ProcessSetupHandler &processSetupHandler, + const ProcessOutputParser &processOutputParser) +{ + if (promise.isCanceled()) + return; + + QEventLoop loop; + + Process process; + processSetupHandler(process); + + const std::optional regExp = regExpFromParameters(parameters); + QStringList outputBuffer; + // The states transition exactly in this order: + enum State { BelowLimit, AboveLimit, Paused, Resumed }; + State state = BelowLimit; + int reportedItemsCount = 0; + QFuture future(promise.future()); + process.setStdOutCallback([&](const QString &output) { + if (promise.isCanceled()) { + process.close(); + loop.quit(); + return; + } + // The SearchResultWidget is going to pause the search anyway, so start buffering + // the output. + if (state == AboveLimit || state == Paused) { + outputBuffer.append(output); + } else { + const SearchResultItems items = processOutputParser(future, output, regExp); + if (!items.isEmpty()) + promise.addResult(items); + reportedItemsCount += items.size(); + if (state == BelowLimit && reportedItemsCount > 200000) + state = AboveLimit; + } + }); + QObject::connect(&process, &Process::done, &loop, [&loop, &promise, &state] { + if (state == BelowLimit || state == Resumed || promise.isCanceled()) + loop.quit(); + }); + + if (promise.isCanceled()) + return; + + process.start(); + if (process.state() == QProcess::NotRunning) + return; + + QFutureWatcher watcher; + QObject::connect(&watcher, &QFutureWatcherBase::canceled, &loop, [&process, &loop] { + process.close(); + loop.quit(); + }); + QObject::connect(&watcher, &QFutureWatcherBase::paused, &loop, [&state] { state = Paused; }); + QObject::connect(&watcher, &QFutureWatcherBase::resumed, &loop, [&] { + state = Resumed; + for (const QString &output : outputBuffer) { + if (promise.isCanceled()) { + process.close(); + loop.quit(); + } + const SearchResultItems items = processOutputParser(future, output, regExp); + if (!items.isEmpty()) + promise.addResult(items); + } + outputBuffer.clear(); + if (process.state() == QProcess::NotRunning) + loop.quit(); + }); + watcher.setFuture(future); + if (promise.isCanceled()) + return; + loop.exec(QEventLoop::ExcludeUserInputEvents); +} + namespace Internal { class InternalEngine : public TextEditor::SearchEngine @@ -51,9 +144,8 @@ public: QVariant parameters() const override { return {}; } void readSettings(QSettings * /*settings*/) override {} void writeSettings(QSettings * /*settings*/) const override {} - QFuture executeSearch( - const TextEditor::FileFindParameters ¶meters, - BaseFileFind *baseFileFind) override + QFuture executeSearch(const TextEditor::FileFindParameters ¶meters, + BaseFileFind *baseFileFind) override { const auto func = parameters.flags & FindRegularExpression ? Utils::findInFilesRegExp : Utils::findInFiles; @@ -65,7 +157,7 @@ public: TextDocument::openedTextDocumentContents()); } - Core::IEditor *openEditor(const Core::SearchResultItem &/*item*/, + Core::IEditor *openEditor(const SearchResultItem &/*item*/, const TextEditor::FileFindParameters &/*parameters*/) override { return nullptr; @@ -91,8 +183,6 @@ public: class BaseFileFindPrivate { public: - BaseFileFindPrivate() { m_futureSynchronizer.setCancelOnWait(true); } - QPointer m_currentFindSupport; Utils::FutureSynchronizer m_futureSynchronizer; @@ -210,34 +300,6 @@ void BaseFileFind::setCurrentSearchEngine(int index) emit currentSearchEngineChanged(); } -static QString displayText(const QString &line) -{ - QString result = line; - auto end = result.end(); - for (auto it = result.begin(); it != end; ++it) { - if (!it->isSpace() && !it->isPrint()) - *it = QChar('?'); - } - return result; -} - -static void displayResult(QFutureWatcher *watcher, - SearchResult *search, int index) -{ - const FileSearchResultList results = watcher->resultAt(index); - QList items; - for (const FileSearchResult &result : results) { - SearchResultItem item; - item.setFilePath(result.fileName); - item.setMainRange(result.lineNumber, result.matchStart, result.matchLength); - item.setLineText(displayText(result.matchingLine)); - item.setUseTextEditorFont(true); - item.setUserData(result.regexpCapturedTexts); - items << item; - } - search->addResults(items, SearchResult::AddOrdered); -} - void BaseFileFind::runNewSearch(const QString &txt, FindFlags findFlags, SearchResultWindow::SearchMode searchMode) { @@ -285,7 +347,7 @@ void BaseFileFind::runSearch(SearchResult *search) { const FileFindParameters parameters = search->userData().value(); SearchResultWindow::instance()->popup(IOutputPane::Flags(IOutputPane::ModeSwitch|IOutputPane::WithFocus)); - auto watcher = new QFutureWatcher(); + auto watcher = new QFutureWatcher; watcher->setPendingResultsLimit(1); // search is deleted if it is removed from search panel connect(search, &QObject::destroyed, watcher, &QFutureWatcherBase::cancel); @@ -295,14 +357,14 @@ void BaseFileFind::runSearch(SearchResult *search) watcher->setPaused(paused); }); connect(watcher, &QFutureWatcherBase::resultReadyAt, search, [watcher, search](int index) { - displayResult(watcher, search, index); + search->addResults(watcher->resultAt(index), SearchResult::AddOrdered); }); // auto-delete: connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater); connect(watcher, &QFutureWatcherBase::finished, search, [watcher, search]() { search->finishSearch(watcher->isCanceled()); }); - QFuture future = executeSearch(parameters); + QFuture future = executeSearch(parameters); watcher->setFuture(future); d->m_futureSynchronizer.addFuture(future); FutureProgress *progress = ProgressManager::addTask(future, @@ -332,8 +394,7 @@ void BaseFileFind::addSearchEngine(SearchEngine *searchEngine) setCurrentSearchEngine(0); } -void BaseFileFind::doReplace(const QString &text, - const QList &items, +void BaseFileFind::doReplace(const QString &text, const SearchResultItems &items, bool preserveCase) { const FilePaths files = replaceAll(text, items, preserveCase); @@ -377,7 +438,7 @@ QList> BaseFileFind::createPatternWidgets() syncComboWithSettings(d->m_filterCombo, d->m_filterSetting); QLabel *exclusionLabel = createLabel(msgExclusionPatternLabel()); d->m_exclusionCombo = createCombo(&d->m_exclusionStrings); - d->m_exclusionCombo->setToolTip(msgFilePatternToolTip()); + d->m_exclusionCombo->setToolTip(msgFilePatternToolTip(Utils::InclusionType::Excluded)); exclusionLabel->setBuddy(d->m_exclusionCombo); syncComboWithSettings(d->m_exclusionCombo, d->m_exclusionSetting); return {{filterLabel, d->m_filterCombo}, {exclusionLabel, d->m_exclusionCombo}}; @@ -474,8 +535,7 @@ void BaseFileFind::recheckEnabled(SearchResult *search) search->setSearchAgainEnabled(isEnabled()); } -FilePaths BaseFileFind::replaceAll(const QString &text, - const QList &items, +FilePaths BaseFileFind::replaceAll(const QString &text, const SearchResultItems &items, bool preserveCase) { if (items.isEmpty()) @@ -483,7 +543,7 @@ FilePaths BaseFileFind::replaceAll(const QString &text, RefactoringChanges refactoring; - QHash > changes; + QHash changes; for (const SearchResultItem &item : items) changes[FilePath::fromUserInput(item.path().constFirst())].append(item); @@ -504,7 +564,7 @@ FilePaths BaseFileFind::replaceAll(const QString &text, for (auto it = changes.cbegin(), end = changes.cend(); it != end; ++it) { const FilePath filePath = it.key(); - const QList changeItems = it.value(); + const SearchResultItems changeItems = it.value(); ChangeSet changeSet; RefactoringFilePtr file = refactoring.file(filePath); @@ -519,9 +579,13 @@ FilePaths BaseFileFind::replaceAll(const QString &text, if (item.userData().canConvert() && !item.userData().toStringList().isEmpty()) { replacement = Utils::expandRegExpReplacement(text, item.userData().toStringList()); } else if (preserveCase) { - const QString originalText = (item.mainRange().length(item.lineText()) == 0) - ? item.lineText() - : item.mainRange().mid(item.lineText()); + Text::Range range = item.mainRange(); + range.end.line -= range.begin.line - 1; + range.begin.line = 1; + QString originalText = item.lineText(); + const int rangeLength = range.length(item.lineText()); + if (rangeLength > 0) + originalText = originalText.mid(range.begin.column, rangeLength); replacement = Utils::matchCaseReplacement(originalText, text); } else { replacement = text; @@ -545,7 +609,7 @@ QVariant BaseFileFind::getAdditionalParameters(SearchResult *search) return search->userData().value().additionalParameters; } -QFuture BaseFileFind::executeSearch(const FileFindParameters ¶meters) +QFuture BaseFileFind::executeSearch(const FileFindParameters ¶meters) { return d->m_searchEngines[parameters.searchEngineIndex]->executeSearch(parameters, this); } diff --git a/src/plugins/texteditor/basefilefind.h b/src/plugins/texteditor/basefilefind.h index 71811978146..c8555f03deb 100644 --- a/src/plugins/texteditor/basefilefind.h +++ b/src/plugins/texteditor/basefilefind.h @@ -4,20 +4,25 @@ #pragma once #include "texteditor_global.h" -#include #include #include +#include +#include + #include -namespace Utils { class FileIterator; } namespace Core { class IEditor; class SearchResult; -class SearchResultItem; } // namespace Core +namespace Utils { +class FileIterator; +class Process; +} + namespace TextEditor { namespace Internal { @@ -37,6 +42,16 @@ public: Core::FindFlags flags; }; +using ProcessSetupHandler = std::function; +using ProcessOutputParser = std::function &, const QString &, const std::optional &)>; + +// Call it from a non-main thread only, it's a blocking invocation. +void TEXTEDITOR_EXPORT searchInProcessOutput(QPromise &promise, + const FileFindParameters ¶meters, + const ProcessSetupHandler &processSetupHandler, + const ProcessOutputParser &processOutputParser); + class BaseFileFind; class TEXTEDITOR_EXPORT SearchEngine : public QObject @@ -52,9 +67,9 @@ public: virtual QVariant parameters() const = 0; virtual void readSettings(QSettings *settings) = 0; virtual void writeSettings(QSettings *settings) const = 0; - virtual QFuture executeSearch( + virtual QFuture executeSearch( const FileFindParameters ¶meters, BaseFileFind *baseFileFind) = 0; - virtual Core::IEditor *openEditor(const Core::SearchResultItem &item, + virtual Core::IEditor *openEditor(const Utils::SearchResultItem &item, const FileFindParameters ¶meters) = 0; bool isEnabled() const; void setEnabled(bool enabled); @@ -81,8 +96,7 @@ public: void addSearchEngine(SearchEngine *searchEngine); /* returns the list of unique files that were passed in items */ - static Utils::FilePaths replaceAll(const QString &txt, - const QList &items, + static Utils::FilePaths replaceAll(const QString &txt, const Utils::SearchResultItems &items, bool preserveCase = false); virtual Utils::FileIterator *files(const QStringList &nameFilters, const QStringList &exclusionFilters, @@ -94,7 +108,7 @@ protected: virtual QString label() const = 0; // see Core::SearchResultWindow::startNewSearch virtual QString toolTip() const = 0; // see Core::SearchResultWindow::startNewSearch, // add %1 placeholder where the find flags should be put - QFuture executeSearch(const FileFindParameters ¶meters); + QFuture executeSearch(const FileFindParameters ¶meters); void writeCommonSettings(QSettings *settings); void readCommonSettings(QSettings *settings, const QString &defaultFilter, const QString &defaultExclusionFilter); @@ -111,10 +125,8 @@ signals: void currentSearchEngineChanged(); private: - void openEditor(Core::SearchResult *result, const Core::SearchResultItem &item); - void doReplace(const QString &txt, - const QList &items, - bool preserveCase); + void openEditor(Core::SearchResult *result, const Utils::SearchResultItem &item); + void doReplace(const QString &txt, const Utils::SearchResultItems &items, bool preserveCase); void hideHighlightAll(bool visible); void searchAgain(Core::SearchResult *search); virtual void recheckEnabled(Core::SearchResult *search); diff --git a/src/plugins/texteditor/basehoverhandler.h b/src/plugins/texteditor/basehoverhandler.h index 9b6d90fd89a..c24ae4a1df5 100644 --- a/src/plugins/texteditor/basehoverhandler.h +++ b/src/plugins/texteditor/basehoverhandler.h @@ -39,7 +39,8 @@ protected: Priority_None = 0, Priority_Tooltip = 5, Priority_Help = 10, - Priority_Diagnostic = 20 + Priority_Diagnostic = 20, + Priority_Suggestion = 40 }; void setPriority(int priority); int priority() const; diff --git a/src/plugins/texteditor/behaviorsettingspage.cpp b/src/plugins/texteditor/behaviorsettingspage.cpp index 5d397458071..73675e2cdb9 100644 --- a/src/plugins/texteditor/behaviorsettingspage.cpp +++ b/src/plugins/texteditor/behaviorsettingspage.cpp @@ -10,6 +10,7 @@ #include "simplecodestylepreferences.h" #include "storagesettings.h" #include "tabsettings.h" +#include "tabsettingswidget.h" #include "texteditorconstants.h" #include "texteditorsettings.h" #include "texteditortr.h" @@ -35,12 +36,12 @@ namespace TextEditor { -struct BehaviorSettingsPage::BehaviorSettingsPagePrivate : public QObject +class BehaviorSettingsPagePrivate : public QObject { +public: BehaviorSettingsPagePrivate(); const QString m_settingsPrefix{"text"}; - QPointer m_widget; TextEditor::BehaviorSettingsWidget *m_behaviorWidget = nullptr; CodeStylePool *m_defaultCodeStylePool = nullptr; @@ -52,7 +53,7 @@ struct BehaviorSettingsPage::BehaviorSettingsPagePrivate : public QObject ExtraEncodingSettings m_extraEncodingSettings; }; -BehaviorSettingsPage::BehaviorSettingsPagePrivate::BehaviorSettingsPagePrivate() +BehaviorSettingsPagePrivate::BehaviorSettingsPagePrivate() { // global tab preferences for all other languages m_codeStyle = new SimpleCodeStylePreferences(this); @@ -71,6 +72,54 @@ BehaviorSettingsPage::BehaviorSettingsPagePrivate::BehaviorSettingsPagePrivate() m_extraEncodingSettings.fromSettings(m_settingsPrefix, s); } +class BehaviorSettingsWidgetImpl : public Core::IOptionsPageWidget +{ +public: + BehaviorSettingsWidgetImpl(BehaviorSettingsPagePrivate *d) : d(d) + { + d->m_behaviorWidget = new BehaviorSettingsWidget(this); + + auto verticalSpacer = new QSpacerItem(20, 13, QSizePolicy::Minimum, QSizePolicy::Expanding); + + auto gridLayout = new QGridLayout(this); + if (Utils::HostOsInfo::isMacHost()) + gridLayout->setContentsMargins(-1, 0, -1, 0); // don't ask. + gridLayout->addWidget(d->m_behaviorWidget, 0, 0, 1, 1); + gridLayout->addItem(verticalSpacer, 1, 0, 1, 1); + + d->m_pageCodeStyle = new SimpleCodeStylePreferences(this); + d->m_pageCodeStyle->setDelegatingPool(d->m_codeStyle->delegatingPool()); + d->m_pageCodeStyle->setTabSettings(d->m_codeStyle->tabSettings()); + d->m_pageCodeStyle->setCurrentDelegate(d->m_codeStyle->currentDelegate()); + d->m_behaviorWidget->setCodeStyle(d->m_pageCodeStyle); + + TabSettingsWidget *tabSettingsWidget = d->m_behaviorWidget->tabSettingsWidget(); + tabSettingsWidget->setCodingStyleWarningVisible(true); + connect(tabSettingsWidget, &TabSettingsWidget::codingStyleLinkClicked, + this, [] (TabSettingsWidget::CodingStyleLink link) { + switch (link) { + case TabSettingsWidget::CppLink: + Core::ICore::showOptionsDialog(CppEditor::Constants::CPP_CODE_STYLE_SETTINGS_ID); + break; + case TabSettingsWidget::QtQuickLink: + Core::ICore::showOptionsDialog(QmlJSTools::Constants::QML_JS_CODE_STYLE_SETTINGS_ID); + break; + } + }); + + d->m_behaviorWidget->setAssignedTypingSettings(d->m_typingSettings); + d->m_behaviorWidget->setAssignedStorageSettings(d->m_storageSettings); + d->m_behaviorWidget->setAssignedBehaviorSettings(d->m_behaviorSettings); + d->m_behaviorWidget->setAssignedExtraEncodingSettings(d->m_extraEncodingSettings); + d->m_behaviorWidget->setAssignedCodec(Core::EditorManager::defaultTextCodec()); + d->m_behaviorWidget->setAssignedLineEnding(Core::EditorManager::defaultLineEnding()); + } + + void apply() final; + + BehaviorSettingsPagePrivate *d; +}; + BehaviorSettingsPage::BehaviorSettingsPage() : d(new BehaviorSettingsPagePrivate) { @@ -81,6 +130,7 @@ BehaviorSettingsPage::BehaviorSettingsPage() setCategory(TextEditor::Constants::TEXT_EDITOR_SETTINGS_CATEGORY); setDisplayCategory(Tr::tr("Text Editor")); setCategoryIconPath(TextEditor::Constants::TEXT_EDITOR_SETTINGS_CATEGORY_ICON_PATH); + setWidgetCreator([this] { return new BehaviorSettingsWidgetImpl(d); }); } BehaviorSettingsPage::~BehaviorSettingsPage() @@ -88,37 +138,7 @@ BehaviorSettingsPage::~BehaviorSettingsPage() delete d; } -QWidget *BehaviorSettingsPage::widget() -{ - if (!d->m_widget) { - d->m_widget = new QWidget; - d->m_behaviorWidget = new BehaviorSettingsWidget(d->m_widget); - - auto verticalSpacer = new QSpacerItem(20, 13, QSizePolicy::Minimum, QSizePolicy::Expanding); - - auto gridLayout = new QGridLayout(d->m_widget); - if (Utils::HostOsInfo::isMacHost()) - gridLayout->setContentsMargins(-1, 0, -1, 0); // don't ask. - gridLayout->addWidget(d->m_behaviorWidget, 0, 0, 1, 1); - gridLayout->addItem(verticalSpacer, 1, 0, 1, 1); - - d->m_pageCodeStyle = new SimpleCodeStylePreferences(d->m_widget); - d->m_pageCodeStyle->setDelegatingPool(d->m_codeStyle->delegatingPool()); - d->m_pageCodeStyle->setTabSettings(d->m_codeStyle->tabSettings()); - d->m_pageCodeStyle->setCurrentDelegate(d->m_codeStyle->currentDelegate()); - d->m_behaviorWidget->setCodeStyle(d->m_pageCodeStyle); - - TabSettingsWidget *tabSettingsWidget = d->m_behaviorWidget->tabSettingsWidget(); - tabSettingsWidget->setCodingStyleWarningVisible(true); - connect(tabSettingsWidget, &TabSettingsWidget::codingStyleLinkClicked, - this, &BehaviorSettingsPage::openCodingStylePreferences); - - settingsToUI(); - } - return d->m_widget; -} - -void BehaviorSettingsPage::apply() +void BehaviorSettingsWidgetImpl::apply() { if (!d->m_behaviorWidget) // page was never shown return; @@ -128,8 +148,10 @@ void BehaviorSettingsPage::apply() BehaviorSettings newBehaviorSettings; ExtraEncodingSettings newExtraEncodingSettings; - settingsFromUI(&newTypingSettings, &newStorageSettings, - &newBehaviorSettings, &newExtraEncodingSettings); + d->m_behaviorWidget->assignedTypingSettings(&newTypingSettings); + d->m_behaviorWidget->assignedStorageSettings(&newStorageSettings); + d->m_behaviorWidget->assignedBehaviorSettings(&newBehaviorSettings); + d->m_behaviorWidget->assignedExtraEncodingSettings(&newExtraEncodingSettings); QSettings *s = Core::ICore::settings(); QTC_ASSERT(s, return); @@ -178,32 +200,6 @@ void BehaviorSettingsPage::apply() d->m_behaviorWidget->assignedLineEnding()); } -void BehaviorSettingsPage::settingsFromUI(TypingSettings *typingSettings, - StorageSettings *storageSettings, - BehaviorSettings *behaviorSettings, - ExtraEncodingSettings *extraEncodingSettings) const -{ - d->m_behaviorWidget->assignedTypingSettings(typingSettings); - d->m_behaviorWidget->assignedStorageSettings(storageSettings); - d->m_behaviorWidget->assignedBehaviorSettings(behaviorSettings); - d->m_behaviorWidget->assignedExtraEncodingSettings(extraEncodingSettings); -} - -void BehaviorSettingsPage::settingsToUI() -{ - d->m_behaviorWidget->setAssignedTypingSettings(d->m_typingSettings); - d->m_behaviorWidget->setAssignedStorageSettings(d->m_storageSettings); - d->m_behaviorWidget->setAssignedBehaviorSettings(d->m_behaviorSettings); - d->m_behaviorWidget->setAssignedExtraEncodingSettings(d->m_extraEncodingSettings); - d->m_behaviorWidget->setAssignedCodec(Core::EditorManager::defaultTextCodec()); - d->m_behaviorWidget->setAssignedLineEnding(Core::EditorManager::defaultLineEnding()); -} - -void BehaviorSettingsPage::finish() -{ - delete d->m_widget; -} - ICodeStylePreferences *BehaviorSettingsPage::codeStyle() const { return d->m_codeStyle; @@ -235,16 +231,4 @@ const ExtraEncodingSettings &BehaviorSettingsPage::extraEncodingSettings() const } -void BehaviorSettingsPage::openCodingStylePreferences(TabSettingsWidget::CodingStyleLink link) -{ - switch (link) { - case TabSettingsWidget::CppLink: - Core::ICore::showOptionsDialog(CppEditor::Constants::CPP_CODE_STYLE_SETTINGS_ID); - break; - case TabSettingsWidget::QtQuickLink: - Core::ICore::showOptionsDialog(QmlJSTools::Constants::QML_JS_CODE_STYLE_SETTINGS_ID); - break; - } -} - } // namespace TextEditor diff --git a/src/plugins/texteditor/behaviorsettingspage.h b/src/plugins/texteditor/behaviorsettingspage.h index 495a800d86f..2ecb48ea29c 100644 --- a/src/plugins/texteditor/behaviorsettingspage.h +++ b/src/plugins/texteditor/behaviorsettingspage.h @@ -3,10 +3,6 @@ #pragma once -#include "texteditor_global.h" - -#include "tabsettingswidget.h" - #include QT_BEGIN_NAMESPACE @@ -25,17 +21,10 @@ class CodeStylePool; class BehaviorSettingsPage : public Core::IOptionsPage { - Q_OBJECT - public: BehaviorSettingsPage(); ~BehaviorSettingsPage() override; - // IOptionsPage - QWidget *widget() override; - void apply() override; - void finish() override; - ICodeStylePreferences *codeStyle() const; CodeStylePool *codeStylePool() const; const TypingSettings &typingSettings() const; @@ -44,17 +33,8 @@ public: const ExtraEncodingSettings &extraEncodingSettings() const; private: - void openCodingStylePreferences(TextEditor::TabSettingsWidget::CodingStyleLink link); - - void settingsFromUI(TypingSettings *typingSettings, - StorageSettings *storageSettings, - BehaviorSettings *behaviorSettings, - ExtraEncodingSettings *extraEncodingSettings) const; - void settingsToUI(); - QList m_codecs; - struct BehaviorSettingsPagePrivate; - BehaviorSettingsPagePrivate *d; + class BehaviorSettingsPagePrivate *d; }; } // namespace TextEditor diff --git a/src/plugins/texteditor/behaviorsettingswidget.cpp b/src/plugins/texteditor/behaviorsettingswidget.cpp index 518f09c8a57..144afa6eb23 100644 --- a/src/plugins/texteditor/behaviorsettingswidget.cpp +++ b/src/plugins/texteditor/behaviorsettingswidget.cpp @@ -65,8 +65,6 @@ BehaviorSettingsWidget::BehaviorSettingsWidget(QWidget *parent) : QWidget(parent) , d(new BehaviorSettingsWidgetPrivate) { - resize(801, 693); - d->tabPreferencesWidget = new SimpleCodeStylePreferencesWidget(this); d->tabPreferencesWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); // FIXME: Desirable? @@ -165,7 +163,7 @@ BehaviorSettingsWidget::BehaviorSettingsWidget(QWidget *parent) d->groupBoxMouse = new QGroupBox(Tr::tr("Mouse and Keyboard")); - using namespace Utils::Layouting; + using namespace Layouting; const auto indent = [](QWidget *inner) { return Row { Space(30), inner }; }; @@ -207,8 +205,9 @@ BehaviorSettingsWidget::BehaviorSettingsWidget(QWidget *parent) Row { Column { d->tabPreferencesWidget, d->groupBoxTyping, st }, - Column { d->groupBoxStorageSettings, d->groupBoxEncodings, d->groupBoxMouse, st } - }.attachTo(this, WithoutMargins); + Column { d->groupBoxStorageSettings, d->groupBoxEncodings, d->groupBoxMouse, st }, + noMargin, + }.attachTo(this); connect(d->cleanWhitespace, &QCheckBox::toggled, d->inEntireDocument, &QCheckBox::setEnabled); diff --git a/src/plugins/texteditor/codeassist/asyncprocessor.cpp b/src/plugins/texteditor/codeassist/asyncprocessor.cpp index 90f993a39c7..3f440bfa709 100644 --- a/src/plugins/texteditor/codeassist/asyncprocessor.cpp +++ b/src/plugins/texteditor/codeassist/asyncprocessor.cpp @@ -6,7 +6,7 @@ #include "assistinterface.h" #include "iassistproposal.h" -#include +#include namespace TextEditor { @@ -21,7 +21,7 @@ IAssistProposal *AsyncProcessor::perform() { IAssistProposal *result = immediateProposal(); interface()->prepareForAsyncUse(); - m_watcher.setFuture(Utils::runAsync([this] { + m_watcher.setFuture(Utils::asyncRun([this] { interface()->recreateTextDocument(); return performAsync(); })); diff --git a/src/plugins/texteditor/codeassist/codeassist_test.cpp b/src/plugins/texteditor/codeassist/codeassist_test.cpp new file mode 100644 index 00000000000..8b724dbe166 --- /dev/null +++ b/src/plugins/texteditor/codeassist/codeassist_test.cpp @@ -0,0 +1,152 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifdef WITH_TESTS + +#include "codeassist_test.h" + +#include "../texteditor.h" + +#include "assistinterface.h" +#include "assistproposaliteminterface.h" +#include "asyncprocessor.h" +#include "completionassistprovider.h" +#include "genericproposal.h" +#include "genericproposalwidget.h" + +#include +#include +#include + +#include + +namespace TextEditor::Internal { + +class TestProposalItem : public AssistProposalItemInterface +{ +public: + QString m_text = "test"; + bool m_implicitlyApplies = false; + bool m_prematurelyApplies = false; + QIcon m_icon; + QString m_detail = "detail"; + bool m_isSnippet = false; + bool m_isValid = true; + + QString text() const override { return m_text; } + bool implicitlyApplies() const override { return m_implicitlyApplies; } + bool prematurelyApplies(const QChar &) const override { return m_prematurelyApplies; } + QIcon icon() const override { return m_icon; } + QString detail() const override { return m_detail; } + bool isSnippet() const override { return m_isSnippet; } + bool isValid() const override { return m_isValid; } + quint64 hash() const override { return 0; } // used to remove duplicates +}; + +class OpenEditorItem : public TestProposalItem +{ +public: + void apply(TextDocumentManipulatorInterface &, int) const override + { + m_openedEditor = Core::EditorManager::openEditor(m_filePath, + Core::Constants::K_DEFAULT_TEXT_EDITOR_ID); + } + + mutable Core::IEditor *m_openedEditor = nullptr; + Utils::FilePath m_filePath; +}; + +class TestProposalWidget : public GenericProposalWidget +{ +public: + void showProposal(const QString &prefix) override + { + GenericProposalModelPtr proposalModel = model(); + if (proposalModel && proposalModel->size() == 1) { + emit proposalItemActivated(proposalModel->proposalItem(0)); + deleteLater(); + return; + } + GenericProposalWidget::showProposal(prefix); + } +}; + +class TestProposal : public GenericProposal +{ +public: + TestProposal(int pos, const QList &items) + : GenericProposal(pos, items) + {} + IAssistProposalWidget *createWidget() const override { return new TestProposalWidget; } +}; + +class TestProcessor : public AsyncProcessor +{ +public: + TestProcessor(const QList &items) + : m_items(items) + {} + IAssistProposal *performAsync() override + { return new TestProposal(interface()->position(), m_items); } + QList m_items; +}; + +class TestProvider : public CompletionAssistProvider +{ +public: + IAssistProcessor *createProcessor(const AssistInterface *assistInterface) const override + { + Q_UNUSED(assistInterface); + return new TestProcessor(m_items); + } + QList m_items; +}; + +void CodeAssistTests::initTestCase() +{ + Core::IEditor *editor = Core::EditorManager::openEditorWithContents( + Core::Constants::K_DEFAULT_TEXT_EDITOR_ID); + QVERIFY(editor); + m_editor = qobject_cast(editor); + QVERIFY(m_editor); + m_editorsToClose << m_editor; + m_testProvider = new TestProvider(); +} + +static Utils::FilePath createBigFile() +{ + constexpr int textChunkSize = 65536; // from utils/textfileformat.cpp + + const Utils::FilePath result = Utils::TemporaryDirectory::masterDirectoryFilePath() / "BigFile"; + QByteArray data; + data.reserve(textChunkSize); + while (data.size() < textChunkSize) + data.append("bigfile line\n"); + result.writeFileContents(data); + return result; +} + +void CodeAssistTests::testFollowSymbolBigFile() +{ + auto item = new OpenEditorItem; + item->m_filePath = createBigFile(); + m_testProvider->m_items = {item}; + auto editorWidget = m_editor->editorWidget(); + + editorWidget->invokeAssist(FollowSymbol, m_testProvider); + QSignalSpy spy(editorWidget, &TextEditorWidget::assistFinished); + QVERIFY(spy.wait(1000)); + QVERIFY(item->m_openedEditor); + m_editorsToClose << item->m_openedEditor; +} + +void CodeAssistTests::cleanupTestCase() +{ + m_testProvider->m_items.clear(); + Core::EditorManager::closeEditors(m_editorsToClose); + QVERIFY(Core::EditorManager::currentEditor() == nullptr); +} + +} // namespace TextEditor::Internal + +#endif // ifdef WITH_TESTS diff --git a/src/plugins/texteditor/codeassist/codeassist_test.h b/src/plugins/texteditor/codeassist/codeassist_test.h new file mode 100644 index 00000000000..3bf734aadfb --- /dev/null +++ b/src/plugins/texteditor/codeassist/codeassist_test.h @@ -0,0 +1,37 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#ifdef WITH_TESTS + +#include + +namespace Core { class IEditor; } +namespace TextEditor { class BaseTextEditor; } + +namespace TextEditor::Internal { + +class TestProvider; + +class CodeAssistTests : public QObject +{ + Q_OBJECT +public: + +private slots: + void initTestCase(); + + void testFollowSymbolBigFile(); + + void cleanupTestCase(); + +private: + TextEditor::BaseTextEditor *m_editor = nullptr; + QList m_editorsToClose; + TestProvider *m_testProvider = nullptr; +}; + +} // namespace TextEditor::Internal + +#endif diff --git a/src/plugins/texteditor/codeassist/codeassistant.cpp b/src/plugins/texteditor/codeassist/codeassistant.cpp index b3d9d477e24..430b7252cdf 100644 --- a/src/plugins/texteditor/codeassist/codeassistant.cpp +++ b/src/plugins/texteditor/codeassist/codeassistant.cpp @@ -81,6 +81,7 @@ private: IAssistProcessor *m_processor = nullptr; AssistKind m_assistKind = TextEditor::Completion; IAssistProposalWidget *m_proposalWidget = nullptr; + TextEditorWidget::SuggestionBlocker m_suggestionBlocker; bool m_receivedContentWhileWaiting = false; QTimer m_automaticProposalTimer; CompletionSettings m_settings; @@ -185,26 +186,26 @@ void CodeAssistantPrivate::requestProposal(AssistReason reason, IAssistProcessor *processor = provider->createProcessor(assistInterface.get()); processor->setAsyncCompletionAvailableHandler([this, reason, processor]( IAssistProposal *newProposal) { + if (processor == m_processor) { + invalidateCurrentRequestData(); + if (processor->needsRestart() && m_receivedContentWhileWaiting) { + delete newProposal; + m_receivedContentWhileWaiting = false; + requestProposal(reason, m_assistKind, m_requestProvider); + } else { + displayProposal(newProposal, reason); + if (processor->running()) + m_processor = processor; + else + emit q->finished(); + } + } if (!processor->running()) { // do not delete this processor directly since this function is called from within the processor QMetaObject::invokeMethod(QCoreApplication::instance(), [processor] { delete processor; }, Qt::QueuedConnection); } - if (processor != m_processor) - return; - invalidateCurrentRequestData(); - if (processor->needsRestart() && m_receivedContentWhileWaiting) { - delete newProposal; - m_receivedContentWhileWaiting = false; - requestProposal(reason, m_assistKind, m_requestProvider); - } else { - displayProposal(newProposal, reason); - if (processor->running()) - m_processor = processor; - else - emit q->finished(); - } }); if (IAssistProposal *newProposal = processor->start(std::move(assistInterface))) @@ -251,6 +252,14 @@ void CodeAssistantPrivate::displayProposal(IAssistProposal *newProposal, AssistR return; } + if (m_editorWidget->suggestionVisible()) { + if (reason != ExplicitlyInvoked) { + destroyContext(); + return; + } + m_editorWidget->clearSuggestion(); + } + const QString prefix = m_editorWidget->textAt(basePosition, m_editorWidget->position() - basePosition); if (!newProposal->hasItemsToPropose(prefix, reason)) { @@ -287,6 +296,7 @@ void CodeAssistantPrivate::displayProposal(IAssistProposal *newProposal, AssistR m_proposalWidget->setDisplayRect(m_editorWidget->cursorRect(basePosition)); m_proposalWidget->setIsSynchronized(!m_receivedContentWhileWaiting); m_proposalWidget->showProposal(prefix); + m_suggestionBlocker = m_editorWidget->blockSuggestions(); } void CodeAssistantPrivate::processProposalItem(AssistProposalItemInterface *proposalItem) @@ -329,6 +339,7 @@ void CodeAssistantPrivate::handlePrefixExpansion(const QString &newPrefix) void CodeAssistantPrivate::finalizeProposal() { stopAutomaticProposalTimer(); + m_suggestionBlocker.reset(); m_proposalWidget = nullptr; if (m_receivedContentWhileWaiting) m_receivedContentWhileWaiting = false; @@ -445,6 +456,7 @@ void CodeAssistantPrivate::automaticProposalTimeout() { if (isWaitingForProposal() || m_editorWidget->multiTextCursor().hasMultipleCursors() + || m_editorWidget->suggestionVisible() || (isDisplayingProposal() && !m_proposalWidget->isFragile())) { return; } diff --git a/src/plugins/texteditor/codestyleeditor.cpp b/src/plugins/texteditor/codestyleeditor.cpp index ac75f1ad6d3..070a230e384 100644 --- a/src/plugins/texteditor/codestyleeditor.cpp +++ b/src/plugins/texteditor/codestyleeditor.cpp @@ -29,6 +29,7 @@ CodeStyleEditor::CodeStyleEditor(ICodeStylePreferencesFactory *factory, , m_codeStyle(codeStyle) { m_layout = new QVBoxLayout(this); + m_layout->setContentsMargins(0, 0, 0, 0); auto selector = new CodeStyleSelectorWidget(factory, project, this); selector->setCodeStyle(codeStyle); m_additionalGlobalSettingsWidget = factory->createAdditionalGlobalSettings(codeStyle, diff --git a/src/plugins/texteditor/codestyleselectorwidget.cpp b/src/plugins/texteditor/codestyleselectorwidget.cpp index 56b8ca4c055..2b21fd12c31 100644 --- a/src/plugins/texteditor/codestyleselectorwidget.cpp +++ b/src/plugins/texteditor/codestyleselectorwidget.cpp @@ -33,8 +33,6 @@ CodeStyleSelectorWidget::CodeStyleSelectorWidget(ICodeStylePreferencesFactory *f , m_factory(factory) , m_project(project) { - resize(536, 59); - m_delegateComboBox = new QComboBox(this); m_delegateComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); @@ -48,7 +46,7 @@ CodeStyleSelectorWidget::CodeStyleSelectorWidget(ICodeStylePreferencesFactory *f m_importButton = new QPushButton(Tr::tr("Import...")); m_importButton->setEnabled(false); - using namespace Utils::Layouting; + using namespace Layouting; Column { Grid { @@ -59,8 +57,8 @@ CodeStyleSelectorWidget::CodeStyleSelectorWidget(ICodeStylePreferencesFactory *f m_exportButton, m_importButton }, - - }.attachTo(this, WithoutMargins); + noMargin, + }.attachTo(this); connect(m_delegateComboBox, &QComboBox::activated, this, &CodeStyleSelectorWidget::slotComboBoxActivated); diff --git a/src/plugins/texteditor/colorschemeedit.cpp b/src/plugins/texteditor/colorschemeedit.cpp index 0bd0c54015f..c6f661df429 100644 --- a/src/plugins/texteditor/colorschemeedit.cpp +++ b/src/plugins/texteditor/colorschemeedit.cpp @@ -6,13 +6,13 @@ #include "texteditortr.h" #include +#include #include #include #include #include #include -#include #include #include #include @@ -25,14 +25,6 @@ namespace TextEditor::Internal { const int layoutSpacing = 6; -static QString colorButtonStyleSheet(const QColor &bgColor) -{ - QString rc("border-width: 2px; border-radius: 2px; border-color: black; "); - rc += bgColor.isValid() ? "border-style: solid; background:" + bgColor.name() + ";" - : QString("border-style: dotted;"); - return rc; -} - class FormatsModel : public QAbstractListModel { public: @@ -129,10 +121,9 @@ ColorSchemeEdit::ColorSchemeEdit(QWidget *parent) : m_formatsModel(new FormatsModel(this)) { setContentsMargins(0, layoutSpacing, 0, 0); - resize(513, 416); auto colorButton = [] () { - auto tb = new QToolButton; + auto tb = new Utils::QtColorButton; tb->setMinimumWidth(56); return tb; }; @@ -210,13 +201,14 @@ ColorSchemeEdit::ColorSchemeEdit(QWidget *parent) : auto bottomSpacer = new QWidget; bottomSpacer->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Expanding); - using namespace Utils::Layouting; + using namespace Layouting; Row { m_itemList, m_builtinSchemeLabel, m_fontProperties, - }.attachTo(this, WithoutMargins); + noMargin + }.attachTo(this); Grid { m_foregroundLabel, m_foregroundToolButton, m_eraseForegroundToolButton, br, @@ -245,9 +237,9 @@ ColorSchemeEdit::ColorSchemeEdit(QWidget *parent) : connect(m_itemList->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &ColorSchemeEdit::currentItemChanged); - connect(m_foregroundToolButton, &QAbstractButton::clicked, + connect(m_foregroundToolButton, &Utils::QtColorButton::colorChanged, this, &ColorSchemeEdit::changeForeColor); - connect(m_backgroundToolButton, &QAbstractButton::clicked, + connect(m_backgroundToolButton, &Utils::QtColorButton::colorChanged, this, &ColorSchemeEdit::changeBackColor); connect(m_eraseBackgroundToolButton, &QAbstractButton::clicked, this, &ColorSchemeEdit::eraseBackColor); @@ -265,7 +257,7 @@ ColorSchemeEdit::ColorSchemeEdit(QWidget *parent) : this, &ColorSchemeEdit::checkCheckBoxes); connect(m_italicCheckBox, &QAbstractButton::toggled, this, &ColorSchemeEdit::checkCheckBoxes); - connect(m_underlineColorToolButton, &QToolButton::clicked, + connect(m_underlineColorToolButton, &Utils::QtColorButton::colorChanged, this, &ColorSchemeEdit::changeUnderlineColor); connect(m_eraseUnderlineColorToolButton, &QToolButton::clicked, this, &ColorSchemeEdit::eraseUnderlineColor); @@ -347,7 +339,7 @@ void ColorSchemeEdit::updateForegroundControls() m_foregroundToolButton->setVisible(isVisible); m_eraseForegroundToolButton->setVisible(isVisible); - m_foregroundToolButton->setStyleSheet(colorButtonStyleSheet(format.foreground())); + m_foregroundToolButton->setColor(format.foreground()); m_eraseForegroundToolButton->setEnabled(!m_readOnly && m_curItem > 0 && format.foreground().isValid()); @@ -366,7 +358,7 @@ void ColorSchemeEdit::updateBackgroundControls() m_backgroundToolButton->setVisible(isVisible); m_eraseBackgroundToolButton->setVisible(isVisible); - m_backgroundToolButton->setStyleSheet(colorButtonStyleSheet(format.background())); + m_backgroundToolButton->setColor(format.background()); m_eraseBackgroundToolButton->setEnabled(!m_readOnly && m_curItem > 0 && format.background().isValid()); @@ -466,7 +458,7 @@ void ColorSchemeEdit::updateUnderlineControls() m_eraseUnderlineColorToolButton->setVisible(isVisible); m_underlineComboBox->setVisible(isVisible); - m_underlineColorToolButton->setStyleSheet(colorButtonStyleSheet(format.underlineColor())); + m_underlineColorToolButton->setColor(format.underlineColor()); m_eraseUnderlineColorToolButton->setEnabled(!m_readOnly && m_curItem > 0 && format.underlineColor().isValid()); @@ -478,11 +470,8 @@ void ColorSchemeEdit::changeForeColor() { if (m_curItem == -1) return; - QColor color = m_scheme.formatFor(m_descriptions[m_curItem].id()).foreground(); - const QColor newColor = QColorDialog::getColor(color, m_boldCheckBox->window()); - if (!newColor.isValid()) - return; - m_foregroundToolButton->setStyleSheet(colorButtonStyleSheet(newColor)); + + const QColor newColor = m_foregroundToolButton->color(); m_eraseForegroundToolButton->setEnabled(true); for (const QModelIndex &index : m_itemList->selectionModel()->selectedRows()) { @@ -498,11 +487,8 @@ void ColorSchemeEdit::changeBackColor() { if (m_curItem == -1) return; - QColor color = m_scheme.formatFor(m_descriptions[m_curItem].id()).background(); - const QColor newColor = QColorDialog::getColor(color, m_boldCheckBox->window()); - if (!newColor.isValid()) - return; - m_backgroundToolButton->setStyleSheet(colorButtonStyleSheet(newColor)); + + const QColor newColor = m_backgroundToolButton->color(); m_eraseBackgroundToolButton->setEnabled(true); for (const QModelIndex &index : m_itemList->selectionModel()->selectedRows()) { @@ -521,14 +507,13 @@ void ColorSchemeEdit::eraseBackColor() { if (m_curItem == -1) return; - QColor newColor; - m_backgroundToolButton->setStyleSheet(colorButtonStyleSheet(newColor)); + m_backgroundToolButton->setColor({}); m_eraseBackgroundToolButton->setEnabled(false); const QList indexes = m_itemList->selectionModel()->selectedRows(); for (const QModelIndex &index : indexes) { const TextStyle category = m_descriptions[index.row()].id(); - m_scheme.formatFor(category).setBackground(newColor); + m_scheme.formatFor(category).setBackground({}); m_formatsModel->emitDataChanged(index); } @@ -539,14 +524,13 @@ void ColorSchemeEdit::eraseForeColor() { if (m_curItem == -1) return; - QColor newColor; - m_foregroundToolButton->setStyleSheet(colorButtonStyleSheet(newColor)); + m_foregroundToolButton->setColor({}); m_eraseForegroundToolButton->setEnabled(false); const QList indexes = m_itemList->selectionModel()->selectedRows(); for (const QModelIndex &index : indexes) { const TextStyle category = m_descriptions[index.row()].id(); - m_scheme.formatFor(category).setForeground(newColor); + m_scheme.formatFor(category).setForeground({}); m_formatsModel->emitDataChanged(index); } @@ -635,11 +619,8 @@ void ColorSchemeEdit::changeUnderlineColor() { if (m_curItem == -1) return; - QColor color = m_scheme.formatFor(m_descriptions[m_curItem].id()).underlineColor(); - const QColor newColor = QColorDialog::getColor(color, m_boldCheckBox->window()); - if (!newColor.isValid()) - return; - m_underlineColorToolButton->setStyleSheet(colorButtonStyleSheet(newColor)); + + const QColor newColor = m_underlineColorToolButton->color(); m_eraseUnderlineColorToolButton->setEnabled(true); for (const QModelIndex &index : m_itemList->selectionModel()->selectedRows()) { @@ -653,13 +634,12 @@ void ColorSchemeEdit::eraseUnderlineColor() { if (m_curItem == -1) return; - QColor newColor; - m_underlineColorToolButton->setStyleSheet(colorButtonStyleSheet(newColor)); + m_underlineColorToolButton->setColor({}); m_eraseUnderlineColorToolButton->setEnabled(false); for (const QModelIndex &index : m_itemList->selectionModel()->selectedRows()) { const TextStyle category = m_descriptions[index.row()].id(); - m_scheme.formatFor(category).setUnderlineColor(newColor); + m_scheme.formatFor(category).setUnderlineColor({}); m_formatsModel->emitDataChanged(index); } } diff --git a/src/plugins/texteditor/colorschemeedit.h b/src/plugins/texteditor/colorschemeedit.h index 645ece9efe7..a2d57970795 100644 --- a/src/plugins/texteditor/colorschemeedit.h +++ b/src/plugins/texteditor/colorschemeedit.h @@ -20,6 +20,8 @@ class QScrollArea; class QToolButton; QT_END_NAMESPACE +namespace Utils { class QtColorButton; } + namespace TextEditor::Internal { class FormatsModel; @@ -80,10 +82,10 @@ private: QLabel *m_builtinSchemeLabel; QWidget *m_fontProperties; QLabel *m_foregroundLabel; - QToolButton *m_foregroundToolButton; + Utils::QtColorButton *m_foregroundToolButton; QAbstractButton *m_eraseForegroundToolButton; QLabel *m_backgroundLabel; - QToolButton *m_backgroundToolButton; + Utils::QtColorButton *m_backgroundToolButton; QAbstractButton *m_eraseBackgroundToolButton; QLabel *m_relativeForegroundHeadline; QLabel *m_foregroundLightnessLabel; @@ -100,7 +102,7 @@ private: QCheckBox *m_italicCheckBox; QLabel *m_underlineHeadline; QLabel *m_underlineLabel; - QToolButton *m_underlineColorToolButton; + Utils::QtColorButton *m_underlineColorToolButton; QAbstractButton *m_eraseUnderlineColorToolButton; QComboBox *m_underlineComboBox; diff --git a/src/plugins/texteditor/completionsettingspage.cpp b/src/plugins/texteditor/completionsettingspage.cpp index 6187688e4e9..167cde25a75 100644 --- a/src/plugins/texteditor/completionsettingspage.cpp +++ b/src/plugins/texteditor/completionsettingspage.cpp @@ -66,8 +66,6 @@ private: CompletionSettingsPageWidget::CompletionSettingsPageWidget(CompletionSettingsPage *owner) : m_owner(owner) { - resize(823, 756); - m_caseSensitivity = new QComboBox; m_caseSensitivity->addItem(Tr::tr("Full")); m_caseSensitivity->addItem(Tr::tr("None")); diff --git a/src/plugins/texteditor/displaysettingspage.cpp b/src/plugins/texteditor/displaysettingspage.cpp index 58f9e04068d..2449618bf05 100644 --- a/src/plugins/texteditor/displaysettingspage.cpp +++ b/src/plugins/texteditor/displaysettingspage.cpp @@ -4,6 +4,7 @@ #include "displaysettingspage.h" #include "displaysettings.h" +#include "fontsettings.h" #include "marginsettings.h" #include "texteditorconstants.h" #include "texteditorsettings.h" @@ -43,30 +44,23 @@ public: DisplaySettingsWidget(DisplaySettingsPagePrivate *data) : m_data(data) { - resize(452, 458); - enableTextWrapping = new QCheckBox(Tr::tr("Enable text &wrapping")); enableTextWrappingHintLabel = new QLabel(Tr::tr("Set font line spacing " "to 100% to enable text wrapping option.")); - fontSettingsPageLineSpacing = fontSettingsPageLineSpacingLink(); - - if (fontSettingsPageLineSpacing) { - connect(fontSettingsPageLineSpacing, &QSpinBox::valueChanged, - this, [this](const int &value) { - if (value != 100) - enableTextWrapping->setChecked(false); - enableTextWrapping->setEnabled(value == 100); - enableTextWrappingHintLabel->setVisible(value != 100); - }); - - if (fontSettingsPageLineSpacing->value() != 100) + auto updateWrapping = [this] { + const bool normalLineSpacing = TextEditorSettings::fontSettings().relativeLineSpacing() == 100; + if (!normalLineSpacing) enableTextWrapping->setChecked(false); + enableTextWrapping->setEnabled(normalLineSpacing); + enableTextWrappingHintLabel->setVisible(!normalLineSpacing); + }; - enableTextWrapping->setEnabled(fontSettingsPageLineSpacing->value() == 100); - enableTextWrappingHintLabel->setVisible(fontSettingsPageLineSpacing->value() != 100); - } + updateWrapping(); + + connect(TextEditorSettings::instance(), &TextEditorSettings::fontSettingsChanged, + this, updateWrapping); connect(enableTextWrappingHintLabel, &QLabel::linkActivated, [] { Core::ICore::showOptionsDialog(Constants::TEXT_EDITOR_FONT_SETTINGS); } ); @@ -113,7 +107,7 @@ public: displayAnnotations = new QGroupBox(Tr::tr("Line annotations")), displayAnnotations->setCheckable(true); - using namespace Utils::Layouting; + using namespace Layouting; Column { leftAligned, @@ -177,8 +171,6 @@ public: void settingsToUI(); void setDisplaySettings(const DisplaySettings &, const MarginSettings &newMarginSettings); - QSpinBox *fontSettingsPageLineSpacingLink(); - DisplaySettingsPagePrivate *m_data = nullptr; QCheckBox *enableTextWrapping; @@ -208,8 +200,6 @@ public: QRadioButton *atMargin; QRadioButton *rightAligned; QRadioButton *betweenLines; - - QSpinBox *fontSettingsPageLineSpacing = nullptr; }; void DisplaySettingsWidget::apply() @@ -226,10 +216,8 @@ void DisplaySettingsWidget::settingsFromUI(DisplaySettings &displaySettings, { displaySettings.m_displayLineNumbers = displayLineNumbers->isChecked(); displaySettings.m_textWrapping = enableTextWrapping->isChecked(); - if (fontSettingsPageLineSpacing) { - if (fontSettingsPageLineSpacing->value() != 100) - displaySettings.m_textWrapping = false; - } + if (TextEditorSettings::fontSettings().relativeLineSpacing() != 100) + displaySettings.m_textWrapping = false; marginSettings.m_showMargin = showWrapColumn->isChecked(); marginSettings.m_tintMarginArea = tintMarginArea->isChecked(); marginSettings.m_useIndenter = useIndenter->isChecked(); @@ -268,8 +256,10 @@ void DisplaySettingsWidget::settingsToUI() enableTextWrapping->setChecked(displaySettings.m_textWrapping); showWrapColumn->setChecked(marginSettings.m_showMargin); tintMarginArea->setChecked(marginSettings.m_tintMarginArea); + tintMarginArea->setEnabled(marginSettings.m_showMargin); useIndenter->setChecked(marginSettings.m_useIndenter); wrapColumn->setValue(marginSettings.m_marginColumn); + wrapColumn->setEnabled(marginSettings.m_showMargin); visualizeWhitespace->setChecked(displaySettings.m_visualizeWhitespace); visualizeIndent->setChecked(displaySettings.m_visualizeIndent); displayFoldingMarkers->setChecked(displaySettings.m_displayFoldingMarkers); @@ -322,23 +312,6 @@ void DisplaySettingsWidget::setDisplaySettings(const DisplaySettings &newDisplay } } - QSpinBox *DisplaySettingsWidget::fontSettingsPageLineSpacingLink() - { - for (const auto &page : Core::IOptionsPage::allOptionsPages()) { - QWidget *widget = page->widget(); - - if (!widget) - continue; - - for (QSpinBox *spinBox : widget->findChildren()) { - if (spinBox->objectName() == QLatin1String("FontSettingsPage.LineSpacingSpinBox")) - return spinBox; - } - } - - return nullptr; - } - DisplaySettingsPage::DisplaySettingsPage() : d(new DisplaySettingsPagePrivate) { diff --git a/src/plugins/texteditor/fontsettingspage.cpp b/src/plugins/texteditor/fontsettingspage.cpp index 1e9ec7fafba..b57e23f35b8 100644 --- a/src/plugins/texteditor/fontsettingspage.cpp +++ b/src/plugins/texteditor/fontsettingspage.cpp @@ -107,8 +107,6 @@ public: { m_lastValue = m_value; - resize(639, 306); - m_antialias = new QCheckBox(Tr::tr("Antialias")); m_antialias->setChecked(m_value.antialias()); @@ -119,7 +117,6 @@ public: m_zoomSpinBox->setValue(m_value.fontZoom()); m_lineSpacingSpinBox = new QSpinBox; - m_lineSpacingSpinBox->setObjectName(QLatin1String("FontSettingsPage.LineSpacingSpinBox")); m_lineSpacingSpinBox->setSuffix(Tr::tr("%")); m_lineSpacingSpinBox->setRange(50, 3000); m_lineSpacingSpinBox->setValue(m_value.relativeLineSpacing()); diff --git a/src/plugins/texteditor/formattexteditor.cpp b/src/plugins/texteditor/formattexteditor.cpp index 6f1139e057b..03de5fb4152 100644 --- a/src/plugins/texteditor/formattexteditor.cpp +++ b/src/plugins/texteditor/formattexteditor.cpp @@ -10,10 +10,10 @@ #include +#include #include +#include #include -#include -#include #include #include @@ -64,13 +64,12 @@ static FormatTask format(FormatTask task) // Format temporary file QStringList options = task.command.options(); options.replaceInStrings(QLatin1String("%file"), sourceFile.filePath().toString()); - QtcProcess process; + Process process; process.setTimeoutS(5); process.setCommand({executable, options}); process.runBlocking(); if (process.result() != ProcessResult::FinishedWithSuccess) { - task.error = Tr::tr("TextEditor", "Failed to format: %1.") - .arg(process.exitMessage()); + task.error = Tr::tr("Failed to format: %1.").arg(process.exitMessage()); return task; } const QString output = process.cleanedStdErr(); @@ -89,7 +88,7 @@ static FormatTask format(FormatTask task) return task; case Command::PipeProcessing: { - QtcProcess process; + Process process; QStringList options = task.command.options(); options.replaceInStrings("%filename", task.filePath.fileName()); options.replaceInStrings("%file", task.filePath.toString()); @@ -249,8 +248,7 @@ void updateEditorText(QPlainTextEdit *editor, const QString &text) static void showError(const QString &error) { - Core::MessageManager::writeFlashing(Tr::tr("TextEditor", "Error in text formatting: %1") - .arg(error.trimmed())); + Core::MessageManager::writeFlashing(Tr::tr("Error in text formatting: %1").arg(error.trimmed())); } /** @@ -324,7 +322,7 @@ void formatEditorAsync(TextEditorWidget *editor, const Command &command, int sta checkAndApplyTask(watcher->result()); watcher->deleteLater(); }); - watcher->setFuture(Utils::runAsync(&format, FormatTask(editor, doc->filePath(), sd, + watcher->setFuture(Utils::asyncRun(&format, FormatTask(editor, doc->filePath(), sd, command, startPos, endPos))); } diff --git a/src/plugins/texteditor/highlighter.cpp b/src/plugins/texteditor/highlighter.cpp index a3765c4bf51..4512b71910f 100644 --- a/src/plugins/texteditor/highlighter.cpp +++ b/src/plugins/texteditor/highlighter.cpp @@ -279,15 +279,24 @@ void Highlighter::highlightBlock(const QString &text) return; } QTextBlock block = currentBlock(); - KSyntaxHighlighting::State state; - TextDocumentLayout::setBraceDepth(block, TextDocumentLayout::braceDepth(block.previous())); + const QTextBlock previousBlock = block.previous(); + TextDocumentLayout::setBraceDepth(block, TextDocumentLayout::braceDepth(previousBlock)); + KSyntaxHighlighting::State previousLineState; + if (TextBlockUserData *data = TextDocumentLayout::textUserData(previousBlock)) + previousLineState = data->syntaxState(); + KSyntaxHighlighting::State oldState; if (TextBlockUserData *data = TextDocumentLayout::textUserData(block)) { - state = data->syntaxState(); + oldState = data->syntaxState(); data->setFoldingStartIncluded(false); data->setFoldingEndIncluded(false); } - state = highlightLine(text, state); - const QTextBlock nextBlock = block.next(); + KSyntaxHighlighting::State state = highlightLine(text, previousLineState); + if (oldState != state) { + TextBlockUserData *data = TextDocumentLayout::userData(block); + data->setSyntaxState(state); + // Toggles the LSB of current block's userState. It forces rehighlight of next block. + setCurrentBlockState(currentBlockState() ^ 1); + } Parentheses parentheses; int pos = 0; @@ -300,13 +309,9 @@ void Highlighter::highlightBlock(const QString &text) } TextDocumentLayout::setParentheses(currentBlock(), parentheses); + const QTextBlock nextBlock = block.next(); if (nextBlock.isValid()) { TextBlockUserData *data = TextDocumentLayout::userData(nextBlock); - if (data->syntaxState() != state) { - data->setSyntaxState(state); - // Toggles the LSB of current block's userState. It forces rehighlight of next block. - setCurrentBlockState(currentBlockState() ^ 1); - } data->setFoldingIndent(TextDocumentLayout::braceDepth(block)); } diff --git a/src/plugins/texteditor/highlightersettingspage.cpp b/src/plugins/texteditor/highlightersettingspage.cpp index ceb8f7d48f1..4cc16a488a6 100644 --- a/src/plugins/texteditor/highlightersettingspage.cpp +++ b/src/plugins/texteditor/highlightersettingspage.cpp @@ -13,7 +13,6 @@ #include #include -#include #include #include #include @@ -24,24 +23,50 @@ using namespace Utils; namespace TextEditor { -namespace Internal { -class HighlighterSettingsPageWidget : public QWidget +class HighlighterSettingsPageWidget; + +class HighlighterSettingsPagePrivate { public: - QLabel *definitionsInfolabel; - QPushButton *downloadDefinitions; - QLabel *updateStatus; - PathChooser *definitionFilesPath; - QPushButton *reloadDefinitions; - QPushButton *resetCache; - QLineEdit *ignoreEdit; - - HighlighterSettingsPageWidget() + void ensureInitialized() { - resize(521, 332); + if (m_initialized) + return; + m_initialized = true; + m_settings.fromSettings(m_settingsPrefix, Core::ICore::settings()); + migrateGenericHighlighterFiles(); + } - definitionsInfolabel = new QLabel(this); + void migrateGenericHighlighterFiles() + { + QDir userDefinitionPath(m_settings.definitionFilesPath().toString()); + if (userDefinitionPath.mkdir("syntax")) { + const auto link = Utils::HostOsInfo::isAnyUnixHost() + ? static_cast(&QFile::link) + : static_cast(&QFile::copy); + + for (const QFileInfo &file : userDefinitionPath.entryInfoList({"*.xml"}, QDir::Files)) + link(file.filePath(), file.absolutePath() + "/syntax/" + file.fileName()); + } + } + + bool m_initialized = false; + const QString m_settingsPrefix{"Text"}; + + HighlighterSettings m_settings; + + QPointer m_widget; +}; + +class HighlighterSettingsPageWidget : public Core::IOptionsPageWidget +{ +public: + HighlighterSettingsPageWidget(HighlighterSettingsPagePrivate *d) : d(d) + { + d->ensureInitialized(); + + auto definitionsInfolabel = new QLabel(this); definitionsInfolabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); definitionsInfolabel->setTextFormat(Qt::RichText); definitionsInfolabel->setAlignment(Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter); @@ -51,24 +76,25 @@ public: "" "KSyntaxHighlighting engine.

")); - downloadDefinitions = new QPushButton(Tr::tr("Download Definitions")); + auto downloadDefinitions = new QPushButton(Tr::tr("Download Definitions")); downloadDefinitions->setToolTip(Tr::tr("Download missing and update existing syntax definition files.")); - updateStatus = new QLabel; + auto updateStatus = new QLabel; updateStatus->setObjectName("updateStatus"); - definitionFilesPath = new PathChooser; - definitionFilesPath->setExpectedKind(PathChooser::ExistingDirectory); - definitionFilesPath->setHistoryCompleter("TextEditor.Highlighter.History"); + m_definitionFilesPath = new PathChooser; + m_definitionFilesPath->setFilePath(d->m_settings.definitionFilesPath()); + m_definitionFilesPath->setExpectedKind(PathChooser::ExistingDirectory); + m_definitionFilesPath->setHistoryCompleter("TextEditor.Highlighter.History"); - reloadDefinitions = new QPushButton(Tr::tr("Reload Definitions")); + auto reloadDefinitions = new QPushButton(Tr::tr("Reload Definitions")); reloadDefinitions->setToolTip(Tr::tr("Reload externally modified definition files.")); - resetCache = new QPushButton(Tr::tr("Reset Remembered Definitions")); + auto resetCache = new QPushButton(Tr::tr("Reset Remembered Definitions")); resetCache->setToolTip(Tr::tr("Reset definitions remembered for files that can be " "associated with more than one highlighter definition.")); - ignoreEdit = new QLineEdit; + m_ignoreEdit = new QLineEdit(d->m_settings.ignoredFilesPatterns()); using namespace Layouting; Column { @@ -79,11 +105,11 @@ public: Column { Row { downloadDefinitions, updateStatus, st }, Row { Tr::tr("User Highlight Definition Files"), - definitionFilesPath, reloadDefinitions }, + m_definitionFilesPath, reloadDefinitions }, Row { st, resetCache } } }, - Row { Tr::tr("Ignored file patterns:"), ignoreEdit }, + Row { Tr::tr("Ignored file patterns:"), m_ignoreEdit }, st }.attachTo(this); @@ -102,53 +128,25 @@ public: Highlighter::clearDefinitionForDocumentCache(); }); } -}; -} // Internal + void apply() final + { + bool changed = d->m_settings.definitionFilesPath() != m_definitionFilesPath->filePath() + || d->m_settings.ignoredFilesPatterns() != m_ignoreEdit->text(); -using namespace Internal; - -class HighlighterSettingsPagePrivate -{ -public: - HighlighterSettingsPagePrivate() = default; - - void ensureInitialized(); - void migrateGenericHighlighterFiles(); - - void settingsFromUI(); - void settingsToUI(); - bool settingsChanged(); - - bool m_initialized = false; - const QString m_settingsPrefix{"Text"}; - - HighlighterSettings m_settings; - - QPointer m_widget; -}; - -void HighlighterSettingsPagePrivate::migrateGenericHighlighterFiles() -{ - QDir userDefinitionPath(m_settings.definitionFilesPath().toString()); - if (userDefinitionPath.mkdir("syntax")) { - const auto link = Utils::HostOsInfo::isAnyUnixHost() - ? static_cast(&QFile::link) - : static_cast(&QFile::copy); - - for (const QFileInfo &file : userDefinitionPath.entryInfoList({"*.xml"}, QDir::Files)) - link(file.filePath(), file.absolutePath() + "/syntax/" + file.fileName()); + if (changed) { + d->m_settings.setDefinitionFilesPath(m_definitionFilesPath->filePath()); + d->m_settings.setIgnoredFilesPatterns(m_ignoreEdit->text()); + d->m_settings.toSettings(d->m_settingsPrefix, Core::ICore::settings()); + } } -} -void HighlighterSettingsPagePrivate::ensureInitialized() -{ - if (m_initialized) - return; - m_initialized = true; - m_settings.fromSettings(m_settingsPrefix, Core::ICore::settings()); - migrateGenericHighlighterFiles(); -} + PathChooser *m_definitionFilesPath; + QLineEdit *m_ignoreEdit; + HighlighterSettingsPagePrivate *d; +}; + +// HighlighterSettingsPage HighlighterSettingsPage::HighlighterSettingsPage() : d(new HighlighterSettingsPagePrivate) @@ -158,6 +156,7 @@ HighlighterSettingsPage::HighlighterSettingsPage() setCategory(TextEditor::Constants::TEXT_EDITOR_SETTINGS_CATEGORY); setDisplayCategory(Tr::tr("Text Editor")); setCategoryIconPath(TextEditor::Constants::TEXT_EDITOR_SETTINGS_CATEGORY_ICON_PATH); + setWidgetCreator([this] { return new HighlighterSettingsPageWidget(d); }); } HighlighterSettingsPage::~HighlighterSettingsPage() @@ -165,55 +164,10 @@ HighlighterSettingsPage::~HighlighterSettingsPage() delete d; } -QWidget *HighlighterSettingsPage::widget() -{ - if (!d->m_widget) { - d->m_widget = new HighlighterSettingsPageWidget; - d->settingsToUI(); - } - return d->m_widget; -} - -void HighlighterSettingsPage::apply() -{ - if (!d->m_widget) // page was not shown - return; - if (d->settingsChanged()) - d->settingsFromUI(); -} - -void HighlighterSettingsPage::finish() -{ - delete d->m_widget; - d->m_widget = nullptr; -} - const HighlighterSettings &HighlighterSettingsPage::highlighterSettings() const { d->ensureInitialized(); return d->m_settings; } -void HighlighterSettingsPagePrivate::settingsFromUI() -{ - ensureInitialized(); - m_settings.setDefinitionFilesPath(m_widget->definitionFilesPath->filePath()); - m_settings.setIgnoredFilesPatterns(m_widget->ignoreEdit->text()); - m_settings.toSettings(m_settingsPrefix, Core::ICore::settings()); -} - -void HighlighterSettingsPagePrivate::settingsToUI() -{ - ensureInitialized(); - m_widget->definitionFilesPath->setFilePath(m_settings.definitionFilesPath()); - m_widget->ignoreEdit->setText(m_settings.ignoredFilesPatterns()); -} - -bool HighlighterSettingsPagePrivate::settingsChanged() -{ - ensureInitialized(); - return m_settings.definitionFilesPath() != m_widget->definitionFilesPath->filePath() - || m_settings.ignoredFilesPatterns() != m_widget->ignoreEdit->text(); -} - } // TextEditor diff --git a/src/plugins/texteditor/highlightersettingspage.h b/src/plugins/texteditor/highlightersettingspage.h index c56e6d2238c..94407c9020b 100644 --- a/src/plugins/texteditor/highlightersettingspage.h +++ b/src/plugins/texteditor/highlightersettingspage.h @@ -15,10 +15,6 @@ public: HighlighterSettingsPage(); ~HighlighterSettingsPage() override; - QWidget *widget() override; - void apply() override; - void finish() override; - const HighlighterSettings &highlighterSettings() const; private: diff --git a/src/plugins/texteditor/icodestylepreferences.cpp b/src/plugins/texteditor/icodestylepreferences.cpp index 931714b71c5..29a524ebf68 100644 --- a/src/plugins/texteditor/icodestylepreferences.cpp +++ b/src/plugins/texteditor/icodestylepreferences.cpp @@ -25,6 +25,7 @@ public: QString m_displayName; bool m_readOnly = false; bool m_temporarilyReadOnly = false; + bool m_isAdditionalTabDisabled = false; QString m_settingsSuffix; }; @@ -82,6 +83,16 @@ bool ICodeStylePreferences::isTemporarilyReadOnly() const return d->m_temporarilyReadOnly; } +bool ICodeStylePreferences::isAdditionalTabDisabled() const +{ + return d->m_isAdditionalTabDisabled; +} + +void ICodeStylePreferences::setIsAdditionalTabDisabled(bool on) +{ + d->m_isAdditionalTabDisabled = on; +} + void ICodeStylePreferences::setTabSettings(const TabSettings &settings) { if (d->m_tabSettings == settings) diff --git a/src/plugins/texteditor/icodestylepreferences.h b/src/plugins/texteditor/icodestylepreferences.h index 1c363903445..7fd616d5604 100644 --- a/src/plugins/texteditor/icodestylepreferences.h +++ b/src/plugins/texteditor/icodestylepreferences.h @@ -40,6 +40,9 @@ public: bool isTemporarilyReadOnly() const; void setTemporarilyReadOnly(bool on); + bool isAdditionalTabDisabled() const; + void setIsAdditionalTabDisabled(bool on); + void setTabSettings(const TabSettings &settings); TabSettings tabSettings() const; TabSettings currentTabSettings() const; diff --git a/src/plugins/texteditor/linenumberfilter.cpp b/src/plugins/texteditor/linenumberfilter.cpp index 78a65c84d2f..b718e5b4331 100644 --- a/src/plugins/texteditor/linenumberfilter.cpp +++ b/src/plugins/texteditor/linenumberfilter.cpp @@ -5,24 +5,14 @@ #include "texteditortr.h" -#include #include -#include -#include - -#include -#include -#include - -using LineColumn = QPair; -Q_DECLARE_METATYPE(LineColumn) using namespace Core; +using namespace Utils; namespace TextEditor::Internal { -LineNumberFilter::LineNumberFilter(QObject *parent) - : ILocatorFilter(parent) +LineNumberFilter::LineNumberFilter() { setId("Line in current document"); setDisplayName(Tr::tr("Line in Current Document")); @@ -33,57 +23,47 @@ LineNumberFilter::LineNumberFilter(QObject *parent) setDefaultIncludedByDefault(true); } -void LineNumberFilter::prepareSearch(const QString &entry) +LocatorMatcherTasks LineNumberFilter::matchers() { - Q_UNUSED(entry) - m_hasCurrentEditor = EditorManager::currentEditor() != nullptr; + using namespace Tasking; + + TreeStorage storage; + + const auto onSetup = [storage] { + const QStringList lineAndColumn = storage->input().split(':'); + int sectionCount = lineAndColumn.size(); + int line = 0; + int column = 0; + bool ok = false; + if (sectionCount > 0) + line = lineAndColumn.at(0).toInt(&ok); + if (ok && sectionCount > 1) + column = lineAndColumn.at(1).toInt(&ok); + if (!ok) + return; + if (EditorManager::currentEditor() && (line > 0 || column > 0)) { + QString text; + if (line > 0 && column > 0) + text = Tr::tr("Line %1, Column %2").arg(line).arg(column); + else if (line > 0) + text = Tr::tr("Line %1").arg(line); + else + text = Tr::tr("Column %1").arg(column); + LocatorFilterEntry entry; + entry.displayName = text; + entry.acceptor = [line, targetColumn = column - 1] { + IEditor *editor = EditorManager::currentEditor(); + if (!editor) + return AcceptResult(); + EditorManager::addCurrentPositionToNavigationHistory(); + editor->gotoLine(line < 1 ? editor->currentLine() : line, targetColumn); + EditorManager::activateEditor(editor); + return AcceptResult(); + }; + storage->reportOutput({entry}); + } + }; + return {{Sync(onSetup), storage}}; } -QList LineNumberFilter::matchesFor(QFutureInterface &, const QString &entry) -{ - QList value; - const QStringList lineAndColumn = entry.split(':'); - int sectionCount = lineAndColumn.size(); - int line = 0; - int column = 0; - bool ok = false; - if (sectionCount > 0) - line = lineAndColumn.at(0).toInt(&ok); - if (ok && sectionCount > 1) - column = lineAndColumn.at(1).toInt(&ok); - if (!ok) - return value; - if (m_hasCurrentEditor && (line > 0 || column > 0)) { - LineColumn data; - data.first = line; - data.second = column - 1; // column API is 0-based - QString text; - if (line > 0 && column > 0) - text = Tr::tr("Line %1, Column %2").arg(line).arg(column); - else if (line > 0) - text = Tr::tr("Line %1").arg(line); - else - text = Tr::tr("Column %1").arg(column); - value.append(LocatorFilterEntry(this, text, QVariant::fromValue(data))); - } - return value; -} - -void LineNumberFilter::accept(const LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const -{ - Q_UNUSED(newText) - Q_UNUSED(selectionStart) - Q_UNUSED(selectionLength) - IEditor *editor = EditorManager::currentEditor(); - if (editor) { - EditorManager::addCurrentPositionToNavigationHistory(); - LineColumn data = selection.internalData.value(); - if (data.first < 1) // jump to column in same line - data.first = editor->currentLine(); - editor->gotoLine(data.first, data.second); - EditorManager::activateEditor(editor); - } -} - -} // TextEditor::Internal +} // namespace TextEditor::Internal diff --git a/src/plugins/texteditor/linenumberfilter.h b/src/plugins/texteditor/linenumberfilter.h index 03b52f270ba..ec72c1bb029 100644 --- a/src/plugins/texteditor/linenumberfilter.h +++ b/src/plugins/texteditor/linenumberfilter.h @@ -5,31 +5,15 @@ #include -#include -#include -#include - -namespace Core { class IEditor; } - -namespace TextEditor { -namespace Internal { +namespace TextEditor::Internal { class LineNumberFilter : public Core::ILocatorFilter { - Q_OBJECT - public: - explicit LineNumberFilter(QObject *parent = nullptr); - - void prepareSearch(const QString &entry) override; - QList matchesFor(QFutureInterface &future, - const QString &entry) override; - void accept(const Core::LocatorFilterEntry &selection, - QString *newText, int *selectionStart, int *selectionLength) const override; + LineNumberFilter(); private: - bool m_hasCurrentEditor = false; + Core::LocatorMatcherTasks matchers() final; }; -} // namespace Internal -} // namespace TextEditor +} // namespace TextEditor::Internal diff --git a/src/plugins/texteditor/markdowneditor.cpp b/src/plugins/texteditor/markdowneditor.cpp new file mode 100644 index 00000000000..9c89636df8e --- /dev/null +++ b/src/plugins/texteditor/markdowneditor.cpp @@ -0,0 +1,330 @@ +// Copyright (C) 2023 Tasuku Suzuki +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "markdowneditor.h" + +#include "textdocument.h" +#include "texteditor.h" +#include "texteditortr.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace TextEditor::Internal { + +const char MARKDOWNVIEWER_ID[] = "Editors.MarkdownViewer"; +const char MARKDOWNVIEWER_TEXT_CONTEXT[] = "Editors.MarkdownViewer.Text"; +const char MARKDOWNVIEWER_MIME_TYPE[] = "text/markdown"; +const char MARKDOWNVIEWER_TEXTEDITOR_RIGHT[] = "Markdown.TextEditorRight"; +const char MARKDOWNVIEWER_SHOW_EDITOR[] = "Markdown.ShowEditor"; +const char MARKDOWNVIEWER_SHOW_PREVIEW[] = "Markdown.ShowPreview"; +const bool kTextEditorRightDefault = true; +const bool kShowEditorDefault = true; +const bool kShowPreviewDefault = true; + +class MarkdownEditor : public Core::IEditor +{ +public: + MarkdownEditor() + : m_document(new TextDocument(MARKDOWNVIEWER_ID)) + { + m_document->setMimeType(MARKDOWNVIEWER_MIME_TYPE); + + QSettings *s = Core::ICore::settings(); + const bool textEditorRight + = s->value(MARKDOWNVIEWER_TEXTEDITOR_RIGHT, kTextEditorRightDefault).toBool(); + const bool showPreview = s->value(MARKDOWNVIEWER_SHOW_PREVIEW, kShowPreviewDefault).toBool(); + const bool showEditor = s->value(MARKDOWNVIEWER_SHOW_EDITOR, kShowEditorDefault).toBool() + || !showPreview; // ensure at least one is visible + + m_splitter = new Core::MiniSplitter; + + // preview + m_previewWidget = new QTextBrowser(); + m_previewWidget->setOpenExternalLinks(true); + m_previewWidget->setFrameShape(QFrame::NoFrame); + new Utils::MarkdownHighlighter(m_previewWidget->document()); + + // editor + m_textEditorWidget = new TextEditorWidget; + m_textEditorWidget->setTextDocument(m_document); + m_textEditorWidget->setupGenericHighlighter(); + m_textEditorWidget->setMarksVisible(false); + auto context = new Core::IContext(this); + context->setWidget(m_textEditorWidget); + context->setContext(Core::Context(MARKDOWNVIEWER_TEXT_CONTEXT)); + Core::ICore::addContextObject(context); + + m_splitter->addWidget(m_previewWidget); + m_splitter->addWidget(m_textEditorWidget); + + setContext(Core::Context(MARKDOWNVIEWER_ID)); + + auto widget = new QWidget; + auto layout = new QVBoxLayout; + layout->setContentsMargins(0, 0, 0, 0); + widget->setLayout(layout); + layout->addWidget(m_splitter); + setWidget(widget); + m_widget->installEventFilter(this); + using namespace Aggregation; + Aggregate *agg = Aggregate::parentAggregate(m_textEditorWidget); + if (!agg) { + agg = new Aggregate; + agg->add(m_textEditorWidget); + } + agg->add(m_widget.get()); + + m_togglePreviewVisible = new QToolButton; + m_togglePreviewVisible->setText(Tr::tr("Show Preview")); + m_togglePreviewVisible->setCheckable(true); + m_togglePreviewVisible->setChecked(showPreview); + m_previewWidget->setVisible(showPreview); + + m_toggleEditorVisible = new QToolButton; + m_toggleEditorVisible->setText(Tr::tr("Show Editor")); + m_toggleEditorVisible->setCheckable(true); + m_toggleEditorVisible->setChecked(showEditor); + m_textEditorWidget->setVisible(showEditor); + + auto swapViews = new QToolButton; + swapViews->setText(Tr::tr("Swap Views")); + swapViews->setEnabled(showEditor && showPreview); + + m_toolbarLayout = new QHBoxLayout(&m_toolbar); + m_toolbarLayout->setSpacing(0); + m_toolbarLayout->setContentsMargins(0, 0, 0, 0); + m_toolbarLayout->addStretch(); + m_toolbarLayout->addWidget(m_togglePreviewVisible); + m_toolbarLayout->addWidget(m_toggleEditorVisible); + m_toolbarLayout->addWidget(swapViews); + + setWidgetOrder(textEditorRight); + + connect(m_document.data(), + &TextDocument::mimeTypeChanged, + m_document.data(), + &TextDocument::changed); + + const auto updatePreview = [this] { + // save scroll positions + const QPoint positions = m_previewRestoreScrollPosition + ? *m_previewRestoreScrollPosition + : QPoint(m_previewWidget->horizontalScrollBar()->value(), + m_previewWidget->verticalScrollBar()->value()); + m_previewRestoreScrollPosition.reset(); + + m_previewWidget->setMarkdown(m_document->plainText()); + + m_previewWidget->horizontalScrollBar()->setValue(positions.x()); + m_previewWidget->verticalScrollBar()->setValue(positions.y()); + }; + + const auto viewToggled = + [swapViews](QWidget *view, bool visible, QWidget *otherView, QToolButton *otherButton) { + if (view->isVisible() == visible) + return; + view->setVisible(visible); + if (visible) { + view->setFocus(); + } else if (otherView->isVisible()) { + otherView->setFocus(); + } else { + // make sure at least one view is visible + otherButton->toggle(); + } + swapViews->setEnabled(view->isVisible() && otherView->isVisible()); + }; + const auto saveViewSettings = [this] { + Utils::QtcSettings *s = Core::ICore::settings(); + s->setValueWithDefault(MARKDOWNVIEWER_SHOW_PREVIEW, + m_togglePreviewVisible->isChecked(), + kShowPreviewDefault); + s->setValueWithDefault(MARKDOWNVIEWER_SHOW_EDITOR, + m_toggleEditorVisible->isChecked(), + kShowEditorDefault); + }; + + connect(m_toggleEditorVisible, + &QToolButton::toggled, + this, + [this, viewToggled, saveViewSettings](bool visible) { + viewToggled(m_textEditorWidget, + visible, + m_previewWidget, + m_togglePreviewVisible); + saveViewSettings(); + }); + connect(m_togglePreviewVisible, + &QToolButton::toggled, + this, + [this, viewToggled, updatePreview, saveViewSettings](bool visible) { + viewToggled(m_previewWidget, visible, m_textEditorWidget, m_toggleEditorVisible); + if (visible && m_performDelayedUpdate) { + m_performDelayedUpdate = false; + updatePreview(); + } + saveViewSettings(); + }); + + connect(swapViews, &QToolButton::clicked, m_textEditorWidget, [this] { + const bool textEditorRight = isTextEditorRight(); + setWidgetOrder(!textEditorRight); + // save settings + Utils::QtcSettings *s = Core::ICore::settings(); + s->setValueWithDefault(MARKDOWNVIEWER_TEXTEDITOR_RIGHT, + !textEditorRight, + kTextEditorRightDefault); + }); + + // TODO directly update when we build with Qt 6.5.2 + m_previewTimer.setInterval(500); + m_previewTimer.setSingleShot(true); + connect(&m_previewTimer, &QTimer::timeout, this, [this, updatePreview] { + if (m_togglePreviewVisible->isChecked()) + updatePreview(); + else + m_performDelayedUpdate = true; + }); + + connect(m_document->document(), &QTextDocument::contentsChanged, &m_previewTimer, [this] { + m_previewTimer.start(); + }); + } + + bool isTextEditorRight() const { return m_splitter->widget(0) == m_previewWidget; } + + void setWidgetOrder(bool textEditorRight) + { + QTC_ASSERT(m_splitter->count() > 1, return); + QWidget *left = textEditorRight ? static_cast(m_previewWidget) + : m_textEditorWidget; + QWidget *right = textEditorRight ? static_cast(m_textEditorWidget) + : m_previewWidget; + m_splitter->insertWidget(0, left); + m_splitter->insertWidget(1, right); + // buttons + QWidget *leftButton = textEditorRight ? m_togglePreviewVisible : m_toggleEditorVisible; + QWidget *rightButton = textEditorRight ? m_toggleEditorVisible : m_togglePreviewVisible; + const int rightIndex = m_toolbarLayout->count() - 2; + m_toolbarLayout->insertWidget(rightIndex, leftButton); + m_toolbarLayout->insertWidget(rightIndex, rightButton); + } + + QWidget *toolBar() override { return &m_toolbar; } + + Core::IDocument *document() const override { return m_document.data(); } + TextEditorWidget *textEditorWidget() const { return m_textEditorWidget; } + int currentLine() const override { return textEditorWidget()->textCursor().blockNumber() + 1; }; + int currentColumn() const override + { + QTextCursor cursor = textEditorWidget()->textCursor(); + return cursor.position() - cursor.block().position() + 1; + } + void gotoLine(int line, int column, bool centerLine) override + { + if (!m_toggleEditorVisible->isChecked()) + m_toggleEditorVisible->toggle(); + textEditorWidget()->gotoLine(line, column, centerLine); + } + + bool eventFilter(QObject *obj, QEvent *ev) override + { + if (obj == m_widget && ev->type() == QEvent::FocusIn) { + if (m_splitter->focusWidget()) + m_splitter->focusWidget()->setFocus(); + else if (m_textEditorWidget->isVisible()) + m_textEditorWidget->setFocus(); + else + m_splitter->widget(0)->setFocus(); + return true; + } + return Core::IEditor::eventFilter(obj, ev); + } + + QByteArray saveState() const override + { + QByteArray state; + QDataStream stream(&state, QIODevice::WriteOnly); + stream << 1; // version number + stream << m_textEditorWidget->saveState(); + stream << m_previewWidget->horizontalScrollBar()->value(); + stream << m_previewWidget->verticalScrollBar()->value(); + stream << isTextEditorRight(); + stream << m_togglePreviewVisible->isChecked(); + stream << m_toggleEditorVisible->isChecked(); + stream << m_splitter->saveState(); + return state; + } + + void restoreState(const QByteArray &state) override + { + if (state.isEmpty()) + return; + int version; + QByteArray editorState; + int previewHV; + int previewVV; + bool textEditorRight; + bool previewShown; + bool textEditorShown; + QByteArray splitterState; + QDataStream stream(state); + stream >> version; + stream >> editorState; + stream >> previewHV; + stream >> previewVV; + stream >> textEditorRight; + stream >> previewShown; + stream >> textEditorShown; + stream >> splitterState; + m_textEditorWidget->restoreState(editorState); + m_previewRestoreScrollPosition.emplace(previewHV, previewVV); + setWidgetOrder(textEditorRight); + m_splitter->restoreState(splitterState); + m_togglePreviewVisible->setChecked(previewShown); + // ensure at least one is shown + m_toggleEditorVisible->setChecked(textEditorShown || !previewShown); + } + +private: + QTimer m_previewTimer; + bool m_performDelayedUpdate = false; + Core::MiniSplitter *m_splitter; + QTextBrowser *m_previewWidget; + TextEditorWidget *m_textEditorWidget; + TextDocumentPtr m_document; + QWidget m_toolbar; + QHBoxLayout *m_toolbarLayout; + QToolButton *m_toggleEditorVisible; + QToolButton *m_togglePreviewVisible; + std::optional m_previewRestoreScrollPosition; +}; + +MarkdownEditorFactory::MarkdownEditorFactory() + : m_actionHandler(MARKDOWNVIEWER_ID, + MARKDOWNVIEWER_TEXT_CONTEXT, + TextEditor::TextEditorActionHandler::None, + [](Core::IEditor *editor) { + return static_cast(editor)->textEditorWidget(); + }) +{ + setId(MARKDOWNVIEWER_ID); + setDisplayName(::Core::Tr::tr("Markdown Viewer")); + addMimeType(MARKDOWNVIEWER_MIME_TYPE); + setEditorCreator([] { return new MarkdownEditor; }); +} + +} // namespace TextEditor::Internal diff --git a/src/plugins/texteditor/markdowneditor.h b/src/plugins/texteditor/markdowneditor.h new file mode 100644 index 00000000000..de87c558e1b --- /dev/null +++ b/src/plugins/texteditor/markdowneditor.h @@ -0,0 +1,21 @@ +// Copyright (C) 2023 Tasuku Suzuki +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +#include + +namespace TextEditor::Internal { + +class MarkdownEditorFactory final : public Core::IEditorFactory +{ +public: + MarkdownEditorFactory(); + +private: + TextEditor::TextEditorActionHandler m_actionHandler; +}; + +} // TextEditor::Internal diff --git a/src/plugins/texteditor/outlinefactory.cpp b/src/plugins/texteditor/outlinefactory.cpp index 76222642c15..bc9aa03c8ba 100644 --- a/src/plugins/texteditor/outlinefactory.cpp +++ b/src/plugins/texteditor/outlinefactory.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -63,6 +64,7 @@ OutlineWidgetStack::OutlineWidgetStack(OutlineFactory *factory) : this, &OutlineWidgetStack::toggleCursorSynchronization); m_filterButton = new QToolButton(this); + Utils::StyleHelper::setPanelWidget(m_filterButton); // The ToolButton needs a parent because updateFilterMenu() sets // it visible. That would open a top-level window if the button // did not have a parent in that moment. @@ -70,11 +72,12 @@ OutlineWidgetStack::OutlineWidgetStack(OutlineFactory *factory) : m_filterButton->setIcon(Utils::Icons::FILTER.icon()); m_filterButton->setToolTip(Tr::tr("Filter tree")); m_filterButton->setPopupMode(QToolButton::InstantPopup); - m_filterButton->setProperty("noArrow", true); + m_filterButton->setProperty(Utils::StyleHelper::C_NO_ARROW, true); m_filterMenu = new QMenu(m_filterButton); m_filterButton->setMenu(m_filterMenu); m_toggleSort = new QToolButton(this); + Utils::StyleHelper::setPanelWidget(m_toggleSort); m_toggleSort->setIcon(Utils::Icons::SORT_ALPHABETICALLY_TOOLBAR.icon()); m_toggleSort->setCheckable(true); m_toggleSort->setChecked(false); diff --git a/src/plugins/texteditor/quickfix.h b/src/plugins/texteditor/quickfix.h index 6469bd7d8bc..e456230ac20 100644 --- a/src/plugins/texteditor/quickfix.h +++ b/src/plugins/texteditor/quickfix.h @@ -35,8 +35,7 @@ public: virtual ~QuickFixOperation(); /*! - \returns The priority for this quick-fix. See the QuickFixCollector for more - information. + Returns The priority for this quick-fix. See the QuickFixCollector for more information. */ virtual int priority() const; @@ -44,8 +43,7 @@ public: void setPriority(int priority); /*! - \returns The description for this quick-fix. This description is shown to the - user. + Returns The description for this quick-fix. This description is shown to the user. */ virtual QString description() const; diff --git a/src/plugins/texteditor/semantichighlighter.cpp b/src/plugins/texteditor/semantichighlighter.cpp index 68b1792ca84..30cb9cf729a 100644 --- a/src/plugins/texteditor/semantichighlighter.cpp +++ b/src/plugins/texteditor/semantichighlighter.cpp @@ -82,26 +82,28 @@ void SemanticHighlighter::incrementalApplyExtraAdditionalFormats(SyntaxHighlight if (to <= from) return; - const int firstResultBlockNumber = int(future.resultAt(from).line) - 1; + const int resultStartLine = future.resultAt(from).line; + int formattingStartLine = 1; - // blocks between currentBlockNumber and the last block with results will - // be cleaned of additional extra formats if they have no results - int currentBlockNumber = 0; + // Find the line on which to start formatting, where "formatting" means to either + // clear out formats from outdated document versions (if there is no current result + // on that line), or apply the format corresponding to the respective result. + // Note that if there are earlier results on the same line, we have to make sure they + // get re-applied by adapting the from variable accordingly. for (int i = from - 1; i >= 0; --i) { const HighlightingResult &result = future.resultAt(i); - const int blockNumber = int(result.line) - 1; - if (blockNumber < firstResultBlockNumber) { - // stop! found where last format stopped - currentBlockNumber = blockNumber + 1; - // add previous results for the same line to avoid undoing their formats + if (result.line == resultStartLine) { + from = i; + } else if (result.line < resultStartLine) { + formattingStartLine = result.line + 1; from = i + 1; break; } } QTextDocument *doc = highlighter->document(); - QTC_ASSERT(currentBlockNumber < doc->blockCount(), return); - QTextBlock currentBlock = doc->findBlockByNumber(currentBlockNumber); + QTC_ASSERT(formattingStartLine <= doc->blockCount(), return); + QTextBlock currentBlock = doc->findBlockByNumber(formattingStartLine - 1); std::map> formatRanges; for (int i = from; i < to; ++i) { diff --git a/src/plugins/texteditor/snippets/snippet.cpp b/src/plugins/texteditor/snippets/snippet.cpp index f41156c4cee..74a4dc543c3 100644 --- a/src/plugins/texteditor/snippets/snippet.cpp +++ b/src/plugins/texteditor/snippets/snippet.cpp @@ -330,7 +330,7 @@ void Internal::TextEditorPlugin::testSnippetParsing_data() << QString::fromLatin1("\\\\$test\\\\\\\\$\\\\") << false << Parts(); QTest::newRow("Q_PROPERTY") << QString( - "Q_PROPERTY($type$ $name$ READ $name$ WRITE set$name:c$ NOTIFY $name$Changed)") + "Q_PROPERTY($type$ $name$ READ $name$ WRITE set$name:c$ NOTIFY $name$Changed FINAL)") << true << Parts{SnippetPart("Q_PROPERTY("), SnippetPart("type", 0), @@ -342,7 +342,7 @@ void Internal::TextEditorPlugin::testSnippetParsing_data() SnippetPart("name", 1, TCMANGLER_ID), SnippetPart(" NOTIFY "), SnippetPart("name", 1), - SnippetPart("Changed)")}; + SnippetPart("Changed FINAL)")}; QTest::newRow("open identifier") << QString("$test") << false << Parts(); QTest::newRow("wrong mangler") << QString("$test:X$") << false << Parts(); diff --git a/src/plugins/texteditor/snippets/snippetscollection.cpp b/src/plugins/texteditor/snippets/snippetscollection.cpp index be658c14777..f799b3fe424 100644 --- a/src/plugins/texteditor/snippets/snippetscollection.cpp +++ b/src/plugins/texteditor/snippets/snippetscollection.cpp @@ -78,9 +78,7 @@ SnippetsCollection::SnippetsCollection() m_builtInSnippetsFiles(Core::ICore::resourcePath("snippets") .dirEntries(FileFilter({"*.xml"}))) { - - connect(Core::ICore::instance(), &Core::ICore::coreOpened, - this, &SnippetsCollection::identifyGroups); + identifyGroups(); } SnippetsCollection::~SnippetsCollection() = default; diff --git a/src/plugins/texteditor/snippets/snippetssettingspage.cpp b/src/plugins/texteditor/snippets/snippetssettingspage.cpp index b64910a0f1d..e4bfdf7df84 100644 --- a/src/plugins/texteditor/snippets/snippetssettingspage.cpp +++ b/src/plugins/texteditor/snippets/snippetssettingspage.cpp @@ -35,15 +35,14 @@ #include #include -namespace TextEditor { -namespace Internal { +namespace TextEditor::Internal { // SnippetsTableModel + class SnippetsTableModel : public QAbstractTableModel { - Q_OBJECT public: - SnippetsTableModel(QObject *parent); + SnippetsTableModel(); ~SnippetsTableModel() override = default; int rowCount(const QModelIndex &parent = QModelIndex()) const override; @@ -74,9 +73,8 @@ private: QString m_activeGroupId; }; -SnippetsTableModel::SnippetsTableModel(QObject *parent) : - QAbstractTableModel(parent), - m_collection(SnippetsCollection::instance()) +SnippetsTableModel::SnippetsTableModel() + : m_collection(SnippetsCollection::instance()) {} int SnippetsTableModel::rowCount(const QModelIndex &) const @@ -244,19 +242,15 @@ void SnippetsTableModel::replaceSnippet(const Snippet &snippet, const QModelInde } } -// SnippetsSettingsPagePrivate -class SnippetsSettingsPagePrivate : public QObject +// SnippetsSettingsWidget + +class SnippetsSettingsWidget : public Core::IOptionsPageWidget { public: - SnippetsSettingsPagePrivate(); - ~SnippetsSettingsPagePrivate() override { delete m_model; } + SnippetsSettingsWidget(); - void configureUi(QWidget *parent); - - void apply(); - void finish(); - - QPointer m_widget; + void apply() final; + void finish() final; private: void loadSnippetGroup(int index); @@ -279,9 +273,9 @@ private: bool settingsChanged() const; void writeSettings(); - const QString m_settingsPrefix; - SnippetsTableModel *m_model; - bool m_snippetsCollectionChanged; + const QString m_settingsPrefix{QLatin1String("Text")}; + SnippetsTableModel m_model; + bool m_snippetsCollectionChanged = false; SnippetsSettings m_settings; QStackedWidget *m_snippetsEditorStack; @@ -290,38 +284,22 @@ private: QPushButton *m_revertButton; }; -SnippetsSettingsPagePrivate::SnippetsSettingsPagePrivate() : - m_settingsPrefix(QLatin1String("Text")), - m_model(new SnippetsTableModel(nullptr)), - m_snippetsCollectionChanged(false) -{} - -SnippetEditorWidget *SnippetsSettingsPagePrivate::currentEditor() const -{ - return editorAt(m_snippetsEditorStack->currentIndex()); -} - -SnippetEditorWidget *SnippetsSettingsPagePrivate::editorAt(int i) const -{ - return static_cast(m_snippetsEditorStack->widget(i)); -} - -void SnippetsSettingsPagePrivate::configureUi(QWidget *w) +SnippetsSettingsWidget::SnippetsSettingsWidget() { m_groupCombo = new QComboBox; m_snippetsEditorStack = new QStackedWidget; for (const SnippetProvider &provider : SnippetProvider::snippetProviders()) { m_groupCombo->addItem(provider.displayName(), provider.groupId()); - auto snippetEditor = new SnippetEditorWidget(w); + auto snippetEditor = new SnippetEditorWidget(this); SnippetProvider::decorateEditor(snippetEditor, provider.groupId()); m_snippetsEditorStack->insertWidget(m_groupCombo->count() - 1, snippetEditor); connect(snippetEditor, &SnippetEditorWidget::snippetContentChanged, - this, &SnippetsSettingsPagePrivate::setSnippetContent); + this, &SnippetsSettingsWidget::setSnippetContent); } m_snippetsTable = new Utils::TreeView; m_snippetsTable->setRootIsDecorated(false); - m_snippetsTable->setModel(m_model); + m_snippetsTable->setModel(&m_model); m_revertButton = new QPushButton(Tr::tr("Revert Built-in")); m_revertButton->setEnabled(false); @@ -332,7 +310,7 @@ void SnippetsSettingsPagePrivate::configureUi(QWidget *w) snippetSplitter->addWidget(m_snippetsTable); snippetSplitter->addWidget(m_snippetsEditorStack); - using namespace Utils::Layouting; + using namespace Layouting; Column { Row { Tr::tr("Group:"), m_groupCombo, st }, Row { @@ -350,40 +328,50 @@ void SnippetsSettingsPagePrivate::configureUi(QWidget *w) st, } } - }.attachTo(w); + }.attachTo(this); loadSettings(); loadSnippetGroup(m_groupCombo->currentIndex()); - connect(m_model, &QAbstractItemModel::rowsInserted, - this, &SnippetsSettingsPagePrivate::selectSnippet); - connect(m_model, &QAbstractItemModel::rowsInserted, - this, &SnippetsSettingsPagePrivate::markSnippetsCollection); - connect(m_model, &QAbstractItemModel::rowsRemoved, - this, &SnippetsSettingsPagePrivate::markSnippetsCollection); - connect(m_model, &QAbstractItemModel::rowsMoved, - this, &SnippetsSettingsPagePrivate::selectMovedSnippet); - connect(m_model, &QAbstractItemModel::rowsMoved, - this, &SnippetsSettingsPagePrivate::markSnippetsCollection); - connect(m_model, &QAbstractItemModel::dataChanged, - this, &SnippetsSettingsPagePrivate::markSnippetsCollection); - connect(m_model, &QAbstractItemModel::modelReset, + connect(&m_model, &QAbstractItemModel::rowsInserted, + this, &SnippetsSettingsWidget::selectSnippet); + connect(&m_model, &QAbstractItemModel::rowsInserted, + this, &SnippetsSettingsWidget::markSnippetsCollection); + connect(&m_model, &QAbstractItemModel::rowsRemoved, + this, &SnippetsSettingsWidget::markSnippetsCollection); + connect(&m_model, &QAbstractItemModel::rowsMoved, + this, &SnippetsSettingsWidget::selectMovedSnippet); + connect(&m_model, &QAbstractItemModel::rowsMoved, + this, &SnippetsSettingsWidget::markSnippetsCollection); + connect(&m_model, &QAbstractItemModel::dataChanged, + this, &SnippetsSettingsWidget::markSnippetsCollection); + connect(&m_model, &QAbstractItemModel::modelReset, this, [this] { this->updateCurrentSnippetDependent(); }); - connect(m_model, &QAbstractItemModel::modelReset, - this, &SnippetsSettingsPagePrivate::markSnippetsCollection); + connect(&m_model, &QAbstractItemModel::modelReset, + this, &SnippetsSettingsWidget::markSnippetsCollection); connect(m_groupCombo, &QComboBox::currentIndexChanged, - this, &SnippetsSettingsPagePrivate::loadSnippetGroup); + this, &SnippetsSettingsWidget::loadSnippetGroup); connect(m_revertButton, &QAbstractButton::clicked, - this, &SnippetsSettingsPagePrivate::revertBuiltInSnippet); + this, &SnippetsSettingsWidget::revertBuiltInSnippet); connect(m_snippetsTable->selectionModel(), &QItemSelectionModel::currentChanged, - this, &SnippetsSettingsPagePrivate::updateCurrentSnippetDependent); + this, &SnippetsSettingsWidget::updateCurrentSnippetDependent); connect(TextEditorSettings::instance(), &TextEditorSettings::fontSettingsChanged, - this, &SnippetsSettingsPagePrivate::decorateEditors); + this, &SnippetsSettingsWidget::decorateEditors); } -void SnippetsSettingsPagePrivate::apply() +SnippetEditorWidget *SnippetsSettingsWidget::currentEditor() const +{ + return editorAt(m_snippetsEditorStack->currentIndex()); +} + +SnippetEditorWidget *SnippetsSettingsWidget::editorAt(int i) const +{ + return static_cast(m_snippetsEditorStack->widget(i)); +} + +void SnippetsSettingsWidget::apply() { if (settingsChanged()) writeSettings(); @@ -402,7 +390,7 @@ void SnippetsSettingsPagePrivate::apply() } } -void SnippetsSettingsPagePrivate::finish() +void SnippetsSettingsWidget::finish() { if (m_snippetsCollectionChanged) { SnippetsCollection::instance()->reload(); @@ -412,7 +400,7 @@ void SnippetsSettingsPagePrivate::finish() disconnect(TextEditorSettings::instance(), nullptr, this, nullptr); } -void SnippetsSettingsPagePrivate::loadSettings() +void SnippetsSettingsWidget::loadSettings() { if (m_groupCombo->count() == 0) return; @@ -426,7 +414,7 @@ void SnippetsSettingsPagePrivate::loadSettings() m_groupCombo->setCurrentIndex(0); } -void SnippetsSettingsPagePrivate::writeSettings() +void SnippetsSettingsWidget::writeSettings() { if (m_groupCombo->count() == 0) return; @@ -435,72 +423,72 @@ void SnippetsSettingsPagePrivate::writeSettings() m_settings.toSettings(m_settingsPrefix, Core::ICore::settings()); } -bool SnippetsSettingsPagePrivate::settingsChanged() const +bool SnippetsSettingsWidget::settingsChanged() const { if (m_settings.lastUsedSnippetGroup() != m_groupCombo->currentText()) return true; return false; } -void SnippetsSettingsPagePrivate::loadSnippetGroup(int index) +void SnippetsSettingsWidget::loadSnippetGroup(int index) { if (index == -1) return; m_snippetsEditorStack->setCurrentIndex(index); currentEditor()->clear(); - m_model->load(m_groupCombo->itemData(index).toString()); + m_model.load(m_groupCombo->itemData(index).toString()); } -void SnippetsSettingsPagePrivate::markSnippetsCollection() +void SnippetsSettingsWidget::markSnippetsCollection() { if (!m_snippetsCollectionChanged) m_snippetsCollectionChanged = true; } -void SnippetsSettingsPagePrivate::addSnippet() +void SnippetsSettingsWidget::addSnippet() { - const QModelIndex &modelIndex = m_model->createSnippet(); + const QModelIndex &modelIndex = m_model.createSnippet(); selectSnippet(QModelIndex(), modelIndex.row()); m_snippetsTable->edit(modelIndex); } -void SnippetsSettingsPagePrivate::removeSnippet() +void SnippetsSettingsWidget::removeSnippet() { const QModelIndex &modelIndex = m_snippetsTable->selectionModel()->currentIndex(); if (!modelIndex.isValid()) { QMessageBox::critical(Core::ICore::dialogParent(), Tr::tr("Error"), Tr::tr("No snippet selected.")); return; } - m_model->removeSnippet(modelIndex); + m_model.removeSnippet(modelIndex); } -void SnippetsSettingsPagePrivate::restoreRemovedBuiltInSnippets() +void SnippetsSettingsWidget::restoreRemovedBuiltInSnippets() { - m_model->restoreRemovedBuiltInSnippets(); + m_model.restoreRemovedBuiltInSnippets(); } -void SnippetsSettingsPagePrivate::revertBuiltInSnippet() +void SnippetsSettingsWidget::revertBuiltInSnippet() { - m_model->revertBuitInSnippet(m_snippetsTable->selectionModel()->currentIndex()); + m_model.revertBuitInSnippet(m_snippetsTable->selectionModel()->currentIndex()); } -void SnippetsSettingsPagePrivate::resetAllSnippets() +void SnippetsSettingsWidget::resetAllSnippets() { - m_model->resetSnippets(); + m_model.resetSnippets(); } -void SnippetsSettingsPagePrivate::selectSnippet(const QModelIndex &parent, int row) +void SnippetsSettingsWidget::selectSnippet(const QModelIndex &parent, int row) { - QModelIndex topLeft = m_model->index(row, 0, parent); - QModelIndex bottomRight = m_model->index(row, 1, parent); + QModelIndex topLeft = m_model.index(row, 0, parent); + QModelIndex bottomRight = m_model.index(row, 1, parent); QItemSelection selection(topLeft, bottomRight); m_snippetsTable->selectionModel()->select(selection, QItemSelectionModel::SelectCurrent); m_snippetsTable->setCurrentIndex(topLeft); m_snippetsTable->scrollTo(topLeft); } -void SnippetsSettingsPagePrivate::selectMovedSnippet(const QModelIndex &, +void SnippetsSettingsWidget::selectMovedSnippet(const QModelIndex &, int sourceRow, int, const QModelIndex &destinationParent, @@ -508,17 +496,17 @@ void SnippetsSettingsPagePrivate::selectMovedSnippet(const QModelIndex &, { QModelIndex modelIndex; if (sourceRow < destinationRow) - modelIndex = m_model->index(destinationRow - 1, 0, destinationParent); + modelIndex = m_model.index(destinationRow - 1, 0, destinationParent); else - modelIndex = m_model->index(destinationRow, 0, destinationParent); + modelIndex = m_model.index(destinationRow, 0, destinationParent); m_snippetsTable->scrollTo(modelIndex); - currentEditor()->setPlainText(m_model->snippetAt(modelIndex).content()); + currentEditor()->setPlainText(m_model.snippetAt(modelIndex).content()); } -void SnippetsSettingsPagePrivate::updateCurrentSnippetDependent(const QModelIndex &modelIndex) +void SnippetsSettingsWidget::updateCurrentSnippetDependent(const QModelIndex &modelIndex) { if (modelIndex.isValid()) { - const Snippet &snippet = m_model->snippetAt(modelIndex); + const Snippet &snippet = m_model.snippetAt(modelIndex); currentEditor()->setPlainText(snippet.content()); m_revertButton->setEnabled(snippet.isBuiltIn()); } else { @@ -527,16 +515,16 @@ void SnippetsSettingsPagePrivate::updateCurrentSnippetDependent(const QModelInde } } -void SnippetsSettingsPagePrivate::setSnippetContent() +void SnippetsSettingsWidget::setSnippetContent() { const QModelIndex &modelIndex = m_snippetsTable->selectionModel()->currentIndex(); if (modelIndex.isValid()) { - m_model->setSnippetContent(modelIndex, currentEditor()->toPlainText()); + m_model.setSnippetContent(modelIndex, currentEditor()->toPlainText()); markSnippetsCollection(); } } -void SnippetsSettingsPagePrivate::decorateEditors(const TextEditor::FontSettings &fontSettings) +void SnippetsSettingsWidget::decorateEditors(const TextEditor::FontSettings &fontSettings) { for (int i = 0; i < m_groupCombo->count(); ++i) { SnippetEditorWidget *snippetEditor = editorAt(i); @@ -550,41 +538,13 @@ void SnippetsSettingsPagePrivate::decorateEditors(const TextEditor::FontSettings // SnippetsSettingsPage SnippetsSettingsPage::SnippetsSettingsPage() - : d(new SnippetsSettingsPagePrivate) { setId(Constants::TEXT_EDITOR_SNIPPETS_SETTINGS); setDisplayName(Tr::tr("Snippets")); setCategory(TextEditor::Constants::TEXT_EDITOR_SETTINGS_CATEGORY); setDisplayCategory(Tr::tr("Text Editor")); setCategoryIconPath(TextEditor::Constants::TEXT_EDITOR_SETTINGS_CATEGORY_ICON_PATH); + setWidgetCreator([] { return new SnippetsSettingsWidget; }); } -SnippetsSettingsPage::~SnippetsSettingsPage() -{ - delete d; -} - -QWidget *SnippetsSettingsPage::widget() -{ - if (!d->m_widget) { - d->m_widget = new QWidget; - d->configureUi(d->m_widget); - } - return d->m_widget; -} - -void SnippetsSettingsPage::apply() -{ - d->apply(); -} - -void SnippetsSettingsPage::finish() -{ - d->finish(); - delete d->m_widget; -} - -} // Internal -} // TextEditor - -#include "snippetssettingspage.moc" +} // TextEditor::Internal diff --git a/src/plugins/texteditor/snippets/snippetssettingspage.h b/src/plugins/texteditor/snippets/snippetssettingspage.h index f7c4b8c3ebb..31216d3d304 100644 --- a/src/plugins/texteditor/snippets/snippetssettingspage.h +++ b/src/plugins/texteditor/snippets/snippetssettingspage.h @@ -5,24 +5,12 @@ #include -namespace TextEditor { -namespace Internal { - -class SnippetsSettingsPagePrivate; +namespace TextEditor::Internal { class SnippetsSettingsPage final : public Core::IOptionsPage { public: SnippetsSettingsPage(); - ~SnippetsSettingsPage() override; - - QWidget *widget() override; - void apply() override; - void finish() override; - -private: - SnippetsSettingsPagePrivate *d; }; -} // Internal -} // TextEditor +} // TextEditor::Internal diff --git a/src/plugins/texteditor/syntaxhighlighter.cpp b/src/plugins/texteditor/syntaxhighlighter.cpp index de72c7b0db6..6d2616193ef 100644 --- a/src/plugins/texteditor/syntaxhighlighter.cpp +++ b/src/plugins/texteditor/syntaxhighlighter.cpp @@ -23,8 +23,12 @@ class SyntaxHighlighterPrivate Q_DECLARE_PUBLIC(SyntaxHighlighter) public: SyntaxHighlighterPrivate() + : SyntaxHighlighterPrivate(TextEditorSettings::fontSettings()) + { } + + SyntaxHighlighterPrivate(const FontSettings &fontSettings) { - updateFormats(TextEditorSettings::fontSettings()); + updateFormats(fontSettings); } QPointer doc; @@ -76,6 +80,16 @@ void SyntaxHighlighter::delayedRehighlight() rehighlight(); } +#ifdef WITH_TESTS +SyntaxHighlighter::SyntaxHighlighter(QTextDocument *parent, const FontSettings &fontsettings) + : QObject(parent), d_ptr(new SyntaxHighlighterPrivate(fontsettings)) +{ + d_ptr->q_ptr = this; + if (parent) + setDocument(parent); +} +#endif + void SyntaxHighlighterPrivate::applyFormatChanges(int from, int charsRemoved, int charsAdded) { bool formatsChanged = false; diff --git a/src/plugins/texteditor/syntaxhighlighter.h b/src/plugins/texteditor/syntaxhighlighter.h index 7a1822e4667..1b66e2ff56a 100644 --- a/src/plugins/texteditor/syntaxhighlighter.h +++ b/src/plugins/texteditor/syntaxhighlighter.h @@ -91,6 +91,11 @@ private: void delayedRehighlight(); QScopedPointer d_ptr; + +#ifdef WITH_TESTS + friend class tst_highlighter; + SyntaxHighlighter(QTextDocument *parent, const FontSettings &fontsettings); +#endif }; } // namespace TextEditor diff --git a/src/plugins/texteditor/tabsettingswidget.cpp b/src/plugins/texteditor/tabsettingswidget.cpp index f55d8d6f441..f3476ec5f42 100644 --- a/src/plugins/texteditor/tabsettingswidget.cpp +++ b/src/plugins/texteditor/tabsettingswidget.cpp @@ -50,7 +50,6 @@ QString continuationTooltip() TabSettingsWidget::TabSettingsWidget(QWidget *parent) : QGroupBox(parent) { - resize(254, 189); setTitle(Tr::tr("Tabs And Indentation")); m_codingStyleWarning = new QLabel( @@ -87,7 +86,7 @@ TabSettingsWidget::TabSettingsWidget(QWidget *parent) : tabSizeLabel->setBuddy(m_tabSize); indentSizeLabel->setBuddy(m_indentSize); - using namespace Utils::Layouting; + using namespace Layouting; const auto indent = [](QWidget *inner) { return Row { Space(30), inner }; }; Column { diff --git a/src/plugins/texteditor/textdocument.cpp b/src/plugins/texteditor/textdocument.cpp index 61c3b319b36..6276d731d06 100644 --- a/src/plugins/texteditor/textdocument.cpp +++ b/src/plugins/texteditor/textdocument.cpp @@ -373,6 +373,16 @@ QAction *TextDocument::createDiffAgainstCurrentFileAction( return diffAction; } +void TextDocument::insertSuggestion(std::unique_ptr &&suggestion) +{ + QTextCursor cursor(&d->m_document); + cursor.setPosition(suggestion->position()); + const QTextBlock block = cursor.block(); + TextDocumentLayout::userData(block)->insertSuggestion(std::move(suggestion)); + TextDocumentLayout::updateSuggestionFormats(block, fontSettings()); + updateLayout(); +} + #ifdef WITH_TESTS void TextDocument::setSilentReload() { @@ -419,6 +429,12 @@ IAssistProvider *TextDocument::quickFixAssistProvider() const void TextDocument::applyFontSettings() { d->m_fontSettingsNeedsApply = false; + QTextBlock block = document()->firstBlock(); + while (block.isValid()) { + TextDocumentLayout::updateSuggestionFormats(block, fontSettings()); + block = block.next(); + } + updateLayout(); if (d->m_highlighter) { d->m_highlighter->setFontSettings(d->m_fontSettings); d->m_highlighter->rehighlight(); @@ -820,15 +836,14 @@ bool TextDocument::reload(QString *errorString, const FilePath &realFilePath) emit aboutToReload(); auto documentLayout = qobject_cast(d->m_document.documentLayout()); - TextMarks marks; if (documentLayout) - marks = documentLayout->documentClosing(); // removes text marks non-permanently + documentLayout->documentAboutToReload(); // removes text marks non-permanently bool success = openImpl(errorString, filePath(), realFilePath, /*reload =*/true) == OpenResult::Success; if (documentLayout) - documentLayout->documentReloaded(marks, this); // re-adds text marks + documentLayout->documentReloaded(this); // re-adds text marks emit reloadFinished(success); return success; } diff --git a/src/plugins/texteditor/textdocument.h b/src/plugins/texteditor/textdocument.h index dc52e1e9a8e..d1f686f6a0c 100644 --- a/src/plugins/texteditor/textdocument.h +++ b/src/plugins/texteditor/textdocument.h @@ -37,6 +37,7 @@ class SyntaxHighlighter; class TabSettings; class TextDocumentPrivate; class TextMark; +class TextSuggestion; class TypingSettings; using TextMarks = QList; @@ -144,6 +145,9 @@ public: static QAction *createDiffAgainstCurrentFileAction(QObject *parent, const std::function &filePath); + void insertSuggestion(const QString &text, const QTextCursor &cursor); + void insertSuggestion(std::unique_ptr &&suggestion); + #ifdef WITH_TESTS void setSilentReload(); #endif diff --git a/src/plugins/texteditor/textdocumentlayout.cpp b/src/plugins/texteditor/textdocumentlayout.cpp index 1f26530dbb0..703f9563bf1 100644 --- a/src/plugins/texteditor/textdocumentlayout.cpp +++ b/src/plugins/texteditor/textdocumentlayout.cpp @@ -345,6 +345,21 @@ void TextBlockUserData::setCodeFormatterData(CodeFormatterData *data) m_codeFormatterData = data; } +void TextBlockUserData::insertSuggestion(std::unique_ptr &&suggestion) +{ + m_suggestion = std::move(suggestion); +} + +TextSuggestion *TextBlockUserData::suggestion() const +{ + return m_suggestion.get(); +} + +void TextBlockUserData::clearSuggestion() +{ + m_suggestion.reset(); +} + void TextBlockUserData::addMark(TextMark *mark) { int i = 0; @@ -355,7 +370,6 @@ void TextBlockUserData::addMark(TextMark *mark) m_marks.insert(i, mark); } - TextDocumentLayout::TextDocumentLayout(QTextDocument *doc) : QPlainTextDocumentLayout(doc) {} @@ -519,6 +533,81 @@ QByteArray TextDocumentLayout::expectedRawStringSuffix(const QTextBlock &block) return {}; } +TextSuggestion *TextDocumentLayout::suggestion(const QTextBlock &block) +{ + if (TextBlockUserData *userData = textUserData(block)) + return userData->suggestion(); + return nullptr; +} + +void TextDocumentLayout::updateSuggestionFormats(const QTextBlock &block, + const FontSettings &fontSettings) +{ + if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(block)) { + QTextDocument *suggestionDoc = suggestion->document(); + const QTextCharFormat replacementFormat = fontSettings.toTextCharFormat( + TextStyles{C_TEXT, {C_DISABLED_CODE}}); + QList formats = block.layout()->formats(); + QTextCursor cursor(suggestionDoc); + cursor.select(QTextCursor::Document); + cursor.setCharFormat(fontSettings.toTextCharFormat(C_TEXT)); + const int position = suggestion->currentPosition() - block.position(); + cursor.setPosition(position); + const QString trailingText = block.text().mid(position); + if (!trailingText.isEmpty()) { + const int trailingIndex = suggestionDoc->firstBlock().text().indexOf(trailingText, + position); + if (trailingIndex >= 0) { + cursor.setPosition(trailingIndex, QTextCursor::KeepAnchor); + cursor.setCharFormat(replacementFormat); + cursor.setPosition(trailingIndex + trailingText.size()); + const int length = std::max(trailingIndex - position, 0); + if (length) { + // we have a replacement in the middle of the line adjust all formats that are + // behind the replacement + QTextLayout::FormatRange rest; + rest.start = -1; + for (QTextLayout::FormatRange &range : formats) { + if (range.start >= position) { + range.start += length; + } else if (range.start + range.length > position) { + // the format range starts before and ends after the position so we need to + // split the format into before and after the suggestion format ranges + rest.start = trailingIndex; + rest.length = range.length - (position - range.start); + rest.format = range.format; + range.length = position - range.start; + } + } + if (rest.start >= 0) + formats += rest; + } + } + } + cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); + cursor.setCharFormat(replacementFormat); + suggestionDoc->firstBlock().layout()->setFormats(formats); + } +} + +bool TextDocumentLayout::updateSuggestion(const QTextBlock &block, + int position, + const FontSettings &fontSettings) +{ + if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(block)) { + auto positionInBlock = position - block.position(); + const QString start = block.text().left(positionInBlock); + const QString end = block.text().mid(positionInBlock); + const QString replacement = suggestion->document()->firstBlock().text(); + if (replacement.startsWith(start) && replacement.indexOf(end, start.size()) >= 0) { + suggestion->setCurrentPosition(position); + TextDocumentLayout::updateSuggestionFormats(block, fontSettings); + return true; + } + } + return false; +} + void TextDocumentLayout::requestExtraAreaUpdate() { emit updateExtraArea(); @@ -567,6 +656,7 @@ QSizeF TextDocumentLayout::documentSize() const TextMarks TextDocumentLayout::documentClosing() { + QTC_ASSERT(m_reloadMarks.isEmpty(), resetReloadMarks()); TextMarks marks; for (QTextBlock block = document()->begin(); block.isValid(); block = block.next()) { if (auto data = static_cast(block.userData())) @@ -575,9 +665,18 @@ TextMarks TextDocumentLayout::documentClosing() return marks; } -void TextDocumentLayout::documentReloaded(TextMarks marks, TextDocument *baseTextDocument) +void TextDocumentLayout::documentAboutToReload() { - for (TextMark *mark : std::as_const(marks)) { + m_reloadMarks = documentClosing(); + for (TextMark *mark : std::as_const(m_reloadMarks)) + mark->setDeleteCallback([this, mark] { m_reloadMarks.removeOne(mark); }); +} + +void TextDocumentLayout::documentReloaded(TextDocument *baseTextDocument) +{ + const TextMarks marks = m_reloadMarks; + resetReloadMarks(); + for (TextMark *mark : marks) { int blockNumber = mark->lineNumber() - 1; QTextBlock block = document()->findBlockByNumber(blockNumber); if (block.isValid()) { @@ -632,8 +731,37 @@ void TextDocumentLayout::requestUpdateNow() requestUpdate(); } +void TextDocumentLayout::resetReloadMarks() +{ + for (TextMark *mark : std::as_const(m_reloadMarks)) + mark->setDeleteCallback({}); + m_reloadMarks.clear(); +} + +static QRectF replacementBoundingRect(const QTextDocument *replacement) +{ + QTC_ASSERT(replacement, return {}); + auto *layout = static_cast(replacement->documentLayout()); + QRectF boundingRect; + QTextBlock block = replacement->firstBlock(); + while (block.isValid()) { + const QRectF blockBoundingRect = layout->blockBoundingRect(block); + boundingRect.setWidth(std::max(boundingRect.width(), blockBoundingRect.width())); + boundingRect.setHeight(boundingRect.height() + blockBoundingRect.height()); + block = block.next(); + } + return boundingRect; +} + QRectF TextDocumentLayout::blockBoundingRect(const QTextBlock &block) const { + if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(block)) { + // since multiple code paths expects that we have a valid block layout after requesting the + // block bounding rect explicitly create that layout here + ensureBlockLayout(block); + return replacementBoundingRect(suggestion->document()); + } + QRectF boundingRect = QPlainTextDocumentLayout::blockBoundingRect(block); if (TextEditorSettings::fontSettings().relativeLineSpacing() != 100) { @@ -722,4 +850,12 @@ void insertSorted(Parentheses &list, const Parenthesis &elem) list.insert(it, elem); } +TextSuggestion::TextSuggestion() +{ + m_replacementDocument.setDocumentLayout(new TextDocumentLayout(&m_replacementDocument)); + m_replacementDocument.setDocumentMargin(0); +} + +TextSuggestion::~TextSuggestion() = default; + } // namespace TextEditor diff --git a/src/plugins/texteditor/textdocumentlayout.h b/src/plugins/texteditor/textdocumentlayout.h index ba31398db7b..33387093da7 100644 --- a/src/plugins/texteditor/textdocumentlayout.h +++ b/src/plugins/texteditor/textdocumentlayout.h @@ -17,8 +17,9 @@ namespace TextEditor { -struct TEXTEDITOR_EXPORT Parenthesis +class TEXTEDITOR_EXPORT Parenthesis { +public: enum Type : char { Opened, Closed }; Parenthesis() = default; @@ -42,6 +43,28 @@ public: virtual ~CodeFormatterData(); }; +class TEXTEDITOR_EXPORT TextSuggestion +{ +public: + TextSuggestion(); + virtual ~TextSuggestion(); + // Returns true if the suggestion was applied completely, false if it was only partially applied. + virtual bool apply() = 0; + // Returns true if the suggestion was applied completely, false if it was only partially applied. + virtual bool applyWord(TextEditorWidget *widget) = 0; + virtual void reset() = 0; + virtual int position() = 0; + + int currentPosition() const { return m_currentPosition; } + void setCurrentPosition(int position) { m_currentPosition = position; } + + QTextDocument *document() { return &m_replacementDocument; } + +private: + QTextDocument m_replacementDocument; + int m_currentPosition = -1; +}; + class TEXTEDITOR_EXPORT TextBlockUserData : public QTextBlockUserData { public: @@ -126,6 +149,10 @@ public: QByteArray expectedRawStringSuffix() { return m_expectedRawStringSuffix; } void setExpectedRawStringSuffix(const QByteArray &suffix) { m_expectedRawStringSuffix = suffix; } + void insertSuggestion(std::unique_ptr &&suggestion); + TextSuggestion *suggestion() const; + void clearSuggestion(); + private: TextMarks m_marks; int m_foldingIndent : 16; @@ -139,9 +166,10 @@ private: CodeFormatterData *m_codeFormatterData; KSyntaxHighlighting::State m_syntaxState; QByteArray m_expectedRawStringSuffix; // A bit C++-specific, but let's be pragmatic. + std::unique_ptr m_replacement; + std::unique_ptr m_suggestion; }; - class TEXTEDITOR_EXPORT TextDocumentLayout : public QPlainTextDocumentLayout { Q_OBJECT @@ -172,6 +200,12 @@ public: static void setFolded(const QTextBlock &block, bool folded); static void setExpectedRawStringSuffix(const QTextBlock &block, const QByteArray &suffix); static QByteArray expectedRawStringSuffix(const QTextBlock &block); + static TextSuggestion *suggestion(const QTextBlock &block); + static void updateSuggestionFormats(const QTextBlock &block, + const FontSettings &fontSettings); + static bool updateSuggestion(const QTextBlock &block, + int position, + const FontSettings &fontSettings); class TEXTEDITOR_EXPORT FoldValidator { @@ -212,7 +246,8 @@ public: QRectF blockBoundingRect(const QTextBlock &block) const override; TextMarks documentClosing(); - void documentReloaded(TextMarks marks, TextDocument *baseextDocument); + void documentAboutToReload(); + void documentReloaded(TextDocument *baseextDocument); void updateMarksLineNumber(); void updateMarksBlock(const QTextBlock &block); void scheduleUpdate(); @@ -220,6 +255,8 @@ public: private: bool m_updateScheduled = false; + TextMarks m_reloadMarks; + void resetReloadMarks(); signals: void updateExtraArea(); diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index 17cabc38911..ecb617aed70 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -61,6 +61,7 @@ #include #include #include +#include #include #include #include @@ -657,6 +658,7 @@ public: uint m_optionalActionMask = TextEditorActionHandler::None; bool m_contentsChanged = false; bool m_lastCursorChangeWasInteresting = false; + std::shared_ptr m_suggestionBlocker; QSharedPointer m_document; QList m_documentConnections; @@ -789,7 +791,7 @@ public: QScopedPointer m_autoCompleter; CommentDefinition m_commentDefinition; - QFutureWatcher *m_searchWatcher = nullptr; + QFutureWatcher *m_searchWatcher = nullptr; QVector m_searchResults; QTimer m_scrollBarUpdateTimer; HighlightScrollBarController *m_highlightScrollBarController = nullptr; @@ -819,6 +821,11 @@ public: QStack m_undoCursorStack; QList m_visualIndentCache; int m_visualIndentOffset = 0; + + void insertSuggestion(std::unique_ptr &&suggestion); + void updateSuggestion(); + void clearCurrentSuggestion(); + QTextBlock m_suggestionBlock; }; class TextEditorWidgetFind : public BaseTextFind @@ -840,10 +847,27 @@ public: private: TextEditorWidget * const m_editor; - static QFutureWatcher *m_selectWatcher; + static QFutureWatcher *m_selectWatcher; }; -QFutureWatcher *TextEditorWidgetFind::m_selectWatcher = nullptr; +static QTextCursor selectRange(QTextDocument *textDocument, const Text::Range &range, + TextEditorWidgetPrivate::SearchResult *searchResult = nullptr) +{ + const int startLine = qMax(range.begin.line - 1, 0); + const int startColumn = qMax(range.begin.column, 0); + const int endLine = qMax(range.end.line - 1, 0); + const int endColumn = qMax(range.end.column, 0); + const int startPosition = textDocument->findBlockByNumber(startLine).position() + startColumn; + const int endPosition = textDocument->findBlockByNumber(endLine).position() + endColumn; + QTextCursor textCursor(textDocument); + textCursor.setPosition(startPosition); + textCursor.setPosition(endPosition, QTextCursor::KeepAnchor); + if (searchResult) + *searchResult = {startPosition + 1, endPosition + 1}; + return textCursor; +} + +QFutureWatcher *TextEditorWidgetFind::m_selectWatcher = nullptr; void TextEditorWidgetFind::selectAll(const QString &txt, FindFlags findFlags) { @@ -852,26 +876,24 @@ void TextEditorWidgetFind::selectAll(const QString &txt, FindFlags findFlags) cancelCurrentSelectAll(); - m_selectWatcher = new QFutureWatcher(); - connect(m_selectWatcher, &QFutureWatcher::finished, - this, [this] { - const QFuture future = m_selectWatcher->future(); - m_selectWatcher->deleteLater(); - m_selectWatcher = nullptr; - if (future.resultCount() <= 0) - return; - const FileSearchResultList &results = future.result(); - const QTextCursor c(m_editor->document()); - auto cursorForResult = [c](const FileSearchResult &r) { - return Utils::Text::selectAt(c, r.lineNumber, r.matchStart + 1, r.matchLength); - }; - QList cursors = Utils::transform(results, cursorForResult); - cursors = Utils::filtered(cursors, [this](const QTextCursor &c) { - return m_editor->inFindScope(c); - }); - m_editor->setMultiTextCursor(MultiTextCursor(cursors)); - m_editor->setFocus(); - }); + m_selectWatcher = new QFutureWatcher(); + connect(m_selectWatcher, &QFutureWatcher::finished, this, [this] { + const QFuture future = m_selectWatcher->future(); + m_selectWatcher->deleteLater(); + m_selectWatcher = nullptr; + if (future.resultCount() <= 0) + return; + const SearchResultItems &results = future.result(); + const auto cursorForResult = [this](const SearchResultItem &item) { + return selectRange(m_editor->document(), item.mainRange()); + }; + QList cursors = Utils::transform(results, cursorForResult); + cursors = Utils::filtered(cursors, [this](const QTextCursor &c) { + return m_editor->inFindScope(c); + }); + m_editor->setMultiTextCursor(MultiTextCursor(cursors)); + m_editor->setFocus(); + }); const FilePath &fileName = m_editor->textDocument()->filePath(); QMap fileToContentsMap; @@ -900,6 +922,7 @@ void TextEditorWidgetFind::cancelCurrentSelectAll() TextEditorWidgetPrivate::TextEditorWidgetPrivate(TextEditorWidget *parent) : q(parent) + , m_suggestionBlocker((void *) this, [](void *) {}) , m_overlay(new TextEditorOverlay(q)) , m_snippetOverlay(new SnippetOverlay(q)) , m_searchResultOverlay(new TextEditorOverlay(q)) @@ -1646,6 +1669,43 @@ void TextEditorWidgetPrivate::handleMoveBlockSelection(QTextCursor::MoveOperatio q->setMultiTextCursor(MultiTextCursor(cursors)); } +void TextEditorWidgetPrivate::insertSuggestion(std::unique_ptr &&suggestion) +{ + clearCurrentSuggestion(); + + if (m_suggestionBlocker.use_count() > 1) + return; + + auto cursor = q->textCursor(); + cursor.setPosition(suggestion->position()); + m_suggestionBlock = cursor.block(); + m_document->insertSuggestion(std::move(suggestion)); +} + +void TextEditorWidgetPrivate::updateSuggestion() +{ + if (!m_suggestionBlock.isValid()) + return; + if (m_cursors.mainCursor().block() != m_suggestionBlock) { + clearCurrentSuggestion(); + } else { + if (!TextDocumentLayout::updateSuggestion(m_suggestionBlock, + m_cursors.mainCursor().position(), + m_document->fontSettings())) { + clearCurrentSuggestion(); + } + } +} + +void TextEditorWidgetPrivate::clearCurrentSuggestion() +{ + if (TextBlockUserData *userData = TextDocumentLayout::textUserData(m_suggestionBlock)) { + userData->clearSuggestion(); + m_document->updateLayout(); + } + m_suggestionBlock = QTextBlock(); +} + void TextEditorWidget::selectEncoding() { TextDocument *doc = d->m_document.data(); @@ -1828,6 +1888,8 @@ TextEditorWidget *TextEditorWidget::fromEditor(const IEditor *editor) void TextEditorWidgetPrivate::editorContentsChange(int position, int charsRemoved, int charsAdded) { + updateSuggestion(); + if (m_bracketsAnimator) m_bracketsAnimator->finish(); @@ -2308,6 +2370,11 @@ void TextEditorWidget::renameSymbolUnderCursor() emit requestRename(textCursor()); } +void TextEditorWidget::openCallHierarchy() +{ + emit requestCallHierarchy(textCursor()); +} + void TextEditorWidget::abortAssist() { d->m_codeAssistant.destroyContext(); @@ -2488,7 +2555,7 @@ void TextEditorWidget::keyPressEvent(QKeyEvent *e) { ICore::restartTrimmer(); - ExecuteOnDestruction eod([&]() { d->clearBlockSelection(); }); + auto clearBlockSelectionGuard = qScopeGuard([&]() { d->clearBlockSelection(); }); if (!isModifier(e) && mouseHidingEnabled()) viewport()->setCursor(Qt::BlankCursor); @@ -2506,6 +2573,11 @@ void TextEditorWidget::keyPressEvent(QKeyEvent *e) d->m_maybeFakeTooltipEvent = false; if (e->key() == Qt::Key_Escape ) { TextEditorWidgetFind::cancelCurrentSelectAll(); + if (d->m_suggestionBlock.isValid()) { + d->clearCurrentSuggestion(); + e->accept(); + return; + } if (d->m_snippetOverlay->isVisible()) { e->accept(); d->m_snippetOverlay->accept(); @@ -2527,6 +2599,21 @@ void TextEditorWidget::keyPressEvent(QKeyEvent *e) const bool inOverwriteMode = overwriteMode(); const bool hasMultipleCursors = cursor.hasMultipleCursors(); + if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(d->m_suggestionBlock)) { + if (e->matches(QKeySequence::MoveToNextWord)) { + e->accept(); + if (suggestion->applyWord(this)) + d->clearCurrentSuggestion(); + return; + } else if (e->modifiers() == Qt::NoModifier + && (e->key() == Qt::Key_Tab || e->key() == Qt::Key_Backtab)) { + e->accept(); + if (suggestion->apply()) + d->clearCurrentSuggestion(); + return; + } + } + if (!ro && (e == QKeySequence::InsertParagraphSeparator || (!d->m_lineSeparatorsAllowed && e == QKeySequence::InsertLineSeparator))) { @@ -2684,6 +2771,8 @@ void TextEditorWidget::keyPressEvent(QKeyEvent *e) | Qt::AltModifier | Qt::MetaModifier)) == Qt::NoModifier) { e->accept(); + if (d->m_suggestionBlock.isValid()) + d->clearCurrentSuggestion(); if (cursor.hasSelection()) { cursor.removeSelectedText(); setMultiTextCursor(cursor); @@ -2745,8 +2834,7 @@ void TextEditorWidget::keyPressEvent(QKeyEvent *e) } if (blockSelectionOperation != QTextCursor::NoMove) { - auto doNothing = [](){}; - eod.reset(doNothing); + clearBlockSelectionGuard.dismiss(); d->handleMoveBlockSelection(blockSelectionOperation); } else if (!d->cursorMoveKeyEvent(e)) { QTextCursor cursor = textCursor(); @@ -2834,13 +2922,13 @@ void TextEditorWidget::keyPressEvent(QKeyEvent *e) if (!autoText.isEmpty()) cursor.setPosition(autoText.length() == 1 ? cursor.position() : cursor.anchor()); + setTextCursor(cursor); + if (doEditBlock) { cursor.endEditBlock(); if (cursorWithinSnippet) d->m_snippetOverlay->updateEquivalentSelections(textCursor()); } - - setTextCursor(cursor); } if (!ro && e->key() == Qt::Key_Delete && d->m_parenthesesMatchingEnabled) @@ -3105,7 +3193,9 @@ bool TextEditorWidget::event(QEvent *e) case QEvent::ShortcutOverride: { auto ke = static_cast(e); if (ke->key() == Qt::Key_Escape - && (d->m_snippetOverlay->isVisible() || multiTextCursor().hasMultipleCursors())) { + && (d->m_snippetOverlay->isVisible() + || multiTextCursor().hasMultipleCursors() + || d->m_suggestionBlock.isValid())) { e->accept(); } else { // hack copied from QInputControl::isCommonTextEditShortcut @@ -3241,7 +3331,7 @@ void TextEditorWidget::restoreState(const QByteArray &state) d->m_lastCursorChangeWasInteresting = false; // avoid adding last position to history // line is 1-based, column is 0-based - gotoLine(lineVal, columnVal - 1); + gotoLine(lineVal, columnVal); verticalScrollBar()->setValue(vval); horizontalScrollBar()->setValue(hval); @@ -3676,7 +3766,10 @@ bool TextEditorWidget::viewportEvent(QEvent *event) // Only handle tool tip for text cursor if mouse is within the block for the text cursor, // and not if the mouse is e.g. in the empty space behind a short line. if (line.isValid()) { - if (pos.x() <= blockBoundingGeometry(block).left() + line.naturalTextRect().right()) { + const QRectF blockGeometry = blockBoundingGeometry(block); + const int width = block == d->m_suggestionBlock ? blockGeometry.width() + : line.naturalTextRect().right(); + if (pos.x() <= blockGeometry.left() + width) { d->processTooltipRequest(tc); return true; } else if (d->processAnnotaionTooltipRequest(block, pos)) { @@ -3999,7 +4092,13 @@ static TextMarks availableMarks(const TextMarks &marks, QRectF TextEditorWidgetPrivate::getLastLineLineRect(const QTextBlock &block) { - const QTextLayout *layout = block.layout(); + QTextLayout *layout = nullptr; + if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(block)) + layout = suggestion->document()->firstBlock().layout(); + else + layout = block.layout(); + + QTC_ASSERT(layout, layout = block.layout()); const int lineCount = layout->lineCount(); if (lineCount < 1) return {}; @@ -4394,15 +4493,28 @@ void TextEditorWidgetPrivate::paintAdditionalVisualWhitespaces(PaintEventData &d visualArrow); } if (!nextBlockIsValid) { // paint EOF symbol - QTextLine line = layout->lineAt(lineCount-1); + if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(data.block)) { + const QTextBlock lastReplacementBlock = suggestion->document()->lastBlock(); + for (QTextBlock block = suggestion->document()->firstBlock(); + block != lastReplacementBlock && block.isValid(); + block = block.next()) { + top += suggestion->document() + ->documentLayout() + ->blockBoundingRect(block) + .height(); + } + layout = lastReplacementBlock.layout(); + lineCount = layout->lineCount(); + } + QTextLine line = layout->lineAt(lineCount - 1); QRectF lineRect = line.naturalTextRect().translated(data.offset.x(), top); int h = 4; lineRect.adjust(0, 0, -1, -1); QPainterPath path; - QPointF pos(lineRect.topRight() + QPointF(h+4, line.ascent())); + QPointF pos(lineRect.topRight() + QPointF(h + 4, line.ascent())); path.moveTo(pos); path.lineTo(pos + QPointF(-h, -h)); - path.lineTo(pos + QPointF(0, -2*h)); + path.lineTo(pos + QPointF(0, -2 * h)); path.lineTo(pos + QPointF(h, -h)); path.closeSubpath(); painter.setBrush(painter.pen().color()); @@ -4649,6 +4761,21 @@ void TextEditorWidgetPrivate::setupSelections(const PaintEventData &data, PaintEventBlockData &blockData) const { QVector prioritySelections; + + int deltaPos = -1; + int delta = 0; + + if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(data.block)) { + deltaPos = suggestion->currentPosition() - data.block.position(); + const QString trailingText = data.block.text().mid(deltaPos); + if (!trailingText.isEmpty()) { + const int trailingIndex + = suggestion->document()->firstBlock().text().indexOf(trailingText, deltaPos); + if (trailingIndex >= 0) + delta = std::max(trailingIndex - deltaPos, 0); + } + } + for (int i = 0; i < data.context.selections.size(); ++i) { const QAbstractTextDocumentLayout::Selection &range = data.context.selections.at(i); const int selStart = range.cursor.selectionStart() - blockData.position; @@ -4658,6 +4785,22 @@ void TextEditorWidgetPrivate::setupSelections(const PaintEventData &data, QTextLayout::FormatRange o; o.start = selStart; o.length = selEnd - selStart; + o.format = range.format; + QTextLayout::FormatRange rest; + rest.start = -1; + if (deltaPos >= 0 && delta != 0) { + if (o.start >= deltaPos) { + o.start += delta; + } else if (o.start + o.length > deltaPos) { + // the format range starts before and ends after the position so we need to + // split the format into before and after the suggestion format ranges + rest.start = deltaPos + delta; + rest.length = o.length - (deltaPos - o.start); + rest.format = o.format; + o.length = deltaPos - o.start; + } + } + o.format = range.format; if (data.textCursor.hasSelection() && data.textCursor == range.cursor && data.textCursor.anchor() == range.cursor.anchor()) { @@ -4670,10 +4813,15 @@ void TextEditorWidgetPrivate::setupSelections(const PaintEventData &data, || (o.format.foreground().style() == Qt::NoBrush && o.format.underlineStyle() != QTextCharFormat::NoUnderline && o.format.background() == Qt::NoBrush)) { - if (q->selectionVisible(data.block.blockNumber())) + if (q->selectionVisible(data.block.blockNumber())) { prioritySelections.append(o); + if (rest.start >= 0) + prioritySelections.append(rest); + } } else { blockData.selections.append(o); + if (rest.start >= 0) + blockData.selections.append(rest); } } } @@ -4784,6 +4932,7 @@ void TextEditorWidget::paintEvent(QPaintEvent *e) if (blockData.boundingRect.bottom() >= data.eventRect.top() && blockData.boundingRect.top() <= data.eventRect.bottom()) { + data.documentLayout->ensureBlockLayout(data.block); d->setupBlockLayout(data, painter, blockData); blockData.position = data.block.position(); blockData.length = data.block.length(); @@ -4868,6 +5017,27 @@ void TextEditorWidget::paintBlock(QPainter *painter, const QVector &selections, const QRect &clipRect) const { + if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(block)) { + QTextBlock suggestionBlock = suggestion->document()->firstBlock(); + QPointF suggestionOffset = offset; + suggestionOffset.rx() += document()->documentMargin(); + while (suggestionBlock.isValid()) { + const QVector blockSelections + = suggestionBlock.blockNumber() == 0 ? selections + : QVector{}; + suggestionBlock.layout()->draw(painter, + suggestionOffset, + blockSelections, + clipRect); + suggestionOffset.ry() += suggestion->document() + ->documentLayout() + ->blockBoundingRect(suggestionBlock) + .height(); + suggestionBlock = suggestionBlock.next(); + } + return; + } + block.layout()->draw(painter, offset, selections, clipRect); } @@ -5407,6 +5577,7 @@ void TextEditorWidget::slotCursorPositionChanged() setMultiTextCursor(cursor); d->updateCursorSelections(); d->updateHighlights(); + d->updateSuggestion(); } void TextEditorWidgetPrivate::updateHighlights() @@ -5842,8 +6013,42 @@ void TextEditorWidget::addHoverHandler(BaseHoverHandler *handler) void TextEditorWidget::removeHoverHandler(BaseHoverHandler *handler) { - d->m_hoverHandlers.removeAll(handler); - d->m_hoverHandlerRunner.handlerRemoved(handler); + if (d->m_hoverHandlers.removeAll(handler) > 0) + d->m_hoverHandlerRunner.handlerRemoved(handler); +} + +void TextEditorWidget::insertSuggestion(std::unique_ptr &&suggestion) +{ + d->insertSuggestion(std::move(suggestion)); +} + +void TextEditorWidget::clearSuggestion() +{ + d->clearCurrentSuggestion(); +} + +TextSuggestion *TextEditorWidget::currentSuggestion() const +{ + if (d->m_suggestionBlock.isValid()) + return TextDocumentLayout::suggestion(d->m_suggestionBlock); + return nullptr; +} + +bool TextEditorWidget::suggestionVisible() const +{ + return currentSuggestion(); +} + +bool TextEditorWidget::suggestionsBlocked() const +{ + return d->m_suggestionBlocker.use_count() > 1; +} + +TextEditorWidget::SuggestionBlocker TextEditorWidget::blockSuggestions() +{ + if (!suggestionsBlocked()) + clearSuggestion(); + return d->m_suggestionBlocker; } #ifdef WITH_TESTS @@ -6482,16 +6687,11 @@ void TextEditorWidgetPrivate::searchResultsReady(int beginIndex, int endIndex) { QVector results; for (int index = beginIndex; index < endIndex; ++index) { - const FileSearchResultList resultList = m_searchWatcher->resultAt(index); - for (FileSearchResult result : resultList) { - const QTextBlock &block = q->document()->findBlockByNumber(result.lineNumber - 1); - const int matchStart = block.position() + result.matchStart; - QTextCursor cursor(block); - cursor.setPosition(matchStart); - cursor.setPosition(matchStart + result.matchLength, QTextCursor::KeepAnchor); - if (!q->inFindScope(cursor)) - continue; - results << SearchResult{matchStart, result.matchLength}; + const SearchResultItems resultList = m_searchWatcher->resultAt(index); + for (const SearchResultItem &result : resultList) { + SearchResult searchResult; + if (q->inFindScope(selectRange(q->document(), result.mainRange(), &searchResult))) + results << searchResult; } } m_searchResults << results; @@ -6537,10 +6737,10 @@ void TextEditorWidgetPrivate::highlightSearchResultsInScrollBar() adjustScrollBarRanges(); - m_searchWatcher = new QFutureWatcher(); - connect(m_searchWatcher, &QFutureWatcher::resultsReadyAt, + m_searchWatcher = new QFutureWatcher; + connect(m_searchWatcher, &QFutureWatcher::resultsReadyAt, this, &TextEditorWidgetPrivate::searchResultsReady); - connect(m_searchWatcher, &QFutureWatcher::finished, + connect(m_searchWatcher, &QFutureWatcher::finished, this, &TextEditorWidgetPrivate::searchFinished); m_searchWatcher->setPendingResultsLimit(10); @@ -6586,7 +6786,7 @@ void TextEditorWidgetPrivate::addSearchResultsToScrollBar(const QVectordocument()->findBlock(result.start); if (block.isValid() && block.isVisible()) { const int firstLine = block.layout()->lineForTextPosition(result.start - block.position()).lineNumber(); @@ -6639,10 +6839,11 @@ MultiTextCursor TextEditorWidget::multiTextCursor() const void TextEditorWidget::setMultiTextCursor(const Utils::MultiTextCursor &cursor) { + if (cursor == d->m_cursors) + return; + const MultiTextCursor oldCursor = d->m_cursors; const_cast(d->m_cursors) = cursor; - if (oldCursor == d->m_cursors) - return; doSetTextCursor(d->m_cursors.mainCursor(), /*keepMultiSelection*/ true); QRect updateRect = d->cursorUpdateRect(oldCursor); if (d->m_highlightCurrentLine) @@ -8068,6 +8269,11 @@ void TextEditorWidget::appendStandardContextMenuActions(QMenu *menu) if (!menu->actions().contains(findUsage)) menu->addAction(findUsage); } + if (optionalActions() & TextEditorActionHandler::CallHierarchy) { + const auto callHierarchy = ActionManager::command(Constants::OPEN_CALL_HIERARCHY)->action(); + if (!menu->actions().contains(callHierarchy)) + menu->addAction(callHierarchy); + } menu->addSeparator(); appendMenuActionsFromContext(menu, Constants::M_STANDARDCONTEXTMENU); diff --git a/src/plugins/texteditor/texteditor.h b/src/plugins/texteditor/texteditor.h index 98bddb84b91..e7cb5b34347 100644 --- a/src/plugins/texteditor/texteditor.h +++ b/src/plugins/texteditor/texteditor.h @@ -42,15 +42,16 @@ class HighlightScrollBarController; } namespace TextEditor { -class TextDocument; -class TextMark; -class BaseHoverHandler; -class RefactorOverlay; -class SyntaxHighlighter; class AssistInterface; +class BaseHoverHandler; +class CompletionAssistProvider; class IAssistProvider; class ICodeStylePreferences; -class CompletionAssistProvider; +class RefactorOverlay; +class SyntaxHighlighter; +class TextDocument; +class TextMark; +class TextSuggestion; using RefactorMarkers = QList; using TextMarks = QList; @@ -437,6 +438,7 @@ public: virtual void findUsages(); virtual void renameSymbolUnderCursor(); + virtual void openCallHierarchy(); /// Abort code assistant if it is running. void abortAssist(); @@ -469,6 +471,16 @@ public: void addHoverHandler(BaseHoverHandler *handler); void removeHoverHandler(BaseHoverHandler *handler); + void insertSuggestion(std::unique_ptr &&suggestion); + void clearSuggestion(); + TextSuggestion *currentSuggestion() const; + bool suggestionVisible() const; + bool suggestionsBlocked() const; + + using SuggestionBlocker = std::shared_ptr; + // Returns an object that blocks suggestions until it is destroyed. + SuggestionBlocker blockSuggestions(); + #ifdef WITH_TESTS void processTooltipRequest(const QTextCursor &c); #endif @@ -483,6 +495,7 @@ signals: bool resolveTarget, bool inNextSplit); void requestUsages(const QTextCursor &cursor); void requestRename(const QTextCursor &cursor); + void requestCallHierarchy(const QTextCursor &cursor); void optionalActionMaskChanged(); void toolbarOutlineChanged(QWidget *newOutline); diff --git a/src/plugins/texteditor/texteditor.qbs b/src/plugins/texteditor/texteditor.qbs index 971ef31faee..071e0b5fc2a 100644 --- a/src/plugins/texteditor/texteditor.qbs +++ b/src/plugins/texteditor/texteditor.qbs @@ -94,6 +94,8 @@ Project { "linenumberfilter.h", "marginsettings.cpp", "marginsettings.h", + "markdowneditor.cpp", + "markdowneditor.h", "outlinefactory.cpp", "outlinefactory.h", "plaintexteditorfactory.cpp", @@ -220,10 +222,10 @@ Project { ] } - Group { - name: "Tests" - condition: qtc.testsEnabled + QtcTestFiles { files: [ + "codeassist/codeassist_test.cpp", + "codeassist/codeassist_test.h", "texteditor_test.cpp", ] } diff --git a/src/plugins/texteditor/texteditor_test.cpp b/src/plugins/texteditor/texteditor_test.cpp index e939e515681..3b88ac5efb5 100644 --- a/src/plugins/texteditor/texteditor_test.cpp +++ b/src/plugins/texteditor/texteditor_test.cpp @@ -3,20 +3,13 @@ #ifdef WITH_TESTS -#include -#include -#include +#include "tabsettings.h" +#include "texteditorplugin.h" + +#include #include -#include -#include - -#include "texteditor.h" -#include "texteditorplugin.h" -#include "textdocument.h" -#include "tabsettings.h" - -using namespace TextEditor; +namespace TextEditor { QString tabPolicyToString(TabSettings::TabPolicy policy) { @@ -149,4 +142,6 @@ void Internal::TextEditorPlugin::testIndentationClean() QCOMPARE(settings.isIndentationClean(block, indentSize), clean); } +} // namespace TextEditor + #endif // ifdef WITH_TESTS diff --git a/src/plugins/texteditor/texteditoractionhandler.cpp b/src/plugins/texteditor/texteditoractionhandler.cpp index 25116729c99..00ed94a5ee7 100644 --- a/src/plugins/texteditor/texteditoractionhandler.cpp +++ b/src/plugins/texteditor/texteditoractionhandler.cpp @@ -116,6 +116,7 @@ public: QAction *m_followSymbolAction = nullptr; QAction *m_followSymbolInNextSplitAction = nullptr; QAction *m_findUsageAction = nullptr; + QAction *m_openCallHierarchyAction = nullptr; QAction *m_renameSymbolAction = nullptr; QAction *m_jumpToFileAction = nullptr; QAction *m_jumpToFileInNextSplitAction = nullptr; @@ -228,6 +229,8 @@ void TextEditorActionHandlerPrivate::createActions() m_jumpToFileInNextSplitAction = registerAction(JUMP_TO_FILE_UNDER_CURSOR_IN_NEXT_SPLIT, [] (TextEditorWidget *w) { w->openLinkUnderCursorInNextSplit(); }, true, Tr::tr("Jump to File Under Cursor in Next Split"), QKeySequence(Utils::HostOsInfo::isMacHost() ? Tr::tr("Meta+E, F2") : Tr::tr("Ctrl+E, F2")).toString()); + m_openCallHierarchyAction = registerAction(OPEN_CALL_HIERARCHY, + [] (TextEditorWidget *w) { w->openCallHierarchy(); }, true, Tr::tr("Open Call Hierarchy")); registerAction(VIEW_PAGE_UP, [] (TextEditorWidget *w) { w->viewPageUp(); }, true, Tr::tr("Move the View a Page Up and Keep the Cursor Position"), @@ -484,6 +487,8 @@ void TextEditorActionHandlerPrivate::updateOptionalActions() optionalActions & TextEditorActionHandler::UnCollapseAll); m_renameSymbolAction->setEnabled( optionalActions & TextEditorActionHandler::RenameSymbol); + m_openCallHierarchyAction->setEnabled( + optionalActions & TextEditorActionHandler::CallHierarchy); bool formatEnabled = (optionalActions & TextEditorActionHandler::Format) && m_currentEditorWidget && !m_currentEditorWidget->isReadOnly(); diff --git a/src/plugins/texteditor/texteditoractionhandler.h b/src/plugins/texteditor/texteditoractionhandler.h index 277c2cf66f4..2bdb8efff3e 100644 --- a/src/plugins/texteditor/texteditoractionhandler.h +++ b/src/plugins/texteditor/texteditoractionhandler.h @@ -36,7 +36,8 @@ public: FollowSymbolUnderCursor = 8, JumpToFileUnderCursor = 16, RenameSymbol = 32, - FindUsage = 64 + FindUsage = 64, + CallHierarchy = 128 }; using TextEditorWidgetResolver = std::function; diff --git a/src/plugins/texteditor/texteditorconstants.h b/src/plugins/texteditor/texteditorconstants.h index 0560dae9d82..f422630f0d2 100644 --- a/src/plugins/texteditor/texteditorconstants.h +++ b/src/plugins/texteditor/texteditorconstants.h @@ -208,6 +208,7 @@ const char FOLLOW_SYMBOL_UNDER_CURSOR_IN_NEXT_SPLIT[] = "TextEditor.FollowSymbol const char FIND_USAGES[] = "TextEditor.FindUsages"; // moved from CppEditor to TextEditor avoid breaking the setting by using the old key const char RENAME_SYMBOL[] = "CppEditor.RenameSymbolUnderCursor"; +const char OPEN_CALL_HIERARCHY[] = "TextEditor.OpenCallHierarchy"; const char JUMP_TO_FILE_UNDER_CURSOR[] = "TextEditor.JumpToFileUnderCursor"; const char JUMP_TO_FILE_UNDER_CURSOR_IN_NEXT_SPLIT[] = "TextEditor.JumpToFileUnderCursorInNextSplit"; diff --git a/src/plugins/texteditor/texteditoroverlay.cpp b/src/plugins/texteditor/texteditoroverlay.cpp index 52ab8709de6..4257adbbdb0 100644 --- a/src/plugins/texteditor/texteditoroverlay.cpp +++ b/src/plugins/texteditor/texteditoroverlay.cpp @@ -129,6 +129,7 @@ QPainterPath TextEditorOverlay::createSelectionPath(const QTextCursor &begin, co QTextLayout *blockLayout = block.layout(); int pos = begin.position() - begin.block().position(); QTextLine line = blockLayout->lineForTextPosition(pos); + QTC_ASSERT(line.isValid(), return {}); QRectF lineRect = line.naturalTextRect(); lineRect = lineRect.translated(blockGeometry.topLeft()); int x = line.cursorToX(pos); @@ -154,12 +155,12 @@ QPainterPath TextEditorOverlay::createSelectionPath(const QTextCursor &begin, co QTextLayout *blockLayout = block.layout(); int firstLine = 0; - QTextLine line = blockLayout->lineAt(firstLine); int beginChar = 0; if (block == begin.block()) { beginChar = begin.positionInBlock(); - line = blockLayout->lineForTextPosition(beginChar); + QTextLine line = blockLayout->lineForTextPosition(beginChar); + QTC_ASSERT(line.isValid(), return {}); firstLine = line.lineNumber(); const int lineEnd = line.textStart() + line.textLength(); if (beginChar == lineEnd) @@ -170,7 +171,9 @@ QPainterPath TextEditorOverlay::createSelectionPath(const QTextCursor &begin, co int endChar = -1; if (block == end.block()) { endChar = end.positionInBlock(); - lastLine = blockLayout->lineForTextPosition(endChar).lineNumber(); + QTextLine line = blockLayout->lineForTextPosition(endChar); + QTC_ASSERT(line.isValid(), return {}); + lastLine = line.lineNumber(); if (endChar == beginChar) break; // Do not expand overlay to empty selection at end } else { @@ -181,7 +184,8 @@ QPainterPath TextEditorOverlay::createSelectionPath(const QTextCursor &begin, co } for (int i = firstLine; i <= lastLine; ++i) { - line = blockLayout->lineAt(i); + QTextLine line = blockLayout->lineAt(i); + QTC_ASSERT(line.isValid(), return {}); QRectF lineRect = line.naturalTextRect(); if (i == firstLine && beginChar > 0) lineRect.setLeft(line.cursorToX(beginChar)); diff --git a/src/plugins/texteditor/texteditorplugin.cpp b/src/plugins/texteditor/texteditorplugin.cpp index e97453addae..4d5c2268d1e 100644 --- a/src/plugins/texteditor/texteditorplugin.cpp +++ b/src/plugins/texteditor/texteditorplugin.cpp @@ -10,6 +10,7 @@ #include "highlighter.h" #include "icodestylepreferences.h" #include "linenumberfilter.h" +#include "markdowneditor.h" #include "outlinefactory.h" #include "plaintexteditorfactory.h" #include "snippets/snippetprovider.h" @@ -19,6 +20,10 @@ #include "texteditorsettings.h" #include "texteditortr.h" +#ifdef WITH_TESTS +#include "codeassist/codeassist_test.h" +#endif + #include #include #include @@ -66,6 +71,7 @@ public: FindInOpenFiles findInOpenFilesFilter; PlainTextEditorFactory plainTextEditorFactory; + MarkdownEditorFactory markdownEditorFactory; }; static TextEditorPlugin *m_instance = nullptr; @@ -140,6 +146,10 @@ void TextEditorPlugin::initialize() Tr::tr("Text", "SnippetProvider")); d->createStandardContextMenu(); + +#ifdef WITH_TESTS + addTest(); +#endif } void TextEditorPluginPrivate::extensionsInitialized() diff --git a/src/plugins/texteditor/textmark.cpp b/src/plugins/texteditor/textmark.cpp index 37be854d9b9..4f86bc5f942 100644 --- a/src/plugins/texteditor/textmark.cpp +++ b/src/plugins/texteditor/textmark.cpp @@ -12,11 +12,13 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -79,6 +81,8 @@ TextMark::~TextMark() TextMarkRegistry::remove(this); if (m_baseTextDocument) m_baseTextDocument->removeMark(this); + if (m_deleteCallback) + m_deleteCallback(); m_baseTextDocument = nullptr; } @@ -282,7 +286,7 @@ void TextMark::addToToolTipLayout(QGridLayout *target) const if (m_category.id.isValid() && !m_lineAnnotation.isEmpty()) { auto visibilityAction = new QAction; const bool isHidden = TextDocument::marksAnnotationHidden(m_category.id); - visibilityAction->setIcon(Utils::Icons::EYE_OPEN_TOOLBAR.icon()); + visibilityAction->setIcon(Utils::Icons::EYE_OPEN.icon()); const QString tooltip = (isHidden ? Tr::tr("Show inline annotations for %1") : Tr::tr("Temporarily hide inline annotations for %1")) .arg(m_category.displayName); @@ -298,7 +302,7 @@ void TextMark::addToToolTipLayout(QGridLayout *target) const } if (m_settingsPage.isValid()) { auto settingsAction = new QAction; - settingsAction->setIcon(Utils::Icons::SETTINGS_TOOLBAR.icon()); + settingsAction->setIcon(Utils::Icons::SETTINGS.icon()); settingsAction->setToolTip(Tr::tr("Show Diagnostic Settings")); QObject::connect(settingsAction, &QAction::triggered, Core::ICore::instance(), [id = m_settingsPage] { Core::ICore::showOptionsDialog(id); }, @@ -338,11 +342,18 @@ bool TextMark::addToolTipContent(QLayout *target) const } auto textLabel = new QLabel; - textLabel->setOpenExternalLinks(true); textLabel->setText(text); // Differentiate between tool tips that where explicitly set and default tool tips. textLabel->setDisabled(useDefaultToolTip); target->addWidget(textLabel); + QObject::connect(textLabel, &QLabel::linkActivated, [](const QString &link) { + if (OutputLineParser::isLinkTarget(link)) { + Core::EditorManager::openEditorAt(OutputLineParser::parseLinkTarget(link), {}, + Core::EditorManager::SwitchSplitIfAlreadyVisible); + } else { + QDesktopServices::openUrl(link); + } + }); return true; } diff --git a/src/plugins/texteditor/textmark.h b/src/plugins/texteditor/textmark.h index fb000da1a44..b6fb4731cb6 100644 --- a/src/plugins/texteditor/textmark.h +++ b/src/plugins/texteditor/textmark.h @@ -118,12 +118,15 @@ public: bool isLocationMarker() const; void setIsLocationMarker(bool newIsLocationMarker); + protected: void setSettingsPage(Utils::Id settingsPage); private: Q_DISABLE_COPY(TextMark) + void setDeleteCallback(const std::function &callback) { m_deleteCallback = callback; }; + TextDocument *m_baseTextDocument = nullptr; Utils::FilePath m_fileName; int m_lineNumber = 0; @@ -141,6 +144,9 @@ private: QVector m_actions; // FIXME Remove in master std::function()> m_actionsProvider; Utils::Id m_settingsPage; + std::function m_deleteCallback; + + friend class TextDocumentLayout; }; } // namespace TextEditor diff --git a/src/plugins/todo/keyworddialog.cpp b/src/plugins/todo/keyworddialog.cpp index 0f92091a1f5..cebeb7b692f 100644 --- a/src/plugins/todo/keyworddialog.cpp +++ b/src/plugins/todo/keyworddialog.cpp @@ -47,7 +47,7 @@ KeywordDialog::KeywordDialog(const Keyword &keyword, const QSet &alread m_buttonBox->setOrientation(Qt::Horizontal); m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); - using namespace Utils::Layouting; + using namespace Layouting; Column { new QLabel(Tr::tr("Icon")), diff --git a/src/plugins/todo/optionsdialog.cpp b/src/plugins/todo/optionsdialog.cpp index f536ca51edb..3b300a7d10c 100644 --- a/src/plugins/todo/optionsdialog.cpp +++ b/src/plugins/todo/optionsdialog.cpp @@ -73,7 +73,7 @@ OptionsDialog::OptionsDialog(Settings *settings, const std::function &o m_scanInSubprojectRadioButton = new QRadioButton(Tr::tr("Scan the current subproject")); - using namespace Utils::Layouting; + using namespace Layouting; Column { Group { diff --git a/src/plugins/todo/todoitemsprovider.cpp b/src/plugins/todo/todoitemsprovider.cpp index 02790c64dfb..02ba71e2fcc 100644 --- a/src/plugins/todo/todoitemsprovider.cpp +++ b/src/plugins/todo/todoitemsprovider.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "todoitemsprovider.h" + #include "constants.h" #include "cpptodoitemsscanner.h" #include "qmljstodoitemsscanner.h" @@ -13,9 +14,9 @@ #include #include +#include #include #include -#include #include @@ -182,8 +183,8 @@ void TodoItemsProvider::updateListTimeoutElapsed() void TodoItemsProvider::setupStartupProjectBinding() { - m_startupProject = SessionManager::startupProject(); - connect(SessionManager::instance(), &SessionManager::startupProjectChanged, + m_startupProject = ProjectManager::startupProject(); + connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, this, &TodoItemsProvider::startupProjectChanged); connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::fileListChanged, this, &TodoItemsProvider::projectsFilesChanged); diff --git a/src/plugins/todo/todoprojectsettingswidget.cpp b/src/plugins/todo/todoprojectsettingswidget.cpp index 0d0e0d732a4..6f68ccc37ba 100644 --- a/src/plugins/todo/todoprojectsettingswidget.cpp +++ b/src/plugins/todo/todoprojectsettingswidget.cpp @@ -32,7 +32,7 @@ TodoProjectSettingsWidget::TodoProjectSettingsWidget(ProjectExplorer::Project *p auto addExcludedPatternButton = new QPushButton(Tr::tr("Add")); - using namespace Utils::Layouting; + using namespace Layouting; Column { Group { diff --git a/src/plugins/updateinfo/settingspage.cpp b/src/plugins/updateinfo/settingspage.cpp index 587102bce75..18d070092e7 100644 --- a/src/plugins/updateinfo/settingspage.cpp +++ b/src/plugins/updateinfo/settingspage.cpp @@ -45,7 +45,7 @@ public: m_nextCheckDateLabel = new QLabel; m_checkForNewQtVersions = new QCheckBox(Tr::tr("Check for new Qt versions")); - using namespace Utils::Layouting; + using namespace Layouting; Column { m_infoLabel, diff --git a/src/plugins/updateinfo/settingspage.h b/src/plugins/updateinfo/settingspage.h index 3ed45a71c65..6da738a9a0c 100644 --- a/src/plugins/updateinfo/settingspage.h +++ b/src/plugins/updateinfo/settingspage.h @@ -5,18 +5,14 @@ #include -namespace UpdateInfo { -namespace Internal { +namespace UpdateInfo::Internal { class UpdateInfoPlugin; class SettingsPage : public Core::IOptionsPage { - Q_OBJECT - public: explicit SettingsPage(UpdateInfoPlugin *plugin); }; -} // namespace Internal -} // namespace UpdateInfo +} // UpdateInfo::Internal diff --git a/src/plugins/updateinfo/updateinfoplugin.cpp b/src/plugins/updateinfo/updateinfoplugin.cpp index decce3a47a9..749cbbbec64 100644 --- a/src/plugins/updateinfo/updateinfoplugin.cpp +++ b/src/plugins/updateinfo/updateinfoplugin.cpp @@ -13,8 +13,8 @@ #include #include #include +#include #include -#include #include #include @@ -43,6 +43,7 @@ const char InstallQtUpdates[] = "UpdateInfo.InstallQtUpdates"; const char M_MAINTENANCE_TOOL[] = "QtCreator.Menu.Tools.MaintenanceTool"; using namespace Core; +using namespace Tasking; using namespace Utils; namespace UpdateInfo { @@ -119,7 +120,7 @@ void UpdateInfoPlugin::startCheckForUpdates() using namespace Tasking; - const auto doSetup = [this](QtcProcess &process, const QStringList &args) { + const auto doSetup = [this](Process &process, const QStringList &args) { process.setCommand({d->m_maintenanceTool, args}); }; const auto doCleanup = [this] { @@ -127,22 +128,22 @@ void UpdateInfoPlugin::startCheckForUpdates() checkForUpdatesStopped(); }; - const auto setupUpdate = [doSetup](QtcProcess &process) { + const auto setupUpdate = [doSetup](Process &process) { doSetup(process, {"ch", "-g", "*=false,ifw.package.*=true"}); }; - const auto updateDone = [this](const QtcProcess &process) { + const auto updateDone = [this](const Process &process) { d->m_updateOutput = process.cleanedStdOut(); }; - QList tasks { Process(setupUpdate, updateDone) }; + QList tasks { ProcessTask(setupUpdate, updateDone) }; if (d->m_settings.checkForQtVersions) { - const auto setupPackages = [doSetup](QtcProcess &process) { + const auto setupPackages = [doSetup](Process &process) { doSetup(process, {"se", "qt[.]qt[0-9][.][0-9]+$", "-g", "*=false,ifw.package.*=true"}); }; - const auto packagesDone = [this](const QtcProcess &process) { + const auto packagesDone = [this](const Process &process) { d->m_packagesOutput = process.cleanedStdOut(); }; - tasks << Process(setupPackages, packagesDone); + tasks << ProcessTask(setupPackages, packagesDone); } d->m_taskTree.reset(new TaskTree(Group{tasks})); @@ -461,7 +462,7 @@ QDate UpdateInfoPlugin::nextCheckDate(CheckUpdateInterval interval) const void UpdateInfoPlugin::startMaintenanceTool(const QStringList &args) const { - QtcProcess::startDetached(CommandLine{d->m_maintenanceTool, args}); + Process::startDetached(CommandLine{d->m_maintenanceTool, args}); } void UpdateInfoPlugin::startUpdater() const diff --git a/src/plugins/valgrind/callgrindengine.cpp b/src/plugins/valgrind/callgrindengine.cpp index d70c667df6f..92b13b4f77b 100644 --- a/src/plugins/valgrind/callgrindengine.cpp +++ b/src/plugins/valgrind/callgrindengine.cpp @@ -12,8 +12,9 @@ #include #include +#include +#include #include -#include #include #include @@ -60,34 +61,32 @@ CallgrindToolRunner::~CallgrindToolRunner() cleanupTempFile(); } -QStringList CallgrindToolRunner::toolArguments() const +void CallgrindToolRunner::addToolArguments(CommandLine &cmd) const { - QStringList arguments = {"--tool=callgrind"}; + cmd << "--tool=callgrind"; - if (m_settings.enableCacheSim.value()) - arguments << "--cache-sim=yes"; + if (m_settings.enableCacheSim()) + cmd << "--cache-sim=yes"; - if (m_settings.enableBranchSim.value()) - arguments << "--branch-sim=yes"; + if (m_settings.enableBranchSim()) + cmd << "--branch-sim=yes"; - if (m_settings.collectBusEvents.value()) - arguments << "--collect-bus=yes"; + if (m_settings.collectBusEvents()) + cmd << "--collect-bus=yes"; - if (m_settings.collectSystime.value()) - arguments << "--collect-systime=yes"; + if (m_settings.collectSystime()) + cmd << "--collect-systime=yes"; if (m_markAsPaused) - arguments << "--instr-atstart=no"; + cmd << "--instr-atstart=no"; // add extra arguments if (!m_argumentForToggleCollect.isEmpty()) - arguments << m_argumentForToggleCollect; + cmd << m_argumentForToggleCollect; - arguments << "--callgrind-out-file=" + m_valgrindOutputFile.path(); + cmd << "--callgrind-out-file=" + m_valgrindOutputFile.path(); - arguments << ProcessArgs::splitArgs(m_settings.callgrindArguments.value()); - - return arguments; + cmd.addArgs(m_settings.callgrindArguments(), CommandLine::Raw); } QString CallgrindToolRunner::progressTitle() const @@ -174,7 +173,7 @@ void CallgrindToolRunner::run(Option option) // save back current running operation m_lastOption = option; - m_controllerProcess.reset(new QtcProcess); + m_controllerProcess.reset(new Process); switch (option) { case CallgrindToolRunner::Dump: @@ -196,11 +195,11 @@ void CallgrindToolRunner::run(Option option) #if CALLGRIND_CONTROL_DEBUG m_controllerProcess->setProcessChannelMode(QProcess::ForwardedChannels); #endif - connect(m_controllerProcess.get(), &QtcProcess::done, + connect(m_controllerProcess.get(), &Process::done, this, &CallgrindToolRunner::controllerProcessDone); const FilePath control = - FilePath(CALLGRIND_CONTROL_BINARY).onDevice(m_valgrindRunnable.command.executable()); + m_valgrindRunnable.command.executable().withNewPath(CALLGRIND_CONTROL_BINARY); m_controllerProcess->setCommand({control, {toOptionString(option), QString::number(m_pid)}}); m_controllerProcess->setWorkingDirectory(m_valgrindRunnable.workingDirectory); m_controllerProcess->setEnvironment(m_valgrindRunnable.environment); @@ -257,11 +256,14 @@ void CallgrindToolRunner::triggerParse() } const auto afterCopy = [this](expected_str res) { - QTC_ASSERT_EXPECTED(res, return); + if (!res) // failed to run callgrind + return; showStatusMessage(Tr::tr("Parsing Profile Data...")); m_parser.parse(m_hostOutputFile); }; - m_valgrindOutputFile.asyncCopyFile(afterCopy, m_hostOutputFile); + // TODO: Store the handle and cancel on CallgrindToolRunner destructor? + // TODO: Should d'tor of context object cancel the running task? + FileStreamerManager::copy(m_valgrindOutputFile, m_hostOutputFile, this, afterCopy); } void CallgrindToolRunner::cleanupTempFile() diff --git a/src/plugins/valgrind/callgrindengine.h b/src/plugins/valgrind/callgrindengine.h index 2f22280ffa8..d5d26e113a5 100644 --- a/src/plugins/valgrind/callgrindengine.h +++ b/src/plugins/valgrind/callgrindengine.h @@ -8,7 +8,7 @@ #include "callgrind/callgrindparsedata.h" #include "callgrind/callgrindparser.h" -#include +#include namespace Valgrind { namespace Internal { @@ -48,7 +48,7 @@ public: Q_ENUM(Option) protected: - QStringList toolArguments() const override; + void addToolArguments(Utils::CommandLine &cmd) const override; QString progressTitle() const override; signals: @@ -73,7 +73,7 @@ private: bool m_markAsPaused = false; - std::unique_ptr m_controllerProcess; + std::unique_ptr m_controllerProcess; ProjectExplorer::Runnable m_valgrindRunnable; qint64 m_pid = 0; diff --git a/src/plugins/valgrind/callgrindtool.cpp b/src/plugins/valgrind/callgrindtool.cpp index fc50b2ecf09..ab5b0e55602 100644 --- a/src/plugins/valgrind/callgrindtool.cpp +++ b/src/plugins/valgrind/callgrindtool.cpp @@ -47,13 +47,13 @@ #include #include #include +#include #include -#include #include #include +#include #include -#include #include #include @@ -256,7 +256,7 @@ CallgrindToolPrivate::CallgrindToolPrivate() menu->addAction(ActionManager::registerAction(action, CallgrindRemoteActionId), Debugger::Constants::G_ANALYZER_REMOTE_TOOLS); QObject::connect(action, &QAction::triggered, this, [this, action] { - auto runConfig = SessionManager::startupRunConfiguration(); + auto runConfig = ProjectManager::startupRunConfiguration(); if (!runConfig) { showCannotStartDialog(action->text()); return; @@ -361,7 +361,7 @@ CallgrindToolPrivate::CallgrindToolPrivate() action->setIcon(kCachegrindIcon.icon()); action->setToolTip(Tr::tr("Open results in KCachegrind.")); connect(action, &QAction::triggered, this, [this, settings] { - QtcProcess::startDetached({FilePath::fromString(settings->kcachegrindExecutable.value()), { m_lastFileName }}); + Process::startDetached({FilePath::fromString(settings->kcachegrindExecutable.value()), { m_lastFileName }}); }); // dump action diff --git a/src/plugins/valgrind/memcheckerrorview.cpp b/src/plugins/valgrind/memcheckerrorview.cpp index 3bb8a66a02e..48e9afd8018 100644 --- a/src/plugins/valgrind/memcheckerrorview.cpp +++ b/src/plugins/valgrind/memcheckerrorview.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/plugins/valgrind/memchecktool.cpp b/src/plugins/valgrind/memchecktool.cpp index d4a5e4a6499..e2921603e98 100644 --- a/src/plugins/valgrind/memchecktool.cpp +++ b/src/plugins/valgrind/memchecktool.cpp @@ -7,7 +7,6 @@ #include "valgrindengine.h" #include "valgrindrunner.h" #include "valgrindsettings.h" -#include "valgrindsettings.h" #include "valgrindtr.h" #include "xmlprotocol/error.h" @@ -30,8 +29,8 @@ #include #include #include +#include #include -#include #include #include #include @@ -50,8 +49,9 @@ #include #include #include +#include #include -#include +#include #include #include @@ -111,7 +111,7 @@ signals: private: QString progressTitle() const override; - QStringList toolArguments() const override; + void addToolArguments(CommandLine &cmd) const override; void startDebugger(qint64 valgrindPid); void appendLog(const QByteArray &data); @@ -129,9 +129,9 @@ public: void start() override { QTC_ASSERT(!m_process, return); - m_process.reset(new QtcProcess); + m_process.reset(new Process); m_process->setCommand({device()->filePath("echo"), "-n $SSH_CLIENT", CommandLine::Raw}); - connect(m_process.get(), &QtcProcess::done, this, [this] { + connect(m_process.get(), &Process::done, this, [this] { if (m_process->error() != QProcess::UnknownError) { reportFailure(); return; @@ -159,7 +159,7 @@ public: } private: - std::unique_ptr m_process = nullptr; + std::unique_ptr m_process = nullptr; QHostAddress *m_localServerAddress = nullptr; }; @@ -181,18 +181,18 @@ void MemcheckToolRunner::stop() ValgrindToolRunner::stop(); } -QStringList MemcheckToolRunner::toolArguments() const +void MemcheckToolRunner::addToolArguments(CommandLine &cmd) const { - QStringList arguments = {"--tool=memcheck", "--gen-suppressions=all"}; + cmd << "--tool=memcheck" << "--gen-suppressions=all"; - if (m_settings.trackOrigins.value()) - arguments << "--track-origins=yes"; + if (m_settings.trackOrigins()) + cmd << "--track-origins=yes"; - if (m_settings.showReachable.value()) - arguments << "--show-reachable=yes"; + if (m_settings.showReachable()) + cmd << "--show-reachable=yes"; QString leakCheckValue; - switch (m_settings.leakCheckOnFinish.value()) { + switch (m_settings.leakCheckOnFinish()) { case ValgrindBaseSettings::LeakCheckOnFinishNo: leakCheckValue = "no"; break; @@ -204,24 +204,22 @@ QStringList MemcheckToolRunner::toolArguments() const leakCheckValue = "summary"; break; } - arguments << "--leak-check=" + leakCheckValue; + cmd << "--leak-check=" + leakCheckValue; - for (const FilePath &file : m_settings.suppressions.value()) - arguments << QString("--suppressions=%1").arg(file.path()); + for (const FilePath &file : m_settings.suppressions()) + cmd << QString("--suppressions=%1").arg(file.path()); - arguments << QString("--num-callers=%1").arg(m_settings.numCallers.value()); + cmd << QString("--num-callers=%1").arg(m_settings.numCallers()); if (m_withGdb) - arguments << "--vgdb=yes" << "--vgdb-error=0"; + cmd << "--vgdb=yes" << "--vgdb-error=0"; - arguments << Utils::ProcessArgs::splitArgs(m_settings.memcheckArguments.value()); - - return arguments; + cmd.addArgs(m_settings.memcheckArguments(), CommandLine::Raw); } const FilePaths MemcheckToolRunner::suppressionFiles() const { - return m_settings.suppressions.value(); + return m_settings.suppressions(); } void MemcheckToolRunner::startDebugger(qint64 valgrindPid) @@ -337,7 +335,7 @@ bool MemcheckErrorFilterProxyModel::filterAcceptsRow(int sourceRow, const QModel // ALGORITHM: look at last five stack frames, if none of these is inside any open projects, // assume this error was created by an external library QSet validFolders; - for (Project *project : SessionManager::projects()) { + for (Project *project : ProjectManager::projects()) { validFolders << project->projectDirectory().toString(); const QList targets = project->targets(); for (const Target *target : targets) { @@ -609,7 +607,7 @@ MemcheckToolPrivate::MemcheckToolPrivate() filterButton->setIcon(Icons::FILTER.icon()); filterButton->setText(Tr::tr("Error Filter")); filterButton->setPopupMode(QToolButton::InstantPopup); - filterButton->setProperty("noArrow", true); + filterButton->setProperty(StyleHelper::C_NO_ARROW, true); m_filterMenu = new QMenu(filterButton); for (QAction *filterAction : std::as_const(m_errorFilterActions)) @@ -676,7 +674,7 @@ MemcheckToolPrivate::MemcheckToolPrivate() menu->addAction(ActionManager::registerAction(action, "Memcheck.Remote"), Debugger::Constants::G_ANALYZER_REMOTE_TOOLS); QObject::connect(action, &QAction::triggered, this, [this, action] { - RunConfiguration *runConfig = SessionManager::startupRunConfiguration(); + RunConfiguration *runConfig = ProjectManager::startupRunConfiguration(); if (!runConfig) { showCannotStartDialog(action->text()); return; @@ -718,7 +716,7 @@ void MemcheckToolPrivate::heobAction() Abi abi; bool hasLocalRc = false; Kit *kit = nullptr; - if (Target *target = SessionManager::startupTarget()) { + if (Target *target = ProjectManager::startupTarget()) { if (RunConfiguration *rc = target->activeRunConfiguration()) { kit = target->kit(); if (kit) { @@ -800,19 +798,18 @@ void MemcheckToolPrivate::heobAction() const QString dwarfstack = QString("dwarfstack%1.dll").arg(abi.wordWidth()); const QString dwarfstackPath = dialog.path() + '/' + dwarfstack; if (!QFile::exists(dwarfstackPath) - && CheckableMessageBox::doNotShowAgainInformation( + && CheckableMessageBox::information( Core::ICore::dialogParent(), Tr::tr("Heob"), Tr::tr("Heob used with MinGW projects needs the %1 DLLs for proper " - "stacktrace resolution.") + "stacktrace resolution.") .arg( "Dwarfstack"), - ICore::settings(), - "HeobDwarfstackInfo", - QDialogButtonBox::Ok | QDialogButtonBox::Cancel, - QDialogButtonBox::Ok) - != QDialogButtonBox::Ok) + QString("HeobDwarfstackInfo"), + QMessageBox::Ok | QMessageBox::Cancel, + QMessageBox::Ok) + != QMessageBox::Ok) return; } @@ -917,22 +914,22 @@ void MemcheckToolPrivate::updateFromSettings() for (const QVariant &v : actions) { bool ok; int kind = v.toInt(&ok); - if (ok && !m_settings->visibleErrorKinds.value().contains(kind)) + if (ok && !m_settings->visibleErrorKinds().contains(kind)) contained = false; } action->setChecked(contained); } - m_filterProjectAction->setChecked(!m_settings->filterExternalIssues.value()); + m_filterProjectAction->setChecked(!m_settings->filterExternalIssues()); m_errorView->settingsChanged(m_settings); connect(&m_settings->visibleErrorKinds, &IntegersAspect::valueChanged, &m_errorProxyModel, &MemcheckErrorFilterProxyModel::setAcceptedKinds); - m_errorProxyModel.setAcceptedKinds(m_settings->visibleErrorKinds.value()); + m_errorProxyModel.setAcceptedKinds(m_settings->visibleErrorKinds()); connect(&m_settings->filterExternalIssues, &BoolAspect::valueChanged, &m_errorProxyModel, &MemcheckErrorFilterProxyModel::setFilterExternalIssues); - m_errorProxyModel.setFilterExternalIssues(m_settings->filterExternalIssues.value()); + m_errorProxyModel.setFilterExternalIssues(m_settings->filterExternalIssues()); } void MemcheckToolPrivate::maybeActiveRunConfigurationChanged() @@ -940,7 +937,7 @@ void MemcheckToolPrivate::maybeActiveRunConfigurationChanged() updateRunActions(); ValgrindBaseSettings *settings = nullptr; - if (Project *project = SessionManager::startupProject()) + if (Project *project = ProjectManager::startupProject()) if (Target *target = project->activeTarget()) if (RunConfiguration *rc = target->activeRunConfiguration()) settings = rc->currentSettings(ANALYZER_VALGRIND_SETTINGS); diff --git a/src/plugins/valgrind/suppressiondialog.cpp b/src/plugins/valgrind/suppressiondialog.cpp index 08ed8ae5ff7..6273580e82d 100644 --- a/src/plugins/valgrind/suppressiondialog.cpp +++ b/src/plugins/valgrind/suppressiondialog.cpp @@ -12,9 +12,9 @@ #include "xmlprotocol/stack.h" #include "xmlprotocol/frame.h" -#include -#include #include +#include +#include #include #include @@ -182,8 +182,8 @@ void SuppressionDialog::accept() return; // Add file to project if there is a project containing this file on the file system. - if (!ProjectExplorer::SessionManager::projectForFile(path)) { - for (ProjectExplorer::Project *p : ProjectExplorer::SessionManager::projects()) { + if (!ProjectExplorer::ProjectManager::projectForFile(path)) { + for (ProjectExplorer::Project *p : ProjectExplorer::ProjectManager::projects()) { if (path.startsWith(p->projectDirectory().toString())) { p->rootProjectNode()->addFiles({path}); break; diff --git a/src/plugins/valgrind/valgrind.qbs b/src/plugins/valgrind/valgrind.qbs index f0e117ce98a..973b1d7b85a 100644 --- a/src/plugins/valgrind/valgrind.qbs +++ b/src/plugins/valgrind/valgrind.qbs @@ -76,9 +76,7 @@ QtcPlugin { ] } - Group { - name: "Test sources" - condition: qtc.testsEnabled + QtcTestFiles { files: [ "valgrindmemcheckparsertest.cpp", "valgrindmemcheckparsertest.h", diff --git a/src/plugins/valgrind/valgrindengine.cpp b/src/plugins/valgrind/valgrindengine.cpp index 58919333f17..a228eb0e09a 100644 --- a/src/plugins/valgrind/valgrindengine.cpp +++ b/src/plugins/valgrind/valgrindengine.cpp @@ -22,8 +22,6 @@ #include -#define VALGRIND_DEBUG_OUTPUT 0 - using namespace Debugger; using namespace Core; using namespace Utils; @@ -39,8 +37,10 @@ ValgrindToolRunner::ValgrindToolRunner(RunControl *runControl) m_settings.fromMap(runControl->settingsData(ANALYZER_VALGRIND_SETTINGS)); - connect(&m_runner, &ValgrindRunner::appendMessage, - this, &ValgrindToolRunner::appendMessage); + connect(&m_runner, + &ValgrindRunner::appendMessage, + this, + [this](const QString &msg, Utils::OutputFormat format) { appendMessage(msg, format); }); connect(&m_runner, &ValgrindRunner::valgrindExecuted, this, [this](const QString &commandLine) { appendMessage(commandLine, NormalMessageFormat); @@ -53,6 +53,19 @@ ValgrindToolRunner::ValgrindToolRunner(RunControl *runControl) void ValgrindToolRunner::start() { + FilePath valgrindExecutable = m_settings.valgrindExecutable(); + if (IDevice::ConstPtr dev = DeviceKitAspect::device(runControl()->kit())) + valgrindExecutable = dev->filePath(valgrindExecutable.path()); + + const FilePath found = valgrindExecutable.searchInPath(); + + if (!found.isExecutableFile()) { + reportFailure(Tr::tr("Valgrind executable \"%1\" not found or not executable.\n" + "Check settings or ensure valgrind is installed and available in PATH.") + .arg(valgrindExecutable.toUserOutput())); + return; + } + FutureProgress *fp = ProgressManager::addTimedTask(m_progress, progressTitle(), "valgrind", 100); connect(fp, &FutureProgress::canceled, this, &ValgrindToolRunner::handleProgressCanceled); @@ -60,21 +73,10 @@ void ValgrindToolRunner::start() this, &ValgrindToolRunner::handleProgressFinished); m_progress.reportStarted(); -#if VALGRIND_DEBUG_OUTPUT - emit outputReceived(Tr::tr("Valgrind options: %1").arg(toolArguments().join(' ')), LogMessageFormat); - emit outputReceived(Tr::tr("Working directory: %1").arg(runnable().workingDirectory), LogMessageFormat); - emit outputReceived(Tr::tr("Command line arguments: %1").arg(runnable().debuggeeArgs), LogMessageFormat); -#endif - - - FilePath valgrindExecutable = m_settings.valgrindExecutable.filePath(); - if (IDevice::ConstPtr dev = DeviceKitAspect::device(runControl()->kit())) - valgrindExecutable = dev->filePath(valgrindExecutable.path()); - CommandLine valgrind{valgrindExecutable}; - valgrind.addArgs(m_settings.valgrindArguments.value(), CommandLine::Raw); + valgrind.addArgs(m_settings.valgrindArguments(), CommandLine::Raw); valgrind.addArgs(genericToolArguments()); - valgrind.addArgs(toolArguments()); + addToolArguments(valgrind); m_runner.setValgrindCommand(valgrind); m_runner.setDebuggee(runControl()->runnable()); diff --git a/src/plugins/valgrind/valgrindengine.h b/src/plugins/valgrind/valgrindengine.h index e5894b17838..d491c7a8476 100644 --- a/src/plugins/valgrind/valgrindengine.h +++ b/src/plugins/valgrind/valgrindengine.h @@ -6,7 +6,6 @@ #include "valgrindsettings.h" #include -#include #include #include @@ -23,7 +22,7 @@ public: protected: virtual QString progressTitle() const = 0; - virtual QStringList toolArguments() const = 0; + virtual void addToolArguments(Utils::CommandLine &cmd) const = 0; ValgrindProjectSettings m_settings; QFutureInterface m_progress; diff --git a/src/plugins/valgrind/valgrindrunner.cpp b/src/plugins/valgrind/valgrindrunner.cpp index ef84fba6759..fd265444226 100644 --- a/src/plugins/valgrind/valgrindrunner.cpp +++ b/src/plugins/valgrind/valgrindrunner.cpp @@ -9,8 +9,8 @@ #include #include +#include #include -#include #include #include @@ -25,18 +25,18 @@ class ValgrindRunner::Private : public QObject { public: Private(ValgrindRunner *owner) : q(owner) { - connect(&m_process, &QtcProcess::started, this, [this] { + connect(&m_process, &Process::started, this, [this] { emit q->valgrindStarted(m_process.processId()); }); - connect(&m_process, &QtcProcess::done, this, [this] { + connect(&m_process, &Process::done, this, [this] { if (m_process.result() != ProcessResult::FinishedWithSuccess) emit q->processErrorReceived(m_process.errorString(), m_process.error()); emit q->finished(); }); - connect(&m_process, &QtcProcess::readyReadStandardOutput, this, [this] { + connect(&m_process, &Process::readyReadStandardOutput, this, [this] { emit q->appendMessage(m_process.readAllStandardOutput(), StdOutFormat); }); - connect(&m_process, &QtcProcess::readyReadStandardError, this, [this] { + connect(&m_process, &Process::readyReadStandardError, this, [this] { emit q->appendMessage(m_process.readAllStandardError(), StdErrFormat); }); @@ -53,7 +53,7 @@ public: Runnable m_debuggee; CommandLine m_command; - QtcProcess m_process; + Process m_process; QHostAddress m_localServerAddress; @@ -197,7 +197,7 @@ void ValgrindRunner::setLocalServerAddress(const QHostAddress &localServerAddres void ValgrindRunner::setUseTerminal(bool on) { - d->m_process.setTerminalMode(on ? TerminalMode::On : TerminalMode::Off); + d->m_process.setTerminalMode(on ? TerminalMode::Run : TerminalMode::Off); } void ValgrindRunner::waitForFinished() const diff --git a/src/plugins/valgrind/valgrindsettings.cpp b/src/plugins/valgrind/valgrindsettings.cpp index fe546ea0cc2..5be58ea3b8c 100644 --- a/src/plugins/valgrind/valgrindsettings.cpp +++ b/src/plugins/valgrind/valgrindsettings.cpp @@ -107,7 +107,8 @@ void SuppressionAspectPrivate::slotSuppressionSelectionChanged() // SuppressionAspect // -SuppressionAspect::SuppressionAspect(bool global) +SuppressionAspect::SuppressionAspect(AspectContainer *container, bool global) + : BaseAspect(container) { d = new SuppressionAspectPrivate(this, global); setSettingsKey("Analyzer.Valgrind.SuppressionFiles"); @@ -128,7 +129,7 @@ void SuppressionAspect::setValue(const FilePaths &val) BaseAspect::setValue(Utils::transform(val, &FilePath::toString)); } -void SuppressionAspect::addToLayout(Layouting::LayoutBuilder &builder) +void SuppressionAspect::addToLayout(Layouting::LayoutItem &parent) { QTC_CHECK(!d->addEntry); QTC_CHECK(!d->removeEntry); @@ -150,12 +151,12 @@ void SuppressionAspect::addToLayout(Layouting::LayoutBuilder &builder) connect(d->entryList->selectionModel(), &QItemSelectionModel::selectionChanged, d, &SuppressionAspectPrivate::slotSuppressionSelectionChanged); - builder.addItem(Column { Tr::tr("Suppression files:"), st }); + parent.addItem(Column { Tr::tr("Suppression files:"), st }); Row group { d->entryList.data(), Column { d->addEntry.data(), d->removeEntry.data(), st } }; - builder.addItem(Span { 2, group }); + parent.addItem(Span { 2, group }); setVolatileValue(BaseAspect::value()); } @@ -195,19 +196,15 @@ void SuppressionAspect::setVolatileValue(const QVariant &val) ////////////////////////////////////////////////////////////////// ValgrindBaseSettings::ValgrindBaseSettings(bool global) - : suppressions(global) + : suppressions(this, global) { // Note that this is used twice, once for project settings in the .user files // and once for global settings in QtCreator.ini. This uses intentionally // the same key to facilitate copying using fromMap/toMap. QString base = "Analyzer.Valgrind."; - registerAspect(&suppressions); - - registerAspect(&valgrindExecutable); valgrindExecutable.setSettingsKey(base + "ValgrindExecutable"); valgrindExecutable.setDefaultValue("valgrind"); - valgrindExecutable.setDisplayStyle(StringAspect::PathChooserDisplay); valgrindExecutable.setExpectedKind(PathChooser::Command); valgrindExecutable.setHistoryCompleter("Valgrind.Command.History"); valgrindExecutable.setDisplayName(Tr::tr("Valgrind Command")); @@ -220,12 +217,10 @@ ValgrindBaseSettings::ValgrindBaseSettings(bool global) //valgrindExecutable. ... buttonAtIndex(0)->hide(); } - registerAspect(&valgrindArguments); valgrindArguments.setSettingsKey(base + "ValgrindArguments"); valgrindArguments.setDisplayStyle(StringAspect::LineEditDisplay); valgrindArguments.setLabelText(Tr::tr("Valgrind arguments:")); - registerAspect(&selfModifyingCodeDetection); selfModifyingCodeDetection.setSettingsKey(base + "SelfModifyingCodeDetection"); selfModifyingCodeDetection.setDefaultValue(DetectSmcStackOnly); selfModifyingCodeDetection.setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox); @@ -236,31 +231,26 @@ ValgrindBaseSettings::ValgrindBaseSettings(bool global) selfModifyingCodeDetection.setLabelText(Tr::tr("Detect self-modifying code:")); // Memcheck - registerAspect(&memcheckArguments); memcheckArguments.setSettingsKey(base + "Memcheck.Arguments"); memcheckArguments.setDisplayStyle(StringAspect::LineEditDisplay); memcheckArguments.setLabelText(Tr::tr("Extra MemCheck arguments:")); - registerAspect(&filterExternalIssues); filterExternalIssues.setSettingsKey(base + "FilterExternalIssues"); filterExternalIssues.setDefaultValue(true); filterExternalIssues.setIcon(Icons::FILTER.icon()); - filterExternalIssues.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel); + filterExternalIssues.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBox); filterExternalIssues.setLabelText(Tr::tr("Show Project Costs Only")); filterExternalIssues.setToolTip(Tr::tr("Show only profiling info that originated from this project source.")); - registerAspect(&trackOrigins); trackOrigins.setSettingsKey(base + "TrackOrigins"); trackOrigins.setDefaultValue(true); - trackOrigins.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel); + trackOrigins.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBox); trackOrigins.setLabelText(Tr::tr("Track origins of uninitialized memory")); - registerAspect(&showReachable); showReachable.setSettingsKey(base + "ShowReachable"); - showReachable.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel); + showReachable.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBox); showReachable.setLabelText(Tr::tr("Show reachable and indirectly lost blocks")); - registerAspect(&leakCheckOnFinish); leakCheckOnFinish.setSettingsKey(base + "LeakCheckOnFinish"); leakCheckOnFinish.setDefaultValue(LeakCheckOnFinishSummaryOnly); leakCheckOnFinish.setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox); @@ -269,35 +259,29 @@ ValgrindBaseSettings::ValgrindBaseSettings(bool global) leakCheckOnFinish.addOption(Tr::tr("Full")); leakCheckOnFinish.setLabelText(Tr::tr("Check for leaks on finish:")); - registerAspect(&numCallers); numCallers.setSettingsKey(base + "NumCallers"); numCallers.setDefaultValue(25); numCallers.setLabelText(Tr::tr("Backtrace frame count:")); // Callgrind - registerAspect(&kcachegrindExecutable); kcachegrindExecutable.setSettingsKey(base + "KCachegrindExecutable"); kcachegrindExecutable.setDefaultValue("kcachegrind"); - kcachegrindExecutable.setDisplayStyle(StringAspect::PathChooserDisplay); kcachegrindExecutable.setLabelText(Tr::tr("KCachegrind executable:")); kcachegrindExecutable.setExpectedKind(Utils::PathChooser::Command); kcachegrindExecutable.setDisplayName(Tr::tr("KCachegrind Command")); - registerAspect(&callgrindArguments); callgrindArguments.setSettingsKey(base + "Callgrind.Arguments"); callgrindArguments.setDisplayStyle(StringAspect::LineEditDisplay); callgrindArguments.setLabelText(Tr::tr("Extra CallGrind arguments:")); - registerAspect(&enableEventToolTips); enableEventToolTips.setDefaultValue(true); enableEventToolTips.setSettingsKey(base + "Callgrind.EnableEventToolTips"); - enableEventToolTips.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel); + enableEventToolTips.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBox); enableEventToolTips.setLabelText(Tr::tr("Show additional information for events in tooltips")); - registerAspect(&enableCacheSim); enableCacheSim.setSettingsKey(base + "Callgrind.EnableCacheSim"); - enableCacheSim.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel); + enableCacheSim.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBox); enableCacheSim.setLabelText(Tr::tr("Enable cache simulation")); enableCacheSim.setToolTip("" + Tr::tr( "

Does full cache simulation.

\n" @@ -309,9 +293,8 @@ ValgrindBaseSettings::ValgrindBaseSettings(bool global) "
  • Data write accesses (\"Dw\") and related cache misses (\"D1mw\"/\"D2mw\").
  • \n" "

    ") + ""); - registerAspect(&enableBranchSim); enableBranchSim.setSettingsKey(base + "Callgrind.EnableBranchSim"); - enableBranchSim.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel); + enableBranchSim.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBox); enableBranchSim.setLabelText(Tr::tr("Enable branch prediction simulation")); enableBranchSim.setToolTip("\n" + Tr::tr( "

    Does branch prediction simulation.

    \n" @@ -321,20 +304,17 @@ ValgrindBaseSettings::ValgrindBaseSettings(bool global) "
  • Executed indirect jumps and related misses of the jump address predictor (\n" "\"Bi\"/\"Bim\").)
  • ") + ""); - registerAspect(&collectSystime); collectSystime.setSettingsKey(base + "Callgrind.CollectSystime"); - collectSystime.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel); + collectSystime.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBox); collectSystime.setLabelText(Tr::tr("Collect system call time")); collectSystime.setToolTip(Tr::tr("Collects information for system call times.")); - registerAspect(&collectBusEvents); - collectBusEvents.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBoxWithoutDummyLabel); + collectBusEvents.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBox); collectBusEvents.setSettingsKey(base + "Callgrind.CollectBusEvents"); collectBusEvents.setLabelText(Tr::tr("Collect global bus events")); collectBusEvents.setToolTip(Tr::tr("Collect the number of global bus events that are executed. " "The event type \"Ge\" is used for these events.")); - registerAspect(&minimumInclusiveCostRatio); minimumInclusiveCostRatio.setSettingsKey(base + "Callgrind.MinimumCostRatio"); minimumInclusiveCostRatio.setDefaultValue(0.01); minimumInclusiveCostRatio.setSuffix(Tr::tr("%")); @@ -342,13 +322,11 @@ ValgrindBaseSettings::ValgrindBaseSettings(bool global) minimumInclusiveCostRatio.setToolTip(Tr::tr("Limits the amount of results the profiler gives you. " "A lower limit will likely increase performance.")); - registerAspect(&visualizationMinimumInclusiveCostRatio); visualizationMinimumInclusiveCostRatio.setSettingsKey(base + "Callgrind.VisualisationMinimumCostRatio"); visualizationMinimumInclusiveCostRatio.setDefaultValue(10.0); visualizationMinimumInclusiveCostRatio.setLabelText(Tr::tr("Visualization: Minimum event cost:")); visualizationMinimumInclusiveCostRatio.setSuffix(Tr::tr("%")); - registerAspect(&visibleErrorKinds); visibleErrorKinds.setSettingsKey(base + "VisibleErrorKinds"); QList defaultErrorKinds; for (int i = 0; i < Valgrind::XmlProtocol::MemcheckErrorKindCount; ++i) @@ -372,25 +350,20 @@ ValgrindGlobalSettings::ValgrindGlobalSettings() const QString base = "Analyzer.Valgrind"; - registerAspect(&lastSuppressionDirectory); lastSuppressionDirectory.setSettingsKey(base + "LastSuppressionDirectory"); - registerAspect(&lastSuppressionHistory); lastSuppressionHistory.setSettingsKey(base + "LastSuppressionHistory"); - registerAspect(&detectCycles); detectCycles.setSettingsKey(base + "Callgrind.CycleDetection"); detectCycles.setDefaultValue(true); detectCycles.setLabelText("O"); // FIXME: Create a real icon detectCycles.setToolTip(Tr::tr("Enable cycle detection to properly handle recursive " "or circular function calls.")); - registerAspect(&costFormat); costFormat.setSettingsKey(base + "Callgrind.CostFormat"); costFormat.setDefaultValue(CostDelegate::FormatRelative); costFormat.setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox); - registerAspect(&shortenTemplates); shortenTemplates.setSettingsKey(base + "Callgrind.ShortenTemplates"); shortenTemplates.setDefaultValue(true); shortenTemplates.setLabelText("<>"); // FIXME: Create a real icon diff --git a/src/plugins/valgrind/valgrindsettings.h b/src/plugins/valgrind/valgrindsettings.h index 22952d0978d..29506b848f5 100644 --- a/src/plugins/valgrind/valgrindsettings.h +++ b/src/plugins/valgrind/valgrindsettings.h @@ -17,13 +17,14 @@ class SuppressionAspect final : public Utils::BaseAspect Q_OBJECT public: - explicit SuppressionAspect(bool global); + SuppressionAspect(Utils::AspectContainer *container, bool global); ~SuppressionAspect() final; + Utils::FilePaths operator()() const { return value(); } Utils::FilePaths value() const; void setValue(const Utils::FilePaths &val); - void addToLayout(Utils::Layouting::LayoutBuilder &builder) final; + void addToLayout(Layouting::LayoutItem &parent) final; void fromMap(const QVariantMap &map) final; void toMap(QVariantMap &map) const final; @@ -61,16 +62,13 @@ public: LeakCheckOnFinishYes }; -signals: - void changed(); // sent when multiple values have changed simulatenously (e.g. fromMap) - /** * Base valgrind settings */ public: - Utils::StringAspect valgrindExecutable; - Utils::StringAspect valgrindArguments; - Utils::SelectionAspect selfModifyingCodeDetection; + Utils::FilePathAspect valgrindExecutable{this}; + Utils::StringAspect valgrindArguments{this}; + Utils::SelectionAspect selfModifyingCodeDetection{this}; SuppressionAspect suppressions; @@ -78,13 +76,13 @@ public: * Base memcheck settings */ public: - Utils::StringAspect memcheckArguments; - Utils::IntegerAspect numCallers; - Utils::SelectionAspect leakCheckOnFinish; - Utils::BoolAspect showReachable; - Utils::BoolAspect trackOrigins; - Utils::BoolAspect filterExternalIssues; - Utils::IntegersAspect visibleErrorKinds; + Utils::StringAspect memcheckArguments{this}; + Utils::IntegerAspect numCallers{this}; + Utils::SelectionAspect leakCheckOnFinish{this}; + Utils::BoolAspect showReachable{this}; + Utils::BoolAspect trackOrigins{this}; + Utils::BoolAspect filterExternalIssues{this}; + Utils::IntegersAspect visibleErrorKinds{this}; void setVisibleErrorKinds(const QList &); @@ -92,16 +90,16 @@ public: * Base callgrind settings */ public: - Utils::StringAspect callgrindArguments; - Utils::StringAspect kcachegrindExecutable; + Utils::StringAspect callgrindArguments{this}; + Utils::FilePathAspect kcachegrindExecutable{this}; - Utils::BoolAspect enableCacheSim; - Utils::BoolAspect enableBranchSim; - Utils::BoolAspect collectSystime; - Utils::BoolAspect collectBusEvents; - Utils::BoolAspect enableEventToolTips; - Utils::DoubleAspect minimumInclusiveCostRatio; - Utils::DoubleAspect visualizationMinimumInclusiveCostRatio; + Utils::BoolAspect enableCacheSim{this}; + Utils::BoolAspect enableBranchSim{this}; + Utils::BoolAspect collectSystime{this}; + Utils::BoolAspect collectBusEvents{this}; + Utils::BoolAspect enableEventToolTips{this}; + Utils::DoubleAspect minimumInclusiveCostRatio{this}; + Utils::DoubleAspect visualizationMinimumInclusiveCostRatio{this}; QVariantMap defaultSettings() const; }; @@ -126,16 +124,16 @@ public: void writeSettings() const; void readSettings(); - Utils::StringAspect lastSuppressionDirectory; - Utils::StringAspect lastSuppressionHistory; + Utils::StringAspect lastSuppressionDirectory{this}; + Utils::StringAspect lastSuppressionHistory{this}; /** * Global callgrind settings */ - Utils::SelectionAspect costFormat; - Utils::BoolAspect detectCycles; - Utils::BoolAspect shortenTemplates; + Utils::SelectionAspect costFormat{this}; + Utils::BoolAspect detectCycles{this}; + Utils::BoolAspect shortenTemplates{this}; }; diff --git a/src/plugins/vcpkg/CMakeLists.txt b/src/plugins/vcpkg/CMakeLists.txt new file mode 100644 index 00000000000..d3fb8d00ed8 --- /dev/null +++ b/src/plugins/vcpkg/CMakeLists.txt @@ -0,0 +1,17 @@ +add_qtc_plugin(Vcpkg + PLUGIN_DEPENDS Core ProjectExplorer + SOURCES + vcpkg.qrc + vcpkgconstants.h + vcpkgmanifesteditor.cpp vcpkgmanifesteditor.h + vcpkgplugin.cpp vcpkgplugin.h + vcpkgsearch.cpp vcpkgsearch.h + vcpkgsettings.cpp vcpkgsettings.h +) + +extend_qtc_plugin(Vcpkg + CONDITION WITH_TESTS + SOURCES + vcpkg_test.cpp vcpkg_test.h + EXPLICIT_MOC vcpkg_test.h +) diff --git a/src/plugins/vcpkg/Vcpkg.json.in b/src/plugins/vcpkg/Vcpkg.json.in new file mode 100644 index 00000000000..7357c9e845b --- /dev/null +++ b/src/plugins/vcpkg/Vcpkg.json.in @@ -0,0 +1,30 @@ +{ + \"Name\" : \"Vcpkg\", + \"Version\" : \"$$QTCREATOR_VERSION\", + \"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\", + \"Vendor\" : \"The Qt Company Ltd\", + \"Copyright\" : \"(C) $$QTCREATOR_COPYRIGHT_YEAR The Qt Company Ltd\", + \"License\" : [ \"Commercial Usage\", + \"\", + \"Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt 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.\", + \"\", + \"GNU General Public License Usage\", + \"\", + \"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html.\" + ], + \"Experimental\" : true, + \"Description\" : \"vcpkg integration.\", + \"Url\" : \"http://www.qt.io\", + $$dependencyList, + + \"Mimetypes\" : [ + \"\", + \"\", + \" \", + \" \", + \" Vcpkg Manifest File\", + \" \", + \" \", + \"\" + ] +} diff --git a/src/plugins/vcpkg/vcpkg.qbs b/src/plugins/vcpkg/vcpkg.qbs new file mode 100644 index 00000000000..dff796ab917 --- /dev/null +++ b/src/plugins/vcpkg/vcpkg.qbs @@ -0,0 +1,32 @@ +import qbs 1.0 + +QtcPlugin { + name: "Vcpkg" + + Depends { name: "Qt.widgets" } + Depends { name: "Utils" } + + Depends { name: "Core" } + Depends { name: "ProjectExplorer" } + Depends { name: "TextEditor" } + + files: [ + "vcpkg.qrc", + "vcpkgconstants.h", + "vcpkgmanifesteditor.cpp", + "vcpkgmanifesteditor.h", + "vcpkgplugin.cpp", + "vcpkgplugin.h", + "vcpkgsearch.cpp", + "vcpkgsearch.h", + "vcpkgsettings.cpp", + "vcpkgsettings.h", + ] + + QtcTestFiles { + files: [ + "vcpkg_test.h", + "vcpkg_test.cpp", + ] + } +} diff --git a/src/plugins/vcpkg/vcpkg.qrc b/src/plugins/vcpkg/vcpkg.qrc new file mode 100644 index 00000000000..a377b736db3 --- /dev/null +++ b/src/plugins/vcpkg/vcpkg.qrc @@ -0,0 +1,6 @@ + + + wizards/manifest/vcpkg.json.tpl + wizards/manifest/wizard.json + + diff --git a/src/plugins/vcpkg/vcpkg_test.cpp b/src/plugins/vcpkg/vcpkg_test.cpp new file mode 100644 index 00000000000..65079b96a4e --- /dev/null +++ b/src/plugins/vcpkg/vcpkg_test.cpp @@ -0,0 +1,110 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "vcpkg_test.h" + +#include "vcpkgsearch.h" + +#include + +namespace Vcpkg::Internal { + +VcpkgSearchTest::VcpkgSearchTest(QObject *parent) + : QObject(parent) +{ } + +VcpkgSearchTest::~VcpkgSearchTest() = default; + +void VcpkgSearchTest::testVcpkgJsonParser_data() +{ + QTest::addColumn("vcpkgManifestJsonData"); + QTest::addColumn("name"); + QTest::addColumn("version"); + QTest::addColumn("license"); + QTest::addColumn("shortDescription"); + QTest::addColumn("description"); + QTest::addColumn("homepage"); + QTest::addColumn("success"); + + QTest::newRow("cimg, version, short description") + << R"({ + "name": "cimg", + "version": "2.9.9", + "description": "The CImg Library is a small, open-source, and modern C++ toolkit for image processing", + "homepage": "https://github.com/dtschump/CImg", + "dependencies": [ + { + "name": "vcpkg-cmake", + "host": true + } + ] + })" + << "cimg" + << "2.9.9" + << "" + << "The CImg Library is a small, open-source, and modern C++ toolkit for image processing" + << QStringList() + << QUrl::fromUserInput("https://github.com/dtschump/CImg") + << true; + + QTest::newRow("catch-classic, version-string, complete description") + << R"({ + "name": "catch-classic", + "version-string": "1.12.2", + "port-version": 1, + "description": [ + "A modern, header-only test framework for unit tests", + "This is specifically the legacy 1.x branch provided for compatibility", + "with older compilers." + ], + "homepage": "https://github.com/catchorg/Catch2" + })" + << "catch-classic" + << "1.12.2" + << "" + << "A modern, header-only test framework for unit tests" + << QStringList({"This is specifically the legacy 1.x branch provided for compatibility", + "with older compilers."}) + << QUrl::fromUserInput("https://github.com/catchorg/Catch2") + << true; + + QTest::newRow("Incomplete") + << R"({ + "version-semver": "1.0", + "description": "foo", + "license": "WTFPL" + })" + << "" + << "1.0" + << "WTFPL" + << "foo" + << QStringList() + << QUrl() + << false; +} + +void VcpkgSearchTest::testVcpkgJsonParser() +{ + QFETCH(QString, vcpkgManifestJsonData); + QFETCH(QString, name); + QFETCH(QString, version); + QFETCH(QString, license); + QFETCH(QString, shortDescription); + QFETCH(QStringList, description); + QFETCH(QUrl, homepage); + QFETCH(bool, success); + + bool ok = false; + const Search::VcpkgManifest mf = + Search::parseVcpkgManifest(vcpkgManifestJsonData.toUtf8(), &ok); + + QCOMPARE(mf.name, name); + QCOMPARE(mf.version, version); + QCOMPARE(mf.license, license); + QCOMPARE(mf.shortDescription, shortDescription); + QCOMPARE(mf.description, description); + QCOMPARE(mf.homepage, homepage); + QCOMPARE(ok, success); +} + +} // namespace Vcpkg::Internal diff --git a/src/plugins/vcpkg/vcpkg_test.h b/src/plugins/vcpkg/vcpkg_test.h new file mode 100644 index 00000000000..8175e28d594 --- /dev/null +++ b/src/plugins/vcpkg/vcpkg_test.h @@ -0,0 +1,24 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include +#include + +namespace Vcpkg::Internal { + +class VcpkgSearchTest : public QObject +{ + Q_OBJECT + +public: + VcpkgSearchTest(QObject *parent = nullptr); + ~VcpkgSearchTest(); + +private slots: + void testVcpkgJsonParser_data(); + void testVcpkgJsonParser(); +}; + +} // namespace Vcpkg::Internal diff --git a/src/plugins/vcpkg/vcpkgconstants.h b/src/plugins/vcpkg/vcpkgconstants.h new file mode 100644 index 00000000000..644dfab95f9 --- /dev/null +++ b/src/plugins/vcpkg/vcpkgconstants.h @@ -0,0 +1,14 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +namespace Vcpkg::Constants { + +const char TOOLSSETTINGSPAGE_ID[] = "Vcpkg.VcpkgSettings"; +const char WEBSITE_URL[] = "https://vcpkg.io/"; +const char ENVVAR_VCPKG_ROOT[] = "VCPKG_ROOT"; +const char VCPKGMANIFEST_EDITOR_ID[] = "Vcpkg.VcpkgManifestEditor"; +const char VCPKGMANIFEST_MIMETYPE[] = "application/vcpkg.manifest+json"; + +} // namespace Vcpkg::Constants diff --git a/src/plugins/vcpkg/vcpkgmanifesteditor.cpp b/src/plugins/vcpkg/vcpkgmanifesteditor.cpp new file mode 100644 index 00000000000..40e4837cf77 --- /dev/null +++ b/src/plugins/vcpkg/vcpkgmanifesteditor.cpp @@ -0,0 +1,72 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "vcpkgmanifesteditor.h" + +#include "vcpkgconstants.h" +#include "vcpkgsearch.h" +#include "vcpkgsettings.h" +#include "vcpkgtr.h" + +#include + +#include + +#include + +#include + +namespace Vcpkg::Internal { + +class VcpkgManifestEditorWidget : public TextEditor::TextEditorWidget +{ +public: + VcpkgManifestEditorWidget() + { + m_searchPkgAction = toolBar()->addAction(Utils::Icons::ZOOM_TOOLBAR.icon(), + Tr::tr("Search package...")); + connect(m_searchPkgAction, &QAction::triggered, this, [this] { + const Search::VcpkgManifest package = Search::showVcpkgPackageSearchDialog(); + if (!package.name.isEmpty()) + textCursor().insertText(package.name); + }); + updateToolBar(); + + QAction *optionsAction = toolBar()->addAction(Utils::Icons::SETTINGS_TOOLBAR.icon(), + Core::ICore::msgShowOptionsDialog()); + connect(optionsAction, &QAction::triggered, [] { + Core::ICore::showOptionsDialog(Constants::TOOLSSETTINGSPAGE_ID); + }); + + connect(&settings().vcpkgRoot, &Utils::BaseAspect::changed, + this, &VcpkgManifestEditorWidget::updateToolBar); + } + + void updateToolBar() + { + Utils::FilePath vcpkg = settings().vcpkgRoot().pathAppended("vcpkg").withExecutableSuffix(); + m_searchPkgAction->setEnabled(vcpkg.isExecutableFile()); + } + +private: + QAction *m_searchPkgAction; +}; + +static TextEditor::TextDocument *createVcpkgManifestDocument() +{ + auto doc = new TextEditor::TextDocument; + doc->setId(Constants::VCPKGMANIFEST_EDITOR_ID); + return doc; +} + +VcpkgManifestEditorFactory::VcpkgManifestEditorFactory() +{ + setId(Constants::VCPKGMANIFEST_EDITOR_ID); + setDisplayName(Tr::tr("Vcpkg Manifest Editor")); + addMimeType(Constants::VCPKGMANIFEST_MIMETYPE); + setDocumentCreator(createVcpkgManifestDocument); + setEditorWidgetCreator([] { return new VcpkgManifestEditorWidget; }); + setUseGenericHighlighter(true); +} + +} // namespace Vcpkg::Internal diff --git a/src/plugins/vcpkg/vcpkgmanifesteditor.h b/src/plugins/vcpkg/vcpkgmanifesteditor.h new file mode 100644 index 00000000000..c7762d69df9 --- /dev/null +++ b/src/plugins/vcpkg/vcpkgmanifesteditor.h @@ -0,0 +1,16 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace Vcpkg::Internal { + +class VcpkgManifestEditorFactory : public TextEditor::TextEditorFactory +{ +public: + VcpkgManifestEditorFactory(); +}; + +} // namespace Vcpkg::Internal diff --git a/src/plugins/vcpkg/vcpkgplugin.cpp b/src/plugins/vcpkg/vcpkgplugin.cpp new file mode 100644 index 00000000000..4ef89499a76 --- /dev/null +++ b/src/plugins/vcpkg/vcpkgplugin.cpp @@ -0,0 +1,38 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "vcpkgplugin.h" + +#ifdef WITH_TESTS +#include "vcpkg_test.h" +#endif // WITH_TESTS +#include "vcpkgmanifesteditor.h" +#include "vcpkgsettings.h" + +#include + +namespace Vcpkg::Internal { + +class VcpkgPluginPrivate +{ +public: + VcpkgManifestEditorFactory manifestEditorFactory; + VcpkgSettings settings; +}; + +VcpkgPlugin::~VcpkgPlugin() +{ + delete d; +} + +void VcpkgPlugin::initialize() +{ + d = new VcpkgPluginPrivate; + ProjectExplorer::JsonWizardFactory::addWizardPath(":/vcpkg/wizards/"); + +#ifdef WITH_TESTS + addTest(); +#endif +} + +} // namespace Vcpkg::Internal diff --git a/src/plugins/vcpkg/vcpkgplugin.h b/src/plugins/vcpkg/vcpkgplugin.h new file mode 100644 index 00000000000..797083ea956 --- /dev/null +++ b/src/plugins/vcpkg/vcpkgplugin.h @@ -0,0 +1,26 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace ProjectExplorer { class Project; } + +namespace Vcpkg::Internal { + +class VcpkgPlugin final : public ExtensionSystem::IPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Vcpkg.json") + +public: + ~VcpkgPlugin(); + + void initialize() final; + +private: + class VcpkgPluginPrivate *d = nullptr; +}; + +} // namespace Vcpkg::Internal diff --git a/src/plugins/vcpkg/vcpkgsearch.cpp b/src/plugins/vcpkg/vcpkgsearch.cpp new file mode 100644 index 00000000000..b9315ec540c --- /dev/null +++ b/src/plugins/vcpkg/vcpkgsearch.cpp @@ -0,0 +1,214 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "vcpkgsearch.h" + +#include "qpushbutton.h" +#include "vcpkgsettings.h" +#include "vcpkgtr.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +using namespace Utils; + +namespace Vcpkg::Internal::Search { + +class VcpkgPackageSearchDialog : public QDialog +{ +public: + explicit VcpkgPackageSearchDialog(QWidget *parent); + + VcpkgManifest selectedPackage() const; + +private: + void listPackages(const QString &filter); + void showPackageDetails(const QString &packageName); + + VcpkgManifests m_allPackages; + VcpkgManifest m_selectedPackage; + + FancyLineEdit *m_packagesFilter; + ListWidget *m_packagesList; + QLineEdit *m_vcpkgName; + QLabel *m_vcpkgVersion; + QLabel *m_vcpkgLicense; + QTextBrowser *m_vcpkgDescription; + QLabel *m_vcpkgHomepage; + QDialogButtonBox *m_buttonBox; +}; + +VcpkgPackageSearchDialog::VcpkgPackageSearchDialog(QWidget *parent) + : QDialog(parent) +{ + resize(920, 400); + + m_packagesFilter = new FancyLineEdit; + m_packagesFilter->setFiltering(true); + m_packagesFilter->setFocus(); + m_packagesFilter->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); + + m_packagesList = new ListWidget; + m_packagesList->setMaximumWidth(300); + + m_vcpkgName = new QLineEdit; + m_vcpkgName->setReadOnly(true); + + m_vcpkgVersion = new QLabel; + m_vcpkgLicense = new QLabel; + m_vcpkgDescription = new QTextBrowser; + + m_vcpkgHomepage = new QLabel; + m_vcpkgHomepage->setOpenExternalLinks(true); + m_vcpkgHomepage->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); + m_vcpkgHomepage->setTextInteractionFlags(Qt::TextBrowserInteraction); + + m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Close); + + using namespace Layouting; + Column { + Row { + Column { + m_packagesFilter, + m_packagesList, + }, + Form { + Tr::tr("Name:"), m_vcpkgName, br, + Tr::tr("Version:"), m_vcpkgVersion, br, + Tr::tr("License:"), m_vcpkgLicense, br, + Tr::tr("Description:"), m_vcpkgDescription, br, + Tr::tr("Homepage:"), m_vcpkgHomepage, br, + }, + }, + m_buttonBox, + }.attachTo(this); + + m_allPackages = vcpkgManifests(settings().vcpkgRoot()); + + listPackages({}); + + connect(m_packagesFilter, &FancyLineEdit::filterChanged, + this, &VcpkgPackageSearchDialog::listPackages); + connect(m_packagesList, &ListWidget::currentTextChanged, + this, &VcpkgPackageSearchDialog::showPackageDetails); + connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + +VcpkgManifest VcpkgPackageSearchDialog::selectedPackage() const +{ + return m_selectedPackage; +} + +void VcpkgPackageSearchDialog::listPackages(const QString &filter) +{ + const VcpkgManifests filteredPackages = filtered(m_allPackages, + [&filter] (const VcpkgManifest &package) { + return filter.isEmpty() + || package.name.contains(filter, Qt::CaseInsensitive) + || package.shortDescription.contains(filter, Qt::CaseInsensitive) + || package.description.contains(filter, Qt::CaseInsensitive); + }); + QStringList names = transform(filteredPackages, [] (const VcpkgManifest &package) { + return package.name; + }); + names.sort(); + m_packagesList->clear(); + m_packagesList->addItems(names); +} + +void VcpkgPackageSearchDialog::showPackageDetails(const QString &packageName) +{ + const VcpkgManifest manifest = findOrDefault(m_allPackages, + [&packageName] (const VcpkgManifest &m) { + return m.name == packageName; + }); + + m_vcpkgName->setText(manifest.name); + m_vcpkgVersion->setText(manifest.version); + m_vcpkgLicense->setText(manifest.license); + QString description = manifest.shortDescription; + if (!manifest.description.isEmpty()) + description.append("

    " + manifest.description.join("

    ") + "

    "); + m_vcpkgDescription->setText(description); + m_vcpkgHomepage->setText(QString::fromLatin1("%1") + .arg(manifest.homepage.toDisplayString())); + + m_selectedPackage = manifest; + m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!manifest.name.isEmpty()); +} + +VcpkgManifest parseVcpkgManifest(const QByteArray &vcpkgManifestJsonData, bool *ok) +{ + // https://learn.microsoft.com/en-us/vcpkg/reference/vcpkg-json + VcpkgManifest result; + const QJsonObject jsonObject = QJsonDocument::fromJson(vcpkgManifestJsonData).object(); + if (const QJsonValue name = jsonObject.value("name"); !name.isUndefined()) + result.name = name.toString(); + for (const char *key : {"version", "version-semver", "version-date", "version-string"} ) { + if (const QJsonValue ver = jsonObject.value(QLatin1String(key)); !ver.isUndefined()) { + result.version = ver.toString(); + break; + } + } + if (const QJsonValue license = jsonObject.value("license"); !license.isUndefined()) + result.license = license.toString(); + if (const QJsonValue description = jsonObject.value("description"); !description.isUndefined()) { + if (description.isArray()) { + const QJsonArray descriptionLines = description.toArray(); + for (const QJsonValue &val : descriptionLines) { + const QString line = val.toString(); + if (result.shortDescription.isEmpty()) { + result.shortDescription = line; + continue; + } + result.description.append(line); + } + } else { + result.shortDescription = description.toString(); + } + } + if (const QJsonValue homepage = jsonObject.value("homepage"); !homepage.isUndefined()) + result.homepage = QUrl::fromUserInput(homepage.toString()); + + if (ok) + *ok = !(result.name.isEmpty() || result.version.isEmpty()); + + return result; +} + +VcpkgManifests vcpkgManifests(const FilePath &vcpkgRoot) +{ + const FilePath portsDir = vcpkgRoot / "ports"; + VcpkgManifests result; + const FilePaths manifestFiles = + portsDir.dirEntries({{"vcpkg.json"}, QDir::Files, QDirIterator::Subdirectories}); + for (const FilePath &manifestFile : manifestFiles) { + FileReader reader; + if (reader.fetch(manifestFile)) { + const QByteArray &manifestData = reader.data(); + const VcpkgManifest manifest = parseVcpkgManifest(manifestData); + result.append(manifest); + } + } + return result; +} + +VcpkgManifest showVcpkgPackageSearchDialog(QWidget *parent) +{ + VcpkgPackageSearchDialog dlg(parent ? parent : Core::ICore::dialogParent()); + return (dlg.exec() == QDialog::Accepted) ? dlg.selectedPackage() : VcpkgManifest(); +} + +} // namespace Vcpkg::Internal::Search diff --git a/src/plugins/vcpkg/vcpkgsearch.h b/src/plugins/vcpkg/vcpkgsearch.h new file mode 100644 index 00000000000..bb2d568a00c --- /dev/null +++ b/src/plugins/vcpkg/vcpkgsearch.h @@ -0,0 +1,29 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include +#include + +#include + +namespace Vcpkg::Internal::Search { + +struct VcpkgManifest +{ + QString name; + QString version; + QString license; + QString shortDescription; + QStringList description; + QUrl homepage; +}; + +using VcpkgManifests = QList; + +VcpkgManifest parseVcpkgManifest(const QByteArray &vcpkgManifestJsonData, bool *ok = nullptr); +VcpkgManifests vcpkgManifests(const Utils::FilePath &vcpkgRoot); +VcpkgManifest showVcpkgPackageSearchDialog(QWidget *parent = nullptr); + +} // namespace Vcpkg::Internal::Search diff --git a/src/plugins/vcpkg/vcpkgsettings.cpp b/src/plugins/vcpkg/vcpkgsettings.cpp new file mode 100644 index 00000000000..3542ec47910 --- /dev/null +++ b/src/plugins/vcpkg/vcpkgsettings.cpp @@ -0,0 +1,68 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "vcpkgsettings.h" + +#include "vcpkgconstants.h" +#include "vcpkgtr.h" + +#include + +#include +#include +#include + +#include +#include + +namespace Vcpkg::Internal { + +static VcpkgSettings *theSettings = nullptr; + +VcpkgSettings &settings() +{ + return *theSettings; +} + +VcpkgSettings::VcpkgSettings() +{ + theSettings = this; + + setSettingsGroup("Vcpkg"); + setId(Constants::TOOLSSETTINGSPAGE_ID); + setDisplayName("Vcpkg"); + setCategory(CMakeProjectManager::Constants::Settings::CATEGORY); + + vcpkgRoot.setSettingsKey("VcpkgRoot"); + vcpkgRoot.setExpectedKind(Utils::PathChooser::ExistingDirectory); + vcpkgRoot.setDefaultValue(Utils::qtcEnvironmentVariable(Constants::ENVVAR_VCPKG_ROOT)); + + setLayouter([this] { + using namespace Layouting; + auto websiteButton = new QToolButton; + websiteButton->setIcon(Utils::Icons::ONLINE.icon()); + websiteButton->setToolTip(Constants::WEBSITE_URL); + + connect(websiteButton, &QAbstractButton::clicked, [] { + QDesktopServices::openUrl(QUrl::fromUserInput(Constants::WEBSITE_URL)); + }); + + // clang-format off + using namespace Layouting; + return Column { + Group { + title(Tr::tr("Vcpkg installation")), + Form { + Utils::PathChooser::label(), + Span { 2, Row { vcpkgRoot, websiteButton } }, + }, + }, + st, + }; + // clang-format on + }); + + readSettings(); +} + +} // Vcpkg::Internal diff --git a/src/plugins/vcpkg/vcpkgsettings.h b/src/plugins/vcpkg/vcpkgsettings.h new file mode 100644 index 00000000000..6a00fd506f7 --- /dev/null +++ b/src/plugins/vcpkg/vcpkgsettings.h @@ -0,0 +1,20 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace Vcpkg::Internal { + +class VcpkgSettings : public Core::PagedSettings +{ +public: + VcpkgSettings(); + + Utils::FilePathAspect vcpkgRoot{this}; +}; + +VcpkgSettings &settings(); + +} // Vcpkg::Internal diff --git a/src/plugins/vcpkg/vcpkgtr.h b/src/plugins/vcpkg/vcpkgtr.h new file mode 100644 index 00000000000..0914ce6444d --- /dev/null +++ b/src/plugins/vcpkg/vcpkgtr.h @@ -0,0 +1,15 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace Vcpkg { + +struct Tr +{ + Q_DECLARE_TR_FUNCTIONS(Vcpkg) +}; + +} // namespace Vcpkg diff --git a/src/plugins/vcpkg/wizards/manifest/vcpkg.json.tpl b/src/plugins/vcpkg/wizards/manifest/vcpkg.json.tpl new file mode 100644 index 00000000000..1519949d2ea --- /dev/null +++ b/src/plugins/vcpkg/wizards/manifest/vcpkg.json.tpl @@ -0,0 +1,6 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", + "name": "%{Name}", + "version-string": "%{VersionString}", +%{Dependencies} +} diff --git a/src/plugins/vcpkg/wizards/manifest/wizard.json b/src/plugins/vcpkg/wizards/manifest/wizard.json new file mode 100644 index 00000000000..80e8b0fcc52 --- /dev/null +++ b/src/plugins/vcpkg/wizards/manifest/wizard.json @@ -0,0 +1,80 @@ +{ + "version": 1, + "supportedProjectTypes": [ ], + "id": "VcpkgManifest.Json", + "category": "U.VcpkgManifest", + "trDescription": "Creates a vcpkg.json manifest file.", + "trDisplayName": "vcpkg.json Manifest File", + "trDisplayCategory": "vcpkg", + "iconText": "json", + + "options": [ + { "key": "InitialFileName", "value": "vcpkg.json" }, + { "key": "TargetPath", "value": "%{Path}" } + ], + + "pages": + [ + { + "trDisplayName": "Location", + "trShortTitle": "Location", + "typeId": "File" + }, + { + "trDisplayName": "vcpkg.json Manifest File", + "trShortTitle": "Manifest fields", + "typeId": "Fields", + "data": + [ + { + "name": "Name", + "trDisplayName": "Name:", + "mandatory": true, + "type": "LineEdit", + "data": + { + "trText": "mypackage", + "validator": "^[a-z_0-9]+$" + } + }, + { + "name": "VersionString", + "trDisplayName": "Version string:", + "mandatory": true, + "type": "LineEdit", + "data": + { + "trText": "0.0.1" + } + }, + { + "name": "Dependencies", + "trDisplayName": "Dependencies:", + "mandatory": false, + "type": "TextEdit", + "data": + { + "trText": " \"dependencies\": [\n \"fmt\"\n ]" + } + } + ] + }, + { + "trDisplayName": "Project Management", + "trShortTitle": "Summary", + "typeId": "Summary" + } + ], + "generators": + [ + { + "typeId": "File", + "data": + { + "source": "vcpkg.json.tpl", + "target": "%{Path}/vcpkg.json", + "openInEditor": true + } + } + ] +} diff --git a/src/plugins/vcsbase/cleandialog.cpp b/src/plugins/vcsbase/cleandialog.cpp index bc5ddafe66d..ae0b14a7ba8 100644 --- a/src/plugins/vcsbase/cleandialog.cpp +++ b/src/plugins/vcsbase/cleandialog.cpp @@ -9,8 +9,8 @@ #include #include +#include #include -#include #include #include @@ -38,10 +38,10 @@ enum { nameColumn, columnCount }; enum { fileNameRole = Qt::UserRole, isDirectoryRole = Qt::UserRole + 1 }; // Helper for recursively removing files. -static void removeFileRecursion(QFutureInterface &futureInterface, - const QFileInfo &f, QString *errorMessage) +static void removeFileRecursion(QPromise &promise, const QFileInfo &f, + QString *errorMessage) { - if (futureInterface.isCanceled()) + if (promise.isCanceled()) return; // The version control system might list files/directory in arbitrary // order, causing files to be removed from parent directories. @@ -51,7 +51,7 @@ static void removeFileRecursion(QFutureInterface &futureInterface, const QDir dir(f.absoluteFilePath()); const QList infos = dir.entryInfoList(QDir::AllEntries|QDir::NoDotAndDotDot|QDir::Hidden); for (const QFileInfo &fi : infos) - removeFileRecursion(futureInterface, fi, errorMessage); + removeFileRecursion(promise, fi, errorMessage); QDir parent = f.absoluteDir(); if (!parent.rmdir(f.fileName())) errorMessage->append(Tr::tr("The directory %1 could not be deleted.") @@ -67,18 +67,19 @@ static void removeFileRecursion(QFutureInterface &futureInterface, } // Cleaning files in the background -static void runCleanFiles(QFutureInterface &futureInterface, - const FilePath &repository, const QStringList &files, +static void runCleanFiles(QPromise &promise, const FilePath &repository, + const QStringList &files, const std::function &errorHandler) { QString errorMessage; - futureInterface.setProgressRange(0, files.size()); - futureInterface.setProgressValue(0); + promise.setProgressRange(0, files.size()); + promise.setProgressValue(0); + int fileIndex = 0; for (const QString &name : files) { - removeFileRecursion(futureInterface, QFileInfo(name), &errorMessage); - if (futureInterface.isCanceled()) + removeFileRecursion(promise, QFileInfo(name), &errorMessage); + if (promise.isCanceled()) break; - futureInterface.setProgressValue(futureInterface.progressValue() + 1); + promise.setProgressValue(++fileIndex); } if (!errorMessage.isEmpty()) { // Format and emit error. @@ -258,8 +259,8 @@ bool CleanDialog::promptToDelete() return false; // Remove in background - QFuture task = runAsync(Internal::runCleanFiles, d->m_workingDirectory, - selectedFiles, Internal::handleError); + QFuture task = Utils::asyncRun(Internal::runCleanFiles, d->m_workingDirectory, + selectedFiles, Internal::handleError); const QString taskName = Tr::tr("Cleaning \"%1\"").arg(d->m_workingDirectory.toUserOutput()); Core::ProgressManager::addTask(task, taskName, "VcsBase.cleanRepository"); diff --git a/src/plugins/vcsbase/commonvcssettings.cpp b/src/plugins/vcsbase/commonvcssettings.cpp index 160095d38dc..d286164b5b9 100644 --- a/src/plugins/vcsbase/commonvcssettings.cpp +++ b/src/plugins/vcsbase/commonvcssettings.cpp @@ -20,8 +20,7 @@ using namespace Utils; -namespace VcsBase { -namespace Internal { +namespace VcsBase::Internal { // Return default for the ssh-askpass command (default to environment) static QString sshPasswordPromptDefault() @@ -39,27 +38,21 @@ CommonVcsSettings::CommonVcsSettings() setSettingsGroup("VCS"); setAutoApply(false); - registerAspect(&nickNameMailMap); nickNameMailMap.setSettingsKey("NickNameMailMap"); - nickNameMailMap.setDisplayStyle(StringAspect::PathChooserDisplay); nickNameMailMap.setExpectedKind(PathChooser::File); nickNameMailMap.setHistoryCompleter("Vcs.NickMap.History"); nickNameMailMap.setLabelText(Tr::tr("User/&alias configuration file:")); nickNameMailMap.setToolTip(Tr::tr("A file listing nicknames in a 4-column mailmap format:\n" "'name alias '.")); - registerAspect(&nickNameFieldListFile); nickNameFieldListFile.setSettingsKey("NickNameFieldListFile"); - nickNameFieldListFile.setDisplayStyle(StringAspect::PathChooserDisplay); nickNameFieldListFile.setExpectedKind(PathChooser::File); nickNameFieldListFile.setHistoryCompleter("Vcs.NickFields.History"); nickNameFieldListFile.setLabelText(Tr::tr("User &fields configuration file:")); nickNameFieldListFile.setToolTip(Tr::tr("A simple file containing lines with field names like " "\"Reviewed-By:\" which will be added below the submit editor.")); - registerAspect(&submitMessageCheckScript); submitMessageCheckScript.setSettingsKey("SubmitMessageCheckScript"); - submitMessageCheckScript.setDisplayStyle(StringAspect::PathChooserDisplay); submitMessageCheckScript.setExpectedKind(PathChooser::ExistingCommand); submitMessageCheckScript.setHistoryCompleter("Vcs.MessageCheckScript.History"); submitMessageCheckScript.setLabelText(Tr::tr("Submit message &check script:")); @@ -67,9 +60,7 @@ CommonVcsSettings::CommonVcsSettings() "in a temporary file as first argument. It should return with an exit != 0 and a message " "on standard error to indicate failure.")); - registerAspect(&sshPasswordPrompt); sshPasswordPrompt.setSettingsKey("SshPasswordPrompt"); - sshPasswordPrompt.setDisplayStyle(StringAspect::PathChooserDisplay); sshPasswordPrompt.setExpectedKind(PathChooser::ExistingCommand); sshPasswordPrompt.setHistoryCompleter("Vcs.SshPrompt.History"); sshPasswordPrompt.setDefaultValue(sshPasswordPromptDefault()); @@ -78,12 +69,10 @@ CommonVcsSettings::CommonVcsSettings() "for a password,\nshould a repository require SSH-authentication " "(see documentation on SSH and the environment variable SSH_ASKPASS).")); - registerAspect(&lineWrap); lineWrap.setSettingsKey("LineWrap"); lineWrap.setDefaultValue(true); lineWrap.setLabelText(Tr::tr("Wrap submit message at:")); - registerAspect(&lineWrapWidth); lineWrapWidth.setSettingsKey("LineWrapWidth"); lineWrapWidth.setSuffix(Tr::tr(" characters")); lineWrapWidth.setDefaultValue(72); @@ -94,61 +83,48 @@ CommonVcsSettings::CommonVcsSettings() class CommonSettingsWidget final : public Core::IOptionsPageWidget { public: - CommonSettingsWidget(CommonOptionsPage *page); + CommonSettingsWidget(CommonOptionsPage *page) + { + CommonVcsSettings &s = page->settings(); - void apply() final; + auto cacheResetButton = new QPushButton(Tr::tr("Reset VCS Cache")); + cacheResetButton->setToolTip(Tr::tr("Reset information about which " + "version control system handles which directory.")); -private: - void updatePath(); - CommonOptionsPage *m_page; -}; + auto updatePath = [&s] { + Environment env; + env.appendToPath(Core::VcsManager::additionalToolsPath()); + s.sshPasswordPrompt.setEnvironment(env); + }; + using namespace Layouting; + Column { + Row { s.lineWrap, s.lineWrapWidth, st }, + Form { + s.submitMessageCheckScript, br, + s.nickNameMailMap, br, + s.nickNameFieldListFile, br, + s.sshPasswordPrompt, br, + {}, cacheResetButton + } + }.attachTo(this); -CommonSettingsWidget::CommonSettingsWidget(CommonOptionsPage *page) - : m_page(page) -{ - CommonVcsSettings &s = m_page->settings(); + updatePath(); - auto cacheResetButton = new QPushButton(Tr::tr("Reset VCS Cache")); - cacheResetButton->setToolTip(Tr::tr("Reset information about which " - "version control system handles which directory.")); + connect(Core::VcsManager::instance(), &Core::VcsManager::configurationChanged, + this, updatePath); + connect(cacheResetButton, &QPushButton::clicked, + Core::VcsManager::instance(), &Core::VcsManager::clearVersionControlCache); - updatePath(); - - using namespace Layouting; - Column { - Row { s.lineWrap, s.lineWrapWidth, st }, - Form { - s.submitMessageCheckScript, - s.nickNameMailMap, - s.nickNameFieldListFile, - s.sshPasswordPrompt, - {}, cacheResetButton - } - }.attachTo(this); - - connect(Core::VcsManager::instance(), &Core::VcsManager::configurationChanged, - this, &CommonSettingsWidget::updatePath); - connect(cacheResetButton, &QPushButton::clicked, - Core::VcsManager::instance(), &Core::VcsManager::clearVersionControlCache); -} - -void CommonSettingsWidget::updatePath() -{ - EnvironmentChange change; - change.addAppendToPath(Core::VcsManager::additionalToolsPath()); - m_page->settings().sshPasswordPrompt.setEnvironmentChange(change); -} - -void CommonSettingsWidget::apply() -{ - CommonVcsSettings &s = m_page->settings(); - if (s.isDirty()) { - s.apply(); - s.writeSettings(Core::ICore::settings()); - emit m_page->settingsChanged(); + setOnApply([&s] { + if (s.isDirty()) { + s.apply(); + s.writeSettings(Core::ICore::settings()); + emit s.settingsChanged(); + } + }); } -} +}; // CommonOptionsPage @@ -165,5 +141,4 @@ CommonOptionsPage::CommonOptionsPage() setWidgetCreator([this] { return new CommonSettingsWidget(this); }); } -} // namespace Internal -} // namespace VcsBase +} // VcsBase::Internal diff --git a/src/plugins/vcsbase/commonvcssettings.h b/src/plugins/vcsbase/commonvcssettings.h index 8d781a45d72..a5005f252a5 100644 --- a/src/plugins/vcsbase/commonvcssettings.h +++ b/src/plugins/vcsbase/commonvcssettings.h @@ -7,43 +7,39 @@ #include -namespace VcsBase { -namespace Internal { +namespace VcsBase::Internal { class CommonVcsSettings : public Utils::AspectContainer -{ -public: - CommonVcsSettings(); - - friend QDebug operator<<(QDebug, const CommonVcsSettings &); - - Utils::StringAspect nickNameMailMap; - Utils::StringAspect nickNameFieldListFile; - - Utils::StringAspect submitMessageCheckScript; - - // Executable run to graphically prompt for a SSH-password. - Utils::StringAspect sshPasswordPrompt; - - Utils::BoolAspect lineWrap; - Utils::IntegerAspect lineWrapWidth; -}; - -class CommonOptionsPage final : public Core::IOptionsPage { Q_OBJECT public: - explicit CommonOptionsPage(); + CommonVcsSettings(); - CommonVcsSettings &settings() { return m_settings; } + Utils::FilePathAspect nickNameMailMap{this}; + Utils::FilePathAspect nickNameFieldListFile{this}; + + Utils::FilePathAspect submitMessageCheckScript{this}; + + // Executable run to graphically prompt for a SSH-password. + Utils::FilePathAspect sshPasswordPrompt{this}; + + Utils::BoolAspect lineWrap{this}; + Utils::IntegerAspect lineWrapWidth{this}; signals: void settingsChanged(); +}; + +class CommonOptionsPage final : public Core::IOptionsPage +{ +public: + CommonOptionsPage(); + + CommonVcsSettings &settings() { return m_settings; } private: CommonVcsSettings m_settings; }; -} // namespace Internal -} // namespace VcsBase +} // VcsBase::Internal diff --git a/src/plugins/vcsbase/submiteditorwidget.cpp b/src/plugins/vcsbase/submiteditorwidget.cpp index eaac1d61d2d..00b4200580a 100644 --- a/src/plugins/vcsbase/submiteditorwidget.cpp +++ b/src/plugins/vcsbase/submiteditorwidget.cpp @@ -127,8 +127,6 @@ struct SubmitEditorWidgetPrivate SubmitEditorWidget::SubmitEditorWidget() : d(new SubmitEditorWidgetPrivate) { - resize(507, 419); - setMinimumSize(QSize(0, 0)); setWindowTitle(Tr::tr("Subversion Submit")); auto scrollAreaWidgetContents = new QWidget(); @@ -208,8 +206,9 @@ SubmitEditorWidget::SubmitEditorWidget() : using namespace Layouting; Column { - scrollArea - }.attachTo(this, WithoutMargins); + scrollArea, + noMargin + }.attachTo(this); connect(d->description, &QWidget::customContextMenuRequested, this, &SubmitEditorWidget::editorCustomContextMenuRequested); @@ -620,15 +619,16 @@ void SubmitEditorWidget::verifyDescription() d->descriptionHint->setText(hints.join("
    ")); if (!d->descriptionHint->text().isEmpty()) { + static_assert(MaxSubjectLength == 72); // change the translated message below when changing d->descriptionHint->setToolTip( Tr::tr("

    Writing good commit messages

    " "
      " "
    • Avoid very short commit messages.
    • " - "
    • Consider the first line as subject (like in email) " - "and keep it shorter than %n characters.
    • " + "
    • Consider the first line as a subject (like in emails) " + "and keep it shorter than 72 characters.
    • " "
    • After an empty second line, a longer description can be added.
    • " "
    • Describe why the change was done, not how it was done.
    • " - "
    ", nullptr, MaxSubjectLength)); + "")); } } diff --git a/src/plugins/vcsbase/vcsbaseclient.cpp b/src/plugins/vcsbase/vcsbaseclient.cpp index c51b1292f9a..ffa19b24f78 100644 --- a/src/plugins/vcsbase/vcsbaseclient.cpp +++ b/src/plugins/vcsbase/vcsbaseclient.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -56,7 +57,7 @@ namespace VcsBase { VcsBaseClientImpl::VcsBaseClientImpl(VcsBaseSettings *baseSettings) : m_baseSettings(baseSettings) { - m_baseSettings->readSettings(ICore::settings()); + m_baseSettings->readSettings(); connect(ICore::instance(), &ICore::saveSettingsRequested, this, &VcsBaseClientImpl::saveSettings); } @@ -68,7 +69,7 @@ VcsBaseSettings &VcsBaseClientImpl::settings() const FilePath VcsBaseClientImpl::vcsBinary() const { - return m_baseSettings->binaryPath.filePath(); + return m_baseSettings->binaryPath(); } VcsCommand *VcsBaseClientImpl::createCommand(const FilePath &workingDirectory, @@ -89,6 +90,16 @@ VcsCommand *VcsBaseClientImpl::createCommand(const FilePath &workingDirectory, return cmd; } +void VcsBaseClientImpl::setupCommand(Utils::Process &process, + const FilePath &workingDirectory, + const QStringList &args) const +{ + process.setEnvironment(processEnvironment()); + process.setWorkingDirectory(workingDirectory); + process.setCommand({vcsBinary(), args}); + process.setUseCtrlCStub(true); +} + void VcsBaseClientImpl::enqueueJob(VcsCommand *cmd, const QStringList &args, const ExitCodeInterpreter &interpreter) const { @@ -193,7 +204,7 @@ void VcsBaseClientImpl::vcsExecWithEditor(const Utils::FilePath &workingDirector int VcsBaseClientImpl::vcsTimeoutS() const { - return m_baseSettings->timeout.value(); + return m_baseSettings->timeout(); } VcsCommand *VcsBaseClientImpl::createVcsCommand(const FilePath &defaultWorkingDir, @@ -224,6 +235,7 @@ VcsBaseEditorWidget *VcsBaseClientImpl::createVcsEditor(Id kind, QString title, connect(baseEditor, &VcsBaseEditorWidget::annotateRevisionRequested, this, &VcsBaseClientImpl::annotateRevisionRequested); baseEditor->setSource(source); + baseEditor->setDefaultLineNumber(1); if (codec) baseEditor->setCodec(codec); } diff --git a/src/plugins/vcsbase/vcsbaseclient.h b/src/plugins/vcsbase/vcsbaseclient.h index 6703495c37f..035e2f00424 100644 --- a/src/plugins/vcsbase/vcsbaseclient.h +++ b/src/plugins/vcsbase/vcsbaseclient.h @@ -23,6 +23,10 @@ class QTextCodec; class QToolBar; QT_END_NAMESPACE +namespace Utils { +class Process; +} + namespace VcsBase { class CommandResult; @@ -56,6 +60,10 @@ public: VcsCommand *createCommand(const Utils::FilePath &workingDirectory, VcsBaseEditorWidget *editor = nullptr) const; + void setupCommand(Utils::Process &process, + const Utils::FilePath &workingDirectory, + const QStringList &args) const; + void enqueueJob(VcsCommand *cmd, const QStringList &args, const Utils::ExitCodeInterpreter &interpreter = {}) const; diff --git a/src/plugins/vcsbase/vcsbaseclientsettings.cpp b/src/plugins/vcsbase/vcsbaseclientsettings.cpp index 7dfd8b3760b..170437864a0 100644 --- a/src/plugins/vcsbase/vcsbaseclientsettings.cpp +++ b/src/plugins/vcsbase/vcsbaseclientsettings.cpp @@ -6,15 +6,7 @@ #include "vcsbasetr.h" #include -#include -#include #include -#include -#include -#include - -#include -#include using namespace Utils; @@ -22,27 +14,19 @@ namespace VcsBase { VcsBaseSettings::VcsBaseSettings() { - setAutoApply(false); - - registerAspect(&binaryPath); binaryPath.setSettingsKey("BinaryPath"); - registerAspect(&userName); userName.setSettingsKey("Username"); - registerAspect(&userEmail); userEmail.setSettingsKey("UserEmail"); - registerAspect(&logCount); logCount.setSettingsKey("LogCount"); logCount.setRange(0, 1000 * 1000); logCount.setDefaultValue(100); logCount.setLabelText(Tr::tr("Log count:")); - registerAspect(&path); path.setSettingsKey("Path"); - registerAspect(&timeout); timeout.setSettingsKey("Timeout"); timeout.setRange(0, 3600 * 24 * 365); timeout.setDefaultValue(30); diff --git a/src/plugins/vcsbase/vcsbaseclientsettings.h b/src/plugins/vcsbase/vcsbaseclientsettings.h index 87835755ca0..6e1f227f218 100644 --- a/src/plugins/vcsbase/vcsbaseclientsettings.h +++ b/src/plugins/vcsbase/vcsbaseclientsettings.h @@ -5,27 +5,24 @@ #include "vcsbase_global.h" -#include +#include namespace VcsBase { -class VCSBASE_EXPORT VcsBaseSettings : public Utils::AspectContainer +class VCSBASE_EXPORT VcsBaseSettings : public Core::PagedSettings { public: VcsBaseSettings(); ~VcsBaseSettings(); - Utils::StringAspect binaryPath; - Utils::StringAspect userName; - Utils::StringAspect userEmail; - Utils::IntegerAspect logCount; - Utils::IntegerAspect timeout; // Seconds - Utils::StringAspect path; + Utils::FilePathAspect binaryPath{this}; + Utils::StringAspect userName{this}; + Utils::StringAspect userEmail{this}; + Utils::IntegerAspect logCount{this}; + Utils::IntegerAspect timeout{this}; // Seconds + Utils::StringAspect path{this}; Utils::FilePaths searchPathList() const; - -private: - QString m_settingsGroup; }; } // namespace VcsBase diff --git a/src/plugins/vcsbase/vcsbasediffeditorcontroller.cpp b/src/plugins/vcsbase/vcsbasediffeditorcontroller.cpp index c0d31775af9..696e96b2916 100644 --- a/src/plugins/vcsbase/vcsbasediffeditorcontroller.cpp +++ b/src/plugins/vcsbase/vcsbasediffeditorcontroller.cpp @@ -2,28 +2,21 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "vcsbasediffeditorcontroller.h" -#include "vcsplugin.h" -#include +#include + +#include #include #include +#include #include -#include using namespace DiffEditor; +using namespace Tasking; using namespace Utils; namespace VcsBase { -static void readPatch(QFutureInterface> &futureInterface, const QString &patch) -{ - bool ok; - const QList &fileDataList = DiffUtils::readPatch(patch, &ok, &futureInterface); - futureInterface.reportResult(fileDataList); -} - -///////////////////// - class VcsBaseDiffEditorControllerPrivate { public: @@ -32,7 +25,7 @@ public: VcsBaseDiffEditorController *q; Environment m_processEnvironment; FilePath m_vcsBinary; - const Tasking::TreeStorage m_inputStorage; + const TreeStorage m_inputStorage; }; ///////////////////// @@ -47,33 +40,32 @@ VcsBaseDiffEditorController::~VcsBaseDiffEditorController() delete d; } -Tasking::TreeStorage VcsBaseDiffEditorController::inputStorage() const +TreeStorage VcsBaseDiffEditorController::inputStorage() const { return d->m_inputStorage; } -Tasking::TaskItem VcsBaseDiffEditorController::postProcessTask() +TaskItem VcsBaseDiffEditorController::postProcessTask() { - using namespace Tasking; - - const auto setupDiffProcessor = [this](AsyncTask> &async) { + const auto setupDiffProcessor = [this](Async> &async) { const QString *storage = inputStorage().activeStorage(); QTC_ASSERT(storage, qWarning("Using postProcessTask() requires putting inputStorage() " "into task tree's root group.")); const QString inputData = storage ? *storage : QString(); - async.setAsyncCallData(readPatch, inputData); - async.setFutureSynchronizer(Internal::VcsPlugin::futureSynchronizer()); + async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer()); + async.setConcurrentCallData(&DiffUtils::readPatchWithPromise, inputData); }; - const auto onDiffProcessorDone = [this](const AsyncTask> &async) { - setDiffFiles(async.result()); + const auto onDiffProcessorDone = [this](const Async> &async) { + setDiffFiles(async.isResultAvailable() ? async.result() : QList()); + // TODO: We should set the right starting line here }; - const auto onDiffProcessorError = [this](const AsyncTask> &) { + const auto onDiffProcessorError = [this](const Async> &) { setDiffFiles({}); }; - return Async>(setupDiffProcessor, onDiffProcessorDone, onDiffProcessorError); + return AsyncTask>(setupDiffProcessor, onDiffProcessorDone, onDiffProcessorError); } -void VcsBaseDiffEditorController::setupCommand(QtcProcess &process, const QStringList &args) const +void VcsBaseDiffEditorController::setupCommand(Process &process, const QStringList &args) const { process.setEnvironment(d->m_processEnvironment); process.setWorkingDirectory(workingDirectory()); diff --git a/src/plugins/vcsbase/vcsbasediffeditorcontroller.h b/src/plugins/vcsbase/vcsbasediffeditorcontroller.h index 75672edb1ff..63976bc26cb 100644 --- a/src/plugins/vcsbase/vcsbasediffeditorcontroller.h +++ b/src/plugins/vcsbase/vcsbasediffeditorcontroller.h @@ -9,7 +9,7 @@ namespace Utils { class Environment; -class QtcProcess; +class Process; } // Utils namespace VcsBase { @@ -28,10 +28,10 @@ public: void setVcsBinary(const Utils::FilePath &path); protected: - Utils::Tasking::TreeStorage inputStorage() const; - Utils::Tasking::TaskItem postProcessTask(); + Tasking::TreeStorage inputStorage() const; + Tasking::TaskItem postProcessTask(); - void setupCommand(Utils::QtcProcess &process, const QStringList &args) const; + void setupCommand(Utils::Process &process, const QStringList &args) const; private: friend class VcsBaseDiffEditorControllerPrivate; diff --git a/src/plugins/vcsbase/vcsbaseeditor.cpp b/src/plugins/vcsbase/vcsbaseeditor.cpp index c73c14f9f66..26c19c7f91e 100644 --- a/src/plugins/vcsbase/vcsbaseeditor.cpp +++ b/src/plugins/vcsbase/vcsbaseeditor.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include @@ -1234,7 +1234,7 @@ static QTextCodec *findProjectCodec(const FilePath &dirPath) { typedef QList ProjectList; // Try to find a project under which file tree the file is. - const ProjectList projects = ProjectExplorer::SessionManager::projects(); + const ProjectList projects = ProjectExplorer::ProjectManager::projects(); const ProjectExplorer::Project *p = findOrDefault(projects, equal(&ProjectExplorer::Project::projectDirectory, dirPath)); return p ? p->editorConfiguration()->textCodec() : nullptr; diff --git a/src/plugins/vcsbase/vcsbaseplugin.cpp b/src/plugins/vcsbase/vcsbaseplugin.cpp index b277a2d0680..b72657cdb3c 100644 --- a/src/plugins/vcsbase/vcsbaseplugin.cpp +++ b/src/plugins/vcsbase/vcsbaseplugin.cpp @@ -9,14 +9,16 @@ #include "vcsplugin.h" #include +#include #include #include -#include -#include + #include -#include +#include +#include + +#include #include -#include #include #include @@ -195,7 +197,7 @@ StateListener::StateListener(QObject *parent) : QObject(parent) connect(ProjectTree::instance(), &ProjectTree::currentProjectChanged, this, &StateListener::slotStateChanged); - connect(SessionManager::instance(), &SessionManager::startupProjectChanged, + connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, this, &StateListener::slotStateChanged); EditorManager::setWindowTitleVcsTopicHandler(&StateListener::windowTitleVcsTopic); @@ -213,7 +215,7 @@ QString StateListener::windowTitleVcsTopic(const FilePath &filePath) searchPath = filePath.absolutePath(); } else { // use single project's information if there is only one loaded. - const QList projects = SessionManager::projects(); + const QList projects = ProjectManager::projects(); if (projects.size() == 1) searchPath = projects.first()->projectDirectory(); } @@ -282,7 +284,7 @@ void StateListener::slotStateChanged() IVersionControl *projectControl = nullptr; Project *currentProject = ProjectTree::currentProject(); if (!currentProject) - currentProject = SessionManager::startupProject(); + currentProject = ProjectManager::startupProject(); if (currentProject) { state.currentProjectPath = currentProject->projectDirectory(); @@ -591,9 +593,27 @@ bool VcsBasePluginPrivate::enableMenuAction(ActionState as, QAction *menuAction) QString VcsBasePluginPrivate::commitDisplayName() const { + //: Name of the "commit" action of the VCS return Tr::tr("Commit", "name of \"commit\" action of the VCS."); } +QString VcsBasePluginPrivate::commitAbortTitle() const +{ + return Tr::tr("Close Commit Editor"); +} + +QString VcsBasePluginPrivate::commitAbortMessage() const +{ + return Tr::tr("Closing this editor will abort the commit."); +} + +QString VcsBasePluginPrivate::commitErrorMessage(const QString &error) const +{ + if (error.isEmpty()) + return Tr::tr("Cannot commit."); + return Tr::tr("Cannot commit: %1.").arg(error); +} + void VcsBasePluginPrivate::commitFromEditor() { QTC_ASSERT(m_submitEditor, return); diff --git a/src/plugins/vcsbase/vcsbaseplugin.h b/src/plugins/vcsbase/vcsbaseplugin.h index af96e12631a..5a4c9ae6b30 100644 --- a/src/plugins/vcsbase/vcsbaseplugin.h +++ b/src/plugins/vcsbase/vcsbaseplugin.h @@ -135,6 +135,9 @@ public: const QStringList &extraArgs); // Display name of the commit action virtual QString commitDisplayName() const; + virtual QString commitAbortTitle() const; + virtual QString commitAbortMessage() const; + virtual QString commitErrorMessage(const QString &error) const; void commitFromEditor(); virtual bool activateCommit() = 0; diff --git a/src/plugins/vcsbase/vcsbasesubmiteditor.cpp b/src/plugins/vcsbase/vcsbasesubmiteditor.cpp index eaa825adb10..5ddab0da77f 100644 --- a/src/plugins/vcsbase/vcsbasesubmiteditor.cpp +++ b/src/plugins/vcsbase/vcsbasesubmiteditor.cpp @@ -28,8 +28,8 @@ #include #include #include +#include #include -#include #include #include @@ -37,7 +37,7 @@ #include #include -#include +#include #include #include @@ -230,8 +230,8 @@ VcsBaseSubmitEditor::~VcsBaseSubmitEditor() void VcsBaseSubmitEditor::slotUpdateEditorSettings() { const CommonVcsSettings &s = VcsPlugin::instance()->settings(); - setLineWrapWidth(s.lineWrapWidth.value()); - setLineWrap(s.lineWrap.value()); + setLineWrapWidth(s.lineWrapWidth()); + setLineWrap(s.lineWrap()); } // Return a trimmed list of non-empty field texts @@ -453,11 +453,7 @@ void VcsBaseSubmitEditor::accept(VcsBasePluginPrivate *plugin) QString errorMessage; const bool canCommit = checkSubmitMessage(&errorMessage) && submitWidget->canSubmit(&errorMessage); if (!canCommit) { - VcsOutputWindow::appendError( - Tr::tr("Cannot %1%2.", - "%2 is an optional error message with ': ' prefix. Don't add space in front.") - .arg(plugin->commitDisplayName().toLower(), - errorMessage.isEmpty() ? errorMessage : ": " + errorMessage)); + VcsOutputWindow::appendError(plugin->commitErrorMessage(errorMessage)); } else if (plugin->activateCommit()) { close(); } @@ -480,12 +476,10 @@ bool VcsBaseSubmitEditor::promptSubmit(VcsBasePluginPrivate *plugin) if (!submitWidget->isEnabled() || !submitWidget->isEdited()) return true; - const QString commitName = plugin->commitDisplayName(); QMessageBox mb(Core::ICore::dialogParent()); - mb.setWindowTitle(Tr::tr("Close %1 %2 Editor").arg(plugin->displayName(), commitName)); + mb.setWindowTitle(plugin->commitAbortTitle()); mb.setIcon(QMessageBox::Warning); - mb.setText(Tr::tr("Closing this editor will abort the %1.") - .arg(commitName.toLower())); + mb.setText(plugin->commitAbortMessage()); mb.setStandardButtons(QMessageBox::Close | QMessageBox::Cancel); // On Windows there is no mnemonic for Close. Set it explicitly. mb.button(QMessageBox::Close)->setText(Tr::tr("&Close")); @@ -561,7 +555,7 @@ bool VcsBaseSubmitEditor::runSubmitMessageCheckScript(const QString &checkScript // Run check process VcsOutputWindow::appendShellCommandLine(msgCheckScript(d->m_checkScriptWorkingDirectory, checkScript)); - QtcProcess checkProcess; + Process checkProcess; if (!d->m_checkScriptWorkingDirectory.isEmpty()) checkProcess.setWorkingDirectory(d->m_checkScriptWorkingDirectory); checkProcess.setCommand({FilePath::fromString(checkScript), {saver.filePath().toString()}}); @@ -606,7 +600,7 @@ void VcsBaseSubmitEditor::filterUntrackedFilesOfProject(const FilePath &reposito { for (QStringList::iterator it = untrackedFiles->begin(); it != untrackedFiles->end(); ) { const FilePath path = repositoryDirectory.resolvePath(*it).absoluteFilePath(); - if (ProjectExplorer::SessionManager::projectForFile(path)) + if (ProjectExplorer::ProjectManager::projectForFile(path)) ++it; else it = untrackedFiles->erase(it); diff --git a/src/plugins/vcsbase/vcscommand.cpp b/src/plugins/vcsbase/vcscommand.cpp index 5a4f43d28cb..885ff004dab 100644 --- a/src/plugins/vcsbase/vcscommand.cpp +++ b/src/plugins/vcsbase/vcscommand.cpp @@ -10,8 +10,8 @@ #include #include +#include #include -#include #include #include @@ -55,10 +55,10 @@ public: void setup(); void cleanup(); - void setupProcess(QtcProcess *process, const Job &job); - void installStdCallbacks(QtcProcess *process); + void setupProcess(Process *process, const Job &job); + void installStdCallbacks(Process *process); EventLoopMode eventLoopMode() const; - void handleDone(QtcProcess *process); + void handleDone(Process *process); void startAll(); void startNextJob(); void processDone(); @@ -73,7 +73,7 @@ public: QList m_jobs; int m_currentJob = 0; - std::unique_ptr m_process; + std::unique_ptr m_process; QString m_stdOut; QString m_stdErr; ProcessResult m_result = ProcessResult::StartFailed; @@ -99,7 +99,7 @@ void VcsCommandPrivate::cleanup() GlobalFileChangeBlocker::instance()->forceBlocked(false); } -void VcsCommandPrivate::setupProcess(QtcProcess *process, const Job &job) +void VcsCommandPrivate::setupProcess(Process *process, const Job &job) { process->setExitCodeInterpreter(job.exitCodeInterpreter); process->setTimeoutS(job.timeoutS); @@ -127,12 +127,12 @@ void VcsCommandPrivate::setupProcess(QtcProcess *process, const Job &job) progress->setProgressParser(m_progressParser); } -void VcsCommandPrivate::installStdCallbacks(QtcProcess *process) +void VcsCommandPrivate::installStdCallbacks(Process *process) { if (!(m_flags & RunFlags::MergeOutputChannels) && (m_flags & RunFlags::ProgressiveOutput || m_progressParser || !(m_flags & RunFlags::SuppressStdErr))) { process->setTextChannelMode(Channel::Error, TextChannelMode::MultiLine); - connect(process, &QtcProcess::textOnStandardError, this, [this](const QString &text) { + connect(process, &Process::textOnStandardError, this, [this](const QString &text) { if (!(m_flags & RunFlags::SuppressStdErr)) VcsOutputWindow::appendError(text); if (m_flags & RunFlags::ProgressiveOutput) @@ -142,7 +142,7 @@ void VcsCommandPrivate::installStdCallbacks(QtcProcess *process) if (m_progressParser || m_flags & RunFlags::ProgressiveOutput || m_flags & RunFlags::ShowStdOut) { process->setTextChannelMode(Channel::Output, TextChannelMode::MultiLine); - connect(process, &QtcProcess::textOnStandardOutput, this, [this](const QString &text) { + connect(process, &Process::textOnStandardOutput, this, [this](const QString &text) { if (m_flags & RunFlags::ShowStdOut) VcsOutputWindow::append(text); if (m_flags & RunFlags::ProgressiveOutput) @@ -158,7 +158,7 @@ EventLoopMode VcsCommandPrivate::eventLoopMode() const return EventLoopMode::Off; } -void VcsCommandPrivate::handleDone(QtcProcess *process) +void VcsCommandPrivate::handleDone(Process *process) { // Success/Fail message in appropriate window? if (process->result() == ProcessResult::FinishedWithSuccess) { @@ -187,8 +187,8 @@ void VcsCommandPrivate::startAll() void VcsCommandPrivate::startNextJob() { QTC_ASSERT(m_currentJob < m_jobs.count(), return); - m_process.reset(new QtcProcess); - connect(m_process.get(), &QtcProcess::done, this, &VcsCommandPrivate::processDone); + m_process.reset(new Process); + connect(m_process.get(), &Process::done, this, &VcsCommandPrivate::processDone); setupProcess(m_process.get(), m_jobs.at(m_currentJob)); m_process->start(); } @@ -297,7 +297,7 @@ CommandResult VcsCommand::runBlocking(const Utils::FilePath &workingDirectory, CommandResult VcsCommand::runBlockingHelper(const CommandLine &command, int timeoutS) { - QtcProcess process; + Process process; if (command.executable().isEmpty()) return {}; @@ -321,7 +321,7 @@ void VcsCommand::setProgressParser(const ProgressParser &parser) d->m_progressParser = parser; } -CommandResult::CommandResult(const QtcProcess &process) +CommandResult::CommandResult(const Process &process) : m_result(process.result()) , m_exitCode(process.exitCode()) , m_exitMessage(process.exitMessage()) diff --git a/src/plugins/vcsbase/vcscommand.h b/src/plugins/vcsbase/vcscommand.h index 568d1095801..1cc4d5db54e 100644 --- a/src/plugins/vcsbase/vcscommand.h +++ b/src/plugins/vcsbase/vcscommand.h @@ -20,7 +20,7 @@ QT_END_NAMESPACE namespace Utils { class CommandLine; class Environment; -class QtcProcess; +class Process; } namespace VcsBase { @@ -33,7 +33,7 @@ class VCSBASE_EXPORT CommandResult { public: CommandResult() = default; - CommandResult(const Utils::QtcProcess &process); + CommandResult(const Utils::Process &process); CommandResult(const VcsCommand &command); CommandResult(Utils::ProcessResult result, const QString &exitMessage) : m_result(result), m_exitMessage(exitMessage) {} diff --git a/src/plugins/vcsbase/vcsenums.h b/src/plugins/vcsbase/vcsenums.h index 69f85f2e5d1..5a5b2c42a74 100644 --- a/src/plugins/vcsbase/vcsenums.h +++ b/src/plugins/vcsbase/vcsenums.h @@ -9,7 +9,7 @@ namespace VcsBase { enum class RunFlags { None = 0, // Empty. - // QtcProcess related + // Process related MergeOutputChannels = (1 << 0), // See QProcess::ProcessChannelMode::MergedChannels. ForceCLocale = (1 << 1), // Force C-locale, sets LANG and LANGUAGE env vars to "C". UseEventLoop = (1 << 2), // Use event loop when executed in UI thread with diff --git a/src/plugins/vcsbase/vcsoutputwindow.cpp b/src/plugins/vcsbase/vcsoutputwindow.cpp index 245de851346..816aa79f022 100644 --- a/src/plugins/vcsbase/vcsoutputwindow.cpp +++ b/src/plugins/vcsbase/vcsoutputwindow.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include #include #include @@ -444,7 +444,7 @@ QString VcsOutputWindow::msgExecutionLogEntry(const FilePath &workingDir, const + ' ' + formatArguments(command.splitArguments()); if (workingDir.isEmpty()) return Tr::tr("Running: %1").arg(maskedCmdline) + '\n'; - return Tr::tr("Running in %1: %2").arg(workingDir.toUserOutput(), maskedCmdline) + '\n'; + return Tr::tr("Running in \"%1\": %2.").arg(workingDir.toUserOutput(), maskedCmdline) + '\n'; } void VcsOutputWindow::appendShellCommandLine(const QString &text) diff --git a/src/plugins/vcsbase/vcsplugin.cpp b/src/plugins/vcsbase/vcsplugin.cpp index e5108942f57..0596f406b8b 100644 --- a/src/plugins/vcsbase/vcsplugin.cpp +++ b/src/plugins/vcsbase/vcsplugin.cpp @@ -23,7 +23,6 @@ #include #include -#include #include #include @@ -33,15 +32,47 @@ using namespace Core; using namespace ProjectExplorer; using namespace Utils; -namespace VcsBase { -namespace Internal { +namespace VcsBase::Internal { class VcsPluginPrivate { public: + explicit VcsPluginPrivate(VcsPlugin *plugin) + : q(plugin) + { + QObject::connect(&m_settingsPage.settings(), &CommonVcsSettings::settingsChanged, + [this] { slotSettingsChanged(); }); + slotSettingsChanged(); + } + + QStandardItemModel *nickNameModel() + { + if (!m_nickNameModel) { + m_nickNameModel = NickNameDialog::createModel(q); + populateNickNameModel(); + } + return m_nickNameModel; + } + + void populateNickNameModel() + { + QString errorMessage; + if (!NickNameDialog::populateModelFromMailCapFile(m_settingsPage.settings().nickNameMailMap.filePath(), + m_nickNameModel, + &errorMessage)) { + qWarning("%s", qPrintable(errorMessage)); + } + } + + void slotSettingsChanged() + { + if (m_nickNameModel) + populateNickNameModel(); + } + + VcsPlugin *q; CommonOptionsPage m_settingsPage; QStandardItemModel *m_nickNameModel = nullptr; - FutureSynchronizer m_futureSynchronizer; }; static VcsPlugin *m_instance = nullptr; @@ -61,7 +92,7 @@ VcsPlugin::~VcsPlugin() void VcsPlugin::initialize() { - d = new VcsPluginPrivate; + d = new VcsPluginPrivate(this); EditorManager::addCloseEditorListener([this](IEditor *editor) -> bool { bool result = true; @@ -70,11 +101,8 @@ void VcsPlugin::initialize() return result; }); - connect(&d->m_settingsPage, &CommonOptionsPage::settingsChanged, + connect(&d->m_settingsPage.settings(), &CommonVcsSettings::settingsChanged, this, &VcsPlugin::settingsChanged); - connect(&d->m_settingsPage, &CommonOptionsPage::settingsChanged, - this, &VcsPlugin::slotSettingsChanged); - slotSettingsChanged(); JsonWizardFactory::registerPageFactory(new Internal::VcsConfigurationPageFactory); JsonWizardFactory::registerPageFactory(new Internal::VcsCommandPageFactory); @@ -123,37 +151,11 @@ CommonVcsSettings &VcsPlugin::settings() const return d->m_settingsPage.settings(); } -FutureSynchronizer *VcsPlugin::futureSynchronizer() -{ - QTC_ASSERT(m_instance, return nullptr); - return &m_instance->d->m_futureSynchronizer; -} - /* Delayed creation/update of the nick name model. */ QStandardItemModel *VcsPlugin::nickNameModel() { - if (!d->m_nickNameModel) { - d->m_nickNameModel = NickNameDialog::createModel(this); - populateNickNameModel(); - } - return d->m_nickNameModel; + QTC_ASSERT(d, return nullptr); + return d->nickNameModel(); } -void VcsPlugin::populateNickNameModel() -{ - QString errorMessage; - if (!NickNameDialog::populateModelFromMailCapFile(settings().nickNameMailMap.filePath(), - d->m_nickNameModel, - &errorMessage)) { - qWarning("%s", qPrintable(errorMessage)); - } -} - -void VcsPlugin::slotSettingsChanged() -{ - if (d->m_nickNameModel) - populateNickNameModel(); -} - -} // namespace Internal -} // namespace VcsBase +} // VcsBase::Internal diff --git a/src/plugins/vcsbase/vcsplugin.h b/src/plugins/vcsbase/vcsplugin.h index 83157fc1408..6d4b35c11fd 100644 --- a/src/plugins/vcsbase/vcsplugin.h +++ b/src/plugins/vcsbase/vcsplugin.h @@ -5,14 +5,10 @@ #include -#include - QT_BEGIN_NAMESPACE class QStandardItemModel; QT_END_NAMESPACE -namespace Utils { class FutureSynchronizer; } - namespace VcsBase { class VcsBaseSubmitEditor; @@ -36,8 +32,6 @@ public: CommonVcsSettings &settings() const; - static Utils::FutureSynchronizer *futureSynchronizer(); - // Model of user nick names used for the submit // editor. Stored centrally here to achieve delayed // initialization and updating on settings change. @@ -48,9 +42,6 @@ signals: void submitEditorAboutToClose(VcsBase::VcsBaseSubmitEditor *e, bool *result); private: - void slotSettingsChanged(); - void populateNickNameModel(); - class VcsPluginPrivate *d = nullptr; }; diff --git a/src/plugins/vcsbase/wizard/vcscommandpage.cpp b/src/plugins/vcsbase/wizard/vcscommandpage.cpp index 1fef5540f40..0877e3ffbea 100644 --- a/src/plugins/vcsbase/wizard/vcscommandpage.cpp +++ b/src/plugins/vcsbase/wizard/vcscommandpage.cpp @@ -212,7 +212,6 @@ bool VcsCommandPageFactory::validateData(Id typeId, const QVariant &data, QStrin VcsCommandPage::VcsCommandPage() : m_startedStatus(Tr::tr("Command started...")) { - resize(264, 200); auto verticalLayout = new QVBoxLayout(this); m_logPlainTextEdit = new QPlainTextEdit; m_formatter = new OutputFormatter; diff --git a/src/plugins/webassembly/CMakeLists.txt b/src/plugins/webassembly/CMakeLists.txt index 3ee300b09ff..6609357f015 100644 --- a/src/plugins/webassembly/CMakeLists.txt +++ b/src/plugins/webassembly/CMakeLists.txt @@ -4,16 +4,16 @@ add_qtc_plugin(WebAssembly SOURCES webassembly.qrc webassembly_global.h - webassemblytr.h webassemblyconstants.h webassemblydevice.cpp webassemblydevice.h webassemblyemsdk.cpp webassemblyemsdk.h - webassemblyoptionspage.cpp webassemblyoptionspage.h webassemblyplugin.cpp webassemblyplugin.h webassemblyqtversion.cpp webassemblyqtversion.h - webassemblyrunconfigurationaspects.cpp webassemblyrunconfigurationaspects.h webassemblyrunconfiguration.cpp webassemblyrunconfiguration.h + webassemblyrunconfigurationaspects.cpp webassemblyrunconfigurationaspects.h + webassemblysettings.cpp webassemblysettings.h webassemblytoolchain.cpp webassemblytoolchain.h + webassemblytr.h ) extend_qtc_plugin(WebAssembly diff --git a/src/plugins/webassembly/webassembly.qbs b/src/plugins/webassembly/webassembly.qbs index 9c6e95f99f7..12b639238cb 100644 --- a/src/plugins/webassembly/webassembly.qbs +++ b/src/plugins/webassembly/webassembly.qbs @@ -13,29 +13,28 @@ QtcPlugin { files: [ "webassembly.qrc", - "webassembly_global.h", "webassemblytr.h", + "webassembly_global.h", "webassemblyconstants.h", "webassemblydevice.cpp", "webassemblydevice.h", "webassemblyemsdk.cpp", "webassemblyemsdk.h", - "webassemblyoptionspage.cpp", - "webassemblyoptionspage.h", "webassemblyplugin.cpp", "webassemblyplugin.h", "webassemblyqtversion.cpp", "webassemblyqtversion.h", - "webassemblyrunconfigurationaspects.cpp", - "webassemblyrunconfigurationaspects.h", "webassemblyrunconfiguration.cpp", "webassemblyrunconfiguration.h", + "webassemblyrunconfigurationaspects.cpp", + "webassemblyrunconfigurationaspects.h", + "webassemblysettings.cpp", + "webassemblysettings.h", "webassemblytoolchain.cpp", "webassemblytoolchain.h", + "webassemblytr.h", ] - Group { - name: "Unit tests" - condition: qtc.testsEnabled + QtcTestFiles { files: [ "webassembly_test.cpp", "webassembly_test.h", diff --git a/src/plugins/webassembly/webassemblyconstants.h b/src/plugins/webassembly/webassemblyconstants.h index 40f49f73a4f..56315b0b404 100644 --- a/src/plugins/webassembly/webassemblyconstants.h +++ b/src/plugins/webassembly/webassemblyconstants.h @@ -14,8 +14,5 @@ const char WEBASSEMBLY_DEVICE_DEVICE_ID[] = "WebAssembly Device"; const char WEBASSEMBLY_QT_VERSION[] = "Qt4ProjectManager.QtVersion.WebAssembly"; const char WEBASSEMBLY_RUNCONFIGURATION_EMRUN[] = "WebAssembly.RunConfiguration.Emrun"; -const char SETTINGS_GROUP[] = "WebAssembly"; -const char SETTINGS_KEY_EMSDK[] = "EmSdk"; - } // namespace WebAssembly } // namespace Constants diff --git a/src/plugins/webassembly/webassemblydevice.h b/src/plugins/webassembly/webassemblydevice.h index 6ff4a2481e7..520d0fb5355 100644 --- a/src/plugins/webassembly/webassemblydevice.h +++ b/src/plugins/webassembly/webassemblydevice.h @@ -4,6 +4,7 @@ #pragma once #include +#include namespace WebAssembly { namespace Internal { diff --git a/src/plugins/webassembly/webassemblyemsdk.cpp b/src/plugins/webassembly/webassemblyemsdk.cpp index 4acf6589c4d..ad12f12e62d 100644 --- a/src/plugins/webassembly/webassemblyemsdk.cpp +++ b/src/plugins/webassembly/webassemblyemsdk.cpp @@ -7,29 +7,36 @@ #include #include -#include +#include #include #include -#include using namespace Utils; namespace WebAssembly::Internal::WebAssemblyEmSdk { -using EmSdkEnvCache = QCache; -Q_GLOBAL_STATIC_WITH_ARGS(EmSdkEnvCache, emSdkEnvCache, (10)) -using EmSdkVersionCache = QCache; -Q_GLOBAL_STATIC_WITH_ARGS(EmSdkVersionCache, emSdkVersionCache, (10)) +using EmSdkEnvCache = QCache; +static EmSdkEnvCache *emSdkEnvCache() +{ + static EmSdkEnvCache cache(10); + return &cache; +} + +using EmSdkVersionCache = QCache; +static EmSdkVersionCache *emSdkVersionCache() +{ + static EmSdkVersionCache cache(10); + return &cache; +} static QString emSdkEnvOutput(const FilePath &sdkRoot) { - const QString cacheKey = sdkRoot.toString(); const bool isWindows = sdkRoot.osType() == OsTypeWindows; - if (!emSdkEnvCache()->contains(cacheKey)) { + if (!emSdkEnvCache()->contains(sdkRoot)) { const FilePath scriptFile = sdkRoot.pathAppended(QLatin1String("emsdk_env") + (isWindows ? ".bat" : ".sh")); - QtcProcess emSdkEnv; + Process emSdkEnv; if (isWindows) { emSdkEnv.setCommand(CommandLine(scriptFile)); } else { @@ -38,9 +45,9 @@ static QString emSdkEnvOutput(const FilePath &sdkRoot) } emSdkEnv.runBlocking(); const QString output = emSdkEnv.allOutput(); - emSdkEnvCache()->insert(cacheKey, new QString(output)); + emSdkEnvCache()->insert(sdkRoot, new QString(output)); } - return *emSdkEnvCache()->object(cacheKey); + return *emSdkEnvCache()->object(sdkRoot); } void parseEmSdkEnvOutputAndAddToEnv(const QString &output, Environment &env) @@ -81,37 +88,21 @@ QVersionNumber version(const FilePath &sdkRoot) { if (!sdkRoot.exists()) return {}; - const QString cacheKey = sdkRoot.toString(); - if (!emSdkVersionCache()->contains(cacheKey)) { + if (!emSdkVersionCache()->contains(sdkRoot)) { Environment env = sdkRoot.deviceEnvironment(); addToEnvironment(sdkRoot, env); QLatin1String scriptFile{sdkRoot.osType() == OsType::OsTypeWindows ? "emcc.bat" : "emcc"}; FilePath script = sdkRoot.withNewPath(scriptFile).searchInDirectories(env.path()); const CommandLine command(script, {"-dumpversion"}); - QtcProcess emcc; + Process emcc; emcc.setCommand(command); emcc.setEnvironment(env); emcc.runBlocking(); const QString version = emcc.cleanedStdOut(); - emSdkVersionCache()->insert(cacheKey, + emSdkVersionCache()->insert(sdkRoot, new QVersionNumber(QVersionNumber::fromString(version))); } - return *emSdkVersionCache()->object(cacheKey); -} - -void registerEmSdk(const FilePath &sdkRoot) -{ - QSettings *s = Core::ICore::settings(); - s->setValue(QLatin1String(Constants::SETTINGS_GROUP) + '/' - + QLatin1String(Constants::SETTINGS_KEY_EMSDK), sdkRoot.toString()); -} - -FilePath registeredEmSdk() -{ - QSettings *s = Core::ICore::settings(); - const QString path = s->value(QLatin1String(Constants::SETTINGS_GROUP) + '/' - + QLatin1String(Constants::SETTINGS_KEY_EMSDK)).toString(); - return FilePath::fromUserInput(path); + return *emSdkVersionCache()->object(sdkRoot); } void clearCaches() diff --git a/src/plugins/webassembly/webassemblyemsdk.h b/src/plugins/webassembly/webassemblyemsdk.h index 8310e0320f2..55d83abcd22 100644 --- a/src/plugins/webassembly/webassemblyemsdk.h +++ b/src/plugins/webassembly/webassemblyemsdk.h @@ -16,8 +16,6 @@ bool isValid(const Utils::FilePath &sdkRoot); void parseEmSdkEnvOutputAndAddToEnv(const QString &output, Utils::Environment &env); void addToEnvironment(const Utils::FilePath &sdkRoot, Utils::Environment &env); QVersionNumber version(const Utils::FilePath &sdkRoot); -void registerEmSdk(const Utils::FilePath &sdkRoot); -Utils::FilePath registeredEmSdk(); void clearCaches(); } // WebAssembly::Internal::WebAssemblyEmSdk diff --git a/src/plugins/webassembly/webassemblyoptionspage.cpp b/src/plugins/webassembly/webassemblyoptionspage.cpp deleted file mode 100644 index 1a4524b945a..00000000000 --- a/src/plugins/webassembly/webassemblyoptionspage.cpp +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "webassemblyconstants.h" -#include "webassemblyemsdk.h" -#include "webassemblyoptionspage.h" -#include "webassemblyqtversion.h" -#include "webassemblytoolchain.h" -#include "webassemblytr.h" - -#include -#include -#include -#include -#include - -#include -#include -#include - -using namespace Utils; - -namespace WebAssembly { -namespace Internal { - -class WebAssemblyOptionsWidget : public Core::IOptionsPageWidget -{ -public: - WebAssemblyOptionsWidget(); - - void updateStatus(); - -private: - void apply() final; - void showEvent(QShowEvent *event) final; - - PathChooser *m_emSdkPathChooser; - InfoLabel *m_emSdkVersionDisplay; - QGroupBox *m_emSdkEnvGroupBox; - QTextBrowser *m_emSdkEnvDisplay; - InfoLabel *m_qtVersionDisplay; -}; - -WebAssemblyOptionsWidget::WebAssemblyOptionsWidget() -{ - auto mainLayout = new QVBoxLayout(this); - - { - auto pathChooserBox = new QGroupBox(Tr::tr("Emscripten SDK path:")); - pathChooserBox->setFlat(true); - auto layout = new QVBoxLayout(pathChooserBox); - auto instruction = new QLabel( - Tr::tr("Select the root directory of an installed %1. " - "Ensure that the activated SDK version is compatible with the %2 " - "or %3 version that you plan to develop against.") - .arg(R"(Emscripten SDK)") - .arg(R"(Qt 5)") - .arg(R"(Qt 6)")); - - instruction->setOpenExternalLinks(true); - instruction->setWordWrap(true); - layout->addWidget(instruction); - m_emSdkPathChooser = new PathChooser(this); - m_emSdkPathChooser->setExpectedKind(PathChooser::Directory); - m_emSdkPathChooser->setInitialBrowsePathBackup(FileUtils::homePath()); - m_emSdkPathChooser->setFilePath(WebAssemblyEmSdk::registeredEmSdk()); - connect(m_emSdkPathChooser, &PathChooser::textChanged, - this, &WebAssemblyOptionsWidget::updateStatus); - layout->addWidget(m_emSdkPathChooser); - m_emSdkVersionDisplay = new InfoLabel(this); - m_emSdkVersionDisplay->setElideMode(Qt::ElideNone); - m_emSdkVersionDisplay->setWordWrap(true); - layout->addWidget(m_emSdkVersionDisplay); - mainLayout->addWidget(pathChooserBox); - } - - { - m_emSdkEnvGroupBox = new QGroupBox(Tr::tr("Emscripten SDK environment:")); - m_emSdkEnvGroupBox->setFlat(true); - m_emSdkEnvGroupBox->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::MinimumExpanding); - auto layout = new QVBoxLayout(m_emSdkEnvGroupBox); - m_emSdkEnvDisplay = new QTextBrowser; - m_emSdkEnvDisplay->setLineWrapMode(QTextBrowser::NoWrap); - m_emSdkEnvDisplay->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); - layout->addWidget(m_emSdkEnvDisplay); - mainLayout->addWidget(m_emSdkEnvGroupBox, 1); - } - - mainLayout->addStretch(); - - { - const QString minimumSupportedQtVersion = - WebAssemblyQtVersion::minimumSupportedQtVersion().toString(); - m_qtVersionDisplay = new InfoLabel( - Tr::tr("Note: %1 supports Qt %2 for WebAssembly and higher. " - "Your installed lower Qt version(s) are not supported.") - .arg(Core::ICore::versionString(), minimumSupportedQtVersion), - InfoLabel::Warning); - m_qtVersionDisplay->setElideMode(Qt::ElideNone); - m_qtVersionDisplay->setWordWrap(true); - mainLayout->addWidget(m_qtVersionDisplay); - } -} - -static QString environmentDisplay(const FilePath &sdkRoot) -{ - Environment env; - WebAssemblyEmSdk::addToEnvironment(sdkRoot, env); - QString result; - auto h4 = [](const QString &text) { return QString("

    " + text + "

    "); }; - result.append(h4(Tr::tr("Adding directories to PATH:"))); - result.append(env.value("PATH").replace(OsSpecificAspects::pathListSeparator(sdkRoot.osType()), "
    ")); - result.append(h4(Tr::tr("Setting environment variables:"))); - for (const QString &envVar : env.toStringList()) { - if (!envVar.startsWith("PATH")) // Path was already printed out above - result.append(envVar + "
    "); - } - return result; -} - -void WebAssemblyOptionsWidget::updateStatus() -{ - WebAssemblyEmSdk::clearCaches(); - - const FilePath sdkPath = m_emSdkPathChooser->filePath(); - const bool sdkValid = sdkPath.exists() && WebAssemblyEmSdk::isValid(sdkPath); - - m_emSdkVersionDisplay->setVisible(sdkValid); - m_emSdkEnvGroupBox->setVisible(sdkValid); - - if (sdkValid) { - const QVersionNumber sdkVersion = WebAssemblyEmSdk::version(sdkPath); - const QVersionNumber minVersion = WebAssemblyToolChain::minimumSupportedEmSdkVersion(); - const bool versionTooLow = sdkVersion < minVersion; - m_emSdkVersionDisplay->setType(versionTooLow ? InfoLabel::NotOk : InfoLabel::Ok); - auto bold = [](const QString &text) { return QString("" + text + ""); }; - m_emSdkVersionDisplay->setText( - versionTooLow ? Tr::tr("The activated version %1 is not supported by %2. " - "Activate version %3 or higher.") - .arg(bold(sdkVersion.toString())) - .arg(bold(Core::ICore::versionString())) - .arg(bold(minVersion.toString())) - : Tr::tr("Activated version: %1") - .arg(bold(sdkVersion.toString()))); - m_emSdkEnvDisplay->setText(environmentDisplay(sdkPath)); - } - - m_qtVersionDisplay->setVisible(WebAssemblyQtVersion::isUnsupportedQtVersionInstalled()); -} - -void WebAssemblyOptionsWidget::showEvent(QShowEvent *event) -{ - Q_UNUSED(event) - updateStatus(); -} - -void WebAssemblyOptionsWidget::apply() -{ - const FilePath sdkPath = m_emSdkPathChooser->filePath(); - if (!WebAssemblyEmSdk::isValid(sdkPath)) - return; - WebAssemblyEmSdk::registerEmSdk(sdkPath); - WebAssemblyToolChain::registerToolChains(); -} - -WebAssemblyOptionsPage::WebAssemblyOptionsPage() -{ - setId(Id(Constants::SETTINGS_ID)); - setDisplayName(Tr::tr("WebAssembly")); - setCategory(ProjectExplorer::Constants::DEVICE_SETTINGS_CATEGORY); - setWidgetCreator([] { return new WebAssemblyOptionsWidget; }); -} - -} // Internal -} // WebAssembly diff --git a/src/plugins/webassembly/webassemblyoptionspage.h b/src/plugins/webassembly/webassemblyoptionspage.h deleted file mode 100644 index 62a17a9b153..00000000000 --- a/src/plugins/webassembly/webassemblyoptionspage.h +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -namespace WebAssembly { -namespace Internal { - -class WebAssemblyOptionsPage final : public Core::IOptionsPage -{ -public: - WebAssemblyOptionsPage(); -}; - -} // namespace Internal -} // namespace WebAssmbly diff --git a/src/plugins/webassembly/webassemblyplugin.cpp b/src/plugins/webassembly/webassemblyplugin.cpp index 0857e43f2f9..28227884da7 100644 --- a/src/plugins/webassembly/webassemblyplugin.cpp +++ b/src/plugins/webassembly/webassemblyplugin.cpp @@ -8,9 +8,9 @@ #endif // WITH_TESTS #include "webassemblyconstants.h" #include "webassemblydevice.h" -#include "webassemblyoptionspage.h" #include "webassemblyqtversion.h" #include "webassemblyrunconfiguration.h" +#include "webassemblysettings.h" #include "webassemblytoolchain.h" #include "webassemblytr.h" @@ -39,7 +39,7 @@ public: WebAssemblyQtVersionFactory qtVersionFactory; EmrunRunConfigurationFactory emrunRunConfigurationFactory; EmrunRunWorkerFactory emrunRunWorkerFactory; - WebAssemblyOptionsPage optionsPage; + WebAssemblySettings settings; }; static WebAssemblyPluginPrivate *dd = nullptr; diff --git a/src/plugins/webassembly/webassemblyrunconfigurationaspects.cpp b/src/plugins/webassembly/webassemblyrunconfigurationaspects.cpp index c1eb243e49a..05b7e89e821 100644 --- a/src/plugins/webassembly/webassemblyrunconfigurationaspects.cpp +++ b/src/plugins/webassembly/webassemblyrunconfigurationaspects.cpp @@ -8,8 +8,8 @@ #include #include +#include #include -#include #include #include @@ -29,7 +29,7 @@ static WebBrowserEntries emrunBrowsers(ProjectExplorer::Target *target) const Utils::Environment environment = bc->environment(); const Utils::FilePath emrunPath = environment.searchInPath("emrun"); - QtcProcess browserLister; + Process browserLister; browserLister.setEnvironment(environment); browserLister.setCommand({emrunPath, {"--list_browsers"}}); browserLister.start(); @@ -55,7 +55,7 @@ WebBrowserSelectionAspect::WebBrowserSelectionAspect(ProjectExplorer::Target *ta addDataExtractor(this, &WebBrowserSelectionAspect::currentBrowser, &Data::currentBrowser); } -void WebBrowserSelectionAspect::addToLayout(Layouting::LayoutBuilder &builder) +void WebBrowserSelectionAspect::addToLayout(Layouting::LayoutItem &parent) { QTC_CHECK(!m_webBrowserComboBox); m_webBrowserComboBox = new QComboBox; @@ -66,7 +66,7 @@ void WebBrowserSelectionAspect::addToLayout(Layouting::LayoutBuilder &builder) m_currentBrowser = m_webBrowserComboBox->currentData().toString(); emit changed(); }); - builder.addItems({Tr::tr("Web browser:"), m_webBrowserComboBox}); + parent.addItems({Tr::tr("Web browser:"), m_webBrowserComboBox}); } void WebBrowserSelectionAspect::fromMap(const QVariantMap &map) diff --git a/src/plugins/webassembly/webassemblyrunconfigurationaspects.h b/src/plugins/webassembly/webassemblyrunconfigurationaspects.h index 1d713274edc..8a3cc24bdb2 100644 --- a/src/plugins/webassembly/webassemblyrunconfigurationaspects.h +++ b/src/plugins/webassembly/webassemblyrunconfigurationaspects.h @@ -20,7 +20,7 @@ class WebBrowserSelectionAspect : public Utils::BaseAspect public: WebBrowserSelectionAspect(ProjectExplorer::Target *target); - void addToLayout(Utils::Layouting::LayoutBuilder &builder) override; + void addToLayout(Layouting::LayoutItem &parent) override; void fromMap(const QVariantMap &map) override; void toMap(QVariantMap &map) const override; diff --git a/src/plugins/webassembly/webassemblysettings.cpp b/src/plugins/webassembly/webassemblysettings.cpp new file mode 100644 index 00000000000..fb9293f2c84 --- /dev/null +++ b/src/plugins/webassembly/webassemblysettings.cpp @@ -0,0 +1,168 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "webassemblysettings.h" + +#include "webassemblyconstants.h" +#include "webassemblyemsdk.h" +#include "webassemblyqtversion.h" +#include "webassemblytoolchain.h" +#include "webassemblytr.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace Utils; + +namespace WebAssembly { +namespace Internal { + +static WebAssemblySettings *theSettings = nullptr; + +WebAssemblySettings *WebAssemblySettings::instance() +{ + return theSettings; +} + +static QString environmentDisplay(const FilePath &sdkRoot) +{ + Environment env; + WebAssemblyEmSdk::addToEnvironment(sdkRoot, env); + QString result; + auto h4 = [](const QString &text) { return QString("

    " + text + "

    "); }; + result.append(h4(Tr::tr("Adding directories to PATH:"))); + result.append(env.value("PATH").replace(OsSpecificAspects::pathListSeparator(sdkRoot.osType()), "
    ")); + result.append(h4(Tr::tr("Setting environment variables:"))); + for (const QString &envVar : env.toStringList()) { + if (!envVar.startsWith("PATH")) // Path was already printed out above + result.append(envVar + "
    "); + } + return result; +} + +WebAssemblySettings::WebAssemblySettings() +{ + theSettings = this; + + setSettingsGroup("WebAssembly"); + + setId(Id(Constants::SETTINGS_ID)); + setDisplayName(Tr::tr("WebAssembly")); + setCategory(ProjectExplorer::Constants::DEVICE_SETTINGS_CATEGORY); + + registerAspect(&emSdk); + emSdk.setSettingsKey("EmSdk"); + emSdk.setExpectedKind(Utils::PathChooser::ExistingDirectory); + emSdk.setDefaultFilePath(FileUtils::homePath()); + + connect(this, &Utils::AspectContainer::applied, &WebAssemblyToolChain::registerToolChains); + + setLayouter([this] { + auto instruction = new QLabel( + Tr::tr("Select the root directory of an installed %1. " + "Ensure that the activated SDK version is compatible with the %2 " + "or %3 version that you plan to develop against.") + .arg(R"(Emscripten SDK)") + .arg(R"(Qt 5)") + .arg(R"(Qt 6)")); + instruction->setOpenExternalLinks(true); + instruction->setWordWrap(true); + + m_emSdkVersionDisplay = new InfoLabel; + m_emSdkVersionDisplay->setElideMode(Qt::ElideNone); + m_emSdkVersionDisplay->setWordWrap(true); + + m_emSdkEnvDisplay = new QTextBrowser; + m_emSdkEnvDisplay->setLineWrapMode(QTextBrowser::NoWrap); + + const QString minimumSupportedQtVersion = + WebAssemblyQtVersion::minimumSupportedQtVersion().toString(); + m_qtVersionDisplay = new InfoLabel( + Tr::tr("Note: %1 supports Qt %2 for WebAssembly and higher. " + "Your installed lower Qt version(s) are not supported.") + .arg(Core::ICore::versionString(), minimumSupportedQtVersion), + InfoLabel::Warning); + m_qtVersionDisplay->setElideMode(Qt::ElideNone); + m_qtVersionDisplay->setWordWrap(true); + + // _clang-format off + using namespace Layouting; + Column col { + Group { + title(Tr::tr("Emscripten SDK path:")), + Column { + instruction, + emSdk, + m_emSdkVersionDisplay, + }, + }, + Group { + title(Tr::tr("Emscripten SDK environment:")), + bindTo(&m_emSdkEnvGroupBox), + Column { + m_emSdkEnvDisplay, + }, + }, + m_qtVersionDisplay, + }; + // _clang-format on + + connect(emSdk.pathChooser(), &Utils::PathChooser::textChanged, + this, &WebAssemblySettings::updateStatus); + + // updateStatus() uses m_emSdkEnvGroupBox which only exists + // after this here emerges. So delay the update a bit. + QTimer::singleShot(0, this, &WebAssemblySettings::updateStatus); + + return col; + }); + + readSettings(); +} + +void WebAssemblySettings::updateStatus() +{ + WebAssemblyEmSdk::clearCaches(); + + const Utils::FilePath newEmSdk = emSdk.pathChooser()->filePath(); + const bool sdkValid = newEmSdk.exists() && WebAssemblyEmSdk::isValid(newEmSdk); + + QTC_ASSERT(m_emSdkVersionDisplay, return); + QTC_ASSERT(m_emSdkEnvGroupBox, return); + m_emSdkVersionDisplay->setVisible(sdkValid); + m_emSdkEnvGroupBox->setEnabled(sdkValid); + + if (sdkValid) { + const QVersionNumber sdkVersion = WebAssemblyEmSdk::version(newEmSdk); + const QVersionNumber minVersion = WebAssemblyToolChain::minimumSupportedEmSdkVersion(); + const bool versionTooLow = sdkVersion < minVersion; + m_emSdkVersionDisplay->setType(versionTooLow ? InfoLabel::NotOk : InfoLabel::Ok); + auto bold = [](const QString &text) { return QString("" + text + ""); }; + m_emSdkVersionDisplay->setText( + versionTooLow ? Tr::tr("The activated version %1 is not supported by %2. " + "Activate version %3 or higher.") + .arg(bold(sdkVersion.toString())) + .arg(bold(Core::ICore::versionString())) + .arg(bold(minVersion.toString())) + : Tr::tr("Activated version: %1") + .arg(bold(sdkVersion.toString()))); + m_emSdkEnvDisplay->setText(environmentDisplay(newEmSdk)); + } else { + m_emSdkEnvDisplay->clear(); + } + + m_qtVersionDisplay->setVisible(WebAssemblyQtVersion::isUnsupportedQtVersionInstalled()); +} + +} // Internal +} // WebAssembly diff --git a/src/plugins/webassembly/webassemblysettings.h b/src/plugins/webassembly/webassemblysettings.h new file mode 100644 index 00000000000..c9e207c409c --- /dev/null +++ b/src/plugins/webassembly/webassemblysettings.h @@ -0,0 +1,34 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +QT_BEGIN_NAMESPACE +class QTextBrowser; +QT_END_NAMESPACE + +namespace WebAssembly { +namespace Internal { + +class WebAssemblySettings final : public Core::PagedSettings +{ +public: + WebAssemblySettings(); + + static WebAssemblySettings *instance(); + + Utils::FilePathAspect emSdk; + +private: + QWidget *m_emSdkEnvGroupBox = nullptr; + Utils::InfoLabel *m_emSdkVersionDisplay = nullptr; + QTextBrowser *m_emSdkEnvDisplay = nullptr; + Utils::InfoLabel *m_qtVersionDisplay = nullptr; + + void updateStatus(); +}; + +} // namespace Internal +} // namespace WebAssmbly diff --git a/src/plugins/webassembly/webassemblytoolchain.cpp b/src/plugins/webassembly/webassemblytoolchain.cpp index e38b00dac6d..c554ee65cd5 100644 --- a/src/plugins/webassembly/webassemblytoolchain.cpp +++ b/src/plugins/webassembly/webassemblytoolchain.cpp @@ -1,9 +1,11 @@ // Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "webassemblytoolchain.h" + #include "webassemblyconstants.h" #include "webassemblyemsdk.h" -#include "webassemblytoolchain.h" +#include "webassemblysettings.h" #include "webassemblytr.h" #include @@ -49,7 +51,8 @@ static void addRegisteredMinGWToEnvironment(Environment &env) void WebAssemblyToolChain::addToEnvironment(Environment &env) const { - WebAssemblyEmSdk::addToEnvironment(WebAssemblyEmSdk::registeredEmSdk(), env); + const FilePath emSdk = WebAssemblySettings::instance()->emSdk(); + WebAssemblyEmSdk::addToEnvironment(emSdk, env); if (env.osType() == OsTypeWindows) addRegisteredMinGWToEnvironment(env); } @@ -92,11 +95,12 @@ const QVersionNumber &WebAssemblyToolChain::minimumSupportedEmSdkVersion() static Toolchains doAutoDetect(const ToolchainDetector &detector) { - const FilePath sdk = WebAssemblyEmSdk::registeredEmSdk(); + const FilePath sdk = WebAssemblySettings::instance()->emSdk(); if (!WebAssemblyEmSdk::isValid(sdk)) return {}; - if (detector.device) { + if (detector.device + && detector.device->type() != ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE) { // Only detect toolchains from the emsdk installation device const FilePath deviceRoot = detector.device->rootPath(); if (deviceRoot.host() != sdk.host()) diff --git a/src/plugins/welcome/introductionwidget.cpp b/src/plugins/welcome/introductionwidget.cpp index a18287b0f60..ef56d9ab3ed 100644 --- a/src/plugins/welcome/introductionwidget.cpp +++ b/src/plugins/welcome/introductionwidget.cpp @@ -26,10 +26,10 @@ const char kTakeTourSetting[] = "TakeUITour"; namespace Welcome { namespace Internal { -void IntroductionWidget::askUserAboutIntroduction(QWidget *parent, QSettings *settings) +void IntroductionWidget::askUserAboutIntroduction(QWidget *parent) { // CheckableMessageBox for compatibility with Qt Creator < 4.11 - if (!CheckableMessageBox::shouldAskAgain(settings, kTakeTourSetting) + if (!CheckableDecider(QString(kTakeTourSetting)).shouldAskAgain() || !Core::ICore::infoBar()->canInfoBeAdded(kTakeTourSetting)) return; diff --git a/src/plugins/welcome/introductionwidget.h b/src/plugins/welcome/introductionwidget.h index a11069003a0..4b1f99c5ae3 100644 --- a/src/plugins/welcome/introductionwidget.h +++ b/src/plugins/welcome/introductionwidget.h @@ -11,7 +11,6 @@ QT_BEGIN_NAMESPACE class QLabel; -class QSettings; QT_END_NAMESPACE namespace Welcome { @@ -31,7 +30,7 @@ class IntroductionWidget : public QWidget public: explicit IntroductionWidget(QWidget *parent = nullptr); - static void askUserAboutIntroduction(QWidget *parent, QSettings *settings); + static void askUserAboutIntroduction(QWidget *parent); protected: bool event(QEvent *e) override; diff --git a/src/plugins/welcome/welcomeplugin.cpp b/src/plugins/welcome/welcomeplugin.cpp index 1a1a1aa647f..915695dafa2 100644 --- a/src/plugins/welcome/welcomeplugin.cpp +++ b/src/plugins/welcome/welcomeplugin.cpp @@ -132,8 +132,7 @@ public: if (!arguments.contains("-notour")) { connect(ICore::instance(), &ICore::coreOpened, this, []() { - IntroductionWidget::askUserAboutIntroduction(ICore::dialogParent(), - ICore::settings()); + IntroductionWidget::askUserAboutIntroduction(ICore::dialogParent()); }, Qt::QueuedConnection); } diff --git a/src/share/qtcreator/externaltools/lrelease.xml b/src/share/qtcreator/externaltools/lrelease.xml index deb9aa0132c..05ab83e115f 100644 --- a/src/share/qtcreator/externaltools/lrelease.xml +++ b/src/share/qtcreator/externaltools/lrelease.xml @@ -11,6 +11,7 @@ 2 %{CurrentDocument:Project:QT_INSTALL_BINS}/lrelease + %{ActiveProject:QT_INSTALL_BINS}/lrelease lrelease %{CurrentDocument:Project:FilePath} %{CurrentDocument:Project:Path} diff --git a/src/share/qtcreator/externaltools/lupdate.xml b/src/share/qtcreator/externaltools/lupdate.xml index 2e885ef3ed4..f0edc35a833 100644 --- a/src/share/qtcreator/externaltools/lupdate.xml +++ b/src/share/qtcreator/externaltools/lupdate.xml @@ -11,6 +11,7 @@ 1 %{CurrentDocument:Project:QT_INSTALL_BINS}/lupdate + %{ActiveProject:QT_INSTALL_BINS}/lupdate lupdate %{CurrentDocument:Project:FilePath} %{CurrentDocument:Project:Path} diff --git a/src/share/qtcreator/externaltools/qml.xml b/src/share/qtcreator/externaltools/qml.xml index ec2cc5a9a7b..ee7d5479a74 100644 --- a/src/share/qtcreator/externaltools/qml.xml +++ b/src/share/qtcreator/externaltools/qml.xml @@ -11,6 +11,7 @@ 1 %{CurrentDocument:Project:QT_INSTALL_BINS}/qml + %{ActiveProject:QT_INSTALL_BINS}/qml qml %{CurrentDocument:FilePath} %{CurrentDocument:Path} diff --git a/src/share/qtcreator/externaltools/qmlscene.xml b/src/share/qtcreator/externaltools/qmlscene.xml index ff8751a7ed5..09b780e521f 100644 --- a/src/share/qtcreator/externaltools/qmlscene.xml +++ b/src/share/qtcreator/externaltools/qmlscene.xml @@ -11,6 +11,7 @@ 1 %{CurrentDocument:Project:QT_INSTALL_BINS}/qmlscene + %{ActiveProject:QT_INSTALL_BINS}/qmlscene qmlscene %{CurrentDocument:FilePath} %{CurrentDocument:Path} diff --git a/src/shared/help/bookmarkmanager.cpp b/src/shared/help/bookmarkmanager.cpp index 6a3ed6e14ea..72bd5db3214 100644 --- a/src/shared/help/bookmarkmanager.cpp +++ b/src/shared/help/bookmarkmanager.cpp @@ -75,7 +75,7 @@ BookmarkDialog::BookmarkDialog(BookmarkManager *manager, const QString &title, m_newFolderButton = new QPushButton(::Help::Tr::tr("New Folder")); m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - using namespace Utils::Layouting; + using namespace Layouting; Column { Form { ::Help::Tr::tr("Bookmark:"), m_bookmarkEdit, br, diff --git a/src/shared/help/topicchooser.cpp b/src/shared/help/topicchooser.cpp index efa3705b14b..93a4bf47be9 100644 --- a/src/shared/help/topicchooser.cpp +++ b/src/shared/help/topicchooser.cpp @@ -52,7 +52,7 @@ TopicChooser::TopicChooser(QWidget *parent, const QString &keyword, auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - using namespace Utils::Layouting; + using namespace Layouting; Column { ::Help::Tr::tr("Choose a topic for %1:").arg(keyword), m_lineEdit, diff --git a/src/shared/qbs b/src/shared/qbs index 03e717b06ed..43af7ba7fa1 160000 --- a/src/shared/qbs +++ b/src/shared/qbs @@ -1 +1 @@ -Subproject commit 03e717b06ed5c0864618e763f08f91d9fc94b733 +Subproject commit 43af7ba7fa12de25268d2cc5582b963e360022e5 diff --git a/src/shared/qtsingleapplication/qtlocalpeer.h b/src/shared/qtsingleapplication/qtlocalpeer.h index bd7f6e9dce0..67a0d42e2c9 100644 --- a/src/shared/qtsingleapplication/qtlocalpeer.h +++ b/src/shared/qtsingleapplication/qtlocalpeer.h @@ -1,6 +1,8 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#pragma once + #include #include diff --git a/src/shared/qtsingleapplication/qtsingleapplication.cpp b/src/shared/qtsingleapplication/qtsingleapplication.cpp index 4a79f4eee6b..0f8fa8b6d13 100644 --- a/src/shared/qtsingleapplication/qtsingleapplication.cpp +++ b/src/shared/qtsingleapplication/qtsingleapplication.cpp @@ -148,13 +148,11 @@ void QtSingleApplication::setActivationWindow(QWidget *aw, bool activateOnMessag disconnect(pidPeer, &QtLocalPeer::messageReceived, this, &QtSingleApplication::activateWindow); } - QWidget* QtSingleApplication::activationWindow() const { return actWin; } - void QtSingleApplication::activateWindow() { if (actWin) { @@ -164,4 +162,70 @@ void QtSingleApplication::activateWindow() } } +static const char s_freezeDetector[] = "QTC_FREEZE_DETECTOR"; + +static std::optional isUsingFreezeDetector() +{ + if (!qEnvironmentVariableIsSet(s_freezeDetector)) + return {}; + + bool ok = false; + const int threshold = qEnvironmentVariableIntValue(s_freezeDetector, &ok); + return ok ? threshold : 100; // default value 100ms +} + +class ApplicationWithFreezerDetector : public SharedTools::QtSingleApplication +{ +public: + ApplicationWithFreezerDetector(const QString &id, int &argc, char **argv) + : QtSingleApplication(id, argc, argv) + , m_align(21, QChar::Space) + {} + void setFreezeTreshold(std::chrono::milliseconds freezeAbove) { m_threshold = freezeAbove; } + + bool notify(QObject *receiver, QEvent *event) override { + using namespace std::chrono; + const auto start = system_clock::now(); + const QPointer p(receiver); + const QString className = QLatin1String(receiver->metaObject()->className()); + const QString name = receiver->objectName(); + + const bool ret = QtSingleApplication::notify(receiver, event); + + const auto end = system_clock::now(); + const auto freeze = duration_cast(end - start); + if (freeze > m_threshold) { + const QString time = QTime::currentTime().toString(Qt::ISODateWithMs); + qDebug().noquote() << QString("FREEZE [%1]").arg(time) + << "of" << freeze.count() << "ms, on:" << event; + const QString receiverMessage = name.isEmpty() + ? QString("receiver class: %1").arg(className) + : QString("receiver class: %1, object name: %2").arg(className, name); + qDebug().noquote() << m_align << receiverMessage; + if (!p) + qDebug().noquote() << m_align << "THE RECEIVER GOT DELETED inside the event filter!"; + } + return ret; + } + +private: + const QString m_align; + std::chrono::milliseconds m_threshold = std::chrono::milliseconds(100); +}; + +QtSingleApplication *createApplication(const QString &id, int &argc, char **argv) +{ + const std::optional freezeDetector = isUsingFreezeDetector(); + if (!freezeDetector) + return new SharedTools::QtSingleApplication(id, argc, argv); + + qDebug() << s_freezeDetector << "evn var is set. The freezes of main thread, above" + << *freezeDetector << "ms, will be reported."; + qDebug() << "Change the freeze detection threshold by setting the" << s_freezeDetector + << "env var to a different numeric value (in ms)."; + ApplicationWithFreezerDetector *app = new ApplicationWithFreezerDetector(id, argc, argv); + app->setFreezeTreshold(std::chrono::milliseconds(*freezeDetector)); + return app; +} + } // namespace SharedTools diff --git a/src/shared/qtsingleapplication/qtsingleapplication.h b/src/shared/qtsingleapplication/qtsingleapplication.h index 3d8e140bb4b..fdc485bd98c 100644 --- a/src/shared/qtsingleapplication/qtsingleapplication.h +++ b/src/shared/qtsingleapplication/qtsingleapplication.h @@ -1,6 +1,8 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#pragma once + #include QT_FORWARD_DECLARE_CLASS(QSharedMemory) @@ -44,4 +46,7 @@ private: bool block; }; +// Instantiates Freeze Detector when QTC_FREEZE_DETECTOR env var is set. +QtSingleApplication *createApplication(const QString &id, int &argc, char **argv); + } // namespace SharedTools diff --git a/src/shared/registryaccess/CMakeLists.txt b/src/shared/registryaccess/CMakeLists.txt index 135df2c501f..634c8c19e60 100644 --- a/src/shared/registryaccess/CMakeLists.txt +++ b/src/shared/registryaccess/CMakeLists.txt @@ -3,10 +3,10 @@ if (WIN32) target_link_libraries(registryaccess PUBLIC advapi32 ole32 shell32 Qt::Widgets) target_compile_definitions(registryaccess PRIVATE _UNICODE UNICODE) target_include_directories(registryaccess PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + if (WITH_SANITIZE) + qtc_enable_sanitize(registryaccess ${SANITIZE_FLAGS}) + endif() else() add_library(registryaccess INTERFACE) endif() -if (WITH_SANITIZE) - qtc_enable_sanitize(registryaccess ${SANITIZE_FLAGS}) -endif() diff --git a/src/shared/registryaccess/registryaccess.h b/src/shared/registryaccess/registryaccess.h index 21862ae4a19..b4e0c089583 100644 --- a/src/shared/registryaccess/registryaccess.h +++ b/src/shared/registryaccess/registryaccess.h @@ -20,10 +20,11 @@ enum AccessMode { Registry64Mode = 0x4 // Corresponds to QSettings::Registry64Format (5.7) }; -static const char *debuggerApplicationFileC = "qtcdebugger"; -static const WCHAR *debuggerRegistryKeyC = L"Software\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug"; -static const WCHAR *debuggerRegistryValueNameC = L"Debugger"; -static const WCHAR *autoRegistryValueNameC = L"Auto"; +constexpr const char debuggerApplicationFileC[] = "qtcdebugger"; +constexpr const WCHAR debuggerRegistryKeyC[] + = L"Software\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug"; +constexpr const WCHAR debuggerRegistryValueNameC[] = L"Debugger"; +constexpr const WCHAR autoRegistryValueNameC[] = L"Auto"; static inline QString wCharToQString(const WCHAR *w) { diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 0eac791aee5..d6663680c25 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -1,10 +1,9 @@ add_subdirectory(3rdparty) add_subdirectory(buildoutputparser) -option(BUILD_CPLUSPLUS_TOOLS "Build CPlusPlus tools" OFF) - function(add_qtc_cpp_tool name) add_qtc_executable(${name} + SKIP_INSTALL DEFINES PATH_PREPROCESSOR_CONFIG=\"${CMAKE_CURRENT_SOURCE_DIR}/cplusplus-shared/pp-configuration.inc\" ${ARGN} @@ -17,12 +16,10 @@ function(add_qtc_cpp_tool name) ) endfunction() -if (BUILD_CPLUSPLUS_TOOLS) - add_qtc_cpp_tool(cplusplus-ast2png "") - add_qtc_cpp_tool(cplusplus-frontend "") - add_qtc_cpp_tool(cplusplus-mkvisitor PATH_AST_H=\"${CMAKE_CURRENT_SOURCE_DIR}/../libs/3rdparty/cplusplus/AST.h\") - add_qtc_cpp_tool(cplusplus-update-frontend PATH_CPP_FRONTEND=\"${CMAKE_CURRENT_SOURCE_DIR}/../libs/3rdparty/cplusplus\" PATH_DUMPERS_FILE=\"${CMAKE_CURRENT_SOURCE_DIR}/cplusplus-ast2png/dumpers.inc\") -endif() +add_qtc_cpp_tool(cplusplus-ast2png "") +add_qtc_cpp_tool(cplusplus-frontend "") +add_qtc_cpp_tool(cplusplus-mkvisitor PATH_AST_H=\"${CMAKE_CURRENT_SOURCE_DIR}/../libs/3rdparty/cplusplus/AST.h\") +add_qtc_cpp_tool(cplusplus-update-frontend PATH_CPP_FRONTEND=\"${CMAKE_CURRENT_SOURCE_DIR}/../libs/3rdparty/cplusplus\" PATH_DUMPERS_FILE=\"${CMAKE_CURRENT_SOURCE_DIR}/cplusplus-ast2png/dumpers.inc\") if (APPLE) add_subdirectory(disclaim) @@ -46,3 +43,5 @@ add_subdirectory(wininterrupt) ## windows only if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/perfparser/CMakeLists.txt) add_subdirectory(perfparser) endif() + +add_subdirectory(process_stub) diff --git a/src/tools/cplusplus-shared/CPlusPlusTool.qbs b/src/tools/cplusplus-shared/CPlusPlusTool.qbs index 55c4aff9409..63313e2462f 100644 --- a/src/tools/cplusplus-shared/CPlusPlusTool.qbs +++ b/src/tools/cplusplus-shared/CPlusPlusTool.qbs @@ -1,6 +1,7 @@ import qbs 1.0 QtcTool { + install: false Depends { name: "Qt"; submodules: ["core", "widgets"]; } Depends { name: "CPlusPlus" } Depends { name: "Utils" } diff --git a/src/tools/cplusplustools.qbs b/src/tools/cplusplustools.qbs index d67d5583309..ab3aa517072 100644 --- a/src/tools/cplusplustools.qbs +++ b/src/tools/cplusplustools.qbs @@ -3,7 +3,6 @@ import qbs.Environment Project { name: "CPlusPlus Tools" - condition: Environment.getEnv("BUILD_CPLUSPLUS_TOOLS") references: [ "3rdparty/cplusplus-keywordgen/cplusplus-keywordgen.qbs", "cplusplus-ast2png/cplusplus-ast2png.qbs", diff --git a/src/tools/icons/GenericDocumentIcon.iconset/readme.txt b/src/tools/icons/GenericDocumentIcon.iconset/readme.txt deleted file mode 100644 index a00748fafd0..00000000000 --- a/src/tools/icons/GenericDocumentIcon.iconset/readme.txt +++ /dev/null @@ -1,15 +0,0 @@ -This directory needs to contain the files - - icon_128x128.png - icon_128x128@2x.png - icon_16x16.png - icon_16x16@2x.png - icon_256x256.png - icon_256x256@2x.png - icon_32x32.png - icon_32x32@2x.png - icon_512x512.png - icon_512x512@2x.png - -On OSX, they are obtainable by executing the following command: -iconutil -c iconset /System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericDocumentIcon.icns diff --git a/src/tools/icons/applicationicons.svg b/src/tools/icons/applicationicons.svg deleted file mode 100644 index 018fb75ea52..00000000000 --- a/src/tools/icons/applicationicons.svg +++ /dev/null @@ -1,2206 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/tools/icons/documenttypeicons.svg b/src/tools/icons/documenttypeicons.svg deleted file mode 100644 index d34829f5b4f..00000000000 --- a/src/tools/icons/documenttypeicons.svg +++ /dev/null @@ -1,879 +0,0 @@ - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - UI - - - - - - - - - - - - - - - - - - - - - - UI - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PRO - - - - - PRO - - - - - PRO - - - - - PRO - - - - - - - - - - PRO - - - - - PRO - - - - - PRO - - - - - PRO - - - - diff --git a/src/tools/icons/exportapplicationicons.sh b/src/tools/icons/exportapplicationicons.sh deleted file mode 100644 index eb74acbb90d..00000000000 --- a/src/tools/icons/exportapplicationicons.sh +++ /dev/null @@ -1,102 +0,0 @@ -#!/bin/sh - -# Copyright (C) 2016 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -# This script creates several application icon files by using -# Inkscape to rasterize .svg items to .png, adding shadows via -# imagemagick, creating .ico files via imagemagick and .icns -# files via iconutil (OSX only). - -# optipng is required by this script -if ! hash optipng 2>/dev/null; then - echo "optipng was not found in PATH" >&2 - exit 1 -fi - -# Imagemagick convert is required by this script -if ! hash convert 2>/dev/null; then - echo "Imagemagick convert was not found in PATH" >&2 - exit 1 -fi - -cd `dirname $0` - -applicationNames="qtcreator designer linguist assistant qdbusviewer qmlviewer" -applicationIconDimensions="16:0 24:0 32:1 48:1 64:1 128:2 256:3 512:7 1024:15" - -# Creating the list of svg IDs to export -for applicationName in $applicationNames;\ -do - for applicationIconDimension in $applicationIconDimensions;\ - do - applicationIconSize=`echo $applicationIconDimension | awk -F: '{ print $1 }'` - iconIDs="${iconIDs} ${applicationName}_icon_${applicationIconSize}x${applicationIconSize}" - done -done - -# Copying the logos for Qt Creator's sources. Without shadows! -creatorLogoDir="logo" -rm -rf $creatorLogoDir -mkdir $creatorLogoDir -for uiFileIconSize in 16 24 32 48 64 128 256 512;\ -do - creatorLogoSource="qtcreator_icon_${uiFileIconSize}x${uiFileIconSize}.png" - creatorLogoTargetDir="${creatorLogoDir}/${uiFileIconSize}" - creatorLogoTarget="${creatorLogoTargetDir}/QtProject-qtcreator.png" - optipng $creatorLogoSource -o 7 -strip all - mkdir $creatorLogoTargetDir - cp $creatorLogoSource $creatorLogoTarget -done - -# Adding the shadows to the .png files -for applicationName in $applicationNames;\ -do - for applicationIconDimension in $applicationIconDimensions;\ - do - iconSize=`echo $applicationIconDimension | awk -F: '{ print $1 }'` - shadowSize=`echo $applicationIconDimension | awk -F: '{ print $2 }'` - iconFile=${applicationName}_icon_${iconSize}x${iconSize}.png - if [ "$shadowSize" != "0" ] - then - convert -page ${iconSize}x${iconSize} ${iconFile} \( +clone -background black -shadow 25x1+0+0 \) +swap -background none -flatten ${iconFile} - convert -page ${iconSize}x${iconSize} ${iconFile} \( +clone -background black -shadow 40x${shadowSize}+0+${shadowSize} \) +swap -background none -flatten ${iconFile} - fi - done -done - -# Creating the .ico files -iconSizes="256 128 64 48 32 24 16" -for applicationName in $applicationNames;\ -do - icoFiles="" - for iconSize in $iconSizes;\ - do - icoFiles="$icoFiles ${applicationName}_icon_${iconSize}x${iconSize}.png" - done - convert ${icoFiles} ${applicationName}.ico -done - -# Optimizing the .pngs -for iconID in $iconIDs;\ -do - optipng "${iconID}.png" -o 7 -strip all -done - -# Preparing application .iconsets for the conversion to .icns -for applicationName in $applicationNames;\ -do - inconsetName=${applicationName}.iconset - rm -rf $inconsetName - mkdir $inconsetName - cp ${applicationName}_icon_16x16.png ${inconsetName}/icon_16x16.png - cp ${applicationName}_icon_32x32.png ${inconsetName}/icon_32x32.png - cp ${applicationName}_icon_128x128.png ${inconsetName}/icon_128x128.png - cp ${applicationName}_icon_256x256.png ${inconsetName}/icon_256x256.png - cp ${applicationName}_icon_512x512.png ${inconsetName}/icon_512x512.png - cp ${applicationName}_icon_32x32.png ${inconsetName}/icon_16x16@2x.png - cp ${applicationName}_icon_64x64.png ${inconsetName}/icon_32x32@2x.png - cp ${applicationName}_icon_256x256.png ${inconsetName}/icon_128x128@2x.png - cp ${applicationName}_icon_512x512.png ${inconsetName}/icon_256x256@2x.png - cp ${applicationName}_icon_1024x1024.png ${inconsetName}/icon_512x512@2x.png -done diff --git a/src/tools/icons/exportdocumenttypeicons.sh b/src/tools/icons/exportdocumenttypeicons.sh deleted file mode 100644 index f0d8351c63d..00000000000 --- a/src/tools/icons/exportdocumenttypeicons.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/sh - -# Copyright (C) 2016 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -# optipng is required by this script -if ! hash optipng 2>/dev/null; then - echo "optipng was not found in PATH" >&2 - exit 1 -fi - -cd `dirname $0` - -# Adding the icons for the OSX document type icon for .ui files -for documentTypeName in "uifile" "qtcreator-project";\ -do - inconsetName=${documentTypeName}.iconset - rm -rf $inconsetName - mkdir $inconsetName - for iconSize in 16 32 128 256 512;\ - do - iconShortID="icon_${iconSize}x${iconSize}" - iconLongID="${documentTypeName}_${iconShortID}" - for sizeVariation in "" "@2x";\ - do - iconSourcePng="${iconLongID}${sizeVariation}.png" - iconTargetPng="${inconsetName}/${iconShortID}${sizeVariation}.png" - optipng $iconSourcePng -o 7 -strip all - cp $iconSourcePng $iconTargetPng - done - done -done diff --git a/src/tools/icons/qtcreatoricons.svg b/src/tools/icons/qtcreatoricons.svg index 8e6a6baa2d9..2c162eaa351 100644 --- a/src/tools/icons/qtcreatoricons.svg +++ b/src/tools/icons/qtcreatoricons.svg @@ -653,6 +653,14 @@ width="100%" height="100%" transform="matrix(1.5,0,0,1.5,0,-242)" /> + + + + + + + + + + + + + + + @@ -3484,6 +3554,56 @@ inkscape:connector-curvature="0" sodipodi:nodetypes="cccc" /> + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/tools/perfparser b/src/tools/perfparser index ac05977da71..5444f962076 160000 --- a/src/tools/perfparser +++ b/src/tools/perfparser @@ -1 +1 @@ -Subproject commit ac05977da7134f78d9b172271c524a32c3176464 +Subproject commit 5444f96207616f922f3093e9d64bd6000f168c51 diff --git a/src/tools/process_stub/CMakeLists.txt b/src/tools/process_stub/CMakeLists.txt new file mode 100644 index 00000000000..b8181ad8361 --- /dev/null +++ b/src/tools/process_stub/CMakeLists.txt @@ -0,0 +1,12 @@ + +add_qtc_executable(qtcreator_process_stub + DEPENDS Qt::Core Qt::Network + SOURCES + main.cpp +) + +if (WIN32) + extend_qtc_executable(qtcreator_process_stub + DEFINES _UNICODE UNICODE _CRT_SECURE_NO_WARNINGS + ) +endif() diff --git a/src/tools/process_stub/main.cpp b/src/tools/process_stub/main.cpp new file mode 100644 index 00000000000..f606b063694 --- /dev/null +++ b/src/tools/process_stub/main.cpp @@ -0,0 +1,572 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_OS_WIN +#include +#else +#include +#include +#include +#include +#endif + +#ifdef Q_OS_LINUX +#include +#endif + +#include + +Q_LOGGING_CATEGORY(log, "qtc.process_stub", QtWarningMsg); + +// Global variables + +QCommandLineParser commandLineParser; + +// The inferior command and arguments +QStringList inferiorCmdAndArguments; +// Whether to Suspend the inferior process on startup (to allow a debugger to attach) +bool debugMode = false; +// Whether to run in test mode (i.e. to start manually from the command line) +bool testMode = false; +// The control socket used to communicate with Qt Creator +QLocalSocket controlSocket; +// Environment variables to set for the inferior process +std::optional environmentVariables; + +QProcess inferiorProcess; +int inferiorId{0}; + +#ifndef Q_OS_WIN + +#ifdef Q_OS_DARWIN +// A memory mapped helper to retrieve the pid of the inferior process in debugMode +static int *shared_child_pid = nullptr; +#endif + +using OSSocketNotifier = QSocketNotifier; +#else +Q_PROCESS_INFORMATION *win_process_information = nullptr; +using OSSocketNotifier = QWinEventNotifier; +#endif +// Helper to read a single character from stdin in testMode +OSSocketNotifier *stdInNotifier; + +QThread processThread; + +// Helper to create the shared memory mapped segment +void setupSharedPid(); +// Parses the command line, returns a status code in case of error +std::optional tryParseCommandLine(QCoreApplication &app); +// Sets the working directory, returns a status code in case of error +std::optional trySetWorkingDir(); +// Reads the environment variables from the env file, returns a status code in case of error +std::optional readEnvFile(); + +void setupControlSocket(); +void setupSignalHandlers(); +void startProcess(const QString &program, const QStringList &arguments, const QString &workingDir); +void readKey(); +void sendSelfPid(); + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + setupSharedPid(); + + auto error = tryParseCommandLine(a); + if (error) + return error.value(); + + qCInfo(log) << "Debug helper started: "; + qCInfo(log) << "Socket:" << commandLineParser.value("socket"); + qCInfo(log) << "Inferior:" << inferiorCmdAndArguments.join(QChar::Space); + qCInfo(log) << "Working Directory" << commandLineParser.value("workingDir"); + qCInfo(log) << "Env file:" << commandLineParser.value("envfile"); + qCInfo(log) << "Mode:" + << QLatin1String(testMode ? "test | " : "") + + QLatin1String(debugMode ? "debug" : "run"); + + error = trySetWorkingDir(); + if (error) + return error.value(); + + error = readEnvFile(); + if (error) + return error.value(); + + if (testMode) { + sendSelfPid(); + setupSignalHandlers(); + + startProcess(inferiorCmdAndArguments[0], + inferiorCmdAndArguments.mid(1), + commandLineParser.value("workingDir")); + + if (debugMode) { + qDebug() << "Press 'c' to continue or 'k' to kill, followed by 'enter'"; + readKey(); + } + + return a.exec(); + } + + setupControlSocket(); + + return a.exec(); +} + +void sendMsg(const QByteArray &msg) +{ + if (controlSocket.state() == QLocalSocket::ConnectedState) { + controlSocket.write(msg); + } else { + qDebug() << "MSG:" << msg; + } +} + +void sendPid(int inferiorPid) +{ + sendMsg(QString("pid %1\n").arg(inferiorPid).toUtf8()); +} + +void sendThreadId(int inferiorThreadPid) +{ + sendMsg(QString("thread %1\n").arg(inferiorThreadPid).toUtf8()); +} + +void sendSelfPid() +{ + sendMsg(QString("spid %1\n").arg(QCoreApplication::applicationPid()).toUtf8()); +} + +void sendExit(int exitCode) +{ + sendMsg(QString("exit %1\n").arg(exitCode).toUtf8()); +} + +void sendCrash(int exitCode) +{ + sendMsg(QString("crash %1\n").arg(exitCode).toUtf8()); +} + +void sendErrChDir() +{ + sendMsg(QString("err:chdir %1\n").arg(errno).toUtf8()); +} + +void doExit(int exitCode) +{ + if (controlSocket.state() == QLocalSocket::ConnectedState && controlSocket.bytesToWrite()) + controlSocket.waitForBytesWritten(1000); + + if (!commandLineParser.value("wait").isEmpty()) { + std::cout << commandLineParser.value("wait").toStdString(); + std::cin.get(); + } + + exit(exitCode); +} + +void onInferiorFinished(int exitCode, QProcess::ExitStatus status) +{ + qCInfo(log) << "Inferior finished"; + + if (status == QProcess::CrashExit) { + sendCrash(exitCode); + doExit(exitCode); + } else { + sendExit(exitCode); + doExit(exitCode); + } +} + +void onInferiorErrorOccurered(QProcess::ProcessError error) +{ + qCInfo(log) << "Inferior error: " << error << inferiorProcess.errorString(); + sendCrash(inferiorProcess.exitCode()); + doExit(1); +} + +void onInferiorStarted() +{ + inferiorId = inferiorProcess.processId(); + qCInfo(log) << "Inferior started ( pid:" << inferiorId << ")"; +#ifdef Q_OS_WIN + sendThreadId(win_process_information->dwThreadId); + sendPid(inferiorId); +#elif defined(Q_OS_DARWIN) + // In debug mode we use the poll timer to send the pid. + if (!debugMode) + sendPid(inferiorId); +#else + ptrace(PTRACE_DETACH, inferiorId, 0, SIGSTOP); + sendPid(inferiorId); +#endif +} + +void setupUnixInferior() +{ +#ifndef Q_OS_WIN + if (debugMode) { + qCInfo(log) << "Debug mode enabled"; +#ifdef Q_OS_DARWIN + // We are using raise(SIGSTOP) to stop the child process, macOS does not support ptrace(...) + inferiorProcess.setChildProcessModifier([] { + // Let the parent know our pid ... + *shared_child_pid = getpid(); + // Suspend ourselves ... + raise(SIGSTOP); + }); +#else + // PTRACE_TRACEME will stop execution of the child process as soon as execve is called. + inferiorProcess.setChildProcessModifier([] { ptrace(PTRACE_TRACEME, 0, 0, 0); }); +#endif + } +#endif +} + +void setupWindowsInferior() +{ +#ifdef Q_OS_WIN + inferiorProcess.setCreateProcessArgumentsModifier([](QProcess::CreateProcessArguments *args) { + if (debugMode) + args->flags |= CREATE_SUSPENDED; + win_process_information = args->processInformation; + }); +#endif +} + +void setupPidPollTimer() +{ +#ifdef Q_OS_DARWIN + if (!debugMode) + return; + + static QTimer pollPidTimer; + + pollPidTimer.setInterval(1); + pollPidTimer.setSingleShot(false); + QObject::connect(&pollPidTimer, &QTimer::timeout, &pollPidTimer, [&] { + if (*shared_child_pid) { + qCInfo(log) << "Received pid during polling:" << *shared_child_pid; + inferiorId = *shared_child_pid; + sendPid(inferiorId); + pollPidTimer.stop(); + munmap(shared_child_pid, sizeof(int)); + } else { + qCDebug(log) << "Waiting for inferior to start..."; + } + }); + pollPidTimer.start(); +#endif +} + +enum class Out { StdOut, StdErr }; + +void writeToOut(const QByteArray &data, Out out) +{ +#ifdef Q_OS_WIN + static const HANDLE outHandle = GetStdHandle(STD_OUTPUT_HANDLE); + static const HANDLE errHandle = GetStdHandle(STD_ERROR_HANDLE); + WriteFile(out == Out::StdOut ? outHandle : errHandle, + data.constData(), + data.size(), + nullptr, + nullptr); +#else + auto fp = out == Out::StdOut ? stdout : stderr; + ::fwrite(data.constData(), 1, data.size(), fp); + ::fflush(fp); +#endif +} + +void startProcess(const QString &executable, const QStringList &arguments, const QString &workingDir) +{ + setupPidPollTimer(); + + qCInfo(log) << "Starting Inferior"; + + QObject::connect(&inferiorProcess, + &QProcess::finished, + QCoreApplication::instance(), + &onInferiorFinished); + QObject::connect(&inferiorProcess, + &QProcess::errorOccurred, + QCoreApplication::instance(), + &onInferiorErrorOccurered); + QObject::connect(&inferiorProcess, + &QProcess::started, + QCoreApplication::instance(), + &onInferiorStarted); + + inferiorProcess.setProcessChannelMode(QProcess::ForwardedChannels); + + if (!(testMode && debugMode)) + inferiorProcess.setInputChannelMode(QProcess::ForwardedInputChannel); + inferiorProcess.setWorkingDirectory(workingDir); + inferiorProcess.setProgram(executable); + inferiorProcess.setArguments(arguments); + + if (environmentVariables) + inferiorProcess.setEnvironment(*environmentVariables); + + setupWindowsInferior(); + setupUnixInferior(); + + inferiorProcess.start(); +} + +std::optional readEnvFile() +{ + if (!commandLineParser.isSet("envfile")) + return std::nullopt; + + const QString path = commandLineParser.value("envfile"); + qCInfo(log) << "Reading env file: " << path << "..."; + QFile file(path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qCWarning(log) << "Failed to open env file: " << path; + return 1; + } + + environmentVariables = QStringList{}; + + while (!file.atEnd()) { + QByteArray data = file.readAll(); + if (!data.isEmpty()) { + for (const auto &line : data.split('\0')) { + if (!line.isEmpty()) + *environmentVariables << QString::fromUtf8(line); + } + } + } + + qCDebug(log) << "Env: "; + for (const auto &env : *environmentVariables) + qCDebug(log) << env; + + return std::nullopt; +} + +#ifndef Q_OS_WIN +void forwardSignal(int signum) +{ + qCDebug(log) << "SIGTERM received, terminating inferior..."; + kill(inferiorId, signum); +} +#else +static BOOL WINAPI ctrlHandler(DWORD dwCtrlType) +{ + if (dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_BREAK_EVENT) { + qCDebug(log) << "Terminate inferior..."; + inferiorProcess.terminate(); + return TRUE; + } + return FALSE; +} +#endif + +void setupSignalHandlers() +{ +#ifdef Q_OS_WIN + SetConsoleCtrlHandler(ctrlHandler, TRUE); +#else + struct sigaction act; + memset(&act, 0, sizeof(act)); + + act.sa_handler = SIG_IGN; + if (sigaction(SIGTTOU, &act, NULL)) { + qCWarning(log) << "sigaction SIGTTOU: " << strerror(errno); + doExit(3); + } + + act.sa_handler = forwardSignal; + if (sigaction(SIGTERM, &act, NULL)) { + qCWarning(log) << "sigaction SIGTERM: " << strerror(errno); + doExit(3); + } + + if (sigaction(SIGINT, &act, NULL)) { + qCWarning(log) << "sigaction SIGINT: " << strerror(errno); + doExit(3); + } + + qCDebug(log) << "Signals set up"; +#endif +} + +std::optional tryParseCommandLine(QCoreApplication &app) +{ + commandLineParser.setApplicationDescription("Debug helper for QtCreator"); + commandLineParser.addHelpOption(); + commandLineParser.addOption(QCommandLineOption({"d", "debug"}, "Start inferior in debug mode")); + commandLineParser.addOption(QCommandLineOption({"t", "test"}, "Don't start the control socket")); + commandLineParser.addOption( + QCommandLineOption({"s", "socket"}, "Path to the unix socket", "socket")); + commandLineParser.addOption( + QCommandLineOption({"w", "workingDir"}, "Working directory for inferior", "workingDir")); + commandLineParser.addOption(QCommandLineOption({"v", "verbose"}, "Print debug messages")); + commandLineParser.addOption(QCommandLineOption({"e", "envfile"}, "Path to env file", "envfile")); + commandLineParser.addOption( + QCommandLineOption("wait", + "Message to display to the user while waiting for key press", + "waitmessage", + "Press enter to continue ...")); + + commandLineParser.process(app); + + inferiorCmdAndArguments = commandLineParser.positionalArguments(); + debugMode = commandLineParser.isSet("debug"); + testMode = commandLineParser.isSet("test"); + + if (!(commandLineParser.isSet("socket") || testMode) || inferiorCmdAndArguments.isEmpty()) { + commandLineParser.showHelp(1); + return 1; + } + + if (commandLineParser.isSet("verbose")) + QLoggingCategory::setFilterRules("qtc.process_stub=true"); + + return std::nullopt; +} + +std::optional trySetWorkingDir() +{ + if (commandLineParser.isSet("workingDir")) { + if (!QDir::setCurrent(commandLineParser.value("workingDir"))) { + qCWarning(log) << "Failed to change working directory to: " + << commandLineParser.value("workingDir"); + sendErrChDir(); + return 1; + } + } + + return std::nullopt; +} + +void setupSharedPid() +{ +#ifdef Q_OS_DARWIN + shared_child_pid = (int *) mmap(NULL, + sizeof *shared_child_pid, + PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, + -1, + 0); + *shared_child_pid = 0; +#endif +} + +void onControlSocketConnected() +{ + qCInfo(log) << "Connected to control socket"; + + sendSelfPid(); + setupSignalHandlers(); + + startProcess(inferiorCmdAndArguments[0], + inferiorCmdAndArguments.mid(1), + commandLineParser.value("workingDir")); +} + +void resumeInferior() +{ + qCDebug(log) << "Continuing inferior... (" << inferiorId << ")"; +#ifdef Q_OS_WIN + ResumeThread(win_process_information->hThread); +#else + kill(inferiorId, SIGCONT); +#endif +} + +void killInferior() +{ +#ifdef Q_OS_WIN + inferiorProcess.kill(); +#else + kill(inferiorId, SIGKILL); +#endif +} + +void onControlSocketReadyRead() +{ + //k = kill, i = interrupt, c = continue, s = shutdown + QByteArray data = controlSocket.readAll(); + for (auto ch : data) { + qCDebug(log) << "Received:" << ch; + + switch (ch) { + case 'k': { + qCDebug(log) << "Killing inferior..."; + killInferior(); + break; + } +#ifndef Q_OS_WIN + case 'i': { + qCDebug(log) << "Interrupting inferior..."; + kill(inferiorId, SIGINT); + break; + } +#endif + case 'c': { + resumeInferior(); + break; + } + case 's': { + qCDebug(log) << "Shutting down..."; + doExit(0); + break; + } + } + } +} + +void onControlSocketErrorOccurred(QLocalSocket::LocalSocketError socketError) +{ + qCWarning(log) << "Control socket error:" << socketError; + doExit(1); +} + +void setupControlSocket() +{ + QObject::connect(&controlSocket, &QLocalSocket::connected, &onControlSocketConnected); + QObject::connect(&controlSocket, &QLocalSocket::readyRead, &onControlSocketReadyRead); + QObject::connect(&controlSocket, &QLocalSocket::errorOccurred, &onControlSocketErrorOccurred); + + qCInfo(log) << "Waiting for connection..."; + controlSocket.connectToServer(commandLineParser.value("socket")); +} + +void onStdInReadyRead() +{ + char ch; + std::cin >> ch; + if (ch == 'k') { + killInferior(); + } else { + resumeInferior(); + } +} + +void readKey() +{ +#ifdef Q_OS_WIN + stdInNotifier = new QWinEventNotifier(GetStdHandle(STD_INPUT_HANDLE)); +#else + stdInNotifier = new QSocketNotifier(fileno(stdin), QSocketNotifier::Read); +#endif + QObject::connect(stdInNotifier, &OSSocketNotifier::activated, &onStdInReadyRead); +} diff --git a/src/tools/process_stub/process_stub.qbs b/src/tools/process_stub/process_stub.qbs new file mode 100644 index 00000000000..1fa1bc32a3f --- /dev/null +++ b/src/tools/process_stub/process_stub.qbs @@ -0,0 +1,10 @@ +import qbs 1.0 + +QtcTool { + name: "qtcreator_process_stub" + consoleApplication: true + + Depends { name: "Qt"; submodules: ["core", "network"]; } + + files: [ "main.cpp" ] +} diff --git a/src/tools/processlauncher/CMakeLists.txt b/src/tools/processlauncher/CMakeLists.txt index 9360d1e9f65..11149814ab8 100644 --- a/src/tools/processlauncher/CMakeLists.txt +++ b/src/tools/processlauncher/CMakeLists.txt @@ -1,7 +1,8 @@ +set(LIBSDIR "${PROJECT_SOURCE_DIR}/src/libs") set(UTILSDIR "${PROJECT_SOURCE_DIR}/src/libs/utils") add_qtc_executable(qtcreator_processlauncher - INCLUDES "${UTILSDIR}" + INCLUDES "${LIBSDIR}" DEPENDS Qt::Core Qt::Network DEFINES UTILS_STATIC_LIBRARY SOURCES diff --git a/src/tools/processlauncher/launchersockethandler.cpp b/src/tools/processlauncher/launchersockethandler.cpp index 21167f8d773..c7879dc7cc6 100644 --- a/src/tools/processlauncher/launchersockethandler.cpp +++ b/src/tools/processlauncher/launchersockethandler.cpp @@ -2,10 +2,10 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "launchersockethandler.h" - #include "launcherlogging.h" -#include "processreaper.h" -#include "processutils.h" + +#include +#include #include #include @@ -14,11 +14,11 @@ namespace Utils { namespace Internal { -class Process : public ProcessHelper +class ProcessWithToken : public ProcessHelper { Q_OBJECT public: - Process(quintptr token, QObject *parent = nullptr) : + ProcessWithToken(quintptr token, QObject *parent = nullptr) : ProcessHelper(parent), m_token(token) { } quintptr token() const { return m_token; } @@ -41,7 +41,7 @@ LauncherSocketHandler::LauncherSocketHandler(QString serverPath, QObject *parent LauncherSocketHandler::~LauncherSocketHandler() { for (auto it = m_processes.cbegin(); it != m_processes.cend(); ++it) { - Process *p = it.value(); + ProcessWithToken *p = it.value(); if (p->state() != QProcess::NotRunning) logWarn(QStringLiteral("Shutting down while process %1 is running").arg(p->program())); ProcessReaper::reap(p); @@ -114,7 +114,7 @@ void LauncherSocketHandler::handleSocketClosed() qApp->quit(); } -void LauncherSocketHandler::handleProcessError(Process *process) +void LauncherSocketHandler::handleProcessError(ProcessWithToken *process) { // In case of FailedToStart we won't receive finished signal, so we send the error // packet and remove the process here and now. For all other errors we should expect @@ -124,7 +124,7 @@ void LauncherSocketHandler::handleProcessError(Process *process) handleProcessFinished(process); } -void LauncherSocketHandler::handleProcessStarted(Process *process) +void LauncherSocketHandler::handleProcessStarted(ProcessWithToken *process) { ProcessStartedPacket packet(process->token()); packet.processId = process->processId(); @@ -132,21 +132,21 @@ void LauncherSocketHandler::handleProcessStarted(Process *process) sendPacket(packet); } -void LauncherSocketHandler::handleReadyReadStandardOutput(Process *process) +void LauncherSocketHandler::handleReadyReadStandardOutput(ProcessWithToken *process) { ReadyReadStandardOutputPacket packet(process->token()); packet.standardChannel = process->readAllStandardOutput(); sendPacket(packet); } -void LauncherSocketHandler::handleReadyReadStandardError(Process *process) +void LauncherSocketHandler::handleReadyReadStandardError(ProcessWithToken *process) { ReadyReadStandardErrorPacket packet(process->token()); packet.standardChannel = process->readAllStandardError(); sendPacket(packet); } -void LauncherSocketHandler::handleProcessFinished(Process *process) +void LauncherSocketHandler::handleProcessFinished(ProcessWithToken *process) { ProcessDonePacket packet(process->token()); packet.exitCode = process->exitCode(); @@ -162,7 +162,7 @@ void LauncherSocketHandler::handleProcessFinished(Process *process) void LauncherSocketHandler::handleStartPacket() { - Process *& process = m_processes[m_packetParser.token()]; + ProcessWithToken *& process = m_processes[m_packetParser.token()]; if (!process) process = setupProcess(m_packetParser.token()); if (process->state() != QProcess::NotRunning) { @@ -172,6 +172,7 @@ void LauncherSocketHandler::handleStartPacket() const auto packet = LauncherPacket::extractPacket( m_packetParser.token(), m_packetParser.packetData()); + process->setEnvironment(packet.env); process->setWorkingDirectory(packet.workingDir); // Forwarding is handled by the LauncherInterface @@ -179,10 +180,10 @@ void LauncherSocketHandler::handleStartPacket() ? QProcess::MergedChannels : QProcess::SeparateChannels); process->setStandardInputFile(packet.standardInputFile); ProcessStartHandler *handler = process->processStartHandler(); + handler->setWindowsSpecificStartupFlags(packet.belowNormalPriority, + packet.createConsoleOnWindows); handler->setProcessMode(packet.processMode); handler->setWriteData(packet.writeData); - if (packet.belowNormalPriority) - handler->setBelowNormalPriority(); handler->setNativeArguments(packet.nativeArguments); if (packet.lowPriority) process->setLowPriority(); @@ -196,7 +197,7 @@ void LauncherSocketHandler::handleStartPacket() void LauncherSocketHandler::handleWritePacket() { - Process * const process = m_processes.value(m_packetParser.token()); + ProcessWithToken * const process = m_processes.value(m_packetParser.token()); if (!process) { logWarn("Got write request for unknown process"); return; @@ -213,7 +214,7 @@ void LauncherSocketHandler::handleWritePacket() void LauncherSocketHandler::handleControlPacket() { - Process * const process = m_processes.value(m_packetParser.token()); + ProcessWithToken * const process = m_processes.value(m_packetParser.token()); if (!process) { // This can happen when the process finishes on its own at about the same time the client // sends the request. In this case the process was already deleted. @@ -252,9 +253,9 @@ void LauncherSocketHandler::sendPacket(const LauncherPacket &packet) m_socket->write(packet.serialize()); } -Process *LauncherSocketHandler::setupProcess(quintptr token) +ProcessWithToken *LauncherSocketHandler::setupProcess(quintptr token) { - const auto p = new Process(token, this); + const auto p = new ProcessWithToken(token, this); connect(p, &QProcess::started, this, [this, p] { handleProcessStarted(p); }); connect(p, &QProcess::errorOccurred, this, [this, p] { handleProcessError(p); }); connect(p, &QProcess::finished, this, [this, p] { handleProcessFinished(p); }); @@ -271,7 +272,7 @@ void LauncherSocketHandler::removeProcess(quintptr token) if (it == m_processes.constEnd()) return; - Process *process = it.value(); + ProcessWithToken *process = it.value(); m_processes.erase(it); ProcessReaper::reap(process, process->reaperTimeout()); } @@ -279,4 +280,4 @@ void LauncherSocketHandler::removeProcess(quintptr token) } // namespace Internal } // namespace Utils -#include +#include "launchersockethandler.moc" diff --git a/src/tools/processlauncher/launchersockethandler.h b/src/tools/processlauncher/launchersockethandler.h index e3ec8a65b8f..05eefb09b82 100644 --- a/src/tools/processlauncher/launchersockethandler.h +++ b/src/tools/processlauncher/launchersockethandler.h @@ -3,7 +3,7 @@ #pragma once -#include +#include #include #include @@ -15,7 +15,7 @@ QT_END_NAMESPACE namespace Utils { namespace Internal { -class Process; +class ProcessWithToken; class LauncherSocketHandler : public QObject { @@ -31,11 +31,11 @@ private: void handleSocketError(); void handleSocketClosed(); - void handleProcessStarted(Process *process); - void handleProcessError(Process *process); - void handleProcessFinished(Process *process); - void handleReadyReadStandardOutput(Process *process); - void handleReadyReadStandardError(Process *process); + void handleProcessStarted(ProcessWithToken *process); + void handleProcessError(ProcessWithToken *process); + void handleProcessFinished(ProcessWithToken *process); + void handleReadyReadStandardOutput(ProcessWithToken *process); + void handleReadyReadStandardError(ProcessWithToken *process); void handleStartPacket(); void handleWritePacket(); @@ -44,13 +44,13 @@ private: void sendPacket(const LauncherPacket &packet); - Process *setupProcess(quintptr token); + ProcessWithToken *setupProcess(quintptr token); void removeProcess(quintptr token); const QString m_serverPath; QLocalSocket * const m_socket; PacketParser m_packetParser; - QHash m_processes; + QHash m_processes; }; } // namespace Internal diff --git a/src/tools/processlauncher/processlauncher-main.cpp b/src/tools/processlauncher/processlauncher-main.cpp index f23ea88c92e..0629ec3c6d2 100644 --- a/src/tools/processlauncher/processlauncher-main.cpp +++ b/src/tools/processlauncher/processlauncher-main.cpp @@ -3,7 +3,8 @@ #include "launcherlogging.h" #include "launchersockethandler.h" -#include "singleton.h" + +#include #include #include diff --git a/src/tools/processlauncher/processlauncher.qbs b/src/tools/processlauncher/processlauncher.qbs index 757d50f9bb6..2f03f7fba57 100644 --- a/src/tools/processlauncher/processlauncher.qbs +++ b/src/tools/processlauncher/processlauncher.qbs @@ -7,7 +7,7 @@ QtcTool { Depends { name: "Qt.network" } cpp.defines: base.concat("UTILS_STATIC_LIBRARY") - cpp.includePaths: base.concat(pathToUtils) + cpp.includePaths: base.concat(pathToLibs) Properties { condition: qbs.targetOS.contains("windows") @@ -24,6 +24,7 @@ QtcTool { "processlauncher-main.cpp", ] + property string pathToLibs: sourceDirectory + "/../../libs" property string pathToUtils: sourceDirectory + "/../../libs/utils" Group { name: "protocol sources" diff --git a/src/tools/qml2puppet/CMakeLists.txt b/src/tools/qml2puppet/CMakeLists.txt index 4f08b1fe355..77bde355e9f 100644 --- a/src/tools/qml2puppet/CMakeLists.txt +++ b/src/tools/qml2puppet/CMakeLists.txt @@ -29,8 +29,6 @@ else() set(QT_VERSION_MAJOR ${Qt6_VERSION_MAJOR}) endif() -configure_file(../../app/app_version.h.cmakein app/app_version.h ESCAPE_QUOTES) - if (NOT TARGET QmlPuppetCommunication) include(../../libs/qmlpuppetcommunication/QmlPuppetCommunication.cmake) endif() @@ -46,28 +44,32 @@ add_qtc_executable(qml2puppet ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} SOURCES qml2puppet/qml2puppetmain.cpp - qml2puppet/qmlbase.h qml2puppet/appmetadata.h + qml2puppet/qmlbase.h + qml2puppet/appmetadata.cpp qml2puppet/appmetadata.h qml2puppet/qmlpuppet.h qml2puppet/qmlpuppet.cpp qml2puppet/configcrashpad.h qmlpuppet.qrc PROPERTIES OUTPUT_NAME qml2puppet-${IDE_VERSION} ) -if(TARGET qml2puppet) +if (TARGET qml2puppet) execute_process( COMMAND git describe --tags --always --dirty=+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} RESULT_VARIABLE GIT_SHA_RESULT - OUTPUT_VARIABLE GIT_SHA_OUTPUT + OUTPUT_VARIABLE GIT_SHA ERROR_VARIABLE GIT_SHA_ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE ) #if we are not a git repository use the .tag file - if(NOT GIT_SHA_OUTPUT) - file(READ ${CMAKE_CURRENT_SOURCE_DIR}/../../../.tag GIT_SHA_OUTPUT) + if(NOT GIT_SHA) + file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/../../../.tag GIT_SHA LIMIT_COUNT 1) endif() - add_definitions( -D GIT_SHA=${GIT_SHA_OUTPUT} ) + set(IDE_REVISION_STR ${GIT_SHA}) + + configure_file(../../app/app_version.h.cmakein app/app_version.h ESCAPE_QUOTES) endif() extend_qtc_executable(qml2puppet diff --git a/src/tools/qml2puppet/qml2puppet/appmetadata.cpp b/src/tools/qml2puppet/qml2puppet/appmetadata.cpp new file mode 100644 index 00000000000..1896e4db926 --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/appmetadata.cpp @@ -0,0 +1,51 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "appmetadata.h" + +#include + +namespace QDSMeta::AppInfo { + +void printAppInfo() +{ + qInfo() << Qt::endl + << "<< QDS Meta Info >>" << Qt::endl + << "App Info" << Qt::endl + << " - Name :" << Core::Constants::IDE_ID << Qt::endl + << " - Version :" << Core::Constants::IDE_VERSION_DISPLAY << Qt::endl + << " - Author :" << Core::Constants::IDE_AUTHOR << Qt::endl + << " - Year :" << Core::Constants::IDE_YEAR << Qt::endl + << " - App :" << QCoreApplication::applicationName() << Qt::endl + << "Build Info " << Qt::endl + << " - Date :" << __DATE__ << Qt::endl + << " - Commit :" << QStringLiteral(QDS_STRINGIFY(IDE_REVISION_STR)) << Qt::endl + << " - Qt Version :" << QT_VERSION_STR << Qt::endl + << "Compiler Info " << Qt::endl +#if defined(__GNUC__) + << " - GCC :" << __GNUC__ << Qt::endl + << " - GCC Minor :" << __GNUC_MINOR__ << Qt::endl + << " - GCC Patch :" << __GNUC_PATCHLEVEL__ << Qt::endl +#endif +#if defined(_MSC_VER) + << " - MSC Short :" << _MSC_VER << Qt::endl + << " - MSC Full :" << _MSC_FULL_VER << Qt::endl +#endif +#if defined(__clang__) + << " - clang maj :" << __clang_major__ << Qt::endl + << " - clang min :" << __clang_minor__ << Qt::endl + << " - clang patch :" << __clang_patchlevel__ << Qt::endl +#endif + << "<< End Of QDS Meta Info >>" << Qt::endl; + exit(0); +} + +void registerAppInfo(const QString &appName) +{ + QCoreApplication::setOrganizationName(Core::Constants::IDE_AUTHOR); + QCoreApplication::setOrganizationDomain("qt-project.org"); + QCoreApplication::setApplicationName(appName); + QCoreApplication::setApplicationVersion(Core::Constants::IDE_VERSION_LONG); +} + +} // namespace QDSMeta::AppInfo diff --git a/src/tools/qml2puppet/qml2puppet/appmetadata.h b/src/tools/qml2puppet/qml2puppet/appmetadata.h index d134135fd8f..18eb650461e 100644 --- a/src/tools/qml2puppet/qml2puppet/appmetadata.h +++ b/src/tools/qml2puppet/qml2puppet/appmetadata.h @@ -5,8 +5,6 @@ #include #include -#include - // Common functions can be used in all QDS apps namespace QDSMeta { @@ -50,46 +48,8 @@ namespace AppInfo { #define STRINGIFY_INTERNAL(x) #x #define QDS_STRINGIFY(x) STRINGIFY_INTERNAL(x) -inline void printAppInfo() -{ - qInfo() << Qt::endl - << "<< QDS Meta Info >>" << Qt::endl - << "App Info" << Qt::endl - << " - Name :" << Core::Constants::IDE_ID << Qt::endl - << " - Version :" << Core::Constants::IDE_VERSION_DISPLAY << Qt::endl - << " - Author :" << Core::Constants::IDE_AUTHOR << Qt::endl - << " - Year :" << Core::Constants::IDE_YEAR << Qt::endl - << " - App :" << QCoreApplication::applicationName() << Qt::endl - << "Build Info " << Qt::endl - << " - Date :" << __DATE__ << Qt::endl - << " - Commit :" << QStringLiteral(QDS_STRINGIFY(GIT_SHA)) << Qt::endl - << " - Qt Version :" << QT_VERSION_STR << Qt::endl - << "Compiler Info " << Qt::endl -#if defined(__GNUC__) - << " - GCC :" << __GNUC__ << Qt::endl - << " - GCC Minor :" << __GNUC_MINOR__ << Qt::endl - << " - GCC Patch :" << __GNUC_PATCHLEVEL__ << Qt::endl -#endif -#if defined(_MSC_VER) - << " - MSC Short :" << _MSC_VER << Qt::endl - << " - MSC Full :" << _MSC_FULL_VER << Qt::endl -#endif -#if defined(__clang__) - << " - clang maj :" << __clang_major__ << Qt::endl - << " - clang min :" << __clang_minor__ << Qt::endl - << " - clang patch :" << __clang_patchlevel__ << Qt::endl -#endif - << "<< End Of QDS Meta Info >>" << Qt::endl; - exit(0); -} - -inline void registerAppInfo(const QString &appName) -{ - QCoreApplication::setOrganizationName(Core::Constants::IDE_AUTHOR); - QCoreApplication::setOrganizationDomain("qt-project.org"); - QCoreApplication::setApplicationName(appName); - QCoreApplication::setApplicationVersion(Core::Constants::IDE_VERSION_LONG); -} +void printAppInfo(); +void registerAppInfo(const QString &appName); } // namespace AppInfo } // namespace QDSMeta diff --git a/src/tools/qml2puppet/qml2puppet/qmlpuppet.cpp b/src/tools/qml2puppet/qml2puppet/qmlpuppet.cpp index eab99ac37ef..6d3fa2ce36a 100644 --- a/src/tools/qml2puppet/qml2puppet/qmlpuppet.cpp +++ b/src/tools/qml2puppet/qml2puppet/qmlpuppet.cpp @@ -8,6 +8,7 @@ #include #endif +#include #include #include diff --git a/src/tools/qtcdebugger/main.cpp b/src/tools/qtcdebugger/main.cpp index 5aee7b6fe91..cbb4b935d72 100644 --- a/src/tools/qtcdebugger/main.cpp +++ b/src/tools/qtcdebugger/main.cpp @@ -520,7 +520,7 @@ int main(int argc, char *argv[]) } if (debug) qDebug() << "Mode=" << optMode << " PID=" << argProcessId << " Evt=" << argWinCrashEvent; - bool ex = 0; + int ex = 0; switch (optMode) { case HelpMode: usage(QCoreApplication::applicationFilePath(), errorMessage); diff --git a/src/tools/qtcreatorcrashhandler/crashhandlerdialog.cpp b/src/tools/qtcreatorcrashhandler/crashhandlerdialog.cpp index ff1d503a6e6..c8853f53459 100644 --- a/src/tools/qtcreatorcrashhandler/crashhandlerdialog.cpp +++ b/src/tools/qtcreatorcrashhandler/crashhandlerdialog.cpp @@ -161,7 +161,7 @@ public: QCoreApplication::quit(); }); - using namespace Utils::Layouting; + using namespace Layouting; Column { Row { m_iconLabel, m_introLabel, st }, @@ -205,11 +205,11 @@ CrashHandlerDialog::~CrashHandlerDialog() bool CrashHandlerDialog::runDebuggerWhileBacktraceNotFinished() { // Check settings. - QSettings settings(QSettings::IniFormat, QSettings::UserScope, + QSettings settings(QSettings::IniFormat, + QSettings::UserScope, QLatin1String(Core::Constants::IDE_SETTINGSVARIANT_STR), QLatin1String(SettingsApplication)); - if (settings.value(QLatin1String(SettingsKeySkipWarningAbortingBacktrace), false).toBool()) - return true; + Utils::CheckableMessageBox::initialize(&settings); // Ask user. const QString title = tr("Run Debugger And Abort Collecting Backtrace?"); @@ -219,15 +219,16 @@ bool CrashHandlerDialog::runDebuggerWhileBacktraceNotFinished() "

    You have requested to run the debugger while collecting the backtrace was not " "finished.

    " ""); - const QString checkBoxText = tr("Do not &ask again."); - bool checkBoxSetting = false; - const QDialogButtonBox::StandardButton button = Utils::CheckableMessageBox::question(this, - title, message, checkBoxText, &checkBoxSetting, - QDialogButtonBox::Yes | QDialogButtonBox::No, QDialogButtonBox::No); - if (checkBoxSetting) - settings.setValue(QLatin1String(SettingsKeySkipWarningAbortingBacktrace), checkBoxSetting); - return button == QDialogButtonBox::Yes; + const QMessageBox::StandardButton button + = Utils::CheckableMessageBox::question(this, + title, + message, + QString(SettingsKeySkipWarningAbortingBacktrace), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No); + + return button == QMessageBox::Yes; } void CrashHandlerDialog::setToFinalState() diff --git a/src/tools/sdktool/CMakeLists.txt b/src/tools/sdktool/CMakeLists.txt index 4adf618b845..937aa04087a 100644 --- a/src/tools/sdktool/CMakeLists.txt +++ b/src/tools/sdktool/CMakeLists.txt @@ -66,33 +66,7 @@ add_qtc_library(sdktoolLib rmqtoperation.cpp rmqtoperation.h rmtoolchainoperation.cpp rmtoolchainoperation.h settings.cpp settings.h -) - -extend_qtc_library(sdktoolLib - SOURCES_PREFIX "${UtilsSourcesDir}" - PUBLIC_DEFINES UTILS_STATIC_LIBRARY - SOURCES - commandline.cpp commandline.h - devicefileaccess.cpp devicefileaccess.h - environment.cpp environment.h - filepath.cpp filepath.h - fileutils.cpp fileutils.h - hostosinfo.cpp hostosinfo.h - macroexpander.cpp macroexpander.h - namevaluedictionary.cpp namevaluedictionary.h - namevalueitem.cpp namevalueitem.h - persistentsettings.cpp persistentsettings.h - qtcassert.cpp qtcassert.h - savefile.cpp savefile.h - stringutils.cpp stringutils.h -) - -extend_qtc_library(sdktoolLib CONDITION APPLE - SOURCES_PREFIX "${UtilsSourcesDir}" - SOURCES - fileutils_mac.mm fileutils_mac.h - PUBLIC_DEPENDS - ${FWFoundation} + sdkpersistentsettings.cpp sdkpersistentsettings.h ) if (MSVC) @@ -120,7 +94,7 @@ add_qtc_executable(sdktool main.cpp ) -if (MSVC AND TARGET sdktool AND Qt5_VERSION VERSION_LESS 6.0.0) +if (MSVC AND TARGET sdktool AND TARGET Qt5::Core) # find out if Qt is static and set /MT if so get_target_property(_input_type Qt5::Core TYPE) if (${_input_type} STREQUAL "STATIC_LIBRARY") diff --git a/src/tools/sdktool/addcmakeoperation.cpp b/src/tools/sdktool/addcmakeoperation.cpp index 751db965e82..f97fcef2a68 100644 --- a/src/tools/sdktool/addcmakeoperation.cpp +++ b/src/tools/sdktool/addcmakeoperation.cpp @@ -4,13 +4,10 @@ #include "addcmakeoperation.h" #include "addkeysoperation.h" -#include "findkeyoperation.h" #include "findvalueoperation.h" #include "getoperation.h" #include "rmkeysoperation.h" -#include "settings.h" - #ifdef WITH_TESTS #include #endif @@ -205,7 +202,7 @@ QVariantMap AddCMakeData::addCMake(const QVariantMap &map) const data << KeyValuePair({cm, ID_KEY}, QVariant(m_id)); data << KeyValuePair({cm, DISPLAYNAME_KEY}, QVariant(m_displayName)); data << KeyValuePair({cm, AUTODETECTED_KEY}, QVariant(true)); - data << KeyValuePair({cm, PATH_KEY}, Utils::FilePath::fromUserInput(m_path).toVariant()); + data << KeyValuePair({cm, PATH_KEY}, QVariant(m_path)); KeyValuePairList extraList; for (const KeyValuePair &pair : std::as_const(m_extra)) extraList << KeyValuePair(QStringList({cm}) << pair.key, pair.value); diff --git a/src/tools/sdktool/adddebuggeroperation.cpp b/src/tools/sdktool/adddebuggeroperation.cpp index c11b20ec81d..69de58be05e 100644 --- a/src/tools/sdktool/adddebuggeroperation.cpp +++ b/src/tools/sdktool/adddebuggeroperation.cpp @@ -222,8 +222,7 @@ QVariantMap AddDebuggerData::addDebugger(const QVariantMap &map) const data << KeyValuePair(QStringList() << debugger << QLatin1String(ABIS), QVariant(m_abis)); data << KeyValuePair(QStringList() << debugger << QLatin1String(ENGINE_TYPE), QVariant(m_engine)); - data << KeyValuePair(QStringList() << debugger << QLatin1String(BINARY), - Utils::FilePath::fromUserInput(m_binary).toSettings()); + data << KeyValuePair(QStringList() << debugger << QLatin1String(BINARY), QVariant(m_binary)); data << KeyValuePair(QStringList() << QLatin1String(COUNT), QVariant(count + 1)); diff --git a/src/tools/sdktool/addkitoperation.cpp b/src/tools/sdktool/addkitoperation.cpp index 14569ba8398..4f0911d011a 100644 --- a/src/tools/sdktool/addkitoperation.cpp +++ b/src/tools/sdktool/addkitoperation.cpp @@ -15,6 +15,7 @@ #include "settings.h" +#include #include #include @@ -685,8 +686,7 @@ QVariantMap AddKitData::addKit(const QVariantMap &map, if (!m_buildDevice.isNull()) data << KeyValuePair({kit, DATA, BUILDDEVICE_ID}, QVariant(m_buildDevice)); if (!m_sysRoot.isNull()) - data << KeyValuePair({kit, DATA, SYSROOT}, - Utils::FilePath::fromUserInput(m_sysRoot).toSettings()); + data << KeyValuePair({kit, DATA, SYSROOT}, QVariant(QDir::cleanPath(m_sysRoot))); for (auto i = m_tcs.constBegin(); i != m_tcs.constEnd(); ++i) data << KeyValuePair({kit, DATA, TOOLCHAIN, i.key()}, QVariant(i.value())); if (!qtId.isNull()) diff --git a/src/tools/sdktool/addqtoperation.cpp b/src/tools/sdktool/addqtoperation.cpp index 875e59d9143..ee48c1e5ec5 100644 --- a/src/tools/sdktool/addqtoperation.cpp +++ b/src/tools/sdktool/addqtoperation.cpp @@ -11,19 +11,16 @@ #include "settings.h" -#include - #ifdef WITH_TESTS #include #endif +#include #include #include Q_LOGGING_CATEGORY(log, "qtc.sdktool.operations.addqt", QtWarningMsg) -using namespace Utils; - // Qt version file stuff: const char PREFIX[] = "QtVersion."; const char VERSION[] = "Version"; @@ -282,7 +279,7 @@ QVariantMap AddQtData::addQt(const QVariantMap &map) const const QString qt = QString::fromLatin1(PREFIX) + QString::number(versionCount); // Sanitize qmake path: - FilePath saneQmake = FilePath::fromUserInput(m_qmake).cleanPath(); + QString saneQmake = QDir::cleanPath(m_qmake); // insert data: KeyValuePairList data; @@ -291,7 +288,7 @@ QVariantMap AddQtData::addQt(const QVariantMap &map) const data << KeyValuePair(QStringList() << qt << QLatin1String(AUTODETECTED), QVariant(true)); data << KeyValuePair(QStringList() << qt << QLatin1String(AUTODETECTION_SOURCE), QVariant(sdkId)); - data << KeyValuePair(QStringList() << qt << QLatin1String(QMAKE), saneQmake.toSettings()); + data << KeyValuePair(QStringList() << qt << QLatin1String(QMAKE), QVariant(saneQmake)); data << KeyValuePair(QStringList() << qt << QLatin1String(TYPE), QVariant(m_type)); data << KeyValuePair(QStringList() << qt << ABIS, QVariant(m_abis)); diff --git a/src/tools/sdktool/addtoolchainoperation.cpp b/src/tools/sdktool/addtoolchainoperation.cpp index 1290711e566..74f4965cf87 100644 --- a/src/tools/sdktool/addtoolchainoperation.cpp +++ b/src/tools/sdktool/addtoolchainoperation.cpp @@ -4,13 +4,10 @@ #include "addtoolchainoperation.h" #include "addkeysoperation.h" -#include "findkeyoperation.h" #include "findvalueoperation.h" #include "getoperation.h" #include "rmkeysoperation.h" -#include "settings.h" - #include #ifdef WITH_TESTS @@ -283,7 +280,7 @@ QVariantMap AddToolChainData::addToolChain(const QVariantMap &map) const data << KeyValuePair({tc, LANGUAGE_KEY_V2}, QVariant(newLang)); data << KeyValuePair({tc, DISPLAYNAME}, QVariant(m_displayName)); data << KeyValuePair({tc, AUTODETECTED}, QVariant(true)); - data << KeyValuePair({tc, PATH}, Utils::FilePath::fromUserInput(m_path).toSettings()); + data << KeyValuePair({tc, PATH}, QVariant(m_path)); data << KeyValuePair({tc, TARGET_ABI}, QVariant(m_targetAbi)); QVariantList abis; const QStringList abiStrings = m_supportedAbis.split(','); diff --git a/src/tools/sdktool/main.cpp b/src/tools/sdktool/main.cpp index 4f28b64b9e5..1136aa9d446 100644 --- a/src/tools/sdktool/main.cpp +++ b/src/tools/sdktool/main.cpp @@ -28,8 +28,10 @@ #include #include +#include #include +#include #include #include @@ -60,10 +62,7 @@ void printHelp(const std::vector> &operations) std::cout << " --sdkpath=PATH|-s PATH Set the path to the SDK files" << std::endl << std::endl; std::cout << "Default sdkpath is \"" - << qPrintable(QDir::cleanPath( - Utils::FilePath::fromString(QCoreApplication::applicationDirPath()) - .pathAppended(DATA_PATH) - .toUserOutput())) + << qPrintable(QDir::cleanPath(QCoreApplication::applicationDirPath() + '/' + DATA_PATH)) << "\"" << std::endl << std::endl; @@ -105,7 +104,7 @@ int parseArguments(const QStringList &args, Settings *s, // sdkpath if (current.startsWith(QLatin1String("--sdkpath="))) { - s->sdkPath = Utils::FilePath::fromString(current.mid(10)); + s->sdkPath = current.mid(10); continue; } if (current == QLatin1String("-s")) { @@ -114,7 +113,7 @@ int parseArguments(const QStringList &args, Settings *s, printHelp(operations); return 1; } - s->sdkPath = Utils::FilePath::fromString(next); + s->sdkPath = next; ++i; // skip next; continue; } diff --git a/src/tools/sdktool/operation.cpp b/src/tools/sdktool/operation.cpp index ed673b9b780..7a81c3c7adf 100644 --- a/src/tools/sdktool/operation.cpp +++ b/src/tools/sdktool/operation.cpp @@ -4,8 +4,7 @@ #include "operation.h" #include "settings.h" - -#include +#include "sdkpersistentsettings.h" #include #include @@ -65,9 +64,9 @@ QVariantMap Operation::load(const QString &file) QVariantMap map; // Read values from original file: - Utils::FilePath path = Settings::instance()->getPath(file); - if (path.exists()) { - Utils::PersistentSettingsReader reader; + QString path = Settings::instance()->getPath(file); + if (QFileInfo::exists(path)) { + SdkPersistentSettingsReader reader; if (!reader.load(path)) return QVariantMap(); map = reader.restoreValues(); @@ -78,32 +77,32 @@ QVariantMap Operation::load(const QString &file) bool Operation::save(const QVariantMap &map, const QString &file) const { - Utils::FilePath path = Settings::instance()->getPath(file); + QString path = Settings::instance()->getPath(file); if (path.isEmpty()) { std::cerr << "Error: No path found for " << qPrintable(file) << "." << std::endl; return false; } - Utils::FilePath dirName = path.parentDir(); - QDir dir(dirName.toString()); + QString dirName = QDir::cleanPath(path + "/.."); + QDir dir(dirName); if (!dir.exists() && !dir.mkpath(QLatin1String("."))) { - std::cerr << "Error: Could not create directory " << qPrintable(dirName.toString()) + std::cerr << "Error: Could not create directory " << qPrintable(dirName) << "." << std::endl; return false; } - Utils::PersistentSettingsWriter writer(path, QLatin1String("QtCreator") + SdkPersistentSettingsWriter writer(path, QLatin1String("QtCreator") + file[0].toUpper() + file.mid(1)); QString errorMessage; if (!writer.save(map, &errorMessage)) { - std::cerr << "Error: Could not save settings " << qPrintable(path.toString()) + std::cerr << "Error: Could not save settings " << qPrintable(path) << "." << std::endl; return false; } - if (!path.setPermissions(QFile::ReadOwner | QFile::WriteOwner + if (!QFile(path).setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadGroup | QFile::ReadOther)) { - std::cerr << "Error: Could not set permissions for " << qPrintable(path.toString()) + std::cerr << "Error: Could not set permissions for " << qPrintable(path) << "." << std::endl; return false; } diff --git a/src/tools/sdktool/operation.h b/src/tools/sdktool/operation.h index 1b43eb7b441..886ed386675 100644 --- a/src/tools/sdktool/operation.h +++ b/src/tools/sdktool/operation.h @@ -3,8 +3,6 @@ #pragma once -#include - #include #include diff --git a/src/tools/sdktool/sdkpersistentsettings.cpp b/src/tools/sdktool/sdkpersistentsettings.cpp new file mode 100644 index 00000000000..4ca9b5e3701 --- /dev/null +++ b/src/tools/sdktool/sdkpersistentsettings.cpp @@ -0,0 +1,871 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "sdkpersistentsettings.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_OS_WIN +# include +# include +#else +# include +# include +#endif + + +#define QTC_ASSERT_STRINGIFY_HELPER(x) #x +#define QTC_ASSERT_STRINGIFY(x) QTC_ASSERT_STRINGIFY_HELPER(x) +#define QTC_ASSERT_STRING(cond) writeAssertLocation(\ + "\"" cond"\" in " __FILE__ ":" QTC_ASSERT_STRINGIFY(__LINE__)) + +// The 'do {...} while (0)' idiom is not used for the main block here to be +// able to use 'break' and 'continue' as 'actions'. + +#define QTC_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QTC_ASSERT_STRING(#cond); action; } do {} while (0) +#define QTC_CHECK(cond) if (Q_LIKELY(cond)) {} else { QTC_ASSERT_STRING(#cond); } do {} while (0) +#define QTC_GUARD(cond) ((Q_LIKELY(cond)) ? true : (QTC_ASSERT_STRING(#cond), false)) + +void writeAssertLocation(const char *msg) +{ + const QByteArray time = QTime::currentTime().toString(Qt::ISODateWithMs).toLatin1(); + static bool goBoom = qEnvironmentVariableIsSet("QTC_FATAL_ASSERTS"); + if (goBoom) + qFatal("SOFT ASSERT [%s] made fatal: %s", time.data(), msg); + else + qDebug("SOFT ASSERT [%s]: %s", time.data(), msg); +} + +static QFile::Permissions m_umask; + +class SdkSaveFile : public QFile +{ +public: + explicit SdkSaveFile(const QString &filePath) : m_finalFilePath(filePath) {} + ~SdkSaveFile() override; + + bool open(OpenMode flags = QIODevice::WriteOnly) override; + + void rollback(); + bool commit(); + + static void initializeUmask(); + +private: + const QString m_finalFilePath; + std::unique_ptr m_tempFile; + bool m_finalized = true; +}; + +SdkSaveFile::~SdkSaveFile() +{ + if (!m_finalized) + rollback(); +} + +bool SdkSaveFile::open(OpenMode flags) +{ + if (m_finalFilePath.isEmpty()) { + qWarning("Save file path empty"); + return false; + } + + QFile ofi(m_finalFilePath); + // Check whether the existing file is writable + if (ofi.exists() && !ofi.open(QIODevice::ReadWrite)) { + setErrorString(ofi.errorString()); + return false; + } + + m_tempFile = std::make_unique(m_finalFilePath); + m_tempFile->setAutoRemove(false); + if (!m_tempFile->open()) + return false; + setFileName(m_tempFile->fileName()); + + if (!QFile::open(flags)) + return false; + + m_finalized = false; // needs clean up in the end + if (ofi.exists()) { + setPermissions(ofi.permissions()); // Ignore errors + } else { + Permissions permAll = QFile::ReadOwner + | QFile::ReadGroup + | QFile::ReadOther + | QFile::WriteOwner + | QFile::WriteGroup + | QFile::WriteOther; + + // set permissions with respect to the current umask + setPermissions(permAll & ~m_umask); + } + + return true; +} + +void SdkSaveFile::rollback() +{ + close(); + if (m_tempFile) + m_tempFile->remove(); + m_finalized = true; +} + +static QString resolveSymlinks(QString current) +{ + int links = 16; + while (links--) { + const QFileInfo info(current); + if (!info.isSymLink()) + return {}; + current = info.symLinkTarget(); + } + return current; +} + +bool SdkSaveFile::commit() +{ + QTC_ASSERT(!m_finalized && m_tempFile, return false;); + m_finalized = true; + + if (!flush()) { + close(); + m_tempFile->remove(); + return false; + } +#ifdef Q_OS_WIN + FlushFileBuffers(reinterpret_cast(_get_osfhandle(handle()))); +#elif _POSIX_SYNCHRONIZED_IO > 0 + fdatasync(handle()); +#else + fsync(handle()); +#endif + close(); + m_tempFile->close(); + if (error() != NoError) { + m_tempFile->remove(); + return false; + } + + QString finalFileName = resolveSymlinks(m_finalFilePath); + +#ifdef Q_OS_WIN + // Release the file lock + m_tempFile.reset(); + bool result = ReplaceFile(finalFileName.toStdWString().data(), + fileName().toStdWString().data(), + nullptr, REPLACEFILE_IGNORE_MERGE_ERRORS, nullptr, nullptr); + if (!result) { + DWORD replaceErrorCode = GetLastError(); + QString errorStr; + if (!QFile::exists(finalFileName)) { + // Replace failed because finalFileName does not exist, try rename. + if (!(result = rename(finalFileName))) + errorStr = errorString(); + } else { + if (replaceErrorCode == ERROR_UNABLE_TO_REMOVE_REPLACED) { + // If we do not get the rights to remove the original final file we still might try + // to replace the file contents + result = MoveFileEx(fileName().toStdWString().data(), + finalFileName.toStdWString().data(), + MOVEFILE_COPY_ALLOWED + | MOVEFILE_REPLACE_EXISTING + | MOVEFILE_WRITE_THROUGH); + if (!result) + replaceErrorCode = GetLastError(); + } + if (!result) { + wchar_t messageBuffer[256]; + FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, replaceErrorCode, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + messageBuffer, sizeof(messageBuffer), nullptr); + errorStr = QString::fromWCharArray(messageBuffer); + } + } + if (!result) { + remove(); + setErrorString(errorStr); + } + } + + return result; +#else + const QString backupName = finalFileName + '~'; + + // Back up current file. + // If it's opened by another application, the lock follows the move. + if (QFile::exists(finalFileName)) { + // Kill old backup. Might be useful if creator crashed before removing backup. + QFile::remove(backupName); + QFile finalFile(finalFileName); + if (!finalFile.rename(backupName)) { + m_tempFile->remove(); + setErrorString(finalFile.errorString()); + return false; + } + } + + bool result = true; + if (!m_tempFile->rename(finalFileName)) { + // The case when someone else was able to create finalFileName after we've renamed it. + // Higher level call may try to save this file again but here we do nothing and + // return false while keeping the error string from last rename call. + const QString &renameError = m_tempFile->errorString(); + m_tempFile->remove(); + setErrorString(renameError); + QFile::rename(backupName, finalFileName); // rollback to backup if possible ... + return false; // ... or keep the backup copy at least + } + + QFile::remove(backupName); + + return result; +#endif +} + +void SdkSaveFile::initializeUmask() +{ +#ifdef Q_OS_WIN + m_umask = QFile::WriteGroup | QFile::WriteOther; +#else + // Get the current process' file creation mask (umask) + // umask() is not thread safe so this has to be done by single threaded + // application initialization + mode_t mask = umask(0); // get current umask + umask(mask); // set it back + + m_umask = ((mask & S_IRUSR) ? QFile::ReadOwner : QFlags()) + | ((mask & S_IWUSR) ? QFile::WriteOwner : QFlags()) + | ((mask & S_IXUSR) ? QFile::ExeOwner : QFlags()) + | ((mask & S_IRGRP) ? QFile::ReadGroup : QFlags()) + | ((mask & S_IWGRP) ? QFile::WriteGroup : QFlags()) + | ((mask & S_IXGRP) ? QFile::ExeGroup : QFlags()) + | ((mask & S_IROTH) ? QFile::ReadOther : QFlags()) + | ((mask & S_IWOTH) ? QFile::WriteOther : QFlags()) + | ((mask & S_IXOTH) ? QFile::ExeOther : QFlags()); +#endif +} + +class SdkFileSaverBase +{ +public: + SdkFileSaverBase() = default; + virtual ~SdkFileSaverBase() = default; + + QString filePath() const { return m_filePath; } + bool hasError() const { return m_hasError; } + QString errorString() const { return m_errorString; } + virtual bool finalize(); + bool finalize(QString *errStr); + + bool write(const char *data, int len); + bool write(const QByteArray &bytes); + bool setResult(QTextStream *stream); + bool setResult(QDataStream *stream); + bool setResult(QXmlStreamWriter *stream); + bool setResult(bool ok); + + QFile *file() { return m_file.get(); } + +protected: + std::unique_ptr m_file; + QString m_filePath; + QString m_errorString; + bool m_hasError = false; +}; + +bool SdkFileSaverBase::finalize() +{ + m_file->close(); + setResult(m_file->error() == QFile::NoError); + m_file.reset(); + return !m_hasError; +} + +bool SdkFileSaverBase::finalize(QString *errStr) +{ + if (finalize()) + return true; + if (errStr) + *errStr = errorString(); + return false; +} + +bool SdkFileSaverBase::write(const char *data, int len) +{ + if (m_hasError) + return false; + return setResult(m_file->write(data, len) == len); +} + +bool SdkFileSaverBase::write(const QByteArray &bytes) +{ + if (m_hasError) + return false; + return setResult(m_file->write(bytes) == bytes.count()); +} + +bool SdkFileSaverBase::setResult(bool ok) +{ + if (!ok && !m_hasError) { + if (!m_file->errorString().isEmpty()) { + m_errorString = QString("Cannot write file %1: %2") + .arg(m_filePath, m_file->errorString()); + } else { + m_errorString = QString("Cannot write file %1. Disk full?") + .arg(m_filePath); + } + m_hasError = true; + } + return ok; +} + +bool SdkFileSaverBase::setResult(QTextStream *stream) +{ + stream->flush(); + return setResult(stream->status() == QTextStream::Ok); +} + +bool SdkFileSaverBase::setResult(QDataStream *stream) +{ + return setResult(stream->status() == QDataStream::Ok); +} + +bool SdkFileSaverBase::setResult(QXmlStreamWriter *stream) +{ + return setResult(!stream->hasError()); +} + +// SdkFileSaver + +class SdkFileSaver : public SdkFileSaverBase +{ +public: + // QIODevice::WriteOnly is implicit + explicit SdkFileSaver(const QString &filePath, QIODevice::OpenMode mode = QIODevice::NotOpen); + + bool finalize() override; + +private: + bool m_isSafe = false; +}; + +SdkFileSaver::SdkFileSaver(const QString &filePath, QIODevice::OpenMode mode) +{ + m_filePath = filePath; + // Workaround an assert in Qt -- and provide a useful error message, too: +#ifdef Q_OS_WIN + // Taken from: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx + static const QStringList reservedNames + = {"CON", "PRN", "AUX", "NUL", + "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", + "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}; + const QString fn = QFileInfo(filePath).baseName().toUpper(); + if (reservedNames.contains(fn)) { + m_errorString = QString("%1: Is a reserved filename on Windows. Cannot save.").arg(filePath); + m_hasError = true; + return; + } +#endif + + if (mode & (QIODevice::ReadOnly | QIODevice::Append)) { + m_file.reset(new QFile{filePath}); + m_isSafe = false; + } else { + m_file.reset(new SdkSaveFile(filePath)); + m_isSafe = true; + } + if (!m_file->open(QIODevice::WriteOnly | mode)) { + QString err = QFileInfo::exists(filePath) ? + QString("Cannot overwrite file %1: %2") : QString("Cannot create file %1: %2"); + m_errorString = err.arg(filePath, m_file->errorString()); + m_hasError = true; + } +} + +bool SdkFileSaver::finalize() +{ + if (!m_isSafe) + return SdkFileSaverBase::finalize(); + + auto sf = static_cast(m_file.get()); + if (m_hasError) { + if (sf->isOpen()) + sf->rollback(); + } else { + setResult(sf->commit()); + } + m_file.reset(); + return !m_hasError; +} + + +// Read and write rectangle in X11 resource syntax "12x12+4+3" +static QString rectangleToString(const QRect &r) +{ + QString result; + QTextStream str(&result); + str << r.width() << 'x' << r.height(); + if (r.x() >= 0) + str << '+'; + str << r.x(); + if (r.y() >= 0) + str << '+'; + str << r.y(); + return result; +} + +static QRect stringToRectangle(const QString &v) +{ + static QRegularExpression pattern("^(\\d+)x(\\d+)([-+]\\d+)([-+]\\d+)$"); + Q_ASSERT(pattern.isValid()); + const QRegularExpressionMatch match = pattern.match(v); + return match.hasMatch() ? + QRect(QPoint(match.captured(3).toInt(), match.captured(4).toInt()), + QSize(match.captured(1).toInt(), match.captured(2).toInt())) : + QRect(); +} + +/*! + \class SdkPersistentSettingsReader + + \note This is aQString based fork of Utils::PersistentSettigsReader + + \brief The SdkPersistentSettingsReader class reads a QVariantMap of arbitrary, + nested data structures from an XML file. + + Handles all string-serializable simple types and QVariantList and QVariantMap. Example: + \code + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + + + \endcode + + When parsing the structure, a parse stack of ParseValueStackEntry is used for each + element. ParseValueStackEntry is a variant/union of: + \list + \li simple value + \li map + \li list + \endlist + + You can register string-serialize functions for custom types by registering them in the Qt Meta + type system. Example: + \code + QMetaType::registerConverter(&MyCustomType::toString); + QMetaType::registerConverter(&myCustomTypeFromString); + \endcode + + When entering a value element ( \c / \c , \c ), entry is pushed + accordingly. When leaving the element, the QVariant-value of the entry is taken off the stack + and added to the stack entry below (added to list or inserted into map). The first element + of the stack is the value of the element. + + \sa SdkPersistentSettingsWriter +*/ + +struct Context // Basic context containing element name string constants. +{ + Context() {} + const QString qtCreatorElement = QString("qtcreator"); + const QString dataElement = QString("data"); + const QString variableElement = QString("variable"); + const QString typeAttribute = QString("type"); + const QString valueElement = QString("value"); + const QString valueListElement = QString("valuelist"); + const QString valueMapElement = QString("valuemap"); + const QString keyAttribute = QString("key"); +}; + +struct ParseValueStackEntry +{ + explicit ParseValueStackEntry(QVariant::Type t = QVariant::Invalid, const QString &k = QString()) : type(t), key(k) {} + explicit ParseValueStackEntry(const QVariant &aSimpleValue, const QString &k); + + QVariant value() const; + void addChild(const QString &key, const QVariant &v); + + QVariant::Type type; + QString key; + QVariant simpleValue; + QVariantList listValue; + QVariantMap mapValue; +}; + +ParseValueStackEntry::ParseValueStackEntry(const QVariant &aSimpleValue, const QString &k) : + type(aSimpleValue.type()), key(k), simpleValue(aSimpleValue) +{ + QTC_ASSERT(simpleValue.isValid(), return); +} + +QVariant ParseValueStackEntry::value() const +{ + switch (type) { + case QVariant::Invalid: + return QVariant(); + case QVariant::Map: + return QVariant(mapValue); + case QVariant::List: + return QVariant(listValue); + default: + break; + } + return simpleValue; +} + +void ParseValueStackEntry::addChild(const QString &key, const QVariant &v) +{ + switch (type) { + case QVariant::Map: + mapValue.insert(key, v); + break; + case QVariant::List: + listValue.push_back(v); + break; + default: + qWarning() << "ParseValueStackEntry::Internal error adding " << key << v << " to " + << QVariant::typeToName(type) << value(); + break; + } +} + +class ParseContext : public Context +{ +public: + QVariantMap parse(const QString &file); + +private: + enum Element { QtCreatorElement, DataElement, VariableElement, + SimpleValueElement, ListValueElement, MapValueElement, UnknownElement }; + + Element element(const QStringView &r) const; + static inline bool isValueElement(Element e) + { return e == SimpleValueElement || e == ListValueElement || e == MapValueElement; } + QVariant readSimpleValue(QXmlStreamReader &r, const QXmlStreamAttributes &attributes) const; + + bool handleStartElement(QXmlStreamReader &r); + bool handleEndElement(const QStringView &name); + + static QString formatWarning(const QXmlStreamReader &r, const QString &message); + + QStack m_valueStack; + QVariantMap m_result; + QString m_currentVariableName; +}; + +static QByteArray fileContents(const QString &path) +{ + QFile f(path); + if (!f.exists()) + return {}; + + if (!f.open(QFile::ReadOnly)) + return {}; + + return f.readAll(); +} + +QVariantMap ParseContext::parse(const QString &file) +{ + QXmlStreamReader r(fileContents(file)); + + m_result.clear(); + m_currentVariableName.clear(); + + while (!r.atEnd()) { + switch (r.readNext()) { + case QXmlStreamReader::StartElement: + if (handleStartElement(r)) + return m_result; + break; + case QXmlStreamReader::EndElement: + if (handleEndElement(r.name())) + return m_result; + break; + case QXmlStreamReader::Invalid: + qWarning("Error reading %s:%d: %s", qPrintable(file), + int(r.lineNumber()), qPrintable(r.errorString())); + return QVariantMap(); + default: + break; + } // switch token + } // while (!r.atEnd()) + return m_result; +} + +bool ParseContext::handleStartElement(QXmlStreamReader &r) +{ + const QStringView name = r.name(); + const Element e = element(name); + if (e == VariableElement) { + m_currentVariableName = r.readElementText(); + return false; + } + if (!ParseContext::isValueElement(e)) + return false; + + const QXmlStreamAttributes attributes = r.attributes(); + const QString key = attributes.hasAttribute(keyAttribute) ? + attributes.value(keyAttribute).toString() : QString(); + switch (e) { + case SimpleValueElement: { + // This reads away the end element, so, handle end element right here. + const QVariant v = readSimpleValue(r, attributes); + if (!v.isValid()) { + qWarning() << ParseContext::formatWarning(r, QString::fromLatin1("Failed to read element \"%1\".").arg(name.toString())); + return false; + } + m_valueStack.push_back(ParseValueStackEntry(v, key)); + return handleEndElement(name); + } + case ListValueElement: + m_valueStack.push_back(ParseValueStackEntry(QVariant::List, key)); + break; + case MapValueElement: + m_valueStack.push_back(ParseValueStackEntry(QVariant::Map, key)); + break; + default: + break; + } + return false; +} + +bool ParseContext::handleEndElement(const QStringView &name) +{ + const Element e = element(name); + if (ParseContext::isValueElement(e)) { + QTC_ASSERT(!m_valueStack.isEmpty(), return true); + const ParseValueStackEntry top = m_valueStack.pop(); + if (m_valueStack.isEmpty()) { // Last element? -> Done with that variable. + QTC_ASSERT(!m_currentVariableName.isEmpty(), return true); + m_result.insert(m_currentVariableName, top.value()); + m_currentVariableName.clear(); + return false; + } + m_valueStack.top().addChild(top.key, top.value()); + } + return e == QtCreatorElement; +} + +QString ParseContext::formatWarning(const QXmlStreamReader &r, const QString &message) +{ + QString result = QLatin1String("Warning reading "); + if (const QIODevice *device = r.device()) + if (const auto file = qobject_cast(device)) + result += QDir::toNativeSeparators(file->fileName()) + QLatin1Char(':'); + result += QString::number(r.lineNumber()); + result += QLatin1String(": "); + result += message; + return result; +} + +ParseContext::Element ParseContext::element(const QStringView &r) const +{ + if (r == valueElement) + return SimpleValueElement; + if (r == valueListElement) + return ListValueElement; + if (r == valueMapElement) + return MapValueElement; + if (r == qtCreatorElement) + return QtCreatorElement; + if (r == dataElement) + return DataElement; + if (r == variableElement) + return VariableElement; + return UnknownElement; +} + +QVariant ParseContext::readSimpleValue(QXmlStreamReader &r, const QXmlStreamAttributes &attributes) const +{ + // Simple value + const QStringView type = attributes.value(typeAttribute); + const QString text = r.readElementText(); + if (type == QLatin1String("QChar")) { // Workaround: QTBUG-12345 + QTC_ASSERT(text.size() == 1, return QVariant()); + return QVariant(QChar(text.at(0))); + } + if (type == QLatin1String("QRect")) { + const QRect rectangle = stringToRectangle(text); + return rectangle.isValid() ? QVariant(rectangle) : QVariant(); + } + QVariant value; + value.setValue(text); + value.convert(QMetaType::type(type.toLatin1().constData())); + return value; +} + +// =================================== SdkPersistentSettingsReader + +SdkPersistentSettingsReader::SdkPersistentSettingsReader() = default; + +QVariant SdkPersistentSettingsReader::restoreValue(const QString &variable, const QVariant &defaultValue) const +{ + if (m_valueMap.contains(variable)) + return m_valueMap.value(variable); + return defaultValue; +} + +QVariantMap SdkPersistentSettingsReader::restoreValues() const +{ + return m_valueMap; +} + +bool SdkPersistentSettingsReader::load(const QString &fileName) +{ + m_valueMap.clear(); + + if (QFileInfo(fileName).size() == 0) // skip empty files + return false; + + ParseContext ctx; + m_valueMap = ctx.parse(fileName); + return true; +} + +/*! + \class SdkPersistentSettingsWriter + + \note This is a fork of Utils::PersistentSettingsWriter + + \brief The SdkPersistentSettingsWriter class serializes a QVariantMap of + arbitrary, nested data structures to an XML file. + \sa SdkPersistentSettingsReader +*/ + +static void writeVariantValue(QXmlStreamWriter &w, const Context &ctx, + const QVariant &variant, const QString &key = QString()) +{ + switch (static_cast(variant.type())) { + case static_cast(QVariant::StringList): + case static_cast(QVariant::List): { + w.writeStartElement(ctx.valueListElement); + w.writeAttribute(ctx.typeAttribute, QLatin1String(QVariant::typeToName(QVariant::List))); + if (!key.isEmpty()) + w.writeAttribute(ctx.keyAttribute, key); + const QList list = variant.toList(); + for (const QVariant &var : list) + writeVariantValue(w, ctx, var); + w.writeEndElement(); + break; + } + case static_cast(QVariant::Map): { + w.writeStartElement(ctx.valueMapElement); + w.writeAttribute(ctx.typeAttribute, QLatin1String(QVariant::typeToName(QVariant::Map))); + if (!key.isEmpty()) + w.writeAttribute(ctx.keyAttribute, key); + const QVariantMap varMap = variant.toMap(); + const QVariantMap::const_iterator cend = varMap.constEnd(); + for (QVariantMap::const_iterator i = varMap.constBegin(); i != cend; ++i) + writeVariantValue(w, ctx, i.value(), i.key()); + w.writeEndElement(); + } + break; + case static_cast(QMetaType::QObjectStar): // ignore QObjects! + case static_cast(QMetaType::VoidStar): // ignore void pointers! + break; + default: + w.writeStartElement(ctx.valueElement); + w.writeAttribute(ctx.typeAttribute, QLatin1String(variant.typeName())); + if (!key.isEmpty()) + w.writeAttribute(ctx.keyAttribute, key); + switch (variant.type()) { + case QVariant::Rect: + w.writeCharacters(rectangleToString(variant.toRect())); + break; + default: + w.writeCharacters(variant.toString()); + break; + } + w.writeEndElement(); + break; + } +} + +SdkPersistentSettingsWriter::SdkPersistentSettingsWriter(const QString &fileName, const QString &docType) : + m_fileName(fileName), m_docType(docType) +{ } + +bool SdkPersistentSettingsWriter::save(const QVariantMap &data, QString *errorString) const +{ + if (data == m_savedData) + return true; + return write(data, errorString); +} + +QString SdkPersistentSettingsWriter::fileName() const +{ return m_fileName; } + +//** * @brief Set contents of file (e.g. from data read from it). */ +void SdkPersistentSettingsWriter::setContents(const QVariantMap &data) +{ + m_savedData = data; +} + +bool SdkPersistentSettingsWriter::write(const QVariantMap &data, QString *errorString) const +{ + const QString parentDir = QDir::cleanPath(m_fileName + "/.."); + + const QFileInfo fi(parentDir); + if (!(fi.exists() && fi.isDir() && fi.isWritable())) { + bool res = QDir().mkpath(parentDir); + if (!res) + return false; + } + + SdkFileSaver saver(m_fileName, QIODevice::Text); + if (!saver.hasError()) { + const Context ctx; + QXmlStreamWriter w(saver.file()); + w.setAutoFormatting(true); + w.setAutoFormattingIndent(1); // Historical, used to be QDom. + w.writeStartDocument(); + w.writeDTD(QLatin1String("')); + w.writeComment(QString::fromLatin1(" Written by %1 %2, %3. "). + arg(QCoreApplication::applicationName(), + QCoreApplication::applicationVersion(), + QDateTime::currentDateTime().toString(Qt::ISODate))); + w.writeStartElement(ctx.qtCreatorElement); + const QVariantMap::const_iterator cend = data.constEnd(); + for (QVariantMap::const_iterator it = data.constBegin(); it != cend; ++it) { + w.writeStartElement(ctx.dataElement); + w.writeTextElement(ctx.variableElement, it.key()); + writeVariantValue(w, ctx, it.value()); + w.writeEndElement(); + } + w.writeEndDocument(); + + saver.setResult(&w); + } + bool ok = saver.finalize(); + if (ok) { + m_savedData = data; + } else if (errorString) { + m_savedData.clear(); + *errorString = saver.errorString(); + } + + return ok; +} diff --git a/src/tools/sdktool/sdkpersistentsettings.h b/src/tools/sdktool/sdkpersistentsettings.h new file mode 100644 index 00000000000..691cf0e08a7 --- /dev/null +++ b/src/tools/sdktool/sdkpersistentsettings.h @@ -0,0 +1,37 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +class SdkPersistentSettingsReader +{ +public: + SdkPersistentSettingsReader(); + QVariant restoreValue(const QString &variable, const QVariant &defaultValue = QVariant()) const; + QVariantMap restoreValues() const; + bool load(const QString &fileName); + +private: + QMap m_valueMap; +}; + +class SdkPersistentSettingsWriter +{ +public: + SdkPersistentSettingsWriter(const QString &fileName, const QString &docType); + + bool save(const QVariantMap &data, QString *errorString) const; + + QString fileName() const; + + void setContents(const QVariantMap &data); + +private: + bool write(const QVariantMap &data, QString *errorString) const; + + const QString m_fileName; + const QString m_docType; + mutable QMap m_savedData; +}; diff --git a/src/tools/sdktool/sdktoollib.qbs b/src/tools/sdktool/sdktoollib.qbs index 60823390832..ca40590d8d1 100644 --- a/src/tools/sdktool/sdktoollib.qbs +++ b/src/tools/sdktool/sdktoollib.qbs @@ -85,34 +85,7 @@ QtcLibrary { "rmtoolchainoperation.h", "settings.cpp", "settings.h", + "sdkpersistentsettings.cpp", + "sdkpersistentsettings.h", ] - - Group { - name: "Utils" - prefix: libsDir + "/utils/" - files: [ - "commandline.cpp", "commandline.h", - "devicefileaccess.cpp", "devicefileaccess.h", - "environment.cpp", "environment.h", - "filepath.cpp", "filepath.h", - "fileutils.cpp", "fileutils.h", - "hostosinfo.cpp", "hostosinfo.h", - "macroexpander.cpp", "macroexpander.h", - "namevaluedictionary.cpp", "namevaluedictionary.h", - "namevalueitem.cpp", "namevalueitem.h", - "persistentsettings.cpp", "persistentsettings.h", - "qtcassert.cpp", "qtcassert.h", - "savefile.cpp", "savefile.h", - "stringutils.cpp" - ] - } - Group { - name: "Utils/macOS" - condition: qbs.targetOS.contains("macos") - prefix: libsDir + "/utils/" - files: [ - "fileutils_mac.h", - "fileutils_mac.mm", - ] - } } diff --git a/src/tools/sdktool/settings.cpp b/src/tools/sdktool/settings.cpp index d87df504f6e..53a1af0565f 100644 --- a/src/tools/sdktool/settings.cpp +++ b/src/tools/sdktool/settings.cpp @@ -2,11 +2,11 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "settings.h" -#include "operation.h" #include #include +#include static Settings *m_instance = nullptr; @@ -21,28 +21,28 @@ Settings::Settings() m_instance = this; // autodetect sdk dir: - sdkPath = Utils::FilePath::fromUserInput(QCoreApplication::applicationDirPath()) - .pathAppended(DATA_PATH).cleanPath() - .pathAppended(Core::Constants::IDE_SETTINGSVARIANT_STR) - .pathAppended(Core::Constants::IDE_ID); + sdkPath = QDir::cleanPath(QCoreApplication::applicationDirPath() + + '/' + DATA_PATH + + '/' + Core::Constants::IDE_SETTINGSVARIANT_STR + + '/' + Core::Constants::IDE_ID); } -Utils::FilePath Settings::getPath(const QString &file) +QString Settings::getPath(const QString &file) { - Utils::FilePath result = sdkPath; + QString result = sdkPath; const QString lowerFile = file.toLower(); const QStringList identical = { "android", "cmaketools", "debuggers", "devices", "profiles", "qtversions", "toolchains", "abi" }; if (lowerFile == "cmake") - result = result.pathAppended("cmaketools"); + result += "/cmaketools"; else if (lowerFile == "kits") - result = result.pathAppended("profiles"); + result += "/profiles"; else if (lowerFile == "qtversions") - result = result.pathAppended("qtversion"); + result += "/qtversion"; else if (identical.contains(lowerFile)) - result = result.pathAppended(lowerFile); + result += '/' + lowerFile; else - result = result.pathAppended(file); // handle arbitrary file names not known yet - return result.stringAppended(".xml"); + result += '/' + file; // handle arbitrary file names not known yet + return result += ".xml"; } diff --git a/src/tools/sdktool/settings.h b/src/tools/sdktool/settings.h index b94c2c9ad0c..3b182e1c96f 100644 --- a/src/tools/sdktool/settings.h +++ b/src/tools/sdktool/settings.h @@ -3,7 +3,7 @@ #pragma once -#include +#include class Operation; @@ -13,8 +13,8 @@ public: Settings(); static Settings *instance(); - Utils::FilePath getPath(const QString &file); + QString getPath(const QString &file); - Utils::FilePath sdkPath; + QString sdkPath; Operation *operation = nullptr; }; diff --git a/src/tools/tools.qbs b/src/tools/tools.qbs index cb4230dfde4..48a35448d7e 100644 --- a/src/tools/tools.qbs +++ b/src/tools/tools.qbs @@ -7,6 +7,7 @@ Project { "buildoutputparser/buildoutputparser.qbs", "cplusplustools.qbs", "disclaim/disclaim.qbs", + "process_stub/process_stub.qbs", "processlauncher/processlauncher.qbs", "qml2puppet/qml2puppet.qbs", "qtcdebugger/qtcdebugger.qbs", diff --git a/tests/auto/CMakeLists.txt b/tests/auto/CMakeLists.txt index 98ee3458965..5dcd026a7af 100644 --- a/tests/auto/CMakeLists.txt +++ b/tests/auto/CMakeLists.txt @@ -19,6 +19,8 @@ add_subdirectory(profilewriter) add_subdirectory(qml) add_subdirectory(runextensions) add_subdirectory(sdktool) +add_subdirectory(solutions) +add_subdirectory(texteditor) add_subdirectory(toolchaincache) add_subdirectory(tracing) add_subdirectory(treeviewfind) diff --git a/tests/auto/auto.qbs b/tests/auto/auto.qbs index 2ff98d14097..603b5cd1b15 100644 --- a/tests/auto/auto.qbs +++ b/tests/auto/auto.qbs @@ -22,6 +22,8 @@ Project { "qml/qml.qbs", "runextensions/runextensions.qbs", "sdktool/sdktool.qbs", + "solutions/solutions.qbs", + "texteditor/texteditor.qbs", "toolchaincache/toolchaincache.qbs", "tracing/tracing.qbs", "treeviewfind/treeviewfind.qbs", diff --git a/tests/auto/cplusplus/cxx11/tst_cxx11.cpp b/tests/auto/cplusplus/cxx11/tst_cxx11.cpp index 0dc6484ff44..d27ba601467 100644 --- a/tests/auto/cplusplus/cxx11/tst_cxx11.cpp +++ b/tests/auto/cplusplus/cxx11/tst_cxx11.cpp @@ -145,6 +145,12 @@ private Q_SLOTS: void lambdaType_data(); void lambdaType(); + + void concepts(); + void requiresClause(); + void coroutines(); + void genericLambdas(); + void ifStatementWithInitialization(); }; @@ -293,5 +299,279 @@ void tst_cxx11::lambdaType() QCOMPARE(oo.prettyType(function->type()), expectedType); } +void tst_cxx11::concepts() +{ + LanguageFeatures features; + features.cxxEnabled = true; + features.cxx11Enabled = features.cxx14Enabled = features.cxx20Enabled = true; + + const QString source = R"( +template concept IsPointer = requires(T p) { *p; }; +template void* func(T p) { return p; } +void *func2(IsPointer auto p) +{ + return p; +} +)"; + QByteArray errors; + Document::Ptr doc = Document::create(FilePath::fromPathPart(u"testFile")); + processDocument(doc, source.toUtf8(), features, &errors); + const bool hasErrors = !errors.isEmpty(); + if (hasErrors) + qDebug().noquote() << errors; + QVERIFY(!hasErrors); +} + +void tst_cxx11::requiresClause() +{ + LanguageFeatures features; + features.cxxEnabled = true; + features.cxx11Enabled = features.cxx14Enabled = features.cxx20Enabled = true; + + const QString source = R"( +template constexpr bool is_meowable = true; +template constexpr bool is_purrable() { return true; } +template void f(T) requires is_meowable; +template requires is_meowable void g(T) ; +template void h(T) requires (is_purrable()); +)"; + QByteArray errors; + Document::Ptr doc = Document::create(FilePath::fromPathPart(u"testFile")); + processDocument(doc, source.toUtf8(), features, &errors); + const bool hasErrors = !errors.isEmpty(); + if (hasErrors) + qDebug().noquote() << errors; + QVERIFY(!hasErrors); +} + +void tst_cxx11::coroutines() +{ + LanguageFeatures features; + features.cxxEnabled = true; + features.cxx11Enabled = features.cxx14Enabled = features.cxx20Enabled = true; + + const QString source = R"( +struct promise; +struct coroutine : std::coroutine_handle +{ + using promise_type = struct promise; +}; +struct promise +{ + coroutine get_return_object() { return {coroutine::from_promise(*this)}; } + std::suspend_always initial_suspend() noexcept { return {}; } + std::suspend_always final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception() {} +}; +struct S +{ + int i; + coroutine f() + { + std::cout << i; + co_return; + } +}; +void good() +{ + coroutine h = [](int i) -> coroutine + { + std::cout << i; + co_return; + }(0); + h.resume(); + h.destroy(); +} +auto switch_to_new_thread(std::jthread& out) +{ + struct awaitable + { + std::jthread* p_out; + bool await_ready() { return false; } + void await_suspend(std::coroutine_handle<> h) + { + std::jthread& out = *p_out; + if (out.joinable()) + throw std::runtime_error("Output jthread parameter not empty"); + out = std::jthread([h] { h.resume(); }); + std::cout << "New thread ID: " << out.get_id() << '\n'; // this is OK + } + void await_resume() {} + }; + return awaitable{&out}; +} +struct task +{ + struct promise_type + { + task get_return_object() { return {}; } + std::suspend_never initial_suspend() { return {}; } + std::suspend_never final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception() {} + }; +}; +task resuming_on_new_thread(std::jthread& out) +{ + std::cout << "Coroutine started on thread: " << std::this_thread::get_id() << '\n'; + co_await switch_to_new_thread(out); + std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n'; +} +void run() +{ + std::jthread out; + resuming_on_new_thread(out); +} +template +struct Generator +{ + struct promise_type; + using handle_type = std::coroutine_handle; + struct promise_type // required + { + T value_; + std::exception_ptr exception_; + + Generator get_return_object() + { + return Generator(handle_type::from_promise(*this)); + } + std::suspend_always initial_suspend() { return {}; } + std::suspend_always final_suspend() noexcept { return {}; } + void unhandled_exception() { exception_ = std::current_exception(); } + template From> + std::suspend_always yield_value(From&& from) + { + value_ = std::forward(from); + return {}; + } + void return_void() { } + }; + handle_type h_; + Generator(handle_type h) : h_(h) {} + ~Generator() { h_.destroy(); } + explicit operator bool() + { + fill(); + return !h_.done(); + } + T operator()() + { + fill(); + full_ = false; + return std::move(h_.promise().value_); + } +private: + bool full_ = false; + void fill() + { + if (!full_) + { + h_(); + if (h_.promise().exception_) + std::rethrow_exception(h_.promise().exception_); + full_ = true; + } + } +}; +Generator +fibonacci_sequence(unsigned n) +{ + if (n == 0) + co_return; + if (n > 94) + throw std::runtime_error("Too big Fibonacci sequence. Elements would overflow."); + co_yield 0; + if (n == 1) + co_return; + co_yield 1; + if (n == 2) + co_return; + std::uint64_t a = 0; + std::uint64_t b = 1; + for (unsigned i = 2; i < n; i++) + { + std::uint64_t s = a + b; + co_yield s; + a = b; + b = s; + } +} +int main() +{ + try + { + auto gen = fibonacci_sequence(10); // max 94 before uint64_t overflows + for (int j = 0; gen; j++) + std::cout << "fib(" << j << ")=" << gen() << '\n'; + } catch (const std::exception& ex) { + std::cerr << "Exception: " << ex.what() << '\n'; + } + catch (...) + { + std::cerr << "Unknown exception.\n"; + } +} +)"; + QByteArray errors; + Document::Ptr doc = Document::create(FilePath::fromPathPart(u"testFile")); + processDocument(doc, source.toUtf8(), features, &errors); + const bool hasErrors = !errors.isEmpty(); + if (hasErrors) + qDebug().noquote() << errors; + QVERIFY(!hasErrors); +} + +void tst_cxx11::genericLambdas() +{ + LanguageFeatures features; + features.cxxEnabled = true; + features.cxx11Enabled = features.cxx14Enabled = features.cxx20Enabled = true; + + const QString source = R"( +template concept C1 = true; +template concept C2 = true; +template concept C3 = true; +int main() +{ + auto f = [](T a, auto&& b) { return a < b; }; + auto g = [](Ts&&... ts) { return foo(std::forward(ts)...); }; + auto h = [] requires C2 + (T1 a1, T1 b1, T2 a2, auto a3, auto a4) requires C3 { + }; +} +)"; + QByteArray errors; + Document::Ptr doc = Document::create(FilePath::fromPathPart(u"testFile")); + processDocument(doc, source.toUtf8(), features, &errors); + const bool hasErrors = !errors.isEmpty(); + if (hasErrors) + qDebug().noquote() << errors; + QVERIFY(!hasErrors); +} + +void tst_cxx11::ifStatementWithInitialization() +{ + LanguageFeatures features; + features.cxxEnabled = true; + features.cxx11Enabled = features.cxx14Enabled = features.cxx17Enabled = true; + + const QString source = R"( +int main() +{ + if (bool b = true; b) + b = false; +} +)"; + QByteArray errors; + Document::Ptr doc = Document::create(FilePath::fromPathPart(u"testFile")); + processDocument(doc, source.toUtf8(), features, &errors); + const bool hasErrors = !errors.isEmpty(); + if (hasErrors) + qDebug().noquote() << errors; + QVERIFY(!hasErrors); +} + QTEST_APPLESS_MAIN(tst_cxx11) #include "tst_cxx11.moc" diff --git a/tests/auto/cplusplus/lexer/tst_lexer.cpp b/tests/auto/cplusplus/lexer/tst_lexer.cpp index 641b62f248c..09a474504ee 100644 --- a/tests/auto/cplusplus/lexer/tst_lexer.cpp +++ b/tests/auto/cplusplus/lexer/tst_lexer.cpp @@ -43,6 +43,7 @@ public: private slots: void basic(); void basic_data(); + void cxx20(); void incremental(); void incremental_data(); void literals(); @@ -250,6 +251,38 @@ void tst_SimpleLexer::basic_data() QTest::newRow(source) << source << expectedTokenKindList; } +void tst_SimpleLexer::cxx20() +{ + LanguageFeatures features; + features.cxxEnabled = features.cxx11Enabled = features.cxx14Enabled + = features.cxx20Enabled = true; + const QString source = R"( +template concept IsPointer = requires(T p) { *p; }; +SomeType coroutine() +{ + constinit const char8_t = 'c'; + if consteval {} else {} + co_await std::suspend_always{}; + co_yield 1; + co_return; +} +)"; + const TokenKindList expectedTokens = { + T_TEMPLATE, T_LESS, T_TYPENAME, T_IDENTIFIER, T_GREATER, T_CONCEPT, T_IDENTIFIER, T_EQUAL, + T_REQUIRES, T_LPAREN, T_IDENTIFIER, T_IDENTIFIER, T_RPAREN, T_LBRACE, T_STAR, T_IDENTIFIER, + T_SEMICOLON, T_RBRACE, T_SEMICOLON, + T_IDENTIFIER, T_IDENTIFIER, T_LPAREN, T_RPAREN, + T_LBRACE, + T_CONSTINIT, T_CONST, T_CHAR8_T, T_EQUAL, T_CHAR_LITERAL, T_SEMICOLON, + T_IF, T_CONSTEVAL, T_LBRACE, T_RBRACE, T_ELSE, T_LBRACE, T_RBRACE, + T_CO_AWAIT, T_IDENTIFIER, T_COLON_COLON, T_IDENTIFIER, T_LBRACE, T_RBRACE, T_SEMICOLON, + T_CO_YIELD, T_NUMERIC_LITERAL, T_SEMICOLON, + T_CO_RETURN, T_SEMICOLON, + T_RBRACE + }; + run(source.toUtf8(), toTokens(expectedTokens), false, CompareKind, false, features); +} + void tst_SimpleLexer::literals() { QFETCH(QByteArray, source); diff --git a/tests/auto/debugger/CMakeLists.txt b/tests/auto/debugger/CMakeLists.txt index 5ed44a94d2f..4abe98c05e8 100644 --- a/tests/auto/debugger/CMakeLists.txt +++ b/tests/auto/debugger/CMakeLists.txt @@ -27,7 +27,7 @@ if (NOT QT_CREATOR_API_DEFINED) set(WITH_TESTS ON) - find_package(Qt5 + find_package(Qt6 COMPONENTS Gui Core Core5Compat Widgets Network Qml Concurrent Test Xml MODULE) find_package(Threads) diff --git a/tests/auto/debugger/tst_dumpers.cpp b/tests/auto/debugger/tst_dumpers.cpp index 3f2f66116f6..a1df2ae70c5 100644 --- a/tests/auto/debugger/tst_dumpers.cpp +++ b/tests/auto/debugger/tst_dumpers.cpp @@ -1736,7 +1736,8 @@ void tst_Dumpers::dumper() expandedq.append(','); } expanded += iname; - expandedq += '\'' + iname + '\''; + expandedq += '\'' + iname + "':"; + expandedq += data.bigArray ? "10000" : "100"; } QString exe = m_debuggerBinary; @@ -1769,7 +1770,7 @@ void tst_Dumpers::dumper() "'token':2,'fancy':1,'forcens':1," "'autoderef':1,'dyntype':1,'passexceptions':1," "'testing':1,'qobjectnames':1," - "'expanded':[" + expandedq + "]})\n"; + "'expanded':{" + expandedq + "}})\n"; cmds += "quit\n"; @@ -1792,7 +1793,7 @@ void tst_Dumpers::dumper() "'token':2,'fancy':1,'forcens':1," "'autoderef':1,'dyntype':1,'passexceptions':0," "'testing':1,'qobjectnames':1," - "'expanded':[" + expandedq + "]})\n" + "'expanded':{" + expandedq + "}})\n" "q\n"; } else if (m_debuggerEngine == LldbEngine) { QFile fullLldb(t->buildPath + "/lldbcommand.txt"); @@ -1808,7 +1809,7 @@ void tst_Dumpers::dumper() "'fancy':1,'forcens':1," "'autoderef':1,'dyntype':1,'passexceptions':1," "'testing':1,'qobjectnames':1," - "'expanded':[" + expandedq + "]})\n" + "'expanded':{" + expandedq + "}})\n" "quit\n"; fullLldb.write(cmds.toUtf8()); @@ -5314,6 +5315,7 @@ void tst_Dumpers::dumper_data() "&v0, &v1, &v2, &v3, &v4, &v5, &b0, &b1, &b2, &b3") + Cxx11Profile() + + BigArrayProfile() + Check("v0", "<0 items>", "std::valarray") + Check("v1", "<3 items>", "std::valarray") diff --git a/tests/auto/environment/tst_environment.cpp b/tests/auto/environment/tst_environment.cpp index 994027308db..1b870ddd6da 100644 --- a/tests/auto/environment/tst_environment.cpp +++ b/tests/auto/environment/tst_environment.cpp @@ -40,6 +40,9 @@ private slots: void incrementalChanges(); + void pathChanges_data(); + void pathChanges(); + void find_data(); void find(); @@ -270,8 +273,9 @@ void tst_Environment::incrementalChanges() newEnv.modify(changes); QVERIFY(!newEnv.hasKey("VAR1")); QCOMPARE(newEnv.value("VAR2"), QString()); - QCOMPARE(newEnv.constFind("VAR2")->first, "VALUE2"); - QVERIFY(!newEnv.isEnabled(newEnv.constFind("VAR2"))); + Environment::FindResult res = newEnv.find("VAR2"); + QCOMPARE(res->value, "VALUE2"); + QVERIFY(!res->enabled); const QChar sep = HostOsInfo::pathListSeparator(); QCOMPARE(newEnv.value("PATH"), QString("/tmp").append(sep).append("/usr/bin").append(sep).append("/usr/local/bin")); @@ -295,6 +299,82 @@ void tst_Environment::incrementalChanges() reverseDiff); } +void tst_Environment::pathChanges_data() +{ + const Environment origEnvLinux({"PATH=/bin:/usr/bin", "VAR=VALUE"}, OsTypeLinux); + const Environment origEnvWin({"PATH=C:\\Windows\\System32;D:\\gnu\\bin", "VAR=VALUE"}, OsTypeWindows); + + QTest::addColumn("environment"); + QTest::addColumn("prepend"); // if false => append + QTest::addColumn("variable"); + QTest::addColumn("value"); + QTest::addColumn("expected"); + + QTest::newRow("appendOrSetPath existingLeading Unix") + << origEnvLinux << false << "PATH" << "/bin" + << Environment({"PATH=/bin:/usr/bin:/bin", "VAR=VALUE"}, OsTypeLinux); + QTest::newRow("appendOrSetPath existingLeading Win") + << origEnvWin << false << "PATH" << "C:\\Windows\\System32" + << Environment({"PATH=C:\\Windows\\System32;D:\\gnu\\bin;C:\\Windows\\System32", + "VAR=VALUE"}, OsTypeWindows); + QTest::newRow("appendOrSetPath existingTrailing Unix") + << origEnvLinux << false << "PATH" << "/usr/bin" + << Environment({"PATH=/bin:/usr/bin", "VAR=VALUE"}, OsTypeLinux); + QTest::newRow("appendOrSetPath existingTrailing Win") + << origEnvWin << false << "PATH" << "D:\\gnu\\bin" + << Environment({"PATH=C:\\Windows\\System32;D:\\gnu\\bin", + "VAR=VALUE"}, OsTypeWindows); + QTest::newRow("prependOrSetPath existingLeading Unix") + << origEnvLinux << true << "PATH" << "/bin" + << Environment({"PATH=/bin:/usr/bin", "VAR=VALUE"}, OsTypeLinux); + QTest::newRow("prependOrSetPath existingLeading Win") + << origEnvWin << true << "PATH" << "C:\\Windows\\System32" + << Environment({"PATH=C:\\Windows\\System32;D:\\gnu\\bin", + "VAR=VALUE"}, OsTypeWindows); + QTest::newRow("prependOrSetPath existingTrailing Unix") + << origEnvLinux << true << "PATH" << "/usr/bin" + << Environment({"PATH=/usr/bin:/bin:/usr/bin", "VAR=VALUE"}, OsTypeLinux); + QTest::newRow("prependOrSetPath existingTrailing Win") + << origEnvWin << true << "PATH" << "D:\\gnu\\bin" + << Environment({"PATH=D:\\gnu\\bin;C:\\Windows\\System32;D:\\gnu\\bin", + "VAR=VALUE"}, OsTypeWindows); + + QTest::newRow("appendOrSetPath non-existing Unix") + << origEnvLinux << false << "PATH" << "/opt" + << Environment({"PATH=/bin:/usr/bin:/opt", "VAR=VALUE"}, OsTypeLinux); + QTest::newRow("appendOrSetPath non-existing Win") + << origEnvWin << false << "PATH" << "C:\\Windows" + << Environment({"PATH=C:\\Windows\\System32;D:\\gnu\\bin;C:\\Windows", + "VAR=VALUE"}, OsTypeWindows); + QTest::newRow("prependOrSetPath non-existing half-matching Unix") + << origEnvLinux << true << "PATH" << "/bi" + << Environment({"PATH=/bi:/bin:/usr/bin", "VAR=VALUE"}, OsTypeLinux); + QTest::newRow("prependOrSetPath non-existing half-matching Win") + << origEnvWin << true << "PATH" << "C:\\Windows" + << Environment({"PATH=C:\\Windows;C:\\Windows\\System32;D:\\gnu\\bin", + "VAR=VALUE"}, OsTypeWindows); +} + +void tst_Environment::pathChanges() +{ + QFETCH(Environment, environment); + QFETCH(bool, prepend); + QFETCH(QString, variable); + QFETCH(QString, value); + QFETCH(Environment, expected); + + const QString sep = OsSpecificAspects::pathListSeparator(environment.osType()); + + if (prepend) + environment.prependOrSet(variable, value, sep); + else + environment.appendOrSet(variable, value, sep); + + qDebug() << "Actual :" << environment.toStringList(); + qDebug() << "Expected:" << expected.toStringList(); + QCOMPARE(environment, expected); +} + void tst_Environment::find_data() { QTest::addColumn("osType"); @@ -317,13 +397,12 @@ void tst_Environment::find() Environment env(QStringList({"Foo=bar", "Hi=HO"}), osType); - auto end = env.constEnd(); - auto it = env.constFind(variable); + Environment::FindResult res = env.find(variable); - QCOMPARE((end != it), contains); + QCOMPARE(bool(res), contains); if (contains) - QCOMPARE(env.value(it), QString("bar")); + QCOMPARE(res->value, QString("bar")); } diff --git a/tests/auto/examples/tst_examples.cpp b/tests/auto/examples/tst_examples.cpp index 5a18f1e4498..d6639663adf 100644 --- a/tests/auto/examples/tst_examples.cpp +++ b/tests/auto/examples/tst_examples.cpp @@ -6,6 +6,7 @@ #include +using namespace Core; using namespace Utils; using namespace QtSupport::Internal; @@ -88,6 +89,7 @@ void tst_Examples::parsing_data() QTest::addColumn("videoLength"); QTest::addColumn("platforms"); QTest::addColumn("metaData"); + QTest::addColumn("categories"); QTest::addRow("example") << QByteArray(R"raw( @@ -102,6 +104,8 @@ void tst_Examples::parsing_data() widgets/widgets/analogclock/analogclock.cpp Graphics + Graphics + Foobar widgets @@ -111,22 +115,38 @@ void tst_Examples::parsing_data() << "The Analog Clock example shows how to draw the contents of a custom widget." << "qthelp://org.qt-project.qtwidgets.660/qtwidgets/images/analogclock-example.png" << QStringList{"ios", "widgets"} - << FilePath::fromUserInput("manifest/widgets/widgets/analogclock/CMakeLists.txt") + << FilePath::fromUserInput("examples/widgets/widgets/analogclock/CMakeLists.txt") << "qthelp://org.qt-project.qtwidgets.660/qtwidgets/" "qtwidgets-widgets-analogclock-example.html" - << FilePaths{FilePath::fromUserInput("manifest/widgets/widgets/analogclock/main.cpp"), - FilePath::fromUserInput("manifest/widgets/widgets/analogclock/analogclock.h"), + << FilePaths{FilePath::fromUserInput("examples/widgets/widgets/analogclock/main.cpp"), + FilePath::fromUserInput("examples/widgets/widgets/analogclock/analogclock.h"), FilePath::fromUserInput( - "manifest/widgets/widgets/analogclock/analogclock.cpp")} - << FilePath::fromUserInput("manifest/widgets/widgets/analogclock/analogclock.cpp") + "examples/widgets/widgets/analogclock/analogclock.cpp")} + << FilePath::fromUserInput("examples/widgets/widgets/analogclock/analogclock.cpp") << FilePaths() << Example << true << false << false << "" - << "" << QStringList() << MetaData({{"category", {"Graphics"}}, {"tags", {"widgets"}}}); + << "" << QStringList() + << MetaData({{"category", {"Graphics", "Graphics", "Foobar"}}, {"tags", {"widgets"}}}) + << QStringList{"Foobar", "Graphics"}; + + QTest::addRow("no category, highlighted") + << QByteArray(R"raw( + + + + + )raw") << /*isExamples=*/true + << "No Category, highlighted" << QString() << QString() << QStringList() + << FilePath("examples") << QString() << FilePaths() << FilePath() << FilePaths() << Example + << /*hasSourceCode=*/false << false << /*isHighlighted=*/true << "" + << "" << QStringList() << MetaData() << QStringList{"Featured"}; } void tst_Examples::parsing() { QFETCH(QByteArray, data); QFETCH(bool, isExamples); + QFETCH(QStringList, categories); const ExampleItem expected = fetchItem(); const expected_str> result = parseExamples(data, @@ -154,7 +174,17 @@ void tst_Examples::parsing() QCOMPARE(item.videoLength, expected.videoLength); QCOMPARE(item.platforms, expected.platforms); QCOMPARE(item.metaData, expected.metaData); - qDeleteAll(*result); + + const QList>> resultCategories = getCategories(*result, + true); + QCOMPARE(resultCategories.size(), categories.size()); + for (int i = 0; i < resultCategories.size(); ++i) { + QCOMPARE(resultCategories.at(i).first.name, categories.at(i)); + QCOMPARE(resultCategories.at(i).second.size(), 1); + } + + for (const auto &category : resultCategories) + qDeleteAll(category.second); } QTEST_APPLESS_MAIN(tst_Examples) diff --git a/tests/auto/extensionsystem/pluginmanager/tst_pluginmanager.cpp b/tests/auto/extensionsystem/pluginmanager/tst_pluginmanager.cpp index 96657471459..e57e71c533e 100644 --- a/tests/auto/extensionsystem/pluginmanager/tst_pluginmanager.cpp +++ b/tests/auto/extensionsystem/pluginmanager/tst_pluginmanager.cpp @@ -57,9 +57,9 @@ void tst_PluginManager::init() m_pm = new PluginManager; PluginManager::setSettings(new Utils::QtcSettings); PluginManager::setPluginIID(QLatin1String("plugin")); - m_objectAdded = new QSignalSpy(m_pm, SIGNAL(objectAdded(QObject*))); - m_aboutToRemoveObject = new QSignalSpy(m_pm, SIGNAL(aboutToRemoveObject(QObject*))); - m_pluginsChanged = new QSignalSpy(m_pm, SIGNAL(pluginsChanged())); + m_objectAdded = new QSignalSpy(m_pm, &PluginManager::objectAdded); + m_aboutToRemoveObject = new QSignalSpy(m_pm, &PluginManager::aboutToRemoveObject); + m_pluginsChanged = new QSignalSpy(m_pm, &PluginManager::pluginsChanged); } void tst_PluginManager::cleanup() diff --git a/tests/auto/filesearch/tst_filesearch.cpp b/tests/auto/filesearch/tst_filesearch.cpp index 51f7d5a8bfe..95bec53e099 100644 --- a/tests/auto/filesearch/tst_filesearch.cpp +++ b/tests/auto/filesearch/tst_filesearch.cpp @@ -22,68 +22,85 @@ private slots: void caseSensitive(); void caseInSensitive(); void matchCaseReplacement(); + +private: + const FilePath m_filePath = FilePath::fromString(":/tst_filesearch/testfile.txt"); }; -namespace { - const char * const FILENAME = ":/tst_filesearch/testfile.txt"; +SearchResultItem searchResult(const FilePath &fileName, const QString &matchingLine, + int lineNumber, int matchStart, int matchLength, + const QStringList ®expCapturedTexts = {}) +{ + SearchResultItem result; + result.setFilePath(fileName); + result.setLineText(matchingLine); + result.setMainRange(lineNumber, matchStart, matchLength); + result.setUserData(regexpCapturedTexts); + result.setUseTextEditorFont(true); + return result; +} - void test_helper(const Utils::FileSearchResultList &expectedResults, - const QString &term, - QTextDocument::FindFlags flags, tst_FileSearch::RegExpFlag regexp = tst_FileSearch::NoRegExp) - { - Utils::FileIterator *it = new Utils::FileListIterator(FilePaths{FilePath::fromString( - FILENAME)}, - QList() - << QTextCodec::codecForLocale()); - QFutureWatcher watcher; - QSignalSpy ready(&watcher, SIGNAL(resultsReadyAt(int,int))); - if (regexp == tst_FileSearch::NoRegExp) - watcher.setFuture(Utils::findInFiles(term, it, flags)); - else - watcher.setFuture(Utils::findInFilesRegExp(term, it, flags)); - watcher.future().waitForFinished(); - QTest::qWait(100); // process events - QCOMPARE(ready.count(), 1); - Utils::FileSearchResultList results = watcher.resultAt(0); - QCOMPARE(results.count(), expectedResults.count()); - for (int i = 0; i < expectedResults.size(); ++i) { - QCOMPARE(results.at(i), expectedResults.at(i)); - } - } +void test_helper(const FilePath &filePath, const SearchResultItems &expectedResults, + const QString &term, QTextDocument::FindFlags flags = {}, + tst_FileSearch::RegExpFlag regexp = tst_FileSearch::NoRegExp) +{ + FileIterator *it = new FileListIterator({filePath}, {QTextCodec::codecForLocale()}); + QFutureWatcher watcher; + QSignalSpy ready(&watcher, &QFutureWatcherBase::resultsReadyAt); + if (regexp == tst_FileSearch::NoRegExp) + watcher.setFuture(Utils::findInFiles(term, it, flags)); + else + watcher.setFuture(Utils::findInFilesRegExp(term, it, flags)); + watcher.future().waitForFinished(); + QTest::qWait(100); // process events + QCOMPARE(ready.count(), 1); + SearchResultItems results = watcher.resultAt(0); + QCOMPARE(results.count(), expectedResults.count()); + for (int i = 0; i < expectedResults.size(); ++i) + QCOMPARE(results.at(i), expectedResults.at(i)); } void tst_FileSearch::multipleResults() { - Utils::FileSearchResultList expectedResults; - expectedResults << FileSearchResult(FilePath::fromString(FILENAME), 2, QLatin1String("search to find multiple find results"), 10, 4, QStringList()); - expectedResults << FileSearchResult(FilePath::fromString(FILENAME), 2, QLatin1String("search to find multiple find results"), 24, 4, QStringList()); - expectedResults << FileSearchResult(FilePath::fromString(FILENAME), 4, QLatin1String("here you find another result"), 9, 4, QStringList()); - test_helper(expectedResults, QLatin1String("find"), QTextDocument::FindFlags()); + SearchResultItems expectedResults; + expectedResults << searchResult(m_filePath, "search to find multiple find results", 2, 10, 4); + expectedResults << searchResult(m_filePath, "search to find multiple find results", 2, 24, 4); + expectedResults << searchResult(m_filePath, "here you find another result", 4, 9, 4); + test_helper(m_filePath, expectedResults, "find"); expectedResults.clear(); - expectedResults << FileSearchResult(FilePath::fromString(FILENAME), 5, QLatin1String("aaaaaaaa this line has 2 results for four a in a row"), 0, 4, QStringList()); - expectedResults << FileSearchResult(FilePath::fromString(FILENAME), 5, QLatin1String("aaaaaaaa this line has 2 results for four a in a row"), 4, 4, QStringList()); - test_helper(expectedResults, QLatin1String("aaaa"), QTextDocument::FindFlags()); + expectedResults << searchResult(m_filePath, + "aaaaaaaa this line has 2 results for four a in a row", + 5, 0, 4); + expectedResults << searchResult(m_filePath, + "aaaaaaaa this line has 2 results for four a in a row", + 5, 4, 4); + test_helper(m_filePath, expectedResults, "aaaa"); expectedResults.clear(); - expectedResults << FileSearchResult(FilePath::fromString(FILENAME), 5, QLatin1String("aaaaaaaa this line has 2 results for four a in a row"), 0, 4, QStringList() << QLatin1String("aaaa")); - expectedResults << FileSearchResult(FilePath::fromString(FILENAME), 5, QLatin1String("aaaaaaaa this line has 2 results for four a in a row"), 4, 4, QStringList() << QLatin1String("aaaa")); - test_helper(expectedResults, QLatin1String("aaaa"), QTextDocument::FindFlags(), RegExp); + expectedResults << searchResult(m_filePath, + "aaaaaaaa this line has 2 results for four a in a row", + 5, 0, 4, {"aaaa"}); + expectedResults << searchResult(m_filePath, + "aaaaaaaa this line has 2 results for four a in a row", + 5, 4, 4, {"aaaa"}); + test_helper(m_filePath, expectedResults, "aaaa", {}, RegExp); } void tst_FileSearch::caseSensitive() { - Utils::FileSearchResultList expectedResults; - expectedResults << FileSearchResult(FilePath::fromString(FILENAME), 3, QLatin1String("search CaseSensitively for casesensitive"), 7, 13, QStringList()); - test_helper(expectedResults, QLatin1String("CaseSensitive"), QTextDocument::FindCaseSensitively); + SearchResultItems expectedResults; + expectedResults << searchResult(m_filePath, "search CaseSensitively for casesensitive", + 3, 7, 13); + test_helper(m_filePath, expectedResults, "CaseSensitive", QTextDocument::FindCaseSensitively); } void tst_FileSearch::caseInSensitive() { - Utils::FileSearchResultList expectedResults; - expectedResults << FileSearchResult(FilePath::fromString(FILENAME), 3, QLatin1String("search CaseSensitively for casesensitive"), 7, 13, QStringList()); - expectedResults << FileSearchResult(FilePath::fromString(FILENAME), 3, QLatin1String("search CaseSensitively for casesensitive"), 27, 13, QStringList()); - test_helper(expectedResults, QLatin1String("CaseSensitive"), QTextDocument::FindFlags()); + SearchResultItems expectedResults; + expectedResults << searchResult(m_filePath, "search CaseSensitively for casesensitive", 3, 7, 13); + expectedResults << searchResult(m_filePath, "search CaseSensitively for casesensitive", 3, 27, 13); + test_helper(m_filePath, expectedResults, "CaseSensitive"); } void tst_FileSearch::matchCaseReplacement() diff --git a/tests/auto/qml/codemodel/check/tst_check.cpp b/tests/auto/qml/codemodel/check/tst_check.cpp index 46542b8bf8f..56ccdfd2a37 100644 --- a/tests/auto/qml/codemodel/check/tst_check.cpp +++ b/tests/auto/qml/codemodel/check/tst_check.cpp @@ -72,12 +72,11 @@ void tst_Check::initTestCase() new ExtensionSystem::PluginManager; ModelManagerInterface *modelManager = ModelManagerInterface::instance(); - QFutureInterface result; PathsAndLanguages lPaths; QStringList paths(QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath)); for (auto p: paths) lPaths.maybeInsert(Utils::FilePath::fromString(p), Dialect::Qml); - ModelManagerInterface::importScan(result, ModelManagerInterface::workingCopy(), lPaths, + ModelManagerInterface::importScan(ModelManagerInterface::workingCopy(), lPaths, modelManager, false); modelManager->test_joinAllThreads(); } diff --git a/tests/auto/qml/codemodel/dependencies/tst_dependencies.cpp b/tests/auto/qml/codemodel/dependencies/tst_dependencies.cpp index 1a09449c114..e550383724f 100644 --- a/tests/auto/qml/codemodel/dependencies/tst_dependencies.cpp +++ b/tests/auto/qml/codemodel/dependencies/tst_dependencies.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include #include @@ -122,13 +121,12 @@ void tst_Dependencies::test() ModelManagerInterface *modelManager = ModelManagerInterface::instance(); - QFutureInterface result; PathsAndLanguages lPaths; QStringList paths(m_basePaths); paths << m_path; for (auto p: paths) lPaths.maybeInsert(Utils::FilePath::fromString(p), Dialect::Qml); - ModelManagerInterface::importScan(result, ModelManagerInterface::workingCopy(), lPaths, + ModelManagerInterface::importScan(ModelManagerInterface::workingCopy(), lPaths, ModelManagerInterface::instance(), false); ModelManagerInterface::instance()->test_joinAllThreads(); TestData data = testData(filename); diff --git a/tests/auto/qml/codemodel/ecmascript7/tst_ecmascript7.cpp b/tests/auto/qml/codemodel/ecmascript7/tst_ecmascript7.cpp index 9ca768cbeee..003630ab0bf 100644 --- a/tests/auto/qml/codemodel/ecmascript7/tst_ecmascript7.cpp +++ b/tests/auto/qml/codemodel/ecmascript7/tst_ecmascript7.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include #include @@ -149,12 +148,11 @@ void tst_Ecmascript::test() ModelManagerInterface *modelManager = ModelManagerInterface::instance(); - QFutureInterface result; PathsAndLanguages lPaths; QStringList paths(m_basePaths); for (auto p: paths) lPaths.maybeInsert(Utils::FilePath::fromString(p), Dialect::Qml); - ModelManagerInterface::importScan(result, ModelManagerInterface::workingCopy(), lPaths, + ModelManagerInterface::importScan(ModelManagerInterface::workingCopy(), lPaths, ModelManagerInterface::instance(), false); TestData data = testData(filename); diff --git a/tests/auto/qml/codemodel/importscheck/tst_importscheck.cpp b/tests/auto/qml/codemodel/importscheck/tst_importscheck.cpp index 5cad7930e8f..e47c77db482 100644 --- a/tests/auto/qml/codemodel/importscheck/tst_importscheck.cpp +++ b/tests/auto/qml/codemodel/importscheck/tst_importscheck.cpp @@ -51,10 +51,9 @@ private: void scanDirectory(const QString &dir) { auto dirPath = Utils::FilePath::fromString(dir); - QFutureInterface result; PathsAndLanguages paths; paths.maybeInsert(dirPath, Dialect::Qml); - ModelManagerInterface::importScan(result, ModelManagerInterface::workingCopy(), paths, + ModelManagerInterface::importScan(ModelManagerInterface::workingCopy(), paths, ModelManagerInterface::instance(), false); ModelManagerInterface::instance()->test_joinAllThreads(); ViewerContext vCtx; @@ -170,11 +169,10 @@ void tst_ImportCheck::test() const auto pathPaths = Utils::transform(paths, [](const QString &s) { return Utils::FilePath::fromString(s); }); - QFutureInterface result; PathsAndLanguages lPaths; for (const Utils::FilePath &path : pathPaths) lPaths.maybeInsert(path, Dialect::Qml); - ModelManagerInterface::importScan(result, ModelManagerInterface::workingCopy(), lPaths, + ModelManagerInterface::importScan(ModelManagerInterface::workingCopy(), lPaths, ModelManagerInterface::instance(), false); ModelManagerInterface::instance()->test_joinAllThreads(); ViewerContext vCtx; diff --git a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp index 5702fd3846e..12490dd1afe 100644 --- a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp +++ b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp @@ -242,12 +242,10 @@ void tst_TestCore::initTestCase() QStringList basePaths; basePaths.append(QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath)); - - QFutureInterface result; QmlJS::PathsAndLanguages lPaths; lPaths.maybeInsert(Utils::FilePath::fromString(basePaths.first()), QmlJS::Dialect::Qml); - QmlJS::ModelManagerInterface::importScan(result, QmlJS::ModelManagerInterface::workingCopy(), + QmlJS::ModelManagerInterface::importScan(QmlJS::ModelManagerInterface::workingCopy(), lPaths, QmlJS::ModelManagerInterface::instance(), false); // Load plugins diff --git a/tests/auto/qml/qmldesigner/wizard/userpresets-test.cpp b/tests/auto/qml/qmldesigner/wizard/userpresets-test.cpp index 7dffbef4160..0605791315a 100644 --- a/tests/auto/qml/qmldesigner/wizard/userpresets-test.cpp +++ b/tests/auto/qml/qmldesigner/wizard/userpresets-test.cpp @@ -44,8 +44,6 @@ void PrintTo(const std::vector &presets, std::ostream *os) using namespace StudioWelcome; -constexpr char ARRAY_NAME[] = "UserPresets"; - class FakeStoreIo : public StoreIo { public: diff --git a/tests/auto/qml/qmleditor/qmlcodeformatter/tst_qmlcodeformatter.cpp b/tests/auto/qml/qmleditor/qmlcodeformatter/tst_qmlcodeformatter.cpp index 8d2194086b2..934bf5d317d 100644 --- a/tests/auto/qml/qmleditor/qmlcodeformatter/tst_qmlcodeformatter.cpp +++ b/tests/auto/qml/qmleditor/qmlcodeformatter/tst_qmlcodeformatter.cpp @@ -83,6 +83,7 @@ private Q_SLOTS: void bug1(); void bug2(); void bug3(); + void indentFunctionWithReturnTypeAnnotation(); }; enum { DontCheck = -2, DontIndent = -1 }; @@ -1564,6 +1565,18 @@ void tst_QMLCodeFormatter::bug3() checkIndent(data); } + +void tst_QMLCodeFormatter::indentFunctionWithReturnTypeAnnotation() +{ + QList data; + data << Line("function test() : void {", 0) + << Line("", 4) + << Line(" }", 0) + ; + checkIndent(data); +} + + QTEST_GUILESS_MAIN(tst_QMLCodeFormatter) #include "tst_qmlcodeformatter.moc" diff --git a/tests/auto/qml/reformatter/forEachType.qml b/tests/auto/qml/reformatter/forEachType.qml new file mode 100644 index 00000000000..6fdabbde1bc --- /dev/null +++ b/tests/auto/qml/reformatter/forEachType.qml @@ -0,0 +1,9 @@ +import QtQml + +QtObject { + Component.onCompleted: { + for (var i of ["one", "two", "free"]) { + console.debug(i) + } + } +} diff --git a/tests/auto/qml/reformatter/jsdirectives.js b/tests/auto/qml/reformatter/jsdirectives.js new file mode 100644 index 00000000000..9029ba72335 --- /dev/null +++ b/tests/auto/qml/reformatter/jsdirectives.js @@ -0,0 +1,15 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + +.pragma library + +.import QtQml as QTQML + + + +.import QtQuick as QTQUICK + + + +function test() {} diff --git a/tests/auto/qml/reformatter/tst_reformatter.cpp b/tests/auto/qml/reformatter/tst_reformatter.cpp index 42d98e02bed..68b192fa600 100644 --- a/tests/auto/qml/reformatter/tst_reformatter.cpp +++ b/tests/auto/qml/reformatter/tst_reformatter.cpp @@ -68,25 +68,15 @@ void tst_Reformatter::test() QString rewritten = reformat(doc); - QStringList sourceLines = source.split(QLatin1Char('\n')); - QStringList newLines = rewritten.split(QLatin1Char('\n')); + QStringList sourceLines = source.split(QLatin1Char('\n'), Qt::SkipEmptyParts); + QStringList newLines = rewritten.split(QLatin1Char('\n'), Qt::SkipEmptyParts); // compare line by line int commonLines = qMin(newLines.size(), sourceLines.size()); - bool insideMultiLineComment = false; for (int i = 0; i < commonLines; ++i) { // names intentional to make 'Actual (sourceLine): ...\nExpected (newLinee): ...' line up const QString &sourceLine = sourceLines.at(i); const QString &newLinee = newLines.at(i); - if (!insideMultiLineComment && sourceLine.trimmed().startsWith("/*")) { - insideMultiLineComment = true; - sourceLines.insert(i, "\n"); - continue; - } - if (sourceLine.trimmed().endsWith("*/")) - insideMultiLineComment = false; - if (sourceLine.trimmed().isEmpty() && newLinee.trimmed().isEmpty()) - continue; bool fail = !QCOMPARE_NOEXIT(newLinee, sourceLine); if (fail) { qDebug() << "in line" << (i + 1); diff --git a/tests/auto/runextensions/tst_runextensions.cpp b/tests/auto/runextensions/tst_runextensions.cpp index 50a3f8a40d6..4ffee270025 100644 --- a/tests/auto/runextensions/tst_runextensions.cpp +++ b/tests/auto/runextensions/tst_runextensions.cpp @@ -22,7 +22,6 @@ private slots: void threadPriority(); void runAsyncNoFutureInterface(); void crefFunction(); - void onResultReady(); }; void report3(QFutureInterface &fi) @@ -536,64 +535,6 @@ void tst_RunExtensions::crefFunction() QCOMPARE(value, true); } -class ObjWithProperty : public QObject -{ - Q_OBJECT - -public slots: - void setValue(const QString &s) - { - value = s; - } - -public: - QString value; -}; - -void tst_RunExtensions::onResultReady() -{ - { // lambda - QFuture f = Utils::runAsync([](QFutureInterface &fi) { - fi.reportResult("Hi"); - fi.reportResult("there"); - }); - int count = 0; - QString res; - Utils::onResultReady(f, [&count, &res](const QString &s) { - ++count; - res = s; - }); - f.waitForFinished(); - QCoreApplication::processEvents(); - QCOMPARE(count, 2); - QCOMPARE(res, QString("there")); - } - { // lambda with guard - QFuture f = Utils::runAsync([](QFutureInterface &fi) { - fi.reportResult("Hi"); - fi.reportResult("there"); - }); - int count = 0; - ObjWithProperty obj; - Utils::onResultReady(f, &obj, [&count, &obj](const QString &s) { - ++count; - obj.setValue(s); - }); - f.waitForFinished(); - QCoreApplication::processEvents(); - QCOMPARE(count, 2); - QCOMPARE(obj.value, QString("there")); - } - { // member - QFuture f = Utils::runAsync([]() { return QString("Hi"); }); - ObjWithProperty obj; - Utils::onResultReady(f, &obj, &ObjWithProperty::setValue); - f.waitForFinished(); - QCoreApplication::processEvents(); - QCOMPARE(obj.value, QString("Hi")); - } -} - QTEST_GUILESS_MAIN(tst_RunExtensions) #include "tst_runextensions.moc" diff --git a/tests/auto/solutions/CMakeLists.txt b/tests/auto/solutions/CMakeLists.txt new file mode 100644 index 00000000000..694d940195d --- /dev/null +++ b/tests/auto/solutions/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(tasking) diff --git a/tests/auto/solutions/solutions.qbs b/tests/auto/solutions/solutions.qbs new file mode 100644 index 00000000000..cc445753cc2 --- /dev/null +++ b/tests/auto/solutions/solutions.qbs @@ -0,0 +1,8 @@ +import qbs + +Project { + name: "Solutions autotests" + references: [ + "tasking/tasking.qbs", + ] +} diff --git a/tests/auto/solutions/tasking/CMakeLists.txt b/tests/auto/solutions/tasking/CMakeLists.txt new file mode 100644 index 00000000000..a425250a5a9 --- /dev/null +++ b/tests/auto/solutions/tasking/CMakeLists.txt @@ -0,0 +1,4 @@ +add_qtc_test(tst_solutions_tasking + DEPENDS Tasking + SOURCES tst_tasking.cpp +) diff --git a/tests/auto/solutions/tasking/tasking.qbs b/tests/auto/solutions/tasking/tasking.qbs new file mode 100644 index 00000000000..f099edb370c --- /dev/null +++ b/tests/auto/solutions/tasking/tasking.qbs @@ -0,0 +1,7 @@ +QtcAutotest { + name: "Tasking autotest" + + Depends { name: "Tasking" } + + files: "tst_tasking.cpp" +} diff --git a/tests/auto/solutions/tasking/tst_tasking.cpp b/tests/auto/solutions/tasking/tst_tasking.cpp new file mode 100644 index 00000000000..294bea71b7e --- /dev/null +++ b/tests/auto/solutions/tasking/tst_tasking.cpp @@ -0,0 +1,2270 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include + +#include + +using namespace Tasking; + +using namespace std::chrono; +using namespace std::chrono_literals; + +using TaskObject = milliseconds; +using TestTask = TimeoutTask; + +namespace PrintableEnums { + +Q_NAMESPACE + +enum class Handler { + Setup, + Done, + Error, + GroupSetup, + GroupDone, + GroupError, + Sync, + BarrierAdvance, + Timeout +}; +Q_ENUM_NS(Handler); + +enum class OnDone { Success, Failure }; +Q_ENUM_NS(OnDone); + +} // namespace PrintableEnums + +using namespace PrintableEnums; + +using Log = QList>; + +struct CustomStorage +{ + CustomStorage() { ++s_count; } + ~CustomStorage() { --s_count; } + Log m_log; + static int instanceCount() { return s_count; } +private: + static int s_count; +}; + +int CustomStorage::s_count = 0; +static const char s_taskIdProperty[] = "__taskId"; + +struct TestData { + TreeStorage storage; + Group root; + Log expectedLog; + int taskCount = 0; + OnDone onDone = OnDone::Success; +}; + +class tst_Tasking : public QObject +{ + Q_OBJECT + +private slots: + void validConstructs(); // compile test + void testTree_data(); + void testTree(); + void storageOperators(); + void storageDestructor(); +}; + +void tst_Tasking::validConstructs() +{ + const Group task { + parallel, + TestTask([](TaskObject &) {}, [](const TaskObject &) {}), + TestTask([](TaskObject &) {}, [](const TaskObject &) {}), + TestTask([](TaskObject &) {}, [](const TaskObject &) {}) + }; + + const Group group1 { + task + }; + + const Group group2 { + parallel, + Group { + parallel, + TestTask([](TaskObject &) {}, [](const TaskObject &) {}), + Group { + parallel, + TestTask([](TaskObject &) {}, [](const TaskObject &) {}), + Group { + parallel, + TestTask([](TaskObject &) {}, [](const TaskObject &) {}) + } + }, + Group { + parallel, + TestTask([](TaskObject &) {}, [](const TaskObject &) {}), + onGroupDone([] {}) + } + }, + task, + onGroupDone([] {}), + onGroupError([] {}) + }; + + const auto setupHandler = [](TaskObject &) {}; + const auto doneHandler = [](const TaskObject &) {}; + const auto errorHandler = [](const TaskObject &) {}; + + // Not fluent interface + + const Group task2 { + parallel, + TestTask(setupHandler), + TestTask(setupHandler, doneHandler), + TestTask(setupHandler, doneHandler, errorHandler), + // need to explicitly pass empty handler for done + TestTask(setupHandler, {}, errorHandler) + }; + + // Fluent interface + + const Group fluent { + parallel, + TestTask().onSetup(setupHandler), + TestTask().onSetup(setupHandler).onDone(doneHandler), + TestTask().onSetup(setupHandler).onDone(doneHandler).onError(errorHandler), + // possible to skip the empty done + TestTask().onSetup(setupHandler).onError(errorHandler), + // possible to set handlers in a different order + TestTask().onError(errorHandler).onDone(doneHandler).onSetup(setupHandler), + }; + + + // When turning each of below blocks on, you should see the specific compiler error message. + +#if 0 + { + // "Sync element: The synchronous function has to return void or bool." + const auto setupSync = [] { return 3; }; + const Sync sync(setupSync); + } +#endif + +#if 0 + { + // "Sync element: The synchronous function can't take any arguments." + const auto setupSync = [](int) { }; + const Sync sync(setupSync); + } +#endif + +#if 0 + { + // "Sync element: The synchronous function can't take any arguments." + const auto setupSync = [](int) { return true; }; + const Sync sync(setupSync); + } +#endif +} + +class TickAndDone : public QObject +{ + Q_OBJECT + +public: + void setInterval(const milliseconds &interval) { m_interval = interval; } + void start() { + QTimer::singleShot(0, this, [this] { + emit tick(); + QTimer::singleShot(m_interval, this, &TickAndDone::done); + }); + } + +signals: + void tick(); + void done(); + +private: + milliseconds m_interval; +}; + +class TickAndDoneTaskAdapter : public TaskAdapter +{ +public: + TickAndDoneTaskAdapter() { connect(task(), &TickAndDone::done, this, + [this] { emit done(true); }); } + void start() final { task()->start(); } +}; + +TASKING_DECLARE_TASK(TickAndDoneTask, TickAndDoneTaskAdapter); + +template +TaskItem createBarrierAdvance(const TreeStorage &storage, + const SharedBarrierType &barrier, int taskId) +{ + return TickAndDoneTask([storage, barrier, taskId](TickAndDone &tickAndDone) { + tickAndDone.setInterval(1ms); + storage->m_log.append({taskId, Handler::Setup}); + + CustomStorage *currentStorage = storage.activeStorage(); + Barrier *sharedBarrier = barrier->barrier(); + QObject::connect(&tickAndDone, &TickAndDone::tick, sharedBarrier, + [currentStorage, sharedBarrier, taskId] { + currentStorage->m_log.append({taskId, Handler::BarrierAdvance}); + sharedBarrier->advance(); + }); + }); +} + +void tst_Tasking::testTree_data() +{ + QTest::addColumn("testData"); + + TreeStorage storage; + + const auto setupTask = [storage](int taskId, milliseconds timeout) { + return [storage, taskId, timeout](TaskObject &taskObject) { + taskObject = timeout; + storage->m_log.append({taskId, Handler::Setup}); + }; + }; + + const auto setupDynamicTask = [storage](int taskId, TaskAction action) { + return [storage, taskId, action](TaskObject &) { + storage->m_log.append({taskId, Handler::Setup}); + return action; + }; + }; + + const auto setupDone = [storage](int taskId) { + return [storage, taskId](const TaskObject &) { + storage->m_log.append({taskId, Handler::Done}); + }; + }; + + const auto setupError = [storage](int taskId) { + return [storage, taskId](const TaskObject &) { + storage->m_log.append({taskId, Handler::Error}); + }; + }; + + const auto setupTimeout = [storage](int taskId) { + return [storage, taskId] { + storage->m_log.append({taskId, Handler::Timeout}); + }; + }; + + const auto createTask = [storage, setupTask, setupDone, setupError]( + int taskId, bool successTask, milliseconds timeout = 0ms) -> TaskItem { + if (successTask) + return TestTask(setupTask(taskId, timeout), setupDone(taskId), setupError(taskId)); + const Group root { + finishAllAndError, + TestTask(setupTask(taskId, timeout)), + onGroupDone([storage, taskId] { storage->m_log.append({taskId, Handler::Done}); }), + onGroupError([storage, taskId] { storage->m_log.append({taskId, Handler::Error}); }) + }; + return root; + }; + + const auto createSuccessTask = [createTask](int taskId, milliseconds timeout = 0ms) { + return createTask(taskId, true, timeout); + }; + + const auto createFailingTask = [createTask](int taskId, milliseconds timeout = 0ms) { + return createTask(taskId, false, timeout); + }; + + const auto createDynamicTask = [storage, setupDynamicTask, setupDone, setupError]( + int taskId, TaskAction action) { + return TestTask(setupDynamicTask(taskId, action), setupDone(taskId), setupError(taskId)); + }; + + const auto groupSetup = [storage](int taskId) { + return onGroupSetup([=] { storage->m_log.append({taskId, Handler::GroupSetup}); }); + }; + const auto groupDone = [storage](int taskId) { + return onGroupDone([=] { storage->m_log.append({taskId, Handler::GroupDone}); }); + }; + const auto groupError = [storage](int taskId) { + return onGroupError([=] { storage->m_log.append({taskId, Handler::GroupError}); }); + }; + const auto createSync = [storage](int taskId) { + return Sync([=] { storage->m_log.append({taskId, Handler::Sync}); }); + }; + const auto createSyncWithReturn = [storage](int taskId, bool success) { + return Sync([=] { storage->m_log.append({taskId, Handler::Sync}); return success; }); + }; + + { + const Group root1 { + Storage(storage), + groupDone(0), + groupError(0) + }; + const Group root2 { + Storage(storage), + onGroupSetup([] { return TaskAction::Continue; }), + groupDone(0), + groupError(0) + }; + const Group root3 { + Storage(storage), + onGroupSetup([] { return TaskAction::StopWithDone; }), + groupDone(0), + groupError(0) + }; + const Group root4 { + Storage(storage), + onGroupSetup([] { return TaskAction::StopWithError; }), + groupDone(0), + groupError(0) + }; + const Log logDone {{0, Handler::GroupDone}}; + const Log logError {{0, Handler::GroupError}}; + QTest::newRow("Empty") << TestData{storage, root1, logDone, 0, OnDone::Success}; + QTest::newRow("EmptyContinue") << TestData{storage, root2, logDone, 0, OnDone::Success}; + QTest::newRow("EmptyDone") << TestData{storage, root3, logDone, 0, OnDone::Success}; + QTest::newRow("EmptyError") << TestData{storage, root4, logError, 0, OnDone::Failure}; + } + + { + const Group root { + Storage(storage), + createDynamicTask(1, TaskAction::StopWithDone), + createDynamicTask(2, TaskAction::StopWithDone) + }; + const Log log {{1, Handler::Setup}, {2, Handler::Setup}}; + QTest::newRow("DynamicTaskDone") << TestData{storage, root, log, 2, OnDone::Success}; + } + + { + const Group root { + Storage(storage), + createDynamicTask(1, TaskAction::StopWithError), + createDynamicTask(2, TaskAction::StopWithError) + }; + const Log log {{1, Handler::Setup}}; + QTest::newRow("DynamicTaskError") << TestData{storage, root, log, 2, OnDone::Failure}; + } + + { + const Group root { + Storage(storage), + createDynamicTask(1, TaskAction::Continue), + createDynamicTask(2, TaskAction::Continue), + createDynamicTask(3, TaskAction::StopWithError), + createDynamicTask(4, TaskAction::Continue) + }; + const Log log { + {1, Handler::Setup}, + {1, Handler::Done}, + {2, Handler::Setup}, + {2, Handler::Done}, + {3, Handler::Setup} + }; + QTest::newRow("DynamicMixed") << TestData{storage, root, log, 4, OnDone::Failure}; + } + + { + const Group root { + parallel, + Storage(storage), + createDynamicTask(1, TaskAction::Continue), + createDynamicTask(2, TaskAction::Continue), + createDynamicTask(3, TaskAction::StopWithError), + createDynamicTask(4, TaskAction::Continue) + }; + const Log log { + {1, Handler::Setup}, + {2, Handler::Setup}, + {3, Handler::Setup}, + {1, Handler::Error}, + {2, Handler::Error} + }; + QTest::newRow("DynamicParallel") << TestData{storage, root, log, 4, OnDone::Failure}; + } + + { + const Group root { + parallel, + Storage(storage), + createDynamicTask(1, TaskAction::Continue), + createDynamicTask(2, TaskAction::Continue), + Group { + createDynamicTask(3, TaskAction::StopWithError) + }, + createDynamicTask(4, TaskAction::Continue) + }; + const Log log { + {1, Handler::Setup}, + {2, Handler::Setup}, + {3, Handler::Setup}, + {1, Handler::Error}, + {2, Handler::Error} + }; + QTest::newRow("DynamicParallelGroup") << TestData{storage, root, log, 4, OnDone::Failure}; + } + + { + const Group root { + parallel, + Storage(storage), + createDynamicTask(1, TaskAction::Continue), + createDynamicTask(2, TaskAction::Continue), + Group { + onGroupSetup([storage] { + storage->m_log.append({0, Handler::GroupSetup}); + return TaskAction::StopWithError; + }), + createDynamicTask(3, TaskAction::Continue) + }, + createDynamicTask(4, TaskAction::Continue) + }; + const Log log { + {1, Handler::Setup}, + {2, Handler::Setup}, + {0, Handler::GroupSetup}, + {1, Handler::Error}, + {2, Handler::Error} + }; + QTest::newRow("DynamicParallelGroupSetup") + << TestData{storage, root, log, 4, OnDone::Failure}; + } + + { + const Group root { + Storage(storage), + Group { + Group { + Group { + Group { + Group { + createSuccessTask(5), + groupSetup(5), + groupDone(5) + }, + groupSetup(4), + groupDone(4) + }, + groupSetup(3), + groupDone(3) + }, + groupSetup(2), + groupDone(2) + }, + groupSetup(1), + groupDone(1) + }, + groupDone(0) + }; + const Log log { + {1, Handler::GroupSetup}, + {2, Handler::GroupSetup}, + {3, Handler::GroupSetup}, + {4, Handler::GroupSetup}, + {5, Handler::GroupSetup}, + {5, Handler::Setup}, + {5, Handler::Done}, + {5, Handler::GroupDone}, + {4, Handler::GroupDone}, + {3, Handler::GroupDone}, + {2, Handler::GroupDone}, + {1, Handler::GroupDone}, + {0, Handler::GroupDone} + }; + QTest::newRow("Nested") << TestData{storage, root, log, 1, OnDone::Success}; + } + + { + const Group root { + Storage(storage), + parallel, + createSuccessTask(1), + createSuccessTask(2), + createSuccessTask(3), + createSuccessTask(4), + createSuccessTask(5), + groupDone(0) + }; + const Log log { + {1, Handler::Setup}, // Setup order is determined in parallel mode + {2, Handler::Setup}, + {3, Handler::Setup}, + {4, Handler::Setup}, + {5, Handler::Setup}, + {1, Handler::Done}, + {2, Handler::Done}, + {3, Handler::Done}, + {4, Handler::Done}, + {5, Handler::Done}, + {0, Handler::GroupDone} + }; + QTest::newRow("Parallel") << TestData{storage, root, log, 5, OnDone::Success}; + } + + { + auto setupSubTree = [storage, createSuccessTask](TaskTree &taskTree) { + const Group nestedRoot { + Storage(storage), + createSuccessTask(2), + createSuccessTask(3), + createSuccessTask(4) + }; + taskTree.setupRoot(nestedRoot); + CustomStorage *activeStorage = storage.activeStorage(); + auto collectSubLog = [activeStorage](CustomStorage *subTreeStorage){ + activeStorage->m_log += subTreeStorage->m_log; + }; + taskTree.onStorageDone(storage, collectSubLog); + }; + const Group root1 { + Storage(storage), + createSuccessTask(1), + createSuccessTask(2), + createSuccessTask(3), + createSuccessTask(4), + createSuccessTask(5), + groupDone(0) + }; + const Group root2 { + Storage(storage), + Group { createSuccessTask(1) }, + Group { createSuccessTask(2) }, + Group { createSuccessTask(3) }, + Group { createSuccessTask(4) }, + Group { createSuccessTask(5) }, + groupDone(0) + }; + const Group root3 { + Storage(storage), + createSuccessTask(1), + TaskTreeTask(setupSubTree), + createSuccessTask(5), + groupDone(0) + }; + const Log log { + {1, Handler::Setup}, + {1, Handler::Done}, + {2, Handler::Setup}, + {2, Handler::Done}, + {3, Handler::Setup}, + {3, Handler::Done}, + {4, Handler::Setup}, + {4, Handler::Done}, + {5, Handler::Setup}, + {5, Handler::Done}, + {0, Handler::GroupDone} + }; + QTest::newRow("Sequential") << TestData{storage, root1, log, 5, OnDone::Success}; + QTest::newRow("SequentialEncapsulated") << TestData{storage, root2, log, 5, OnDone::Success}; + // We don't inspect subtrees, so taskCount is 3, not 5. + QTest::newRow("SequentialSubTree") << TestData{storage, root3, log, 3, OnDone::Success}; + } + + { + const Group root { + Storage(storage), + Group { + createSuccessTask(1), + Group { + createSuccessTask(2), + Group { + createSuccessTask(3), + Group { + createSuccessTask(4), + Group { + createSuccessTask(5), + groupDone(5) + }, + groupDone(4) + }, + groupDone(3) + }, + groupDone(2) + }, + groupDone(1) + }, + groupDone(0) + }; + const Log log { + {1, Handler::Setup}, + {1, Handler::Done}, + {2, Handler::Setup}, + {2, Handler::Done}, + {3, Handler::Setup}, + {3, Handler::Done}, + {4, Handler::Setup}, + {4, Handler::Done}, + {5, Handler::Setup}, + {5, Handler::Done}, + {5, Handler::GroupDone}, + {4, Handler::GroupDone}, + {3, Handler::GroupDone}, + {2, Handler::GroupDone}, + {1, Handler::GroupDone}, + {0, Handler::GroupDone} + }; + QTest::newRow("SequentialNested") << TestData{storage, root, log, 5, OnDone::Success}; + } + + { + const Group root { + Storage(storage), + createSuccessTask(1), + createSuccessTask(2), + createFailingTask(3), + createSuccessTask(4), + createSuccessTask(5), + groupDone(0), + groupError(0) + }; + const Log log { + {1, Handler::Setup}, + {1, Handler::Done}, + {2, Handler::Setup}, + {2, Handler::Done}, + {3, Handler::Setup}, + {3, Handler::Error}, + {0, Handler::GroupError} + }; + QTest::newRow("SequentialError") << TestData{storage, root, log, 5, OnDone::Failure}; + } + + { + const auto createRoot = [storage, groupDone, groupError](WorkflowPolicy policy) { + return Group { + Storage(storage), + workflowPolicy(policy), + groupDone(0), + groupError(0) + }; + }; + + const Log doneLog = {{0, Handler::GroupDone}}; + const Log errorLog = {{0, Handler::GroupError}}; + + const Group root1 = createRoot(WorkflowPolicy::StopOnError); + QTest::newRow("EmptyStopOnError") << TestData{storage, root1, doneLog, 0, + OnDone::Success}; + + const Group root2 = createRoot(WorkflowPolicy::ContinueOnError); + QTest::newRow("EmptyContinueOnError") << TestData{storage, root2, doneLog, 0, + OnDone::Success}; + + const Group root3 = createRoot(WorkflowPolicy::StopOnDone); + QTest::newRow("EmptyStopOnDone") << TestData{storage, root3, errorLog, 0, + OnDone::Failure}; + + const Group root4 = createRoot(WorkflowPolicy::ContinueOnDone); + QTest::newRow("EmptyContinueOnDone") << TestData{storage, root4, errorLog, 0, + OnDone::Failure}; + + const Group root5 = createRoot(WorkflowPolicy::StopOnFinished); + QTest::newRow("EmptyStopOnFinished") << TestData{storage, root5, errorLog, 0, + OnDone::Failure}; + + const Group root6 = createRoot(WorkflowPolicy::FinishAllAndDone); + QTest::newRow("EmptyFinishAllAndDone") << TestData{storage, root6, doneLog, 0, + OnDone::Success}; + + const Group root7 = createRoot(WorkflowPolicy::FinishAllAndError); + QTest::newRow("EmptyFinishAllAndError") << TestData{storage, root7, errorLog, 0, + OnDone::Failure}; + } + + { + const auto createRoot = [storage, createSuccessTask, groupDone, groupError]( + WorkflowPolicy policy) { + return Group { + Storage(storage), + workflowPolicy(policy), + createSuccessTask(1), + groupDone(0), + groupError(0) + }; + }; + + const Log doneLog = { + {1, Handler::Setup}, + {1, Handler::Done}, + {0, Handler::GroupDone} + }; + + const Log errorLog = { + {1, Handler::Setup}, + {1, Handler::Done}, + {0, Handler::GroupError} + }; + + const Group root1 = createRoot(WorkflowPolicy::StopOnError); + QTest::newRow("DoneStopOnError") << TestData{storage, root1, doneLog, 1, + OnDone::Success}; + + const Group root2 = createRoot(WorkflowPolicy::ContinueOnError); + QTest::newRow("DoneContinueOnError") << TestData{storage, root2, doneLog, 1, + OnDone::Success}; + + const Group root3 = createRoot(WorkflowPolicy::StopOnDone); + QTest::newRow("DoneStopOnDone") << TestData{storage, root3, doneLog, 1, + OnDone::Success}; + + const Group root4 = createRoot(WorkflowPolicy::ContinueOnDone); + QTest::newRow("DoneContinueOnDone") << TestData{storage, root4, doneLog, 1, + OnDone::Success}; + + const Group root5 = createRoot(WorkflowPolicy::StopOnFinished); + QTest::newRow("DoneStopOnFinished") << TestData{storage, root5, doneLog, 1, + OnDone::Success}; + + const Group root6 = createRoot(WorkflowPolicy::FinishAllAndDone); + QTest::newRow("DoneFinishAllAndDone") << TestData{storage, root6, doneLog, 1, + OnDone::Success}; + + const Group root7 = createRoot(WorkflowPolicy::FinishAllAndError); + QTest::newRow("DoneFinishAllAndError") << TestData{storage, root7, errorLog, 1, + OnDone::Failure}; + } + + { + const auto createRoot = [storage, createFailingTask, groupDone, groupError]( + WorkflowPolicy policy) { + return Group { + Storage(storage), + workflowPolicy(policy), + createFailingTask(1), + groupDone(0), + groupError(0) + }; + }; + + const Log doneLog = { + {1, Handler::Setup}, + {1, Handler::Error}, + {0, Handler::GroupDone} + }; + + const Log errorLog = { + {1, Handler::Setup}, + {1, Handler::Error}, + {0, Handler::GroupError} + }; + + const Group root1 = createRoot(WorkflowPolicy::StopOnError); + QTest::newRow("ErrorStopOnError") << TestData{storage, root1, errorLog, 1, + OnDone::Failure}; + + const Group root2 = createRoot(WorkflowPolicy::ContinueOnError); + QTest::newRow("ErrorContinueOnError") << TestData{storage, root2, errorLog, 1, + OnDone::Failure}; + + const Group root3 = createRoot(WorkflowPolicy::StopOnDone); + QTest::newRow("ErrorStopOnDone") << TestData{storage, root3, errorLog, 1, + OnDone::Failure}; + + const Group root4 = createRoot(WorkflowPolicy::ContinueOnDone); + QTest::newRow("ErrorContinueOnDone") << TestData{storage, root4, errorLog, 1, + OnDone::Failure}; + + const Group root5 = createRoot(WorkflowPolicy::StopOnFinished); + QTest::newRow("ErrorStopOnFinished") << TestData{storage, root5, errorLog, 1, + OnDone::Failure}; + + const Group root6 = createRoot(WorkflowPolicy::FinishAllAndDone); + QTest::newRow("ErrorFinishAllAndDone") << TestData{storage, root6, doneLog, 1, + OnDone::Success}; + + const Group root7 = createRoot(WorkflowPolicy::FinishAllAndError); + QTest::newRow("ErrorFinishAllAndError") << TestData{storage, root7, errorLog, 1, + OnDone::Failure}; + } + + { + // These tests check whether the proper root's group end handler is called + // when the group is stopped. Test it with different workflow policies. + // The root starts one short failing task together with one long task. + const auto createRoot = [storage, createSuccessTask, createFailingTask, groupDone, + groupError](WorkflowPolicy policy) { + return Group { + Storage(storage), + parallel, + workflowPolicy(policy), + createFailingTask(1, 1ms), + createSuccessTask(2, 1ms), + groupDone(0), + groupError(0) + }; + }; + + const Log errorErrorLog = { + {1, Handler::Setup}, + {2, Handler::Setup}, + {1, Handler::Error}, + {2, Handler::Error}, + {0, Handler::GroupError} + }; + + const Log errorDoneLog = { + {1, Handler::Setup}, + {2, Handler::Setup}, + {1, Handler::Error}, + {2, Handler::Done}, + {0, Handler::GroupError} + }; + + const Log doneLog = { + {1, Handler::Setup}, + {2, Handler::Setup}, + {1, Handler::Error}, + {2, Handler::Done}, + {0, Handler::GroupDone} + }; + + const Group root1 = createRoot(WorkflowPolicy::StopOnError); + QTest::newRow("StopRootWithStopOnError") + << TestData{storage, root1, errorErrorLog, 2, OnDone::Failure}; + + const Group root2 = createRoot(WorkflowPolicy::ContinueOnError); + QTest::newRow("StopRootWithContinueOnError") + << TestData{storage, root2, errorDoneLog, 2, OnDone::Failure}; + + const Group root3 = createRoot(WorkflowPolicy::StopOnDone); + QTest::newRow("StopRootWithStopOnDone") + << TestData{storage, root3, doneLog, 2, OnDone::Success}; + + const Group root4 = createRoot(WorkflowPolicy::ContinueOnDone); + QTest::newRow("StopRootWithContinueOnDone") + << TestData{storage, root4, doneLog, 2, OnDone::Success}; + + const Group root5 = createRoot(WorkflowPolicy::StopOnFinished); + QTest::newRow("StopRootWithStopOnFinished") + << TestData{storage, root5, errorErrorLog, 2, OnDone::Failure}; + + const Group root6 = createRoot(WorkflowPolicy::FinishAllAndDone); + QTest::newRow("StopRootWithFinishAllAndDone") + << TestData{storage, root6, doneLog, 2, OnDone::Success}; + + const Group root7 = createRoot(WorkflowPolicy::FinishAllAndError); + QTest::newRow("StopRootWithFinishAllAndError") + << TestData{storage, root7, errorDoneLog, 2, OnDone::Failure}; + } + + { + // These tests check whether the proper root's group end handler is called + // when the group is stopped. Test it with different workflow policies. + // The root starts in parallel: one very short successful task, one short failing task + // and one long task. + const auto createRoot = [storage, createSuccessTask, createFailingTask, groupDone, + groupError](WorkflowPolicy policy) { + return Group { + Storage(storage), + parallel, + workflowPolicy(policy), + createSuccessTask(1), + createFailingTask(2, 1ms), + createSuccessTask(3, 1ms), + groupDone(0), + groupError(0) + }; + }; + + const Log errorErrorLog = { + {1, Handler::Setup}, + {2, Handler::Setup}, + {3, Handler::Setup}, + {1, Handler::Done}, + {2, Handler::Error}, + {3, Handler::Error}, + {0, Handler::GroupError} + }; + + const Log errorDoneLog = { + {1, Handler::Setup}, + {2, Handler::Setup}, + {3, Handler::Setup}, + {1, Handler::Done}, + {2, Handler::Error}, + {3, Handler::Done}, + {0, Handler::GroupError} + }; + + const Log doneErrorLog = { + {1, Handler::Setup}, + {2, Handler::Setup}, + {3, Handler::Setup}, + {1, Handler::Done}, + {2, Handler::Error}, + {3, Handler::Error}, + {0, Handler::GroupDone} + }; + + const Log doneDoneLog = { + {1, Handler::Setup}, + {2, Handler::Setup}, + {3, Handler::Setup}, + {1, Handler::Done}, + {2, Handler::Error}, + {3, Handler::Done}, + {0, Handler::GroupDone} + }; + + const Group root1 = createRoot(WorkflowPolicy::StopOnError); + QTest::newRow("StopRootAfterDoneWithStopOnError") + << TestData{storage, root1, errorErrorLog, 3, OnDone::Failure}; + + const Group root2 = createRoot(WorkflowPolicy::ContinueOnError); + QTest::newRow("StopRootAfterDoneWithContinueOnError") + << TestData{storage, root2, errorDoneLog, 3, OnDone::Failure}; + + const Group root3 = createRoot(WorkflowPolicy::StopOnDone); + QTest::newRow("StopRootAfterDoneWithStopOnDone") + << TestData{storage, root3, doneErrorLog, 3, OnDone::Success}; + + const Group root4 = createRoot(WorkflowPolicy::ContinueOnDone); + QTest::newRow("StopRootAfterDoneWithContinueOnDone") + << TestData{storage, root4, doneDoneLog, 3, OnDone::Success}; + + const Group root5 = createRoot(WorkflowPolicy::StopOnFinished); + QTest::newRow("StopRootAfterDoneWithStopOnFinished") + << TestData{storage, root5, doneErrorLog, 3, OnDone::Success}; + + const Group root6 = createRoot(WorkflowPolicy::FinishAllAndDone); + QTest::newRow("StopRootAfterDoneWithFinishAllAndDone") + << TestData{storage, root6, doneDoneLog, 3, OnDone::Success}; + + const Group root7 = createRoot(WorkflowPolicy::FinishAllAndError); + QTest::newRow("StopRootAfterDoneWithFinishAllAndError") + << TestData{storage, root7, errorDoneLog, 3, OnDone::Failure}; + } + + { + // These tests check whether the proper subgroup's end handler is called + // when the group is stopped. Test it with different workflow policies. + // The subgroup starts one long task. + const auto createRoot = [storage, createSuccessTask, createFailingTask, groupDone, + groupError](WorkflowPolicy policy) { + return Group { + Storage(storage), + parallel, + Group { + workflowPolicy(policy), + createSuccessTask(1, 1000ms), + groupDone(1), + groupError(1) + }, + createFailingTask(2, 1ms), + groupDone(2), + groupError(2) + }; + }; + + const Log errorLog = { + {1, Handler::Setup}, + {2, Handler::Setup}, + {2, Handler::Error}, + {1, Handler::Error}, + {1, Handler::GroupError}, + {2, Handler::GroupError} + }; + + const Log doneLog = { + {1, Handler::Setup}, + {2, Handler::Setup}, + {2, Handler::Error}, + {1, Handler::Error}, + {1, Handler::GroupDone}, + {2, Handler::GroupError} + }; + + const Group root1 = createRoot(WorkflowPolicy::StopOnError); + QTest::newRow("StopGroupWithStopOnError") + << TestData{storage, root1, errorLog, 2, OnDone::Failure}; + + const Group root2 = createRoot(WorkflowPolicy::ContinueOnError); + QTest::newRow("StopGroupWithContinueOnError") + << TestData{storage, root2, errorLog, 2, OnDone::Failure}; + + const Group root3 = createRoot(WorkflowPolicy::StopOnDone); + QTest::newRow("StopGroupWithStopOnDone") + << TestData{storage, root3, errorLog, 2, OnDone::Failure}; + + const Group root4 = createRoot(WorkflowPolicy::ContinueOnDone); + QTest::newRow("StopGroupWithContinueOnDone") + << TestData{storage, root4, errorLog, 2, OnDone::Failure}; + + const Group root5 = createRoot(WorkflowPolicy::StopOnFinished); + QTest::newRow("StopGroupWithStopOnFinished") + << TestData{storage, root5, errorLog, 2, OnDone::Failure}; + + const Group root6 = createRoot(WorkflowPolicy::FinishAllAndDone); + QTest::newRow("StopGroupWithFinishAllAndDone") + << TestData{storage, root6, doneLog, 2, OnDone::Failure}; + + const Group root7 = createRoot(WorkflowPolicy::FinishAllAndError); + QTest::newRow("StopGroupWithFinishAllAndError") + << TestData{storage, root7, errorLog, 2, OnDone::Failure}; + } + + { + // These tests check whether the proper subgroup's end handler is called + // when the group is stopped. Test it with different workflow policies. + // The sequential subgroup starts one short successful task followed by one long task. + const auto createRoot = [storage, createSuccessTask, createFailingTask, groupDone, + groupError](WorkflowPolicy policy) { + return Group { + Storage(storage), + parallel, + Group { + workflowPolicy(policy), + createSuccessTask(1), + createSuccessTask(2, 1000ms), + groupDone(1), + groupError(1) + }, + createFailingTask(3, 1ms), + groupDone(2), + groupError(2) + }; + }; + + const Log errorLog = { + {1, Handler::Setup}, + {3, Handler::Setup}, + {1, Handler::Done}, + {2, Handler::Setup}, + {3, Handler::Error}, + {2, Handler::Error}, + {1, Handler::GroupError}, + {2, Handler::GroupError} + }; + + const Log shortDoneLog = { + {1, Handler::Setup}, + {3, Handler::Setup}, + {1, Handler::Done}, + {1, Handler::GroupDone}, + {3, Handler::Error}, + {2, Handler::GroupError} + }; + + const Log longDoneLog = { + {1, Handler::Setup}, + {3, Handler::Setup}, + {1, Handler::Done}, + {2, Handler::Setup}, + {3, Handler::Error}, + {2, Handler::Error}, + {1, Handler::GroupDone}, + {2, Handler::GroupError} + }; + + const Group root1 = createRoot(WorkflowPolicy::StopOnError); + QTest::newRow("StopGroupAfterDoneWithStopOnError") + << TestData{storage, root1, errorLog, 3, OnDone::Failure}; + + const Group root2 = createRoot(WorkflowPolicy::ContinueOnError); + QTest::newRow("StopGroupAfterDoneWithContinueOnError") + << TestData{storage, root2, errorLog, 3, OnDone::Failure}; + + const Group root3 = createRoot(WorkflowPolicy::StopOnDone); + QTest::newRow("StopGroupAfterDoneWithStopOnDone") + << TestData{storage, root3, shortDoneLog, 3, OnDone::Failure}; + + const Group root4 = createRoot(WorkflowPolicy::ContinueOnDone); + QTest::newRow("StopGroupAfterDoneWithContinueOnDone") + << TestData{storage, root4, longDoneLog, 3, OnDone::Failure}; + + const Group root5 = createRoot(WorkflowPolicy::StopOnFinished); + QTest::newRow("StopGroupAfterDoneWithStopOnFinished") + << TestData{storage, root5, shortDoneLog, 3, OnDone::Failure}; + + const Group root6 = createRoot(WorkflowPolicy::FinishAllAndDone); + QTest::newRow("StopGroupAfterDoneWithFinishAllAndDone") + << TestData{storage, root6, longDoneLog, 3, OnDone::Failure}; + + const Group root7 = createRoot(WorkflowPolicy::FinishAllAndError); + QTest::newRow("StopGroupAfterDoneWithFinishAllAndError") + << TestData{storage, root7, errorLog, 3, OnDone::Failure}; + } + + { + // These tests check whether the proper subgroup's end handler is called + // when the group is stopped. Test it with different workflow policies. + // The sequential subgroup starts one short failing task followed by one long task. + const auto createRoot = [storage, createSuccessTask, createFailingTask, groupDone, + groupError](WorkflowPolicy policy) { + return Group { + Storage(storage), + parallel, + Group { + workflowPolicy(policy), + createFailingTask(1), + createSuccessTask(2, 1000ms), + groupDone(1), + groupError(1) + }, + createFailingTask(3, 1ms), + groupDone(2), + groupError(2) + }; + }; + + const Log shortErrorLog = { + {1, Handler::Setup}, + {3, Handler::Setup}, + {1, Handler::Error}, + {1, Handler::GroupError}, + {3, Handler::Error}, + {2, Handler::GroupError} + }; + + const Log longErrorLog = { + {1, Handler::Setup}, + {3, Handler::Setup}, + {1, Handler::Error}, + {2, Handler::Setup}, + {3, Handler::Error}, + {2, Handler::Error}, + {1, Handler::GroupError}, + {2, Handler::GroupError} + }; + + const Log doneLog = { + {1, Handler::Setup}, + {3, Handler::Setup}, + {1, Handler::Error}, + {2, Handler::Setup}, + {3, Handler::Error}, + {2, Handler::Error}, + {1, Handler::GroupDone}, + {2, Handler::GroupError} + }; + + const Group root1 = createRoot(WorkflowPolicy::StopOnError); + QTest::newRow("StopGroupAfterErrorWithStopOnError") + << TestData{storage, root1, shortErrorLog, 3, OnDone::Failure}; + + const Group root2 = createRoot(WorkflowPolicy::ContinueOnError); + QTest::newRow("StopGroupAfterErrorWithContinueOnError") + << TestData{storage, root2, longErrorLog, 3, OnDone::Failure}; + + const Group root3 = createRoot(WorkflowPolicy::StopOnDone); + QTest::newRow("StopGroupAfterErrorWithStopOnDone") + << TestData{storage, root3, longErrorLog, 3, OnDone::Failure}; + + const Group root4 = createRoot(WorkflowPolicy::ContinueOnDone); + QTest::newRow("StopGroupAfterErrorWithContinueOnDone") + << TestData{storage, root4, longErrorLog, 3, OnDone::Failure}; + + const Group root5 = createRoot(WorkflowPolicy::StopOnFinished); + QTest::newRow("StopGroupAfterErrorWithStopOnFinished") + << TestData{storage, root5, shortErrorLog, 3, OnDone::Failure}; + + const Group root6 = createRoot(WorkflowPolicy::FinishAllAndDone); + QTest::newRow("StopGroupAfterErrorWithFinishAllAndDone") + << TestData{storage, root6, doneLog, 3, OnDone::Failure}; + + const Group root7 = createRoot(WorkflowPolicy::FinishAllAndError); + QTest::newRow("StopGroupAfterErrorWithFinishAllAndError") + << TestData{storage, root7, longErrorLog, 3, OnDone::Failure}; + } + + { + const auto createRoot = [storage, createSuccessTask, createFailingTask, groupDone, + groupError](WorkflowPolicy policy) { + return Group { + Storage(storage), + workflowPolicy(policy), + createSuccessTask(1), + createFailingTask(2), + createSuccessTask(3), + groupDone(0), + groupError(0) + }; + }; + + const Group root1 = createRoot(WorkflowPolicy::StopOnError); + const Log log1 { + {1, Handler::Setup}, + {1, Handler::Done}, + {2, Handler::Setup}, + {2, Handler::Error}, + {0, Handler::GroupError} + }; + QTest::newRow("StopOnError") << TestData{storage, root1, log1, 3, OnDone::Failure}; + + const Group root2 = createRoot(WorkflowPolicy::ContinueOnError); + const Log errorLog { + {1, Handler::Setup}, + {1, Handler::Done}, + {2, Handler::Setup}, + {2, Handler::Error}, + {3, Handler::Setup}, + {3, Handler::Done}, + {0, Handler::GroupError} + }; + QTest::newRow("ContinueOnError") << TestData{storage, root2, errorLog, 3, OnDone::Failure}; + + const Group root3 = createRoot(WorkflowPolicy::StopOnDone); + const Log log3 { + {1, Handler::Setup}, + {1, Handler::Done}, + {0, Handler::GroupDone} + }; + QTest::newRow("StopOnDone") << TestData{storage, root3, log3, 3, OnDone::Success}; + + const Group root4 = createRoot(WorkflowPolicy::ContinueOnDone); + const Log doneLog { + {1, Handler::Setup}, + {1, Handler::Done}, + {2, Handler::Setup}, + {2, Handler::Error}, + {3, Handler::Setup}, + {3, Handler::Done}, + {0, Handler::GroupDone} + }; + QTest::newRow("ContinueOnDone") << TestData{storage, root4, doneLog, 3, OnDone::Success}; + + const Group root5 = createRoot(WorkflowPolicy::StopOnFinished); + const Log log5 { + {1, Handler::Setup}, + {1, Handler::Done}, + {0, Handler::GroupDone} + }; + QTest::newRow("StopOnFinished") << TestData{storage, root5, log5, 3, OnDone::Success}; + + const Group root6 = createRoot(WorkflowPolicy::FinishAllAndDone); + QTest::newRow("FinishAllAndDone") << TestData{storage, root6, doneLog, 3, OnDone::Success}; + + const Group root7 = createRoot(WorkflowPolicy::FinishAllAndError); + QTest::newRow("FinishAllAndError") << TestData{storage, root7, errorLog, 3, OnDone::Failure}; + } + + { + const auto createRoot = [storage, createTask, groupDone, groupError]( + bool firstSuccess, bool secondSuccess) { + return Group { + parallel, + stopOnFinished, + Storage(storage), + createTask(1, firstSuccess, 1000ms), + createTask(2, secondSuccess, 1ms), + groupDone(0), + groupError(0) + }; + }; + + const Group root1 = createRoot(true, true); + const Group root2 = createRoot(true, false); + const Group root3 = createRoot(false, true); + const Group root4 = createRoot(false, false); + + const Log success { + {1, Handler::Setup}, + {2, Handler::Setup}, + {2, Handler::Done}, + {1, Handler::Error}, + {0, Handler::GroupDone} + }; + const Log failure { + {1, Handler::Setup}, + {2, Handler::Setup}, + {2, Handler::Error}, + {1, Handler::Error}, + {0, Handler::GroupError} + }; + + QTest::newRow("StopOnFinished1") << TestData{storage, root1, success, 2, OnDone::Success}; + QTest::newRow("StopOnFinished2") << TestData{storage, root2, failure, 2, OnDone::Failure}; + QTest::newRow("StopOnFinished3") << TestData{storage, root3, success, 2, OnDone::Success}; + QTest::newRow("StopOnFinished4") << TestData{storage, root4, failure, 2, OnDone::Failure}; + } + + { + const auto createRoot = [storage, createSuccessTask, groupDone, groupError]( + TaskAction taskAction) { + return Group { + Storage(storage), + Group { + createSuccessTask(1) + }, + Group { + onGroupSetup([=] { return taskAction; }), + createSuccessTask(2), + createSuccessTask(3), + createSuccessTask(4) + }, + groupDone(0), + groupError(0) + }; + }; + + const Group root1 = createRoot(TaskAction::StopWithDone); + const Log log1 { + {1, Handler::Setup}, + {1, Handler::Done}, + {0, Handler::GroupDone} + }; + QTest::newRow("DynamicSetupDone") << TestData{storage, root1, log1, 4, OnDone::Success}; + + const Group root2 = createRoot(TaskAction::StopWithError); + const Log log2 { + {1, Handler::Setup}, + {1, Handler::Done}, + {0, Handler::GroupError} + }; + QTest::newRow("DynamicSetupError") << TestData{storage, root2, log2, 4, OnDone::Failure}; + + const Group root3 = createRoot(TaskAction::Continue); + const Log log3 { + {1, Handler::Setup}, + {1, Handler::Done}, + {2, Handler::Setup}, + {2, Handler::Done}, + {3, Handler::Setup}, + {3, Handler::Done}, + {4, Handler::Setup}, + {4, Handler::Done}, + {0, Handler::GroupDone} + }; + QTest::newRow("DynamicSetupContinue") << TestData{storage, root3, log3, 4, OnDone::Success}; + } + + { + const Group root { + parallelLimit(2), + Storage(storage), + Group { + groupSetup(1), + createSuccessTask(1) + }, + Group { + groupSetup(2), + createSuccessTask(2) + }, + Group { + groupSetup(3), + createSuccessTask(3) + }, + Group { + groupSetup(4), + createSuccessTask(4) + } + }; + const Log log { + {1, Handler::GroupSetup}, + {1, Handler::Setup}, + {2, Handler::GroupSetup}, + {2, Handler::Setup}, + {1, Handler::Done}, + {3, Handler::GroupSetup}, + {3, Handler::Setup}, + {2, Handler::Done}, + {4, Handler::GroupSetup}, + {4, Handler::Setup}, + {3, Handler::Done}, + {4, Handler::Done} + }; + QTest::newRow("NestedParallel") << TestData{storage, root, log, 4, OnDone::Success}; + } + + { + const Group root { + parallelLimit(2), + Storage(storage), + Group { + groupSetup(1), + createSuccessTask(1) + }, + Group { + groupSetup(2), + createSuccessTask(2) + }, + Group { + groupSetup(3), + createDynamicTask(3, TaskAction::StopWithDone) + }, + Group { + groupSetup(4), + createSuccessTask(4) + }, + Group { + groupSetup(5), + createSuccessTask(5) + } + }; + const Log log { + {1, Handler::GroupSetup}, + {1, Handler::Setup}, + {2, Handler::GroupSetup}, + {2, Handler::Setup}, + {1, Handler::Done}, + {3, Handler::GroupSetup}, + {3, Handler::Setup}, + {4, Handler::GroupSetup}, + {4, Handler::Setup}, + {2, Handler::Done}, + {5, Handler::GroupSetup}, + {5, Handler::Setup}, + {4, Handler::Done}, + {5, Handler::Done} + }; + QTest::newRow("NestedParallelDone") << TestData{storage, root, log, 5, OnDone::Success}; + } + + { + const Group root1 { + parallelLimit(2), + Storage(storage), + Group { + groupSetup(1), + createSuccessTask(1) + }, + Group { + groupSetup(2), + createSuccessTask(2) + }, + Group { + groupSetup(3), + createDynamicTask(3, TaskAction::StopWithError) + }, + Group { + groupSetup(4), + createSuccessTask(4) + }, + Group { + groupSetup(5), + createSuccessTask(5) + } + }; + const Log log1 { + {1, Handler::GroupSetup}, + {1, Handler::Setup}, + {2, Handler::GroupSetup}, + {2, Handler::Setup}, + {1, Handler::Done}, + {3, Handler::GroupSetup}, + {3, Handler::Setup}, + {2, Handler::Error} + }; + + // Inside this test the task 2 should finish first, then synchonously: + // - task 3 should exit setup with error + // - task 1 should be stopped as a consequence of the error inside the group + // - tasks 4 and 5 should be skipped + const Group root2 { + parallelLimit(2), + Storage(storage), + Group { + groupSetup(1), + createSuccessTask(1, 10ms) + }, + Group { + groupSetup(2), + createSuccessTask(2) + }, + Group { + groupSetup(3), + createDynamicTask(3, TaskAction::StopWithError) + }, + Group { + groupSetup(4), + createSuccessTask(4) + }, + Group { + groupSetup(5), + createSuccessTask(5) + } + }; + const Log log2 { + {1, Handler::GroupSetup}, + {1, Handler::Setup}, + {2, Handler::GroupSetup}, + {2, Handler::Setup}, + {2, Handler::Done}, + {3, Handler::GroupSetup}, + {3, Handler::Setup}, + {1, Handler::Error} + }; + + // This test ensures that the task 1 doesn't invoke its done handler, + // being ready while sleeping in the task's 2 done handler. + // Inside this test the task 2 should finish first, then synchonously: + // - task 3 should exit setup with error + // - task 1 should be stopped as a consequence of error inside the group + // - task 4 should be skipped + // - the first child group of root should finish with error + // - task 5 should be started (because of root's continueOnError policy) + const Group root3 { + continueOnError, + Storage(storage), + Group { + parallelLimit(2), + Group { + groupSetup(1), + createSuccessTask(1, 10ms) + }, + Group { + groupSetup(2), + createSuccessTask(2, 1ms) + }, + Group { + groupSetup(3), + createDynamicTask(3, TaskAction::StopWithError) + }, + Group { + groupSetup(4), + createSuccessTask(4) + } + }, + Group { + groupSetup(5), + createSuccessTask(5) + } + }; + const Log log3 { + {1, Handler::GroupSetup}, + {1, Handler::Setup}, + {2, Handler::GroupSetup}, + {2, Handler::Setup}, + {2, Handler::Done}, + {3, Handler::GroupSetup}, + {3, Handler::Setup}, + {1, Handler::Error}, + {5, Handler::GroupSetup}, + {5, Handler::Setup}, + {5, Handler::Done} + }; + QTest::newRow("NestedParallelError1") + << TestData{storage, root1, log1, 5, OnDone::Failure}; + QTest::newRow("NestedParallelError2") + << TestData{storage, root2, log2, 5, OnDone::Failure}; + QTest::newRow("NestedParallelError3") + << TestData{storage, root3, log3, 5, OnDone::Failure}; + } + + { + const Group root { + parallelLimit(2), + Storage(storage), + Group { + groupSetup(1), + Group { + parallel, + createSuccessTask(1) + } + }, + Group { + groupSetup(2), + Group { + parallel, + createSuccessTask(2) + } + }, + Group { + groupSetup(3), + Group { + parallel, + createSuccessTask(3) + } + }, + Group { + groupSetup(4), + Group { + parallel, + createSuccessTask(4) + } + } + }; + const Log log { + {1, Handler::GroupSetup}, + {1, Handler::Setup}, + {2, Handler::GroupSetup}, + {2, Handler::Setup}, + {1, Handler::Done}, + {3, Handler::GroupSetup}, + {3, Handler::Setup}, + {2, Handler::Done}, + {4, Handler::GroupSetup}, + {4, Handler::Setup}, + {3, Handler::Done}, + {4, Handler::Done} + }; + QTest::newRow("DeeplyNestedParallel") << TestData{storage, root, log, 4, OnDone::Success}; + } + + { + const Group root { + parallelLimit(2), + Storage(storage), + Group { + groupSetup(1), + Group { createSuccessTask(1) } + }, + Group { + groupSetup(2), + Group { createSuccessTask(2) } + }, + Group { + groupSetup(3), + Group { createDynamicTask(3, TaskAction::StopWithDone) } + }, + Group { + groupSetup(4), + Group { createSuccessTask(4) } + }, + Group { + groupSetup(5), + Group { createSuccessTask(5) } + } + }; + const Log log { + {1, Handler::GroupSetup}, + {1, Handler::Setup}, + {2, Handler::GroupSetup}, + {2, Handler::Setup}, + {1, Handler::Done}, + {3, Handler::GroupSetup}, + {3, Handler::Setup}, + {4, Handler::GroupSetup}, + {4, Handler::Setup}, + {2, Handler::Done}, + {5, Handler::GroupSetup}, + {5, Handler::Setup}, + {4, Handler::Done}, + {5, Handler::Done} + }; + QTest::newRow("DeeplyNestedParallelDone") + << TestData{storage, root, log, 5, OnDone::Success}; + } + + { + const Group root { + parallelLimit(2), + Storage(storage), + Group { + groupSetup(1), + Group { createSuccessTask(1) } + }, + Group { + groupSetup(2), + Group { createSuccessTask(2) } + }, + Group { + groupSetup(3), + Group { createDynamicTask(3, TaskAction::StopWithError) } + }, + Group { + groupSetup(4), + Group { createSuccessTask(4) } + }, + Group { + groupSetup(5), + Group { createSuccessTask(5) } + } + }; + const Log log { + {1, Handler::GroupSetup}, + {1, Handler::Setup}, + {2, Handler::GroupSetup}, + {2, Handler::Setup}, + {1, Handler::Done}, + {3, Handler::GroupSetup}, + {3, Handler::Setup}, + {2, Handler::Error} + }; + QTest::newRow("DeeplyNestedParallelError") + << TestData{storage, root, log, 5, OnDone::Failure}; + } + + { + const Group root { + Storage(storage), + createSync(1), + createSync(2), + createSync(3), + createSync(4), + createSync(5) + }; + const Log log { + {1, Handler::Sync}, + {2, Handler::Sync}, + {3, Handler::Sync}, + {4, Handler::Sync}, + {5, Handler::Sync} + }; + QTest::newRow("SyncSequential") << TestData{storage, root, log, 0, OnDone::Success}; + } + + { + const Group root { + Storage(storage), + createSyncWithReturn(1, true), + createSyncWithReturn(2, true), + createSyncWithReturn(3, true), + createSyncWithReturn(4, true), + createSyncWithReturn(5, true) + }; + const Log log { + {1, Handler::Sync}, + {2, Handler::Sync}, + {3, Handler::Sync}, + {4, Handler::Sync}, + {5, Handler::Sync} + }; + QTest::newRow("SyncWithReturn") << TestData{storage, root, log, 0, OnDone::Success}; + } + + { + const Group root { + Storage(storage), + parallel, + createSync(1), + createSync(2), + createSync(3), + createSync(4), + createSync(5) + }; + const Log log { + {1, Handler::Sync}, + {2, Handler::Sync}, + {3, Handler::Sync}, + {4, Handler::Sync}, + {5, Handler::Sync} + }; + QTest::newRow("SyncParallel") << TestData{storage, root, log, 0, OnDone::Success}; + } + + { + const Group root { + Storage(storage), + parallel, + createSync(1), + createSync(2), + createSyncWithReturn(3, false), + createSync(4), + createSync(5) + }; + const Log log { + {1, Handler::Sync}, + {2, Handler::Sync}, + {3, Handler::Sync} + }; + QTest::newRow("SyncError") << TestData{storage, root, log, 0, OnDone::Failure}; + } + + { + const Group root { + Storage(storage), + createSync(1), + createSuccessTask(2), + createSync(3), + createSuccessTask(4), + createSync(5), + groupDone(0) + }; + const Log log { + {1, Handler::Sync}, + {2, Handler::Setup}, + {2, Handler::Done}, + {3, Handler::Sync}, + {4, Handler::Setup}, + {4, Handler::Done}, + {5, Handler::Sync}, + {0, Handler::GroupDone} + }; + QTest::newRow("SyncAndAsync") << TestData{storage, root, log, 2, OnDone::Success}; + } + + { + const Group root { + Storage(storage), + createSync(1), + createSuccessTask(2), + createSyncWithReturn(3, false), + createSuccessTask(4), + createSync(5), + groupError(0) + }; + const Log log { + {1, Handler::Sync}, + {2, Handler::Setup}, + {2, Handler::Done}, + {3, Handler::Sync}, + {0, Handler::GroupError} + }; + QTest::newRow("SyncAndAsyncError") << TestData{storage, root, log, 2, OnDone::Failure}; + } + + { + SingleBarrier barrier; + + // Test that barrier advance, triggered from inside the task described by + // setupBarrierAdvance, placed BEFORE the group containing the waitFor() element + // in the tree order, works OK in SEQUENTIAL mode. + const Group root1 { + Storage(storage), + Storage(barrier), + sequential, + createBarrierAdvance(storage, barrier, 1), + Group { + groupSetup(2), + WaitForBarrierTask(barrier), + createSuccessTask(2), + createSuccessTask(3) + } + }; + const Log log1 { + {1, Handler::Setup}, + {1, Handler::BarrierAdvance}, + {2, Handler::GroupSetup}, + {2, Handler::Setup}, + {2, Handler::Done}, + {3, Handler::Setup}, + {3, Handler::Done} + }; + + // Test that barrier advance, triggered from inside the task described by + // setupTaskWithCondition, placed BEFORE the group containing the waitFor() element + // in the tree order, works OK in PARALLEL mode. + const Group root2 { + Storage(storage), + Storage(barrier), + parallel, + createBarrierAdvance(storage, barrier, 1), + Group { + groupSetup(2), + WaitForBarrierTask(barrier), + createSuccessTask(2), + createSuccessTask(3) + } + }; + const Log log2 { + {1, Handler::Setup}, + {2, Handler::GroupSetup}, + {1, Handler::BarrierAdvance}, + {2, Handler::Setup}, + {2, Handler::Done}, + {3, Handler::Setup}, + {3, Handler::Done} + }; + + // Test that barrier advance, triggered from inside the task described by + // setupTaskWithCondition, placed AFTER the group containing the waitFor() element + // in the tree order, works OK in PARALLEL mode. + // + // Notice: This won't work in SEQUENTIAL mode, since the advancing barrier, placed after the + // group containing the WaitFor element, has no chance to be started in SEQUENTIAL mode, + // as in SEQUENTIAL mode the next task may only be started after the previous one finished. + // In this case, the previous task (Group element) awaits for the barrier's advance to + // come from the not yet started next task, causing a deadlock. + // The minimal requirement for this scenario to succeed is to set parallelLimit(2) or more. + const Group root3 { + Storage(storage), + Storage(barrier), + parallel, + Group { + groupSetup(2), + WaitForBarrierTask(barrier), + createSuccessTask(2), + createSuccessTask(3) + }, + createBarrierAdvance(storage, barrier, 1) + }; + const Log log3 { + {2, Handler::GroupSetup}, + {1, Handler::Setup}, + {1, Handler::BarrierAdvance}, + {2, Handler::Setup}, + {2, Handler::Done}, + {3, Handler::Setup}, + {3, Handler::Done} + }; + + // Test that barrier advance, triggered from inside the task described by + // setupBarrierAdvance, placed BEFORE the groups containing the waitFor() element + // in the tree order, wakes both waitFor tasks. + const Group root4 { + Storage(storage), + Storage(barrier), + parallel, + createBarrierAdvance(storage, barrier, 1), + Group { + groupSetup(2), + WaitForBarrierTask(barrier), + createSuccessTask(4) + }, + Group { + groupSetup(3), + WaitForBarrierTask(barrier), + createSuccessTask(5) + } + }; + const Log log4 { + {1, Handler::Setup}, + {2, Handler::GroupSetup}, + {3, Handler::GroupSetup}, + {1, Handler::BarrierAdvance}, + {4, Handler::Setup}, + {5, Handler::Setup}, + {4, Handler::Done}, + {5, Handler::Done} + }; + + // Test two separate single barriers. + + SingleBarrier barrier2; + + const Group root5 { + Storage(storage), + Storage(barrier), + Storage(barrier2), + parallel, + createBarrierAdvance(storage, barrier, 1), + createBarrierAdvance(storage, barrier2, 2), + Group { + Group { + parallel, + groupSetup(1), + WaitForBarrierTask(barrier), + WaitForBarrierTask(barrier2) + }, + createSuccessTask(3) + }, + }; + const Log log5 { + {1, Handler::Setup}, + {2, Handler::Setup}, + {1, Handler::GroupSetup}, + {1, Handler::BarrierAdvance}, + {2, Handler::BarrierAdvance}, + {3, Handler::Setup}, + {3, Handler::Done} + }; + + // Notice the different log order for each scenario. + QTest::newRow("BarrierSequential") + << TestData{storage, root1, log1, 4, OnDone::Success}; + QTest::newRow("BarrierParallelAdvanceFirst") + << TestData{storage, root2, log2, 4, OnDone::Success}; + QTest::newRow("BarrierParallelWaitForFirst") + << TestData{storage, root3, log3, 4, OnDone::Success}; + QTest::newRow("BarrierParallelMultiWaitFor") + << TestData{storage, root4, log4, 5, OnDone::Success}; + QTest::newRow("BarrierParallelTwoSingleBarriers") + << TestData{storage, root5, log5, 5, OnDone::Success}; + } + + { + MultiBarrier<2> barrier; + + // Test that multi barrier advance, triggered from inside the tasks described by + // setupBarrierAdvance, placed BEFORE the group containing the waitFor() element + // in the tree order, works OK in SEQUENTIAL mode. + const Group root1 { + Storage(storage), + Storage(barrier), + sequential, + createBarrierAdvance(storage, barrier, 1), + createBarrierAdvance(storage, barrier, 2), + Group { + groupSetup(2), + WaitForBarrierTask(barrier), + createSuccessTask(2), + createSuccessTask(3) + } + }; + const Log log1 { + {1, Handler::Setup}, + {1, Handler::BarrierAdvance}, + {2, Handler::Setup}, + {2, Handler::BarrierAdvance}, + {2, Handler::GroupSetup}, + {2, Handler::Setup}, + {2, Handler::Done}, + {3, Handler::Setup}, + {3, Handler::Done} + }; + + // Test that multi barrier advance, triggered from inside the tasks described by + // setupBarrierAdvance, placed BEFORE the group containing the waitFor() element + // in the tree order, works OK in PARALLEL mode. + const Group root2 { + Storage(storage), + Storage(barrier), + parallel, + createBarrierAdvance(storage, barrier, 1), + createBarrierAdvance(storage, barrier, 2), + Group { + groupSetup(2), + WaitForBarrierTask(barrier), + createSuccessTask(3), + createSuccessTask(4) + } + }; + const Log log2 { + {1, Handler::Setup}, + {2, Handler::Setup}, + {2, Handler::GroupSetup}, + {1, Handler::BarrierAdvance}, + {2, Handler::BarrierAdvance}, + {3, Handler::Setup}, + {3, Handler::Done}, + {4, Handler::Setup}, + {4, Handler::Done} + }; + + // Test that multi barrier advance, triggered from inside the tasks described by + // setupBarrierAdvance, placed AFTER the group containing the waitFor() element + // in the tree order, works OK in PARALLEL mode. + // + // Notice: This won't work in SEQUENTIAL mode, since the advancing barriers, placed after + // the group containing the WaitFor element, has no chance to be started in SEQUENTIAL mode, + // as in SEQUENTIAL mode the next task may only be started after the previous one finished. + // In this case, the previous task (Group element) awaits for the barrier's advance to + // come from the not yet started next task, causing a deadlock. + // The minimal requirement for this scenario to succeed is to set parallelLimit(2) or more. + const Group root3 { + Storage(storage), + Storage(barrier), + parallel, + Group { + groupSetup(2), + WaitForBarrierTask(barrier), + createSuccessTask(3), + createSuccessTask(4) + }, + createBarrierAdvance(storage, barrier, 1), + createBarrierAdvance(storage, barrier, 2) + }; + const Log log3 { + {2, Handler::GroupSetup}, + {1, Handler::Setup}, + {2, Handler::Setup}, + {1, Handler::BarrierAdvance}, + {2, Handler::BarrierAdvance}, + {3, Handler::Setup}, + {3, Handler::Done}, + {4, Handler::Setup}, + {4, Handler::Done} + }; + + // Test that multi barrier advance, triggered from inside the task described by + // setupBarrierAdvance, placed BEFORE the groups containing the waitFor() element + // in the tree order, wakes both waitFor tasks. + const Group root4 { + Storage(storage), + Storage(barrier), + parallel, + createBarrierAdvance(storage, barrier, 1), + createBarrierAdvance(storage, barrier, 2), + Group { + groupSetup(2), + WaitForBarrierTask(barrier), + createSuccessTask(3) + }, + Group { + groupSetup(3), + WaitForBarrierTask(barrier), + createSuccessTask(4) + } + }; + const Log log4 { + {1, Handler::Setup}, + {2, Handler::Setup}, + {2, Handler::GroupSetup}, + {3, Handler::GroupSetup}, + {1, Handler::BarrierAdvance}, + {2, Handler::BarrierAdvance}, + {3, Handler::Setup}, + {4, Handler::Setup}, + {3, Handler::Done}, + {4, Handler::Done} + }; + + // Notice the different log order for each scenario. + QTest::newRow("MultiBarrierSequential") + << TestData{storage, root1, log1, 5, OnDone::Success}; + QTest::newRow("MultiBarrierParallelAdvanceFirst") + << TestData{storage, root2, log2, 5, OnDone::Success}; + QTest::newRow("MultiBarrierParallelWaitForFirst") + << TestData{storage, root3, log3, 5, OnDone::Success}; + QTest::newRow("MultiBarrierParallelMultiWaitFor") + << TestData{storage, root4, log4, 6, OnDone::Success}; + } + + { + // Test CustomTask::withTimeout() combinations: + // 1. When the timeout has triggered or not. + // 2. With and without timeout handler. + const Group root1 { + Storage(storage), + TestTask(setupTask(1, 1000ms), setupDone(1), setupError(1)) + .withTimeout(1ms) + }; + const Log log1 { + {1, Handler::Setup}, + {1, Handler::Error} + }; + QTest::newRow("TaskErrorWithTimeout") << TestData{storage, root1, log1, 2, + OnDone::Failure}; + + const Group root2 { + Storage(storage), + TestTask(setupTask(1, 1000ms), setupDone(1), setupError(1)) + .withTimeout(1ms, setupTimeout(1)) + }; + const Log log2 { + {1, Handler::Setup}, + {1, Handler::Timeout}, + {1, Handler::Error} + }; + QTest::newRow("TaskErrorWithTimeoutHandler") << TestData{storage, root2, log2, 2, + OnDone::Failure}; + + const Group root3 { + Storage(storage), + TestTask(setupTask(1, 1ms), setupDone(1), setupError(1)) + .withTimeout(1000ms) + }; + const Log doneLog { + {1, Handler::Setup}, + {1, Handler::Done} + }; + QTest::newRow("TaskDoneWithTimeout") << TestData{storage, root3, doneLog, 2, + OnDone::Success}; + + const Group root4 { + Storage(storage), + TestTask(setupTask(1, 1ms), setupDone(1), setupError(1)) + .withTimeout(1000ms, setupTimeout(1)) + }; + QTest::newRow("TaskDoneWithTimeoutHandler") << TestData{storage, root4, doneLog, 2, + OnDone::Success}; + } + + { + // Test Group::withTimeout() combinations: + // 1. When the timeout has triggered or not. + // 2. With and without timeout handler. + const Group root1 { + Storage(storage), + Group { + createSuccessTask(1, 1000ms) + }.withTimeout(1ms) + }; + const Log log1 { + {1, Handler::Setup}, + {1, Handler::Error} + }; + QTest::newRow("GroupErrorWithTimeout") << TestData{storage, root1, log1, 2, + OnDone::Failure}; + + // Test Group::withTimeout(), passing custom handler + const Group root2 { + Storage(storage), + Group { + createSuccessTask(1, 1000ms) + }.withTimeout(1ms, setupTimeout(1)) + }; + const Log log2 { + {1, Handler::Setup}, + {1, Handler::Timeout}, + {1, Handler::Error} + }; + QTest::newRow("GroupErrorWithTimeoutHandler") << TestData{storage, root2, log2, 2, + OnDone::Failure}; + + const Group root3 { + Storage(storage), + Group { + createSuccessTask(1, 1ms) + }.withTimeout(1000ms) + }; + const Log doneLog { + {1, Handler::Setup}, + {1, Handler::Done} + }; + QTest::newRow("GroupDoneWithTimeout") << TestData{storage, root3, doneLog, 2, + OnDone::Success}; + + // Test Group::withTimeout(), passing custom handler + const Group root4 { + Storage(storage), + Group { + createSuccessTask(1, 1ms) + }.withTimeout(1000ms, setupTimeout(1)) + }; + QTest::newRow("GroupDoneWithTimeoutHandler") << TestData{storage, root4, doneLog, 2, + OnDone::Success}; + } +} + +void tst_Tasking::testTree() +{ + QFETCH(TestData, testData); + + TaskTree taskTree({testData.root.withTimeout(1000ms)}); + QCOMPARE(taskTree.taskCount() - 1, testData.taskCount); // -1 for the timeout task above + Log actualLog; + const auto collectLog = [&actualLog](CustomStorage *storage) { actualLog = storage->m_log; }; + taskTree.onStorageDone(testData.storage, collectLog); + const OnDone result = taskTree.runBlocking() ? OnDone::Success : OnDone::Failure; + QCOMPARE(taskTree.isRunning(), false); + + QCOMPARE(taskTree.progressValue(), taskTree.progressMaximum()); + QCOMPARE(actualLog, testData.expectedLog); + QCOMPARE(CustomStorage::instanceCount(), 0); + + QCOMPARE(result, testData.onDone); +} + +void tst_Tasking::storageOperators() +{ + TreeStorageBase storage1 = TreeStorage(); + TreeStorageBase storage2 = TreeStorage(); + TreeStorageBase storage3 = storage1; + + QVERIFY(storage1 == storage3); + QVERIFY(storage1 != storage2); + QVERIFY(storage2 != storage3); +} + +// This test checks whether a running task tree may be safely destructed. +// It also checks whether the destructor of a task tree deletes properly the storage created +// while starting the task tree. When running task tree is destructed, the storage done +// handler shouldn't be invoked. +void tst_Tasking::storageDestructor() +{ + bool setupCalled = false; + const auto setupHandler = [&setupCalled](CustomStorage *) { + setupCalled = true; + }; + bool doneCalled = false; + const auto doneHandler = [&doneCalled](CustomStorage *) { + doneCalled = true; + }; + QCOMPARE(CustomStorage::instanceCount(), 0); + { + TreeStorage storage; + const auto setupSleepingTask = [](TaskObject &taskObject) { + taskObject = 1000ms; + }; + const Group root { + Storage(storage), + TestTask(setupSleepingTask) + }; + + TaskTree taskTree(root); + QCOMPARE(CustomStorage::instanceCount(), 0); + taskTree.onStorageSetup(storage, setupHandler); + taskTree.onStorageDone(storage, doneHandler); + taskTree.start(); + QCOMPARE(CustomStorage::instanceCount(), 1); + } + QCOMPARE(CustomStorage::instanceCount(), 0); + QVERIFY(setupCalled); + QVERIFY(!doneCalled); +} + +QTEST_GUILESS_MAIN(tst_Tasking) + +#include "tst_tasking.moc" diff --git a/tests/auto/texteditor/CMakeLists.txt b/tests/auto/texteditor/CMakeLists.txt new file mode 100644 index 00000000000..23684431c43 --- /dev/null +++ b/tests/auto/texteditor/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(highlighter) diff --git a/tests/auto/texteditor/highlighter/CMakeLists.txt b/tests/auto/texteditor/highlighter/CMakeLists.txt new file mode 100644 index 00000000000..76370bf8255 --- /dev/null +++ b/tests/auto/texteditor/highlighter/CMakeLists.txt @@ -0,0 +1,4 @@ +add_qtc_test(tst_highlighter + DEPENDS TextEditor Utils Qt::Widgets + SOURCES tst_highlighter.cpp +) diff --git a/tests/auto/texteditor/highlighter/highlighter.qbs b/tests/auto/texteditor/highlighter/highlighter.qbs new file mode 100644 index 00000000000..641a71ef66d --- /dev/null +++ b/tests/auto/texteditor/highlighter/highlighter.qbs @@ -0,0 +1,15 @@ +import qbs + +QtcAutotest { + + Depends { name: "TextEditor" } + Depends { name: "Utils" } + Depends { name: "Qt.widgets" } // For QTextDocument & friends + + name: "Highlighter autotest" + + Group { + name: "Source Files" + files: "tst_highlighter.cpp" + } +} diff --git a/tests/auto/texteditor/highlighter/tst_highlighter.cpp b/tests/auto/texteditor/highlighter/tst_highlighter.cpp new file mode 100644 index 00000000000..e3f4e3dced6 --- /dev/null +++ b/tests/auto/texteditor/highlighter/tst_highlighter.cpp @@ -0,0 +1,352 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef WITH_TESTS +#define WITH_TESTS +#endif + +#include +#include +#include + +#include +#include +#include +#include + +namespace TextEditor { + +class tst_highlighter: public QObject +{ + Q_OBJECT + +private slots: + void init_testCase(); + void init(); + void test_setExtraAdditionalFormats(); + void test_clearExtraFormats(); + void test_incrementalApplyAdditionalFormats(); + void cleanup(); + +private: + QTextDocument *doc = nullptr; + SyntaxHighlighter *highlighter = nullptr; + FontSettings fontsettings; + QHash formatHash; + QTextCharFormat whitespaceFormat; +}; + +void tst_highlighter::init_testCase() +{ + QTextCharFormat c0; + c0.setFontItalic(true); + formatHash[0] = c0; + QTextCharFormat c1; + c1.setFontOverline(true); + formatHash[1] = c1; +} + +void tst_highlighter::init() +{ + const QString text = +R"(First +Second with spaces + +Last)"; + + + doc = new QTextDocument(); + doc->setPlainText(text); + + highlighter = new SyntaxHighlighter(doc, fontsettings); +} + +static const HighlightingResults &highlightingResults() +{ + static HighlightingResults results{HighlightingResult(), + HighlightingResult(1, 1, 5, 0), + HighlightingResult(2, 4, 7, 0), + HighlightingResult(2, 17, 5, 1), + HighlightingResult(4, 1, 8, 0), + HighlightingResult(6, 1, 8, 1)}; + return results; +} + +void tst_highlighter::test_setExtraAdditionalFormats() +{ + QCOMPARE(doc->blockCount(), 4); + + SemanticHighlighter::setExtraAdditionalFormats(highlighter, highlightingResults(), formatHash); + + for (auto block = doc->firstBlock(); block.isValid(); block = block.next()) { + QVERIFY(block.blockNumber() < 4); + QVERIFY(block.layout()); + auto formats = block.layout()->formats(); + switch (block.blockNumber()) { + case 0: // First + QCOMPARE(formats.size(), 1); + QCOMPARE(formats.at(0).format.fontItalic(), true); + QCOMPARE(formats.at(0).format.fontOverline(), false); + QCOMPARE(formats.at(0).start, 0); + QCOMPARE(formats.at(0).length, 5); + break; + case 1: // Second with spaces + QCOMPARE(formats.size(), 4); + QCOMPARE(formats.at(0).format.fontItalic(), false); + QCOMPARE(formats.at(0).format.fontOverline(), false); + QCOMPARE(formats.at(0).start, 6); + QCOMPARE(formats.at(0).length, 1); + + QCOMPARE(formats.at(1).format.fontItalic(), false); + QCOMPARE(formats.at(1).format.fontOverline(), false); + QCOMPARE(formats.at(1).start, 11); + QCOMPARE(formats.at(1).length, 1); + + QCOMPARE(formats.at(2).format.fontItalic(), true); + QCOMPARE(formats.at(2).format.fontOverline(), false); + QCOMPARE(formats.at(2).start, 3); + QCOMPARE(formats.at(2).length, 7); + + QCOMPARE(formats.at(3).format.fontItalic(), false); + QCOMPARE(formats.at(3).format.fontOverline(), true); + QCOMPARE(formats.at(3).start, 16); + QCOMPARE(formats.at(3).length, 3); + break; + case 2: // + QCOMPARE(formats.size(), 1); + QCOMPARE(formats.at(0).format.fontItalic(), false); + QCOMPARE(formats.at(0).format.fontOverline(), true); + QCOMPARE(formats.at(0).start, 0); + QCOMPARE(formats.at(0).length, 1); + break; + case 3: // Last + QCOMPARE(formats.size(), 2); + QCOMPARE(formats.at(0).format.fontItalic(), false); + QCOMPARE(formats.at(0).format.fontOverline(), true); + QCOMPARE(formats.at(0).start, 0); + QCOMPARE(formats.at(0).length, 1); + + QCOMPARE(formats.at(1).format.fontItalic(), true); + QCOMPARE(formats.at(1).format.fontOverline(), false); + QCOMPARE(formats.at(1).start, 0); + QCOMPARE(formats.at(1).length, 5); + break; + } + } +} + +void tst_highlighter::test_clearExtraFormats() +{ + QCOMPARE(doc->blockCount(), 4); + + SemanticHighlighter::setExtraAdditionalFormats(highlighter, highlightingResults(), formatHash); + + QTextBlock firstBlock = doc->findBlockByNumber(0); + QTextBlock spacesLineBlock = doc->findBlockByNumber(1); + QTextBlock emptyLineBlock = doc->findBlockByNumber(2); + QTextBlock lastBlock = doc->findBlockByNumber(3); + + highlighter->clearExtraFormats(emptyLineBlock); + QVERIFY(emptyLineBlock.layout()->formats().isEmpty()); + + highlighter->clearExtraFormats(spacesLineBlock); + + auto formats = spacesLineBlock.layout()->formats(); + // the spaces are not extra formats and should remain when clearing extra formats + QCOMPARE(formats.size(), 2); + QCOMPARE(formats.at(0).format.fontItalic(), false); + QCOMPARE(formats.at(0).format.fontOverline(), false); + QCOMPARE(formats.at(0).start, 6); + QCOMPARE(formats.at(0).length, 1); + + QCOMPARE(formats.at(1).format.fontItalic(), false); + QCOMPARE(formats.at(1).format.fontOverline(), false); + QCOMPARE(formats.at(1).start, 11); + QCOMPARE(formats.at(1).length, 1); + + // first and last should be untouched + formats = firstBlock.layout()->formats(); + QCOMPARE(formats.size(), 1); + QCOMPARE(formats.at(0).format.fontItalic(), true); + QCOMPARE(formats.at(0).format.fontOverline(), false); + QCOMPARE(formats.at(0).start, 0); + QCOMPARE(formats.at(0).length, 5); + + formats = lastBlock.layout()->formats(); + QCOMPARE(formats.size(), 2); + QCOMPARE(formats.at(0).format.fontItalic(), false); + QCOMPARE(formats.at(0).format.fontOverline(), true); + QCOMPARE(formats.at(0).start, 0); + QCOMPARE(formats.at(0).length, 1); + + QCOMPARE(formats.at(1).format.fontItalic(), true); + QCOMPARE(formats.at(1).format.fontOverline(), false); + QCOMPARE(formats.at(1).start, 0); + QCOMPARE(formats.at(1).length, 5); + + highlighter->clearAllExtraFormats(); + + QVERIFY(firstBlock.layout()->formats().isEmpty()); + QVERIFY(emptyLineBlock.layout()->formats().isEmpty()); + formats = spacesLineBlock.layout()->formats(); + + QCOMPARE(formats.size(), 2); + QCOMPARE(formats.at(0).format.fontItalic(), false); + QCOMPARE(formats.at(0).format.fontOverline(), false); + QCOMPARE(formats.at(0).start, 6); + QCOMPARE(formats.at(0).length, 1); + + QCOMPARE(formats.at(1).format.fontItalic(), false); + QCOMPARE(formats.at(1).format.fontOverline(), false); + QCOMPARE(formats.at(1).start, 11); + QCOMPARE(formats.at(1).length, 1); + + QVERIFY(lastBlock.layout()->formats().isEmpty()); +} + +void tst_highlighter::test_incrementalApplyAdditionalFormats() +{ + const HighlightingResults newResults{HighlightingResult(), + HighlightingResult(1, 1, 5, 0), + HighlightingResult(2, 4, 7, 0), + HighlightingResult(4, 1, 8, 0), + HighlightingResult(6, 1, 8, 1)}; + + QCOMPARE(doc->blockCount(), 4); + QTextBlock firstBlock = doc->findBlockByNumber(0); + QTextBlock spacesLineBlock = doc->findBlockByNumber(1); + QTextBlock emptyLineBlock = doc->findBlockByNumber(2); + QTextBlock lastBlock = doc->findBlockByNumber(3); + + QFutureInterface fiOld; + fiOld.reportResults(highlightingResults()); + SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighter, + fiOld.future(), + 2, + 0, + formatHash); + auto formats = firstBlock.layout()->formats(); + QVERIFY(formats.isEmpty()); + + SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighter, + fiOld.future(), + 0, + 2, + formatHash); + + formats = firstBlock.layout()->formats(); + QCOMPARE(formats.at(0).format.fontItalic(), true); + QCOMPARE(formats.at(0).format.fontOverline(), false); + QCOMPARE(formats.at(0).start, 0); + QCOMPARE(formats.at(0).length, 5); + + formats = spacesLineBlock.layout()->formats(); + QCOMPARE(formats.size(), 2); + QCOMPARE(formats.at(0).format.fontItalic(), false); + QCOMPARE(formats.at(0).format.fontOverline(), false); + QCOMPARE(formats.at(0).start, 6); + QCOMPARE(formats.at(0).length, 1); + + QCOMPARE(formats.at(1).format.fontItalic(), false); + QCOMPARE(formats.at(1).format.fontOverline(), false); + QCOMPARE(formats.at(1).start, 11); + QCOMPARE(formats.at(1).length, 1); + + SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighter, + fiOld.future(), + 3, + 6, + formatHash); + + formats = firstBlock.layout()->formats(); + QCOMPARE(formats.at(0).format.fontItalic(), true); + QCOMPARE(formats.at(0).format.fontOverline(), false); + QCOMPARE(formats.at(0).start, 0); + QCOMPARE(formats.at(0).length, 5); + + formats = lastBlock.layout()->formats(); + QCOMPARE(formats.size(), 2); + QCOMPARE(formats.at(0).format.fontItalic(), false); + QCOMPARE(formats.at(0).format.fontOverline(), true); + QCOMPARE(formats.at(0).start, 0); + QCOMPARE(formats.at(0).length, 1); + + QCOMPARE(formats.at(1).format.fontItalic(), true); + QCOMPARE(formats.at(1).format.fontOverline(), false); + QCOMPARE(formats.at(1).start, 0); + QCOMPARE(formats.at(1).length, 5); + + QFutureInterface fiNew; + fiNew.reportResults(newResults); + SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighter, + fiNew.future(), + 0, + 3, + formatHash); + + // should still contain the highlight from oldResults + formats = emptyLineBlock.layout()->formats(); + QCOMPARE(formats.size(), 1); + QCOMPARE(formats.at(0).format.fontItalic(), false); + QCOMPARE(formats.at(0).format.fontOverline(), true); + QCOMPARE(formats.at(0).start, 0); + QCOMPARE(formats.at(0).length, 1); + + SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighter, + fiNew.future(), + 3, + 4, + formatHash); + + // should have no results since the new results do not contain a highlight at that position + formats = emptyLineBlock.layout()->formats(); + QVERIFY(formats.isEmpty()); + + // QTCREATORBUG-29218 + highlighter->clearAllExtraFormats(); + const HighlightingResults bug29218Results{HighlightingResult(1, 1, 2, 0), + HighlightingResult(1, 3, 2, 1)}; + QFutureInterface fi29218; + fi29218.reportResults(bug29218Results); + formats = firstBlock.layout()->formats(); + QVERIFY(formats.isEmpty()); + SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighter, + fi29218.future(), + 0, + 1, + formatHash); + formats = firstBlock.layout()->formats(); + QCOMPARE(formats.size(), 1); + QCOMPARE(formats.at(0).format.fontItalic(), true); + QCOMPARE(formats.at(0).format.fontOverline(), false); + QCOMPARE(formats.at(0).start, 0); + QCOMPARE(formats.at(0).length, 2); + SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighter, + fi29218.future(), + 1, + 2, + formatHash); + formats = firstBlock.layout()->formats(); + QCOMPARE(formats.size(), 2); + QCOMPARE(formats.at(0).format.fontItalic(), true); + QCOMPARE(formats.at(0).format.fontOverline(), false); + QCOMPARE(formats.at(0).start, 0); + QCOMPARE(formats.at(0).length, 2); + QCOMPARE(formats.at(1).format.fontItalic(), false); + QCOMPARE(formats.at(1).format.fontOverline(), true); + QCOMPARE(formats.at(1).start, 2); + QCOMPARE(formats.at(1).length, 2); +} + +void tst_highlighter::cleanup() +{ + delete doc; + doc = nullptr; + highlighter = nullptr; +} + +} // namespace TextEditor + +QTEST_MAIN(TextEditor::tst_highlighter) + +#include "tst_highlighter.moc" diff --git a/tests/auto/texteditor/texteditor.qbs b/tests/auto/texteditor/texteditor.qbs new file mode 100644 index 00000000000..957bb448f16 --- /dev/null +++ b/tests/auto/texteditor/texteditor.qbs @@ -0,0 +1,6 @@ +import qbs + +Project { + name: "TextEditor autotests" + references: [ "highlighter/highlighter.qbs" ] +} diff --git a/tests/auto/tracing/timelineabstractrenderer/tst_timelineabstractrenderer.cpp b/tests/auto/tracing/timelineabstractrenderer/tst_timelineabstractrenderer.cpp index 0da3dd1f6a7..66ec7f67098 100644 --- a/tests/auto/tracing/timelineabstractrenderer/tst_timelineabstractrenderer.cpp +++ b/tests/auto/tracing/timelineabstractrenderer/tst_timelineabstractrenderer.cpp @@ -36,7 +36,7 @@ void tst_TimelineAbstractRenderer::privateCtor() void tst_TimelineAbstractRenderer::selectionLocked() { TimelineAbstractRenderer renderer; - QSignalSpy spy(&renderer, SIGNAL(selectionLockedChanged(bool))); + QSignalSpy spy(&renderer, &TimelineAbstractRenderer::selectionLockedChanged); QCOMPARE(spy.count(), 0); QVERIFY(renderer.selectionLocked()); renderer.setSelectionLocked(false); @@ -50,7 +50,7 @@ void tst_TimelineAbstractRenderer::selectionLocked() void tst_TimelineAbstractRenderer::selectedItem() { TimelineAbstractRenderer renderer; - QSignalSpy spy(&renderer, SIGNAL(selectedItemChanged(int))); + QSignalSpy spy(&renderer, &TimelineAbstractRenderer::selectedItemChanged); QCOMPARE(spy.count(), 0); QCOMPARE(renderer.selectedItem(), -1); renderer.setSelectedItem(12); @@ -62,7 +62,7 @@ void tst_TimelineAbstractRenderer::model() { TimelineAbstractRenderer renderer; TimelineModelAggregator aggregator; - QSignalSpy spy(&renderer, SIGNAL(modelChanged(TimelineModel*))); + QSignalSpy spy(&renderer, &TimelineAbstractRenderer::modelChanged); QVERIFY(!renderer.modelDirty()); QCOMPARE(spy.count(), 0); TimelineModel model(&aggregator); @@ -80,7 +80,7 @@ void tst_TimelineAbstractRenderer::model() void tst_TimelineAbstractRenderer::notes() { TimelineAbstractRenderer renderer; - QSignalSpy spy(&renderer, SIGNAL(notesChanged(TimelineNotesModel*))); + QSignalSpy spy(&renderer, &TimelineAbstractRenderer::notesChanged); QVERIFY(!renderer.notesDirty()); QCOMPARE(spy.count(), 0); TimelineNotesModel notes; @@ -98,7 +98,7 @@ void tst_TimelineAbstractRenderer::notes() void tst_TimelineAbstractRenderer::zoomer() { TimelineAbstractRenderer renderer; - QSignalSpy spy(&renderer, SIGNAL(zoomerChanged(TimelineZoomControl*))); + QSignalSpy spy(&renderer, &TimelineAbstractRenderer::zoomerChanged); QCOMPARE(spy.count(), 0); TimelineZoomControl zoomer; QCOMPARE(renderer.zoomer(), static_cast(0)); diff --git a/tests/auto/tracing/timelinemodel/tst_timelinemodel.cpp b/tests/auto/tracing/timelinemodel/tst_timelinemodel.cpp index 27b457ce3fd..559b827de2e 100644 --- a/tests/auto/tracing/timelinemodel/tst_timelinemodel.cpp +++ b/tests/auto/tracing/timelinemodel/tst_timelinemodel.cpp @@ -6,17 +6,19 @@ #include #include +using namespace Timeline; + static const int NumItems = 32; static const qint64 ItemDuration = 1 << 19; static const qint64 ItemSpacing = 1 << 20; -class DummyModel : public Timeline::TimelineModel +class DummyModel : public TimelineModel { Q_OBJECT friend class tst_TimelineModel; public: - DummyModel(Timeline::TimelineModelAggregator *parent); - DummyModel(QString displayName, Timeline::TimelineModelAggregator *parent); + DummyModel(TimelineModelAggregator *parent); + DummyModel(QString displayName, TimelineModelAggregator *parent); int expandedRow(int) const { return 2; } int collapsedRow(int) const { return 1; } @@ -57,15 +59,15 @@ private slots: void parentingOfEqualStarts(); private: - Timeline::TimelineModelAggregator aggregator; + TimelineModelAggregator aggregator; }; -DummyModel::DummyModel(Timeline::TimelineModelAggregator *parent) : - Timeline::TimelineModel(parent) +DummyModel::DummyModel(TimelineModelAggregator *parent) : + TimelineModel(parent) { } -DummyModel::DummyModel(QString displayName, Timeline::TimelineModelAggregator *parent) : +DummyModel::DummyModel(QString displayName, TimelineModelAggregator *parent) : TimelineModel(parent) { setDisplayName(displayName); @@ -90,7 +92,7 @@ void DummyModel::loadData() } tst_TimelineModel::tst_TimelineModel() : - DefaultRowHeight(Timeline::TimelineModel::defaultRowHeight()) + DefaultRowHeight(TimelineModel::defaultRowHeight()) { } @@ -147,7 +149,7 @@ void tst_TimelineModel::rowHeight() QCOMPARE(dummy.rowHeight(0), 100); QCOMPARE(dummy.rowHeight(1), 50); - QSignalSpy expandedSpy(&dummy, SIGNAL(expandedRowHeightChanged(int,int))); + QSignalSpy expandedSpy(&dummy, &TimelineModel::expandedRowHeightChanged); dummy.clear(); QCOMPARE(expandedSpy.count(), 1); } @@ -194,7 +196,7 @@ void tst_TimelineModel::height() DummyModel dummy(&aggregator); int heightAfterLastSignal = 0; int heightChangedSignals = 0; - connect(&dummy, &Timeline::TimelineModel::heightChanged, [&](){ + connect(&dummy, &TimelineModel::heightChanged, [&] { ++heightChangedSignals; heightAfterLastSignal = dummy.height(); }); @@ -237,7 +239,7 @@ void tst_TimelineModel::count() QCOMPARE(dummy.count(), 0); dummy.loadData(); QCOMPARE(dummy.count(), NumItems); - QSignalSpy emptySpy(&dummy, SIGNAL(contentChanged())); + QSignalSpy emptySpy(&dummy, &TimelineModel::contentChanged); dummy.clear(); QCOMPARE(emptySpy.count(), 1); QCOMPARE(dummy.count(), 0); @@ -283,7 +285,7 @@ void tst_TimelineModel::firstLast() void tst_TimelineModel::expand() { DummyModel dummy(&aggregator); - QSignalSpy spy(&dummy, SIGNAL(expandedChanged())); + QSignalSpy spy(&dummy, &TimelineModel::expandedChanged); QVERIFY(!dummy.expanded()); dummy.setExpanded(true); QVERIFY(dummy.expanded()); @@ -302,7 +304,7 @@ void tst_TimelineModel::expand() void tst_TimelineModel::hide() { DummyModel dummy(&aggregator); - QSignalSpy spy(&dummy, SIGNAL(hiddenChanged())); + QSignalSpy spy(&dummy, &TimelineModel::hiddenChanged); QVERIFY(!dummy.hidden()); dummy.setHidden(true); QVERIFY(dummy.hidden()); @@ -322,7 +324,7 @@ void tst_TimelineModel::displayName() { QLatin1String name("testest"); DummyModel dummy(name, &aggregator); - QSignalSpy spy(&dummy, SIGNAL(displayNameChanged())); + QSignalSpy spy(&dummy, &TimelineModel::displayNameChanged); QCOMPARE(dummy.displayName(), name); QCOMPARE(spy.count(), 0); dummy.setDisplayName(name); @@ -336,7 +338,7 @@ void tst_TimelineModel::displayName() void tst_TimelineModel::defaultValues() { - Timeline::TimelineModel dummy(&aggregator); + TimelineModel dummy(&aggregator); QCOMPARE(dummy.location(0), QVariantMap()); QCOMPARE(dummy.handlesTypeId(0), false); QCOMPARE(dummy.relativeHeight(0), 1.0); @@ -361,7 +363,7 @@ void tst_TimelineModel::row() void tst_TimelineModel::colorByHue() { - Timeline::TimelineModelAggregator aggregator; + TimelineModelAggregator aggregator; DummyModel dummy(&aggregator); QCOMPARE(dummy.colorByHue(10), QColor::fromHsl(10, 150, 166).rgb()); QCOMPARE(dummy.colorByHue(500), QColor::fromHsl(140, 150, 166).rgb()); @@ -410,7 +412,7 @@ void tst_TimelineModel::insertStartEnd() void tst_TimelineModel::rowCount() { DummyModel dummy(&aggregator); - QSignalSpy contentSpy(&dummy, SIGNAL(contentChanged())); + QSignalSpy contentSpy(&dummy, &TimelineModel::contentChanged); QCOMPARE(dummy.rowCount(), 1); dummy.setExpanded(true); QCOMPARE(dummy.rowCount(), 1); diff --git a/tests/auto/tracing/timelinemodelaggregator/tst_timelinemodelaggregator.cpp b/tests/auto/tracing/timelinemodelaggregator/tst_timelinemodelaggregator.cpp index 77c9c155e58..ac30ff0ad32 100644 --- a/tests/auto/tracing/timelinemodelaggregator/tst_timelinemodelaggregator.cpp +++ b/tests/auto/tracing/timelinemodelaggregator/tst_timelinemodelaggregator.cpp @@ -4,6 +4,8 @@ #include #include +using namespace Timeline; + class tst_TimelineModelAggregator : public QObject { Q_OBJECT @@ -14,9 +16,9 @@ private slots: void prevNext(); }; -class HeightTestModel : public Timeline::TimelineModel { +class HeightTestModel : public TimelineModel { public: - HeightTestModel(Timeline::TimelineModelAggregator *parent) : TimelineModel(parent) + HeightTestModel(TimelineModelAggregator *parent) : TimelineModel(parent) { insert(0, 1, 1); } @@ -24,11 +26,11 @@ public: void tst_TimelineModelAggregator::height() { - Timeline::TimelineModelAggregator aggregator; + TimelineModelAggregator aggregator; QCOMPARE(aggregator.height(), 0); - QSignalSpy heightSpy(&aggregator, SIGNAL(heightChanged())); - Timeline::TimelineModel *model = new Timeline::TimelineModel(&aggregator); + QSignalSpy heightSpy(&aggregator, &TimelineModelAggregator::heightChanged); + TimelineModel *model = new TimelineModel(&aggregator); aggregator.addModel(model); QCOMPARE(aggregator.height(), 0); QCOMPARE(heightSpy.count(), 0); @@ -42,15 +44,15 @@ void tst_TimelineModelAggregator::height() void tst_TimelineModelAggregator::addRemoveModel() { - Timeline::TimelineNotesModel notes; - Timeline::TimelineModelAggregator aggregator; + TimelineNotesModel notes; + TimelineModelAggregator aggregator; aggregator.setNotes(¬es); - QSignalSpy spy(&aggregator, SIGNAL(modelsChanged())); + QSignalSpy spy(&aggregator, &TimelineModelAggregator::modelsChanged); QCOMPARE(aggregator.notes(), ¬es); - Timeline::TimelineModel *model1 = new Timeline::TimelineModel(&aggregator); - Timeline::TimelineModel *model2 = new Timeline::TimelineModel(&aggregator); + TimelineModel *model1 = new TimelineModel(&aggregator); + TimelineModel *model2 = new TimelineModel(&aggregator); aggregator.addModel(model1); QCOMPARE(spy.count(), 1); QCOMPARE(aggregator.modelCount(), 1); @@ -75,10 +77,10 @@ void tst_TimelineModelAggregator::addRemoveModel() QCOMPARE(aggregator.modelCount(), 0); } -class PrevNextTestModel : public Timeline::TimelineModel +class PrevNextTestModel : public TimelineModel { public: - PrevNextTestModel(Timeline::TimelineModelAggregator *parent) : TimelineModel(parent) + PrevNextTestModel(TimelineModelAggregator *parent) : TimelineModel(parent) { for (int i = 0; i < 20; ++i) insert(i + modelId(), i * modelId(), modelId()); @@ -87,14 +89,14 @@ public: void tst_TimelineModelAggregator::prevNext() { - Timeline::TimelineModelAggregator aggregator; + TimelineModelAggregator aggregator; aggregator.generateModelId(); // start modelIds at 1 aggregator.addModel(new PrevNextTestModel(&aggregator)); aggregator.addModel(new PrevNextTestModel(&aggregator)); aggregator.addModel(new PrevNextTestModel(&aggregator)); // Add an empty model to trigger the special code paths that skip it - aggregator.addModel(new Timeline::TimelineModel(&aggregator)); + aggregator.addModel(new TimelineModel(&aggregator)); QLatin1String item("item"); QLatin1String model("model"); QVariantMap result; diff --git a/tests/auto/tracing/timelinenotesmodel/tst_timelinenotesmodel.cpp b/tests/auto/tracing/timelinenotesmodel/tst_timelinenotesmodel.cpp index d01db8f12e6..d00ba47154e 100644 --- a/tests/auto/tracing/timelinenotesmodel/tst_timelinenotesmodel.cpp +++ b/tests/auto/tracing/timelinenotesmodel/tst_timelinenotesmodel.cpp @@ -63,7 +63,7 @@ void tst_TimelineNotesModel::addRemove() TestModel model(&aggregator); notes.addTimelineModel(&model); - QSignalSpy spy(¬es, SIGNAL(changed(int,int,int))); + QSignalSpy spy(¬es, &TestNotesModel::changed); int id = notes.add(model.modelId(), 0, QLatin1String("xyz")); QCOMPARE(spy.count(), 1); QCOMPARE(notes.isModified(), true); @@ -129,7 +129,7 @@ void tst_TimelineNotesModel::modify() TestNotesModel notes; TestModel model(&aggregator); notes.addTimelineModel(&model); - QSignalSpy spy(¬es, SIGNAL(changed(int,int,int))); + QSignalSpy spy(¬es, &TestNotesModel::changed); int id = notes.add(model.modelId(), 0, QLatin1String("a")); QCOMPARE(spy.count(), 1); notes.resetModified(); diff --git a/tests/auto/tracing/timelinezoomcontrol/tst_timelinezoomcontrol.cpp b/tests/auto/tracing/timelinezoomcontrol/tst_timelinezoomcontrol.cpp index 4e00fc26d5d..3e1c936c0d5 100644 --- a/tests/auto/tracing/timelinezoomcontrol/tst_timelinezoomcontrol.cpp +++ b/tests/auto/tracing/timelinezoomcontrol/tst_timelinezoomcontrol.cpp @@ -5,11 +5,13 @@ #include #include +using namespace Timeline; + class tst_TimelineZoomControl : public QObject { Q_OBJECT private: - void verifyWindow(const Timeline::TimelineZoomControl &zoomControl); + void verifyWindow(const TimelineZoomControl &zoomControl); private slots: void trace(); @@ -18,7 +20,7 @@ private slots: void selection(); }; -void tst_TimelineZoomControl::verifyWindow(const Timeline::TimelineZoomControl &zoomControl) +void tst_TimelineZoomControl::verifyWindow(const TimelineZoomControl &zoomControl) { QVERIFY(zoomControl.windowStart() <= zoomControl.rangeStart()); QVERIFY(zoomControl.windowEnd() >= zoomControl.rangeEnd()); @@ -28,8 +30,8 @@ void tst_TimelineZoomControl::verifyWindow(const Timeline::TimelineZoomControl & void tst_TimelineZoomControl::trace() { - Timeline::TimelineZoomControl zoomControl; - QSignalSpy spy(&zoomControl, SIGNAL(traceChanged(qint64,qint64))); + TimelineZoomControl zoomControl; + QSignalSpy spy(&zoomControl, &TimelineZoomControl::traceChanged); QCOMPARE(zoomControl.traceStart(), -1); QCOMPARE(zoomControl.traceEnd(), -1); QCOMPARE(zoomControl.traceDuration(), 0); @@ -49,18 +51,18 @@ void tst_TimelineZoomControl::trace() void tst_TimelineZoomControl::window() { - Timeline::TimelineZoomControl zoomControl; + TimelineZoomControl zoomControl; QTimer timer; timer.setSingleShot(true); - connect(&timer, &QTimer::timeout, [&](){ + connect(&timer, &QTimer::timeout, [&] { QVERIFY(zoomControl.windowLocked()); zoomControl.setWindowLocked(false); }); int numWindowChanges = 0; - connect(&zoomControl, &Timeline::TimelineZoomControl::windowChanged, + connect(&zoomControl, &TimelineZoomControl::windowChanged, [&](qint64, qint64) { verifyWindow(zoomControl); @@ -98,8 +100,7 @@ void tst_TimelineZoomControl::window() zoomControl.setRange(152000, 152005); // move right QMetaObject::Connection connection = connect( - &zoomControl, &Timeline::TimelineZoomControl::windowMovingChanged, - [&](bool moving) { + &zoomControl, &TimelineZoomControl::windowMovingChanged, [&](bool moving) { if (moving) return; @@ -129,7 +130,7 @@ void tst_TimelineZoomControl::window() disconnect(connection); bool stopDetected = false; - connect(&zoomControl, &Timeline::TimelineZoomControl::windowMovingChanged, [&](bool moving) { + connect(&zoomControl, &TimelineZoomControl::windowMovingChanged, [&](bool moving) { if (!moving) { QCOMPARE(stopDetected, false); stopDetected = true; @@ -148,8 +149,8 @@ void tst_TimelineZoomControl::window() void tst_TimelineZoomControl::range() { - Timeline::TimelineZoomControl zoomControl; - QSignalSpy spy(&zoomControl, SIGNAL(rangeChanged(qint64,qint64))); + TimelineZoomControl zoomControl; + QSignalSpy spy(&zoomControl, &TimelineZoomControl::rangeChanged); QCOMPARE(zoomControl.rangeStart(), -1); QCOMPARE(zoomControl.rangeEnd(), -1); QCOMPARE(zoomControl.rangeDuration(), 0); @@ -175,8 +176,8 @@ void tst_TimelineZoomControl::range() void tst_TimelineZoomControl::selection() { - Timeline::TimelineZoomControl zoomControl; - QSignalSpy spy(&zoomControl, SIGNAL(selectionChanged(qint64,qint64))); + TimelineZoomControl zoomControl; + QSignalSpy spy(&zoomControl, &TimelineZoomControl::selectionChanged); QCOMPARE(zoomControl.selectionStart(), -1); QCOMPARE(zoomControl.selectionEnd(), -1); QCOMPARE(zoomControl.selectionDuration(), 0); diff --git a/tests/auto/utils/CMakeLists.txt b/tests/auto/utils/CMakeLists.txt index 29548a5680f..e78cb3380cc 100644 --- a/tests/auto/utils/CMakeLists.txt +++ b/tests/auto/utils/CMakeLists.txt @@ -1,8 +1,9 @@ add_subdirectory(ansiescapecodehandler) -add_subdirectory(asynctask) +add_subdirectory(async) add_subdirectory(commandline) add_subdirectory(deviceshell) add_subdirectory(expected) +add_subdirectory(filepath) add_subdirectory(fileutils) add_subdirectory(fsengine) add_subdirectory(fuzzymatcher) @@ -10,9 +11,10 @@ add_subdirectory(indexedcontainerproxyconstiterator) add_subdirectory(mathutils) add_subdirectory(multicursor) add_subdirectory(persistentsettings) -add_subdirectory(qtcprocess) +add_subdirectory(process) add_subdirectory(settings) add_subdirectory(stringutils) -add_subdirectory(tasktree) add_subdirectory(templateengine) add_subdirectory(treemodel) +add_subdirectory(text) +add_subdirectory(unixdevicefileaccess) diff --git a/tests/auto/utils/async/CMakeLists.txt b/tests/auto/utils/async/CMakeLists.txt new file mode 100644 index 00000000000..0e6de32f567 --- /dev/null +++ b/tests/auto/utils/async/CMakeLists.txt @@ -0,0 +1,4 @@ +add_qtc_test(tst_utils_async + DEPENDS Utils + SOURCES tst_async.cpp +) diff --git a/tests/auto/utils/async/async.qbs b/tests/auto/utils/async/async.qbs new file mode 100644 index 00000000000..696b0959a5b --- /dev/null +++ b/tests/auto/utils/async/async.qbs @@ -0,0 +1,7 @@ +import qbs + +QtcAutotest { + name: "Async autotest" + Depends { name: "Utils" } + files: "tst_async.cpp" +} diff --git a/tests/auto/utils/async/tst_async.cpp b/tests/auto/utils/async/tst_async.cpp new file mode 100644 index 00000000000..5d4a81dc961 --- /dev/null +++ b/tests/auto/utils/async/tst_async.cpp @@ -0,0 +1,587 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include +#include + +#include + +using namespace Utils; + +using namespace std::chrono_literals; + +class tst_Async : public QObject +{ + Q_OBJECT + +private slots: + void runAsync(); + void crefFunction(); + void onResultReady(); + void futureSynchonizer(); + void taskTree(); + void mapReduce_data(); + void mapReduce(); +private: + QThreadPool m_threadPool; +}; + +void report3(QPromise &promise) +{ + promise.addResult(0); + promise.addResult(2); + promise.addResult(1); +} + +void reportN(QPromise &promise, int n) +{ + for (int i = 0; i < n; ++i) + promise.addResult(0); +} + +void reportString1(QPromise &promise, const QString &s) +{ + promise.addResult(s); +} + +void reportString2(QPromise &promise, QString s) +{ + promise.addResult(s); +} + +class Callable { +public: + void operator()(QPromise &promise, int n) const + { + for (int i = 0; i < n; ++i) + promise.addResult(0); + } +}; + +class MyObject { +public: + static void staticMember0(QPromise &promise) + { + promise.addResult(0); + promise.addResult(2); + promise.addResult(1); + } + + static void staticMember1(QPromise &promise, int n) + { + for (int i = 0; i < n; ++i) + promise.addResult(0); + } + + void member0(QPromise &promise) const + { + promise.addResult(0); + promise.addResult(2); + promise.addResult(1); + } + + void member1(QPromise &promise, int n) const + { + for (int i = 0; i < n; ++i) + promise.addResult(0); + } + + void memberString1(QPromise &promise, const QString &s) const + { + promise.addResult(s); + } + + void memberString2(QPromise &promise, QString s) const + { + promise.addResult(s); + } + + void nonConstMember(QPromise &promise) + { + promise.addResult(0); + promise.addResult(2); + promise.addResult(1); + } +}; + +template +struct FutureArgType; + +template +struct FutureArgType> +{ + using Type = Arg; +}; + +template +struct ConcurrentResultType; + +template +struct ConcurrentResultType +{ + using Type = typename FutureArgType(), std::declval()...))>::Type; +}; + +template ::Type> +std::shared_ptr> createAsyncTask(Function &&function, Args &&...args) +{ + auto asyncTask = std::make_shared>(); + asyncTask->setConcurrentCallData(std::forward(function), std::forward(args)...); + asyncTask->start(); + return asyncTask; +} + +void tst_Async::runAsync() +{ + // free function pointer + QCOMPARE(createAsyncTask(&report3)->results(), + QList({0, 2, 1})); + QCOMPARE(Utils::asyncRun(&report3).results(), + QList({0, 2, 1})); + QCOMPARE(createAsyncTask(report3)->results(), + QList({0, 2, 1})); + QCOMPARE(Utils::asyncRun(report3).results(), + QList({0, 2, 1})); + + QCOMPARE(createAsyncTask(reportN, 4)->results(), + QList({0, 0, 0, 0})); + QCOMPARE(Utils::asyncRun(reportN, 4).results(), + QList({0, 0, 0, 0})); + QCOMPARE(createAsyncTask(reportN, 2)->results(), + QList({0, 0})); + QCOMPARE(Utils::asyncRun(reportN, 2).results(), + QList({0, 0})); + + QString s = QLatin1String("string"); + const QString &crs = QLatin1String("cr string"); + const QString cs = QLatin1String("c string"); + + QCOMPARE(createAsyncTask(reportString1, s)->results(), + QList({s})); + QCOMPARE(Utils::asyncRun(reportString1, s).results(), + QList({s})); + QCOMPARE(createAsyncTask(reportString1, crs)->results(), + QList({crs})); + QCOMPARE(Utils::asyncRun(reportString1, crs).results(), + QList({crs})); + QCOMPARE(createAsyncTask(reportString1, cs)->results(), + QList({cs})); + QCOMPARE(Utils::asyncRun(reportString1, cs).results(), + QList({cs})); + QCOMPARE(createAsyncTask(reportString1, QString(QLatin1String("rvalue")))->results(), + QList({QString(QLatin1String("rvalue"))})); + QCOMPARE(Utils::asyncRun(reportString1, QString(QLatin1String("rvalue"))).results(), + QList({QString(QLatin1String("rvalue"))})); + + QCOMPARE(createAsyncTask(reportString2, s)->results(), + QList({s})); + QCOMPARE(Utils::asyncRun(reportString2, s).results(), + QList({s})); + QCOMPARE(createAsyncTask(reportString2, crs)->results(), + QList({crs})); + QCOMPARE(Utils::asyncRun(reportString2, crs).results(), + QList({crs})); + QCOMPARE(createAsyncTask(reportString2, cs)->results(), + QList({cs})); + QCOMPARE(Utils::asyncRun(reportString2, cs).results(), + QList({cs})); + QCOMPARE(createAsyncTask(reportString2, QString(QLatin1String("rvalue")))->results(), + QList({QString(QLatin1String("rvalue"))})); + QCOMPARE(Utils::asyncRun(reportString2, QString(QLatin1String("rvalue"))).results(), + QList({QString(QLatin1String("rvalue"))})); + + // lambda + QCOMPARE(createAsyncTask([](QPromise &promise, int n) { + for (int i = 0; i < n; ++i) + promise.addResult(0); + }, 3)->results(), + QList({0, 0, 0})); + QCOMPARE(Utils::asyncRun([](QPromise &promise, int n) { + for (int i = 0; i < n; ++i) + promise.addResult(0); + }, 3).results(), + QList({0, 0, 0})); + + // std::function + const std::function&,int)> fun = [](QPromise &promise, int n) { + for (int i = 0; i < n; ++i) + promise.addResult(0); + }; + QCOMPARE(createAsyncTask(fun, 2)->results(), + QList({0, 0})); + QCOMPARE(Utils::asyncRun(fun, 2).results(), + QList({0, 0})); + + // operator() + QCOMPARE(createAsyncTask(Callable(), 3)->results(), + QList({0, 0, 0})); + QCOMPARE(Utils::asyncRun(Callable(), 3).results(), + QList({0, 0, 0})); + const Callable c{}; + QCOMPARE(createAsyncTask(c, 2)->results(), + QList({0, 0})); + QCOMPARE(Utils::asyncRun(c, 2).results(), + QList({0, 0})); + + // static member functions + QCOMPARE(createAsyncTask(&MyObject::staticMember0)->results(), + QList({0, 2, 1})); + QCOMPARE(Utils::asyncRun(&MyObject::staticMember0).results(), + QList({0, 2, 1})); + QCOMPARE(createAsyncTask(&MyObject::staticMember1, 2)->results(), + QList({0, 0})); + QCOMPARE(Utils::asyncRun(&MyObject::staticMember1, 2).results(), + QList({0, 0})); + + // member functions + const MyObject obj{}; + QCOMPARE(createAsyncTask(&MyObject::member0, &obj)->results(), + QList({0, 2, 1})); + QCOMPARE(Utils::asyncRun(&MyObject::member0, &obj).results(), + QList({0, 2, 1})); + QCOMPARE(createAsyncTask(&MyObject::member1, &obj, 4)->results(), + QList({0, 0, 0, 0})); + QCOMPARE(Utils::asyncRun(&MyObject::member1, &obj, 4).results(), + QList({0, 0, 0, 0})); + QCOMPARE(createAsyncTask(&MyObject::memberString1, &obj, s)->results(), + QList({s})); + QCOMPARE(Utils::asyncRun(&MyObject::memberString1, &obj, s).results(), + QList({s})); + QCOMPARE(createAsyncTask(&MyObject::memberString1, &obj, crs)->results(), + QList({crs})); + QCOMPARE(Utils::asyncRun(&MyObject::memberString1, &obj, crs).results(), + QList({crs})); + QCOMPARE(createAsyncTask(&MyObject::memberString1, &obj, cs)->results(), + QList({cs})); + QCOMPARE(Utils::asyncRun(&MyObject::memberString1, &obj, cs).results(), + QList({cs})); + QCOMPARE(createAsyncTask(&MyObject::memberString1, &obj, QString(QLatin1String("rvalue")))->results(), + QList({QString(QLatin1String("rvalue"))})); + QCOMPARE(Utils::asyncRun(&MyObject::memberString1, &obj, QString(QLatin1String("rvalue"))).results(), + QList({QString(QLatin1String("rvalue"))})); + QCOMPARE(createAsyncTask(&MyObject::memberString2, &obj, s)->results(), + QList({s})); + QCOMPARE(Utils::asyncRun(&MyObject::memberString2, &obj, s).results(), + QList({s})); + QCOMPARE(createAsyncTask(&MyObject::memberString2, &obj, crs)->results(), + QList({crs})); + QCOMPARE(Utils::asyncRun(&MyObject::memberString2, &obj, crs).results(), + QList({crs})); + QCOMPARE(createAsyncTask(&MyObject::memberString2, &obj, cs)->results(), + QList({cs})); + QCOMPARE(Utils::asyncRun(&MyObject::memberString2, &obj, cs).results(), + QList({cs})); + QCOMPARE(createAsyncTask(&MyObject::memberString2, &obj, QString(QLatin1String("rvalue")))->results(), + QList({QString(QLatin1String("rvalue"))})); + QCOMPARE(Utils::asyncRun(&MyObject::memberString2, &obj, QString(QLatin1String("rvalue"))).results(), + QList({QString(QLatin1String("rvalue"))})); + MyObject nonConstObj{}; + QCOMPARE(createAsyncTask(&MyObject::nonConstMember, &nonConstObj)->results(), + QList({0, 2, 1})); + QCOMPARE(Utils::asyncRun(&MyObject::nonConstMember, &nonConstObj).results(), + QList({0, 2, 1})); +} + +void tst_Async::crefFunction() +{ + // free function pointer with promise + auto fun = &report3; + QCOMPARE(createAsyncTask(std::cref(fun))->results(), + QList({0, 2, 1})); + QCOMPARE(Utils::asyncRun(std::cref(fun)).results(), + QList({0, 2, 1})); + + // lambda with promise + auto lambda = [](QPromise &promise, int n) { + for (int i = 0; i < n; ++i) + promise.addResult(0); + }; + QCOMPARE(createAsyncTask(std::cref(lambda), 3)->results(), + QList({0, 0, 0})); + QCOMPARE(Utils::asyncRun(std::cref(lambda), 3).results(), + QList({0, 0, 0})); + + // std::function with promise + const std::function&,int)> funObj = [](QPromise &promise, int n) { + for (int i = 0; i < n; ++i) + promise.addResult(0); + }; + QCOMPARE(createAsyncTask(std::cref(funObj), 2)->results(), + QList({0, 0})); + QCOMPARE(Utils::asyncRun(std::cref(funObj), 2).results(), + QList({0, 0})); + + // callable with promise + const Callable c{}; + QCOMPARE(createAsyncTask(std::cref(c), 2)->results(), + QList({0, 0})); + QCOMPARE(Utils::asyncRun(std::cref(c), 2).results(), + QList({0, 0})); + + // member functions with promise + auto member = &MyObject::member0; + const MyObject obj{}; + QCOMPARE(createAsyncTask(std::cref(member), &obj)->results(), + QList({0, 2, 1})); + QCOMPARE(Utils::asyncRun(std::cref(member), &obj).results(), + QList({0, 2, 1})); +} + +class ObjWithProperty : public QObject +{ + Q_OBJECT + +public slots: + void setValue(const QString &s) + { + value = s; + } + +public: + QString value; +}; + +void tst_Async::onResultReady() +{ + { // lambda + QObject context; + QFuture f = Utils::asyncRun([](QPromise &fi) { + fi.addResult("Hi"); + fi.addResult("there"); + }); + int count = 0; + QString res; + Utils::onResultReady(f, &context, [&count, &res](const QString &s) { + ++count; + res = s; + }); + f.waitForFinished(); + QCoreApplication::processEvents(); + QCOMPARE(count, 2); + QCOMPARE(res, QString("there")); + } + { // lambda with guard + QFuture f = Utils::asyncRun([](QPromise &fi) { + fi.addResult("Hi"); + fi.addResult("there"); + }); + int count = 0; + ObjWithProperty obj; + Utils::onResultReady(f, &obj, [&count, &obj](const QString &s) { + ++count; + obj.setValue(s); + }); + f.waitForFinished(); + QCoreApplication::processEvents(); + QCOMPARE(count, 2); + QCOMPARE(obj.value, QString("there")); + } + { // member + QFuture f = Utils::asyncRun([] { return QString("Hi"); }); + ObjWithProperty obj; + Utils::onResultReady(f, &obj, &ObjWithProperty::setValue); + f.waitForFinished(); + QCoreApplication::processEvents(); + QCOMPARE(obj.value, QString("Hi")); + } +} + +void tst_Async::futureSynchonizer() +{ + auto lambda = [](QPromise &promise) { + while (true) { + if (promise.isCanceled()) { + promise.future().cancel(); + promise.finish(); + return; + } + QThread::msleep(100); + } + }; + + FutureSynchronizer synchronizer; + synchronizer.setCancelOnWait(false); + { + Async task; + task.setConcurrentCallData(lambda); + task.setFutureSynchronizer(&synchronizer); + task.start(); + QThread::msleep(10); + // We assume here that worker thread will still work for about 90 ms. + QVERIFY(!task.isCanceled()); + QVERIFY(!task.isDone()); + } + synchronizer.flushFinishedFutures(); + QVERIFY(!synchronizer.isEmpty()); + // The destructor of synchronizer should wait for about 90 ms for worker thread to be canceled +} + +void multiplyBy2(QPromise &promise, int input) { promise.addResult(input * 2); } + +void tst_Async::taskTree() +{ + using namespace Tasking; + + int value = 1; + + const auto setupIntAsync = [&](Async &task) { + task.setConcurrentCallData(multiplyBy2, value); + }; + const auto handleIntAsync = [&](const Async &task) { + value = task.result(); + }; + + const Group root { + AsyncTask(setupIntAsync, handleIntAsync), + AsyncTask(setupIntAsync, handleIntAsync), + AsyncTask(setupIntAsync, handleIntAsync), + AsyncTask(setupIntAsync, handleIntAsync), + }; + + + QVERIFY(TaskTree::runBlocking(root, 1000ms)); + QCOMPARE(value, 16); +} + +static int returnxx(int x) +{ + return x * x; +} + +static void returnxxWithPromise(QPromise &promise, int x) +{ + promise.addResult(x * x); +} + +static double s_sum = 0; +static QList s_results; + +void tst_Async::mapReduce_data() +{ + using namespace Tasking; + + QTest::addColumn("root"); + QTest::addColumn("sum"); + QTest::addColumn>("results"); + + const auto initTree = [] { + s_sum = 0; + s_results.append(s_sum); + }; + const auto setupAsync = [](Async &task, int input) { + task.setConcurrentCallData(returnxx, input); + }; + const auto setupAsyncWithFI = [](Async &task, int input) { + task.setConcurrentCallData(returnxxWithPromise, input); + }; + const auto setupAsyncWithTP = [this](Async &task, int input) { + task.setConcurrentCallData(returnxx, input); + task.setThreadPool(&m_threadPool); + }; + const auto handleAsync = [](const Async &task) { + s_sum += task.result(); + s_results.append(task.result()); + }; + const auto handleTreeParallel = [] { + s_sum /= 2; + s_results.append(s_sum); + Utils::sort(s_results); // mapping order is undefined + }; + const auto handleTreeSequential = [] { + s_sum /= 2; + s_results.append(s_sum); + }; + + using namespace Tasking; + using namespace std::placeholders; + + using SetupHandler = std::function &task, int input)>; + using DoneHandler = std::function; + + const auto createTask = [=](const TaskItem &executeMode, + const SetupHandler &setupHandler, + const DoneHandler &doneHandler) { + return Group { + executeMode, + onGroupSetup(initTree), + AsyncTask(std::bind(setupHandler, _1, 1), handleAsync), + AsyncTask(std::bind(setupHandler, _1, 2), handleAsync), + AsyncTask(std::bind(setupHandler, _1, 3), handleAsync), + AsyncTask(std::bind(setupHandler, _1, 4), handleAsync), + AsyncTask(std::bind(setupHandler, _1, 5), handleAsync), + onGroupDone(doneHandler) + }; + }; + + const Group parallelRoot = createTask(parallel, setupAsync, handleTreeParallel); + const Group parallelRootWithFI = createTask(parallel, setupAsyncWithFI, handleTreeParallel); + const Group parallelRootWithTP = createTask(parallel, setupAsyncWithTP, handleTreeParallel); + const Group sequentialRoot = createTask(sequential, setupAsync, handleTreeSequential); + const Group sequentialRootWithFI = createTask(sequential, setupAsyncWithFI, handleTreeSequential); + const Group sequentialRootWithTP = createTask(sequential, setupAsyncWithTP, handleTreeSequential); + + const double defaultSum = 27.5; + const QList defaultResult{0., 1., 4., 9., 16., 25., 27.5}; + + QTest::newRow("Parallel") << parallelRoot << defaultSum << defaultResult; + QTest::newRow("ParallelWithFutureInterface") << parallelRootWithFI << defaultSum << defaultResult; + QTest::newRow("ParallelWithThreadPool") << parallelRootWithTP << defaultSum << defaultResult; + QTest::newRow("Sequential") << sequentialRoot << defaultSum << defaultResult; + QTest::newRow("SequentialWithFutureInterface") << sequentialRootWithFI << defaultSum << defaultResult; + QTest::newRow("SequentialWithThreadPool") << sequentialRootWithTP << defaultSum << defaultResult; + + const auto setupSimpleAsync = [](Async &task, int input) { + task.setConcurrentCallData([](int input) { return input * 2; }, input); + }; + const auto handleSimpleAsync = [](const Async &task) { + s_sum += task.result() / 4.; + s_results.append(s_sum); + }; + const Group simpleRoot = { + sequential, + onGroupSetup([] { s_sum = 0; }), + AsyncTask(std::bind(setupSimpleAsync, _1, 1), handleSimpleAsync), + AsyncTask(std::bind(setupSimpleAsync, _1, 2), handleSimpleAsync), + AsyncTask(std::bind(setupSimpleAsync, _1, 3), handleSimpleAsync) + }; + QTest::newRow("Simple") << simpleRoot << 3.0 << QList({.5, 1.5, 3.}); + + const auto setupStringAsync = [](Async &task, const QString &input) { + task.setConcurrentCallData([](const QString &input) -> int { return input.size(); }, input); + }; + const auto handleStringAsync = [](const Async &task) { + s_sum /= task.result(); + }; + const Group stringRoot = { + parallel, + onGroupSetup([] { s_sum = 90.0; }), + AsyncTask(std::bind(setupStringAsync, _1, "blubb"), handleStringAsync), + AsyncTask(std::bind(setupStringAsync, _1, "foo"), handleStringAsync), + AsyncTask(std::bind(setupStringAsync, _1, "blah"), handleStringAsync) + }; + QTest::newRow("String") << stringRoot << 1.5 << QList({}); +} + +void tst_Async::mapReduce() +{ + QThreadPool pool; + + s_sum = 0; + s_results.clear(); + + using namespace Tasking; + + QFETCH(Group, root); + QFETCH(double, sum); + QFETCH(QList, results); + + QVERIFY(TaskTree::runBlocking(root, 1000ms)); + QCOMPARE(s_results, results); + QCOMPARE(s_sum, sum); +} + +QTEST_GUILESS_MAIN(tst_Async) + +#include "tst_async.moc" diff --git a/tests/auto/utils/asynctask/CMakeLists.txt b/tests/auto/utils/asynctask/CMakeLists.txt deleted file mode 100644 index 8b234dbb024..00000000000 --- a/tests/auto/utils/asynctask/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -add_qtc_test(tst_utils_asynctask - DEPENDS Utils - SOURCES tst_asynctask.cpp -) diff --git a/tests/auto/utils/asynctask/asynctask.qbs b/tests/auto/utils/asynctask/asynctask.qbs deleted file mode 100644 index 8f478147e8d..00000000000 --- a/tests/auto/utils/asynctask/asynctask.qbs +++ /dev/null @@ -1,7 +0,0 @@ -import qbs - -QtcAutotest { - name: "AsyncTask autotest" - Depends { name: "Utils" } - files: "tst_asynctask.cpp" -} diff --git a/tests/auto/utils/asynctask/tst_asynctask.cpp b/tests/auto/utils/asynctask/tst_asynctask.cpp deleted file mode 100644 index 83b3c82aed9..00000000000 --- a/tests/auto/utils/asynctask/tst_asynctask.cpp +++ /dev/null @@ -1,437 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "utils/asynctask.h" - -#include "utils/algorithm.h" - -#include - -using namespace Utils; - -class tst_AsyncTask : public QObject -{ - Q_OBJECT - -private slots: - void runAsync(); - void crefFunction(); - void futureSynchonizer(); - void taskTree(); - void mapReduce_data(); - void mapReduce(); -private: - QThreadPool m_threadPool; -}; - -void report3(QFutureInterface &fi) -{ - fi.reportResults({0, 2, 1}); -} - -void reportN(QFutureInterface &fi, int n) -{ - fi.reportResults(QVector(n, 0)); -} - -void reportString1(QFutureInterface &fi, const QString &s) -{ - fi.reportResult(s); -} - -void reportString2(QFutureInterface &fi, QString s) -{ - fi.reportResult(s); -} - -class Callable { -public: - void operator()(QFutureInterface &fi, int n) const - { - fi.reportResults(QVector(n, 0)); - } -}; - -class MyObject { -public: - static void staticMember0(QFutureInterface &fi) - { - fi.reportResults({0, 2, 1}); - } - - static void staticMember1(QFutureInterface &fi, int n) - { - fi.reportResults(QVector(n, 0)); - } - - void member0(QFutureInterface &fi) const - { - fi.reportResults({0, 2, 1}); - } - - void member1(QFutureInterface &fi, int n) const - { - fi.reportResults(QVector(n, 0)); - } - - void memberString1(QFutureInterface &fi, const QString &s) const - { - fi.reportResult(s); - } - - void memberString2(QFutureInterface &fi, QString s) const - { - fi.reportResult(s); - } - - void nonConstMember(QFutureInterface &fi) - { - fi.reportResults({0, 2, 1}); - } -}; - -template ::type> -std::shared_ptr> createAsyncTask(const Function &function, const Args &...args) -{ - auto asyncTask = std::make_shared>(); - asyncTask->setAsyncCallData(function, args...); - asyncTask->start(); - return asyncTask; -} - -void tst_AsyncTask::runAsync() -{ - // free function pointer - QCOMPARE(createAsyncTask(&report3)->results(), - QList({0, 2, 1})); - QCOMPARE(createAsyncTask(report3)->results(), - QList({0, 2, 1})); - - QCOMPARE(createAsyncTask(reportN, 4)->results(), - QList({0, 0, 0, 0})); - QCOMPARE(createAsyncTask(reportN, 2)->results(), - QList({0, 0})); - - QString s = QLatin1String("string"); - const QString &crs = QLatin1String("cr string"); - const QString cs = QLatin1String("c string"); - - QCOMPARE(createAsyncTask(reportString1, s)->results(), - QList({s})); - QCOMPARE(createAsyncTask(reportString1, crs)->results(), - QList({crs})); - QCOMPARE(createAsyncTask(reportString1, cs)->results(), - QList({cs})); - QCOMPARE(createAsyncTask(reportString1, QString(QLatin1String("rvalue")))->results(), - QList({QString(QLatin1String("rvalue"))})); - - QCOMPARE(createAsyncTask(reportString2, s)->results(), - QList({s})); - QCOMPARE(createAsyncTask(reportString2, crs)->results(), - QList({crs})); - QCOMPARE(createAsyncTask(reportString2, cs)->results(), - QList({cs})); - QCOMPARE(createAsyncTask(reportString2, QString(QLatin1String("rvalue")))->results(), - QList({QString(QLatin1String("rvalue"))})); - - // lambda - QCOMPARE(createAsyncTask([](QFutureInterface &fi, int n) { - fi.reportResults(QVector(n, 0)); - }, 3)->results(), - QList({0, 0, 0})); - - // std::function - const std::function&,int)> fun = [](QFutureInterface &fi, int n) { - fi.reportResults(QVector(n, 0)); - }; - QCOMPARE(createAsyncTask(fun, 2)->results(), - QList({0, 0})); - - // operator() - QCOMPARE(createAsyncTask(Callable(), 3)->results(), - QList({0, 0, 0})); - const Callable c{}; - QCOMPARE(createAsyncTask(c, 2)->results(), - QList({0, 0})); - - // static member functions - QCOMPARE(createAsyncTask(&MyObject::staticMember0)->results(), - QList({0, 2, 1})); - QCOMPARE(createAsyncTask(&MyObject::staticMember1, 2)->results(), - QList({0, 0})); - - // member functions - const MyObject obj{}; - QCOMPARE(createAsyncTask(&MyObject::member0, &obj)->results(), - QList({0, 2, 1})); - QCOMPARE(createAsyncTask(&MyObject::member1, &obj, 4)->results(), - QList({0, 0, 0, 0})); - QCOMPARE(createAsyncTask(&MyObject::memberString1, &obj, s)->results(), - QList({s})); - QCOMPARE(createAsyncTask(&MyObject::memberString1, &obj, crs)->results(), - QList({crs})); - QCOMPARE(createAsyncTask(&MyObject::memberString1, &obj, cs)->results(), - QList({cs})); - QCOMPARE(createAsyncTask(&MyObject::memberString1, &obj, QString(QLatin1String("rvalue")))->results(), - QList({QString(QLatin1String("rvalue"))})); - QCOMPARE(createAsyncTask(&MyObject::memberString2, &obj, s)->results(), - QList({s})); - QCOMPARE(createAsyncTask(&MyObject::memberString2, &obj, crs)->results(), - QList({crs})); - QCOMPARE(createAsyncTask(&MyObject::memberString2, &obj, cs)->results(), - QList({cs})); - QCOMPARE(createAsyncTask(&MyObject::memberString2, &obj, QString(QLatin1String("rvalue")))->results(), - QList({QString(QLatin1String("rvalue"))})); - MyObject nonConstObj{}; - QCOMPARE(createAsyncTask(&MyObject::nonConstMember, &nonConstObj)->results(), - QList({0, 2, 1})); -} - -void tst_AsyncTask::crefFunction() -{ - // free function pointer with future interface - auto fun = &report3; - QCOMPARE(createAsyncTask(std::cref(fun))->results(), - QList({0, 2, 1})); - - // lambda with future interface - auto lambda = [](QFutureInterface &fi, int n) { - fi.reportResults(QVector(n, 0)); - }; - QCOMPARE(createAsyncTask(std::cref(lambda), 3)->results(), - QList({0, 0, 0})); - - // std::function with future interface - const std::function&,int)> funObj = [](QFutureInterface &fi, int n) { - fi.reportResults(QVector(n, 0)); - }; - QCOMPARE(createAsyncTask(std::cref(funObj), 2)->results(), - QList({0, 0})); - - // callable with future interface - const Callable c{}; - QCOMPARE(createAsyncTask(std::cref(c), 2)->results(), - QList({0, 0})); - - // member functions with future interface - auto member = &MyObject::member0; - const MyObject obj{}; - QCOMPARE(createAsyncTask(std::cref(member), &obj)->results(), - QList({0, 2, 1})); -} - -template ::type> -typename AsyncTask::StartHandler startHandler(const Function &function, const Args &...args) -{ - return [=] { return Utils::runAsync(function, args...); }; -} - -void tst_AsyncTask::futureSynchonizer() -{ - auto lambda = [](QFutureInterface &fi) { - while (true) { - if (fi.isCanceled()) { - fi.reportCanceled(); - fi.reportFinished(); - return; - } - QThread::msleep(100); - } - }; - - FutureSynchronizer synchronizer; - { - AsyncTask task; - task.setAsyncCallData(lambda); - task.setFutureSynchronizer(&synchronizer); - task.start(); - QThread::msleep(10); - // We assume here that worker thread will still work for about 90 ms. - QVERIFY(!task.isCanceled()); - QVERIFY(!task.isDone()); - } - synchronizer.flushFinishedFutures(); - QVERIFY(!synchronizer.isEmpty()); - // The destructor of synchronizer should wait for about 90 ms for worker thread to be canceled -} - -void multiplyBy2(QFutureInterface &fi, int input) { fi.reportResult(input * 2); } - -void tst_AsyncTask::taskTree() -{ - using namespace Tasking; - - int value = 1; - - const auto setupIntAsync = [&](AsyncTask &task) { - task.setAsyncCallData(multiplyBy2, value); - }; - const auto handleIntAsync = [&](const AsyncTask &task) { - value = task.result(); - }; - - const Group root { - Async(setupIntAsync, handleIntAsync), - Async(setupIntAsync, handleIntAsync), - Async(setupIntAsync, handleIntAsync), - Async(setupIntAsync, handleIntAsync), - }; - - TaskTree tree(root); - - QEventLoop eventLoop; - connect(&tree, &TaskTree::done, &eventLoop, &QEventLoop::quit); - tree.start(); - eventLoop.exec(); - - QCOMPARE(value, 16); -} - -static int returnxx(int x) -{ - return x * x; -} - -static void returnxxWithFI(QFutureInterface &fi, int x) -{ - fi.reportResult(x * x); -} - -static double s_sum = 0; -static QList s_results; - -void tst_AsyncTask::mapReduce_data() -{ - using namespace Tasking; - - QTest::addColumn("root"); - QTest::addColumn("sum"); - QTest::addColumn>("results"); - - const auto initTree = [] { - s_sum = 0; - s_results.append(s_sum); - }; - const auto setupAsync = [](AsyncTask &task, int input) { - task.setAsyncCallData(returnxx, input); - }; - const auto setupAsyncWithFI = [](AsyncTask &task, int input) { - task.setAsyncCallData(returnxxWithFI, input); - }; - const auto setupAsyncWithTP = [this](AsyncTask &task, int input) { - task.setAsyncCallData(returnxx, input); - task.setThreadPool(&m_threadPool); - }; - const auto handleAsync = [](const AsyncTask &task) { - s_sum += task.result(); - s_results.append(task.result()); - }; - const auto handleTreeParallel = [] { - s_sum /= 2; - s_results.append(s_sum); - Utils::sort(s_results); // mapping order is undefined - }; - const auto handleTreeSequential = [] { - s_sum /= 2; - s_results.append(s_sum); - }; - - using namespace Tasking; - using namespace std::placeholders; - - using SetupHandler = std::function &task, int input)>; - using DoneHandler = std::function; - - const auto createTask = [=](const TaskItem &executeMode, - const SetupHandler &setupHandler, - const DoneHandler &doneHandler) { - return Group { - executeMode, - OnGroupSetup(initTree), - Async(std::bind(setupHandler, _1, 1), handleAsync), - Async(std::bind(setupHandler, _1, 2), handleAsync), - Async(std::bind(setupHandler, _1, 3), handleAsync), - Async(std::bind(setupHandler, _1, 4), handleAsync), - Async(std::bind(setupHandler, _1, 5), handleAsync), - OnGroupDone(doneHandler) - }; - }; - - const Group parallelRoot = createTask(parallel, setupAsync, handleTreeParallel); - const Group parallelRootWithFI = createTask(parallel, setupAsyncWithFI, handleTreeParallel); - const Group parallelRootWithTP = createTask(parallel, setupAsyncWithTP, handleTreeParallel); - const Group sequentialRoot = createTask(sequential, setupAsync, handleTreeSequential); - const Group sequentialRootWithFI = createTask(sequential, setupAsyncWithFI, handleTreeSequential); - const Group sequentialRootWithTP = createTask(sequential, setupAsyncWithTP, handleTreeSequential); - - const double defaultSum = 27.5; - const QList defaultResult{0., 1., 4., 9., 16., 25., 27.5}; - - QTest::newRow("Parallel") << parallelRoot << defaultSum << defaultResult; - QTest::newRow("ParallelWithFutureInterface") << parallelRootWithFI << defaultSum << defaultResult; - QTest::newRow("ParallelWithThreadPool") << parallelRootWithTP << defaultSum << defaultResult; - QTest::newRow("Sequential") << sequentialRoot << defaultSum << defaultResult; - QTest::newRow("SequentialWithFutureInterface") << sequentialRootWithFI << defaultSum << defaultResult; - QTest::newRow("SequentialWithThreadPool") << sequentialRootWithTP << defaultSum << defaultResult; - - const auto setupSimpleAsync = [](AsyncTask &task, int input) { - task.setAsyncCallData([](int input) { return input * 2; }, input); - }; - const auto handleSimpleAsync = [](const AsyncTask &task) { - s_sum += task.result() / 4.; - s_results.append(s_sum); - }; - const Group simpleRoot = { - sequential, - OnGroupSetup([] { s_sum = 0; }), - Async(std::bind(setupSimpleAsync, _1, 1), handleSimpleAsync), - Async(std::bind(setupSimpleAsync, _1, 2), handleSimpleAsync), - Async(std::bind(setupSimpleAsync, _1, 3), handleSimpleAsync) - }; - QTest::newRow("Simple") << simpleRoot << 3.0 << QList({.5, 1.5, 3.}); - - const auto setupStringAsync = [](AsyncTask &task, const QString &input) { - task.setAsyncCallData([](const QString &input) -> int { return input.size(); }, input); - }; - const auto handleStringAsync = [](const AsyncTask &task) { - s_sum /= task.result(); - }; - const Group stringRoot = { - parallel, - OnGroupSetup([] { s_sum = 90.0; }), - Async(std::bind(setupStringAsync, _1, "blubb"), handleStringAsync), - Async(std::bind(setupStringAsync, _1, "foo"), handleStringAsync), - Async(std::bind(setupStringAsync, _1, "blah"), handleStringAsync) - }; - QTest::newRow("String") << stringRoot << 1.5 << QList({}); -} - -void tst_AsyncTask::mapReduce() -{ - QThreadPool pool; - - s_sum = 0; - s_results.clear(); - - using namespace Tasking; - - QFETCH(Group, root); - QFETCH(double, sum); - QFETCH(QList, results); - - TaskTree tree(root); - - QEventLoop eventLoop; - connect(&tree, &TaskTree::done, &eventLoop, &QEventLoop::quit); - tree.start(); - eventLoop.exec(); - - QCOMPARE(s_results, results); - QCOMPARE(s_sum, sum); -} - -QTEST_GUILESS_MAIN(tst_AsyncTask) - -#include "tst_asynctask.moc" diff --git a/tests/auto/utils/commandline/tst_commandline.cpp b/tests/auto/utils/commandline/tst_commandline.cpp index 05357d2526e..2691b1e84ad 100644 --- a/tests/auto/utils/commandline/tst_commandline.cpp +++ b/tests/auto/utils/commandline/tst_commandline.cpp @@ -7,8 +7,9 @@ #include #include #include +#include +#include #include -#include #include #include @@ -29,7 +30,7 @@ private: QString run(const CommandLine &cmd) { - QtcProcess p; + Process p; p.setCommand(cmd); p.setEnvironment(testEnv); p.runBlocking(); @@ -123,6 +124,82 @@ private slots: QString actual = run(shell); QCOMPARE(actual, expected); } + + void testFromUserInput_data() + { + QTest::addColumn("input"); + QTest::addColumn("executable"); + QTest::addColumn("arguments"); + + QTest::newRow("empty") << "" + << "" + << ""; + QTest::newRow("command") << "command" + << "command" + << ""; + QTest::newRow("command-with-args") << "command and args" + << "command" + << "and args"; + + if (!HostOsInfo::isWindowsHost()) { + QTest::newRow("command-with-space-slash") << "command\\ with-space and args" + << "command with-space" + << "and args"; + QTest::newRow("command-with-space-single-quote") << "'command with-space' and args" + << "command with-space" + << "and args"; + } + QTest::newRow("command-with-space-double-quote") << "\"command with-space\" and args" + << "command with-space" + << "and args"; + + QTest::newRow("command-with-space-double-quote-in-name") + << "\"command\\\"with-quote\" and args" + << "command\"with-quote" + << "and args"; + + QTest::newRow("inside-space-quoted") << "command\" \"withspace args here" + << "command withspace" + << "args here"; + } + + void testFromUserInput() + { + QFETCH(QString, input); + QFETCH(QString, executable); + QFETCH(QString, arguments); + + CommandLine cmd = CommandLine::fromUserInput(input); + QCOMPARE(cmd.executable(), FilePath::fromUserInput(executable)); + QCOMPARE(cmd.arguments(), arguments); + } + + void testFromInputFails() + { + if (HostOsInfo::isWindowsHost()) + QSKIP("The test does not work on Windows."); + + CommandLine cmd = CommandLine::fromUserInput("command\\\\\\ with-space and args"); + QEXPECT_FAIL("", + "CommandLine::fromUserInput (and FilePath::fromUserInput) does not handle " + "backslashes correctly", + Continue); + QCOMPARE(cmd.executable().fileName(), "command\\ with-space"); + QCOMPARE(cmd.arguments(), "and args"); + } + + void testFromInputWithMacro() + { + MacroExpander expander; + expander.registerVariable("hello", "world var", [] { return "hello world"; }); + CommandLine cmd = CommandLine::fromUserInput("command macroarg: %{hello}", &expander); + QCOMPARE(cmd.executable(), "command"); + + if (HostOsInfo::isWindowsHost()) + QEXPECT_FAIL("", "Windows does not correctly quote macro arguments", Continue); + + QCOMPARE(cmd.arguments(), "macroarg: 'hello world'"); + } }; int main(int argc, char *argv[]) diff --git a/tests/auto/utils/deviceshell/tst_deviceshell.cpp b/tests/auto/utils/deviceshell/tst_deviceshell.cpp index 6080f99bf7c..6e081495c36 100644 --- a/tests/auto/utils/deviceshell/tst_deviceshell.cpp +++ b/tests/auto/utils/deviceshell/tst_deviceshell.cpp @@ -3,16 +3,16 @@ #include +#include #include #include #include #include -#include -#include -#include +#include #include #include +#include #include using namespace Utils; @@ -28,7 +28,7 @@ public: } private: - void setupShellProcess(QtcProcess *shellProcess) override + void setupShellProcess(Process *shellProcess) override { shellProcess->setCommand(m_cmdLine); } @@ -38,7 +38,7 @@ private: bool testDocker(const FilePath &executable) { - QtcProcess p; + Process p; p.setCommand({executable, {"info", "--format", "{{.OSType}}"}}); p.runBlocking(); const QString platform = p.cleanedStdOut().trimmed(); @@ -312,15 +312,14 @@ private slots: TestShell shell(cmdLine); QCOMPARE(shell.state(), DeviceShell::State::Succeeded); - QList runs{1,2,3,4,5,6,7,8,9}; - int maxDepth = 4; int numMs = 0; while (true) { QElapsedTimer t; t.start(); - RunResult result = shell.runInShell({"find", {"/usr", "-maxdepth", QString::number(maxDepth)}}); + const RunResult result = shell.runInShell({"find", {"/usr", "-maxdepth", + QString::number(maxDepth)}}); numMs = t.elapsed(); qDebug() << "adjusted maxDepth" << maxDepth << "took" << numMs << "ms"; if (numMs < 100 || maxDepth == 1) { @@ -329,15 +328,17 @@ private slots: maxDepth--; } - QList results = Utils::mapped(runs, [&shell, maxDepth](const int i) -> QByteArray{ + const auto find = [&shell, maxDepth](int i) { QElapsedTimer t; t.start(); - RunResult result = shell.runInShell({"find", {"/usr", "-maxdepth", QString::number(maxDepth)}}); + const RunResult result = shell.runInShell({"find", {"/usr", "-maxdepth", + QString::number(maxDepth)}}); qDebug() << i << "took" << t.elapsed() << "ms"; return result.stdOut; - }); - - QVERIFY (!Utils::anyOf(results, [&results](const QByteArray r){ return r != results[0]; })); + }; + const QList runs{1,2,3,4,5,6,7,8,9}; + const QList results = QtConcurrent::blockingMapped(runs, find); + QVERIFY(!Utils::anyOf(results, [&results](const QByteArray r) { return r != results[0]; })); } void testNoScript_data() diff --git a/tests/auto/utils/filepath/CMakeLists.txt b/tests/auto/utils/filepath/CMakeLists.txt new file mode 100644 index 00000000000..3d85eb0c64e --- /dev/null +++ b/tests/auto/utils/filepath/CMakeLists.txt @@ -0,0 +1,4 @@ +add_qtc_test(tst_utils_filepath + DEPENDS Utils + SOURCES tst_filepath.cpp +) diff --git a/tests/auto/utils/filepath/filepath.qbs b/tests/auto/utils/filepath/filepath.qbs new file mode 100644 index 00000000000..73c106b2e8c --- /dev/null +++ b/tests/auto/utils/filepath/filepath.qbs @@ -0,0 +1,11 @@ +import qbs + +QtcAutotest { + name: "FilePath autotest" + Depends { name: "Utils" } + Properties { + condition: qbs.toolchain.contains("gcc") + cpp.cxxFlags: base.concat(["-Wno-trigraphs"]) + } + files: "tst_filepath.cpp" +} diff --git a/tests/auto/utils/filepath/tst_filepath.cpp b/tests/auto/utils/filepath/tst_filepath.cpp new file mode 100644 index 00000000000..3699ec6b2e8 --- /dev/null +++ b/tests/auto/utils/filepath/tst_filepath.cpp @@ -0,0 +1,1688 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include +#include + +#include +#include +#include +#include + +using namespace Utils; + +namespace QTest { +template<> +char *toString(const FilePath &filePath) +{ + return qstrdup(filePath.toString().toLocal8Bit().constData()); +} +} // namespace QTest + +class tst_filepath : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + + void isEmpty_data(); + void isEmpty(); + + void parentDir_data(); + void parentDir(); + + void isChildOf_data(); + void isChildOf(); + + void fileName_data(); + void fileName(); + + void calcRelativePath_data(); + void calcRelativePath(); + + void relativePath_specials(); + void relativePath_data(); + void relativePath(); + + void absolute_data(); + void absolute(); + + void fromToString_data(); + void fromToString(); + + void fromString_data(); + void fromString(); + + void fromUserInput_data(); + void fromUserInput(); + + void toString_data(); + void toString(); + + void toFSPathString_data(); + void toFSPathString(); + + void comparison_data(); + void comparison(); + + void linkFromString_data(); + void linkFromString(); + + void pathAppended_data(); + void pathAppended(); + + void resolvePath_data(); + void resolvePath(); + + void relativeChildPath_data(); + void relativeChildPath(); + + void rootLength_data(); + void rootLength(); + + void schemeAndHostLength_data(); + void schemeAndHostLength(); + + void asyncLocalCopy(); + void startsWithDriveLetter(); + void startsWithDriveLetter_data(); + + void withNewMappedPath_data(); + void withNewMappedPath(); + + void stringAppended(); + void stringAppended_data(); + void url(); + void url_data(); + + void cleanPath_data(); + void cleanPath(); + + void isSameFile_data(); + void isSameFile(); + + void hostSpecialChars_data(); + void hostSpecialChars(); + + void tmp(); + void tmp_data(); + + void searchInWithFilter(); + + void sort(); + void sort_data(); + +private: + QTemporaryDir tempDir; + QString rootPath; + QString exeExt; +}; + +static void touch(const QDir &dir, const QString &filename, bool fill, bool executable = false) +{ + QFile file(dir.absoluteFilePath(filename)); + file.open(QIODevice::WriteOnly); + if (executable) + file.setPermissions(file.permissions() | QFileDevice::ExeUser); + + if (fill) { + QRandomGenerator *random = QRandomGenerator::global(); + for (int i = 0; i < 10; ++i) + file.write(QString::number(random->generate(), 16).toUtf8()); + } + file.close(); +} + +void tst_filepath::initTestCase() +{ + // initialize test for tst_filepath::relativePath*() + QVERIFY(tempDir.isValid()); + rootPath = tempDir.path(); + QDir dir(rootPath); + dir.mkpath("a/b/c/d"); + dir.mkpath("a/x/y/z"); + dir.mkpath("a/b/x/y/z"); + dir.mkpath("x/y/z"); + touch(dir, "a/b/c/d/file1.txt", false); + touch(dir, "a/x/y/z/file2.txt", false); + touch(dir, "a/file3.txt", false); + touch(dir, "x/y/file4.txt", false); + + // initialize test for tst_filepath::asyncLocalCopy() + touch(dir, "x/y/fileToCopy.txt", true); + +// initialize test for tst_filepath::searchIn() +#ifdef Q_OS_WIN + exeExt = ".exe"; +#endif + + dir.mkpath("s/1"); + dir.mkpath("s/2"); + touch(dir, "s/1/testexe" + exeExt, false, true); + touch(dir, "s/2/testexe" + exeExt, false, true); +} + +void tst_filepath::searchInWithFilter() +{ + const FilePaths dirs = {FilePath::fromUserInput(rootPath) / "s" / "1", + FilePath::fromUserInput(rootPath) / "s" / "2"}; + + FilePath exe = FilePath::fromUserInput("testexe" + exeExt) + .searchInDirectories(dirs, [](const FilePath &path) { + return path.path().contains("/2/"); + }); + + QVERIFY(!exe.path().endsWith("/1/testexe" + exeExt) + && exe.path().endsWith("/2/testexe" + exeExt)); + + FilePath exe2 = FilePath::fromUserInput("testexe" + exeExt) + .searchInDirectories(dirs, [](const FilePath &path) { + return path.path().contains("/1/"); + }); + + QVERIFY(!exe2.path().endsWith("/2/testexe" + exeExt) + && exe2.path().endsWith("/1/testexe" + exeExt)); +} + +void tst_filepath::isEmpty_data() +{ + QTest::addColumn("path"); + QTest::addColumn("result"); + + QTest::newRow("empty path") << "" << true; + QTest::newRow("root only") << "/" << false; + QTest::newRow("//") << "//" << false; + QTest::newRow("scheme://host") << "scheme://host" << true; // Intentional (for now?) + QTest::newRow("scheme://host/") << "scheme://host/" << false; + QTest::newRow("scheme://host/a") << "scheme://host/a" << false; + QTest::newRow("scheme://host/.") << "scheme://host/." << false; +} + +void tst_filepath::isEmpty() +{ + QFETCH(QString, path); + QFETCH(bool, result); + + FilePath filePath = FilePath::fromString(path); + QCOMPARE(filePath.isEmpty(), result); +} + +void tst_filepath::parentDir_data() +{ + QTest::addColumn("path"); + QTest::addColumn("parentPath"); + QTest::addColumn("expectFailMessage"); + + QTest::newRow("empty path") << "" + << "" + << ""; + QTest::newRow("root only") << "/" + << "" + << ""; + QTest::newRow("//") << "//" + << "" + << ""; + QTest::newRow("/tmp/dir") << "/tmp/dir" + << "/tmp" + << ""; + QTest::newRow("relative/path") << "relative/path" + << "relative" + << ""; + QTest::newRow("relativepath") << "relativepath" + << "." + << ""; + + // Windows stuff: + QTest::newRow("C:/data") << "C:/data" + << "C:/" + << ""; + QTest::newRow("C:/") << "C:/" + << "" + << ""; + QTest::newRow("//./com1") << "//./com1" + << "//./" + << ""; + QTest::newRow("//?/path") << "//?/path" + << "/" + << "Qt 4 cannot handle this path."; + QTest::newRow("/Global?\?/UNC/host") << "/Global?\?/UNC/host" + << "/Global?\?/UNC/host" + << "Qt 4 cannot handle this path."; + QTest::newRow("//server/directory/file") << "//server/directory/file" + << "//server/directory" + << ""; + QTest::newRow("//server/directory") << "//server/directory" + << "//server/" + << ""; + QTest::newRow("//server") << "//server" + << "" + << ""; + + QTest::newRow("qrc") << ":/foo/bar.txt" + << ":/foo" + << ""; +} + +void tst_filepath::parentDir() +{ + QFETCH(QString, path); + QFETCH(QString, parentPath); + QFETCH(QString, expectFailMessage); + + FilePath result = FilePath::fromUserInput(path).parentDir(); + if (!expectFailMessage.isEmpty()) + QEXPECT_FAIL("", expectFailMessage.toUtf8().constData(), Continue); + QCOMPARE(result.toString(), parentPath); +} + +void tst_filepath::isChildOf_data() +{ + QTest::addColumn("path"); + QTest::addColumn("childPath"); + QTest::addColumn("result"); + + QTest::newRow("empty path") << "" + << "/tmp" << false; + QTest::newRow("root only") << "/" + << "/tmp" << true; + QTest::newRow("/tmp/dir") << "/tmp" + << "/tmp/dir" << true; + QTest::newRow("relative/path") << "relative" + << "relative/path" << true; + QTest::newRow("/tmpdir") << "/tmp" + << "/tmpdir" << false; + QTest::newRow("same") << "/tmp/dir" + << "/tmp/dir" << false; + + // Windows stuff: + QTest::newRow("C:/data") << "C:/" + << "C:/data" << true; + QTest::newRow("C:/") << "" + << "C:/" << false; + QTest::newRow("com-port") << "//./" + << "//./com1" << true; + QTest::newRow("extended-length-path") << "\\\\?\\C:\\" + << "\\\\?\\C:\\path" << true; + QTest::newRow("/Global?\?/UNC/host") << "/Global?\?/UNC/host" + << "/Global?\?/UNC/host/file" << true; + QTest::newRow("//server/directory/file") << "//server/directory" + << "//server/directory/file" << true; + QTest::newRow("//server/directory") << "//server" + << "//server/directory" << true; + + QTest::newRow("qrc") << ":/foo/bar" + << ":/foo/bar/blah" << true; +} + +void tst_filepath::isChildOf() +{ + QFETCH(QString, path); + QFETCH(QString, childPath); + QFETCH(bool, result); + + const FilePath child = FilePath::fromUserInput(childPath); + const FilePath parent = FilePath::fromUserInput(path); + + QCOMPARE(child.isChildOf(parent), result); +} + +void tst_filepath::fileName_data() +{ + QTest::addColumn("path"); + QTest::addColumn("components"); + QTest::addColumn("result"); + + QTest::newRow("empty 1") << "" << 0 << ""; + QTest::newRow("empty 2") << "" << 1 << ""; + QTest::newRow("basic") << "/foo/bar/baz" << 0 << "baz"; + QTest::newRow("2 parts") << "/foo/bar/baz" << 1 << "bar/baz"; + QTest::newRow("root no depth") << "/foo" << 0 << "foo"; + QTest::newRow("root full") << "/foo" << 1 << "/foo"; + QTest::newRow("root included") << "/foo/bar/baz" << 2 << "/foo/bar/baz"; + QTest::newRow("too many parts") << "/foo/bar/baz" << 5 << "/foo/bar/baz"; + QTest::newRow("windows root") << "C:/foo/bar/baz" << 2 << "C:/foo/bar/baz"; + QTest::newRow("smb share") << "//server/share/file" << 2 << "//server/share/file"; + QTest::newRow("no slashes") << "foobar" << 0 << "foobar"; + QTest::newRow("no slashes with depth") << "foobar" << 1 << "foobar"; + QTest::newRow("multiple slashes 1") << "/foo/bar////baz" << 0 << "baz"; + QTest::newRow("multiple slashes 2") << "/foo/bar////baz" << 1 << "bar////baz"; + QTest::newRow("multiple slashes 3") << "/foo////bar/baz" << 2 << "/foo////bar/baz"; + QTest::newRow("single char 1") << "/a/b/c" << 0 << "c"; + QTest::newRow("single char 2") << "/a/b/c" << 1 << "b/c"; + QTest::newRow("single char 3") << "/a/b/c" << 2 << "/a/b/c"; + QTest::newRow("slash at end 1") << "/a/b/" << 0 << ""; + QTest::newRow("slash at end 2") << "/a/b/" << 1 << "b/"; + QTest::newRow("slashes at end 1") << "/a/b//" << 0 << ""; + QTest::newRow("slashes at end 2") << "/a/b//" << 1 << "b//"; + QTest::newRow("root only 1") << "/" << 0 << ""; + QTest::newRow("root only 2") << "/" << 1 << "/"; + QTest::newRow("qrc 0") << ":/foo/bar" << 0 << "bar"; + QTest::newRow("qrc with root") << ":/foo/bar" << 1 << ":/foo/bar"; +} + +void tst_filepath::fileName() +{ + QFETCH(QString, path); + QFETCH(int, components); + QFETCH(QString, result); + QCOMPARE(FilePath::fromString(path).fileNameWithPathComponents(components), result); +} + +void tst_filepath::calcRelativePath_data() +{ + QTest::addColumn("absolutePath"); + QTest::addColumn("anchorPath"); + QTest::addColumn("result"); + + QTest::newRow("empty") << "" + << "" + << ""; + QTest::newRow("leftempty") << "" + << "/" + << ""; + QTest::newRow("rightempty") << "/" + << "" + << ""; + QTest::newRow("root") << "/" + << "/" + << "."; + QTest::newRow("simple1") << "/a" + << "/" + << "a"; + QTest::newRow("simple2") << "/" + << "/a" + << ".."; + QTest::newRow("simple3") << "/a" + << "/a" + << "."; + QTest::newRow("extraslash1") << "/a/b/c" + << "/a/b/c" + << "."; + QTest::newRow("extraslash2") << "/a/b/c" + << "/a/b/c/" + << "."; + QTest::newRow("extraslash3") << "/a/b/c/" + << "/a/b/c" + << "."; + QTest::newRow("normal1") << "/a/b/c" + << "/a/x" + << "../b/c"; + QTest::newRow("normal2") << "/a/b/c" + << "/a/x/y" + << "../../b/c"; + QTest::newRow("normal3") << "/a/b/c" + << "/x/y" + << "../../a/b/c"; +} + +void tst_filepath::calcRelativePath() +{ + QFETCH(QString, absolutePath); + QFETCH(QString, anchorPath); + QFETCH(QString, result); + QString relativePath = Utils::FilePath::calcRelativePath(absolutePath, anchorPath); + QCOMPARE(relativePath, result); +} + +void tst_filepath::relativePath_specials() +{ + QString path = FilePath("").relativePathFrom("").toString(); + QCOMPARE(path, ""); +} + +void tst_filepath::relativePath_data() +{ + QTest::addColumn("relative"); + QTest::addColumn("anchor"); + QTest::addColumn("result"); + + QTest::newRow("samedir") << "/" + << "/" + << "."; + QTest::newRow("samedir_but_file") << "a/b/c/d/file1.txt" + << "a/b/c/d" + << "file1.txt"; + QTest::newRow("samedir_but_file2") << "a/b/c/d" + << "a/b/c/d/file1.txt" + << "."; + QTest::newRow("dir2dir_1") << "a/b/c/d" + << "a/x/y/z" + << "../../../b/c/d"; + QTest::newRow("dir2dir_2") << "a/b" + << "a/b/c" + << ".."; + QTest::newRow("file2file_1") << "a/b/c/d/file1.txt" + << "a/file3.txt" + << "b/c/d/file1.txt"; + QTest::newRow("dir2file_1") << "a/b/c" + << "a/x/y/z/file2.txt" + << "../../../b/c"; + QTest::newRow("file2dir_1") << "a/b/c/d/file1.txt" + << "x/y" + << "../../a/b/c/d/file1.txt"; +} + +void tst_filepath::relativePath() +{ + QFETCH(QString, relative); + QFETCH(QString, anchor); + QFETCH(QString, result); + FilePath actualPath = FilePath::fromString(rootPath + "/" + relative) + .relativePathFrom(FilePath::fromString(rootPath + "/" + anchor)); + QCOMPARE(actualPath.toString(), result); +} + +void tst_filepath::rootLength_data() +{ + QTest::addColumn("path"); + QTest::addColumn("result"); + + QTest::newRow("empty") << "" << 0; + QTest::newRow("slash") << "/" << 1; + QTest::newRow("slash-rest") << "/abc" << 1; + QTest::newRow("rest") << "abc" << 0; + QTest::newRow("drive-slash") << "x:/" << 3; + QTest::newRow("drive-rest") << "x:abc" << 0; + QTest::newRow("drive-slash-rest") << "x:/abc" << 3; + + QTest::newRow("unc-root") << "//" << 2; + QTest::newRow("unc-localhost-unfinished") << "//localhost" << 11; + QTest::newRow("unc-localhost") << "//localhost/" << 12; + QTest::newRow("unc-localhost-rest") << "//localhost/abs" << 12; + QTest::newRow("unc-localhost-drive") << "//localhost/c$" << 12; + QTest::newRow("unc-localhost-drive-slash") << "//localhost//c$/" << 12; + QTest::newRow("unc-localhost-drive-slash-rest") << "//localhost//c$/x" << 12; +} + +void tst_filepath::rootLength() +{ + QFETCH(QString, path); + QFETCH(int, result); + + int actual = FilePath::rootLength(path); + QCOMPARE(actual, result); +} + +void tst_filepath::schemeAndHostLength_data() +{ + QTest::addColumn("path"); + QTest::addColumn("result"); + + QTest::newRow("empty") << "" << 0; + QTest::newRow("drive-slash-rest") << "x:/abc" << 0; + QTest::newRow("rest") << "abc" << 0; + QTest::newRow("slash-rest") << "/abc" << 0; + QTest::newRow("dev-empty") << "dev://" << 6; + QTest::newRow("dev-localhost-unfinished") << "dev://localhost" << 15; + QTest::newRow("dev-localhost") << "dev://localhost/" << 16; + QTest::newRow("dev-localhost-rest") << "dev://localhost/abs" << 16; + QTest::newRow("dev-localhost-drive") << "dev://localhost/c$" << 16; + QTest::newRow("dev-localhost-drive-slash") << "dev://localhost//c$/" << 16; + QTest::newRow("dev-localhost-drive-slash-rest") << "dev://localhost//c$/x" << 16; +} + +void tst_filepath::schemeAndHostLength() +{ + QFETCH(QString, path); + QFETCH(int, result); + + int actual = FilePath::schemeAndHostLength(path); + QCOMPARE(actual, result); +} + +void tst_filepath::absolute_data() +{ + QTest::addColumn("path"); + QTest::addColumn("absoluteFilePath"); + QTest::addColumn("absolutePath"); + + QTest::newRow("absolute1") << FilePath::fromString("/") << FilePath::fromString("/") + << FilePath::fromString("/"); + QTest::newRow("absolute2") << FilePath::fromString("C:/a/b") << FilePath::fromString("C:/a/b") + << FilePath::fromString("C:/a"); + QTest::newRow("absolute3") << FilePath::fromString("/a/b") << FilePath::fromString("/a/b") + << FilePath::fromString("/a"); + QTest::newRow("absolute4") << FilePath::fromString("/a/b/..") << FilePath::fromString("/a") + << FilePath::fromString("/"); + QTest::newRow("absolute5") << FilePath::fromString("/a/b/c/../d") + << FilePath::fromString("/a/b/d") << FilePath::fromString("/a/b"); + QTest::newRow("absolute6") << FilePath::fromString("/a/../b/c/d") + << FilePath::fromString("/b/c/d") << FilePath::fromString("/b/c"); + QTest::newRow("default-constructed") << FilePath() << FilePath() << FilePath(); + QTest::newRow("relative") << FilePath::fromString("a/b") + << FilePath::fromString(QDir::currentPath() + "/a/b") + << FilePath::fromString(QDir::currentPath() + "/a"); + QTest::newRow("qrc") << FilePath::fromString(":/foo/bar.txt") + << FilePath::fromString(":/foo/bar.txt") << FilePath::fromString(":/foo"); +} + +void tst_filepath::absolute() +{ + QFETCH(FilePath, path); + QFETCH(FilePath, absoluteFilePath); + QFETCH(FilePath, absolutePath); + QCOMPARE(path.absoluteFilePath(), absoluteFilePath); + QCOMPARE(path.absolutePath(), absolutePath); +} + +void tst_filepath::toString_data() +{ + QTest::addColumn("scheme"); + QTest::addColumn("host"); + QTest::addColumn("path"); + QTest::addColumn("result"); + QTest::addColumn("userResult"); + + QTest::newRow("empty") << "" + << "" + << "" + << "" + << ""; + QTest::newRow("scheme") << "http" + << "" + << "" + << "http://" + << "http://"; + QTest::newRow("scheme-and-host") << "http" + << "127.0.0.1" + << "" + << "http://127.0.0.1" + << "http://127.0.0.1"; + QTest::newRow("root") << "http" + << "127.0.0.1" + << "/" + << "http://127.0.0.1/" + << "http://127.0.0.1/"; + + QTest::newRow("root-folder") << "" + << "" + << "/" + << "/" + << "/"; + QTest::newRow("qtc-dev-root-folder-linux") << "" + << "" + << "/__qtc_devices__" + << "/__qtc_devices__" + << "/__qtc_devices__"; + QTest::newRow("qtc-dev-root-folder-win") << "" + << "" + << "c:/__qtc_devices__" + << "c:/__qtc_devices__" + << "c:/__qtc_devices__"; + QTest::newRow("qtc-dev-type-root-folder-linux") << "" + << "" + << "/__qtc_devices__/docker" + << "/__qtc_devices__/docker" + << "/__qtc_devices__/docker"; + QTest::newRow("qtc-dev-type-root-folder-win") << "" + << "" + << "c:/__qtc_devices__/docker" + << "c:/__qtc_devices__/docker" + << "c:/__qtc_devices__/docker"; + QTest::newRow("qtc-root-folder") << "docker" + << "alpine:latest" + << "/" + << "docker://alpine:latest/" + << "docker://alpine:latest/"; + QTest::newRow("qtc-root-folder-rel") << "docker" + << "alpine:latest" + << "" + << "docker://alpine:latest" + << "docker://alpine:latest"; +} + +void tst_filepath::toString() +{ + QFETCH(QString, scheme); + QFETCH(QString, host); + QFETCH(QString, path); + QFETCH(QString, result); + QFETCH(QString, userResult); + + FilePath filePath = FilePath::fromParts(scheme, host, path); + QCOMPARE(filePath.toString(), result); + QString cleanedOutput = filePath.needsDevice() ? filePath.toUserOutput() + : QDir::cleanPath(filePath.toUserOutput()); + QCOMPARE(cleanedOutput, userResult); +} + +void tst_filepath::toFSPathString_data() +{ + QTest::addColumn("scheme"); + QTest::addColumn("host"); + QTest::addColumn("path"); + QTest::addColumn("result"); + QTest::addColumn("userResult"); + + QTest::newRow("empty") << "" + << "" + << "" + << "" + << ""; + QTest::newRow("scheme") << "http" + << "" + << "" << QDir::rootPath() + "__qtc_devices__/http/" + << "http://"; + QTest::newRow("scheme-and-host") << "http" + << "127.0.0.1" + << "" << QDir::rootPath() + "__qtc_devices__/http/127.0.0.1" + << "http://127.0.0.1"; + QTest::newRow("root") << "http" + << "127.0.0.1" + << "/" << QDir::rootPath() + "__qtc_devices__/http/127.0.0.1/" + << "http://127.0.0.1/"; + + QTest::newRow("root-folder") << "" + << "" + << "/" + << "/" + << "/"; + QTest::newRow("qtc-dev-root-folder") + << "" + << "" << QDir::rootPath() + "__qtc_devices__" << QDir::rootPath() + "__qtc_devices__" + << QDir::rootPath() + "__qtc_devices__"; + QTest::newRow("qtc-dev-type-root-folder") << "" + << "" << QDir::rootPath() + "__qtc_devices__/docker" + << QDir::rootPath() + "__qtc_devices__/docker" + << QDir::rootPath() + "__qtc_devices__/docker"; + QTest::newRow("qtc-root-folder") + << "docker" + << "alpine:latest" + << "/" << QDir::rootPath() + "__qtc_devices__/docker/alpine:latest/" + << "docker://alpine:latest/"; + QTest::newRow("qtc-root-folder-rel") + << "docker" + << "alpine:latest" + << "" << QDir::rootPath() + "__qtc_devices__/docker/alpine:latest" + << "docker://alpine:latest"; +} + +void tst_filepath::toFSPathString() +{ + QFETCH(QString, scheme); + QFETCH(QString, host); + QFETCH(QString, path); + QFETCH(QString, result); + QFETCH(QString, userResult); + + FilePath filePath = FilePath::fromParts(scheme, host, path); + QCOMPARE(filePath.toFSPathString(), result); + QString cleanedOutput = filePath.needsDevice() ? filePath.toUserOutput() + : QDir::cleanPath(filePath.toUserOutput()); + QCOMPARE(cleanedOutput, userResult); +} + +enum ExpectedPass { PassEverywhere = 0, FailOnWindows = 1, FailOnLinux = 2, FailEverywhere = 3 }; + +class FromStringData +{ +public: + FromStringData(const QString &input, + const QString &scheme, + const QString &host, + const QString &path, + ExpectedPass expectedPass = PassEverywhere) + : input(input) + , scheme(scheme) + , host(host) + , path(path) + , expectedPass(expectedPass) + {} + + QString input; + QString scheme; + QString host; + QString path; + ExpectedPass expectedPass = PassEverywhere; +}; + +Q_DECLARE_METATYPE(FromStringData); + +void tst_filepath::fromString_data() +{ + using D = FromStringData; + QTest::addColumn("data"); + + QTest::newRow("empty") << D("", "", "", ""); + QTest::newRow("single-colon") << D(":", "", "", ":"); + QTest::newRow("single-slash") << D("/", "", "", "/"); + QTest::newRow("single-char") << D("a", "", "", "a"); + QTest::newRow("relative") << D("./rel", "", "", "./rel"); + QTest::newRow("qrc") << D(":/test.txt", "", "", ":/test.txt"); + QTest::newRow("qrc-no-slash") << D(":test.txt", "", "", ":test.txt"); + + QTest::newRow("unc-incomplete") << D("//", "", "", "//"); + QTest::newRow("unc-incomplete-only-server") << D("//server", "", "", "//server"); + QTest::newRow("unc-incomplete-only-server-2") << D("//server/", "", "", "//server/"); + QTest::newRow("unc-server-and-share") << D("//server/share", "", "", "//server/share"); + QTest::newRow("unc-server-and-share-2") << D("//server/share/", "", "", "//server/share/"); + QTest::newRow("unc-full") << D("//server/share/test.txt", "", "", "//server/share/test.txt"); + + QTest::newRow("unix-root") << D("/", "", "", "/"); + QTest::newRow("unix-folder") << D("/tmp", "", "", "/tmp"); + QTest::newRow("unix-folder-with-trailing-slash") << D("/tmp/", "", "", "/tmp/"); + + QTest::newRow("windows-root") << D("c:", "", "", "c:"); + QTest::newRow("windows-folder") << D("c:/Windows", "", "", "c:/Windows"); + QTest::newRow("windows-folder-with-trailing-slash") << D("c:/Windows/", "", "", "c:/Windows/"); + QTest::newRow("windows-folder-slash") << D("C:/Windows", "", "", "C:/Windows"); + + QTest::newRow("docker-root-url") << D("docker://1234/", "docker", "1234", "/"); + QTest::newRow("docker-root-url-special-linux") + << D("/__qtc_devices__/docker/1234/", "docker", "1234", "/"); + QTest::newRow("docker-root-url-special-win") + << D("c:/__qtc_devices__/docker/1234/", "docker", "1234", "/"); + QTest::newRow("docker-relative-path") << D("docker://1234/./rel", "docker", "1234", "rel"); + + QTest::newRow("qtc-dev-linux") << D("/__qtc_devices__", "", "", "/__qtc_devices__"); + QTest::newRow("qtc-dev-win") << D("c:/__qtc_devices__", "", "", "c:/__qtc_devices__"); + QTest::newRow("qtc-dev-type-linux") + << D("/__qtc_devices__/docker", "", "", "/__qtc_devices__/docker"); + QTest::newRow("qtc-dev-type-win") + << D("c:/__qtc_devices__/docker", "", "", "c:/__qtc_devices__/docker"); + QTest::newRow("qtc-dev-type-dev-linux") + << D("/__qtc_devices__/docker/1234", "docker", "1234", "/"); + QTest::newRow("qtc-dev-type-dev-win") + << D("c:/__qtc_devices__/docker/1234", "docker", "1234", "/"); + + // "Remote Windows" is currently truly not supported. + QTest::newRow("cross-os-linux") << D("/__qtc_devices__/docker/1234/c:/test.txt", + "docker", + "1234", + "c:/test.txt", + FailEverywhere); + QTest::newRow("cross-os-win") << D("c:/__qtc_devices__/docker/1234/c:/test.txt", + "docker", + "1234", + "c:/test.txt", + FailEverywhere); + QTest::newRow("cross-os-unclean-linux") << D("/__qtc_devices__/docker/1234/c:\\test.txt", + "docker", + "1234", + "c:/test.txt", + FailEverywhere); + QTest::newRow("cross-os-unclean-win") << D("c:/__qtc_devices__/docker/1234/c:\\test.txt", + "docker", + "1234", + "c:/test.txt", + FailEverywhere); + + QTest::newRow("unc-full-in-docker-linux") + << D("/__qtc_devices__/docker/1234//server/share/test.txt", + "docker", + "1234", + "//server/share/test.txt"); + QTest::newRow("unc-full-in-docker-win") + << D("c:/__qtc_devices__/docker/1234//server/share/test.txt", + "docker", + "1234", + "//server/share/test.txt"); + + QTest::newRow("unc-dos-1") << D("//?/c:", "", "", "//?/c:"); + QTest::newRow("unc-dos-com") << D("//./com1", "", "", "//./com1"); +} + +void tst_filepath::fromString() +{ + QFETCH(FromStringData, data); + + FilePath filePath = FilePath::fromString(data.input); + + bool expectFail = ((data.expectedPass & FailOnLinux) && !HostOsInfo::isWindowsHost()) + || ((data.expectedPass & FailOnWindows) && HostOsInfo::isWindowsHost()); + + if (expectFail) { + QString actual = filePath.scheme() + '|' + filePath.host() + '|' + filePath.path(); + QString expected = data.scheme + '|' + data.host + '|' + data.path; + QEXPECT_FAIL("", "", Continue); + QCOMPARE(actual, expected); + return; + } + + QCOMPARE(filePath.scheme(), data.scheme); + QCOMPARE(filePath.host(), data.host); + QCOMPARE(filePath.path(), data.path); +} + +void tst_filepath::fromUserInput_data() +{ + using D = FromStringData; + QTest::addColumn("data"); + + QTest::newRow("empty") << D("", "", "", ""); + QTest::newRow("single-colon") << D(":", "", "", ":"); + QTest::newRow("single-slash") << D("/", "", "", "/"); + QTest::newRow("single-char") << D("a", "", "", "a"); + QTest::newRow("relative") << D("./rel", "", "", "rel"); + QTest::newRow("qrc") << D(":/test.txt", "", "", ":/test.txt"); + QTest::newRow("qrc-no-slash") << D(":test.txt", "", "", ":test.txt"); + QTest::newRow("tilde") << D("~/", "", "", QDir::homePath()); + QTest::newRow("tilde-with-path") << D("~/foo", "", "", QDir::homePath() + "/foo"); + QTest::newRow("tilde-only") << D("~", "", "", "~"); + + QTest::newRow("unc-incomplete") << D("//", "", "", "//"); + QTest::newRow("unc-incomplete-only-server") << D("//server", "", "", "//server"); + QTest::newRow("unc-incomplete-only-server-2") << D("//server/", "", "", "//server/"); + QTest::newRow("unc-server-and-share") << D("//server/share", "", "", "//server/share"); + QTest::newRow("unc-server-and-share-2") << D("//server/share/", "", "", "//server/share"); + QTest::newRow("unc-full") << D("//server/share/test.txt", "", "", "//server/share/test.txt"); + + QTest::newRow("unix-root") << D("/", "", "", "/"); + QTest::newRow("unix-folder") << D("/tmp", "", "", "/tmp"); + QTest::newRow("unix-folder-with-trailing-slash") << D("/tmp/", "", "", "/tmp"); + + QTest::newRow("windows-root") << D("c:", "", "", "c:"); + QTest::newRow("windows-folder") << D("c:/Windows", "", "", "c:/Windows"); + QTest::newRow("windows-folder-with-trailing-slash") << D("c:\\Windows\\", "", "", "c:/Windows"); + QTest::newRow("windows-folder-slash") << D("C:/Windows", "", "", "C:/Windows"); + + QTest::newRow("docker-root-url") << D("docker://1234/", "docker", "1234", "/"); + QTest::newRow("docker-root-url-special-linux") + << D("/__qtc_devices__/docker/1234/", "docker", "1234", "/"); + QTest::newRow("docker-root-url-special-win") + << D("c:/__qtc_devices__/docker/1234/", "docker", "1234", "/"); + QTest::newRow("docker-relative-path") + << D("docker://1234/./rel", "docker", "1234", "rel", FailEverywhere); + + QTest::newRow("qtc-dev-linux") << D("/__qtc_devices__", "", "", "/__qtc_devices__"); + QTest::newRow("qtc-dev-win") << D("c:/__qtc_devices__", "", "", "c:/__qtc_devices__"); + QTest::newRow("qtc-dev-type-linux") + << D("/__qtc_devices__/docker", "", "", "/__qtc_devices__/docker"); + QTest::newRow("qtc-dev-type-win") + << D("c:/__qtc_devices__/docker", "", "", "c:/__qtc_devices__/docker"); + QTest::newRow("qtc-dev-type-dev-linux") + << D("/__qtc_devices__/docker/1234", "docker", "1234", "/"); + QTest::newRow("qtc-dev-type-dev-win") + << D("c:/__qtc_devices__/docker/1234", "docker", "1234", "/"); + + // "Remote Windows" is currently truly not supported. + QTest::newRow("cross-os-linux") << D("/__qtc_devices__/docker/1234/c:/test.txt", + "docker", + "1234", + "c:/test.txt", + FailEverywhere); + QTest::newRow("cross-os-win") << D("c:/__qtc_devices__/docker/1234/c:/test.txt", + "docker", + "1234", + "c:/test.txt", + FailEverywhere); + QTest::newRow("cross-os-unclean-linux") << D("/__qtc_devices__/docker/1234/c:\\test.txt", + "docker", + "1234", + "c:/test.txt", + FailEverywhere); + QTest::newRow("cross-os-unclean-win") << D("c:/__qtc_devices__/docker/1234/c:\\test.txt", + "docker", + "1234", + "c:/test.txt", + FailEverywhere); + + QTest::newRow("unc-full-in-docker-linux") + << D("/__qtc_devices__/docker/1234//server/share/test.txt", + "docker", + "1234", + "//server/share/test.txt", + FailEverywhere); + QTest::newRow("unc-full-in-docker-win") + << D("c:/__qtc_devices__/docker/1234//server/share/test.txt", + "docker", + "1234", + "//server/share/test.txt", + FailEverywhere); + + QTest::newRow("unc-dos-1") << D("//?/c:", "", "", "c:"); + QTest::newRow("unc-dos-com") << D("//./com1", "", "", "//./com1"); +} + +void tst_filepath::fromUserInput() +{ + QFETCH(FromStringData, data); + + FilePath filePath = FilePath::fromUserInput(data.input); + + bool expectFail = ((data.expectedPass & FailOnLinux) && !HostOsInfo::isWindowsHost()) + || ((data.expectedPass & FailOnWindows) && HostOsInfo::isWindowsHost()); + + if (expectFail) { + QString actual = filePath.scheme() + '|' + filePath.host() + '|' + filePath.path(); + QString expected = data.scheme + '|' + data.host + '|' + data.path; + QEXPECT_FAIL("", "", Continue); + QCOMPARE(actual, expected); + return; + } + + QCOMPARE(filePath.scheme(), data.scheme); + QCOMPARE(filePath.host(), data.host); + QCOMPARE(filePath.path(), data.path); +} + +void tst_filepath::fromToString_data() +{ + QTest::addColumn("scheme"); + QTest::addColumn("host"); + QTest::addColumn("path"); + QTest::addColumn("full"); + + QTest::newRow("s0") << "" + << "" + << "" + << ""; + QTest::newRow("s1") << "" + << "" + << "/" + << "/"; + QTest::newRow("s2") << "" + << "" + << "a/b/c/d" + << "a/b/c/d"; + QTest::newRow("s3") << "" + << "" + << "/a/b" + << "/a/b"; + + QTest::newRow("s4") << "docker" + << "1234abcdef" + << "/bin/ls" + << "docker://1234abcdef/bin/ls"; + QTest::newRow("s5") << "docker" + << "1234" + << "/bin/ls" + << "docker://1234/bin/ls"; + + // This is not a proper URL. + QTest::newRow("s6") << "docker" + << "1234" + << "somefile" + << "docker://1234/./somefile"; + + // Local Windows paths: + QTest::newRow("w1") << "" + << "" + << "C:/data" + << "C:/data"; + QTest::newRow("w2") << "" + << "" + << "C:/" + << "C:/"; + QTest::newRow("w3") << "" + << "" + << "/Global?\?/UNC/host" + << "/Global?\?/UNC/host"; + QTest::newRow("w4") << "" + << "" + << "//server/dir/file" + << "//server/dir/file"; +} + +void tst_filepath::fromToString() +{ + QFETCH(QString, full); + QFETCH(QString, scheme); + QFETCH(QString, host); + QFETCH(QString, path); + + FilePath filePath = FilePath::fromString(full); + + QCOMPARE(filePath.toString(), full); + + QCOMPARE(filePath.scheme(), scheme); + QCOMPARE(filePath.host(), host); + QCOMPARE(filePath.path(), path); + + FilePath copy = FilePath::fromParts(scheme, host, path); + QCOMPARE(copy.toString(), full); +} + +void tst_filepath::comparison() +{ + QFETCH(QString, left); + QFETCH(QString, right); + QFETCH(bool, hostSensitive); + QFETCH(bool, expected); + + HostOsInfo::setOverrideFileNameCaseSensitivity(hostSensitive ? Qt::CaseSensitive + : Qt::CaseInsensitive); + + FilePath l = FilePath::fromUserInput(left); + FilePath r = FilePath::fromUserInput(right); + QCOMPARE(l == r, expected); +} + +void tst_filepath::comparison_data() +{ + QTest::addColumn("left"); + QTest::addColumn("right"); + QTest::addColumn("hostSensitive"); + QTest::addColumn("expected"); + + QTest::newRow("r1") << "Abc" + << "abc" << true << false; + QTest::newRow("r2") << "Abc" + << "abc" << false << true; + QTest::newRow("r3") << "x://y/Abc" + << "x://y/abc" << true << false; + QTest::newRow("r4") << "x://y/Abc" + << "x://y/abc" << false << false; + + QTest::newRow("s1") << "abc" + << "abc" << true << true; + QTest::newRow("s2") << "abc" + << "abc" << false << true; + QTest::newRow("s3") << "x://y/abc" + << "x://y/abc" << true << true; + QTest::newRow("s4") << "x://y/abc" + << "x://y/abc" << false << true; +} + +void tst_filepath::linkFromString() +{ + QFETCH(QString, testFile); + QFETCH(Utils::FilePath, filePath); + QFETCH(int, line); + QFETCH(int, column); + const Link link = Link::fromString(testFile, true); + QCOMPARE(link.targetFilePath, filePath); + QCOMPARE(link.targetLine, line); + QCOMPARE(link.targetColumn, column); +} + +void tst_filepath::linkFromString_data() +{ + QTest::addColumn("testFile"); + QTest::addColumn("filePath"); + QTest::addColumn("line"); + QTest::addColumn("column"); + + QTest::newRow("no-line-no-column") + << QString("someFile.txt") << FilePath("someFile.txt") << 0 << -1; + QTest::newRow(":line-:column") << QString::fromLatin1("/some/path/file.txt:42:3") + << FilePath("/some/path/file.txt") << 42 << 2; + QTest::newRow("(42) at end") << QString::fromLatin1("/some/path/file.txt(42)") + << FilePath("/some/path/file.txt") << 42 << 0; +} + +void tst_filepath::pathAppended() +{ + QFETCH(QString, left); + QFETCH(QString, right); + QFETCH(QString, expected); + + const FilePath fleft = FilePath::fromString(left); + const FilePath fexpected = FilePath::fromString(expected); + + const FilePath result = fleft.pathAppended(right); + + QCOMPARE(result, fexpected); +} + +void tst_filepath::pathAppended_data() +{ + QTest::addColumn("left"); + QTest::addColumn("right"); + QTest::addColumn("expected"); + + QTest::newRow("p0") << "" + << "" + << ""; + QTest::newRow("p1") << "" + << "/" + << "/"; + QTest::newRow("p2") << "" + << "c/" + << "c/"; + QTest::newRow("p3") << "" + << "/d" + << "/d"; + QTest::newRow("p4") << "" + << "c/d" + << "c/d"; + + QTest::newRow("r0") << "/" + << "" + << "/"; + QTest::newRow("r1") << "/" + << "/" + << "/"; + QTest::newRow("r2") << "/" + << "c/" + << "/c/"; + QTest::newRow("r3") << "/" + << "/d" + << "/d"; + QTest::newRow("r4") << "/" + << "c/d" + << "/c/d"; + + QTest::newRow("s0") << "/b" + << "" + << "/b"; + QTest::newRow("s1") << "/b" + << "/" + << "/b/"; + QTest::newRow("s2") << "/b" + << "c/" + << "/b/c/"; + QTest::newRow("s3") << "/b" + << "/d" + << "/b/d"; + QTest::newRow("s4") << "/b" + << "c/d" + << "/b/c/d"; + + QTest::newRow("t0") << "a/" + << "" + << "a/"; + QTest::newRow("t1") << "a/" + << "/" + << "a/"; + QTest::newRow("t2") << "a/" + << "c/" + << "a/c/"; + QTest::newRow("t3") << "a/" + << "/d" + << "a/d"; + QTest::newRow("t4") << "a/" + << "c/d" + << "a/c/d"; + + QTest::newRow("u0") << "a/b" + << "" + << "a/b"; + QTest::newRow("u1") << "a/b" + << "/" + << "a/b/"; + QTest::newRow("u2") << "a/b" + << "c/" + << "a/b/c/"; + QTest::newRow("u3") << "a/b" + << "/d" + << "a/b/d"; + QTest::newRow("u4") << "a/b" + << "c/d" + << "a/b/c/d"; + + if (HostOsInfo::isWindowsHost()) { + QTest::newRow("win-1") << "c:" + << "/a/b" + << "c:/a/b"; + QTest::newRow("win-2") << "c:/" + << "/a/b" + << "c:/a/b"; + QTest::newRow("win-3") << "c:/" + << "a/b" + << "c:/a/b"; + } +} + +void tst_filepath::resolvePath_data() +{ + QTest::addColumn("left"); + QTest::addColumn("right"); + QTest::addColumn("expected"); + + QTest::newRow("empty") << FilePath() << FilePath() << FilePath(); + QTest::newRow("s0") << FilePath("/") << FilePath("b") << FilePath("/b"); + QTest::newRow("s1") << FilePath() << FilePath("b") << FilePath("b"); + QTest::newRow("s2") << FilePath("a") << FilePath() << FilePath("a"); + QTest::newRow("s3") << FilePath("a") << FilePath("b") << FilePath("a/b"); + QTest::newRow("s4") << FilePath("/a") << FilePath("/b") << FilePath("/b"); + QTest::newRow("s5") << FilePath("a") << FilePath("/b") << FilePath("/b"); + QTest::newRow("s6") << FilePath("/a") << FilePath("b") << FilePath("/a/b"); + QTest::newRow("s7") << FilePath("/a") << FilePath(".") << FilePath("/a"); + QTest::newRow("s8") << FilePath("/a") << FilePath("./b") << FilePath("/a/b"); +} + +void tst_filepath::resolvePath() +{ + QFETCH(FilePath, left); + QFETCH(FilePath, right); + QFETCH(FilePath, expected); + + const FilePath result = left.resolvePath(right); + + QCOMPARE(result, expected); +} + +void tst_filepath::relativeChildPath_data() +{ + QTest::addColumn("parent"); + QTest::addColumn("child"); + QTest::addColumn("expected"); + + QTest::newRow("empty") << FilePath() << FilePath() << FilePath(); + + QTest::newRow("simple-0") << FilePath("/a") << FilePath("/a/b") << FilePath("b"); + QTest::newRow("simple-1") << FilePath("/a/") << FilePath("/a/b") << FilePath("b"); + QTest::newRow("simple-2") << FilePath("/a") << FilePath("/a/b/c/d/e/f") + << FilePath("b/c/d/e/f"); + + QTest::newRow("not-0") << FilePath("/x") << FilePath("/a/b") << FilePath(); + QTest::newRow("not-1") << FilePath("/a/b/c") << FilePath("/a/b") << FilePath(); +} + +void tst_filepath::relativeChildPath() +{ + QFETCH(FilePath, parent); + QFETCH(FilePath, child); + QFETCH(FilePath, expected); + + const FilePath result = child.relativeChildPath(parent); + + QCOMPARE(result, expected); +} + +void tst_filepath::asyncLocalCopy() +{ + const FilePath orig = FilePath::fromString(rootPath).pathAppended("x/y/fileToCopy.txt"); + QVERIFY(orig.exists()); + const FilePath dest = FilePath::fromString(rootPath).pathAppended("x/fileToCopyDest.txt"); + bool wasCalled = false; + // When QTRY_VERIFY failed, don't call the continuation after we leave this method + QObject context; + auto afterCopy = [&orig, &dest, &wasCalled](expected_str result) { + QVERIFY(result); + // check existence, size and content + QVERIFY(dest.exists()); + QCOMPARE(dest.fileSize(), orig.fileSize()); + QCOMPARE(dest.fileContents(), orig.fileContents()); + wasCalled = true; + }; + orig.asyncCopy(dest, &context, afterCopy); + QTRY_VERIFY(wasCalled); +} + +void tst_filepath::startsWithDriveLetter_data() +{ + QTest::addColumn("path"); + QTest::addColumn("expected"); + + QTest::newRow("empty") << FilePath() << false; + QTest::newRow("simple-win") << FilePath::fromString("c:/a") << true; + QTest::newRow("simple-linux") << FilePath::fromString("/c:/a") << false; + QTest::newRow("relative") << FilePath("a/b") << false; +} + +void tst_filepath::startsWithDriveLetter() +{ + QFETCH(FilePath, path); + QFETCH(bool, expected); + + QCOMPARE(path.startsWithDriveLetter(), expected); +} + +void tst_filepath::withNewMappedPath_data() +{ + QTest::addColumn("path"); + QTest::addColumn("templatePath"); + QTest::addColumn("expected"); + + QTest::newRow("empty") << FilePath() << FilePath() << FilePath(); + QTest::newRow("same-local") << FilePath("/a/b") << FilePath("/a/b") << FilePath("/a/b"); + QTest::newRow("same-docker") << FilePath("docker://1234/a/b") << FilePath("docker://1234/e") + << FilePath("docker://1234/a/b"); + + QTest::newRow("docker-to-local") + << FilePath("docker://1234/a/b") << FilePath("/c/d") << FilePath("/a/b"); + QTest::newRow("local-to-docker") + << FilePath("/a/b") << FilePath("docker://1234/c/d") << FilePath("docker://1234/a/b"); +} + +void tst_filepath::withNewMappedPath() +{ + QFETCH(FilePath, path); + QFETCH(FilePath, templatePath); + QFETCH(FilePath, expected); + + QCOMPARE(templatePath.withNewMappedPath(path), expected); +} + +void tst_filepath::stringAppended_data() +{ + QTest::addColumn("left"); + QTest::addColumn("right"); + QTest::addColumn("expected"); + + QTest::newRow("empty") << FilePath() << QString() << FilePath(); + QTest::newRow("empty-left") << FilePath() << "a" << FilePath("a"); + QTest::newRow("empty-right") << FilePath("a") << QString() << FilePath("a"); + QTest::newRow("add-root") << FilePath() << QString("/") << FilePath("/"); + QTest::newRow("add-root-and-more") + << FilePath() << QString("/test/blah") << FilePath("/test/blah"); + QTest::newRow("add-extension") + << FilePath::fromString("/a") << QString(".txt") << FilePath("/a.txt"); + QTest::newRow("trailing-slash") + << FilePath::fromString("/a") << QString("b/") << FilePath("/ab/"); + QTest::newRow("slash-trailing-slash") + << FilePath::fromString("/a/") << QString("b/") << FilePath("/a/b/"); +} + +void tst_filepath::stringAppended() +{ + QFETCH(FilePath, left); + QFETCH(QString, right); + QFETCH(FilePath, expected); + + const FilePath result = left.stringAppended(right); + + QCOMPARE(expected, result); +} + +void tst_filepath::url() +{ + QFETCH(QString, url); + QFETCH(QString, expectedScheme); + QFETCH(QString, expectedHost); + QFETCH(QString, expectedPath); + + const FilePath result = FilePath::fromString(url); + QCOMPARE(result.scheme(), expectedScheme); + QCOMPARE(result.host(), expectedHost); + QCOMPARE(result.path(), expectedPath); +} + +void tst_filepath::url_data() +{ + QTest::addColumn("url"); + QTest::addColumn("expectedScheme"); + QTest::addColumn("expectedHost"); + QTest::addColumn("expectedPath"); + QTest::newRow("empty") << QString() << QString() << QString() << QString(); + QTest::newRow("simple-file") << QString("file:///a/b") << QString("file") << QString() + << QString("/a/b"); + QTest::newRow("simple-file-root") + << QString("file:///") << QString("file") << QString() << QString("/"); + QTest::newRow("simple-docker") + << QString("docker://1234/a/b") << QString("docker") << QString("1234") << QString("/a/b"); + QTest::newRow("simple-ssh") << QString("ssh://user@host/a/b") << QString("ssh") + << QString("user@host") << QString("/a/b"); + QTest::newRow("simple-ssh-with-port") << QString("ssh://user@host:1234/a/b") << QString("ssh") + << QString("user@host:1234") << QString("/a/b"); + QTest::newRow("http-qt.io") << QString("http://qt.io") << QString("http") << QString("qt.io") + << QString(); + QTest::newRow("http-qt.io-index.html") << QString("http://qt.io/index.html") << QString("http") + << QString("qt.io") << QString("/index.html"); +} + +void tst_filepath::cleanPath_data() +{ + QTest::addColumn("path"); + QTest::addColumn("expected"); + + QTest::newRow("data0") << "/Users/sam/troll/qt4.0//.." + << "/Users/sam/troll"; + QTest::newRow("data1") << "/Users/sam////troll/qt4.0//.." + << "/Users/sam/troll"; + QTest::newRow("data2") << "/" + << "/"; + QTest::newRow("data2-up") << "/path/.." + << "/"; + QTest::newRow("data2-above-root") << "/.." + << "/.."; + QTest::newRow("data3") << QDir::cleanPath("../.") << ".."; + QTest::newRow("data4") << QDir::cleanPath("../..") << "../.."; + QTest::newRow("data5") << "d:\\a\\bc\\def\\.." + << "d:/a/bc"; // QDir/Linux had: "d:\\a\\bc\\def\\.." + QTest::newRow("data6") << "d:\\a\\bc\\def\\../../.." + << "d:/"; // QDir/Linux had: ".." + QTest::newRow("data7") << ".//file1.txt" + << "file1.txt"; + QTest::newRow("data8") << "/foo/bar/..//file1.txt" + << "/foo/file1.txt"; + QTest::newRow("data9") << "//" + << "//"; // QDir had: "/" + QTest::newRow("data10w") << "c:\\" + << "c:/"; + QTest::newRow("data10l") << "/:/" + << "/:"; + QTest::newRow("data11") << "//foo//bar" + << "//foo/bar"; // QDir/Win had: "//foo/bar" + QTest::newRow("data12") << "ab/a/" + << "ab/a"; // Path item with length of 2 + QTest::newRow("data13w") << "c:/" + << "c:/"; + QTest::newRow("data13w2") << "c:\\" + << "c:/"; + //QTest::newRow("data13l") << "c://" << "c:"; + + // QTest::newRow("data14") << "c://foo" << "c:/foo"; + QTest::newRow("data15") << "//c:/foo" + << "//c:/foo"; // QDir/Lin had: "/c:/foo"; + QTest::newRow("drive-up") << "A:/path/.." + << "A:/"; + QTest::newRow("drive-above-root") << "A:/.." + << "A:/.."; + QTest::newRow("unc-server-up") << "//server/path/.." + << "//server/"; + QTest::newRow("unc-server-above-root") << "//server/.." + << "//server/.."; + + QTest::newRow("longpath") << "\\\\?\\d:\\" + << "d:/"; + QTest::newRow("longpath-slash") << "//?/d:/" + << "d:/"; + QTest::newRow("longpath-mixed-slashes") << "//?/d:\\" + << "d:/"; + QTest::newRow("longpath-mixed-slashes-2") << "\\\\?\\d:/" + << "d:/"; + + QTest::newRow("unc-network-share") << "\\\\?\\UNC\\localhost\\c$\\tmp.txt" + << "//localhost/c$/tmp.txt"; + QTest::newRow("unc-network-share-slash") << "//?/UNC/localhost/c$/tmp.txt" + << "//localhost/c$/tmp.txt"; + QTest::newRow("unc-network-share-mixed-slashes") << "//?/UNC/localhost\\c$\\tmp.txt" + << "//localhost/c$/tmp.txt"; + QTest::newRow("unc-network-share-mixed-slashes-2") << "\\\\?\\UNC\\localhost/c$/tmp.txt" + << "//localhost/c$/tmp.txt"; + + QTest::newRow("QTBUG-23892_0") << "foo/.." + << "."; + QTest::newRow("QTBUG-23892_1") << "foo/../" + << "."; + + QTest::newRow("QTBUG-3472_0") << "/foo/./bar" + << "/foo/bar"; + QTest::newRow("QTBUG-3472_1") << "./foo/.." + << "."; + QTest::newRow("QTBUG-3472_2") << "./foo/../" + << "."; + + QTest::newRow("resource0") << ":/prefix/foo.bar" + << ":/prefix/foo.bar"; + QTest::newRow("resource1") << ":/prefix/..//prefix/foo.bar" + << ":/prefix/foo.bar"; + + QTest::newRow("ssh") << "ssh://host/prefix/../foo.bar" + << "ssh://host/foo.bar"; + QTest::newRow("ssh2") << "ssh://host/../foo.bar" + << "ssh://host/../foo.bar"; +} + +void tst_filepath::cleanPath() +{ + QFETCH(QString, path); + QFETCH(QString, expected); + QString cleaned = doCleanPath(path); + QCOMPARE(cleaned, expected); +} + +void tst_filepath::isSameFile_data() +{ + QTest::addColumn("left"); + QTest::addColumn("right"); + QTest::addColumn("shouldBeEqual"); + + QTest::addRow("/==/") << FilePath::fromString("/") << FilePath::fromString("/") << true; + QTest::addRow("/!=tmp") << FilePath::fromString("/") << FilePath::fromString(tempDir.path()) + << false; + + QDir dir(tempDir.path()); + touch(dir, "target-file", false); + + QFile file(dir.absoluteFilePath("target-file")); + if (file.link(dir.absoluteFilePath("source-file"))) { + QTest::addRow("real==link") + << FilePath::fromString(file.fileName()) + << FilePath::fromString(dir.absoluteFilePath("target-file")) << true; + } + + QTest::addRow("/!=non-existing") + << FilePath::fromString("/") << FilePath::fromString("/this-path/does-not-exist") << false; + + QTest::addRow("two-devices") << FilePath::fromString( + "docker://boot2qt-raspberrypi4-64:6.5.0/opt/toolchain/sysroots/aarch64-pokysdk-linux/usr/" + "bin/aarch64-poky-linux/aarch64-poky-linux-g++") + << FilePath::fromString("docker://qt-linux:6/usr/bin/g++") + << false; +} + +void tst_filepath::isSameFile() +{ + QFETCH(FilePath, left); + QFETCH(FilePath, right); + QFETCH(bool, shouldBeEqual); + + QCOMPARE(left.isSameFile(right), shouldBeEqual); +} + +void tst_filepath::hostSpecialChars_data() +{ + QTest::addColumn("scheme"); + QTest::addColumn("host"); + QTest::addColumn("path"); + QTest::addColumn("expected"); + + QTest::addRow("slash-in-host") << "device" + << "host/name" + << "/" << FilePath::fromString("device://host%2fname/"); + QTest::addRow("percent-in-host") << "device" + << "host%name" + << "/" << FilePath::fromString("device://host%25name/"); + QTest::addRow("percent-and-slash-in-host") + << "device" + << "host/%name" + << "/" << FilePath::fromString("device://host%2f%25name/"); + QTest::addRow("qtc-dev-slash-in-host-linux") + << "device" + << "host/name" + << "/" << FilePath::fromString("/__qtc_devices__/device/host%2fname/"); + QTest::addRow("qtc-dev-slash-in-host-windows") + << "device" + << "host/name" + << "/" << FilePath::fromString("c:/__qtc_devices__/device/host%2fname/"); +} + +void tst_filepath::hostSpecialChars() +{ + QFETCH(QString, scheme); + QFETCH(QString, host); + QFETCH(QString, path); + QFETCH(FilePath, expected); + + FilePath fp; + fp.setParts(scheme, host, path); + + // Check that setParts and fromString give the same result + QCOMPARE(fp, expected); + QCOMPARE(fp.host(), expected.host()); + QCOMPARE(fp.host(), host); + QCOMPARE(expected.host(), host); + + QString toStringExpected = expected.toString(); + QString toStringActual = fp.toString(); + + // Check that toString gives the same result + QCOMPARE(toStringActual, toStringExpected); + + // Check that fromString => toString => fromString gives the same result + FilePath toFromExpected = FilePath::fromString(expected.toString()); + QCOMPARE(toFromExpected, expected); + QCOMPARE(toFromExpected, fp); + + // Check that setParts => toString => fromString gives the same result + FilePath toFromActual = FilePath::fromString(fp.toString()); + QCOMPARE(toFromActual, fp); + QCOMPARE(toFromExpected, expected); +} + +void tst_filepath::tmp_data() +{ + QTest::addColumn("templatepath"); + QTest::addColumn("expected"); + + QTest::addRow("empty") << "" << true; + QTest::addRow("no-template") << "foo" << true; + QTest::addRow("realtive-template") << "my-file-XXXXXXXX" << true; + QTest::addRow("absolute-template") << QDir::tempPath() + "/my-file-XXXXXXXX" << true; + QTest::addRow("non-existing-dir") << "/this/path/does/not/exist/my-file-XXXXXXXX" << false; + + QTest::addRow("on-device") << "device://test/./my-file-XXXXXXXX" << true; +} + +void tst_filepath::tmp() +{ + QFETCH(QString, templatepath); + QFETCH(bool, expected); + + FilePath fp = FilePath::fromString(templatepath); + + const auto result = fp.createTempFile(); + QCOMPARE(result.has_value(), expected); + + if (result.has_value()) { + QVERIFY(result->exists()); + QVERIFY(result->removeFile()); + } +} + +void tst_filepath::sort() +{ + QFETCH(QStringList, input); + + FilePaths filePaths = Utils::transform(input, &FilePath::fromString); + QStringList sorted = input; + sorted.sort(); + + FilePath::sort(filePaths); + QStringList sortedPaths = Utils::transform(filePaths, &FilePath::toString); + + QCOMPARE(sortedPaths, sorted); +} + +void tst_filepath::sort_data() +{ + QTest::addColumn("input"); + + QTest::addRow("empty") << QStringList{}; + + QTest::addRow("one") << QStringList{"foo"}; + QTest::addRow("two") << QStringList{"foo", "bar"}; + QTest::addRow("three") << QStringList{"foo", "bar", "baz"}; + + QTest::addRow("one-absolute") << QStringList{"/foo"}; + QTest::addRow("two-absolute") << QStringList{"/foo", "/bar"}; + + QTest::addRow("one-relative") << QStringList{"foo"}; + + QTest::addRow("one-absolute-one-relative") << QStringList{"/foo", "bar"}; + + QTest::addRow("host") << QStringList{"ssh://test/blah", "ssh://gulp/blah", "ssh://zzz/blah"}; + + QTest::addRow("scheme") << QStringList{"ssh://test/blah", + "ssh://gulp/blah", + "ssh://zzz/blah", + "aaa://gulp/blah", + "xyz://test/blah"}; + + QTest::addRow("others") << QStringList{"a://a//a", + "a://b//a", + "a://a//b", + "a://b//b", + "b://b//b"}; + QTest::addRow("others-reversed") + << QStringList{"b://b//b", "a://b//b", "a://a//b", "a://b//a", "a://a//a"}; +} + +QTEST_GUILESS_MAIN(tst_filepath) + +#include "tst_filepath.moc" diff --git a/tests/auto/utils/fileutils/tst_fileutils.cpp b/tests/auto/utils/fileutils/tst_fileutils.cpp index 033cf680bea..198abf3398b 100644 --- a/tests/auto/utils/fileutils/tst_fileutils.cpp +++ b/tests/auto/utils/fileutils/tst_fileutils.cpp @@ -1,13 +1,10 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include -#include #include #include #include -#include //TESTED_COMPONENT=src/libs/utils using namespace Utils; @@ -24,948 +21,20 @@ class tst_fileutils : public QObject { Q_OBJECT -signals: - void asyncTestDone(); // test internal helper signal - private slots: void initTestCase(); - void isEmpty_data(); - void isEmpty(); - - void parentDir_data(); - void parentDir(); - - void isChildOf_data(); - void isChildOf(); - - void fileName_data(); - void fileName(); - - void calcRelativePath_data(); - void calcRelativePath(); - - void relativePath_specials(); - void relativePath_data(); - void relativePath(); - - void absolute_data(); - void absolute(); - - void fromToString_data(); - void fromToString(); - - void fromString_data(); - void fromString(); - - void fromUserInput_data(); - void fromUserInput(); - - void toString_data(); - void toString(); - - void toFSPathString_data(); - void toFSPathString(); - - void comparison_data(); - void comparison(); - - void linkFromString_data(); - void linkFromString(); - - void pathAppended_data(); - void pathAppended(); - void commonPath_data(); void commonPath(); - void resolvePath_data(); - void resolvePath(); - - void relativeChildPath_data(); - void relativeChildPath(); - void bytesAvailableFromDF_data(); void bytesAvailableFromDF(); - void rootLength_data(); - void rootLength(); - - void schemeAndHostLength_data(); - void schemeAndHostLength(); - - void asyncLocalCopy(); - void startsWithDriveLetter(); - void startsWithDriveLetter_data(); - - void onDevice_data(); - void onDevice(); - - void stringAppended(); - void stringAppended_data(); - void url(); - void url_data(); - - void cleanPath_data(); - void cleanPath(); - - void isSameFile_data(); - void isSameFile(); - - void hostSpecialChars_data(); - void hostSpecialChars(); - - void tmp_data(); - void tmp(); - void filePathInfoFromTriple_data(); void filePathInfoFromTriple(); - -private: - QTemporaryDir tempDir; - QString rootPath; }; -static void touch(const QDir &dir, const QString &filename, bool fill) -{ - QFile file(dir.absoluteFilePath(filename)); - file.open(QIODevice::WriteOnly); - if (fill) { - QRandomGenerator *random = QRandomGenerator::global(); - for (int i = 0; i < 10; ++i) - file.write(QString::number(random->generate(), 16).toUtf8()); - } - file.close(); -} - -void tst_fileutils::initTestCase() -{ - // initialize test for tst_fileutiles::relativePath*() - QVERIFY(tempDir.isValid()); - rootPath = tempDir.path(); - QDir dir(rootPath); - dir.mkpath("a/b/c/d"); - dir.mkpath("a/x/y/z"); - dir.mkpath("a/b/x/y/z"); - dir.mkpath("x/y/z"); - touch(dir, "a/b/c/d/file1.txt", false); - touch(dir, "a/x/y/z/file2.txt", false); - touch(dir, "a/file3.txt", false); - touch(dir, "x/y/file4.txt", false); - - // initialize test for tst_fileutils::asyncLocalCopy() - touch(dir, "x/y/fileToCopy.txt", true); -} - -void tst_fileutils::isEmpty_data() -{ - QTest::addColumn("path"); - QTest::addColumn("result"); - - QTest::newRow("empty path") << "" << true; - QTest::newRow("root only") << "/" << false; - QTest::newRow("//") << "//" << false; - QTest::newRow("scheme://host") << "scheme://host" << true; // Intentional (for now?) - QTest::newRow("scheme://host/") << "scheme://host/" << false; - QTest::newRow("scheme://host/a") << "scheme://host/a" << false; - QTest::newRow("scheme://host/.") << "scheme://host/." << false; -} - -void tst_fileutils::isEmpty() -{ - QFETCH(QString, path); - QFETCH(bool, result); - - FilePath filePath = FilePath::fromString(path); - QCOMPARE(filePath.isEmpty(), result); -} - -void tst_fileutils::parentDir_data() -{ - QTest::addColumn("path"); - QTest::addColumn("parentPath"); - QTest::addColumn("expectFailMessage"); - - QTest::newRow("empty path") << "" << "" << ""; - QTest::newRow("root only") << "/" << "" << ""; - QTest::newRow("//") << "//" << "" << ""; - QTest::newRow("/tmp/dir") << "/tmp/dir" << "/tmp" << ""; - QTest::newRow("relative/path") << "relative/path" << "relative" << ""; - QTest::newRow("relativepath") << "relativepath" << "." << ""; - - // Windows stuff: - QTest::newRow("C:/data") << "C:/data" << "C:/" << ""; - QTest::newRow("C:/") << "C:/" << "" << ""; - QTest::newRow("//./com1") << "//./com1" << "//./" << ""; - QTest::newRow("//?/path") << "//?/path" << "/" << "Qt 4 cannot handle this path."; - QTest::newRow("/Global?\?/UNC/host") << "/Global?\?/UNC/host" << "/Global?\?/UNC/host" - << "Qt 4 cannot handle this path."; - QTest::newRow("//server/directory/file") - << "//server/directory/file" << "//server/directory" << ""; - QTest::newRow("//server/directory") << "//server/directory" << "//server/" << ""; - QTest::newRow("//server") << "//server" << "" << ""; - - QTest::newRow("qrc") << ":/foo/bar.txt" << ":/foo" << ""; -} - -void tst_fileutils::parentDir() -{ - QFETCH(QString, path); - QFETCH(QString, parentPath); - QFETCH(QString, expectFailMessage); - - FilePath result = FilePath::fromUserInput(path).parentDir(); - if (!expectFailMessage.isEmpty()) - QEXPECT_FAIL("", expectFailMessage.toUtf8().constData(), Continue); - QCOMPARE(result.toString(), parentPath); -} - -void tst_fileutils::isChildOf_data() -{ - QTest::addColumn("path"); - QTest::addColumn("childPath"); - QTest::addColumn("result"); - - QTest::newRow("empty path") << "" << "/tmp" << false; - QTest::newRow("root only") << "/" << "/tmp" << true; - QTest::newRow("/tmp/dir") << "/tmp" << "/tmp/dir" << true; - QTest::newRow("relative/path") << "relative" << "relative/path" << true; - QTest::newRow("/tmpdir") << "/tmp" << "/tmpdir" << false; - QTest::newRow("same") << "/tmp/dir" << "/tmp/dir" << false; - - // Windows stuff: - QTest::newRow("C:/data") << "C:/" << "C:/data" << true; - QTest::newRow("C:/") << "" << "C:/" << false; - QTest::newRow("com-port") << "//./" << "//./com1" << true; - QTest::newRow("extended-length-path") << "\\\\?\\C:\\" << "\\\\?\\C:\\path" << true; - QTest::newRow("/Global?\?/UNC/host") << "/Global?\?/UNC/host" - << "/Global?\?/UNC/host/file" << true; - QTest::newRow("//server/directory/file") - << "//server/directory" << "//server/directory/file" << true; - QTest::newRow("//server/directory") - << "//server" << "//server/directory" << true; - - QTest::newRow("qrc") << ":/foo/bar" << ":/foo/bar/blah" << true; -} - -void tst_fileutils::isChildOf() -{ - QFETCH(QString, path); - QFETCH(QString, childPath); - QFETCH(bool, result); - - const FilePath child = FilePath::fromUserInput(childPath); - const FilePath parent = FilePath::fromUserInput(path); - - QCOMPARE(child.isChildOf(parent), result); -} - -void tst_fileutils::fileName_data() -{ - QTest::addColumn("path"); - QTest::addColumn("components"); - QTest::addColumn("result"); - - QTest::newRow("empty 1") << "" << 0 << ""; - QTest::newRow("empty 2") << "" << 1 << ""; - QTest::newRow("basic") << "/foo/bar/baz" << 0 << "baz"; - QTest::newRow("2 parts") << "/foo/bar/baz" << 1 << "bar/baz"; - QTest::newRow("root no depth") << "/foo" << 0 << "foo"; - QTest::newRow("root full") << "/foo" << 1 << "/foo"; - QTest::newRow("root included") << "/foo/bar/baz" << 2 << "/foo/bar/baz"; - QTest::newRow("too many parts") << "/foo/bar/baz" << 5 << "/foo/bar/baz"; - QTest::newRow("windows root") << "C:/foo/bar/baz" << 2 << "C:/foo/bar/baz"; - QTest::newRow("smb share") << "//server/share/file" << 2 << "//server/share/file"; - QTest::newRow("no slashes") << "foobar" << 0 << "foobar"; - QTest::newRow("no slashes with depth") << "foobar" << 1 << "foobar"; - QTest::newRow("multiple slashes 1") << "/foo/bar////baz" << 0 << "baz"; - QTest::newRow("multiple slashes 2") << "/foo/bar////baz" << 1 << "bar////baz"; - QTest::newRow("multiple slashes 3") << "/foo////bar/baz" << 2 << "/foo////bar/baz"; - QTest::newRow("single char 1") << "/a/b/c" << 0 << "c"; - QTest::newRow("single char 2") << "/a/b/c" << 1 << "b/c"; - QTest::newRow("single char 3") << "/a/b/c" << 2 << "/a/b/c"; - QTest::newRow("slash at end 1") << "/a/b/" << 0 << ""; - QTest::newRow("slash at end 2") << "/a/b/" << 1 << "b/"; - QTest::newRow("slashes at end 1") << "/a/b//" << 0 << ""; - QTest::newRow("slashes at end 2") << "/a/b//" << 1 << "b//"; - QTest::newRow("root only 1") << "/" << 0 << ""; - QTest::newRow("root only 2") << "/" << 1 << "/"; - QTest::newRow("qrc 0") << ":/foo/bar" << 0 << "bar"; - QTest::newRow("qrc with root") << ":/foo/bar" << 1 << ":/foo/bar"; -} - -void tst_fileutils::fileName() -{ - QFETCH(QString, path); - QFETCH(int, components); - QFETCH(QString, result); - QCOMPARE(FilePath::fromString(path).fileNameWithPathComponents(components), result); -} - -void tst_fileutils::calcRelativePath_data() -{ - QTest::addColumn("absolutePath"); - QTest::addColumn("anchorPath"); - QTest::addColumn("result"); - - QTest::newRow("empty") << "" << "" << ""; - QTest::newRow("leftempty") << "" << "/" << ""; - QTest::newRow("rightempty") << "/" << "" << ""; - QTest::newRow("root") << "/" << "/" << "."; - QTest::newRow("simple1") << "/a" << "/" << "a"; - QTest::newRow("simple2") << "/" << "/a" << ".."; - QTest::newRow("simple3") << "/a" << "/a" << "."; - QTest::newRow("extraslash1") << "/a/b/c" << "/a/b/c" << "."; - QTest::newRow("extraslash2") << "/a/b/c" << "/a/b/c/" << "."; - QTest::newRow("extraslash3") << "/a/b/c/" << "/a/b/c" << "."; - QTest::newRow("normal1") << "/a/b/c" << "/a/x" << "../b/c"; - QTest::newRow("normal2") << "/a/b/c" << "/a/x/y" << "../../b/c"; - QTest::newRow("normal3") << "/a/b/c" << "/x/y" << "../../a/b/c"; -} - -void tst_fileutils::calcRelativePath() -{ - QFETCH(QString, absolutePath); - QFETCH(QString, anchorPath); - QFETCH(QString, result); - QString relativePath = Utils::FilePath::calcRelativePath(absolutePath, anchorPath); - QCOMPARE(relativePath, result); -} - -void tst_fileutils::relativePath_specials() -{ - QString path = FilePath("").relativePathFrom("").toString(); - QCOMPARE(path, ""); -} - -void tst_fileutils::relativePath_data() -{ - QTest::addColumn("relative"); - QTest::addColumn("anchor"); - QTest::addColumn("result"); - - QTest::newRow("samedir") << "/" << "/" << "."; - QTest::newRow("samedir_but_file") << "a/b/c/d/file1.txt" << "a/b/c/d" << "file1.txt"; - QTest::newRow("samedir_but_file2") << "a/b/c/d" << "a/b/c/d/file1.txt" << "."; - QTest::newRow("dir2dir_1") << "a/b/c/d" << "a/x/y/z" << "../../../b/c/d"; - QTest::newRow("dir2dir_2") << "a/b" <<"a/b/c" << ".."; - QTest::newRow("file2file_1") << "a/b/c/d/file1.txt" << "a/file3.txt" << "b/c/d/file1.txt"; - QTest::newRow("dir2file_1") << "a/b/c" << "a/x/y/z/file2.txt" << "../../../b/c"; - QTest::newRow("file2dir_1") << "a/b/c/d/file1.txt" << "x/y" << "../../a/b/c/d/file1.txt"; -} - -void tst_fileutils::relativePath() -{ - QFETCH(QString, relative); - QFETCH(QString, anchor); - QFETCH(QString, result); - FilePath actualPath = FilePath::fromString(rootPath + "/" + relative) - .relativePathFrom(FilePath::fromString(rootPath + "/" + anchor)); - QCOMPARE(actualPath.toString(), result); -} - -void tst_fileutils::rootLength_data() -{ - QTest::addColumn("path"); - QTest::addColumn("result"); - - QTest::newRow("empty") << "" << 0; - QTest::newRow("slash") << "/" << 1; - QTest::newRow("slash-rest") << "/abc" << 1; - QTest::newRow("rest") << "abc" << 0; - QTest::newRow("drive-slash") << "x:/" << 3; - QTest::newRow("drive-rest") << "x:abc" << 0; - QTest::newRow("drive-slash-rest") << "x:/abc" << 3; - - QTest::newRow("unc-root") << "//" << 2; - QTest::newRow("unc-localhost-unfinished") << "//localhost" << 11; - QTest::newRow("unc-localhost") << "//localhost/" << 12; - QTest::newRow("unc-localhost-rest") << "//localhost/abs" << 12; - QTest::newRow("unc-localhost-drive") << "//localhost/c$" << 12; - QTest::newRow("unc-localhost-drive-slash") << "//localhost//c$/" << 12; - QTest::newRow("unc-localhost-drive-slash-rest") << "//localhost//c$/x" << 12; -} - -void tst_fileutils::rootLength() -{ - QFETCH(QString, path); - QFETCH(int, result); - - int actual = FilePath::rootLength(path); - QCOMPARE(actual, result); -} - -void tst_fileutils::schemeAndHostLength_data() -{ - QTest::addColumn("path"); - QTest::addColumn("result"); - - QTest::newRow("empty") << "" << 0; - QTest::newRow("drive-slash-rest") << "x:/abc" << 0; - QTest::newRow("rest") << "abc" << 0; - QTest::newRow("slash-rest") << "/abc" << 0; - QTest::newRow("dev-empty") << "dev://" << 6; - QTest::newRow("dev-localhost-unfinished") << "dev://localhost" << 15; - QTest::newRow("dev-localhost") << "dev://localhost/" << 16; - QTest::newRow("dev-localhost-rest") << "dev://localhost/abs" << 16; - QTest::newRow("dev-localhost-drive") << "dev://localhost/c$" << 16; - QTest::newRow("dev-localhost-drive-slash") << "dev://localhost//c$/" << 16; - QTest::newRow("dev-localhost-drive-slash-rest") << "dev://localhost//c$/x" << 16; -} - -void tst_fileutils::schemeAndHostLength() -{ - QFETCH(QString, path); - QFETCH(int, result); - - int actual = FilePath::schemeAndHostLength(path); - QCOMPARE(actual, result); -} - -void tst_fileutils::absolute_data() -{ - QTest::addColumn("path"); - QTest::addColumn("absoluteFilePath"); - QTest::addColumn("absolutePath"); - - QTest::newRow("absolute1") << FilePath::fromString("/") - << FilePath::fromString("/") - << FilePath::fromString("/"); - QTest::newRow("absolute2") << FilePath::fromString("C:/a/b") - << FilePath::fromString("C:/a/b") - << FilePath::fromString("C:/a"); - QTest::newRow("absolute3") << FilePath::fromString("/a/b") - << FilePath::fromString("/a/b") - << FilePath::fromString("/a"); - QTest::newRow("absolute4") << FilePath::fromString("/a/b/..") - << FilePath::fromString("/a") - << FilePath::fromString("/"); - QTest::newRow("absolute5") << FilePath::fromString("/a/b/c/../d") - << FilePath::fromString("/a/b/d") - << FilePath::fromString("/a/b"); - QTest::newRow("absolute6") << FilePath::fromString("/a/../b/c/d") - << FilePath::fromString("/b/c/d") - << FilePath::fromString("/b/c"); - QTest::newRow("default-constructed") << FilePath() << FilePath() << FilePath(); - QTest::newRow("relative") << FilePath::fromString("a/b") - << FilePath::fromString(QDir::currentPath() + "/a/b") - << FilePath::fromString(QDir::currentPath() + "/a"); - QTest::newRow("qrc") << FilePath::fromString(":/foo/bar.txt") - << FilePath::fromString(":/foo/bar.txt") - << FilePath::fromString(":/foo"); -} - -void tst_fileutils::absolute() -{ - QFETCH(FilePath, path); - QFETCH(FilePath, absoluteFilePath); - QFETCH(FilePath, absolutePath); - QCOMPARE(path.absoluteFilePath(), absoluteFilePath); - QCOMPARE(path.absolutePath(), absolutePath); -} - -void tst_fileutils::toString_data() -{ - QTest::addColumn("scheme"); - QTest::addColumn("host"); - QTest::addColumn("path"); - QTest::addColumn("result"); - QTest::addColumn("userResult"); - - QTest::newRow("empty") << "" << "" << "" << "" << ""; - QTest::newRow("scheme") << "http" << "" << "" << "http:///./" << "http:///./"; - QTest::newRow("scheme-and-host") << "http" << "127.0.0.1" << "" << "http://127.0.0.1/./" << "http://127.0.0.1/./"; - QTest::newRow("root") << "http" << "127.0.0.1" << "/" << "http://127.0.0.1/" << "http://127.0.0.1/"; - - QTest::newRow("root-folder") << "" << "" << "/" << "/" << "/"; - QTest::newRow("qtc-dev-root-folder-linux") << "" << "" << "/__qtc_devices__" << "/__qtc_devices__" << "/__qtc_devices__"; - QTest::newRow("qtc-dev-root-folder-win") << "" << "" << "c:/__qtc_devices__" << "c:/__qtc_devices__" << "c:/__qtc_devices__"; - QTest::newRow("qtc-dev-type-root-folder-linux") << "" << "" << "/__qtc_devices__/docker" << "/__qtc_devices__/docker" << "/__qtc_devices__/docker"; - QTest::newRow("qtc-dev-type-root-folder-win") << "" << "" << "c:/__qtc_devices__/docker" << "c:/__qtc_devices__/docker" << "c:/__qtc_devices__/docker"; - QTest::newRow("qtc-root-folder") << "docker" << "alpine:latest" << "/" << "docker://alpine:latest/" << "docker://alpine:latest/"; - QTest::newRow("qtc-root-folder-rel") << "docker" << "alpine:latest" << "" << "docker://alpine:latest/./" << "docker://alpine:latest/./"; -} - -void tst_fileutils::toString() -{ - QFETCH(QString, scheme); - QFETCH(QString, host); - QFETCH(QString, path); - QFETCH(QString, result); - QFETCH(QString, userResult); - - FilePath filePath = FilePath::fromParts(scheme, host, path); - QCOMPARE(filePath.toString(), result); - QString cleanedOutput = filePath.needsDevice() ? filePath.toUserOutput() : QDir::cleanPath(filePath.toUserOutput()); - QCOMPARE(cleanedOutput, userResult); -} - -void tst_fileutils::toFSPathString_data() -{ - QTest::addColumn("scheme"); - QTest::addColumn("host"); - QTest::addColumn("path"); - QTest::addColumn("result"); - QTest::addColumn("userResult"); - - QTest::newRow("empty") << "" << "" << "" << "" << ""; - QTest::newRow("scheme") << "http" << "" << "" << QDir::rootPath() + "__qtc_devices__/http//./" << "http:///./"; - QTest::newRow("scheme-and-host") << "http" << "127.0.0.1" << "" << QDir::rootPath() + "__qtc_devices__/http/127.0.0.1/./" << "http://127.0.0.1/./"; - QTest::newRow("root") << "http" << "127.0.0.1" << "/" << QDir::rootPath() + "__qtc_devices__/http/127.0.0.1/" << "http://127.0.0.1/"; - - QTest::newRow("root-folder") << "" << "" << "/" << "/" << "/"; - QTest::newRow("qtc-dev-root-folder") << "" << "" << QDir::rootPath() + "__qtc_devices__" << QDir::rootPath() + "__qtc_devices__" << QDir::rootPath() + "__qtc_devices__"; - QTest::newRow("qtc-dev-type-root-folder") << "" << "" << QDir::rootPath() + "__qtc_devices__/docker" << QDir::rootPath() + "__qtc_devices__/docker" << QDir::rootPath() + "__qtc_devices__/docker"; - QTest::newRow("qtc-root-folder") << "docker" << "alpine:latest" << "/" << QDir::rootPath() + "__qtc_devices__/docker/alpine:latest/" << "docker://alpine:latest/"; - QTest::newRow("qtc-root-folder-rel") << "docker" << "alpine:latest" << "" << QDir::rootPath() + "__qtc_devices__/docker/alpine:latest/./" << "docker://alpine:latest/./"; -} - -void tst_fileutils::toFSPathString() -{ - QFETCH(QString, scheme); - QFETCH(QString, host); - QFETCH(QString, path); - QFETCH(QString, result); - QFETCH(QString, userResult); - - FilePath filePath = FilePath::fromParts(scheme, host, path); - QCOMPARE(filePath.toFSPathString(), result); - QString cleanedOutput = filePath.needsDevice() ? filePath.toUserOutput() : QDir::cleanPath(filePath.toUserOutput()); - QCOMPARE(cleanedOutput, userResult); -} - -enum ExpectedPass -{ - PassEverywhere = 0, - FailOnWindows = 1, - FailOnLinux = 2, - FailEverywhere = 3 -}; - -class FromStringData -{ -public: - FromStringData(const QString &input, const QString &scheme, const QString &host, - const QString &path, ExpectedPass expectedPass = PassEverywhere) - : input(input), scheme(scheme), host(host), - path(path), expectedPass(expectedPass) - {} - - QString input; - QString scheme; - QString host; - QString path; - ExpectedPass expectedPass = PassEverywhere; -}; - -Q_DECLARE_METATYPE(FromStringData); - -void tst_fileutils::fromString_data() -{ - using D = FromStringData; - QTest::addColumn("data"); - - QTest::newRow("empty") << D("", "", "", ""); - QTest::newRow("single-colon") << D(":", "", "", ":"); - QTest::newRow("single-slash") << D("/", "", "", "/"); - QTest::newRow("single-char") << D("a", "", "", "a"); - QTest::newRow("relative") << D("./rel", "", "", "./rel"); - QTest::newRow("qrc") << D(":/test.txt", "", "", ":/test.txt"); - QTest::newRow("qrc-no-slash") << D(":test.txt", "", "", ":test.txt"); - - QTest::newRow("unc-incomplete") << D("//", "", "", "//"); - QTest::newRow("unc-incomplete-only-server") << D("//server", "", "", "//server"); - QTest::newRow("unc-incomplete-only-server-2") << D("//server/", "", "", "//server/"); - QTest::newRow("unc-server-and-share") << D("//server/share", "", "", "//server/share"); - QTest::newRow("unc-server-and-share-2") << D("//server/share/", "", "", "//server/share/"); - QTest::newRow("unc-full") << D("//server/share/test.txt", "", "", "//server/share/test.txt"); - - QTest::newRow("unix-root") << D("/", "", "", "/"); - QTest::newRow("unix-folder") << D("/tmp", "", "", "/tmp"); - QTest::newRow("unix-folder-with-trailing-slash") << D("/tmp/", "", "", "/tmp/"); - - QTest::newRow("windows-root") << D("c:", "", "", "c:"); - QTest::newRow("windows-folder") << D("c:/Windows", "", "", "c:/Windows"); - QTest::newRow("windows-folder-with-trailing-slash") << D("c:/Windows/", "", "", "c:/Windows/"); - QTest::newRow("windows-folder-slash") << D("C:/Windows", "", "", "C:/Windows"); - - QTest::newRow("docker-root-url") << D("docker://1234/", "docker", "1234", "/"); - QTest::newRow("docker-root-url-special-linux") << D("/__qtc_devices__/docker/1234/", "docker", "1234", "/"); - QTest::newRow("docker-root-url-special-win") << D("c:/__qtc_devices__/docker/1234/", "docker", "1234", "/"); - QTest::newRow("docker-relative-path") << D("docker://1234/./rel", "docker", "1234", "rel"); - - QTest::newRow("qtc-dev-linux") << D("/__qtc_devices__", "", "", "/__qtc_devices__"); - QTest::newRow("qtc-dev-win") << D("c:/__qtc_devices__", "", "", "c:/__qtc_devices__"); - QTest::newRow("qtc-dev-type-linux") << D("/__qtc_devices__/docker", "", "", "/__qtc_devices__/docker"); - QTest::newRow("qtc-dev-type-win") << D("c:/__qtc_devices__/docker", "", "", "c:/__qtc_devices__/docker"); - QTest::newRow("qtc-dev-type-dev-linux") << D("/__qtc_devices__/docker/1234", "docker", "1234", "/"); - QTest::newRow("qtc-dev-type-dev-win") << D("c:/__qtc_devices__/docker/1234", "docker", "1234", "/"); - - // "Remote Windows" is currently truly not supported. - QTest::newRow("cross-os-linux") - << D("/__qtc_devices__/docker/1234/c:/test.txt", "docker", "1234", "c:/test.txt", FailEverywhere); - QTest::newRow("cross-os-win") - << D("c:/__qtc_devices__/docker/1234/c:/test.txt", "docker", "1234", "c:/test.txt", FailEverywhere); - QTest::newRow("cross-os-unclean-linux") - << D("/__qtc_devices__/docker/1234/c:\\test.txt", "docker", "1234", "c:/test.txt", FailEverywhere); - QTest::newRow("cross-os-unclean-win") - << D("c:/__qtc_devices__/docker/1234/c:\\test.txt", "docker", "1234", "c:/test.txt", FailEverywhere); - - QTest::newRow("unc-full-in-docker-linux") - << D("/__qtc_devices__/docker/1234//server/share/test.txt", "docker", "1234", "//server/share/test.txt"); - QTest::newRow("unc-full-in-docker-win") - << D("c:/__qtc_devices__/docker/1234//server/share/test.txt", "docker", "1234", "//server/share/test.txt"); - - QTest::newRow("unc-dos-1") << D("//?/c:", "", "", "//?/c:"); - QTest::newRow("unc-dos-com") << D("//./com1", "", "", "//./com1"); -} - -void tst_fileutils::fromString() -{ - QFETCH(FromStringData, data); - - FilePath filePath = FilePath::fromString(data.input); - - bool expectFail = ((data.expectedPass & FailOnLinux) && !HostOsInfo::isWindowsHost()) - || ((data.expectedPass & FailOnWindows) && HostOsInfo::isWindowsHost()); - - if (expectFail) { - QString actual = filePath.scheme() + '|' + filePath.host() + '|' + filePath.path(); - QString expected = data.scheme + '|' + data.host + '|' + data.path; - QEXPECT_FAIL("", "", Continue); - QCOMPARE(actual, expected); - return; - } - - QCOMPARE(filePath.scheme(), data.scheme); - QCOMPARE(filePath.host(), data.host); - QCOMPARE(filePath.path(), data.path); -} - -void tst_fileutils::fromUserInput_data() -{ - using D = FromStringData; - QTest::addColumn("data"); - - QTest::newRow("empty") << D("", "", "", ""); - QTest::newRow("single-colon") << D(":", "", "", ":"); - QTest::newRow("single-slash") << D("/", "", "", "/"); - QTest::newRow("single-char") << D("a", "", "", "a"); - QTest::newRow("relative") << D("./rel", "", "", "rel"); - QTest::newRow("qrc") << D(":/test.txt", "", "", ":/test.txt"); - QTest::newRow("qrc-no-slash") << D(":test.txt", "", "", ":test.txt"); - - QTest::newRow("unc-incomplete") << D("//", "", "", "//"); - QTest::newRow("unc-incomplete-only-server") << D("//server", "", "", "//server"); - QTest::newRow("unc-incomplete-only-server-2") << D("//server/", "", "", "//server/"); - QTest::newRow("unc-server-and-share") << D("//server/share", "", "", "//server/share"); - QTest::newRow("unc-server-and-share-2") << D("//server/share/", "", "", "//server/share"); - QTest::newRow("unc-full") << D("//server/share/test.txt", "", "", "//server/share/test.txt"); - - QTest::newRow("unix-root") << D("/", "", "", "/"); - QTest::newRow("unix-folder") << D("/tmp", "", "", "/tmp"); - QTest::newRow("unix-folder-with-trailing-slash") << D("/tmp/", "", "", "/tmp"); - - QTest::newRow("windows-root") << D("c:", "", "", "c:"); - QTest::newRow("windows-folder") << D("c:/Windows", "", "", "c:/Windows"); - QTest::newRow("windows-folder-with-trailing-slash") << D("c:\\Windows\\", "", "", "c:/Windows"); - QTest::newRow("windows-folder-slash") << D("C:/Windows", "", "", "C:/Windows"); - - QTest::newRow("docker-root-url") << D("docker://1234/", "docker", "1234", "/"); - QTest::newRow("docker-root-url-special-linux") << D("/__qtc_devices__/docker/1234/", "docker", "1234", "/"); - QTest::newRow("docker-root-url-special-win") << D("c:/__qtc_devices__/docker/1234/", "docker", "1234", "/"); - QTest::newRow("docker-relative-path") << D("docker://1234/./rel", "docker", "1234", "rel", FailEverywhere); - - QTest::newRow("qtc-dev-linux") << D("/__qtc_devices__", "", "", "/__qtc_devices__"); - QTest::newRow("qtc-dev-win") << D("c:/__qtc_devices__", "", "", "c:/__qtc_devices__"); - QTest::newRow("qtc-dev-type-linux") << D("/__qtc_devices__/docker", "", "", "/__qtc_devices__/docker"); - QTest::newRow("qtc-dev-type-win") << D("c:/__qtc_devices__/docker", "", "", "c:/__qtc_devices__/docker"); - QTest::newRow("qtc-dev-type-dev-linux") << D("/__qtc_devices__/docker/1234", "docker", "1234", "/"); - QTest::newRow("qtc-dev-type-dev-win") << D("c:/__qtc_devices__/docker/1234", "docker", "1234", "/"); - - // "Remote Windows" is currently truly not supported. - QTest::newRow("cross-os-linux") - << D("/__qtc_devices__/docker/1234/c:/test.txt", "docker", "1234", "c:/test.txt", FailEverywhere); - QTest::newRow("cross-os-win") - << D("c:/__qtc_devices__/docker/1234/c:/test.txt", "docker", "1234", "c:/test.txt", FailEverywhere); - QTest::newRow("cross-os-unclean-linux") - << D("/__qtc_devices__/docker/1234/c:\\test.txt", "docker", "1234", "c:/test.txt", FailEverywhere); - QTest::newRow("cross-os-unclean-win") - << D("c:/__qtc_devices__/docker/1234/c:\\test.txt", "docker", "1234", "c:/test.txt", FailEverywhere); - - QTest::newRow("unc-full-in-docker-linux") - << D("/__qtc_devices__/docker/1234//server/share/test.txt", "docker", "1234", "//server/share/test.txt", FailEverywhere); - QTest::newRow("unc-full-in-docker-win") - << D("c:/__qtc_devices__/docker/1234//server/share/test.txt", "docker", "1234", "//server/share/test.txt", FailEverywhere); - - QTest::newRow("unc-dos-1") << D("//?/c:", "", "", "c:"); - QTest::newRow("unc-dos-com") << D("//./com1", "", "", "//./com1"); -} - -void tst_fileutils::fromUserInput() -{ - QFETCH(FromStringData, data); - - FilePath filePath = FilePath::fromUserInput(data.input); - - bool expectFail = ((data.expectedPass & FailOnLinux) && !HostOsInfo::isWindowsHost()) - || ((data.expectedPass & FailOnWindows) && HostOsInfo::isWindowsHost()); - - if (expectFail) { - QString actual = filePath.scheme() + '|' + filePath.host() + '|' + filePath.path(); - QString expected = data.scheme + '|' + data.host + '|' + data.path; - QEXPECT_FAIL("", "", Continue); - QCOMPARE(actual, expected); - return; - } - - QCOMPARE(filePath.scheme(), data.scheme); - QCOMPARE(filePath.host(), data.host); - QCOMPARE(filePath.path(), data.path); -} - -void tst_fileutils::fromToString_data() -{ - QTest::addColumn("scheme"); - QTest::addColumn("host"); - QTest::addColumn("path"); - QTest::addColumn("full"); - - QTest::newRow("s0") << "" << "" << "" << ""; - QTest::newRow("s1") << "" << "" << "/" << "/"; - QTest::newRow("s2") << "" << "" << "a/b/c/d" << "a/b/c/d"; - QTest::newRow("s3") << "" << "" << "/a/b" << "/a/b"; - - QTest::newRow("s4") << "docker" << "1234abcdef" << "/bin/ls" << "docker://1234abcdef/bin/ls"; - QTest::newRow("s5") << "docker" << "1234" << "/bin/ls" << "docker://1234/bin/ls"; - - // This is not a proper URL. - QTest::newRow("s6") << "docker" << "1234" << "somefile" << "docker://1234/./somefile"; - - // Local Windows paths: - QTest::newRow("w1") << "" << "" << "C:/data" << "C:/data"; - QTest::newRow("w2") << "" << "" << "C:/" << "C:/"; - QTest::newRow("w3") << "" << "" << "/Global?\?/UNC/host" << "/Global?\?/UNC/host"; - QTest::newRow("w4") << "" << "" << "//server/dir/file" << "//server/dir/file"; -} - -void tst_fileutils::fromToString() -{ - QFETCH(QString, full); - QFETCH(QString, scheme); - QFETCH(QString, host); - QFETCH(QString, path); - - FilePath filePath = FilePath::fromString(full); - - QCOMPARE(filePath.toString(), full); - - QCOMPARE(filePath.scheme(), scheme); - QCOMPARE(filePath.host(), host); - QCOMPARE(filePath.path(), path); - - FilePath copy = FilePath::fromParts(scheme, host, path); - QCOMPARE(copy.toString(), full); -} - -void tst_fileutils::comparison() -{ - QFETCH(QString, left); - QFETCH(QString, right); - QFETCH(bool, hostSensitive); - QFETCH(bool, expected); - - HostOsInfo::setOverrideFileNameCaseSensitivity( - hostSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive); - - FilePath l = FilePath::fromUserInput(left); - FilePath r = FilePath::fromUserInput(right); - QCOMPARE(l == r, expected); -} - -void tst_fileutils::comparison_data() -{ - QTest::addColumn("left"); - QTest::addColumn("right"); - QTest::addColumn("hostSensitive"); - QTest::addColumn("expected"); - - QTest::newRow("r1") << "Abc" << "abc" << true << false; - QTest::newRow("r2") << "Abc" << "abc" << false << true; - QTest::newRow("r3") << "x://y/Abc" << "x://y/abc" << true << false; - QTest::newRow("r4") << "x://y/Abc" << "x://y/abc" << false << false; - - QTest::newRow("s1") << "abc" << "abc" << true << true; - QTest::newRow("s2") << "abc" << "abc" << false << true; - QTest::newRow("s3") << "x://y/abc" << "x://y/abc" << true << true; - QTest::newRow("s4") << "x://y/abc" << "x://y/abc" << false << true; -} - -void tst_fileutils::linkFromString() -{ - QFETCH(QString, testFile); - QFETCH(Utils::FilePath, filePath); - QFETCH(int, line); - QFETCH(int, column); - const Link link = Link::fromString(testFile, true); - QCOMPARE(link.targetFilePath, filePath); - QCOMPARE(link.targetLine, line); - QCOMPARE(link.targetColumn, column); -} - -void tst_fileutils::linkFromString_data() -{ - QTest::addColumn("testFile"); - QTest::addColumn("filePath"); - QTest::addColumn("line"); - QTest::addColumn("column"); - - QTest::newRow("no-line-no-column") << QString("someFile.txt") - << FilePath("someFile.txt") << -1 << -1; - QTest::newRow(": at end") << QString::fromLatin1("someFile.txt:") - << FilePath("someFile.txt") << 0 << -1; - QTest::newRow("+ at end") << QString::fromLatin1("someFile.txt+") - << FilePath("someFile.txt") << 0 << -1; - QTest::newRow(": for column") << QString::fromLatin1("someFile.txt:10:") - << FilePath("someFile.txt") << 10 << -1; - QTest::newRow("+ for column") << QString::fromLatin1("someFile.txt:10+") - << FilePath("someFile.txt") << 10 << -1; - QTest::newRow(": and + at end") << QString::fromLatin1("someFile.txt:+") - << FilePath("someFile.txt") << 0 << -1; - QTest::newRow("empty line") << QString::fromLatin1("someFile.txt:+10") - << FilePath("someFile.txt") << 0 << 9; - QTest::newRow(":line-no-column") << QString::fromLatin1("/some/path/file.txt:42") - << FilePath("/some/path/file.txt") << 42 << -1; - QTest::newRow("+line-no-column") << QString::fromLatin1("/some/path/file.txt+42") - << FilePath("/some/path/file.txt") << 42 << -1; - QTest::newRow(":line-:column") << QString::fromLatin1("/some/path/file.txt:42:3") - << FilePath("/some/path/file.txt") << 42 << 2; - QTest::newRow(":line-+column") << QString::fromLatin1("/some/path/file.txt:42+33") - << FilePath("/some/path/file.txt") << 42 << 32; - QTest::newRow("+line-:column") << QString::fromLatin1("/some/path/file.txt+142:30") - << FilePath("/some/path/file.txt") << 142 << 29; - QTest::newRow("+line-+column") << QString::fromLatin1("/some/path/file.txt+142+33") - << FilePath("/some/path/file.txt") << 142 << 32; - QTest::newRow("( at end") << QString::fromLatin1("/some/path/file.txt(") - << FilePath("/some/path/file.txt") << -1 << -1; - QTest::newRow("(42 at end") << QString::fromLatin1("/some/path/file.txt(42") - << FilePath("/some/path/file.txt") << 42 << -1; - QTest::newRow("(42) at end") << QString::fromLatin1("/some/path/file.txt(42)") - << FilePath("/some/path/file.txt") << 42 << -1; -} - -void tst_fileutils::pathAppended() -{ - QFETCH(QString, left); - QFETCH(QString, right); - QFETCH(QString, expected); - - const FilePath fleft = FilePath::fromString(left); - const FilePath fexpected = FilePath::fromString(expected); - const FilePath result = fleft.pathAppended(right); - - QCOMPARE(result, fexpected); -} - -void tst_fileutils::pathAppended_data() -{ - QTest::addColumn("left"); - QTest::addColumn("right"); - QTest::addColumn("expected"); - - QTest::newRow("p0") << "" << "" << ""; - QTest::newRow("p1") << "" << "/" << "/"; - QTest::newRow("p2") << "" << "c/" << "c/"; - QTest::newRow("p3") << "" << "/d" << "/d"; - QTest::newRow("p4") << "" << "c/d" << "c/d"; - - QTest::newRow("r0") << "/" << "" << "/"; - QTest::newRow("r1") << "/" << "/" << "/"; - QTest::newRow("r2") << "/" << "c/" << "/c/"; - QTest::newRow("r3") << "/" << "/d" << "/d"; - QTest::newRow("r4") << "/" << "c/d" << "/c/d"; - - QTest::newRow("s0") << "/b" << "" << "/b"; - QTest::newRow("s1") << "/b" << "/" << "/b/"; - QTest::newRow("s2") << "/b" << "c/" << "/b/c/"; - QTest::newRow("s3") << "/b" << "/d" << "/b/d"; - QTest::newRow("s4") << "/b" << "c/d" << "/b/c/d"; - - QTest::newRow("t0") << "a/" << "" << "a/"; - QTest::newRow("t1") << "a/" << "/" << "a/"; - QTest::newRow("t2") << "a/" << "c/" << "a/c/"; - QTest::newRow("t3") << "a/" << "/d" << "a/d"; - QTest::newRow("t4") << "a/" << "c/d" << "a/c/d"; - - QTest::newRow("u0") << "a/b" << "" << "a/b"; - QTest::newRow("u1") << "a/b" << "/" << "a/b/"; - QTest::newRow("u2") << "a/b" << "c/" << "a/b/c/"; - QTest::newRow("u3") << "a/b" << "/d" << "a/b/d"; - QTest::newRow("u4") << "a/b" << "c/d" << "a/b/c/d"; - - if (HostOsInfo::isWindowsHost()) { - QTest::newRow("win-1") << "c:" << "/a/b" << "c:/a/b"; - QTest::newRow("win-2") << "c:/" << "/a/b" << "c:/a/b"; - QTest::newRow("win-3") << "c:/" << "a/b" << "c:/a/b"; - } -} - -void tst_fileutils::resolvePath_data() -{ - QTest::addColumn("left"); - QTest::addColumn("right"); - QTest::addColumn("expected"); - - QTest::newRow("empty") << FilePath() << FilePath() << FilePath(); - QTest::newRow("s0") << FilePath("/") << FilePath("b") << FilePath("/b"); - QTest::newRow("s1") << FilePath() << FilePath("b") << FilePath("b"); - QTest::newRow("s2") << FilePath("a") << FilePath() << FilePath("a"); - QTest::newRow("s3") << FilePath("a") << FilePath("b") << FilePath("a/b"); - QTest::newRow("s4") << FilePath("/a") << FilePath("/b") << FilePath("/b"); - QTest::newRow("s5") << FilePath("a") << FilePath("/b") << FilePath("/b"); - QTest::newRow("s6") << FilePath("/a") << FilePath("b") << FilePath("/a/b"); - QTest::newRow("s7") << FilePath("/a") << FilePath(".") << FilePath("/a"); - QTest::newRow("s8") << FilePath("/a") << FilePath("./b") << FilePath("/a/b"); -} - -void tst_fileutils::resolvePath() -{ - QFETCH(FilePath, left); - QFETCH(FilePath, right); - QFETCH(FilePath, expected); - - const FilePath result = left.resolvePath(right); - - QCOMPARE(result, expected); -} - -void tst_fileutils::relativeChildPath_data() -{ - QTest::addColumn("parent"); - QTest::addColumn("child"); - QTest::addColumn("expected"); - - QTest::newRow("empty") << FilePath() << FilePath() << FilePath(); - - QTest::newRow("simple-0") << FilePath("/a") << FilePath("/a/b") << FilePath("b"); - QTest::newRow("simple-1") << FilePath("/a/") << FilePath("/a/b") << FilePath("b"); - QTest::newRow("simple-2") << FilePath("/a") << FilePath("/a/b/c/d/e/f") << FilePath("b/c/d/e/f"); - - QTest::newRow("not-0") << FilePath("/x") << FilePath("/a/b") << FilePath(); - QTest::newRow("not-1") << FilePath("/a/b/c") << FilePath("/a/b") << FilePath(); - -} - -void tst_fileutils::relativeChildPath() -{ - QFETCH(FilePath, parent); - QFETCH(FilePath, child); - QFETCH(FilePath, expected); - - const FilePath result = child.relativeChildPath(parent); - - QCOMPARE(result, expected); -} +void tst_fileutils::initTestCase() {} void tst_fileutils::commonPath() { @@ -996,133 +65,15 @@ void tst_fileutils::commonPath_data() const FilePath url6 = FilePath::fromString("http:///./"); QTest::newRow("Zero paths") << FilePaths{} << FilePath(); - QTest::newRow("Single path") << FilePaths{ p1 } << p1; - QTest::newRow("3 identical paths") << FilePaths{ p1, p1, p1 } << p1; - QTest::newRow("3 paths, common path") << FilePaths{ p1, p2, p3 } << p1; - QTest::newRow("3 paths, no common path") << FilePaths{ p1, p2, p4 } << p5; - QTest::newRow("3 paths, first is part of second") << FilePaths{ p4, p1, p3 } << p5; - QTest::newRow("Common scheme") << FilePaths{ url1, url3 } << url6; - QTest::newRow("Different scheme") << FilePaths{ url1, url2 } << FilePath(); - QTest::newRow("Common host") << FilePaths{ url4, url5 } << url4; - QTest::newRow("Different host") << FilePaths{ url1, url3 } << url6; -} - -void tst_fileutils::asyncLocalCopy() -{ - const FilePath orig = FilePath::fromString(rootPath).pathAppended("x/y/fileToCopy.txt"); - QVERIFY(orig.exists()); - const FilePath dest = FilePath::fromString(rootPath).pathAppended("x/fileToCopyDest.txt"); - auto afterCopy = [&orig, &dest, this](expected_str result) { - QVERIFY(result); - // check existence, size and content - QVERIFY(dest.exists()); - QCOMPARE(dest.fileSize(), orig.fileSize()); - QCOMPARE(dest.fileContents(), orig.fileContents()); - emit asyncTestDone(); - }; - QSignalSpy spy(this, &tst_fileutils::asyncTestDone); - orig.asyncCopyFile(afterCopy, dest); - // we usually have already received the signal, but if it fails wait 3s - QVERIFY(spy.count() == 1 || spy.wait(3000)); -} - -void tst_fileutils::startsWithDriveLetter_data() -{ - QTest::addColumn("path"); - QTest::addColumn("expected"); - - QTest::newRow("empty") << FilePath() << false; - QTest::newRow("simple-win") << FilePath::fromString("c:/a") << true; - QTest::newRow("simple-linux") << FilePath::fromString("/c:/a") << false; - QTest::newRow("relative") << FilePath("a/b") << false; -} - -void tst_fileutils::startsWithDriveLetter() -{ - QFETCH(FilePath, path); - QFETCH(bool, expected); - - QCOMPARE(path.startsWithDriveLetter(), expected); -} - -void tst_fileutils::onDevice_data() -{ - QTest::addColumn("path"); - QTest::addColumn("templatePath"); - QTest::addColumn("expected"); - - QTest::newRow("empty") << FilePath() << FilePath() << FilePath(); - QTest::newRow("same-local") << FilePath("/a/b") << FilePath("/a/b") << FilePath("/a/b"); - QTest::newRow("same-docker") << FilePath("docker://1234/a/b") << FilePath("docker://1234/e") << FilePath("docker://1234/a/b"); - - QTest::newRow("docker-to-local") << FilePath("docker://1234/a/b") << FilePath("/c/d") << FilePath("/a/b"); - QTest::newRow("local-to-docker") << FilePath("/a/b") << FilePath("docker://1234/c/d") << FilePath("docker://1234/a/b"); - -} - -void tst_fileutils::onDevice() -{ - QFETCH(FilePath, path); - QFETCH(FilePath, templatePath); - QFETCH(FilePath, expected); - - QCOMPARE(path.onDevice(templatePath), expected); -} - -void tst_fileutils::stringAppended_data() -{ - QTest::addColumn("left"); - QTest::addColumn("right"); - QTest::addColumn("expected"); - - QTest::newRow("empty") << FilePath() << QString() << FilePath(); - QTest::newRow("empty-left") << FilePath() << "a" << FilePath("a"); - QTest::newRow("empty-right") << FilePath("a") << QString() << FilePath("a"); - QTest::newRow("add-root") << FilePath() << QString("/") << FilePath("/"); - QTest::newRow("add-root-and-more") << FilePath() << QString("/test/blah") << FilePath("/test/blah"); - QTest::newRow("add-extension") << FilePath::fromString("/a") << QString(".txt") << FilePath("/a.txt"); - QTest::newRow("trailing-slash") << FilePath::fromString("/a") << QString("b/") << FilePath("/ab/"); - QTest::newRow("slash-trailing-slash") << FilePath::fromString("/a/") << QString("b/") << FilePath("/a/b/"); -} - -void tst_fileutils::stringAppended() -{ - QFETCH(FilePath, left); - QFETCH(QString, right); - QFETCH(FilePath, expected); - - const FilePath result = left.stringAppended(right); - - QCOMPARE(expected, result); -} - -void tst_fileutils::url() -{ - QFETCH(QString, url); - QFETCH(QString, expectedScheme); - QFETCH(QString, expectedHost); - QFETCH(QString, expectedPath); - - const FilePath result = FilePath::fromString(url); - QCOMPARE(result.scheme(), expectedScheme); - QCOMPARE(result.host(), expectedHost); - QCOMPARE(result.path(), expectedPath); -} - -void tst_fileutils::url_data() -{ - QTest::addColumn("url"); - QTest::addColumn("expectedScheme"); - QTest::addColumn("expectedHost"); - QTest::addColumn("expectedPath"); - QTest::newRow("empty") << QString() << QString() << QString() << QString(); - QTest::newRow("simple-file") << QString("file:///a/b") << QString("file") << QString() << QString("/a/b"); - QTest::newRow("simple-file-root") << QString("file:///") << QString("file") << QString() << QString("/"); - QTest::newRow("simple-docker") << QString("docker://1234/a/b") << QString("docker") << QString("1234") << QString("/a/b"); - QTest::newRow("simple-ssh") << QString("ssh://user@host/a/b") << QString("ssh") << QString("user@host") << QString("/a/b"); - QTest::newRow("simple-ssh-with-port") << QString("ssh://user@host:1234/a/b") << QString("ssh") << QString("user@host:1234") << QString("/a/b"); - QTest::newRow("http-qt.io") << QString("http://qt.io") << QString("http") << QString("qt.io") << QString(); - QTest::newRow("http-qt.io-index.html") << QString("http://qt.io/index.html") << QString("http") << QString("qt.io") << QString("/index.html"); + QTest::newRow("Single path") << FilePaths{p1} << p1; + QTest::newRow("3 identical paths") << FilePaths{p1, p1, p1} << p1; + QTest::newRow("3 paths, common path") << FilePaths{p1, p2, p3} << p1; + QTest::newRow("3 paths, no common path") << FilePaths{p1, p2, p4} << p5; + QTest::newRow("3 paths, first is part of second") << FilePaths{p4, p1, p3} << p5; + QTest::newRow("Common scheme") << FilePaths{url1, url3} << url6; + QTest::newRow("Different scheme") << FilePaths{url1, url2} << FilePath(); + QTest::newRow("Common host") << FilePaths{url4, url5} << url4; + QTest::newRow("Different host") << FilePaths{url1, url3} << url6; } void tst_fileutils::bytesAvailableFromDF_data() @@ -1131,13 +82,33 @@ void tst_fileutils::bytesAvailableFromDF_data() QTest::addColumn("expected"); QTest::newRow("empty") << QByteArray("") << qint64(-1); - QTest::newRow("mac") << QByteArray("Filesystem 1024-blocks Used Available Capacity iused ifree %iused Mounted on\n/dev/disk3s5 971350180 610014564 342672532 65% 4246780 3426725320 0% /System/Volumes/Data\n") << qint64(342672532); - QTest::newRow("alpine") << QByteArray("Filesystem 1K-blocks Used Available Use% Mounted on\noverlay 569466448 163526072 376983360 30% /\n") << qint64(376983360); - QTest::newRow("alpine-no-trailing-br") << QByteArray("Filesystem 1K-blocks Used Available Use% Mounted on\noverlay 569466448 163526072 376983360 30% /") << qint64(376983360); - QTest::newRow("alpine-missing-line") << QByteArray("Filesystem 1K-blocks Used Available Use% Mounted on\n") << qint64(-1); - QTest::newRow("wrong-header") << QByteArray("Filesystem 1K-blocks Used avail Use% Mounted on\noverlay 569466448 163526072 376983360 30% /\n") << qint64(-1); - QTest::newRow("not-enough-fields") << QByteArray("Filesystem 1K-blocks Used avail Use% Mounted on\noverlay 569466448\n") << qint64(-1); - QTest::newRow("not-enough-header-fields") << QByteArray("Filesystem 1K-blocks Used \noverlay 569466448 163526072 376983360 30% /\n") << qint64(-1); + QTest::newRow("mac") << QByteArray( + "Filesystem 1024-blocks Used Available Capacity iused ifree %iused Mounted " + "on\n/dev/disk3s5 971350180 610014564 342672532 65% 4246780 3426725320 0% " + "/System/Volumes/Data\n") + << qint64(342672532); + QTest::newRow("alpine") << QByteArray( + "Filesystem 1K-blocks Used Available Use% Mounted on\noverlay " + "569466448 163526072 376983360 30% /\n") + << qint64(376983360); + QTest::newRow("alpine-no-trailing-br") + << QByteArray("Filesystem 1K-blocks Used Available Use% Mounted on\noverlay " + " 569466448 163526072 376983360 30% /") + << qint64(376983360); + QTest::newRow("alpine-missing-line") + << QByteArray("Filesystem 1K-blocks Used Available Use% Mounted on\n") + << qint64(-1); + QTest::newRow("wrong-header") << QByteArray( + "Filesystem 1K-blocks Used avail Use% Mounted on\noverlay " + "569466448 163526072 376983360 30% /\n") + << qint64(-1); + QTest::newRow("not-enough-fields") << QByteArray( + "Filesystem 1K-blocks Used avail Use% Mounted on\noverlay 569466448\n") + << qint64(-1); + QTest::newRow("not-enough-header-fields") + << QByteArray("Filesystem 1K-blocks Used \noverlay 569466448 " + "163526072 376983360 30% /\n") + << qint64(-1); } void tst_fileutils::bytesAvailableFromDF() @@ -1152,194 +123,6 @@ void tst_fileutils::bytesAvailableFromDF() QCOMPARE(result, expected); } -void tst_fileutils::cleanPath_data() -{ - QTest::addColumn("path"); - QTest::addColumn("expected"); - - QTest::newRow("data0") << "/Users/sam/troll/qt4.0//.." << "/Users/sam/troll"; - QTest::newRow("data1") << "/Users/sam////troll/qt4.0//.." << "/Users/sam/troll"; - QTest::newRow("data2") << "/" << "/"; - QTest::newRow("data2-up") << "/path/.." << "/"; - QTest::newRow("data2-above-root") << "/.." << "/.."; - QTest::newRow("data3") << QDir::cleanPath("../.") << ".."; - QTest::newRow("data4") << QDir::cleanPath("../..") << "../.."; - QTest::newRow("data5") << "d:\\a\\bc\\def\\.." << "d:/a/bc"; // QDir/Linux had: "d:\\a\\bc\\def\\.." - QTest::newRow("data6") << "d:\\a\\bc\\def\\../../.." << "d:/"; // QDir/Linux had: ".." - QTest::newRow("data7") << ".//file1.txt" << "file1.txt"; - QTest::newRow("data8") << "/foo/bar/..//file1.txt" << "/foo/file1.txt"; - QTest::newRow("data9") << "//" << "//"; // QDir had: "/" - QTest::newRow("data10w") << "c:\\" << "c:/"; - QTest::newRow("data10l") << "/:/" << "/:"; - QTest::newRow("data11") << "//foo//bar" << "//foo/bar"; // QDir/Win had: "//foo/bar" - QTest::newRow("data12") << "ab/a/" << "ab/a"; // Path item with length of 2 - QTest::newRow("data13w") << "c:/" << "c:/"; - QTest::newRow("data13w2") << "c:\\" << "c:/"; - //QTest::newRow("data13l") << "c://" << "c:"; - -// QTest::newRow("data14") << "c://foo" << "c:/foo"; - QTest::newRow("data15") << "//c:/foo" << "//c:/foo"; // QDir/Lin had: "/c:/foo"; - QTest::newRow("drive-up") << "A:/path/.." << "A:/"; - QTest::newRow("drive-above-root") << "A:/.." << "A:/.."; - QTest::newRow("unc-server-up") << "//server/path/.." << "//server/"; - QTest::newRow("unc-server-above-root") << "//server/.." << "//server/.."; - - QTest::newRow("longpath") << "\\\\?\\d:\\" << "d:/"; - QTest::newRow("longpath-slash") << "//?/d:/" << "d:/"; - QTest::newRow("longpath-mixed-slashes") << "//?/d:\\" << "d:/"; - QTest::newRow("longpath-mixed-slashes-2") << "\\\\?\\d:/" << "d:/"; - - QTest::newRow("unc-network-share") << "\\\\?\\UNC\\localhost\\c$\\tmp.txt" - << "//localhost/c$/tmp.txt"; - QTest::newRow("unc-network-share-slash") << "//?/UNC/localhost/c$/tmp.txt" - << "//localhost/c$/tmp.txt"; - QTest::newRow("unc-network-share-mixed-slashes") << "//?/UNC/localhost\\c$\\tmp.txt" - << "//localhost/c$/tmp.txt"; - QTest::newRow("unc-network-share-mixed-slashes-2") << "\\\\?\\UNC\\localhost/c$/tmp.txt" - << "//localhost/c$/tmp.txt"; - - QTest::newRow("QTBUG-23892_0") << "foo/.." << "."; - QTest::newRow("QTBUG-23892_1") << "foo/../" << "."; - - QTest::newRow("QTBUG-3472_0") << "/foo/./bar" << "/foo/bar"; - QTest::newRow("QTBUG-3472_1") << "./foo/.." << "."; - QTest::newRow("QTBUG-3472_2") << "./foo/../" << "."; - - QTest::newRow("resource0") << ":/prefix/foo.bar" << ":/prefix/foo.bar"; - QTest::newRow("resource1") << ":/prefix/..//prefix/foo.bar" << ":/prefix/foo.bar"; - - QTest::newRow("ssh") << "ssh://host/prefix/../foo.bar" << "ssh://host/foo.bar"; - QTest::newRow("ssh2") << "ssh://host/../foo.bar" << "ssh://host/../foo.bar"; -} - -void tst_fileutils::cleanPath() -{ - QFETCH(QString, path); - QFETCH(QString, expected); - QString cleaned = doCleanPath(path); - QCOMPARE(cleaned, expected); -} - -void tst_fileutils::isSameFile_data() -{ - QTest::addColumn("left"); - QTest::addColumn("right"); - QTest::addColumn("shouldBeEqual"); - - QTest::addRow("/==/") - << FilePath::fromString("/") << FilePath::fromString("/") << true; - QTest::addRow("/!=tmp") - << FilePath::fromString("/") << FilePath::fromString(tempDir.path()) << false; - - - QDir dir(tempDir.path()); - touch(dir, "target-file", false); - - QFile file(dir.absoluteFilePath("target-file")); - if (file.link(dir.absoluteFilePath("source-file"))) { - QTest::addRow("real==link") - << FilePath::fromString(file.fileName()) - << FilePath::fromString(dir.absoluteFilePath("target-file")) - << true; - } - - QTest::addRow("/!=non-existing") - << FilePath::fromString("/") << FilePath::fromString("/this-path/does-not-exist") << false; - - QTest::addRow("two-devices") << FilePath::fromString( - "docker://boot2qt-raspberrypi4-64:6.5.0/opt/toolchain/sysroots/aarch64-pokysdk-linux/usr/" - "bin/aarch64-poky-linux/aarch64-poky-linux-g++") - << FilePath::fromString("docker://qt-linux:6/usr/bin/g++") - << false; -} - -void tst_fileutils::isSameFile() -{ - QFETCH(FilePath, left); - QFETCH(FilePath, right); - QFETCH(bool, shouldBeEqual); - - QCOMPARE(left.isSameFile(right), shouldBeEqual); -} - -void tst_fileutils::hostSpecialChars_data() -{ - QTest::addColumn("scheme"); - QTest::addColumn("host"); - QTest::addColumn("path"); - QTest::addColumn("expected"); - - QTest::addRow("slash-in-host") << "device" << "host/name" << "/" << FilePath::fromString("device://host%2fname/"); - QTest::addRow("percent-in-host") << "device" << "host%name" << "/" << FilePath::fromString("device://host%25name/"); - QTest::addRow("percent-and-slash-in-host") << "device" << "host/%name" << "/" << FilePath::fromString("device://host%2f%25name/"); - QTest::addRow("qtc-dev-slash-in-host-linux") << "device" << "host/name" << "/" << FilePath::fromString("/__qtc_devices__/device/host%2fname/"); - QTest::addRow("qtc-dev-slash-in-host-windows") << "device" << "host/name" << "/" << FilePath::fromString("c:/__qtc_devices__/device/host%2fname/"); - -} - -void tst_fileutils::hostSpecialChars() -{ - QFETCH(QString, scheme); - QFETCH(QString, host); - QFETCH(QString, path); - QFETCH(FilePath, expected); - - FilePath fp; - fp.setParts(scheme, host, path); - - // Check that setParts and fromString give the same result - QCOMPARE(fp, expected); - QCOMPARE(fp.host(), expected.host()); - QCOMPARE(fp.host(), host); - QCOMPARE(expected.host(), host); - - QString toStringExpected = expected.toString(); - QString toStringActual = fp.toString(); - - // Check that toString gives the same result - QCOMPARE(toStringActual, toStringExpected); - - // Check that fromString => toString => fromString gives the same result - FilePath toFromExpected = FilePath::fromString(expected.toString()); - QCOMPARE(toFromExpected, expected); - QCOMPARE(toFromExpected, fp); - - // Check that setParts => toString => fromString gives the same result - FilePath toFromActual = FilePath::fromString(fp.toString()); - QCOMPARE(toFromActual, fp); - QCOMPARE(toFromExpected, expected); -} - -void tst_fileutils::tmp_data() -{ - QTest::addColumn("templatepath"); - QTest::addColumn("expected"); - - QTest::addRow("empty") << "" << true; - QTest::addRow("no-template") << "foo" << true; - QTest::addRow("realtive-template") << "my-file-XXXXXXXX" << true; - QTest::addRow("absolute-template") << QDir::tempPath() + "/my-file-XXXXXXXX" << true; - QTest::addRow("non-existing-dir") << "/this/path/does/not/exist/my-file-XXXXXXXX" << false; - - QTest::addRow("on-device") << "device://test/./my-file-XXXXXXXX" << true; -} - -void tst_fileutils::tmp() -{ - QFETCH(QString, templatepath); - QFETCH(bool, expected); - - FilePath fp = FilePath::fromString(templatepath); - - const auto result = fp.createTempFile(); - QCOMPARE(result.has_value(), expected); - - if (result.has_value()) { - QVERIFY(result->exists()); - QVERIFY(result->removeFile()); - } -} - void tst_fileutils::filePathInfoFromTriple_data() { QTest::addColumn("statoutput"); @@ -1353,6 +136,9 @@ void tst_fileutils::filePathInfoFromTriple_data() FilePathInfo::ReadOwnerPerm | FilePathInfo::WriteOwnerPerm | FilePathInfo::ExeOwnerPerm + | FilePathInfo::ReadUserPerm + | FilePathInfo::WriteUserPerm + | FilePathInfo::ExeUserPerm | FilePathInfo::ReadGroupPerm | FilePathInfo::ExeGroupPerm | FilePathInfo::ReadOtherPerm @@ -1366,10 +152,11 @@ void tst_fileutils::filePathInfoFromTriple_data() << FilePathInfo{808104, FilePathInfo::FileFlags( FilePathInfo::ReadOwnerPerm | FilePathInfo::WriteOwnerPerm - | FilePathInfo::ExeOwnerPerm | FilePathInfo::ReadGroupPerm - | FilePathInfo::ExeGroupPerm | FilePathInfo::ReadOtherPerm - | FilePathInfo::ExeOtherPerm | FilePathInfo::FileType - | FilePathInfo::ExistsFlag), + | FilePathInfo::ExeOwnerPerm | FilePathInfo::ReadUserPerm + | FilePathInfo::WriteUserPerm | FilePathInfo::ExeUserPerm + | FilePathInfo::ReadGroupPerm | FilePathInfo::ExeGroupPerm + | FilePathInfo::ReadOtherPerm | FilePathInfo::ExeOtherPerm + | FilePathInfo::FileType | FilePathInfo::ExistsFlag), QDateTime::fromSecsSinceEpoch(1668852790)}; QTest::newRow("linux-disk") << QString("61b0 1651167746 0") @@ -1377,6 +164,8 @@ void tst_fileutils::filePathInfoFromTriple_data() FilePathInfo::FileFlags( FilePathInfo::ReadOwnerPerm | FilePathInfo::WriteOwnerPerm + | FilePathInfo::ReadUserPerm + | FilePathInfo::WriteUserPerm | FilePathInfo::ReadGroupPerm | FilePathInfo::WriteGroupPerm | FilePathInfo::LocalDiskFlag @@ -1389,7 +178,7 @@ void tst_fileutils::filePathInfoFromTriple() QFETCH(QString, statoutput); QFETCH(FilePathInfo, expected); - const FilePathInfo result = FileUtils::filePathInfoFromTriple(statoutput); + const FilePathInfo result = FileUtils::filePathInfoFromTriple(statoutput, 16); QCOMPARE(result, expected); } diff --git a/tests/auto/utils/fsengine/tst_fsengine.cpp b/tests/auto/utils/fsengine/tst_fsengine.cpp index 8b0cdfc1b45..d516bda8c21 100644 --- a/tests/auto/utils/fsengine/tst_fsengine.cpp +++ b/tests/auto/utils/fsengine/tst_fsengine.cpp @@ -31,6 +31,8 @@ private slots: void testBrokenWindowsPath(); void testRead(); void testWrite(); + void testRootFromDotDot(); + void testDirtyPaths(); private: QString makeTestPath(QString path, bool asUrl = false); @@ -93,6 +95,10 @@ void tst_fsengine::testRootPathContainsFakeDir() const QStringList schemeList = schemes.entryList(); QVERIFY(schemeList.contains("device")); + QDir devices(FilePath::specialDeviceRootPath()); + const QStringList deviceList = devices.entryList(); + QVERIFY(deviceList.contains("test")); + QDir deviceRoot(FilePath::specialDeviceRootPath() + "/test" + startWithSlash(QDir::rootPath())); const QStringList deviceRootList = deviceRoot.entryList(); QVERIFY(!deviceRootList.isEmpty()); @@ -129,7 +135,8 @@ QString tst_fsengine::makeTestPath(QString path, bool asUrl) return QString("device://test%1/tst_fsengine/%2").arg(tempFolder, path); return QString(FilePath::specialDeviceRootPath() + "/test%1/tst_fsengine/%2") - .arg(startWithSlash(tempFolder), path); + .arg(startWithSlash(tempFolder)) + .arg(path); } void tst_fsengine::testListDir() @@ -209,5 +216,49 @@ void tst_fsengine::testWrite() QCOMPARE(f.readAll(), data); } +void tst_fsengine::testRootFromDotDot() +{ + const QString path = QDir::rootPath() + "some-folder/.."; + QFileInfo fInfo(path); + + QCOMPARE(fInfo.fileName(), QString("..")); + + QDir dRoot(path); + const auto dRootEntryList = dRoot.entryList(); + QVERIFY(dRootEntryList.contains(FilePath::specialRootName())); + + QFileInfo fInfo2(FilePath::specialRootPath() + "/xyz/.."); + QCOMPARE(fInfo2.fileName(), ".."); + + QDir schemesWithDotDot(FilePath::specialRootPath() + "/xyz/.."); + const QStringList schemeWithDotDotList = schemesWithDotDot.entryList(); + QVERIFY(schemeWithDotDotList.contains("device")); + + QFileInfo fInfo3(FilePath::specialDeviceRootPath() + "/xyz/.."); + QCOMPARE(fInfo3.fileName(), ".."); + + QDir devicesWithDotDot(FilePath::specialDeviceRootPath() + "/test/.."); + const QStringList deviceListWithDotDot = devicesWithDotDot.entryList(); + QVERIFY(deviceListWithDotDot.contains("test")); + + QFileInfo fInfo4(FilePath::specialDeviceRootPath() + "/test/tmp/.."); + QCOMPARE(fInfo4.fileName(), ".."); +} + +void tst_fsengine::testDirtyPaths() +{ + // "//__qtc_devices" + QVERIFY(QFileInfo("/" + FilePath::specialRootPath()).exists()); + + // "///__qtc_devices/device" + QVERIFY(QFileInfo("//" + FilePath::specialDeviceRootPath()).exists()); + + // "////__qtc_devices/device////test" + QVERIFY(QFileInfo("///" + FilePath::specialDeviceRootPath() + "////test").exists()); + + // "/////__qtc_devices/device/test/..." + QVERIFY(QFileInfo("////" + makeTestPath("")).exists()); +} + QTEST_GUILESS_MAIN(tst_fsengine) #include "tst_fsengine.moc" diff --git a/tests/auto/utils/mathutils/tst_mathutils.cpp b/tests/auto/utils/mathutils/tst_mathutils.cpp index b817f157436..a29f908748d 100644 --- a/tests/auto/utils/mathutils/tst_mathutils.cpp +++ b/tests/auto/utils/mathutils/tst_mathutils.cpp @@ -1,7 +1,7 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "utils/mathutils.h" +#include #include diff --git a/tests/auto/utils/qtcprocess/CMakeLists.txt b/tests/auto/utils/process/CMakeLists.txt similarity index 89% rename from tests/auto/utils/qtcprocess/CMakeLists.txt rename to tests/auto/utils/process/CMakeLists.txt index 51720bbeb5b..29bdf61451b 100644 --- a/tests/auto/utils/qtcprocess/CMakeLists.txt +++ b/tests/auto/utils/process/CMakeLists.txt @@ -3,11 +3,11 @@ add_subdirectory(processtestapp) file(RELATIVE_PATH RELATIVE_TEST_PATH "${PROJECT_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}") file(RELATIVE_PATH TEST_RELATIVE_LIBEXEC_PATH "/${RELATIVE_TEST_PATH}" "/${IDE_LIBEXEC_PATH}") -add_qtc_test(tst_qtcprocess +add_qtc_test(tst_process DEFINES "TEST_RELATIVE_LIBEXEC_PATH=\"${TEST_RELATIVE_LIBEXEC_PATH}\"" "PROCESS_TESTAPP=\"${CMAKE_CURRENT_BINARY_DIR}/processtestapp\"" DEPENDS Utils app_version - SOURCES tst_qtcprocess.cpp + SOURCES tst_process.cpp processtestapp/processtestapp.h processtestapp/processtestapp.cpp ) diff --git a/tests/auto/utils/qtcprocess/qtcprocess.qbs b/tests/auto/utils/process/process.qbs similarity index 93% rename from tests/auto/utils/qtcprocess/qtcprocess.qbs rename to tests/auto/utils/process/process.qbs index cc37a0124fc..0ff6e929fcf 100644 --- a/tests/auto/utils/qtcprocess/qtcprocess.qbs +++ b/tests/auto/utils/process/process.qbs @@ -2,7 +2,7 @@ import qbs.FileInfo Project { QtcAutotest { - name: "QtcProcess autotest" + name: "Process autotest" Depends { name: "Utils" } Depends { name: "app_version_header" } @@ -10,7 +10,7 @@ Project { files: [ "processtestapp/processtestapp.cpp", "processtestapp/processtestapp.h", - "tst_qtcprocess.cpp", + "tst_process.cpp", ] cpp.defines: { var defines = base; diff --git a/tests/auto/utils/qtcprocess/processtestapp/CMakeLists.txt b/tests/auto/utils/process/processtestapp/CMakeLists.txt similarity index 100% rename from tests/auto/utils/qtcprocess/processtestapp/CMakeLists.txt rename to tests/auto/utils/process/processtestapp/CMakeLists.txt diff --git a/tests/auto/utils/qtcprocess/processtestapp/main.cpp b/tests/auto/utils/process/processtestapp/main.cpp similarity index 97% rename from tests/auto/utils/qtcprocess/processtestapp/main.cpp rename to tests/auto/utils/process/processtestapp/main.cpp index 6851a3597cf..91952c2528e 100644 --- a/tests/auto/utils/qtcprocess/processtestapp/main.cpp +++ b/tests/auto/utils/process/processtestapp/main.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include diff --git a/tests/auto/utils/qtcprocess/processtestapp/processtestapp.cpp b/tests/auto/utils/process/processtestapp/processtestapp.cpp similarity index 97% rename from tests/auto/utils/qtcprocess/processtestapp/processtestapp.cpp rename to tests/auto/utils/process/processtestapp/processtestapp.cpp index 0884ab44a78..3905d0a22b5 100644 --- a/tests/auto/utils/qtcprocess/processtestapp/processtestapp.cpp +++ b/tests/auto/utils/process/processtestapp/processtestapp.cpp @@ -3,7 +3,7 @@ #include "processtestapp.h" -#include +#include #include #include @@ -77,7 +77,7 @@ SubProcessConfig::SubProcessConfig(const char *envVar, const QString &envVal) { } -void SubProcessConfig::setupSubProcess(QtcProcess *subProcess) const +void SubProcessConfig::setupSubProcess(Process *subProcess) const { subProcess->setEnvironment(m_environment); const FilePath filePath = FilePath::fromString(s_pathToProcessTestApp @@ -148,7 +148,7 @@ int ProcessTestApp::ChannelForwarding::main() qunsetenv(envVar()); SubProcessConfig subConfig(StandardOutputAndErrorWriter::envVar(), {}); - QtcProcess process; + Process process; subConfig.setupSubProcess(&process); process.setProcessChannelMode(channelMode); @@ -206,7 +206,7 @@ int ProcessTestApp::RecursiveCrashingProcess::main() return 1; } SubProcessConfig subConfig(envVar(), QString::number(currentDepth - 1)); - QtcProcess process; + Process process; subConfig.setupSubProcess(&process); process.start(); process.waitForFinished(); @@ -249,7 +249,7 @@ int ProcessTestApp::RecursiveBlockingProcess::main() } } SubProcessConfig subConfig(envVar(), QString::number(currentDepth - 1)); - QtcProcess process; + Process process; subConfig.setupSubProcess(&process); process.setProcessChannelMode(QProcess::ForwardedChannels); process.start(); diff --git a/tests/auto/utils/qtcprocess/processtestapp/processtestapp.h b/tests/auto/utils/process/processtestapp/processtestapp.h similarity index 94% rename from tests/auto/utils/qtcprocess/processtestapp/processtestapp.h rename to tests/auto/utils/process/processtestapp/processtestapp.h index d529ac8265d..adc0ebb9c88 100644 --- a/tests/auto/utils/qtcprocess/processtestapp/processtestapp.h +++ b/tests/auto/utils/process/processtestapp/processtestapp.h @@ -11,7 +11,7 @@ QT_BEGIN_NAMESPACE class QProcess; QT_END_NAMESPACE -namespace Utils { class QtcProcess; } +namespace Utils { class Process; } #define SUB_PROCESS(SubProcessClass)\ class SubProcessClass\ @@ -35,7 +35,7 @@ public: static void invokeSubProcess(); - // Many tests inside tst_qtcprocess need to start a new subprocess with custom code. + // Many tests inside tst_Process need to start a new subprocess with custom code. // In order to simplify things we produce just one separate executable - processtestapp. // We embed all our custom subprocesses in processtestapp and enclose them in separate // classes. We select desired process to run by setting the relevant environment variable. @@ -72,7 +72,7 @@ class SubProcessConfig { public: SubProcessConfig(const char *envVar, const QString &envVal); - void setupSubProcess(Utils::QtcProcess *subProcess) const; + void setupSubProcess(Utils::Process *subProcess) const; void setupSubProcess(QProcess *subProcess) const; static void setPathToProcessTestApp(const QString &path); diff --git a/tests/auto/utils/qtcprocess/processtestapp/processtestapp.qbs b/tests/auto/utils/process/processtestapp/processtestapp.qbs similarity index 100% rename from tests/auto/utils/qtcprocess/processtestapp/processtestapp.qbs rename to tests/auto/utils/process/processtestapp/processtestapp.qbs diff --git a/tests/auto/utils/qtcprocess/tst_qtcprocess.cpp b/tests/auto/utils/process/tst_process.cpp similarity index 92% rename from tests/auto/utils/qtcprocess/tst_qtcprocess.cpp rename to tests/auto/utils/process/tst_process.cpp index a26924089e0..589adbb6fa8 100644 --- a/tests/auto/utils/qtcprocess/tst_qtcprocess.cpp +++ b/tests/auto/utils/process/tst_process.cpp @@ -8,10 +8,10 @@ #include #include #include +#include #include #include #include -#include #include #include #include @@ -87,13 +87,38 @@ private: static constexpr char s_skipTerminateOnWindows[] = "Windows implementation of this test is lacking handling of WM_CLOSE message."; -class tst_QtcProcess : public QObject +class tst_Process : public QObject { Q_OBJECT private slots: void initTestCase(); + void testEnv() + { + if (HostOsInfo::isWindowsHost()) + QSKIP("Skipping env test on Windows"); + + QProcess qproc; + FilePath envPath = Environment::systemEnvironment().searchInPath("env"); + + qproc.setProgram(envPath.nativePath()); + qproc.start(); + qproc.waitForFinished(); + QByteArray qoutput = qproc.readAllStandardOutput() + qproc.readAllStandardError(); + qDebug() << "QProcess output:" << qoutput; + QCOMPARE(qproc.exitCode(), 0); + + Process proc; + proc.setCommand({envPath, {}}); + proc.runBlocking(); + QCOMPARE(proc.exitCode(), 0); + const QByteArray output = proc.readAllRawStandardOutput() + proc.readAllRawStandardError(); + qDebug() << "Process output:" << output; + + QCOMPARE(output.size() > 0, qoutput.size() > 0); + } + void multiRead(); void splitArgs_data(); @@ -135,6 +160,7 @@ private slots: void quitBlockingProcess_data(); void quitBlockingProcess(); void tarPipe(); + void stdinToShell(); void cleanupTestCase(); @@ -152,7 +178,7 @@ private: MessageHandler *msgHandler = nullptr; }; -void tst_QtcProcess::initTestCase() +void tst_Process::initTestCase() { msgHandler = new MessageHandler; Utils::TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath() + "/" @@ -203,7 +229,7 @@ void tst_QtcProcess::initTestCase() mxUnix.insert("z", ""); } -void tst_QtcProcess::cleanupTestCase() +void tst_Process::cleanupTestCase() { Utils::Singleton::deleteAll(); const int destroyCount = msgHandler->destroyCount(); @@ -217,13 +243,13 @@ Q_DECLARE_METATYPE(ProcessArgs::SplitError) Q_DECLARE_METATYPE(Utils::OsType) Q_DECLARE_METATYPE(Utils::ProcessResult) -void tst_QtcProcess::multiRead() +void tst_Process::multiRead() { if (HostOsInfo::isWindowsHost()) QSKIP("This test uses /bin/sh."); QByteArray buffer; - QtcProcess process; + Process process; process.setCommand({"/bin/sh", {}}); process.setProcessChannelMode(QProcess::SeparateChannels); @@ -245,7 +271,7 @@ void tst_QtcProcess::multiRead() QCOMPARE(buffer, QByteArray("you\n")); } -void tst_QtcProcess::splitArgs_data() +void tst_Process::splitArgs_data() { QTest::addColumn("in"); QTest::addColumn("out"); @@ -302,7 +328,7 @@ void tst_QtcProcess::splitArgs_data() } } -void tst_QtcProcess::splitArgs() +void tst_Process::splitArgs() { QFETCH(QString, in); QFETCH(QString, out); @@ -316,7 +342,7 @@ void tst_QtcProcess::splitArgs() QCOMPARE(outstr, out); } -void tst_QtcProcess::prepareArgs_data() +void tst_Process::prepareArgs_data() { QTest::addColumn("in"); QTest::addColumn("out"); @@ -370,7 +396,7 @@ void tst_QtcProcess::prepareArgs_data() } } -void tst_QtcProcess::prepareArgs() +void tst_Process::prepareArgs() { QFETCH(QString, in); QFETCH(QString, out); @@ -386,7 +412,7 @@ void tst_QtcProcess::prepareArgs() QCOMPARE(outstr, out); } -void tst_QtcProcess::prepareArgsEnv_data() +void tst_Process::prepareArgsEnv_data() { QTest::addColumn("in"); QTest::addColumn("out"); @@ -460,7 +486,7 @@ void tst_QtcProcess::prepareArgsEnv_data() } } -void tst_QtcProcess::prepareArgsEnv() +void tst_Process::prepareArgsEnv() { QFETCH(QString, in); QFETCH(QString, out); @@ -476,7 +502,7 @@ void tst_QtcProcess::prepareArgsEnv() QCOMPARE(outstr, out); } -void tst_QtcProcess::expandMacros_data() +void tst_Process::expandMacros_data() { QTest::addColumn("in"); @@ -698,11 +724,11 @@ void tst_QtcProcess::expandMacros_data() title = vals[i].in; } else { char buf[80]; - sprintf(buf, "%s: %s", title, vals[i].in); + snprintf(buf, 80, "%s: %s", title, vals[i].in); QTest::newRow(buf) << QString::fromLatin1(vals[i].in) << QString::fromLatin1(vals[i].out) << vals[i].os; - sprintf(buf, "padded %s: %s", title, vals[i].in); + snprintf(buf, 80, "padded %s: %s", title, vals[i].in); QTest::newRow(buf) << QString(sp + QString::fromLatin1(vals[i].in) + sp) << QString(sp + QString::fromLatin1(vals[i].out) + sp) << vals[i].os; @@ -710,7 +736,7 @@ void tst_QtcProcess::expandMacros_data() } } -void tst_QtcProcess::expandMacros() +void tst_Process::expandMacros() { QFETCH(QString, in); QFETCH(QString, out); @@ -723,7 +749,7 @@ void tst_QtcProcess::expandMacros() QCOMPARE(in, out); } -void tst_QtcProcess::iterations_data() +void tst_Process::iterations_data() { QTest::addColumn("in"); QTest::addColumn("out"); @@ -778,7 +804,7 @@ void tst_QtcProcess::iterations_data() << vals[i].os; } -void tst_QtcProcess::iterations() +void tst_Process::iterations() { QFETCH(QString, in); QFETCH(QString, out); @@ -794,7 +820,7 @@ void tst_QtcProcess::iterations() QCOMPARE(outstr, out); } -void tst_QtcProcess::iteratorEditsHelper(OsType osType) +void tst_Process::iteratorEditsHelper(OsType osType) { QString in1 = "one two three", in2 = in1, in3 = in1, in4 = in1, in5 = in1; @@ -845,17 +871,17 @@ void tst_QtcProcess::iteratorEditsHelper(OsType osType) QCOMPARE(in5, QString::fromLatin1("one two")); } -void tst_QtcProcess::iteratorEditsWindows() +void tst_Process::iteratorEditsWindows() { iteratorEditsHelper(OsTypeWindows); } -void tst_QtcProcess::iteratorEditsLinux() +void tst_Process::iteratorEditsLinux() { iteratorEditsHelper(OsTypeLinux); } -void tst_QtcProcess::exitCode_data() +void tst_Process::exitCode_data() { QTest::addColumn("exitCode"); @@ -869,13 +895,13 @@ void tst_QtcProcess::exitCode_data() QTest::newRow(exitCode) << QString::fromLatin1(exitCode).toInt(); } -void tst_QtcProcess::exitCode() +void tst_Process::exitCode() { QFETCH(int, exitCode); SubProcessConfig subConfig(ProcessTestApp::ExitCode::envVar(), QString::number(exitCode)); { - QtcProcess process; + Process process; subConfig.setupSubProcess(&process); process.start(); const bool finished = process.waitForFinished(); @@ -885,7 +911,7 @@ void tst_QtcProcess::exitCode() QCOMPARE(process.exitCode() == 0, process.result() == ProcessResult::FinishedWithSuccess); } { - QtcProcess process; + Process process; subConfig.setupSubProcess(&process); process.runBlocking(); @@ -894,7 +920,7 @@ void tst_QtcProcess::exitCode() } } -void tst_QtcProcess::runBlockingStdOut_data() +void tst_Process::runBlockingStdOut_data() { QTest::addColumn("withEndl"); QTest::addColumn("timeOutS"); @@ -915,14 +941,14 @@ void tst_QtcProcess::runBlockingStdOut_data() << false << 20 << ProcessResult::FinishedWithSuccess; } -void tst_QtcProcess::runBlockingStdOut() +void tst_Process::runBlockingStdOut() { QFETCH(bool, withEndl); QFETCH(int, timeOutS); QFETCH(ProcessResult, expectedResult); SubProcessConfig subConfig(ProcessTestApp::RunBlockingStdOut::envVar(), withEndl ? "true" : "false"); - QtcProcess process; + Process process; subConfig.setupSubProcess(&process); process.setTimeoutS(timeOutS); @@ -935,31 +961,31 @@ void tst_QtcProcess::runBlockingStdOut() }); process.runBlocking(); - // See also QTCREATORBUG-25667 for why it is a bad idea to use QtcProcess::runBlocking + // See also QTCREATORBUG-25667 for why it is a bad idea to use Process::runBlocking // with interactive cli tools. QCOMPARE(process.result(), expectedResult); QVERIFY2(readLastLine, "Last line was read."); } -void tst_QtcProcess::runBlockingSignal_data() +void tst_Process::runBlockingSignal_data() { runBlockingStdOut_data(); } -void tst_QtcProcess::runBlockingSignal() +void tst_Process::runBlockingSignal() { QFETCH(bool, withEndl); QFETCH(int, timeOutS); QFETCH(ProcessResult, expectedResult); SubProcessConfig subConfig(ProcessTestApp::RunBlockingStdOut::envVar(), withEndl ? "true" : "false"); - QtcProcess process; + Process process; subConfig.setupSubProcess(&process); process.setTimeoutS(timeOutS); bool readLastLine = false; process.setTextChannelMode(Channel::Output, TextChannelMode::MultiLine); - connect(&process, &QtcProcess::textOnStandardOutput, + connect(&process, &Process::textOnStandardOutput, this, [&readLastLine, &process](const QString &out) { if (out.startsWith(s_runBlockingStdOutSubProcessMagicWord)) { readLastLine = true; @@ -968,16 +994,16 @@ void tst_QtcProcess::runBlockingSignal() }); process.runBlocking(); - // See also QTCREATORBUG-25667 for why it is a bad idea to use QtcProcess::runBlocking + // See also QTCREATORBUG-25667 for why it is a bad idea to use Process::runBlocking // with interactive cli tools. QCOMPARE(process.result(), expectedResult); QVERIFY2(readLastLine, "Last line was read."); } -void tst_QtcProcess::lineCallback() +void tst_Process::lineCallback() { SubProcessConfig subConfig(ProcessTestApp::LineCallback::envVar(), {}); - QtcProcess process; + Process process; subConfig.setupSubProcess(&process); const QStringList lines = QString(s_lineCallbackData).split('|'); @@ -997,16 +1023,16 @@ void tst_QtcProcess::lineCallback() QCOMPARE(lineNumber, lines.size()); } -void tst_QtcProcess::lineSignal() +void tst_Process::lineSignal() { SubProcessConfig subConfig(ProcessTestApp::LineCallback::envVar(), {}); - QtcProcess process; + Process process; subConfig.setupSubProcess(&process); const QStringList lines = QString(s_lineCallbackData).split('|'); int lineNumber = 0; process.setTextChannelMode(Channel::Error, TextChannelMode::SingleLine); - connect(&process, &QtcProcess::textOnStandardError, + connect(&process, &Process::textOnStandardError, this, [lines, &lineNumber](const QString &actual) { QString expected = lines.at(lineNumber); expected.replace("\r\n", "\n"); @@ -1022,15 +1048,15 @@ void tst_QtcProcess::lineSignal() QCOMPARE(lineNumber, lines.size()); } -void tst_QtcProcess::waitForStartedAfterStarted() +void tst_Process::waitForStartedAfterStarted() { SubProcessConfig subConfig(ProcessTestApp::SimpleTest::envVar(), {}); - QtcProcess process; + Process process; subConfig.setupSubProcess(&process); bool started = false; bool waitForStartedResult = false; - connect(&process, &QtcProcess::started, this, [&] { + connect(&process, &Process::started, this, [&] { started = true; waitForStartedResult = process.waitForStarted(); }); @@ -1043,7 +1069,7 @@ void tst_QtcProcess::waitForStartedAfterStarted() } // This version is using QProcess -void tst_QtcProcess::waitForStartedAfterStarted2() +void tst_Process::waitForStartedAfterStarted2() { SubProcessConfig subConfig(ProcessTestApp::SimpleTest::envVar(), {}); QProcess process; @@ -1063,10 +1089,10 @@ void tst_QtcProcess::waitForStartedAfterStarted2() QVERIFY(!process.waitForStarted()); } -void tst_QtcProcess::waitForStartedAndFinished() +void tst_Process::waitForStartedAndFinished() { SubProcessConfig subConfig(ProcessTestApp::SimpleTest::envVar(), {}); - QtcProcess process; + Process process; subConfig.setupSubProcess(&process); process.start(); @@ -1079,7 +1105,7 @@ void tst_QtcProcess::waitForStartedAndFinished() Q_DECLARE_METATYPE(ProcessSignalType) -void tst_QtcProcess::notRunningAfterStartingNonExistingProgram_data() +void tst_Process::notRunningAfterStartingNonExistingProgram_data() { QTest::addColumn("signalType"); @@ -1088,16 +1114,16 @@ void tst_QtcProcess::notRunningAfterStartingNonExistingProgram_data() QTest::newRow("Done") << ProcessSignalType::Done; } -void tst_QtcProcess::notRunningAfterStartingNonExistingProgram() +void tst_Process::notRunningAfterStartingNonExistingProgram() { QFETCH(ProcessSignalType, signalType); - QtcProcess process; + Process process; process.setCommand({ FilePath::fromString( "there_is_a_big_chance_that_executable_with_that_name_does_not_exists"), {} }); int doneCount = 0; - QObject::connect(&process, &QtcProcess::done, [&process, &doneCount]() { + QObject::connect(&process, &Process::done, [&process, &doneCount]() { ++doneCount; QCOMPARE(process.error(), QProcess::FailedToStart); }); @@ -1138,7 +1164,7 @@ void tst_QtcProcess::notRunningAfterStartingNonExistingProgram() // and the error channels. Then ChannelForwarding::main() either forwards these channels // or not - we check it in the outer channelForwarding() test. -void tst_QtcProcess::channelForwarding_data() +void tst_Process::channelForwarding_data() { QTest::addColumn("channelMode"); QTest::addColumn("outputForwarded"); @@ -1151,7 +1177,7 @@ void tst_QtcProcess::channelForwarding_data() QTest::newRow("ForwardedErrorChannel") << QProcess::ForwardedErrorChannel << false << true; } -void tst_QtcProcess::channelForwarding() +void tst_Process::channelForwarding() { QFETCH(QProcess::ProcessChannelMode, channelMode); QFETCH(bool, outputForwarded); @@ -1159,7 +1185,7 @@ void tst_QtcProcess::channelForwarding() SubProcessConfig subConfig(ProcessTestApp::ChannelForwarding::envVar(), QString::number(int(channelMode))); - QtcProcess process; + Process process; subConfig.setupSubProcess(&process); process.start(); @@ -1172,7 +1198,7 @@ void tst_QtcProcess::channelForwarding() QCOMPARE(error.contains(QByteArray(s_errorData)), errorForwarded); } -void tst_QtcProcess::mergedChannels_data() +void tst_Process::mergedChannels_data() { QTest::addColumn("channelMode"); QTest::addColumn("outputOnOutput"); @@ -1193,7 +1219,7 @@ void tst_QtcProcess::mergedChannels_data() } -void tst_QtcProcess::mergedChannels() +void tst_Process::mergedChannels() { QFETCH(QProcess::ProcessChannelMode, channelMode); QFETCH(bool, outputOnOutput); @@ -1202,7 +1228,7 @@ void tst_QtcProcess::mergedChannels() QFETCH(bool, errorOnError); SubProcessConfig subConfig(ProcessTestApp::StandardOutputAndErrorWriter::envVar(), {}); - QtcProcess process; + Process process; subConfig.setupSubProcess(&process); process.setProcessChannelMode(channelMode); @@ -1218,7 +1244,7 @@ void tst_QtcProcess::mergedChannels() QCOMPARE(error.contains(QByteArray(s_errorData)), errorOnError); } -void tst_QtcProcess::destroyBlockingProcess_data() +void tst_Process::destroyBlockingProcess_data() { QTest::addColumn("blockType"); @@ -1228,24 +1254,24 @@ void tst_QtcProcess::destroyBlockingProcess_data() QTest::newRow("EventLoop") << BlockType::EventLoop; } -void tst_QtcProcess::destroyBlockingProcess() +void tst_Process::destroyBlockingProcess() { QFETCH(BlockType, blockType); SubProcessConfig subConfig(ProcessTestApp::BlockingProcess::envVar(), QString::number(int(blockType))); - QtcProcess process; + Process process; subConfig.setupSubProcess(&process); process.start(); QVERIFY(process.waitForStarted()); QVERIFY(!process.waitForFinished(1000)); } -void tst_QtcProcess::flushFinishedWhileWaitingForReadyRead() +void tst_Process::flushFinishedWhileWaitingForReadyRead() { SubProcessConfig subConfig(ProcessTestApp::SimpleTest::envVar(), {}); - QtcProcess process; + Process process; subConfig.setupSubProcess(&process); process.start(); @@ -1267,10 +1293,10 @@ void tst_QtcProcess::flushFinishedWhileWaitingForReadyRead() QVERIFY(reply.contains(s_simpleTestData)); } -void tst_QtcProcess::crash() +void tst_Process::crash() { SubProcessConfig subConfig(ProcessTestApp::Crash::envVar(), {}); - QtcProcess process; + Process process; subConfig.setupSubProcess(&process); process.start(); @@ -1278,17 +1304,17 @@ void tst_QtcProcess::crash() QVERIFY(process.isRunning()); QEventLoop loop; - connect(&process, &QtcProcess::done, &loop, &QEventLoop::quit); + connect(&process, &Process::done, &loop, &QEventLoop::quit); loop.exec(); QCOMPARE(process.error(), QProcess::Crashed); QCOMPARE(process.exitStatus(), QProcess::CrashExit); } -void tst_QtcProcess::crashAfterOneSecond() +void tst_Process::crashAfterOneSecond() { SubProcessConfig subConfig(ProcessTestApp::CrashAfterOneSecond::envVar(), {}); - QtcProcess process; + Process process; subConfig.setupSubProcess(&process); process.start(); @@ -1303,12 +1329,12 @@ void tst_QtcProcess::crashAfterOneSecond() QCOMPARE(process.error(), QProcess::Crashed); } -void tst_QtcProcess::recursiveCrashingProcess() +void tst_Process::recursiveCrashingProcess() { const int recursionDepth = 5; // must be at least 2 SubProcessConfig subConfig(ProcessTestApp::RecursiveCrashingProcess::envVar(), QString::number(recursionDepth)); - QtcProcess process; + Process process; subConfig.setupSubProcess(&process); process.start(); QVERIFY(process.waitForStarted(1000)); @@ -1329,7 +1355,7 @@ static int runningTestProcessCount() return testProcessCounter; } -void tst_QtcProcess::recursiveBlockingProcess() +void tst_Process::recursiveBlockingProcess() { if (HostOsInfo::isWindowsHost()) QSKIP(s_skipTerminateOnWindows); @@ -1340,7 +1366,7 @@ void tst_QtcProcess::recursiveBlockingProcess() SubProcessConfig subConfig(ProcessTestApp::RecursiveBlockingProcess::envVar(), QString::number(recursionDepth)); { - QtcProcess process; + Process process; subConfig.setupSubProcess(&process); process.start(); QVERIFY(process.waitForStarted(1000)); @@ -1368,7 +1394,7 @@ enum class QuitType { Q_DECLARE_METATYPE(QuitType) -void tst_QtcProcess::quitBlockingProcess_data() +void tst_Process::quitBlockingProcess_data() { QTest::addColumn("quitType"); QTest::addColumn("doneExpected"); @@ -1380,7 +1406,7 @@ void tst_QtcProcess::quitBlockingProcess_data() QTest::newRow("Close") << QuitType::Close << false << true; } -void tst_QtcProcess::quitBlockingProcess() +void tst_Process::quitBlockingProcess() { QFETCH(QuitType, quitType); QFETCH(bool, doneExpected); @@ -1394,10 +1420,10 @@ void tst_QtcProcess::quitBlockingProcess() SubProcessConfig subConfig(ProcessTestApp::RecursiveBlockingProcess::envVar(), QString::number(recursionDepth)); - QtcProcess process; + Process process; subConfig.setupSubProcess(&process); bool done = false; - connect(&process, &QtcProcess::done, this, [&done] { done = true; }); + connect(&process, &Process::done, this, [&done] { done = true; }); process.start(); QVERIFY(process.waitForStarted()); @@ -1440,17 +1466,17 @@ void tst_QtcProcess::quitBlockingProcess() } } -void tst_QtcProcess::tarPipe() +void tst_Process::tarPipe() { if (!FilePath::fromString("tar").searchInPath().isExecutableFile()) QSKIP("This test uses \"tar\" command."); - QtcProcess sourceProcess; - QtcProcess targetProcess; + Process sourceProcess; + Process targetProcess; targetProcess.setProcessMode(ProcessMode::Writer); - QObject::connect(&sourceProcess, &QtcProcess::readyReadStandardOutput, + QObject::connect(&sourceProcess, &Process::readyReadStandardOutput, &targetProcess, [&sourceProcess, &targetProcess]() { targetProcess.writeRaw(sourceProcess.readAllRawStandardOutput()); }); @@ -1494,6 +1520,21 @@ void tst_QtcProcess::tarPipe() QCOMPARE(sourceFile.fileSize(), destinationFile.fileSize()); } -QTEST_GUILESS_MAIN(tst_QtcProcess) +void tst_Process::stdinToShell() +{ + // proc.setCommand({"cmd.exe", {}}); - Piping into cmd.exe does not appear to work. + if (HostOsInfo::isWindowsHost()) + QSKIP("Skipping env test on Windows"); -#include "tst_qtcprocess.moc" + Process proc; + proc.setCommand({"sh", {}}); + proc.setWriteData("echo hallo"); + proc.runBlocking(); + + QString result = proc.readAllStandardOutput().trimmed(); + QCOMPARE(result, "hallo"); +} + +QTEST_GUILESS_MAIN(tst_Process) + +#include "tst_process.moc" diff --git a/tests/auto/utils/settings/tst_settings.cpp b/tests/auto/utils/settings/tst_settings.cpp index a436ac45e81..d45d673e4d3 100644 --- a/tests/auto/utils/settings/tst_settings.cpp +++ b/tests/auto/utils/settings/tst_settings.cpp @@ -10,7 +10,6 @@ using namespace Utils; -const char TESTACCESSOR_DN[] = "Test Settings Accessor"; const char TESTACCESSOR_APPLICATION_DN[] = "SettingsAccessor Test (Basic)"; const char TESTACCESSOR_DEFAULT_ID[] = "testId"; @@ -143,10 +142,11 @@ public: using Utils::MergingSettingsAccessor::upgradeSettings; }; -BasicTestSettingsAccessor::BasicTestSettingsAccessor(const FilePath &baseName, const QByteArray &id) : - Utils::MergingSettingsAccessor(std::make_unique(this), - "TestData", TESTACCESSOR_DN, TESTACCESSOR_APPLICATION_DN) +BasicTestSettingsAccessor::BasicTestSettingsAccessor(const FilePath &baseName, const QByteArray &id) { + setDocType("TestData"); + setApplicationDisplayName(TESTACCESSOR_APPLICATION_DN); + setStrategy(std::make_unique(this)); setSettingsId(id); setBaseFilePath(baseName); } diff --git a/tests/auto/utils/stringutils/tst_stringutils.cpp b/tests/auto/utils/stringutils/tst_stringutils.cpp index e13e8411a5b..4863bfd9508 100644 --- a/tests/auto/utils/stringutils/tst_stringutils.cpp +++ b/tests/auto/utils/stringutils/tst_stringutils.cpp @@ -79,6 +79,8 @@ private slots: void testTrim(); void testWildcardToRegularExpression_data(); void testWildcardToRegularExpression(); + void testSplitAtFirst_data(); + void testSplitAtFirst(); private: TestMacroExpander mx; @@ -404,6 +406,38 @@ void tst_StringUtils::testWildcardToRegularExpression() QCOMPARE(string.contains(re), matches); } +void tst_StringUtils::testSplitAtFirst_data() +{ + QTest::addColumn("string"); + QTest::addColumn("separator"); + QTest::addColumn("left"); + QTest::addColumn("right"); + + QTest::newRow("Empty") << QString{} << QChar{} << QString{} << QString{}; + QTest::newRow("EmptyString") << QString{} << QChar{'a'} << QString{} << QString{}; + QTest::newRow("EmptySeparator") << QString{"abc"} << QChar{} << QString{"abc"} << QString{}; + QTest::newRow("NoSeparator") << QString{"abc"} << QChar{'d'} << QString{"abc"} << QString{}; + QTest::newRow("SeparatorAtStart") << QString{"abc"} << QChar{'a'} << QString{} << QString{"bc"}; + QTest::newRow("SeparatorAtEnd") << QString{"abc"} << QChar{'c'} << QString{"ab"} << QString{}; + QTest::newRow("SeparatorInMiddle") + << QString{"abc"} << QChar{'b'} << QString{"a"} << QString{"c"}; + QTest::newRow("SeparatorAtStartAndEnd") + << QString{"abca"} << QChar{'a'} << QString{} << QString{"bca"}; +} + +void tst_StringUtils::testSplitAtFirst() +{ + QFETCH(QString, string); + QFETCH(QChar, separator); + QFETCH(QString, left); + QFETCH(QString, right); + + const auto [l, r] = Utils::splitAtFirst(string, separator); + + QCOMPARE(l, left); + QCOMPARE(r, right); +} + QTEST_GUILESS_MAIN(tst_StringUtils) #include "tst_stringutils.moc" diff --git a/tests/auto/utils/tasktree/tasktree.qbs b/tests/auto/utils/tasktree/tasktree.qbs deleted file mode 100644 index ed138d9f393..00000000000 --- a/tests/auto/utils/tasktree/tasktree.qbs +++ /dev/null @@ -1,27 +0,0 @@ -import qbs.FileInfo - -Project { - QtcAutotest { - name: "TaskTree autotest" - - Depends { name: "Utils" } - Depends { name: "app_version_header" } - - files: [ - "tst_tasktree.cpp", - ] - cpp.defines: { - var defines = base; - if (qbs.targetOS === "windows") - defines.push("_CRT_SECURE_NO_WARNINGS"); - var absLibExecPath = FileInfo.joinPaths(qbs.installRoot, qbs.installPrefix, - qtc.ide_libexec_path); - var relLibExecPath = FileInfo.relativePath(destinationDirectory, absLibExecPath); - defines.push('TEST_RELATIVE_LIBEXEC_PATH="' + relLibExecPath + '"'); - defines.push('TESTAPP_PATH="' - + FileInfo.joinPaths(destinationDirectory, "testapp") + '"'); - return defines; - } - } - references: "testapp/testapp.qbs" -} diff --git a/tests/auto/utils/tasktree/testapp/CMakeLists.txt b/tests/auto/utils/tasktree/testapp/CMakeLists.txt deleted file mode 100644 index 27a1289137c..00000000000 --- a/tests/auto/utils/tasktree/testapp/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -add_qtc_executable(testapp - DEPENDS Utils app_version - SOURCES main.cpp - SKIP_INSTALL - INTERNAL_ONLY -) - -set_target_properties(testapp PROPERTIES - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" - ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" -) diff --git a/tests/auto/utils/tasktree/testapp/main.cpp b/tests/auto/utils/tasktree/testapp/main.cpp deleted file mode 100644 index 59e2d7baa07..00000000000 --- a/tests/auto/utils/tasktree/testapp/main.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include - -#include -#include - -#ifdef Q_OS_WIN -#include -#include -#endif - -const char CRASH_OPTION[] = "-crash"; -const char RETURN_OPTION[] = "-return"; -const char SLEEP_OPTION[] = "-sleep"; - -int main(int argc, char **argv) -{ -#ifdef Q_OS_WIN - // avoid crash reporter dialog - _set_error_mode(_OUT_TO_STDERR); - _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG); -#endif - - if (argc > 1) { - const auto arg = QString::fromLocal8Bit(argv[1]); - if (arg == CRASH_OPTION) { - qFatal("The application has crashed purposefully!"); - return 1; - } - if (arg == RETURN_OPTION) { - if (argc > 2) { - const auto retString = QString::fromLocal8Bit(argv[2]); - bool ok = false; - const int retVal = retString.toInt(&ok); - if (ok) - return retVal; - // not an int return value - return 1; - } - // lacking return value - return 1; - } - if (arg == SLEEP_OPTION) { - if (argc > 2) { - const auto msecsString = QString::fromLocal8Bit(argv[2]); - bool ok = false; - const int msecsVal = msecsString.toInt(&ok); - if (ok) { - QThread::msleep(msecsVal); - return 0; - } - // not an int return value - return 1; - } - // lacking return value - return 1; - } - } - // not recognized option - return 1; -} diff --git a/tests/auto/utils/tasktree/testapp/testapp.qbs b/tests/auto/utils/tasktree/testapp/testapp.qbs deleted file mode 100644 index a106e851463..00000000000 --- a/tests/auto/utils/tasktree/testapp/testapp.qbs +++ /dev/null @@ -1,20 +0,0 @@ -import qbs.FileInfo - -QtApplication { - name: "testapp" - Depends { name: "qtc" } - Depends { name: "Utils" } - Depends { name: "app_version_header" } - - consoleApplication: true - cpp.cxxLanguageVersion: "c++17" - cpp.rpaths: project.buildDirectory + '/' + qtc.ide_library_path - - install: false - destinationDirectory: project.buildDirectory + '/' - + FileInfo.relativePath(project.ide_source_tree, sourceDirectory) - - files: [ - "main.cpp", - ] -} diff --git a/tests/auto/utils/tasktree/tst_tasktree.cpp b/tests/auto/utils/tasktree/tst_tasktree.cpp deleted file mode 100644 index 43bf0468c5d..00000000000 --- a/tests/auto/utils/tasktree/tst_tasktree.cpp +++ /dev/null @@ -1,1095 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include - -#include -#include -#include -#include - -#include - -using namespace Utils; -using namespace Utils::Tasking; - -enum class Handler { - Setup, - Done, - Error, - GroupSetup, - GroupDone, - GroupError -}; - -using Log = QList>; - -struct CustomStorage -{ - CustomStorage() { ++s_count; } - ~CustomStorage() { --s_count; } - Log m_log; - static int instanceCount() { return s_count; } -private: - static int s_count; -}; - -int CustomStorage::s_count = 0; -static const char s_processIdProperty[] = "__processId"; - -enum class OnStart { Running, NotRunning }; -enum class OnDone { Success, Failure }; - -struct TestData { - TreeStorage storage; - Group root; - Log expectedLog; - int taskCount = 0; - OnStart onStart = OnStart::Running; - OnDone onDone = OnDone::Success; -}; - -class tst_TaskTree : public QObject -{ - Q_OBJECT - -private slots: - void initTestCase(); - - void validConstructs(); // compile test - void processTree_data(); - void processTree(); - void storageOperators(); - void storageDestructor(); - - void cleanupTestCase(); - -private: - FilePath m_testAppPath; -}; - -void tst_TaskTree::initTestCase() -{ - TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath() + "/" - + Core::Constants::IDE_CASED_ID + "-XXXXXX"); - const QString libExecPath(qApp->applicationDirPath() + '/' - + QLatin1String(TEST_RELATIVE_LIBEXEC_PATH)); - LauncherInterface::setPathToLauncher(libExecPath); - m_testAppPath = FilePath::fromString(QLatin1String(TESTAPP_PATH) - + QLatin1String("/testapp")).withExecutableSuffix(); -} - -void tst_TaskTree::cleanupTestCase() -{ - Singleton::deleteAll(); -} - -void tst_TaskTree::validConstructs() -{ - const Group process { - parallel, - Process([](QtcProcess &) {}, [](const QtcProcess &) {}), - Process([](QtcProcess &) {}, [](const QtcProcess &) {}), - Process([](QtcProcess &) {}, [](const QtcProcess &) {}) - }; - - const Group group1 { - process - }; - - const Group group2 { - parallel, - Group { - parallel, - Process([](QtcProcess &) {}, [](const QtcProcess &) {}), - Group { - parallel, - Process([](QtcProcess &) {}, [](const QtcProcess &) {}), - Group { - parallel, - Process([](QtcProcess &) {}, [](const QtcProcess &) {}) - } - }, - Group { - parallel, - Process([](QtcProcess &) {}, [](const QtcProcess &) {}), - OnGroupDone([] {}) - } - }, - process, - OnGroupDone([] {}), - OnGroupError([] {}) - }; -} - -void tst_TaskTree::processTree_data() -{ - QTest::addColumn("testData"); - - TreeStorage storage; - - const auto setupProcessHelper = [storage, testAppPath = m_testAppPath] - (QtcProcess &process, const QStringList &args, int processId) { - process.setCommand(CommandLine(testAppPath, args)); - process.setProperty(s_processIdProperty, processId); - storage->m_log.append({processId, Handler::Setup}); - }; - const auto setupProcess = [setupProcessHelper](int processId) { - return [=](QtcProcess &process) { - setupProcessHelper(process, {"-return", "0"}, processId); - }; - }; - const auto setupCrashProcess = [setupProcessHelper](int processId) { - return [=](QtcProcess &process) { - setupProcessHelper(process, {"-crash"}, processId); - }; - }; - const auto setupSleepProcess = [setupProcessHelper](int processId, int msecs) { - return [=](QtcProcess &process) { - setupProcessHelper(process, {"-sleep", QString::number(msecs)}, processId); - }; - }; - const auto setupDynamicProcess = [setupProcessHelper](int processId, TaskAction action) { - return [=](QtcProcess &process) { - setupProcessHelper(process, {"-return", "0"}, processId); - return action; - }; - }; - const auto readResult = [storage](const QtcProcess &process) { - const int processId = process.property(s_processIdProperty).toInt(); - storage->m_log.append({processId, Handler::Done}); - }; - const auto readError = [storage](const QtcProcess &process) { - const int processId = process.property(s_processIdProperty).toInt(); - storage->m_log.append({processId, Handler::Error}); - }; - const auto groupSetup = [storage](int groupId) { - return [=] { storage->m_log.append({groupId, Handler::GroupSetup}); }; - }; - const auto groupDone = [storage](int groupId) { - return [=] { storage->m_log.append({groupId, Handler::GroupDone}); }; - }; - const auto groupError = [storage](int groupId) { - return [=] { storage->m_log.append({groupId, Handler::GroupError}); }; - }; - - const auto constructSimpleSequence = [=](const Workflow &policy) { - return Group { - Storage(storage), - policy, - Process(setupProcess(1), readResult), - Process(setupCrashProcess(2), readResult, readError), - Process(setupProcess(3), readResult), - OnGroupDone(groupDone(0)), - OnGroupError(groupError(0)) - }; - }; - const auto constructDynamicHierarchy = [=](TaskAction taskAction) { - return Group { - Storage(storage), - Group { - Process(setupProcess(1), readResult) - }, - Group { - OnGroupSetup([=] { return taskAction; }), - Process(setupProcess(2), readResult), - Process(setupProcess(3), readResult), - Process(setupProcess(4), readResult) - }, - OnGroupDone(groupDone(0)), - OnGroupError(groupError(0)) - }; - }; - - { - const Group root1 { - Storage(storage), - OnGroupDone(groupDone(0)), - OnGroupError(groupError(0)) - }; - const Group root2 { - Storage(storage), - OnGroupSetup([] { return TaskAction::Continue; }), - OnGroupDone(groupDone(0)), - OnGroupError(groupError(0)) - }; - const Group root3 { - Storage(storage), - OnGroupSetup([] { return TaskAction::StopWithDone; }), - OnGroupDone(groupDone(0)), - OnGroupError(groupError(0)) - }; - const Group root4 { - Storage(storage), - OnGroupSetup([] { return TaskAction::StopWithError; }), - OnGroupDone(groupDone(0)), - OnGroupError(groupError(0)) - }; - const Log logDone {{0, Handler::GroupDone}}; - const Log logError {{0, Handler::GroupError}}; - QTest::newRow("Empty") - << TestData{storage, root1, logDone, 0, OnStart::NotRunning, OnDone::Success}; - QTest::newRow("EmptyContinue") - << TestData{storage, root2, logDone, 0, OnStart::NotRunning, OnDone::Success}; - QTest::newRow("EmptyDone") - << TestData{storage, root3, logDone, 0, OnStart::NotRunning, OnDone::Success}; - QTest::newRow("EmptyError") - << TestData{storage, root4, logError, 0, OnStart::NotRunning, OnDone::Failure}; - } - - { - const Group root { - Storage(storage), - Process(setupDynamicProcess(1, TaskAction::StopWithDone), readResult, readError), - Process(setupDynamicProcess(2, TaskAction::StopWithDone), readResult, readError) - }; - const Log log {{1, Handler::Setup}, {2, Handler::Setup}}; - QTest::newRow("DynamicTaskDone") - << TestData{storage, root, log, 2, OnStart::NotRunning, OnDone::Success}; - } - - { - const Group root { - Storage(storage), - Process(setupDynamicProcess(1, TaskAction::StopWithError), readResult, readError), - Process(setupDynamicProcess(2, TaskAction::StopWithError), readResult, readError) - }; - const Log log {{1, Handler::Setup}}; - QTest::newRow("DynamicTaskError") - << TestData{storage, root, log, 2, OnStart::NotRunning, OnDone::Failure}; - } - - { - const Group root { - Storage(storage), - Process(setupDynamicProcess(1, TaskAction::Continue), readResult, readError), - Process(setupDynamicProcess(2, TaskAction::Continue), readResult, readError), - Process(setupDynamicProcess(3, TaskAction::StopWithError), readResult, readError), - Process(setupDynamicProcess(4, TaskAction::Continue), readResult, readError) - }; - const Log log { - {1, Handler::Setup}, - {1, Handler::Done}, - {2, Handler::Setup}, - {2, Handler::Done}, - {3, Handler::Setup} - }; - QTest::newRow("DynamicMixed") - << TestData{storage, root, log, 4, OnStart::Running, OnDone::Failure}; - } - - { - const Group root { - parallel, - Storage(storage), - Process(setupDynamicProcess(1, TaskAction::Continue), readResult, readError), - Process(setupDynamicProcess(2, TaskAction::Continue), readResult, readError), - Process(setupDynamicProcess(3, TaskAction::StopWithError), readResult, readError), - Process(setupDynamicProcess(4, TaskAction::Continue), readResult, readError) - }; - const Log log { - {1, Handler::Setup}, - {2, Handler::Setup}, - {3, Handler::Setup}, - {1, Handler::Error}, - {2, Handler::Error} - }; - QTest::newRow("DynamicParallel") - << TestData{storage, root, log, 4, OnStart::NotRunning, OnDone::Failure}; - } - - { - const Group root { - parallel, - Storage(storage), - Process(setupDynamicProcess(1, TaskAction::Continue), readResult, readError), - Process(setupDynamicProcess(2, TaskAction::Continue), readResult, readError), - Group { - Process(setupDynamicProcess(3, TaskAction::StopWithError), readResult, readError) - }, - Process(setupDynamicProcess(4, TaskAction::Continue), readResult, readError) - }; - const Log log { - {1, Handler::Setup}, - {2, Handler::Setup}, - {3, Handler::Setup}, - {1, Handler::Error}, - {2, Handler::Error} - }; - QTest::newRow("DynamicParallelGroup") - << TestData{storage, root, log, 4, OnStart::NotRunning, OnDone::Failure}; - } - - { - const Group root { - parallel, - Storage(storage), - Process(setupDynamicProcess(1, TaskAction::Continue), readResult, readError), - Process(setupDynamicProcess(2, TaskAction::Continue), readResult, readError), - Group { - OnGroupSetup([storage] { - storage->m_log.append({0, Handler::GroupSetup}); - return TaskAction::StopWithError; - }), - Process(setupDynamicProcess(3, TaskAction::Continue), readResult, readError) - }, - Process(setupDynamicProcess(4, TaskAction::Continue), readResult, readError) - }; - const Log log { - {1, Handler::Setup}, - {2, Handler::Setup}, - {0, Handler::GroupSetup}, - {1, Handler::Error}, - {2, Handler::Error} - }; - QTest::newRow("DynamicParallelGroupSetup") - << TestData{storage, root, log, 4, OnStart::NotRunning, OnDone::Failure}; - } - - { - const Group root { - Storage(storage), - Group { - Group { - Group { - Group { - Group { - Process(setupProcess(5), readResult, readError), - OnGroupSetup(groupSetup(5)), - OnGroupDone(groupDone(5)) - }, - OnGroupSetup(groupSetup(4)), - OnGroupDone(groupDone(4)) - }, - OnGroupSetup(groupSetup(3)), - OnGroupDone(groupDone(3)) - }, - OnGroupSetup(groupSetup(2)), - OnGroupDone(groupDone(2)) - }, - OnGroupSetup(groupSetup(1)), - OnGroupDone(groupDone(1)) - }, - OnGroupDone(groupDone(0)) - }; - const Log log { - {1, Handler::GroupSetup}, - {2, Handler::GroupSetup}, - {3, Handler::GroupSetup}, - {4, Handler::GroupSetup}, - {5, Handler::GroupSetup}, - {5, Handler::Setup}, - {5, Handler::Done}, - {5, Handler::GroupDone}, - {4, Handler::GroupDone}, - {3, Handler::GroupDone}, - {2, Handler::GroupDone}, - {1, Handler::GroupDone}, - {0, Handler::GroupDone} - }; - QTest::newRow("Nested") - << TestData{storage, root, log, 1, OnStart::Running, OnDone::Success}; - } - - { - const auto readResultAnonymous = [=](const QtcProcess &) { - storage->m_log.append({0, Handler::Done}); - }; - const Group root { - Storage(storage), - parallel, - Process(setupProcess(1), readResultAnonymous), - Process(setupProcess(2), readResultAnonymous), - Process(setupProcess(3), readResultAnonymous), - Process(setupProcess(4), readResultAnonymous), - Process(setupProcess(5), readResultAnonymous), - OnGroupDone(groupDone(0)) - }; - const Log log { - {1, Handler::Setup}, // Setup order is determined in parallel mode - {2, Handler::Setup}, - {3, Handler::Setup}, - {4, Handler::Setup}, - {5, Handler::Setup}, - {0, Handler::Done}, // Done order isn't determined in parallel mode - {0, Handler::Done}, - {0, Handler::Done}, - {0, Handler::Done}, - {0, Handler::Done}, - {0, Handler::GroupDone} - }; - QTest::newRow("Parallel") - << TestData{storage, root, log, 5, OnStart::Running, OnDone::Success}; - } - - { - auto setupSubTree = [=](TaskTree &taskTree) { - const Group nestedRoot { - Storage(storage), - Process(setupProcess(2), readResult), - Process(setupProcess(3), readResult), - Process(setupProcess(4), readResult) - }; - taskTree.setupRoot(nestedRoot); - CustomStorage *activeStorage = storage.activeStorage(); - auto collectSubLog = [activeStorage](CustomStorage *subTreeStorage){ - activeStorage->m_log += subTreeStorage->m_log; - }; - taskTree.onStorageDone(storage, collectSubLog); - }; - const Group root1 { - Storage(storage), - Process(setupProcess(1), readResult), - Process(setupProcess(2), readResult), - Process(setupProcess(3), readResult), - Process(setupProcess(4), readResult), - Process(setupProcess(5), readResult), - OnGroupDone(groupDone(0)) - }; - const Group root2 { - Storage(storage), - Group { Process(setupProcess(1), readResult) }, - Group { Process(setupProcess(2), readResult) }, - Group { Process(setupProcess(3), readResult) }, - Group { Process(setupProcess(4), readResult) }, - Group { Process(setupProcess(5), readResult) }, - OnGroupDone(groupDone(0)) - }; - const Group root3 { - Storage(storage), - Process(setupProcess(1), readResult), - Tree(setupSubTree), - Process(setupProcess(5), readResult), - OnGroupDone(groupDone(0)) - }; - const Log log { - {1, Handler::Setup}, - {1, Handler::Done}, - {2, Handler::Setup}, - {2, Handler::Done}, - {3, Handler::Setup}, - {3, Handler::Done}, - {4, Handler::Setup}, - {4, Handler::Done}, - {5, Handler::Setup}, - {5, Handler::Done}, - {0, Handler::GroupDone} - }; - QTest::newRow("Sequential") - << TestData{storage, root1, log, 5, OnStart::Running, OnDone::Success}; - QTest::newRow("SequentialEncapsulated") - << TestData{storage, root2, log, 5, OnStart::Running, OnDone::Success}; - QTest::newRow("SequentialSubTree") // We don't inspect subtrees, so taskCount is 3, not 5. - << TestData{storage, root3, log, 3, OnStart::Running, OnDone::Success}; - } - - { - const Group root { - Storage(storage), - Group { - Process(setupProcess(1), readResult), - Group { - Process(setupProcess(2), readResult), - Group { - Process(setupProcess(3), readResult), - Group { - Process(setupProcess(4), readResult), - Group { - Process(setupProcess(5), readResult), - OnGroupDone(groupDone(5)) - }, - OnGroupDone(groupDone(4)) - }, - OnGroupDone(groupDone(3)) - }, - OnGroupDone(groupDone(2)) - }, - OnGroupDone(groupDone(1)) - }, - OnGroupDone(groupDone(0)) - }; - const Log log { - {1, Handler::Setup}, - {1, Handler::Done}, - {2, Handler::Setup}, - {2, Handler::Done}, - {3, Handler::Setup}, - {3, Handler::Done}, - {4, Handler::Setup}, - {4, Handler::Done}, - {5, Handler::Setup}, - {5, Handler::Done}, - {5, Handler::GroupDone}, - {4, Handler::GroupDone}, - {3, Handler::GroupDone}, - {2, Handler::GroupDone}, - {1, Handler::GroupDone}, - {0, Handler::GroupDone} - }; - QTest::newRow("SequentialNested") - << TestData{storage, root, log, 5, OnStart::Running, OnDone::Success}; - } - - { - const Group root { - Storage(storage), - Process(setupProcess(1), readResult), - Process(setupProcess(2), readResult), - Process(setupCrashProcess(3), readResult, readError), - Process(setupProcess(4), readResult), - Process(setupProcess(5), readResult), - OnGroupDone(groupDone(0)), - OnGroupError(groupError(0)) - }; - const Log log { - {1, Handler::Setup}, - {1, Handler::Done}, - {2, Handler::Setup}, - {2, Handler::Done}, - {3, Handler::Setup}, - {3, Handler::Error}, - {0, Handler::GroupError} - }; - QTest::newRow("SequentialError") - << TestData{storage, root, log, 5, OnStart::Running, OnDone::Failure}; - } - - { - const Group root = constructSimpleSequence(stopOnError); - const Log log { - {1, Handler::Setup}, - {1, Handler::Done}, - {2, Handler::Setup}, - {2, Handler::Error}, - {0, Handler::GroupError} - }; - QTest::newRow("StopOnError") - << TestData{storage, root, log, 3, OnStart::Running, OnDone::Failure}; - } - - { - const Group root = constructSimpleSequence(continueOnError); - const Log log { - {1, Handler::Setup}, - {1, Handler::Done}, - {2, Handler::Setup}, - {2, Handler::Error}, - {3, Handler::Setup}, - {3, Handler::Done}, - {0, Handler::GroupError} - }; - QTest::newRow("ContinueOnError") - << TestData{storage, root, log, 3, OnStart::Running, OnDone::Failure}; - } - - { - const Group root = constructSimpleSequence(stopOnDone); - const Log log { - {1, Handler::Setup}, - {1, Handler::Done}, - {0, Handler::GroupDone} - }; - QTest::newRow("StopOnDone") - << TestData{storage, root, log, 3, OnStart::Running, OnDone::Success}; - } - - { - const Group root = constructSimpleSequence(continueOnDone); - const Log log { - {1, Handler::Setup}, - {1, Handler::Done}, - {2, Handler::Setup}, - {2, Handler::Error}, - {3, Handler::Setup}, - {3, Handler::Done}, - {0, Handler::GroupDone} - }; - QTest::newRow("ContinueOnDone") - << TestData{storage, root, log, 3, OnStart::Running, OnDone::Success}; - } - - { - const Group root { - Storage(storage), - optional, - Process(setupCrashProcess(1), readResult, readError), - Process(setupCrashProcess(2), readResult, readError), - OnGroupDone(groupDone(0)), - OnGroupError(groupError(0)) - }; - const Log log { - {1, Handler::Setup}, - {1, Handler::Error}, - {2, Handler::Setup}, - {2, Handler::Error}, - {0, Handler::GroupDone} - }; - QTest::newRow("Optional") - << TestData{storage, root, log, 2, OnStart::Running, OnDone::Success}; - } - - { - const Group root = constructDynamicHierarchy(TaskAction::StopWithDone); - const Log log { - {1, Handler::Setup}, - {1, Handler::Done}, - {0, Handler::GroupDone} - }; - QTest::newRow("DynamicSetupDone") - << TestData{storage, root, log, 4, OnStart::Running, OnDone::Success}; - } - - { - const Group root = constructDynamicHierarchy(TaskAction::StopWithError); - const Log log { - {1, Handler::Setup}, - {1, Handler::Done}, - {0, Handler::GroupError} - }; - QTest::newRow("DynamicSetupError") - << TestData{storage, root, log, 4, OnStart::Running, OnDone::Failure}; - } - - { - const Group root = constructDynamicHierarchy(TaskAction::Continue); - const Log log { - {1, Handler::Setup}, - {1, Handler::Done}, - {2, Handler::Setup}, - {2, Handler::Done}, - {3, Handler::Setup}, - {3, Handler::Done}, - {4, Handler::Setup}, - {4, Handler::Done}, - {0, Handler::GroupDone} - }; - QTest::newRow("DynamicSetupContinue") - << TestData{storage, root, log, 4, OnStart::Running, OnDone::Success}; - } - - { - const Group root { - ParallelLimit(2), - Storage(storage), - Group { - OnGroupSetup(groupSetup(1)), - Process(setupProcess(1)) - }, - Group { - OnGroupSetup(groupSetup(2)), - Process(setupProcess(2)) - }, - Group { - OnGroupSetup(groupSetup(3)), - Process(setupProcess(3)) - }, - Group { - OnGroupSetup(groupSetup(4)), - Process(setupProcess(4)) - } - }; - const Log log { - {1, Handler::GroupSetup}, - {1, Handler::Setup}, - {2, Handler::GroupSetup}, - {2, Handler::Setup}, - {3, Handler::GroupSetup}, - {3, Handler::Setup}, - {4, Handler::GroupSetup}, - {4, Handler::Setup} - }; - QTest::newRow("NestedParallel") - << TestData{storage, root, log, 4, OnStart::Running, OnDone::Success}; - } - - { - const Group root { - ParallelLimit(2), - Storage(storage), - Group { - OnGroupSetup(groupSetup(1)), - Process(setupProcess(1)) - }, - Group { - OnGroupSetup(groupSetup(2)), - Process(setupProcess(2)) - }, - Group { - OnGroupSetup(groupSetup(3)), - Process(setupDynamicProcess(3, TaskAction::StopWithDone)) - }, - Group { - OnGroupSetup(groupSetup(4)), - Process(setupProcess(4)) - }, - Group { - OnGroupSetup(groupSetup(5)), - Process(setupProcess(5)) - } - }; - const Log log { - {1, Handler::GroupSetup}, - {1, Handler::Setup}, - {2, Handler::GroupSetup}, - {2, Handler::Setup}, - {3, Handler::GroupSetup}, - {3, Handler::Setup}, - {4, Handler::GroupSetup}, - {4, Handler::Setup}, - {5, Handler::GroupSetup}, - {5, Handler::Setup} - }; - QTest::newRow("NestedParallelDone") - << TestData{storage, root, log, 5, OnStart::Running, OnDone::Success}; - } - - { - const Group root1 { - ParallelLimit(2), - Storage(storage), - Group { - OnGroupSetup(groupSetup(1)), - Process(setupProcess(1)) - }, - Group { - OnGroupSetup(groupSetup(2)), - Process(setupProcess(2)) - }, - Group { - OnGroupSetup(groupSetup(3)), - Process(setupDynamicProcess(3, TaskAction::StopWithError)) - }, - Group { - OnGroupSetup(groupSetup(4)), - Process(setupProcess(4)) - }, - Group { - OnGroupSetup(groupSetup(5)), - Process(setupProcess(5)) - } - }; - - // Inside this test the process 2 should finish first, then synchonously: - // - process 3 should exit setup with error - // - process 1 should be stopped as a consequence of error inside the group - // - processes 4 and 5 should be skipped - const Group root2 { - ParallelLimit(2), - Storage(storage), - Group { - OnGroupSetup(groupSetup(1)), - Process(setupSleepProcess(1, 100)) - }, - Group { - OnGroupSetup(groupSetup(2)), - Process(setupProcess(2)) - }, - Group { - OnGroupSetup(groupSetup(3)), - Process(setupDynamicProcess(3, TaskAction::StopWithError)) - }, - Group { - OnGroupSetup(groupSetup(4)), - Process(setupProcess(4)) - }, - Group { - OnGroupSetup(groupSetup(5)), - Process(setupProcess(5)) - } - }; - - // This test ensures process 1 doesn't invoke its done handler, - // being ready while sleeping in process 2 done handler. - // Inside this test the process 2 should finish first, then synchonously: - // - process 3 should exit setup with error - // - process 1 should be stopped as a consequence of error inside the group - // - process 4 should be skipped - // - the first child group of root should finish with error - // - process 5 should be started (because of root's continueOnError policy) - const Group root3 { - continueOnError, - Storage(storage), - Group { - ParallelLimit(2), - Group { - OnGroupSetup(groupSetup(1)), - Process(setupSleepProcess(1, 100)) - }, - Group { - OnGroupSetup(groupSetup(2)), - Process(setupProcess(2), [](const QtcProcess &) { QThread::msleep(200); }) - }, - Group { - OnGroupSetup(groupSetup(3)), - Process(setupDynamicProcess(3, TaskAction::StopWithError)) - }, - Group { - OnGroupSetup(groupSetup(4)), - Process(setupProcess(4)) - } - }, - Group { - OnGroupSetup(groupSetup(5)), - Process(setupProcess(5)) - } - }; - const Log shortLog { - {1, Handler::GroupSetup}, - {1, Handler::Setup}, - {2, Handler::GroupSetup}, - {2, Handler::Setup}, - {3, Handler::GroupSetup}, - {3, Handler::Setup} - }; - const Log longLog = shortLog + Log {{5, Handler::GroupSetup}, {5, Handler::Setup}}; - QTest::newRow("NestedParallelError1") - << TestData{storage, root1, shortLog, 5, OnStart::Running, OnDone::Failure}; - QTest::newRow("NestedParallelError2") - << TestData{storage, root2, shortLog, 5, OnStart::Running, OnDone::Failure}; - QTest::newRow("NestedParallelError3") - << TestData{storage, root3, longLog, 5, OnStart::Running, OnDone::Failure}; - } - - { - const Group root { - ParallelLimit(2), - Storage(storage), - Group { - Storage(TreeStorage()), - OnGroupSetup(groupSetup(1)), - Group { - parallel, - Process(setupProcess(1)) - } - }, - Group { - Storage(TreeStorage()), - OnGroupSetup(groupSetup(2)), - Group { - parallel, - Process(setupProcess(2)) - } - }, - Group { - Storage(TreeStorage()), - OnGroupSetup(groupSetup(3)), - Group { - parallel, - Process(setupProcess(3)) - } - }, - Group { - Storage(TreeStorage()), - OnGroupSetup(groupSetup(4)), - Group { - parallel, - Process(setupProcess(4)) - } - } - }; - const Log log { - {1, Handler::GroupSetup}, - {1, Handler::Setup}, - {2, Handler::GroupSetup}, - {2, Handler::Setup}, - {3, Handler::GroupSetup}, - {3, Handler::Setup}, - {4, Handler::GroupSetup}, - {4, Handler::Setup} - }; - QTest::newRow("DeeplyNestedParallel") - << TestData{storage, root, log, 4, OnStart::Running, OnDone::Success}; - } - - { - const Group root { - ParallelLimit(2), - Storage(storage), - Group { - Storage(TreeStorage()), - OnGroupSetup(groupSetup(1)), - Group { Process(setupProcess(1)) } - }, - Group { - Storage(TreeStorage()), - OnGroupSetup(groupSetup(2)), - Group { Process(setupProcess(2)) } - }, - Group { - Storage(TreeStorage()), - OnGroupSetup(groupSetup(3)), - Group { Process(setupDynamicProcess(3, TaskAction::StopWithDone)) } - }, - Group { - Storage(TreeStorage()), - OnGroupSetup(groupSetup(4)), - Group { Process(setupProcess(4)) } - }, - Group { - Storage(TreeStorage()), - OnGroupSetup(groupSetup(5)), - Group { Process(setupProcess(5)) } - } - }; - const Log log { - {1, Handler::GroupSetup}, - {1, Handler::Setup}, - {2, Handler::GroupSetup}, - {2, Handler::Setup}, - {3, Handler::GroupSetup}, - {3, Handler::Setup}, - {4, Handler::GroupSetup}, - {4, Handler::Setup}, - {5, Handler::GroupSetup}, - {5, Handler::Setup} - }; - QTest::newRow("DeeplyNestedParallelDone") - << TestData{storage, root, log, 5, OnStart::Running, OnDone::Success}; - } - - { - const Group root { - ParallelLimit(2), - Storage(storage), - Group { - Storage(TreeStorage()), - OnGroupSetup(groupSetup(1)), - Group { Process(setupProcess(1)) } - }, - Group { - Storage(TreeStorage()), - OnGroupSetup(groupSetup(2)), - Group { Process(setupProcess(2)) } - }, - Group { - Storage(TreeStorage()), - OnGroupSetup(groupSetup(3)), - Group { Process(setupDynamicProcess(3, TaskAction::StopWithError)) } - }, - Group { - Storage(TreeStorage()), - OnGroupSetup(groupSetup(4)), - Group { Process(setupProcess(4)) } - }, - Group { - Storage(TreeStorage()), - OnGroupSetup(groupSetup(5)), - Group { Process(setupProcess(5)) } - } - }; - const Log log { - {1, Handler::GroupSetup}, - {1, Handler::Setup}, - {2, Handler::GroupSetup}, - {2, Handler::Setup}, - {3, Handler::GroupSetup}, - {3, Handler::Setup} - }; - QTest::newRow("DeeplyNestedParallelError") - << TestData{storage, root, log, 5, OnStart::Running, OnDone::Failure}; - } -} - -void tst_TaskTree::processTree() -{ - QFETCH(TestData, testData); - - QEventLoop eventLoop; - TaskTree taskTree(testData.root); - QCOMPARE(taskTree.taskCount(), testData.taskCount); - int doneCount = 0; - int errorCount = 0; - connect(&taskTree, &TaskTree::done, this, [&doneCount, &eventLoop] { - ++doneCount; - eventLoop.quit(); - }); - connect(&taskTree, &TaskTree::errorOccurred, this, [&errorCount, &eventLoop] { - ++errorCount; - eventLoop.quit(); - }); - Log actualLog; - auto collectLog = [&actualLog](CustomStorage *storage){ - actualLog = storage->m_log; - }; - taskTree.onStorageDone(testData.storage, collectLog); - taskTree.start(); - const bool expectRunning = testData.onStart == OnStart::Running; - QCOMPARE(taskTree.isRunning(), expectRunning); - - if (expectRunning) { - QTimer timer; - bool timedOut = false; - connect(&timer, &QTimer::timeout, &eventLoop, [&eventLoop, &timedOut] { - timedOut = true; - eventLoop.quit(); - }); - timer.setInterval(2000); - timer.setSingleShot(true); - timer.start(); - eventLoop.exec(); - QCOMPARE(timedOut, false); - QCOMPARE(taskTree.isRunning(), false); - } - - QCOMPARE(taskTree.progressValue(), testData.taskCount); - QCOMPARE(actualLog, testData.expectedLog); - QCOMPARE(CustomStorage::instanceCount(), 0); - - const bool expectSuccess = testData.onDone == OnDone::Success; - const int expectedDoneCount = expectSuccess ? 1 : 0; - const int expectedErrorCount = expectSuccess ? 0 : 1; - QCOMPARE(doneCount, expectedDoneCount); - QCOMPARE(errorCount, expectedErrorCount); -} - -void tst_TaskTree::storageOperators() -{ - TreeStorageBase storage1 = TreeStorage(); - TreeStorageBase storage2 = TreeStorage(); - TreeStorageBase storage3 = storage1; - - QVERIFY(storage1 == storage3); - QVERIFY(storage1 != storage2); - QVERIFY(storage2 != storage3); -} - -// This test checks whether a running task tree may be safely destructed. -// It also checks whether the destructor of a task tree deletes properly the storage created -// while starting the task tree. When running task tree is destructed, the storage done -// handler shouldn't be invoked. -void tst_TaskTree::storageDestructor() -{ - bool setupCalled = false; - const auto setupHandler = [&setupCalled](CustomStorage *) { - setupCalled = true; - }; - bool doneCalled = false; - const auto doneHandler = [&doneCalled](CustomStorage *) { - doneCalled = true; - }; - QCOMPARE(CustomStorage::instanceCount(), 0); - { - TreeStorage storage; - const auto setupProcess = [testAppPath = m_testAppPath](QtcProcess &process) { - process.setCommand(CommandLine(testAppPath, {"-sleep", "1000"})); - }; - const Group root { - Storage(storage), - Process(setupProcess) - }; - - TaskTree taskTree(root); - QCOMPARE(CustomStorage::instanceCount(), 0); - taskTree.onStorageSetup(storage, setupHandler); - taskTree.onStorageDone(storage, doneHandler); - taskTree.start(); - QCOMPARE(CustomStorage::instanceCount(), 1); - } - QCOMPARE(CustomStorage::instanceCount(), 0); - QVERIFY(setupCalled); - QVERIFY(!doneCalled); -} - -QTEST_GUILESS_MAIN(tst_TaskTree) - -#include "tst_tasktree.moc" diff --git a/tests/auto/utils/text/CMakeLists.txt b/tests/auto/utils/text/CMakeLists.txt new file mode 100644 index 00000000000..78fd3838f42 --- /dev/null +++ b/tests/auto/utils/text/CMakeLists.txt @@ -0,0 +1,4 @@ +add_qtc_test(tst_utils_text + DEPENDS Utils + SOURCES tst_text.cpp +) diff --git a/tests/auto/utils/text/text.qbs b/tests/auto/utils/text/text.qbs new file mode 100644 index 00000000000..91b2cdb6950 --- /dev/null +++ b/tests/auto/utils/text/text.qbs @@ -0,0 +1,7 @@ +import qbs + +QtcAutotest { + name: "Text autotest" + Depends { name: "Utils" } + files: "tst_text.cpp" +} diff --git a/tests/auto/utils/text/tst_text.cpp b/tests/auto/utils/text/tst_text.cpp new file mode 100644 index 00000000000..3672eb2ce2a --- /dev/null +++ b/tests/auto/utils/text/tst_text.cpp @@ -0,0 +1,211 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include + +#include +#include +#include + +using namespace Utils::Text; + +class tst_Text : public QObject +{ + Q_OBJECT + +private slots: + void testPositionFromFileName_data(); + void testPositionFromFileName(); + + void testPositionFromPositionInDocument_data(); + void testPositionFromPositionInDocument(); + + void testPositionFromCursor_data(); + void testPositionFromCursor(); + + void testRangeLength_data(); + void testRangeLength(); +}; + +void tst_Text::testPositionFromFileName_data() +{ + QTest::addColumn("fileName"); + QTest::addColumn("pos"); + QTest::addColumn("expectedPostfixPos"); + + const QString file("/foo/bar"); + const QString fileWin("C:\\foo\\bar"); + + QTest::newRow("no pos") << file << Position() << -1; + QTest::newRow("no pos win") << fileWin << Position() << -1; + + QTest::newRow("empty:") << file + ":" << Position() << 8; + QTest::newRow("empty: win") << fileWin + ":" << Position() << 10; + + QTest::newRow("empty+") << file + "+" << Position() << 8; + QTest::newRow("empty+ win") << fileWin + "+" << Position() << 10; + + QTest::newRow("empty::") << file + "::" << Position() << 8; + QTest::newRow("empty:: win") << fileWin + "::" << Position() << 10; + + QTest::newRow("empty++") << file + "++" << Position() << 8; + QTest::newRow("empty++ win") << fileWin + "++" << Position() << 10; + + QTest::newRow("line:") << file + ":1" << Position{1, 0} << 8; + QTest::newRow("line: win") << fileWin + ":1" << Position{1, 0} << 10; + + QTest::newRow("line+") << file + "+8" << Position{8, 0} << 8; + QTest::newRow("line+ win") << fileWin + "+8" << Position{8, 0} << 10; + + QTest::newRow("multi digit line:") << file + ":42" << Position{42, 0} << 8; + QTest::newRow("multi digit line: win") << fileWin + ":42" << Position{42, 0} << 10; + + QTest::newRow("multi digit line+") << file + "+1234567890" << Position{1234567890, 0} << 8; + QTest::newRow("multi digit line+ win") + << fileWin + "+1234567890" << Position{1234567890, 0} << 10; + + QTest::newRow("line: empty column:") << file + ":1:" << Position{1, 0} << 8; + QTest::newRow("line: empty column: win") << fileWin + ":1:" << Position{1, 0} << 10; + + QTest::newRow("line+ empty column+") << file + "+8+" << Position{8, 0} << 8; + QTest::newRow("line+ empty column+ win") << fileWin + "+8+" << Position{8, 0} << 10; + + QTest::newRow("line: column:") << file + ":1:2" << Position{1, 1} << 8; + QTest::newRow("line: column: win") << fileWin + ":1:2" << Position{1, 1} << 10; + + QTest::newRow("line+ column+") << file + "+8+3" << Position{8, 2} << 8; + QTest::newRow("line+ column+ win") << fileWin + "+8+3" << Position{8, 2} << 10; + + QTest::newRow("mixed:+") << file + ":1+2" << Position{1, 1} << 8; + QTest::newRow("mixed:+ win") << fileWin + ":1+2" << Position{1, 1} << 10; + + QTest::newRow("mixed+:") << file + "+8:3" << Position{8, 2} << 8; + QTest::newRow("mixed+: win") << fileWin + "+8:3" << Position{8, 2} << 10; + + QTest::newRow("garbage:") << file + ":foo" << Position() << -1; + QTest::newRow("garbage: win") << fileWin + ":bar" << Position() << -1; + + QTest::newRow("garbage+") << file + "+snu" << Position() << -1; + QTest::newRow("garbage+ win") << fileWin + "+snu" << Position() << -1; + + QTest::newRow("msvc(") << file + "(" << Position() << 8; + QTest::newRow("msvc( win") << fileWin + "(" << Position() << 10; + + QTest::newRow("msvc(empty)") << file + "()" << Position() << -1; + QTest::newRow("msvc(empty) win") << fileWin + "()" << Position() << -1; + + QTest::newRow("msvc(line") << file + "(1" << Position{1, 0} << 8; + QTest::newRow("msvc(line win") << fileWin + "(4569871" << Position{4569871, 0} << 10; + + QTest::newRow("msvc(line)") << file + "(1)" << Position{1, 0} << 8; + QTest::newRow("msvc(line) win") << fileWin + "(4569871)" << Position{4569871, 0} << 10; + + QTest::newRow("msvc(garbage)") << file + "(foo)" << Position() << -1; + QTest::newRow("msvc(garbage) win") << fileWin + "(bar)" << Position() << -1; +} + +void tst_Text::testPositionFromFileName() +{ + QFETCH(QString, fileName); + QFETCH(Position, pos); + QFETCH(int, expectedPostfixPos); + + int postfixPos = -1; + QCOMPARE(Position::fromFileName(fileName, postfixPos), pos); + QCOMPARE(postfixPos, expectedPostfixPos); +} + +void tst_Text::testPositionFromPositionInDocument_data() +{ + QTest::addColumn("text"); + QTest::addColumn("documentPosition"); + QTest::addColumn("position"); + + QTest::newRow("0") << QString() << 0 << Position{1, 0}; + QTest::newRow("-1") << QString() << -1 << Position(); + QTest::newRow("mid line") << QString("foo\n") << 1 << Position{1, 1}; + QTest::newRow("second line") << QString("foo\n") << 4 << Position{2, 0}; + QTest::newRow("second mid line") << QString("foo\nbar") << 5 << Position{2, 1}; + QTest::newRow("behind content") << QString("foo\nbar") << 8 << Position(); +} + +void tst_Text::testPositionFromPositionInDocument() +{ + QFETCH(QString, text); + QFETCH(int, documentPosition); + QFETCH(Position, position); + + const QTextDocument doc(text); + QCOMPARE(Position::fromPositionInDocument(&doc, documentPosition), position); +} + +void tst_Text::testPositionFromCursor_data() +{ + QTest::addColumn("text"); + QTest::addColumn("documentPosition"); + QTest::addColumn("position"); + + QTest::newRow("0") << QString() << 0 << Position{1, 0}; + QTest::newRow("-1") << QString() << -1 << Position{}; + QTest::newRow("mid line") << QString("foo\n") << 1 << Position{1, 1}; + QTest::newRow("second line") << QString("foo\n") << 4 << Position{2, 0}; + QTest::newRow("second mid line") << QString("foo\nbar") << 5 << Position{2, 1}; + QTest::newRow("behind content") << QString("foo\nbar") << 8 << Position{1, 0}; +} + +void tst_Text::testPositionFromCursor() +{ + QFETCH(QString, text); + QFETCH(int, documentPosition); + QFETCH(Position, position); + + if (documentPosition < 0) {// test invalid Cursor { + QCOMPARE(Position::fromCursor(QTextCursor()), position); + } else { + QTextDocument doc(text); + QTextCursor cursor(&doc); + cursor.setPosition(documentPosition); + QCOMPARE(Position::fromCursor(cursor), position); + } +} + +void tst_Text::testRangeLength_data() +{ + QTest::addColumn("text"); + QTest::addColumn("range"); + QTest::addColumn("length"); + + QTest::newRow("empty range") << QString() << Range{{1, 0}, {1, 0}} << 0; + + QTest::newRow("range on same line") << QString() << Range{{1, 0}, {1, 1}} << 1; + + QTest::newRow("range spanning lines") << QString("foo\nbar") << Range{{1, 0}, {2, 0}} << 4; + + QTest::newRow("range spanning lines and column offset") + << QString("foo\nbar") << Range{{1, 1}, {2, 1}} << 4; + + QTest::newRow("range spanning lines and begin column offset") + << QString("foo\nbar") << Range{{1, 1}, {2, 0}} << 3; + + QTest::newRow("range spanning lines and end column offset") + << QString("foo\nbar") << Range{{1, 0}, {2, 1}} << 5; + + QTest::newRow("hyper range") << QString("foo\nbar\nfoobar") << Range{{2, 1}, {3, 1}} << 4; + + QTest::newRow("flipped range") << QString() << Range{{2, 0}, {1, 0}} << -1; + + QTest::newRow("out of range") << QString() << Range{{1, 0}, {2, 0}} << -1; +} + +void tst_Text::testRangeLength() +{ + QFETCH(QString, text); + QFETCH(Range, range); + QFETCH(int, length); + + QCOMPARE(range.length(text), length); +} + +QTEST_GUILESS_MAIN(tst_Text) + +#include "tst_text.moc" diff --git a/tests/auto/utils/unixdevicefileaccess/CMakeLists.txt b/tests/auto/utils/unixdevicefileaccess/CMakeLists.txt new file mode 100644 index 00000000000..0cf8d43c712 --- /dev/null +++ b/tests/auto/utils/unixdevicefileaccess/CMakeLists.txt @@ -0,0 +1,4 @@ +add_qtc_test(tst_utils_unixdevicefileaccess + DEPENDS Utils + SOURCES tst_unixdevicefileaccess.cpp +) diff --git a/tests/auto/utils/unixdevicefileaccess/tst_unixdevicefileaccess.cpp b/tests/auto/utils/unixdevicefileaccess/tst_unixdevicefileaccess.cpp new file mode 100644 index 00000000000..b5aef12d06c --- /dev/null +++ b/tests/auto/utils/unixdevicefileaccess/tst_unixdevicefileaccess.cpp @@ -0,0 +1,78 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include +#include +#include +#include + +#include +#include +#include +#include + +//TESTED_COMPONENT=src/libs/utils +using namespace Utils; + +namespace QTest { +template<> +char *toString(const FilePath &filePath) +{ + return qstrdup(filePath.toString().toLocal8Bit().constData()); +} +} // namespace QTest + +class TestDFA : public UnixDeviceFileAccess +{ +public: + using UnixDeviceFileAccess::UnixDeviceFileAccess; + + virtual RunResult runInShell(const CommandLine &cmdLine, + const QByteArray &inputData = {}) const override + { + QProcess p; + p.setProgram(cmdLine.executable().toString()); + p.setArguments(cmdLine.splitArguments()); + p.setProcessChannelMode(QProcess::SeparateChannels); + + p.start(); + p.waitForStarted(); + if (inputData.size() > 0) { + p.write(inputData); + p.closeWriteChannel(); + } + p.waitForFinished(); + return {p.exitCode(), p.readAllStandardOutput(), p.readAllStandardError()}; + } +}; + +class tst_unixdevicefileaccess : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase() + { + if (HostOsInfo::isWindowsHost()) + QSKIP("This test is only for Unix hosts"); + + m_fileSizeTestFile.writeFileContents(QByteArray(1024, 'a')); + } + + void fileSize() + { + const auto size = m_dfaPtr->fileSize(m_fileSizeTestFile); + QCOMPARE(size, 1024); + } + +private: + TestDFA m_dfa; + DeviceFileAccess *m_dfaPtr = &m_dfa; + + QTemporaryDir m_tempDir; + FilePath m_fileSizeTestFile = FilePath::fromString(m_tempDir.filePath("size-test")); +}; + +QTEST_GUILESS_MAIN(tst_unixdevicefileaccess) + +#include "tst_unixdevicefileaccess.moc" diff --git a/tests/auto/utils/unixdevicefileaccess/unixdevicefileaccess.qbs b/tests/auto/utils/unixdevicefileaccess/unixdevicefileaccess.qbs new file mode 100644 index 00000000000..1776ef4a6ee --- /dev/null +++ b/tests/auto/utils/unixdevicefileaccess/unixdevicefileaccess.qbs @@ -0,0 +1,11 @@ +import qbs + +QtcAutotest { + name: "UnixDeviceFileAccess autotest" + Depends { name: "Utils" } + Properties { + condition: qbs.toolchain.contains("gcc") + cpp.cxxFlags: base.concat(["-Wno-trigraphs"]) + } + files: "tst_unixdevicefileaccess.cpp" +} diff --git a/tests/auto/utils/utils.qbs b/tests/auto/utils/utils.qbs index eea36644236..9f5a580435a 100644 --- a/tests/auto/utils/utils.qbs +++ b/tests/auto/utils/utils.qbs @@ -4,10 +4,11 @@ Project { name: "Utils autotests" references: [ "ansiescapecodehandler/ansiescapecodehandler.qbs", - "asynctask/asynctask.qbs", + "async/async.qbs", "commandline/commandline.qbs", "deviceshell/deviceshell.qbs", "expected/expected.qbs", + "filepath/filepath.qbs", "fileutils/fileutils.qbs", "fsengine/fsengine.qbs", "fuzzymatcher/fuzzymatcher.qbs", @@ -15,11 +16,12 @@ Project { "mathutils/mathutils.qbs", "multicursor/multicursor.qbs", "persistentsettings/persistentsettings.qbs", - "qtcprocess/qtcprocess.qbs", + "process/process.qbs", "settings/settings.qbs", "stringutils/stringutils.qbs", - "tasktree/tasktree.qbs", "templateengine/templateengine.qbs", + "text/text.qbs", "treemodel/treemodel.qbs", + "unixdevicefileaccess/unixdevicefileaccess.qbs", ] } diff --git a/tests/manual/CMakeLists.txt b/tests/manual/CMakeLists.txt index c171a70deb6..fde3cb962ea 100644 --- a/tests/manual/CMakeLists.txt +++ b/tests/manual/CMakeLists.txt @@ -16,5 +16,6 @@ add_subdirectory(proparser) # add_subdirectory(qt4projectmanager) # add_subdirectory(search) add_subdirectory(shootout) -add_subdirectory(tasktree) +add_subdirectory(subdirfileiterator) +add_subdirectory(tasking) add_subdirectory(widgets) diff --git a/tests/manual/cmakeprojectmanager/hello-widgets/CMakeLists.txt b/tests/manual/cmakeprojectmanager/hello-widgets/CMakeLists.txt new file mode 100644 index 00000000000..8c0c413d663 --- /dev/null +++ b/tests/manual/cmakeprojectmanager/hello-widgets/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.18) + +project(hello-widgets) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 REQUIRED COMPONENTS Widgets) + +qt_add_executable(hello-widgets + main.cpp + mainwindow.cpp + mainwindow.h + mainwindow.ui +) + +target_link_libraries(hello-widgets PRIVATE Qt6::Widgets) + +include(my_add_executable.cmake) + +my_add_executable(hello-my-widgets + main.cpp + mainwindow.cpp + mainwindow.h + mainwindow.ui +) + +target_link_libraries(hello-my-widgets PRIVATE Qt6::Widgets) + +file(GLOB SOURCE_FILES CONFIGURE_DEPENDS *.cpp *.h *.ui) + +add_executable(hello-widgets-glob ${SOURCE_FILES}) +target_link_libraries(hello-widgets-glob PRIVATE Qt6::Widgets) diff --git a/tests/manual/cmakeprojectmanager/hello-widgets/README.md b/tests/manual/cmakeprojectmanager/hello-widgets/README.md new file mode 100644 index 00000000000..52da33ef8ac --- /dev/null +++ b/tests/manual/cmakeprojectmanager/hello-widgets/README.md @@ -0,0 +1,4 @@ +Qt6 Widgets project to test adding new or existing files to a CMake project. + +The project uses both custom CMake API and normal Qt6 qt_add_executable API +function call. diff --git a/tests/manual/cmakeprojectmanager/hello-widgets/main.cpp b/tests/manual/cmakeprojectmanager/hello-widgets/main.cpp new file mode 100644 index 00000000000..a0ccba3ce92 --- /dev/null +++ b/tests/manual/cmakeprojectmanager/hello-widgets/main.cpp @@ -0,0 +1,15 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "mainwindow.h" + +#include + + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/tests/manual/cmakeprojectmanager/hello-widgets/mainwindow.cpp b/tests/manual/cmakeprojectmanager/hello-widgets/mainwindow.cpp new file mode 100644 index 00000000000..c36872995c3 --- /dev/null +++ b/tests/manual/cmakeprojectmanager/hello-widgets/mainwindow.cpp @@ -0,0 +1,20 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "mainwindow.h" +#include "./ui_mainwindow.h" + + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +{ + ui->setupUi(this); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + + diff --git a/tests/manual/cmakeprojectmanager/hello-widgets/mainwindow.h b/tests/manual/cmakeprojectmanager/hello-widgets/mainwindow.h new file mode 100644 index 00000000000..35d90b0cfd3 --- /dev/null +++ b/tests/manual/cmakeprojectmanager/hello-widgets/mainwindow.h @@ -0,0 +1,28 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + + + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + +class MainWindow : public QMainWindow + +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private: + Ui::MainWindow *ui; +}; + +#endif // MAINWINDOW_H diff --git a/tests/manual/cmakeprojectmanager/hello-widgets/mainwindow.ui b/tests/manual/cmakeprojectmanager/hello-widgets/mainwindow.ui new file mode 100644 index 00000000000..b232854ba81 --- /dev/null +++ b/tests/manual/cmakeprojectmanager/hello-widgets/mainwindow.ui @@ -0,0 +1,22 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + + + + diff --git a/tests/manual/cmakeprojectmanager/hello-widgets/my_add_executable.cmake b/tests/manual/cmakeprojectmanager/hello-widgets/my_add_executable.cmake new file mode 100644 index 00000000000..dca664be47b --- /dev/null +++ b/tests/manual/cmakeprojectmanager/hello-widgets/my_add_executable.cmake @@ -0,0 +1,5 @@ +function(my_add_executable targetName) + add_executable(${targetName} + ${ARGN} + ) +endfunction() diff --git a/tests/manual/cmakeprojectmanager/hello-widgets/myclass.cpp b/tests/manual/cmakeprojectmanager/hello-widgets/myclass.cpp new file mode 100644 index 00000000000..ce14aae2ce5 --- /dev/null +++ b/tests/manual/cmakeprojectmanager/hello-widgets/myclass.cpp @@ -0,0 +1,10 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "myclass.h" + +myclass::myclass() +{ + +} + diff --git a/tests/manual/cmakeprojectmanager/hello-widgets/myclass.h b/tests/manual/cmakeprojectmanager/hello-widgets/myclass.h new file mode 100644 index 00000000000..98e0e47cb1c --- /dev/null +++ b/tests/manual/cmakeprojectmanager/hello-widgets/myclass.h @@ -0,0 +1,16 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef MYCLASS_H +#define MYCLASS_H + + + + +class myclass +{ +public: + myclass(); +}; + +#endif // MYCLASS_H diff --git a/tests/manual/deviceshell/tst_deviceshell.cpp b/tests/manual/deviceshell/tst_deviceshell.cpp index 10c14149758..99c506d1bda 100644 --- a/tests/manual/deviceshell/tst_deviceshell.cpp +++ b/tests/manual/deviceshell/tst_deviceshell.cpp @@ -5,14 +5,12 @@ #include #include -#include #include -#include -#include +#include #include -#include #include +#include #include using namespace Utils; @@ -56,7 +54,7 @@ public: } private: - void setupShellProcess(QtcProcess *shellProcess) override + void setupShellProcess(Process *shellProcess) override { shellProcess->setCommand(cmdLine()); } @@ -86,45 +84,47 @@ class tst_DeviceShell : public QObject return result; } - void test(int maxNumThreads, int numCalls) + void test(int numCalls, int maxNumThreads) { TestShell shell; QCOMPARE(shell.state(), DeviceShell::State::Succeeded); QThreadPool::globalInstance()->setMaxThreadCount(maxNumThreads); - QList testArray = testArrays(numCalls); + const QList testArray = testArrays(numCalls); QElapsedTimer t; t.start(); - const QList result - = mapped(testArray, [&shell](QByteArray data) -> QByteArray { - return shell.runInShell({"cat", {}}, data).stdOut; - }, MapReduceOption::Ordered, QThreadPool::globalInstance()); - - QCOMPARE(result, testArray); + const auto cat = [&shell](const QByteArray &data) { + return shell.runInShell({"cat", {}}, data).stdOut; + }; + const QList results = QtConcurrent::blockingMapped(testArray, cat); + QCOMPARE(results, testArray); qDebug() << "maxThreads:" << maxNumThreads << ", took:" << t.elapsed() / 1000.0 << "seconds"; } - void testSleep(QList testData, int nThreads) + void testSleep(QList testData, int maxNumThreads) { TestShell shell; QCOMPARE(shell.state(), DeviceShell::State::Succeeded); - QThreadPool::globalInstance()->setMaxThreadCount(nThreads); + QThreadPool::globalInstance()->setMaxThreadCount(maxNumThreads); QElapsedTimer t; t.start(); - const auto result = mapped(testData, [&shell](const int &time) { + const auto sleep = [&shell](int time) { shell.runInShell({"sleep", {QString("%1").arg(time)}}); return 0; - }, MapReduceOption::Unordered, QThreadPool::globalInstance()); + }; + const QList results = QtConcurrent::blockingMapped(testData, sleep); + QCOMPARE(results, QList(testData.size(), 0)); - qDebug() << "maxThreads:" << nThreads << ", took:" << t.elapsed() / 1000.0 << "seconds"; + qDebug() << "maxThreads:" << maxNumThreads << ", took:" << t.elapsed() / 1000.0 + << "seconds"; } private slots: @@ -183,7 +183,7 @@ private slots: QFETCH(int, numThreads); QFETCH(int, numIterations); - test(numThreads, numIterations); + test(numIterations, numThreads); } void testSleepMulti() diff --git a/tests/manual/fakevim/main.cpp b/tests/manual/fakevim/main.cpp index c733dd63198..288c244e43c 100644 --- a/tests/manual/fakevim/main.cpp +++ b/tests/manual/fakevim/main.cpp @@ -187,12 +187,12 @@ int main(int argc, char *argv[]) // Create FakeVimHandler instance which will emulate Vim behavior in editor widget. FakeVimHandler handler(editor, nullptr); - handler.commandBufferChanged.connect([&](const QString &msg, int cursorPos, int, int) { + handler.commandBufferChanged.set([&](const QString &msg, int cursorPos, int, int) { statusData.setStatusMessage(msg, cursorPos); mainWindow.statusBar()->showMessage(statusData.currentStatusLine()); }); - handler.selectionChanged.connect([&handler](const QList &s) { + handler.selectionChanged.set([&handler](const QList &s) { QWidget *widget = handler.widget(); if (auto ed = qobject_cast(widget)) ed->setExtraSelections(s); @@ -200,21 +200,20 @@ int main(int argc, char *argv[]) ed->setExtraSelections(s); }); - handler.extraInformationChanged.connect([&](const QString &info) { + handler.extraInformationChanged.set([&](const QString &info) { statusData.setStatusInfo(info); mainWindow.statusBar()->showMessage(statusData.currentStatusLine()); }); - handler.statusDataChanged.connect([&](const QString &info) { + handler.statusDataChanged.set([&](const QString &info) { statusData.setStatusInfo(info); mainWindow.statusBar()->showMessage(statusData.currentStatusLine()); }); - handler.highlightMatches.connect([&](const QString &needle) { - highlightMatches(handler.widget(), needle); - }); + handler.highlightMatches.set( + [&](const QString &needle) { highlightMatches(handler.widget(), needle); }); - handler.handleExCommandRequested.connect([](bool *handled, const ExCommand &cmd) { + handler.handleExCommandRequested.set([](bool *handled, const ExCommand &cmd) { if (cmd.matches("q", "quit") || cmd.matches("qa", "qall")) { QApplication::quit(); *handled = true; diff --git a/tests/manual/layoutbuilder/comparison/CMakeLists.txt b/tests/manual/layoutbuilder/comparison/CMakeLists.txt new file mode 100644 index 00000000000..ac1c62fabdb --- /dev/null +++ b/tests/manual/layoutbuilder/comparison/CMakeLists.txt @@ -0,0 +1,6 @@ + +project(Comparison) + +add_subdirectory(quick) +add_subdirectory(widgets) +add_subdirectory(layoutbuilder) diff --git a/tests/manual/layoutbuilder/comparison/layoutbuilder/CMakeLists.txt b/tests/manual/layoutbuilder/comparison/layoutbuilder/CMakeLists.txt new file mode 100644 index 00000000000..326985aae03 --- /dev/null +++ b/tests/manual/layoutbuilder/comparison/layoutbuilder/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 3.5) + +project(layoutbuilder VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 REQUIRED COMPONENTS Widgets) + +set(PROJECT_SOURCES + main.cpp + mainwindow.cpp + mainwindow.h + ../../../../../src/libs/utils/layoutbuilder.cpp + ../../../../../src/libs/utils/layoutbuilder.h +) + +add_executable(layoutbuilder + ${PROJECT_SOURCES} +) + +target_include_directories(layoutbuilder PRIVATE + ../../../../../src/libs/utils/ +) + +target_link_libraries(layoutbuilder PRIVATE + Qt6::Widgets +) + +set_target_properties(layoutbuilder PROPERTIES + MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE TRUE + WIN32_EXECUTABLE TRUE +) + +install(TARGETS layoutbuilder + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) diff --git a/tests/manual/layoutbuilder/comparison/layoutbuilder/main.cpp b/tests/manual/layoutbuilder/comparison/layoutbuilder/main.cpp new file mode 100644 index 00000000000..6620ba52e21 --- /dev/null +++ b/tests/manual/layoutbuilder/comparison/layoutbuilder/main.cpp @@ -0,0 +1,6 @@ +#include "mainwindow.h" + +int main(int argc, char *argv[]) +{ + return app.exec(argc, argv); +} diff --git a/tests/manual/layoutbuilder/comparison/layoutbuilder/mainwindow.cpp b/tests/manual/layoutbuilder/comparison/layoutbuilder/mainwindow.cpp new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/manual/layoutbuilder/comparison/layoutbuilder/mainwindow.h b/tests/manual/layoutbuilder/comparison/layoutbuilder/mainwindow.h new file mode 100644 index 00000000000..e238a5cfcdb --- /dev/null +++ b/tests/manual/layoutbuilder/comparison/layoutbuilder/mainwindow.h @@ -0,0 +1,24 @@ +#pragma once + +#include "layoutbuilder.h" + +#include + +using namespace Layouting; + +Application app +{ + resize(600, 400), + title("Hello World"), + + Column { + TextEdit { + text("Hallo") + }, + + PushButton { + text("Quit"), + onClicked(QApplication::quit) + } + } +}; diff --git a/tests/manual/layoutbuilder/comparison/quick/CMakeLists.txt b/tests/manual/layoutbuilder/comparison/quick/CMakeLists.txt new file mode 100644 index 00000000000..4166619afd4 --- /dev/null +++ b/tests/manual/layoutbuilder/comparison/quick/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.16) + +project(quick VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 6.4 REQUIRED COMPONENTS Quick) + +qt_standard_project_setup() + +qt_add_executable(appquick + main.cpp +) + +qt_add_qml_module(appquick + URI quick + VERSION 1.0 + QML_FILES Main.qml +) + +set_target_properties(appquick PROPERTIES + MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE TRUE + WIN32_EXECUTABLE TRUE +) + +target_link_libraries(appquick + PRIVATE Qt6::Quick +) + +install(TARGETS appquick + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) diff --git a/tests/manual/layoutbuilder/comparison/quick/Main.qml b/tests/manual/layoutbuilder/comparison/quick/Main.qml new file mode 100644 index 00000000000..617b573caaa --- /dev/null +++ b/tests/manual/layoutbuilder/comparison/quick/Main.qml @@ -0,0 +1,30 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +ApplicationWindow +{ + width: 640 + height: 480 + visible: true + title: "Hello World" + + ColumnLayout { + anchors.fill: parent + + Text { + Layout.fillHeight: true + Layout.fillWidth: true + + text: "Hallo" + } + + Button { + text: "Quit" + height: 20 + Layout.fillWidth: true + + onClicked: { Qt.quit() } + } + } +} diff --git a/tests/manual/layoutbuilder/comparison/quick/main.cpp b/tests/manual/layoutbuilder/comparison/quick/main.cpp new file mode 100644 index 00000000000..76df95e6ea3 --- /dev/null +++ b/tests/manual/layoutbuilder/comparison/quick/main.cpp @@ -0,0 +1,16 @@ +#include +#include + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + const QUrl url(u"qrc:/quick/Main.qml"_qs); + QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, + &app, []() { QCoreApplication::exit(-1); }, + Qt::QueuedConnection); + engine.load(url); + + return app.exec(); +} diff --git a/tests/manual/layoutbuilder/comparison/widgets/CMakeLists.txt b/tests/manual/layoutbuilder/comparison/widgets/CMakeLists.txt new file mode 100644 index 00000000000..8f895f41217 --- /dev/null +++ b/tests/manual/layoutbuilder/comparison/widgets/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.5) + +project(widgets VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 REQUIRED COMPONENTS Widgets) + +set(PROJECT_SOURCES + main.cpp + mainwindow.cpp + mainwindow.h +) + +add_executable(widgets + ${PROJECT_SOURCES} +) + +target_link_libraries(widgets PRIVATE + Qt6::Widgets +) + +set_target_properties(widgets PROPERTIES + MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE TRUE + WIN32_EXECUTABLE TRUE +) + +install(TARGETS widgets + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) diff --git a/tests/manual/layoutbuilder/comparison/widgets/main.cpp b/tests/manual/layoutbuilder/comparison/widgets/main.cpp new file mode 100644 index 00000000000..c610fc6a497 --- /dev/null +++ b/tests/manual/layoutbuilder/comparison/widgets/main.cpp @@ -0,0 +1,9 @@ +#include "mainwindow.h" + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + ApplicationWindow w; + w.show(); + return a.exec(); +} diff --git a/tests/manual/layoutbuilder/comparison/widgets/mainwindow.cpp b/tests/manual/layoutbuilder/comparison/widgets/mainwindow.cpp new file mode 100644 index 00000000000..346e6a1ca0d --- /dev/null +++ b/tests/manual/layoutbuilder/comparison/widgets/mainwindow.cpp @@ -0,0 +1,24 @@ +//#include "mainwindow.h" + +//#include +//#include +//#include +//#include + +//ApplicationWindow::ApplicationWindow() +//{ +// resize(600, 400); +// setWindowTitle("Hello World"); + +// auto textEdit = new QTextEdit; +// textEdit->setText("Hallo"); + +// auto pushButton = new QPushButton("Quit"); + +// auto l = new QVBoxLayout(this); +// l->addWidget(textEdit); +// l->addWidget(pushButton); + +// connect(pushButton, &QPushButton::clicked, +// qApp, &QCoreApplication::quit); +//} diff --git a/tests/manual/layoutbuilder/comparison/widgets/mainwindow.h b/tests/manual/layoutbuilder/comparison/widgets/mainwindow.h new file mode 100644 index 00000000000..1dc8ad6f838 --- /dev/null +++ b/tests/manual/layoutbuilder/comparison/widgets/mainwindow.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include + +class ApplicationWindow : public QWidget +{ +public: + ApplicationWindow() + { + resize(600, 400); + setWindowTitle("Hello World"); + + auto textEdit = new QTextEdit; + textEdit->setText("Hallo"); + + auto pushButton = new QPushButton("Quit"); + + auto l = new QVBoxLayout(this); + l->addWidget(textEdit); + l->addWidget(pushButton); + + connect(pushButton, &QPushButton::clicked, + qApp, &QApplication::quit); + } +}; diff --git a/tests/manual/layoutbuilder/demo/CMakeLists.txt b/tests/manual/layoutbuilder/demo/CMakeLists.txt new file mode 100644 index 00000000000..3a93056bea8 --- /dev/null +++ b/tests/manual/layoutbuilder/demo/CMakeLists.txt @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 3.5) + +project(layoutbuilderdemo VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 REQUIRED COMPONENTS Widgets) + +set(PROJECT_SOURCES + main.cpp + ../../../../src/libs/utils/layoutbuilder.h + ../../../../src/libs/utils/layoutbuilder.cpp +) + +add_executable(layoutbuilderdemo + ${PROJECT_SOURCES} +) + +target_include_directories(layoutbuilderdemo PRIVATE + ../../../../src/libs/utils/ +) + +target_link_libraries(layoutbuilderdemo PRIVATE + Qt6::Widgets +) + +set_target_properties(layoutbuilderdemo PROPERTIES + MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE TRUE + WIN32_EXECUTABLE TRUE +) + +install(TARGETS layoutbuilderdemo + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) diff --git a/tests/manual/layoutbuilder/demo/main.cpp b/tests/manual/layoutbuilder/demo/main.cpp new file mode 100644 index 00000000000..2dd95dfddbf --- /dev/null +++ b/tests/manual/layoutbuilder/demo/main.cpp @@ -0,0 +1,41 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "layoutbuilder.h" + +#include + +using namespace Layouting; + +int main(int argc, char *argv[]) +{ + ID textId; + + Application app + { + resize(600, 400), + title("Hello World"), + + Column { + TextEdit { + id(textId), + text("Hallo") + }, + + Row { + SpinBox { + onTextChanged([&](const QString &text) { setText(textId, text); }) + }, + + Stretch(), + + PushButton { + text("Quit"), + onClicked(QApplication::quit) + }, + } + } + }; + + return app.exec(argc, argv); +} diff --git a/tests/manual/layoutbuilder/demo/mainwindow.cpp b/tests/manual/layoutbuilder/demo/mainwindow.cpp new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/manual/layoutbuilder/demo/mainwindow.h b/tests/manual/layoutbuilder/demo/mainwindow.h new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/manual/manual.qbs b/tests/manual/manual.qbs index e3ebf0707a3..66c480c7a62 100644 --- a/tests/manual/manual.qbs +++ b/tests/manual/manual.qbs @@ -13,7 +13,8 @@ Project { "pluginview/pluginview.qbs", "proparser/testreader.qbs", "shootout/shootout.qbs", - "tasktree/tasktree.qbs", + "subdirfileiterator/subdirfileiterator.qbs", + "tasking/demo/demo.qbs", "widgets/widgets.qbs", ] } diff --git a/tests/auto/utils/tasktree/CMakeLists.txt b/tests/manual/subdirfileiterator/CMakeLists.txt similarity index 65% rename from tests/auto/utils/tasktree/CMakeLists.txt rename to tests/manual/subdirfileiterator/CMakeLists.txt index 87c31f18866..9613e197b77 100644 --- a/tests/auto/utils/tasktree/CMakeLists.txt +++ b/tests/manual/subdirfileiterator/CMakeLists.txt @@ -1,11 +1,10 @@ -add_subdirectory(testapp) - file(RELATIVE_PATH RELATIVE_TEST_PATH "${PROJECT_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}") file(RELATIVE_PATH TEST_RELATIVE_LIBEXEC_PATH "/${RELATIVE_TEST_PATH}" "/${IDE_LIBEXEC_PATH}") -add_qtc_test(tst_utils_tasktree +add_qtc_test(tst_manual_subdirfileiterator + MANUALTEST DEFINES "TEST_RELATIVE_LIBEXEC_PATH=\"${TEST_RELATIVE_LIBEXEC_PATH}\"" - "TESTAPP_PATH=\"${CMAKE_CURRENT_BINARY_DIR}/testapp\"" DEPENDS Utils app_version - SOURCES tst_tasktree.cpp + SOURCES + tst_subdirfileiterator.cpp ) diff --git a/tests/manual/subdirfileiterator/subdirfileiterator.qbs b/tests/manual/subdirfileiterator/subdirfileiterator.qbs new file mode 100644 index 00000000000..852fb9b545b --- /dev/null +++ b/tests/manual/subdirfileiterator/subdirfileiterator.qbs @@ -0,0 +1,22 @@ +import qbs.FileInfo + +QtcManualtest { + name: "Manual SubDirFileIterator test" + type: ["application"] + + Depends { name: "Utils" } + Depends { name: "app_version_header" } + + files: [ + "tst_subdirfileiterator.cpp", + ] + + cpp.defines: { + var defines = base; + var absLibExecPath = FileInfo.joinPaths(qbs.installRoot, qbs.installPrefix, + qtc.ide_libexec_path); + var relLibExecPath = FileInfo.relativePath(destinationDirectory, absLibExecPath); + defines.push('TEST_RELATIVE_LIBEXEC_PATH="' + relLibExecPath + '"'); + return defines; + } +} diff --git a/tests/manual/subdirfileiterator/tst_subdirfileiterator.cpp b/tests/manual/subdirfileiterator/tst_subdirfileiterator.cpp new file mode 100644 index 00000000000..882a9993a26 --- /dev/null +++ b/tests/manual/subdirfileiterator/tst_subdirfileiterator.cpp @@ -0,0 +1,260 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +using namespace Tasking; +using namespace Utils; + +static const int s_subDirsCount = 8; +static const int s_subFilesCount = 8; +static const int s_treeDepth = 4; + +static const QDir::Filters s_filters = QDir::Dirs | QDir::Files | QDir::Hidden + | QDir::NoDotAndDotDot; + +static const char s_dirPrefix[] = "dir_"; +static const char s_filePrefix[] = "file_"; + +static int expectedDirsCountHelper(int depth) +{ + if (depth == 1) + return s_subDirsCount + 1; // +1 -> dir itself + return expectedDirsCountHelper(depth - 1) * s_subDirsCount + 1; // +1 -> dir itself +} + +static int expectedDirsCount(int tasksCount) +{ + return expectedDirsCountHelper(s_treeDepth) * tasksCount + 1; // +1 -> root temp dir +} + +static int expectedFilesCountHelper(int depth) +{ + if (depth == 0) + return s_subFilesCount; + return expectedFilesCountHelper(depth - 1) * s_subDirsCount + s_subFilesCount; +} + +static int expectedFilesCount(int tasksCount) +{ + return expectedFilesCountHelper(s_treeDepth) * tasksCount + 1; // +1 -> fileTemplate.txt +} + +static void generate(QPromise &promise, const QString &parentPath, + const QString &templateFile, int depth) +{ + const QDir parentDir(parentPath); + for (int i = 0; i < s_subFilesCount; ++i) + QFile::copy(templateFile, parentDir.filePath(QString("%1%2").arg(s_filePrefix).arg(i))); + + if (depth == 0) + return; + + if (promise.isCanceled()) + return; + + const QString templateDir("dirTemplate"); + if (!parentDir.mkdir(templateDir)) { + promise.future().cancel(); + return; + } + + const QString templateDirPath = parentDir.filePath(templateDir); + generate(promise, templateDirPath, templateFile, depth - 1); + + if (promise.isCanceled()) + return; + + for (int i = 0; i < s_subDirsCount - 1; ++i) { + const QString dirName = QString("%1%2").arg(s_dirPrefix).arg(i); + const FilePath source = FilePath::fromString(templateDirPath); + const FilePath destination = FilePath::fromString(parentDir.filePath(dirName)); + if (!source.copyRecursively(destination)) { + promise.future().cancel(); + return; + } + } +} + +class tst_SubDirFileIterator : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase() + { + TemporaryDirectory::setMasterTemporaryDirectory( + QDir::tempPath() + "/" + Core::Constants::IDE_CASED_ID + "-XXXXXX"); + + const QString libExecPath(qApp->applicationDirPath() + '/' + + QLatin1String(TEST_RELATIVE_LIBEXEC_PATH)); + LauncherInterface::setPathToLauncher(libExecPath); + + qDebug() << "This manual test compares the performance of the SubDirFileIterator with " + "a manually written iterator using QDir::entryInfoList()."; + QTC_SCOPED_TIMER("GENERATING TEMPORARY FILES TREE"); + const int tasksCount = QThread::idealThreadCount(); + m_filesCount = expectedFilesCount(tasksCount); + m_dirsCount = expectedDirsCount(tasksCount); + m_tempDir.reset(new QTemporaryDir); + qDebug() << "Generating on" << tasksCount << "cores..."; + qDebug() << "Generating" << m_filesCount << "files..."; + qDebug() << "Generating" << m_dirsCount << "dirs..."; + qDebug() << "Generating inside" << m_tempDir->path() << "dir..."; + const QString templateFile = m_tempDir->filePath("fileTemplate.txt"); + { + QFile file(templateFile); + QVERIFY(file.open(QIODevice::ReadWrite)); + file.write("X"); + } + + // Parallelize tree generation + const QDir parentDir(m_tempDir->path()); + const auto onSetup = [](const QString &parentPath, const QString &templateFile) { + return [parentPath, templateFile](Async &async) { + async.setConcurrentCallData(generate, parentPath, templateFile, s_treeDepth); + }; + }; + QList tasks {parallel}; + for (int i = 0; i < tasksCount; ++i) { + const QString dirName = QString("%1%2").arg(s_dirPrefix).arg(i); + QVERIFY(parentDir.mkdir(dirName)); + tasks.append(AsyncTask(onSetup(parentDir.filePath(dirName), templateFile))); + } + + QVERIFY(TaskTree::runBlocking(tasks)); + } + + void cleanupTestCase() + { + QTC_SCOPED_TIMER("CLEANING UP"); + + // Parallelize tree removal + const auto removeTree = [](const QString &parentPath) { + FilePath::fromString(parentPath).removeRecursively(); + }; + + const QDir parentDir(m_tempDir->path()); + const auto onSetup = [removeTree](const QString &parentPath) { + return [parentPath, removeTree](Async &async) { + async.setConcurrentCallData(removeTree, parentPath); + }; + }; + QList tasks {parallel}; + const int tasksCount = QThread::idealThreadCount(); + for (int i = 0; i < tasksCount; ++i) { + const QString dirName = QString("%1%2").arg(s_dirPrefix).arg(i); + tasks.append(AsyncTask(onSetup(parentDir.filePath(dirName)))); + } + + QVERIFY(TaskTree::runBlocking(tasks)); + + m_tempDir.reset(); + Singleton::deleteAll(); + } + + void testSubDirFileIterator() + { + QTC_SCOPED_TIMER("ITERATING with SubDirFileIterator"); + int filesCount = 0; + { + const FilePath root(FilePath::fromString(m_tempDir->path())); + SubDirFileIterator it({root}, {}, {}); + auto i = it.begin(); + const auto e = it.end(); + while (i != e) { + ++filesCount; + ++i; + if (filesCount % 100000 == 0) + qDebug() << filesCount << '/' << m_filesCount << "files visited so far..."; + } + } + qDebug() << "Visited" << filesCount << "files."; + } + + void testManualIterator() + { + QTC_SCOPED_TIMER("ITERATING with manual iterator"); + int filesCount = 0; + int dirsCount = 0; + { + const QDir root(m_tempDir->path()); + std::unordered_set visitedFiles; + std::unordered_set visitedDirs; + visitedDirs.emplace(root.absolutePath()); + QFileInfoList workingList = root.entryInfoList(s_filters); + ++dirsCount; // for root itself + while (!workingList.isEmpty()) { + const QFileInfo fi = workingList.takeLast(); + const QString absoluteFilePath = fi.absoluteFilePath(); + if (fi.isDir()) { + if (!visitedDirs.emplace(absoluteFilePath).second) + continue; + if (fi.isSymbolicLink() && !visitedDirs.emplace(fi.symLinkTarget()).second) + continue; + ++dirsCount; + workingList.append(QDir(absoluteFilePath).entryInfoList(s_filters)); + } else { + if (!visitedFiles.emplace(absoluteFilePath).second) + continue; + if (fi.isSymbolicLink() && !visitedFiles.emplace(fi.symLinkTarget()).second) + continue; + ++filesCount; + if (filesCount % 100000 == 0) + qDebug() << filesCount << '/' << m_filesCount << "files visited so far..."; + } + } + } + qDebug() << "Visited" << filesCount << "files and" << dirsCount << "directories."; + } + + void testQDirIterator() + { + QTC_SCOPED_TIMER("ITERATING with QDirIterator"); + int filesCount = 0; + int dirsCount = 0; + { + ++dirsCount; // for root itself + QDirIterator it(m_tempDir->path(), s_filters, QDirIterator::Subdirectories + | QDirIterator::FollowSymlinks); + while (it.hasNext()) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 3, 0) + const QFileInfo fi = it.nextFileInfo(); +#else + it.next(); + const QFileInfo fi = it.fileInfo(); +#endif + if (fi.isDir()) { + ++dirsCount; + } else { + ++filesCount; + if (filesCount % 100000 == 0) + qDebug() << filesCount << '/' << m_filesCount << "files visited so far..."; + } + } + } + qDebug() << "Visited" << filesCount << "files and" << dirsCount << "directories."; + } + +private: + int m_dirsCount = 0; + int m_filesCount = 0; + std::unique_ptr m_tempDir; +}; + +QTEST_GUILESS_MAIN(tst_SubDirFileIterator) + +#include "tst_subdirfileiterator.moc" diff --git a/tests/manual/tasking/CMakeLists.txt b/tests/manual/tasking/CMakeLists.txt new file mode 100644 index 00000000000..a16f5f12201 --- /dev/null +++ b/tests/manual/tasking/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(demo) diff --git a/tests/manual/tasking/demo/CMakeLists.txt b/tests/manual/tasking/demo/CMakeLists.txt new file mode 100644 index 00000000000..bdd790cd898 --- /dev/null +++ b/tests/manual/tasking/demo/CMakeLists.txt @@ -0,0 +1,11 @@ +add_qtc_test(tst_tasking_demo + MANUALTEST + DEPENDS Tasking Qt::Widgets + SOURCES + demo.qrc + main.cpp + progressindicator.h + progressindicator.cpp + taskwidget.h + taskwidget.cpp +) diff --git a/tests/manual/tasking/demo/demo.qbs b/tests/manual/tasking/demo/demo.qbs new file mode 100644 index 00000000000..2a54143c415 --- /dev/null +++ b/tests/manual/tasking/demo/demo.qbs @@ -0,0 +1,18 @@ +import qbs.FileInfo + +QtcManualtest { + name: "Tasking demo" + type: ["application"] + + Depends { name: "Qt"; submodules: ["widgets"] } + Depends { name: "Tasking" } + + files: [ + "demo.qrc", + "main.cpp", + "progressindicator.h", + "progressindicator.cpp", + "taskwidget.h", + "taskwidget.cpp", + ] +} diff --git a/tests/manual/tasking/demo/demo.qrc b/tests/manual/tasking/demo/demo.qrc new file mode 100644 index 00000000000..5ea09fe25ec --- /dev/null +++ b/tests/manual/tasking/demo/demo.qrc @@ -0,0 +1,6 @@ + + + icons/progressindicator.png + icons/progressindicator@2x.png + + diff --git a/tests/manual/tasking/demo/icons/progressindicator.png b/tests/manual/tasking/demo/icons/progressindicator.png new file mode 100644 index 00000000000..c968ae8ac9f Binary files /dev/null and b/tests/manual/tasking/demo/icons/progressindicator.png differ diff --git a/tests/manual/tasking/demo/icons/progressindicator@2x.png b/tests/manual/tasking/demo/icons/progressindicator@2x.png new file mode 100644 index 00000000000..36821a92e19 Binary files /dev/null and b/tests/manual/tasking/demo/icons/progressindicator@2x.png differ diff --git a/tests/manual/tasking/demo/main.cpp b/tests/manual/tasking/demo/main.cpp new file mode 100644 index 00000000000..c4b885efe2f --- /dev/null +++ b/tests/manual/tasking/demo/main.cpp @@ -0,0 +1,285 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "taskwidget.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace Tasking; + +using namespace std::chrono; + +static QWidget *hr() +{ + auto frame = new QFrame; + frame->setFrameShape(QFrame::HLine); + frame->setFrameShadow(QFrame::Sunken); + return frame; +} + +QWidget *taskGroup(QWidget *groupWidget, const QList &widgets) +{ + QWidget *widget = new QWidget; + QBoxLayout *layout = new QHBoxLayout(widget); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(groupWidget); + QGroupBox *groupBox = new QGroupBox; + QBoxLayout *subLayout = new QVBoxLayout(groupBox); + for (int i = 0; i < widgets.size(); ++i) { + if (i > 0) + subLayout->addWidget(hr()); + subLayout->addWidget(widgets.at(i)); + } + layout->addWidget(groupBox); + return widget; +} + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + QWidget mainWidget; + mainWidget.setWindowTitle("Task Tree Demo"); + + // Non-task GUI + + QToolButton *startButton = new QToolButton; + startButton->setText("Start"); + QToolButton *stopButton = new QToolButton; + stopButton->setText("Stop"); + QToolButton *resetButton = new QToolButton; + resetButton->setText("Reset"); + QProgressBar *progressBar = new QProgressBar; + QScrollArea *scrollArea = new QScrollArea; + scrollArea->setWidgetResizable(true); + QWidget *scrollAreaWidget = new QWidget; + + // Task GUI + + QList allStateWidgets; + + auto createGroupWidget = [&allStateWidgets] { + auto *widget = new GroupWidget(); + allStateWidgets.append(widget); + return widget; + }; + auto createTaskWidget = [&allStateWidgets] { + auto *widget = new TaskWidget(); + allStateWidgets.append(widget); + return widget; + }; + + GroupWidget *rootGroup = createGroupWidget(); + + GroupWidget *groupTask_1 = createGroupWidget(); + TaskWidget *task_1_1 = createTaskWidget(); + TaskWidget *task_1_2 = createTaskWidget(); + TaskWidget *task_1_3 = createTaskWidget(); + + TaskWidget *task_2 = createTaskWidget(); + TaskWidget *task_3 = createTaskWidget(); + + GroupWidget *groupTask_4 = createGroupWidget(); + TaskWidget *task_4_1 = createTaskWidget(); + TaskWidget *task_4_2 = createTaskWidget(); + GroupWidget *groupTask_4_3 = createGroupWidget(); + TaskWidget *task_4_3_1 = createTaskWidget(); + TaskWidget *task_4_3_2 = createTaskWidget(); + TaskWidget *task_4_3_3 = createTaskWidget(); + TaskWidget *task_4_3_4 = createTaskWidget(); + TaskWidget *task_4_4 = createTaskWidget(); + TaskWidget *task_4_5 = createTaskWidget(); + + TaskWidget *task_5 = createTaskWidget(); + + // Task initial configuration + + task_1_2->setBusyTime(2); + task_1_2->setSuccess(false); + task_1_3->setBusyTime(3); + task_4_3_1->setBusyTime(4); + task_4_3_2->setBusyTime(2); + task_4_3_3->setBusyTime(1); + task_4_3_4->setBusyTime(3); + task_4_3_4->setSuccess(false); + task_4_4->setBusyTime(6); + task_4_4->setBusyTime(3); + + groupTask_1->setWorkflowPolicy(WorkflowPolicy::ContinueOnDone); + groupTask_4->setWorkflowPolicy(WorkflowPolicy::FinishAllAndDone); + groupTask_4_3->setExecuteMode(ExecuteMode::Parallel); + groupTask_4_3->setWorkflowPolicy(WorkflowPolicy::StopOnError); + + // Task layout + + { + QWidget *taskTree = taskGroup(rootGroup, { + taskGroup(groupTask_1, { + task_1_1, + task_1_2, + task_1_3 + }), + task_2, + task_3, + taskGroup(groupTask_4, { + task_4_1, + task_4_2, + taskGroup(groupTask_4_3, { + task_4_3_1, + task_4_3_2, + task_4_3_3, + task_4_3_4, + }), + task_4_4, + task_4_5 + }), + task_5 + }); + QBoxLayout *scrollLayout = new QVBoxLayout(scrollAreaWidget); + scrollLayout->addWidget(taskTree); + scrollLayout->addStretch(); + scrollArea->setWidget(scrollAreaWidget); + + QBoxLayout *mainLayout = new QVBoxLayout(&mainWidget); + QBoxLayout *subLayout = new QHBoxLayout; + subLayout->addWidget(startButton); + subLayout->addWidget(stopButton); + subLayout->addWidget(resetButton); + subLayout->addWidget(progressBar); + mainLayout->addLayout(subLayout); + mainLayout->addWidget(hr()); + mainLayout->addWidget(scrollArea); + } + + // Task tree (takes initial configuation from GUI) + + std::unique_ptr taskTree; + + const auto createTask = [](TaskWidget *widget) -> TaskItem { + const auto setupTask = [](TaskWidget *widget) { + return [widget](milliseconds &taskObject) { + taskObject = milliseconds{widget->busyTime() * 1000}; + widget->setState(State::Running); + }; + }; + if (widget->isSuccess()) { + return TimeoutTask(setupTask(widget), + [widget](const milliseconds &) { widget->setState(State::Done); }, + [widget](const milliseconds &) { widget->setState(State::Error); }); + } + const Group root { + finishAllAndError, + TimeoutTask(setupTask(widget)), + onGroupDone([widget] { widget->setState(State::Done); }), + onGroupError([widget] { widget->setState(State::Error); }) + }; + return root; + }; + + auto treeRoot = [&] { + + const Group root { + rootGroup->executeMode(), + rootGroup->workflowPolicy(), + onGroupSetup([rootGroup] { rootGroup->setState(State::Running); }), + onGroupDone([rootGroup] { rootGroup->setState(State::Done); }), + onGroupError([rootGroup] { rootGroup->setState(State::Error); }), + + Group { + groupTask_1->executeMode(), + groupTask_1->workflowPolicy(), + onGroupSetup([groupTask_1] { groupTask_1->setState(State::Running); }), + onGroupDone([groupTask_1] { groupTask_1->setState(State::Done); }), + onGroupError([groupTask_1] { groupTask_1->setState(State::Error); }), + + createTask(task_1_1), + createTask(task_1_2), + createTask(task_1_3) + }, + createTask(task_2), + createTask(task_3), + Group { + groupTask_4->executeMode(), + groupTask_4->workflowPolicy(), + onGroupSetup([groupTask_4] { groupTask_4->setState(State::Running); }), + onGroupDone([groupTask_4] { groupTask_4->setState(State::Done); }), + onGroupError([groupTask_4] { groupTask_4->setState(State::Error); }), + + createTask(task_4_1), + createTask(task_4_2), + Group { + groupTask_4_3->executeMode(), + groupTask_4_3->workflowPolicy(), + onGroupSetup([groupTask_4_3] { groupTask_4_3->setState(State::Running); }), + onGroupDone([groupTask_4_3] { groupTask_4_3->setState(State::Done); }), + onGroupError([groupTask_4_3] { groupTask_4_3->setState(State::Error); }), + + createTask(task_4_3_1), + createTask(task_4_3_2), + createTask(task_4_3_3), + createTask(task_4_3_4) + }, + createTask(task_4_4), + createTask(task_4_5) + }, + createTask(task_5) + }; + return root; + }; + + // Non-task GUI handling + + auto createTaskTree = [&] { + TaskTree *taskTree = new TaskTree(treeRoot()); + progressBar->setMaximum(taskTree->progressMaximum()); + QObject::connect(taskTree, &TaskTree::progressValueChanged, + progressBar, &QProgressBar::setValue); + return taskTree; + }; + + auto stopTaskTree = [&] { + if (taskTree) + taskTree->stop(); + // TODO: unlock GUI controls? + }; + + auto resetTaskTree = [&] { + if (!taskTree) + return; + + stopTaskTree(); + taskTree.reset(); + for (StateWidget *widget : allStateWidgets) + widget->setState(State::Initial); + progressBar->setValue(0); + }; + + auto startTaskTree = [&] { + resetTaskTree(); + taskTree.reset(createTaskTree()); + taskTree->start(); + // TODO: lock GUI controls? + }; + + QObject::connect(startButton, &QAbstractButton::clicked, startTaskTree); + QObject::connect(stopButton, &QAbstractButton::clicked, stopTaskTree); + QObject::connect(resetButton, &QAbstractButton::clicked, resetTaskTree); + + // Hack in order to show initial size minimal, but without scrollbars. + // Apparently setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow) doesn't work. + const int margin = 4; + scrollArea->setMinimumSize(scrollAreaWidget->minimumSizeHint().grownBy({0, 0, margin, margin})); + QTimer::singleShot(0, scrollArea, [&] { scrollArea->setMinimumSize({0, 0}); }); + + mainWidget.show(); + + return app.exec(); +} diff --git a/tests/manual/tasking/demo/progressindicator.cpp b/tests/manual/tasking/demo/progressindicator.cpp new file mode 100644 index 00000000000..38f577f4ed7 --- /dev/null +++ b/tests/manual/tasking/demo/progressindicator.cpp @@ -0,0 +1,149 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "progressindicator.h" + +#include +#include +#include +#include + +class OverlayWidget : public QWidget +{ +public: + using PaintFunction = std::function; + + explicit OverlayWidget(QWidget *parent = nullptr) + { + setAttribute(Qt::WA_TransparentForMouseEvents); + if (parent) + attachToWidget(parent); + } + + void attachToWidget(QWidget *parent) + { + if (parentWidget()) + parentWidget()->removeEventFilter(this); + setParent(parent); + if (parent) { + parent->installEventFilter(this); + resizeToParent(); + raise(); + } + } + void setPaintFunction(const PaintFunction &paint) { m_paint = paint; } + +protected: + bool eventFilter(QObject *obj, QEvent *ev) override + { + if (obj == parent() && ev->type() == QEvent::Resize) + resizeToParent(); + return QWidget::eventFilter(obj, ev); + } + void paintEvent(QPaintEvent *ev) override + { + if (m_paint) { + QPainter p(this); + m_paint(this, p, ev); + } + } + +private: + void resizeToParent() { setGeometry(QRect(QPoint(0, 0), parentWidget()->size())); } + + PaintFunction m_paint; +}; + +class ProgressIndicatorPainter +{ +public: + using UpdateCallback = std::function; + + ProgressIndicatorPainter(); + virtual ~ProgressIndicatorPainter() = default; + + void setUpdateCallback(const UpdateCallback &cb) { m_callback = cb; } + + QSize size() const { return m_pixmap.size() / m_pixmap.devicePixelRatio(); } + + void paint(QPainter &painter, const QRect &rect) const; + void startAnimation() { m_timer.start(); } + void stopAnimation() { m_timer.stop(); } + +protected: + void nextAnimationStep() { m_rotation = (m_rotation + m_rotationStep + 360) % 360; } + +private: + const int m_rotationStep = 45; + int m_rotation = 0; + QTimer m_timer; + QPixmap m_pixmap; + UpdateCallback m_callback; +}; + +ProgressIndicatorPainter::ProgressIndicatorPainter() +{ + m_timer.setSingleShot(false); + QObject::connect(&m_timer, &QTimer::timeout, &m_timer, [this] { + nextAnimationStep(); + if (m_callback) + m_callback(); + }); + + m_timer.setInterval(100); + m_pixmap = QPixmap(":/icons/progressindicator.png"); +} + +void ProgressIndicatorPainter::paint(QPainter &painter, const QRect &rect) const +{ + painter.save(); + painter.setRenderHint(QPainter::SmoothPixmapTransform); + QPoint translate(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2); + QTransform t; + t.translate(translate.x(), translate.y()); + t.rotate(m_rotation); + t.translate(-translate.x(), -translate.y()); + painter.setTransform(t); + QSize pixmapUserSize(m_pixmap.size() / m_pixmap.devicePixelRatio()); + painter.drawPixmap(QPoint(rect.x() + ((rect.width() - pixmapUserSize.width()) / 2), + rect.y() + ((rect.height() - pixmapUserSize.height()) / 2)), + m_pixmap); + painter.restore(); +} + +class ProgressIndicatorWidget : public OverlayWidget +{ +public: + explicit ProgressIndicatorWidget(QWidget *parent = nullptr) + : OverlayWidget(parent) + { + setPaintFunction( + [this](QWidget *w, QPainter &p, QPaintEvent *) { m_paint.paint(p, w->rect()); }); + m_paint.setUpdateCallback([this] { update(); }); + updateGeometry(); + } + + QSize sizeHint() const final { return m_paint.size(); } + +protected: + void showEvent(QShowEvent *) final { m_paint.startAnimation(); } + void hideEvent(QHideEvent *) final { m_paint.stopAnimation(); } + +private: + ProgressIndicatorPainter m_paint; +}; + +ProgressIndicator::ProgressIndicator(QWidget *parent) + : QObject(parent) + , m_widget(new ProgressIndicatorWidget(parent)) {} + + +void ProgressIndicator::show() +{ + m_widget->show(); +} + +void ProgressIndicator::hide() +{ + m_widget->hide(); +} diff --git a/tests/manual/tasking/demo/progressindicator.h b/tests/manual/tasking/demo/progressindicator.h new file mode 100644 index 00000000000..406ebc9e831 --- /dev/null +++ b/tests/manual/tasking/demo/progressindicator.h @@ -0,0 +1,21 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PROGRESSINDICATOR_H +#define PROGRESSINDICATOR_H + +#include + +class ProgressIndicator : public QObject +{ +public: + ProgressIndicator(QWidget *parent = nullptr); + + void show(); + void hide(); + +private: + class ProgressIndicatorWidget *m_widget = nullptr; +}; + +#endif // PROGRESSINDICATOR_H diff --git a/tests/manual/tasktree/taskwidget.cpp b/tests/manual/tasking/demo/taskwidget.cpp similarity index 59% rename from tests/manual/tasktree/taskwidget.cpp rename to tests/manual/tasking/demo/taskwidget.cpp index cca5394bbba..6bc68db1a1b 100644 --- a/tests/manual/tasktree/taskwidget.cpp +++ b/tests/manual/tasking/demo/taskwidget.cpp @@ -1,20 +1,18 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +#include "progressindicator.h" #include "taskwidget.h" -#include -#include -#include - +#include #include #include #include #include +#include #include -using namespace Utils; -using namespace Layouting; +using namespace Tasking; static QString colorButtonStyleSheet(const QColor &bgColor) { @@ -37,10 +35,10 @@ static QColor stateToColor(State state) { class StateIndicator : public QLabel { public: - StateIndicator() + StateIndicator(QWidget *parent = nullptr) + : QLabel(parent) { - m_progressIndicator = new ProgressIndicator(ProgressIndicatorSize::Small); - m_progressIndicator->attachToWidget(this); + m_progressIndicator = new ProgressIndicator(this); m_progressIndicator->hide(); updateState(); } @@ -67,9 +65,7 @@ private: }; StateWidget::StateWidget() - : m_stateIndicator(new StateIndicator) -{ -} + : m_stateIndicator(new StateIndicator(this)) {} void StateWidget::setState(State state) { @@ -86,13 +82,13 @@ TaskWidget::TaskWidget() setBusyTime(1); setSuccess(true); - Row { - m_stateIndicator, - m_infoLabel, - m_spinBox, - m_checkBox, - st - }.attachTo(this, WithoutMargins); + QBoxLayout *layout = new QHBoxLayout(this); + layout->addWidget(m_stateIndicator); + layout->addWidget(m_infoLabel); + layout->addWidget(m_spinBox); + layout->addWidget(m_checkBox); + layout->addStretch(); + layout->setContentsMargins(0, 0, 0, 0); } void TaskWidget::setBusyTime(int seconds) @@ -121,33 +117,32 @@ GroupWidget::GroupWidget() { m_stateIndicator->setFixedWidth(30); - m_executeCombo->addItem("Sequential", (int)ExecuteMode::Sequential); - m_executeCombo->addItem("Parallel", (int)ExecuteMode::Parallel); + m_executeCombo->addItem("Sequential", int(ExecuteMode::Sequential)); + m_executeCombo->addItem("Parallel", int(ExecuteMode::Parallel)); updateExecuteMode(); connect(m_executeCombo, &QComboBox::currentIndexChanged, this, [this](int index) { m_executeMode = (ExecuteMode)m_executeCombo->itemData(index).toInt(); }); - m_workflowCombo->addItem("Stop On Error", (int)Tasking::WorkflowPolicy::StopOnError); - m_workflowCombo->addItem("Cont On Error", (int)Tasking::WorkflowPolicy::ContinueOnError); - m_workflowCombo->addItem("Stop On Done", (int)Tasking::WorkflowPolicy::StopOnDone); - m_workflowCombo->addItem("Cont On Done", (int)Tasking::WorkflowPolicy::ContinueOnDone); - m_workflowCombo->addItem("Optional", (int)Tasking::WorkflowPolicy::Optional); + const QMetaEnum workflow = QMetaEnum::fromType(); + for (int i = 0; i < workflow.keyCount(); ++i) + m_workflowCombo->addItem(workflow.key(i), workflow.value(i)); + updateWorkflowPolicy(); connect(m_workflowCombo, &QComboBox::currentIndexChanged, this, [this](int index) { - m_workflowPolicy = (Tasking::WorkflowPolicy)m_workflowCombo->itemData(index).toInt(); + m_workflowPolicy = (WorkflowPolicy)m_workflowCombo->itemData(index).toInt(); }); - Row { - m_stateIndicator, - Column { - new QLabel("Execute:"), - m_executeCombo, - new QLabel("Workflow:"), - m_workflowCombo, - st - } - }.attachTo(this, WithoutMargins); + QBoxLayout *layout = new QHBoxLayout(this); + layout->addWidget(m_stateIndicator); + QBoxLayout *subLayout = new QVBoxLayout; + subLayout->addWidget(new QLabel("Execute Mode:")); + subLayout->addWidget(m_executeCombo); + subLayout->addWidget(new QLabel("Workflow Policy:")); + subLayout->addWidget(m_workflowCombo); + subLayout->addStretch(); + layout->addLayout(subLayout); + layout->setContentsMargins(0, 0, 0, 0); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); } @@ -163,12 +158,12 @@ void GroupWidget::updateExecuteMode() m_executeCombo->setCurrentIndex(m_executeCombo->findData((int)m_executeMode)); } -Tasking::ParallelLimit GroupWidget::executeMode() const +TaskItem GroupWidget::executeMode() const { - return m_executeMode == ExecuteMode::Sequential ? Tasking::sequential : Tasking::parallel; + return m_executeMode == ExecuteMode::Sequential ? sequential : parallel; } -void GroupWidget::setWorkflowPolicy(Tasking::WorkflowPolicy policy) +void GroupWidget::setWorkflowPolicy(WorkflowPolicy policy) { m_workflowPolicy = policy; updateWorkflowPolicy(); @@ -179,12 +174,7 @@ void GroupWidget::updateWorkflowPolicy() m_workflowCombo->setCurrentIndex(m_workflowCombo->findData((int)m_workflowPolicy)); } -Tasking::WorkflowPolicy GroupWidget::workflowPolicy() const +TaskItem GroupWidget::workflowPolicy() const { - return m_workflowPolicy; + return Tasking::workflowPolicy(m_workflowPolicy); } - -TaskGroup::TaskGroup(QWidget *group, std::initializer_list items) - : Row({ group, Group { Column { items } } }) -{} - diff --git a/tests/manual/tasktree/taskwidget.h b/tests/manual/tasking/demo/taskwidget.h similarity index 63% rename from tests/manual/tasktree/taskwidget.h rename to tests/manual/tasking/demo/taskwidget.h index 1f867dfeaec..3ce210eb0d4 100644 --- a/tests/manual/tasktree/taskwidget.h +++ b/tests/manual/tasking/demo/taskwidget.h @@ -1,8 +1,10 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -#include -#include +#ifndef TASKWIDGET_H +#define TASKWIDGET_H + +#include #include @@ -60,10 +62,10 @@ public: GroupWidget(); void setExecuteMode(ExecuteMode mode); - Utils::Tasking::ParallelLimit executeMode() const; + Tasking::TaskItem executeMode() const; - void setWorkflowPolicy(Utils::Tasking::WorkflowPolicy policy); - Utils::Tasking::WorkflowPolicy workflowPolicy() const; + void setWorkflowPolicy(Tasking::WorkflowPolicy policy); + Tasking::TaskItem workflowPolicy() const; private: void updateExecuteMode(); @@ -73,11 +75,7 @@ private: QComboBox *m_workflowCombo = nullptr; ExecuteMode m_executeMode = ExecuteMode::Sequential; - Utils::Tasking::WorkflowPolicy m_workflowPolicy = Utils::Tasking::WorkflowPolicy::StopOnError; + Tasking::WorkflowPolicy m_workflowPolicy = Tasking::WorkflowPolicy::StopOnError; }; -class TaskGroup : public Utils::Layouting::Row -{ -public: - TaskGroup(QWidget *group, std::initializer_list items); -}; +#endif // TASKWIDGET_H diff --git a/tests/manual/tasktree/CMakeLists.txt b/tests/manual/tasktree/CMakeLists.txt deleted file mode 100644 index 19bd81bfb2b..00000000000 --- a/tests/manual/tasktree/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -add_qtc_test(tst_manual_tasktree - MANUALTEST - DEPENDS Utils - SOURCES - main.cpp taskwidget.h taskwidget.cpp -) diff --git a/tests/manual/tasktree/main.cpp b/tests/manual/tasktree/main.cpp deleted file mode 100644 index b57481bfc41..00000000000 --- a/tests/manual/tasktree/main.cpp +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "taskwidget.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -using namespace Utils; - -// TODO: make tasks cancellable -static void sleepInThread(QFutureInterface &fi, int seconds, bool reportSuccess) -{ - QThread::sleep(seconds); - if (!reportSuccess) - fi.reportCanceled(); -} - -int main(int argc, char *argv[]) -{ - QApplication app(argc, argv); - - setCreatorTheme(new Theme("default", &app)); - - QWidget mainWidget; - mainWidget.setWindowTitle("Task Tree Demo"); - - // Non-task GUI - - QToolButton *startButton = new QToolButton(); - startButton->setText("Start"); - QToolButton *stopButton = new QToolButton(); - stopButton->setText("Stop"); - QToolButton *resetButton = new QToolButton(); - resetButton->setText("Reset"); - QProgressBar *progressBar = new QProgressBar(); - QCheckBox *synchronizerCheckBox = new QCheckBox("Use Future Synchronizer"); - synchronizerCheckBox->setChecked(true); - QScrollArea *scrollArea = new QScrollArea(); - scrollArea->setWidgetResizable(true); - QWidget *scrollAreaWidget = new QWidget(); - - // Task GUI - - QList allStateWidgets; - - auto createGroupWidget = [&allStateWidgets] { - auto *widget = new GroupWidget(); - allStateWidgets.append(widget); - return widget; - }; - auto createTaskWidget = [&allStateWidgets] { - auto *widget = new TaskWidget(); - allStateWidgets.append(widget); - return widget; - }; - - GroupWidget *rootGroup = createGroupWidget(); - - GroupWidget *groupTask_1 = createGroupWidget(); - TaskWidget *task_1_1 = createTaskWidget(); - TaskWidget *task_1_2 = createTaskWidget(); - TaskWidget *task_1_3 = createTaskWidget(); - - TaskWidget *task_2 = createTaskWidget(); - TaskWidget *task_3 = createTaskWidget(); - - GroupWidget *groupTask_4 = createGroupWidget(); - TaskWidget *task_4_1 = createTaskWidget(); - TaskWidget *task_4_2 = createTaskWidget(); - GroupWidget *groupTask_4_3 = createGroupWidget(); - TaskWidget *task_4_3_1 = createTaskWidget(); - TaskWidget *task_4_3_2 = createTaskWidget(); - TaskWidget *task_4_3_3 = createTaskWidget(); - TaskWidget *task_4_3_4 = createTaskWidget(); - TaskWidget *task_4_4 = createTaskWidget(); - TaskWidget *task_4_5 = createTaskWidget(); - - TaskWidget *task_5 = createTaskWidget(); - - // Task initial configuration - - task_1_2->setBusyTime(2); - task_1_2->setSuccess(false); - task_1_3->setBusyTime(3); - task_4_3_1->setBusyTime(4); - task_4_3_2->setBusyTime(2); - task_4_3_3->setBusyTime(1); - task_4_3_4->setBusyTime(3); - task_4_3_4->setSuccess(false); - task_4_4->setBusyTime(6); - task_4_4->setBusyTime(3); - - groupTask_1->setWorkflowPolicy(Tasking::WorkflowPolicy::ContinueOnDone); - groupTask_4->setWorkflowPolicy(Tasking::WorkflowPolicy::Optional); - groupTask_4_3->setExecuteMode(ExecuteMode::Parallel); - groupTask_4_3->setWorkflowPolicy(Tasking::WorkflowPolicy::StopOnError); - - // Task layout - - { - using namespace Layouting; - - Column { - TaskGroup { rootGroup, { - TaskGroup { groupTask_1, { - task_1_1, hr, - task_1_2, hr, - task_1_3, - }}, hr, - task_2, hr, - task_3, hr, - TaskGroup { groupTask_4, { - task_4_1, hr, - task_4_2, hr, - TaskGroup { groupTask_4_3, { - task_4_3_1, hr, - task_4_3_2, hr, - task_4_3_3, hr, - task_4_3_4, - }}, hr, - task_4_4, hr, - task_4_5, - }}, hr, - task_5 - }}, st - }.attachTo(scrollAreaWidget); - - scrollArea->setWidget(scrollAreaWidget); - - Column { - Row { startButton, stopButton, resetButton, synchronizerCheckBox, progressBar }, - hr, - scrollArea - }.attachTo(&mainWidget); - } - - // Task tree creator (takes configuation from GUI) - - std::unique_ptr taskTree; - FutureSynchronizer synchronizer; - synchronizer.setCancelOnWait(true); - - auto treeRoot = [&] { - using namespace Tasking; - - auto taskItem = [sync = &synchronizer, synchronizerCheckBox](TaskWidget *widget) { - const auto setupHandler = [=](AsyncTask &task) { - task.setAsyncCallData(sleepInThread, widget->busyTime(), widget->isSuccess()); - if (synchronizerCheckBox->isChecked()) - task.setFutureSynchronizer(sync); - widget->setState(State::Running); - }; - const auto doneHandler = [widget](const AsyncTask &) { - widget->setState(State::Done); - }; - const auto errorHandler = [widget](const AsyncTask &) { - widget->setState(State::Error); - }; - return Async(setupHandler, doneHandler, errorHandler); - }; - - const Group root { - rootGroup->executeMode(), - Workflow(rootGroup->workflowPolicy()), - OnGroupSetup([rootGroup] { rootGroup->setState(State::Running); }), - OnGroupDone([rootGroup] { rootGroup->setState(State::Done); }), - OnGroupError([rootGroup] { rootGroup->setState(State::Error); }), - - Group { - groupTask_1->executeMode(), - Workflow(groupTask_1->workflowPolicy()), - OnGroupSetup([groupTask_1] { groupTask_1->setState(State::Running); }), - OnGroupDone([groupTask_1] { groupTask_1->setState(State::Done); }), - OnGroupError([groupTask_1] { groupTask_1->setState(State::Error); }), - - taskItem(task_1_1), - taskItem(task_1_2), - taskItem(task_1_3) - }, - taskItem(task_2), - taskItem(task_3), - Group { - groupTask_4->executeMode(), - Workflow(groupTask_4->workflowPolicy()), - OnGroupSetup([groupTask_4] { groupTask_4->setState(State::Running); }), - OnGroupDone([groupTask_4] { groupTask_4->setState(State::Done); }), - OnGroupError([groupTask_4] { groupTask_4->setState(State::Error); }), - - taskItem(task_4_1), - taskItem(task_4_2), - Group { - groupTask_4_3->executeMode(), - Workflow(groupTask_4_3->workflowPolicy()), - OnGroupSetup([groupTask_4_3] { groupTask_4_3->setState(State::Running); }), - OnGroupDone([groupTask_4_3] { groupTask_4_3->setState(State::Done); }), - OnGroupError([groupTask_4_3] { groupTask_4_3->setState(State::Error); }), - - taskItem(task_4_3_1), - taskItem(task_4_3_2), - taskItem(task_4_3_3), - taskItem(task_4_3_4) - }, - taskItem(task_4_4), - taskItem(task_4_5) - }, - taskItem(task_5) - }; - return root; - }; - - // Non-task GUI handling - - auto createTaskTree = [&] { - TaskTree *taskTree = new TaskTree(treeRoot()); - progressBar->setMaximum(taskTree->progressMaximum()); - QObject::connect(taskTree, &TaskTree::progressValueChanged, - progressBar, &QProgressBar::setValue); - return taskTree; - }; - - auto stopTaskTree = [&] { - if (taskTree) - taskTree->stop(); - // TODO: unlock GUI controls? - }; - - auto resetTaskTree = [&] { - if (!taskTree) - return; - - stopTaskTree(); - taskTree.reset(); - for (StateWidget *widget : allStateWidgets) - widget->setState(State::Initial); - progressBar->setValue(0); - }; - - auto startTaskTree = [&] { - resetTaskTree(); - taskTree.reset(createTaskTree()); - taskTree->start(); - // TODO: lock GUI controls? - }; - - QObject::connect(startButton, &QAbstractButton::clicked, startTaskTree); - QObject::connect(stopButton, &QAbstractButton::clicked, stopTaskTree); - QObject::connect(resetButton, &QAbstractButton::clicked, resetTaskTree); - - // Hack in order to show initial size minimal, but without scrollbars. - // Apparently setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow) doesn't work. - const int margin = 2; - scrollArea->setMinimumSize(scrollAreaWidget->minimumSizeHint().grownBy({0, 0, margin, margin})); - QTimer::singleShot(0, scrollArea, [&] { scrollArea->setMinimumSize({0, 0}); }); - - mainWidget.show(); - - return app.exec(); -} diff --git a/tests/manual/tasktree/tasktree.qbs b/tests/manual/tasktree/tasktree.qbs deleted file mode 100644 index dad8e14763c..00000000000 --- a/tests/manual/tasktree/tasktree.qbs +++ /dev/null @@ -1,14 +0,0 @@ -import qbs.FileInfo - -QtcManualtest { - name: "Manual TaskTree test" - type: ["application"] - - Depends { name: "Utils" } - - files: [ - "main.cpp", - "taskwidget.h", - "taskwidget.cpp", - ] -} diff --git a/tests/manual/widgets/layoutbuilder/tst_manual_widgets_layoutbuilder.cpp b/tests/manual/widgets/layoutbuilder/tst_manual_widgets_layoutbuilder.cpp index 4f6bf710b2f..03a1cbc4bef 100644 --- a/tests/manual/widgets/layoutbuilder/tst_manual_widgets_layoutbuilder.cpp +++ b/tests/manual/widgets/layoutbuilder/tst_manual_widgets_layoutbuilder.cpp @@ -7,7 +7,7 @@ #include #include -using namespace Utils::Layouting; +using namespace Layouting; int main(int argc, char *argv[]) { diff --git a/tests/system/objects.map b/tests/system/objects.map index 591b671e539..97d44b7ed26 100644 --- a/tests/system/objects.map +++ b/tests/system/objects.map @@ -62,6 +62,8 @@ :Dialog.componentNameEdit_QLineEdit {name='componentNameEdit' type='Utils::ClassNameValidatingLineEdit' visible='1' window=':Dialog_QmlJSEditor::Internal::ComponentNameDialog'} :Dialog_Debugger::Internal::SymbolPathsDialog {name='Debugger__Internal__SymbolPathsDialog' type='Debugger::Internal::SymbolPathsDialog' visible='1' windowTitle='Dialog'} :Dialog_QmlJSEditor::Internal::ComponentNameDialog {type='QmlJSEditor::Internal::ComponentNameDialog' unnamed='1' visible='1' windowTitle='Move Component into Separate File'} +:EnableQMLDebugger_ComboBox {buddy=':EnableQMLDebugger_Label' type='QComboBox' unnamed='1' visible='1'} +:EnableQMLDebugger_Label {text='QML debugger:' type='QLabel' window=':Qt Creator_Core::Internal::MainWindow'} :Events.QmlProfilerEventsTable_QmlProfiler::Internal::QmlProfilerStatisticsMainView {container=':Qt Creator.Events_QDockWidget' name='QmlProfilerEventsTable' type='QmlProfiler::Internal::QmlProfilerStatisticsMainView' visible='1'} :Failed to start application_QMessageBox {type='QMessageBox' unnamed='1' visible='1' windowTitle='Failed to start application'} :File has been removed.Close_QPushButton {text='Close' type='QPushButton' unnamed='1' visible='1' window=':File has been removed_QMessageBox'} @@ -124,7 +126,7 @@ :Qt Creator.DragDoc_QToolButton {toolTip='Drag to drag documents between splits' type='QToolButton' unnamed='1' visible='1' window=':Qt Creator_Core::Internal::MainWindow'} :Qt Creator.Events_QDockWidget {name='QmlProfiler.Statistics.DockDockWidget' type='QDockWidget' visible='1' window=':Qt Creator_Core::Internal::MainWindow'} :Qt Creator.Events_QTabBar {aboveWidget=':Qt Creator.Events_QDockWidget' type='QTabBar' unnamed='1' visible='1' window=':Qt Creator_Core::Internal::MainWindow'} -:Qt Creator.Issues_QListView {type='QListView' unnamed='1' visible='1' window=':Qt Creator_Core::Internal::MainWindow' windowTitle='Issues'} +:Qt Creator.Issues_QListView {type='Utils::TreeView' unnamed='1' visible='1' window=':Qt Creator_Core::Internal::MainWindow' windowTitle='Issues'} :Qt Creator.Project.Menu.File_QMenu {name='Project.Menu.File' type='QMenu'} :Qt Creator.Project.Menu.Folder_QMenu {name='Project.Menu.Folder' type='QMenu' visible='1'} :Qt Creator.QML debugging and profiling:_QComboBox {leftWidget=':Qt Creator.QML debugging and profiling:_QLabel' type='QComboBox' unnamed='1' visible='1' window=':Qt Creator_Core::Internal::MainWindow'} diff --git a/tests/system/settings/mac/QtProject/qtcreator/toolchains.xml b/tests/system/settings/mac/QtProject/qtcreator/toolchains.xml index ac3136362c7..962e4208be4 100644 --- a/tests/system/settings/mac/QtProject/qtcreator/toolchains.xml +++ b/tests/system/settings/mac/QtProject/qtcreator/toolchains.xml @@ -11,7 +11,7 @@ x86-linux-generic-elf-32bit x86-darwin-generic-mach_o-64bit - SET_BY_SQUISH + x86-darwin-generic-mach_o-64bit false GCC ProjectExplorer.ToolChain.Gcc:{c3f59b87-6997-4bd8-8067-ee04dc536371} @@ -26,7 +26,7 @@ x86-darwin-generic-mach_o-64bit x86-darwin-generic-mach_o-32bit - SET_BY_SQUISH + x86-darwin-generic-mach_o-64bit false Clang (x86 64bit) ProjectExplorer.ToolChain.Clang:{6afe7dea-8caa-424e-b370-b7b0a34015fb} @@ -41,7 +41,7 @@ x86-darwin-generic-mach_o-64bit x86-darwin-generic-mach_o-32bit - SET_BY_SQUISH + x86-darwin-generic-mach_o-64bit false Clang [C] ProjectExplorer.ToolChain.Clang:{0db093b8-890b-4fad-a4bc-9078124fe866} @@ -57,7 +57,7 @@ x86-linux-generic-elf-32bit x86-darwin-generic-mach_o-64bit - SET_BY_SQUISH + x86-darwin-generic-mach_o-64bit false GCC [C] ProjectExplorer.ToolChain.Gcc:{461bb8dc-22ff-461f-82fe-ebe8b21b697f} diff --git a/tests/system/settings/unix/QtProject/qtcreator/toolchains.xml b/tests/system/settings/unix/QtProject/qtcreator/toolchains.xml index 0c98a8f561f..ae15015bbc1 100644 --- a/tests/system/settings/unix/QtProject/qtcreator/toolchains.xml +++ b/tests/system/settings/unix/QtProject/qtcreator/toolchains.xml @@ -11,7 +11,7 @@ x86-linux-generic-elf-32bit x86-macos-generic-mach_o-64bit - SET_BY_SQUISH + x86-linux-generic-elf-64bit false GCC ProjectExplorer.ToolChain.Gcc:{c3f59b87-6997-4bd8-8067-ee04dc536371} @@ -28,7 +28,7 @@ x86-linux-generic-elf-64bit x86-linux-generic-elf-32bit - SET_BY_SQUISH + x86-linux-generic-elf-64bit false GCC ProjectExplorer.ToolChain.Gcc:{7bfd4fd4-e64a-417f-b10f-20602e1719d1} diff --git a/tests/system/shared/build_utils.py b/tests/system/shared/build_utils.py index 74063c72f9f..6a2904286a3 100644 --- a/tests/system/shared/build_utils.py +++ b/tests/system/shared/build_utils.py @@ -21,11 +21,11 @@ def toggleIssuesFilter(filterName, checked): except: t,v = sys.exc_info()[:2] test.log("Exception while toggling filter '%s'" % filterName, - "%s(%s)" % (str(t), str(v))) + "%s: %s" % (t.__name__, str(v))) def getBuildIssues(ignoreCodeModel=True): - ensureChecked(":Qt Creator_Issues_Core::Internal::OutputPaneToggleButton") + ensureChecked(":Qt Creator_Issues_Core::Internal::OutputPaneToggleButton" , silent=True) model = waitForObject(":Qt Creator.Issues_QListView").model() if ignoreCodeModel: # filter out possible code model issues present inside the Issues pane @@ -39,8 +39,7 @@ def getBuildIssues(ignoreCodeModel=True): # this method checks the last build (if there's one) and logs the number of errors, warnings and # lines within the Issues output # param expectedToFail can be used to tell this function if the build was expected to fail or not -# param createTasksFileOnError whether a tasks file should be created when building ends with errors -def checkLastBuild(expectedToFail=False, createTasksFileOnError=True): +def checkLastBuild(expectedToFail=False): try: # can't use waitForObject() 'cause visible is always 0 findObject("{type='ProjectExplorer::Internal::BuildProgress' unnamed='1' }") @@ -48,14 +47,11 @@ def checkLastBuild(expectedToFail=False, createTasksFileOnError=True): test.log("checkLastBuild called without a build") return buildIssues = getBuildIssues() - types = [i[5] for i in buildIssues] + types = [i[1] for i in buildIssues] errors = types.count("1") warnings = types.count("2") gotErrors = errors != 0 test.verify(not (gotErrors ^ expectedToFail), "Errors: %s | Warnings: %s" % (errors, warnings)) - # additional stuff - could be removed... or improved :) - if gotErrors and createTasksFileOnError: - createTasksFile(buildIssues) return not gotErrors # helper function to check the compilation when build wasn't successful @@ -93,55 +89,9 @@ def dumpBuildIssues(listModel): issueDump = [] for index in dumpIndices(listModel): issueDump.extend([[str(index.data(role).toString()) for role - in range(Qt.UserRole, Qt.UserRole + 6)]]) + in range(Qt.UserRole, Qt.UserRole + 2)]]) return issueDump -# counter for written tasks files -tasksFileCount = 0 - -# helper method that writes a tasks file -def createTasksFile(buildIssues): - # currently used directory for tasks files - tasksFileDir = None - global tasksFileCount - if tasksFileDir == None: - tasksFileDir = os.getcwd() + "/tasks" - tasksFileDir = os.path.abspath(tasksFileDir) - if not os.path.exists(tasksFileDir): - try: - os.makedirs(tasksFileDir) - except OSError: - test.log("Could not create %s - falling back to a temporary directory" % tasksFileDir) - tasksFileDir = tempDir() - - tasksFileCount += 1 - outfile = os.path.join(tasksFileDir, os.path.basename(squishinfo.testCase)+"_%d.tasks" % tasksFileCount) - file = codecs.open(outfile, "w", "utf-8") - test.log("Writing tasks file - can take some time (according to number of issues)") - rows = len(buildIssues) - if os.environ.get("SYSTEST_DEBUG") == "1": - firstrow = 0 - else: - firstrow = max(0, rows - 100) - for issue in buildIssues[firstrow:rows]: - # the following is currently a bad work-around - fData = issue[0] # file - lData = issue[1] # line -> linenumber or empty - tData = issue[5] # type -> 1==error 2==warning - dData = issue[3] # description - if lData == "": - lData = "-1" - if tData == "1": - tData = "error" - elif tData == "2": - tData = "warning" - else: - tData = "unknown" - if str(fData).strip() == "" and lData == "-1" and str(dData).strip() == "": - test.fatal("Found empty task.") - file.write("%s\t%s\t%s\t%s\n" % (fData, lData, tData, dData)) - file.close() - test.log("Written tasks file %s" % outfile) # returns a list of pairs each containing the ID of a kit (see class Targets) # and the name of the matching build configuration diff --git a/tests/system/shared/clang.py b/tests/system/shared/clang.py index 1753d8e724a..812ead3e6a5 100644 --- a/tests/system/shared/clang.py +++ b/tests/system/shared/clang.py @@ -16,7 +16,7 @@ def startCreatorVerifyingClang(useClang): if strv == "startApplication() failed": test.fatal("%s. Creator built without ClangCodeModel?" % strv) else: - test.fatal("Exception caught", "%s(%s)" % (str(t), strv)) + test.fatal("Exception caught", "%s: %s" % (t.__name__, strv)) return False if platform.system() not in ('Microsoft', 'Windows'): # only Win uses dialogs for this return startedWithoutPluginError() diff --git a/tests/system/shared/debugger.py b/tests/system/shared/debugger.py index 424785b3a37..7176afc8e59 100644 --- a/tests/system/shared/debugger.py +++ b/tests/system/shared/debugger.py @@ -113,8 +113,7 @@ def doSimpleDebugging(currentKit, currentConfigName, expectedBPOrder=[], enableQ expectedLabelTexts.append("Running\.") switchViewTo(ViewConstants.PROJECTS) switchToBuildOrRunSettingsFor(currentKit, ProjectSettings.RUN) - ensureChecked(waitForObject("{container=':Qt Creator_Core::Internal::MainWindow' text='Enable QML' " - "type='QCheckBox' unnamed='1' visible='1'}"), enableQml) + selectFromCombo(":EnableQMLDebugger_ComboBox", "Enabled" if enableQml else "Disabled") switchViewTo(ViewConstants.EDIT) if not __startDebugger__(currentKit, currentConfigName): return False diff --git a/tests/system/shared/editor_utils.py b/tests/system/shared/editor_utils.py index c1df47ac3e6..de5619472e7 100644 --- a/tests/system/shared/editor_utils.py +++ b/tests/system/shared/editor_utils.py @@ -67,7 +67,7 @@ def menuVisibleAtEditor(editor, menuInList): return success except: t, v = sys.exc_info()[:2] - test.log("Exception: %s" % str(t), str(v)) + test.log("Exception: %s" % t.__name__, str(v)) return False # this function checks whether the given global point (QPoint) @@ -413,7 +413,7 @@ def openDocument(treeElement): return False except: t,v = sys.exc_info()[:2] - test.log("An exception occurred in openDocument(): %s(%s)" % (str(t), str(v))) + test.log("An exception occurred in openDocument(): %s: %s" % (t.__name__, str(v))) return False def earlyExit(details="No additional information"): diff --git a/tests/system/shared/fs_utils.py b/tests/system/shared/fs_utils.py index 1bcc3c81850..af76c7c8125 100644 --- a/tests/system/shared/fs_utils.py +++ b/tests/system/shared/fs_utils.py @@ -30,7 +30,7 @@ def changeFilePermissions(dirPath, readPerm, writePerm, excludeFileNames=None): os.chmod(filePath, permission) except: t,v = sys.exc_info()[:2] - test.log("Error: %s(%s)" % (str(t), str(v))) + test.log("%s: %s" % (t.__name__, str(v))) result = False return result diff --git a/tests/system/shared/project.py b/tests/system/shared/project.py index babbf0eb6ea..d1e44102bcc 100644 --- a/tests/system/shared/project.py +++ b/tests/system/shared/project.py @@ -120,18 +120,18 @@ def __handleBuildSystem__(buildSystem): selectFromCombo(combo, buildSystem) except: t, v = sys.exc_info()[:2] - test.warning("Exception while handling build system", "%s(%s)" % (str(t), str(v))) + test.warning("Exception while handling build system", "%s: %s" % (t.__name__, str(v))) clickButton(waitForObject(":Next_QPushButton")) return buildSystem def __createProjectHandleQtQuickSelection__(minimumQtVersion): - comboBox = waitForObject("{name='MinimumSupportedQtVersion' type='QComboBox' " - "visible='1' window=':New_ProjectExplorer::JsonWizard'}") + comboBox = waitForObject("{name?='*QtVersion' type='QComboBox' visible='1'" + " window=':New_ProjectExplorer::JsonWizard'}") try: - selectFromCombo(comboBox, minimumQtVersion) + selectFromCombo(comboBox, "Qt " + minimumQtVersion) except: t,v = sys.exc_info()[:2] - test.fatal("Exception while trying to select Qt version", "%s (%s)" % (str(t), str(v))) + test.fatal("Exception while trying to select Qt version", "%s: %s" % (t.__name__, str(v))) clickButton(waitForObject(":Next_QPushButton")) return minimumQtVersion @@ -277,7 +277,12 @@ def createNewQtQuickApplication(workingDir, projectName=None, buildSystem=None): available = __createProjectOrFileSelectType__(" Application (Qt)", template, fromWelcome) projectName = __createProjectSetNameAndPath__(workingDir, projectName) - __handleBuildSystem__(buildSystem) + if template == "Qt Quick Application": + if buildSystem: + test.warning("Build system set explicitly for a template which can't change it.", + "Template: %s, Build System: %s" % (template, buildSystem)) + else: + __handleBuildSystem__(buildSystem) requiredQt = __createProjectHandleQtQuickSelection__(minimumQtVersion) __modifyAvailableTargets__(available, requiredQt) checkedTargets = __chooseTargets__(targets, available) @@ -505,7 +510,7 @@ def __getSupportedPlatforms__(text, templateName, getAsStrings=False, ignoreVali version = res.group("version") else: version = None - if "Qt Quick" in templateName: + if templateName == "Qt Quick Application": result = set([Targets.DESKTOP_6_2_4]) elif 'Supported Platforms' in text: supports = text[text.find('Supported Platforms'):].split(":")[1].strip().split("\n") diff --git a/tests/system/shared/qtcreator.py b/tests/system/shared/qtcreator.py index 543c3c7ad4f..f243d00506a 100644 --- a/tests/system/shared/qtcreator.py +++ b/tests/system/shared/qtcreator.py @@ -12,10 +12,10 @@ import subprocess; import sys import errno; from datetime import datetime,timedelta; -try: - import __builtin__ # Python 2 -except ImportError: - import builtins as __builtin__ # Python 3 +if sys.version_info.major > 2: + import builtins as __builtin__ +else: + import __builtin__ srcPath = '' @@ -200,10 +200,7 @@ def substituteDefaultCompiler(settingsDir): if platform.system() == 'Darwin': compiler = "clang_64" elif platform.system() == 'Linux': - if __is64BitOS__(): - compiler = "gcc_64" - else: - compiler = "gcc" + compiler = "gcc_64" else: test.warning("Called substituteDefaultCompiler() on wrong platform.", "This is a script error.") @@ -280,71 +277,6 @@ def prependWindowsKit(settingsDir, targetBitness=64): __substitute__(profilesPath, "SQUISH_ENV_MODIFICATION", "") -def __guessABI__(supportedABIs, use64Bit): - if platform.system() == 'Linux': - supportedABIs = filter(lambda x: 'linux' in x, supportedABIs) - elif platform.system() == 'Darwin': - supportedABIs = filter(lambda x: 'darwin' in x, supportedABIs) - if use64Bit: - searchFor = "64bit" - else: - searchFor = "32bit" - for abi in supportedABIs: - if searchFor in abi: - return abi - if use64Bit: - test.log("Supported ABIs do not include an ABI supporting 64bit - trying 32bit now") - return __guessABI__(supportedABIs, False) - test.fatal('Could not guess ABI!', - 'Given ABIs: %s' % str(supportedABIs)) - return '' - -def __is64BitOS__(): - if platform.system() in ('Microsoft', 'Windows'): - machine = os.getenv("PROCESSOR_ARCHITEW6432", os.getenv("PROCESSOR_ARCHITECTURE")) - else: - machine = platform.machine() - if machine: - return '64' in machine - else: - return False - -def substituteUnchosenTargetABIs(settingsDir): - class ReadState: - NONE = 0 - READING = 1 - CLOSED = 2 - - on64Bit = __is64BitOS__() - toolchains = os.path.join(settingsDir, "QtProject", 'qtcreator', 'toolchains.xml') - origToolchains = toolchains + "_orig" - os.rename(toolchains, origToolchains) - origFile = open(origToolchains, "r") - modifiedFile = open(toolchains, "w") - supported = [] - readState = ReadState.NONE - for line in origFile: - if readState == ReadState.NONE: - if "SupportedAbis" in line: - supported = [] - readState = ReadState.READING - elif readState == ReadState.READING: - if "" in line: - readState = ReadState.CLOSED - else: - supported.append(line.split(">", 1)[1].rsplit("<", 1)[0]) - elif readState == ReadState.CLOSED: - if "SupportedAbis" in line: - supported = [] - readState = ReadState.READING - elif "SET_BY_SQUISH" in line: - line = line.replace("SET_BY_SQUISH", __guessABI__(supported, on64Bit)) - modifiedFile.write(line) - origFile.close() - modifiedFile.close() - os.remove(origToolchains) - test.log("Substituted unchosen ABIs inside toolchains.xml...") - def copySettingsToTmpDir(destination=None, omitFiles=[]): global tmpSettingsDir, SettingsPath, origSettingsDir if destination: @@ -379,9 +311,9 @@ def copySettingsToTmpDir(destination=None, omitFiles=[]): substituteMsvcPaths(tmpSettingsDir, '2019', 64) prependWindowsKit(tmpSettingsDir, 32) substituteOnlineInstallerPath(tmpSettingsDir) - substituteUnchosenTargetABIs(tmpSettingsDir) SettingsPath = ['-settingspath', '"%s"' % tmpSettingsDir] +test.log("Test is running on Python %s" % sys.version) # current dir is directory holding qtcreator.py origSettingsDir = os.path.abspath(os.path.join(os.getcwd(), "..", "..", "settings")) diff --git a/tests/system/shared/suites_qtta.py b/tests/system/shared/suites_qtta.py index 2c63e009e83..97032ae0638 100644 --- a/tests/system/shared/suites_qtta.py +++ b/tests/system/shared/suites_qtta.py @@ -20,9 +20,9 @@ def checkSyntaxError(issuesView, expectedTextsArray, warnIfMoreIssues = True): if(warnIfMoreIssues and issuesModel.rowCount() > 1): test.warning("More than one expected issues reported") # iterate issues and check if there exists "Unexpected token" message - for description, type in zip(dumpItems(issuesModel, role=Qt.UserRole + 3), - dumpItems(issuesModel, role=Qt.UserRole + 5)): - # enum Roles { File = Qt::UserRole, Line, MovedLine, Description, FileNotFound, Type, Category, Icon, Task_t }; + for description, type in zip(dumpItems(issuesModel, role=Qt.UserRole), + dumpItems(issuesModel, role=Qt.UserRole + 1)): + # enum Roles { Description = Qt::UserRole, Type}; # check if at least one of expected texts found in issue text for expectedText in expectedTextsArray: if expectedText in description: diff --git a/tests/system/shared/utils.py b/tests/system/shared/utils.py index 96e63f29f05..9b4f2dc95a3 100644 --- a/tests/system/shared/utils.py +++ b/tests/system/shared/utils.py @@ -25,7 +25,8 @@ def verifyChecked(objectName, checked=True): test.compare(object.checked, checked) return object -def ensureChecked(objectName, shouldBeChecked = True, timeout=20000): + +def ensureChecked(objectName, shouldBeChecked=True, timeout=20000, silent=False): if shouldBeChecked: targetState = Qt.Checked state = "checked" @@ -39,14 +40,13 @@ def ensureChecked(objectName, shouldBeChecked = True, timeout=20000): while not waitFor('widget.checkState() == targetState', 1500) and clicked < 2: clickButton(widget) clicked += 1 - test.verify(waitFor("widget.checkState() == targetState", 1000)) + silent or test.verify(waitFor("widget.checkState() == targetState", 1000)) except: # widgets not derived from QCheckbox don't have checkState() if not waitFor('widget.checked == shouldBeChecked', 1500): mouseClick(widget) - test.verify(waitFor("widget.checked == shouldBeChecked", 1000)) - test.log("New state for QCheckBox: %s" % state, - str(objectName)) + silent or test.verify(waitFor("widget.checked == shouldBeChecked", 1000)) + silent or test.log("New state for QCheckBox: %s" % state, str(objectName)) return widget # verify that an object is in an expected enable state. Returns the object. @@ -436,8 +436,8 @@ def iterateKits(clickOkWhenDone, alreadyOnOptionsDialog, t, v, _ = sys.exc_info() currResult = None test.fatal("Function to additionally execute on Options Dialog could not be " - "found or an exception occurred while executing it.", "%s(%s)" % - (str(t), str(v))) + "found or an exception occurred while executing it.", "%s: %s" % + (t.__name__, str(v))) additionalResult.append(currResult) if clickOkWhenDone: clickButton(waitForObject(":Options.OK_QPushButton")) diff --git a/tests/system/suite_APTW/tst_APTW02/test.py b/tests/system/suite_APTW/tst_APTW02/test.py index 716145a6401..0a5ff2bd72e 100644 --- a/tests/system/suite_APTW/tst_APTW02/test.py +++ b/tests/system/suite_APTW/tst_APTW02/test.py @@ -8,7 +8,7 @@ def main(): startQC() if not startedWithoutPluginError(): return - createNewQtQuickApplication(tempDir(), "SampleApp", buildSystem="qmake") + createNewQtQuickApplication(tempDir(), "SampleApp") # run project for debug and release and verify results runVerify() #close Qt Creator diff --git a/tests/system/suite_APTW/tst_APTW03/test.py b/tests/system/suite_APTW/tst_APTW03/test.py index bb09f8d7510..bbbec3b1630 100644 --- a/tests/system/suite_APTW/tst_APTW03/test.py +++ b/tests/system/suite_APTW/tst_APTW03/test.py @@ -73,7 +73,7 @@ def main(): invokeMenuItem('Build', 'Build Project "%s"' % projectName) waitForCompile(10000) if not virtualFunctionsAdded: - checkLastBuild(True, False) + checkLastBuild(True) if not openDocument("%s.Sources.%s\.cpp" % (projectName, className.lower())): test.fatal("Could not open %s.cpp - continuing." % className.lower()) continue diff --git a/tests/system/suite_HELP/tst_HELP02/test.py b/tests/system/suite_HELP/tst_HELP02/test.py index 0f8ff6767b0..7cacdf6e515 100644 --- a/tests/system/suite_HELP/tst_HELP02/test.py +++ b/tests/system/suite_HELP/tst_HELP02/test.py @@ -36,12 +36,12 @@ def checkQtCreatorHelpVersion(expectedVersion): helpContentWidget = waitForObject(':Qt Creator_QHelpContentWidget', 5000) waitFor("any(map(rightStart, dumpItems(helpContentWidget.model())))", 10000) items = dumpItems(helpContentWidget.model()) - test.compare(filter(rightStart, items)[0], + test.compare(list(filter(rightStart, items))[0], 'Qt Creator Manual %s' % expectedVersion, 'Verifying whether manual uses expected version.') except: t, v = sys.exc_info()[:2] - test.log("Exception caught", "%s(%s)" % (str(t), str(v))) + test.log("Exception caught", "%s: %s" % (t.__name__, str(v))) test.fail("Missing Qt Creator Manual.") diff --git a/tests/system/suite_debugger/tst_qml_js_console/test.py b/tests/system/suite_debugger/tst_qml_js_console/test.py index 6580e4988a4..4760296cf69 100644 --- a/tests/system/suite_debugger/tst_qml_js_console/test.py +++ b/tests/system/suite_debugger/tst_qml_js_console/test.py @@ -114,8 +114,7 @@ def main(): # make sure QML Debugging is enabled switchViewTo(ViewConstants.PROJECTS) switchToBuildOrRunSettingsFor(Targets.getDefaultKit(), ProjectSettings.RUN) - ensureChecked("{container=':Qt Creator.scrollArea_QScrollArea' text='Enable QML' " - "type='QCheckBox' unnamed='1' visible='1'}") + selectFromCombo(":EnableQMLDebugger_ComboBox", "Enabled") switchViewTo(ViewConstants.EDIT) # start debugging clickButton(fancyDebugButton) diff --git a/tests/system/suite_debugger/tst_qml_locals/test.py b/tests/system/suite_debugger/tst_qml_locals/test.py index 70256ee2d44..37ecb515cc6 100644 --- a/tests/system/suite_debugger/tst_qml_locals/test.py +++ b/tests/system/suite_debugger/tst_qml_locals/test.py @@ -35,8 +35,7 @@ def main(): return switchViewTo(ViewConstants.PROJECTS) switchToBuildOrRunSettingsFor(Targets.getDefaultKit(), ProjectSettings.RUN) - ensureChecked("{container=':Qt Creator_Core::Internal::MainWindow' text='Enable QML' " - "type='QCheckBox' unnamed='1' visible='1'}") + selectFromCombo(":EnableQMLDebugger_ComboBox", "Enabled") switchViewTo(ViewConstants.EDIT) clickButton(fancyDebugButton) locAndExprTV = waitForObject(":Locals and Expressions_Debugger::Internal::WatchTreeView") diff --git a/tests/system/suite_editors/tst_memberoperator/test.py b/tests/system/suite_editors/tst_memberoperator/test.py index ba1f1476850..4d11465b719 100644 --- a/tests/system/suite_editors/tst_memberoperator/test.py +++ b/tests/system/suite_editors/tst_memberoperator/test.py @@ -27,11 +27,11 @@ def __noBuildIssues__(): def __syntaxErrorDetected__(): buildIssues = getBuildIssues(False) for issue in buildIssues: - if issue[3] in ["Expected ';' after expression (fix available)", + if issue[0] in ["Expected ';' after expression (fix available)", "Expected ';' at end of declaration (fix available)", "Use of undeclared identifier 'syntaxError'"]: return True - if re.match(issue[3], "Declaration of reference variable '.+' requires an initializer"): + if re.match(issue[0], "Declaration of reference variable '.+' requires an initializer"): return True return False diff --git a/tests/system/suite_general/tst_create_proj_wizard/test.py b/tests/system/suite_general/tst_create_proj_wizard/test.py index 5cf9365275d..8579111b02c 100644 --- a/tests/system/suite_general/tst_create_proj_wizard/test.py +++ b/tests/system/suite_general/tst_create_proj_wizard/test.py @@ -53,12 +53,16 @@ def main(): template = list(current.values())[0] with TestSection("Testing project template %s -> %s" % (category, template)): displayedPlatforms = __createProject__(category, template) - if template == "Qt Quick Application": + if template.startswith("Qt Quick Application"): qtVersionsForQuick = ["6.2"] + if "(compat)" in template: + qtVersionsForQuick += ["5.14"] for counter, qtVersion in enumerate(qtVersionsForQuick): + def additionalFunc(displayedPlatforms, qtVersion): requiredQtVersion = __createProjectHandleQtQuickSelection__(qtVersion) __modifyAvailableTargets__(displayedPlatforms, requiredQtVersion, True) + handleBuildSystemVerifyKits(category, template, kits, displayedPlatforms, additionalFunc, qtVersion) # are there more Quick combinations - then recreate this project @@ -83,13 +87,15 @@ def verifyKitCheckboxes(kits, displayedPlatforms): unexpectedShownKits = availableCheckboxes.difference(displayedPlatforms) missingKits = displayedPlatforms.difference(availableCheckboxes) - test.log("Expected kits shown on 'Kit Selection' page:\n%s" % "\n".join(expectedShownKits)) - if len(unexpectedShownKits): - test.fail("Kits found on 'Kit Selection' page but not expected:\n%s" - % "\n".join(unexpectedShownKits)) - if len(missingKits): - test.fail("Expected kits missing on 'Kit Selection' page:\n%s" - % "\n".join(missingKits)) + if not test.verify(len(unexpectedShownKits) == 0 and len(missingKits) == 0, + "No missing or unexpected kits found on 'Kit Selection' page?\n" + "Found expected kits:\n%s" % "\n".join(expectedShownKits)): + if len(unexpectedShownKits): + test.log("Kits found on 'Kit Selection' page but not expected:\n%s" + % "\n".join(unexpectedShownKits)) + if len(missingKits): + test.log("Expected kits missing on 'Kit Selection' page:\n%s" + % "\n".join(missingKits)) def handleBuildSystemVerifyKits(category, template, kits, displayedPlatforms, specialHandlingFunc = None, *args): @@ -120,7 +126,7 @@ def handleBuildSystemVerifyKits(category, template, kits, displayedPlatforms, clickButton(waitForObject(":Next_QPushButton")) if specialHandlingFunc: specialHandlingFunc(displayedPlatforms, *args) - if not ('Plain C' in template or 'Qt Quick' in template): + if not ('Plain C' in template or template == 'Qt Quick Application'): __createProjectHandleTranslationSelection__() verifyKitCheckboxes(kits, displayedPlatforms) safeClickButton("Cancel") diff --git a/tests/system/suite_general/tst_opencreator_qbs/test.py b/tests/system/suite_general/tst_opencreator_qbs/test.py index 555e1437582..ca941547c90 100644 --- a/tests/system/suite_general/tst_opencreator_qbs/test.py +++ b/tests/system/suite_general/tst_opencreator_qbs/test.py @@ -24,7 +24,7 @@ def main(): else: test.warning("Parsing project timed out") compareProjectTree(rootNodeTemplate % "Qt Creator", "projecttree_creator.tsv") - buildIssuesTexts = map(lambda i: str(i[3]), getBuildIssues()) + buildIssuesTexts = map(lambda i: str(i[0]), getBuildIssues()) deprecationWarnings = filter(lambda s: "deprecated" in s, buildIssuesTexts) if deprecationWarnings: test.warning("Creator claims that the .qbs file uses deprecated features.", diff --git a/tests/system/suite_general/tst_save_before_build/test.py b/tests/system/suite_general/tst_save_before_build/test.py index d6b9b5f8574..87c2d56d39a 100644 --- a/tests/system/suite_general/tst_save_before_build/test.py +++ b/tests/system/suite_general/tst_save_before_build/test.py @@ -29,7 +29,7 @@ def main(): if not startedWithoutPluginError(): return verifySaveBeforeBuildChecked(False) - for buildSystem in ["CMake", "Qbs"]: + for buildSystem in ["CMake"]: projectName = "SampleApp-" + buildSystem ensureSaveBeforeBuildChecked(False) # create qt quick application diff --git a/tests/system/suite_general/tst_tasks_handling/test.py b/tests/system/suite_general/tst_tasks_handling/test.py index 053d5d24c74..865d22a2485 100644 --- a/tests/system/suite_general/tst_tasks_handling/test.py +++ b/tests/system/suite_general/tst_tasks_handling/test.py @@ -31,7 +31,7 @@ def generateRandomTaskType(): return 2 def generateMockTasksFile(): - descriptions = ["", "dummy information", "unknown error", "not found", "syntax error", + descriptions = ["dummy information", "unknown error", "not found", "syntax error", "missing information", "unused"] tasks = ["warn", "error", "other"] isWin = platform.system() in ('Microsoft', 'Windows') @@ -44,7 +44,7 @@ def generateMockTasksFile(): tasksType = generateRandomTaskType() tasksCount[tasksType] += 1 tData = tasks[tasksType] - dData = descriptions[random.randint(0, 6)] + dData = descriptions[random.randint(0, len(descriptions) - 1)] tFile.write("%s\t%d\t%s\t%s\n" % (fData, lData, tData, dData)) tFile.close() test.log("Wrote tasks file with %d warnings, %d errors and %d other tasks." % tuple(tasksCount)) @@ -58,7 +58,7 @@ def checkOrUncheckMyTasks(): "My Tasks")) def getBuildIssuesTypeCounts(model): - issueTypes = list(map(lambda x: x.data(Qt.UserRole + 5).toInt(), dumpIndices(model))) + issueTypes = list(map(lambda x: x.data(Qt.UserRole + 1).toInt(), dumpIndices(model))) result = [issueTypes.count(0), issueTypes.count(1), issueTypes.count(2)] if len(issueTypes) != sum(result): test.fatal("Found unexpected value(s) for TaskType...") diff --git a/tests/system/suite_tools/tst_codepasting/test.py b/tests/system/suite_tools/tst_codepasting/test.py index 5db5a28c901..ec0d9ee226e 100644 --- a/tests/system/suite_tools/tst_codepasting/test.py +++ b/tests/system/suite_tools/tst_codepasting/test.py @@ -34,8 +34,8 @@ def closeHTTPStatusAndPasterDialog(protocol, pasterDialog): test.log("Closed dialog without expected error.", text) except: t,v = sys.exc_info()[:2] - test.warning("An exception occurred in closeHTTPStatusAndPasterDialog(): %s(%s)" - % (str(t), str(v))) + test.warning("An exception occurred in closeHTTPStatusAndPasterDialog(): %s: %s" + % (t.__name__, str(v))) return False def pasteFile(sourceFile, protocol): @@ -72,7 +72,7 @@ def pasteFile(sourceFile, protocol): try: outputWindow = waitForObject(":Qt Creator_Core::OutputWindow") waitFor("re.search('^https://', str(outputWindow.plainText)) is not None", 20000) - output = filter(lambda x: len(x), str(outputWindow.plainText).splitlines())[-1] + output = list(filter(lambda x: len(x), str(outputWindow.plainText).splitlines()))[-1] except: output = "" if closeHTTPStatusAndPasterDialog(protocol, ':Send to Codepaster_CodePaster::PasteView'): @@ -107,7 +107,7 @@ def fetchSnippet(protocol, description, pasteId, skippedPasting): pasteModel = waitForObject(":PasteSelectDialog.listWidget_QListWidget").model() except: closeHTTPStatusAndPasterDialog(protocol, ':PasteSelectDialog_CodePaster::PasteSelectDialog') - return -1 + return invalidPasteId(protocol) condition = "pasteModel.rowCount() > 1" if protocol == NAME_DPCOM: # no list support @@ -125,11 +125,12 @@ def fetchSnippet(protocol, description, pasteId, skippedPasting): "window=':PasteSelectDialog_CodePaster::PasteSelectDialog'}") waitFor("pasteModel.rowCount() == 1", 1000) waitFor("pasteModel.rowCount() > 1", 20000) - if pasteId == -1: - try: - pasteLine = filter(lambda str:description in str, dumpItems(pasteModel))[0] - pasteId = pasteLine.split(" ", 1)[0] - except: + if pasteId == invalidPasteId(protocol): + for currentItem in dumpItems(pasteModel): + if description in currentItem: + pasteId = currentItem.split(" ", 1)[0] + break + if pasteId == invalidPasteId(protocol): test.fail("Could not find description line in list of pastes from %s" % protocol) clickButton(waitForObject(":PasteSelectDialog.Cancel_QPushButton")) return pasteId @@ -167,7 +168,7 @@ def checkForMovedUrl(): # protocol may be redirected (HTTP status 30x) - check test.fail("URL has moved permanently.", match[0].group(0)) except: t,v,tb = sys.exc_info() - test.log(str(t), str(v)) + test.log("%s: %s" % (t.__name__, str(v))) def main(): startQC() @@ -207,7 +208,7 @@ def main(): test.fatal(message) continue pasteId = fetchSnippet(protocol, description, pasteId, skippedPasting) - if pasteId == -1: + if pasteId == invalidPasteId(protocol): continue filenameCombo = waitForObject(":Qt Creator_FilenameQComboBox") waitFor("not filenameCombo.currentText.isEmpty()", 20000) @@ -242,7 +243,8 @@ def main(): # QString QTextCursor::selectedText () const: # "Note: If the selection obtained from an editor spans a line break, the text will contain a # Unicode U+2029 paragraph separator character instead of a newline \n character." - selectedText = str(editor.textCursor().selectedText()).replace(unichr(0x2029), "\n") + newParagraph = chr(0x2029) if sys.version_info.major > 2 else unichr(0x2029) + selectedText = str(editor.textCursor().selectedText()).replace(newParagraph, "\n") invokeMenuItem("Tools", "Code Pasting", "Paste Snippet...") test.compare(waitForObject(":stackedWidget.plainTextEdit_QPlainTextEdit").plainText, selectedText, "Verify that dialog shows selected text from the editor") diff --git a/tests/system/suite_tools/tst_git_local/test.py b/tests/system/suite_tools/tst_git_local/test.py index 07208367fdb..ab36b86b251 100644 --- a/tests/system/suite_tools/tst_git_local/test.py +++ b/tests/system/suite_tools/tst_git_local/test.py @@ -65,7 +65,8 @@ def __clickCommit__(count): # find commit try: # Commits are listed in reverse chronologic order, so we have to invert count - line = filter(lambda line: line.startswith("commit"), content.splitlines())[-count].strip() + line = list(filter(lambda line: line.startswith("commit"), + content.splitlines()))[-count].strip() commit = line.split(" ", 1)[1] except: test.fail("Could not find the %d. commit - leaving test" % count) @@ -90,7 +91,7 @@ def __clickCommit__(count): {"Committer: %s, %s" % (id, time): True}] for line, exp in zip(show.splitlines(), expected): expLine = list(exp.keys())[0] - isRegex = exp.values()[0] + isRegex = list(exp.values())[0] if isRegex: test.verify(re.match(expLine, line), "Verifying commit header line '%s'" % line) else: diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 8971a289dea..95353c01ea6 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -30,7 +30,7 @@ if (NOT QT_CREATOR_API_DEFINED) set(GOOGLETEST_DIR ${CMAKE_CURRENT_LIST_DIR}/unittest/3rdparty/googletest) find_package(Clang MODULE) - find_package(Qt5 + find_package(Qt6 COMPONENTS Gui Core Core5Compat Widgets Network Qml Concurrent Test Xml MODULE) find_package(Threads) diff --git a/tests/unit/tools/CMakeLists.txt b/tests/unit/tools/CMakeLists.txt index 05b561491fc..123825fe81b 100644 --- a/tests/unit/tools/CMakeLists.txt +++ b/tests/unit/tools/CMakeLists.txt @@ -1 +1,3 @@ -add_subdirectory(qmlprojectmanager) +if (Qt6_Version VERSION_GREATER_EQUAL "6.4.3") + add_subdirectory(qmlprojectmanager) +endif () diff --git a/tests/unit/unittest/CMakeLists.txt b/tests/unit/unittest/CMakeLists.txt index 2f0634b61cb..0fd002dd7af 100644 --- a/tests/unit/unittest/CMakeLists.txt +++ b/tests/unit/unittest/CMakeLists.txt @@ -26,7 +26,7 @@ add_qtc_test(unittest GTEST DEPENDS Qt::Core Qt::Network Qt::Widgets Qt::Xml Qt::Concurrent Qt::QmlPrivate Qt::Gui - Qt6Core5Compat QmlJS Sqlite SqliteC + Qt::Core5Compat QmlJS Sqlite SqliteC Googletest DEFINES GTEST_INTERNAL_HAS_STRING_VIEW @@ -43,7 +43,6 @@ add_qtc_test(unittest GTEST QDS_USE_PROJECTSTORAGE SOURCES abstractviewmock.h - compare-operators.h conditionally-disabled-tests.h dynamicastmatcherdiagnosticcontainer-matcher.h eventspy.cpp eventspy.h @@ -343,15 +342,6 @@ endif() extend_qtc_test(unittest DEPENDS Utils CPlusPlus) -extend_qtc_test(unittest - SOURCES_PREFIX ../../../src/plugins/coreplugin - DEFINES CORE_STATIC_LIBRARY - SOURCES - coreicons.cpp coreicons.h - find/ifindfilter.cpp find/ifindfilter.h - locator/ilocatorfilter.cpp locator/ilocatorfilter.h -) - extend_qtc_test(unittest CONDITION TARGET Qt6::QmlDomPrivate AND TARGET Qt6::QmlCompilerPrivate AND Qt6_VERSION VERSION_GREATER_EQUAL 6.5.0 AND Qt6_VERSION VERSION_LESS 6.6.0 DEPENDS Qt6::QmlDomPrivate Qt6::QmlCompilerPrivate diff --git a/tests/unit/unittest/compare-operators.h b/tests/unit/unittest/compare-operators.h deleted file mode 100644 index dc3b3357a2e..00000000000 --- a/tests/unit/unittest/compare-operators.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include - -namespace Core { -namespace Search { - -inline -bool operator==(const TextPosition first, class TextPosition second) -{ - return first.line == second.line - && first.column == second.column; -} - -inline -bool operator==(const TextRange first, class TextRange second) -{ - return first.begin == second.begin - && first.end == second.end; -} -} -} diff --git a/tests/unit/unittest/externaldependenciesmock.h b/tests/unit/unittest/externaldependenciesmock.h index df11dec75f6..22654f70551 100644 --- a/tests/unit/unittest/externaldependenciesmock.h +++ b/tests/unit/unittest/externaldependenciesmock.h @@ -7,8 +7,6 @@ #include -#include - class ExternalDependenciesMock : public QmlDesigner::ExternalDependenciesInterface { public: diff --git a/tests/unit/unittest/googletest.h b/tests/unit/unittest/googletest.h index 72ece6a6022..a3d3012a09a 100644 --- a/tests/unit/unittest/googletest.h +++ b/tests/unit/unittest/googletest.h @@ -13,8 +13,6 @@ #include #include -#include "compare-operators.h" - #include "conditionally-disabled-tests.h" #include "gtest-creator-printing.h" #include "gtest-llvm-printing.h" diff --git a/tests/unit/unittest/gtest-creator-printing.cpp b/tests/unit/unittest/gtest-creator-printing.cpp index d9af745406b..8c88ee07515 100644 --- a/tests/unit/unittest/gtest-creator-printing.cpp +++ b/tests/unit/unittest/gtest-creator-printing.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include @@ -43,11 +42,6 @@ std::ostream &operator<<(std::ostream &out, const monostate &) } // namespace std namespace Utils { - -std::ostream &operator<<(std::ostream &out, const LineColumn &lineColumn) -{ - return out << "(" << lineColumn.line << ", " << lineColumn.column << ")"; -} namespace { const char * toText(Utils::Language language) { diff --git a/tests/unit/unittest/gtest-creator-printing.h b/tests/unit/unittest/gtest-creator-printing.h index 170084f67a1..7506f6cb11b 100644 --- a/tests/unit/unittest/gtest-creator-printing.h +++ b/tests/unit/unittest/gtest-creator-printing.h @@ -73,7 +73,6 @@ class LineColumn; class SmallStringView; class FilePath; -std::ostream &operator<<(std::ostream &out, const LineColumn &lineColumn); std::ostream &operator<<(std::ostream &out, const Utils::Language &language); std::ostream &operator<<(std::ostream &out, const Utils::LanguageVersion &languageVersion); std::ostream &operator<<(std::ostream &out, const Utils::LanguageExtension &languageExtension); diff --git a/tests/unit/unittest/sqlitedatabasemock.h b/tests/unit/unittest/sqlitedatabasemock.h index 62a1d6585b7..658a0ec88b4 100644 --- a/tests/unit/unittest/sqlitedatabasemock.h +++ b/tests/unit/unittest/sqlitedatabasemock.h @@ -28,7 +28,7 @@ public: MOCK_METHOD(void, prepare, (Utils::SmallStringView sqlStatement), ()); - MOCK_METHOD(void, execute, (Utils::SmallStringView sqlStatement), ()); + MOCK_METHOD(void, execute, (Utils::SmallStringView sqlStatement), (override)); MOCK_METHOD(int64_t, lastInsertedRowId, (), (const)); diff --git a/tests/unit/unittest/sqlitereadwritestatementmock.h b/tests/unit/unittest/sqlitereadwritestatementmock.h index 3dab28d9946..aaf59530741 100644 --- a/tests/unit/unittest/sqlitereadwritestatementmock.h +++ b/tests/unit/unittest/sqlitereadwritestatementmock.h @@ -43,7 +43,7 @@ public: ()); template - auto optionalValue(const QueryTypes &...queryValues) + auto optionalValue([[maybe_unused]] const QueryTypes &...queryValues) { static_assert(!std::is_same_v, "SqliteReadStatementMock::value does not handle result type!");