diff options
author | Eike Ziller <eike.ziller@qt.io> | 2025-09-15 10:38:00 +0200 |
---|---|---|
committer | Eike Ziller <eike.ziller@qt.io> | 2025-09-15 10:38:00 +0200 |
commit | 49b98c83c60dbb06153e5c17c78b0a4c63d429fe (patch) | |
tree | 3450ac11663b9f091f5358695e4f9bf0eaa42069 | |
parent | d553d0cfcb7913c1b5acf3614d935c5cd82ff30d (diff) | |
parent | 95e198eec04166705c1fd7450335609e08df3f58 (diff) |
Merge remote-tracking branch 'origin/18.0'
Change-Id: Ia88ed45d7a4271d472be61b5c465e54e175b036b
149 files changed, 3149 insertions, 958 deletions
diff --git a/dist/api-changelog/api-changes-18.md b/dist/api-changelog/api-changes-18.md new file mode 100644 index 00000000000..26bcc4d05ce --- /dev/null +++ b/dist/api-changelog/api-changes-18.md @@ -0,0 +1,70 @@ +Qt Creator 18 +============= + +This document aims to summarize the API changes in selected libraries and +plugins. + +| Before | After | +|------------------------------------------------------------------|--------------------------------------------------------------------------------| +| | | +| **General** | | +| QTextCodec * | Replaced by Utils::TextEncoding | +| | | +| **Layouting** | | +| If (*condition*, { *then-case* }, { *else-case* }) | If (*condition*) >> Then { *then-case* } >> Else { *else-case* } | +| | | +| **Tasking** | | +| CallDoneIf::Success | Renamed to CallDone::OnSuccess | +| CallDoneIf::Error | Renamed to CallDone::OnError | +| CallDoneIf::SuccessOrError | Renamed to CallDone::Always | +| - | Added CallDone::OnCancel | +| MultiBarrier | Renamed to StoredMultiBarrier | +| NetworkOperation | Replaced by QNetworkAccessManager::Operation | +| SharedBarrier | Renamed to StartedBarrier and derived from Barrier | +| SingleBarrier | Renamed to StoredBarrier | +| TaskTreeRunner | Renamed to SingleTaskTreeRunner | +| | | +| **Utils** | | +| makeWritable | FilePath::makeWritable | +| StyleHelper::SpacingTokens | Were renamed and adapted to the new design | +| Text::positionInText | The column parameter is now 0-based | +| toFilePathList | FilePaths::fromStrings | +| **Utils::FilePath** | | +| fromStrings | FilePaths::fromStrings | +| fromSettingsList | FilePaths::fromSettings | +| **Utils::FilePathAspect** | | +| FilePath baseFileName() | Lazy\<FilePath\> baseFileName() | +| **Utils::FilePaths** | | +| alias for QList\<FilePath\> | Custom class derived from QList\<FilePath> | +| **Utils::Link** | | +| targetLine and targetColumn members | Merged into one Text::Position member | +| **Utils::PathChooser** | | +| FilePath baseDirectory() | Lazy\<FilePath\> baseDirectory() | +| **Utils::TextFileFormat** | | +| QStringList-based decode | Removed, use the QString-based method | +| static readFile | Made a member function instead of returning TextFileFormat as output parameter | +| readFile QString(List) and decodingErrorSample output parameters | Removed, part of ReadResult now | +| static void detect | void detectFromData | +| | | +| **ExtensionSystem** | | +| | | +| **Core** | | +| **Core::ExternalToolManager** | | +| readSettings and parseDirectory | Removed | +| **Core::TextDocument** | | +| read QString(List) output parameters | ReadResult includes the read contents | +| | | +| **TextEditor** | | +| **TextEditor::TabSettings** | | +| removeTrailingWhitespace(QTextCursor cursor, QTextBlock &block) | removeTrailingWhitespace(const QTextBlock &block) | +| | | +| **ProjectExplorer** | | +| **ProjectExplorer::BuildConfiguration** | | +| rawBuildDirectory | Removed | +| **ProjectExplorer::DetectionSource** | Has been added and is used for various detectionSource() getters | +| **ProjectExplorer::ITaskHandler** | | +| actionManagerId and createAction | Removed, pass them to the constructor instead | +| **ProjectExplorer::RawProjectPart** | | +| setIncludeFiles | Changed to use Utils::FilePaths | +| **ProjectExplorer::Task** | | +| Members | Are replaced by setters and getters | diff --git a/dist/changelog/changes-18.0.0.md b/dist/changelog/changes-18.0.0.md new file mode 100644 index 00000000000..909a71ab533 --- /dev/null +++ b/dist/changelog/changes-18.0.0.md @@ -0,0 +1,319 @@ +Qt Creator 18 +============= + +Qt Creator version 18 contains bug fixes and new features. +It is a free upgrade for commercial license holders. + +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 or view online at + +<https://code.qt.io/cgit/qt-creator/qt-creator.git/log/?id=17.0..v18.0.0> + +New plugins +----------- + +### Development Container Support + +The development container support detects a `devcontainer.json` in your project +directory and creates a docker container for it. +It supports Qt Creator specific `customizations` in the `devcontainer.json` that +let you auto detect or specify custom kits for the container and control other +aspects like the command bridge. + +([Development Container Documentation](https://containers.dev/)) + +General +------- + +* Moved some messages from the `General Messages` to `Issues` +* Changed the notifications to popups with the option to opt-out with + `Environment > Interface > Prefer banner style info bars over pop-ups` +* Added the `HostOs:DocumentsLocation`, `HostOs:GenericDataLocation`, + `HostOs:HomeLocation`, and `HostOs:TempLocation` Qt Creator variables that + map to the corresponding `QStandardPaths` +* Added the `:DirName` postfix for Qt Creator variables that map to file paths + ([QTCREATORBUG-33205](https://bugreports.qt.io/browse/QTCREATORBUG-33205)) +* Fixed a freeze when installing large plugins + ([QTCREATORBUG-33069](https://bugreports.qt.io/browse/QTCREATORBUG-33069)) +* Welcome + * Added an `Overview` tab +* Locator + * Made `Use Tab Completion` (when pressing the `Tab` key) optional + ([QTCREATORBUG-33193](https://bugreports.qt.io/browse/QTCREATORBUG-33193)) + +Editing +------- + +* Added the option `Environment > Interface > Use tabbed editors` + ([QTCREATORBUG-31644](https://bugreports.qt.io/browse/QTCREATORBUG-31644)) + * Added `Window > Previous Tab` and `Window > Next Tab` +* Added `File > Save Without Formatting` +* Added `Open With > Cycle to Next Editor` + ([QTCREATORBUG-32482](https://bugreports.qt.io/browse/QTCREATORBUG-32482), + [QTCREATORBUG-32610](https://bugreports.qt.io/browse/QTCREATORBUG-32610)) +* Fixed that error markers were not removed when fixing the error + ([QTCREATORBUG-33108](https://bugreports.qt.io/browse/QTCREATORBUG-33108)) + +### C++ + +* Added automatic insertion of the closing part of a raw string literal prefix + ([QTCREATORBUG-31901](https://bugreports.qt.io/browse/QTCREATORBUG-31901)) +* Fixed that trailing white space was removed from raw string literals + ([QTCREATORBUG-30003](https://bugreports.qt.io/browse/QTCREATORBUG-30003)) +* Fixed that `Re-order Member Function Definitions According to Declaration Order` + did not move comments accordingly + ([QTCREATORBUG-33070](https://bugreports.qt.io/browse/QTCREATORBUG-33070)) +* Fixed the generation of `compile_commands.json` for remote projects +* Quick fixes + * Added `Remove Curly Braces` + * Added `Add definition` for static data members + ([QTCREATORBUG-20961](https://bugreports.qt.io/browse/QTCREATORBUG-20961)) + * Fixed issues with templates and nested classes + ([QTCREATORBUG-9727](https://bugreports.qt.io/browse/QTCREATORBUG-9727)) + * Fixed issues with nested template parameters + ([QTCREATORBUG-17695](https://bugreports.qt.io/browse/QTCREATORBUG-17695)) +* Built-in + * Added support for template deduction guides + * Added support for fold expressions + * Added support for `decltype(auto)` as function return types + * Added support for `if consteval` and `if not consteval` + * Added support for __int128 + * Fixed the preprocessor expansion in include directives + ([QTCREATORBUG-27473](https://bugreports.qt.io/browse/QTCREATORBUG-27473)) + * Fixed issues with defaulted and deleted functions + ([QTCREATORBUG-26090](https://bugreports.qt.io/browse/QTCREATORBUG-26090)) + * Fixed the parsing of friend declarations with a simple type specifier + * Fixed the parsing of attributes after an operator name + +### QML + +* Added the option to install Qt Design Studio via the Qt Online Installer + ([QTCREATORBUG-30787](https://bugreports.qt.io/browse/QTCREATORBUG-30787)) +* Added the option to override the path to `qmlls` + ([QTCREATORBUG-32749](https://bugreports.qt.io/browse/QTCREATORBUG-32749)) + +### Copilot + +* Added support for GitHub Enterprise environments + ([QTCREATORBUG-33220](https://bugreports.qt.io/browse/QTCREATORBUG-33220)) +* Fixed configuration issues with Copilot >= v1.49.0 + +### Markdown + +* Improved table rendering +* Fixed the scaling of images + ([QTCREATORBUG-33325](https://bugreports.qt.io/browse/QTCREATORBUG-33325)) + +### SCXML + +* Fixed the positioning of the transition arrow + ([QTCREATORBUG-32654](https://bugreports.qt.io/browse/QTCREATORBUG-32654)) + +### GLSL + +* Fixed the handling of interface blocks + ([QTCREATORBUG-12784](https://bugreports.qt.io/browse/QTCREATORBUG-12784), + [QTCREATORBUG-27068](https://bugreports.qt.io/browse/QTCREATORBUG-27068)) + +Projects +-------- + +* Moved the project settings to a `.qtcreator` subdirectory in the project + directory. The `.user` file at the old location in the project directory is + kept up to date in addition, for old projects + ([QTCREATORBUG-28610](https://bugreports.qt.io/browse/QTCREATORBUG-28610)) +* Changed the `Build` and `Run` subitems to tabs in `Projects` mode and + separated `Deploy Settings` from `Run Settings` +* Changed the `Current Project` advanced search to `Single Project` with + an explicit choice of the project to search + ([QTCREATORBUG-29790](https://bugreports.qt.io/browse/QTCREATORBUG-29790)) +* Removed the `Code Snippet` wizard from `File > New Project > Other Project`. + Use `Plain C++` instead +* Made options from the global `Build & Run` settings available as project + specific options +* Made `Copy Steps From Another Kit` available without first enabling the kit + ([QTCREATORBUG-24123](https://bugreports.qt.io/browse/QTCREATORBUG-24123)) +* Made the default deploy configuration available for all target devices +* Added a configuration for various tools on devices, like GDB server, CMake, + clangd, rsync, qmake, and more, and the option to auto-detect them +* Added the setting `Build & Run > General > Keep run configurations in sync` + with the option to synchronize run configurations within one or all kits + ([QTCREATORBUG-33172](https://bugreports.qt.io/browse/QTCREATORBUG-33172)) +* Added the tool button `Create Issues From External Build Output` to the + `Issues` view + ([QTCREATORBUG-30776](https://bugreports.qt.io/browse/QTCREATORBUG-30776)) +* Added the + `Preferences > Build & Run > Default Build Properties > Default working directory` + setting for run configurations +* Added keyboard shortcuts for editing the active build and run configurations + ([QTCREATORBUG-27887](https://bugreports.qt.io/browse/QTCREATORBUG-27887)) +* Added the option to add a file to a project directly from the + `This file is not part of any project` warning + ([QTCREATORBUG-25834](https://bugreports.qt.io/browse/QTCREATORBUG-25834)) +* Added the `Project` Qt Creator variable for the build configuration settings + that maps to the project file path +* Added a Qt Interface Framework project wizard + ([QTBUG-99070](https://bugreports.qt.io/browse/QTBUG-99070)) +* Added the `Enable logging category filtering` option for desktop run + configurations with Qt 6.11 and later + ([QTCREATORBUG-33169](https://bugreports.qt.io/browse/QTCREATORBUG-33169)) +* Fixed `Duplicate File` for remote projects + +### CMake + +* Added more detailed information to the build progress tool tip + ([QTCREATORBUG-33356](https://bugreports.qt.io/browse/QTCREATORBUG-33356)) +* Added the `ct` locator filter for running CTest tests +* Fixed `Build for All Configurations` + ([QTCREATORBUG-33178](https://bugreports.qt.io/browse/QTCREATORBUG-33178)) +* Fixed issues with rewriting `CMakeLists.txt` files with the UTF-8 BOM set + ([QTCREATORBUG-33363](https://bugreports.qt.io/browse/QTCREATORBUG-33363)) +* vcpkg + +### qmake + +* Fixed various issues with opening remote projects + TODO: what state is that exactly in now? + +### Python + +* Removed PySide2 from the project wizard options + ([QTCREATORBUG-33030](https://bugreports.qt.io/browse/QTCREATORBUG-33030)) + +### Workspace + +* Changed projects to be automatically configured for the default kit on first + use +* Added minimal support for Cargo build projects (Rust) + +Debugging +--------- + +### C++ + +* Fixed `Load QML Stack` + ([QTCREATORBUG-33244](https://bugreports.qt.io/browse/QTCREATORBUG-33244)) + +Analyzer +-------- + +### Clang + +* Added Clang-Tidy and Clazy issues from the current document to the `Issues` + view + ([QTCREATORBUG-29789](https://bugreports.qt.io/browse/QTCREATORBUG-29789)) +* Improved the performance of loading diagnostics from a file +* Fixed freezes when applying multiple fix-its + ([QTCREATORBUG-25394](https://bugreports.qt.io/browse/QTCREATORBUG-25394)) + +### Axivion + +* Added a request for the user to add a path mapping when opening files from + the issues table and none exist + +### Coco + +* Fixed issues with MinGW + ([QTCREATORBUG-33287](https://bugreports.qt.io/browse/QTCREATORBUG-33287)) + +Version Control Systems +----------------------- + +### Git + +* Added `Git > Local Repository > Patch > Apply from Clipboard` +* Added `Git > Local Repository > Patch > Create from Commits` +* Commit editor + * Added `Recover File`, `Revert All Changes to File`, and + `Revert Unstaged Changes to File` to the context menu on files + * Added `Stage`, `Unstage`, and `Add to .gitignore` to the context menu on + untracked files + * Added actions for resolving conflicts +* Added an error indicator and error messages to the `Add Branch` dialog +* Added `Diff & Cancel` to the `Checkout Branch` dialog +* Added a visualization of the version control state to the `File System` view +* Improved performance of file modification status updates + ([QTCREATORBUG-32002](https://bugreports.qt.io/browse/QTCREATORBUG-32002)) +* Fixed updating the `Branch` view after changes + ([QTCREATORBUG-29918](https://bugreports.qt.io/browse/QTCREATORBUG-29918)) + +Platforms +--------- + +### macOS + +* Removed the auto-detection of 32-bit compilers +* Made it clearer which auto-detected toolchains are only for iOS +* Fixed that the automatically set toolchain for desktop kits could be an iOS + toolchain + +### Android + +* Fixed the qmake project path set when creating APK templates + ([QTCREATORBUG-33215](https://bugreports.qt.io/browse/QTCREATORBUG-33215)) + +### Remote Linux + +* Added the `Auto-connect on startup` option and removed automatic connection + to devices if it is turned off (the default) +* Added support for deployment with `rsync` with remote build devices +* Improved the error message when device tests fail + ([QTCREATORBUG-32933](https://bugreports.qt.io/browse/QTCREATORBUG-32933)) + +### Docker + +* Added the option `Mount Command Bridge` to the docker device configuration + ([QTCREATORBUG-33006](https://bugreports.qt.io/browse/QTCREATORBUG-33006)) + +Credits for these changes go to: +-------------------------------- +Aaron McCarthy +Alessandro Portale +Alexandru Croitor +Alexis Jeandet +Ali Kianian +Amr Essam +Andre Hartmann +Andrzej Biniek +AndrÊ PÃļnitz +Assam Boudjelthia +BjÃļrn Schäpers +Burak Hancerli +Christian Kandeler +Christian Stenger +Cristian Adam +David Schulz +Dheerendra Purohit +Eike Ziller +Eren Bursali +faust747 +Friedemann Kleint +Jaroslaw Kobus +Johanna Vanhatapio +Kai KÃļhne +Leena Miettinen +Mahmoud Badri +Marco Bubke +Marcus Tillmanns +Markus Redeker +Miikka Heikkinen +Mitch Curtis +Nicholas Bennett +Nikita Baryshnikov +Olivier De Cannière +Orgad Shaneh +Philip Van Hoof +Renaud Guezennec +Sami Shalayel +Samuli Piippo +Semih Yavuz +Sheree Morphett +Stanislav Polukhanov +Teea Poldsam +Thiago Macieira +Tian Shilin +Tim JenÃen +Volodymyr Zibarov +Xavier Besson +Zoltan Gera diff --git a/dist/changelog/template.md b/dist/changelog/template.md index a1c571bfe92..4fe759dd153 100644 --- a/dist/changelog/template.md +++ b/dist/changelog/template.md @@ -48,6 +48,8 @@ Editing ### FakeVim +### GLSL + ### Binary Files Projects diff --git a/doc/qtcreator/images/qtcreator-personalize-learning.webp b/doc/qtcreator/images/qtcreator-personalize-learning.webp Binary files differnew file mode 100644 index 00000000000..3a5e0e02724 --- /dev/null +++ b/doc/qtcreator/images/qtcreator-personalize-learning.webp diff --git a/doc/qtcreator/images/qtcreator-preferences-copilot.webp b/doc/qtcreator/images/qtcreator-preferences-copilot.webp Binary files differindex cec0b434aa0..ce1fd1edc70 100644 --- a/doc/qtcreator/images/qtcreator-preferences-copilot.webp +++ b/doc/qtcreator/images/qtcreator-preferences-copilot.webp diff --git a/doc/qtcreator/images/qtcreator-preferences-language-client-qmlls.webp b/doc/qtcreator/images/qtcreator-preferences-language-client-qmlls.webp Binary files differindex 4c7aa6653b9..cf5d0cbe960 100644 --- a/doc/qtcreator/images/qtcreator-preferences-language-client-qmlls.webp +++ b/doc/qtcreator/images/qtcreator-preferences-language-client-qmlls.webp diff --git a/doc/qtcreator/images/qtcreator-projects-building-and-running-settings.webp b/doc/qtcreator/images/qtcreator-projects-building-and-running-settings.webp Binary files differnew file mode 100644 index 00000000000..d66d654b161 --- /dev/null +++ b/doc/qtcreator/images/qtcreator-projects-building-and-running-settings.webp diff --git a/doc/qtcreator/images/qtcreator-welcome-overview.webp b/doc/qtcreator/images/qtcreator-welcome-overview.webp Binary files differnew file mode 100644 index 00000000000..7016135d429 --- /dev/null +++ b/doc/qtcreator/images/qtcreator-welcome-overview.webp diff --git a/doc/qtcreator/images/qtcreator-welcome.webp b/doc/qtcreator/images/qtcreator-welcome.webp Binary files differdeleted file mode 100644 index 17741ac1486..00000000000 --- a/doc/qtcreator/images/qtcreator-welcome.webp +++ /dev/null diff --git a/doc/qtcreator/src/analyze/cpu-usage-analyzer.qdoc b/doc/qtcreator/src/analyze/cpu-usage-analyzer.qdoc index 2da0ab5ef50..de308920e19 100644 --- a/doc/qtcreator/src/analyze/cpu-usage-analyzer.qdoc +++ b/doc/qtcreator/src/analyze/cpu-usage-analyzer.qdoc @@ -122,7 +122,7 @@ \uicontrol Analyzer > \uicontrol {CPU Usage}. To set preferences for a particular run configuration, go to - \uicontrol Projects > \uicontrol Run, and then select \uicontrol Details + \uicontrol Projects > \uicontrol {Run Settings} and select \uicontrol Details next to \uicontrol {Performance Analyzer Settings}. \image {qtcreator-performance-analyzer-settings.png} {Performance Analyzer Settings} diff --git a/doc/qtcreator/src/analyze/creator-axivion.qdoc b/doc/qtcreator/src/analyze/creator-axivion.qdoc index 0ae7ffc7b83..8797f0018a9 100644 --- a/doc/qtcreator/src/analyze/creator-axivion.qdoc +++ b/doc/qtcreator/src/analyze/creator-axivion.qdoc @@ -17,9 +17,8 @@ \section1 View inline annotations in editor - The editor shows found issues as inline annotations if the project is - configured with a path mapping or if the project matches the - currently open one and the respective file is part of the project. + The editor shows found issues as inline annotations only if the project is + configured with a path mapping. Hover over an annotation to bring up a tooltip with a short description of the issue. @@ -140,6 +139,10 @@ select the link in the details or in the \uicontrol {Right Path} or \uicontrol {Target Path} column. + If there is no valid mapping configured for the current selected project + you will be prompted to set up a valid path mapping as jumping to the + issue relies on having a valid path mapping configured. + \sa {Axivion}{Axivion Preferences}, {Local analysis with Axivion}{Local Analysis}, {Enable and disable plugins},{Analyze}{How To: Analyze}, {Analyzers}, {Analyzing Code} diff --git a/doc/qtcreator/src/analyze/creator-coco.qdoc b/doc/qtcreator/src/analyze/creator-coco.qdoc index 424d5f3aeab..b78bab2e226 100644 --- a/doc/qtcreator/src/analyze/creator-coco.qdoc +++ b/doc/qtcreator/src/analyze/creator-coco.qdoc @@ -29,8 +29,7 @@ CMake: \list 1 - \li Go to \uicontrol Projects > \uicontrol {Build & Run} > - \uicontrol Build > \uicontrol {Build Settings}. + \li Go to \uicontrol Projects > \uicontrol {Build Settings}. \li Select an existing build configuration, such as \e Debug, and then select \uicontrol Clone to clone it with a new name, such as \e DebugCoverage. diff --git a/doc/qtcreator/src/android/creator-projects-settings-run-android.qdoc b/doc/qtcreator/src/android/creator-projects-settings-run-android.qdoc index 83edfc4a911..8ef4eaec169 100644 --- a/doc/qtcreator/src/android/creator-projects-settings-run-android.qdoc +++ b/doc/qtcreator/src/android/creator-projects-settings-run-android.qdoc @@ -11,9 +11,8 @@ \brief Settings for running applications on Android devices. - Specify settings for running applications on the \l {Kits}{Run device} that - you select for a kit in \uicontrol Projects > \uicontrol {Build & Run} > - \uicontrol Run > \uicontrol {Run Settings}. + To specify settings for running applications on the \l {Kits}{Run device} that + you select for a kit, go to \uicontrol Projects > \uicontrol {Run Settings}. To run and debug an application on an Android device, you must create connections from the development host to the device, as instructed in diff --git a/doc/qtcreator/src/android/deploying-android.qdoc b/doc/qtcreator/src/android/deploying-android.qdoc index 248a6f412d9..1e8c2479a34 100644 --- a/doc/qtcreator/src/android/deploying-android.qdoc +++ b/doc/qtcreator/src/android/deploying-android.qdoc @@ -41,8 +41,8 @@ \note Since \QC 4.12, Ministro is not supported. To \l{Specifying Settings for Packages}{specify settings} for application - packages, select \uicontrol Projects > \uicontrol Build > - \uicontrol {Build Android APK} > \uicontrol Details. + packages, go to \uicontrol Projects > \uicontrol {Build Settings} > + \uicontrol {Build Android APK} and select \uicontrol Details. For more information about the options that you have for running applications, see \l {Android Run Settings}. @@ -60,10 +60,13 @@ \section2 Specifying Deployment Settings - The \uicontrol Method field lists deployment settings. - To add deployment methods for a project, select \uicontrol Add. + To specify settings for deploying applications, go to \uicontrol Projects > + \uicontrol {Deploy Settings} and select a deploy configuration in + \uicontrol {Active deployment configuration}. - \image {qtcreator-android-deployment-settings.png} {Deployment settings} + \image {qtcreator-android-deployment-settings.png} {Deploy Settings tab in Projects} + + To add deploy configurations for a project, select \uicontrol Add. To rename the current deployment method, select \uicontrol Rename. @@ -84,9 +87,9 @@ \section2 Specifying Settings for Packages - To specify settings for the \c androiddeployqt tool, select - \uicontrol Projects > \uicontrol {Build & Run} > \uicontrol Build > - \uicontrol {Build Android APK} > \uicontrol Details. + To specify settings for the \c androiddeployqt tool, go to + \uicontrol Projects > \uicontrol {Build Settings} > + \uicontrol {Build Android APK} and select \uicontrol Details. \image {qtcreator-build-settings-android-apk.webp} {Build Android APK step} @@ -212,9 +215,10 @@ \section3 Adding External Libraries \QC automatically detects which Qt libraries the application uses and adds - them as dependencies. If the application needs external libraries, specify - them in \uicontrol Projects > \uicontrol Build > \uicontrol {Build Android APK} - > \uicontrol {Additional Libraries} field. The libraries are copied into + them as dependencies. If the application needs external libraries, go to + \uicontrol Projects > \uicontrol {Build Settings} > + \uicontrol {Build Android APK} and specify them in + \uicontrol {Additional Libraries}. The libraries are copied into your application's library folder and loaded on startup. To add OpenSSL libraries, select \uicontrol {Include prebuilt OpenSSL libraries} @@ -246,8 +250,8 @@ \list 1 - \li Select \uicontrol Projects > \uicontrol Build > - \uicontrol {Build Android APK} > \uicontrol {Create Templates}. + \li Go to \uicontrol Projects > \uicontrol {Build Settings} > + \uicontrol {Build Android APK}, and select \uicontrol {Create Templates}. \li Check the path in \uicontrol {Android package source directory}. diff --git a/doc/qtcreator/src/appman/creator-appman-how-to-run.qdoc b/doc/qtcreator/src/appman/creator-appman-how-to-run.qdoc index f7015eb44d4..6287901d510 100644 --- a/doc/qtcreator/src/appman/creator-appman-how-to-run.qdoc +++ b/doc/qtcreator/src/appman/creator-appman-how-to-run.qdoc @@ -57,8 +57,7 @@ \section1 Customize the installation To change the settings for deploying and running the application with the - selected kit, go to \uicontrol Projects and select \uicontrol {Build & Run} - > \uicontrol Run. + selected kit, go to \uicontrol Projects > \uicontrol {Deploy Settings}. \image {qtcreator-appman-deploy-settings.webp} {Deploy to application manager} @@ -75,8 +74,9 @@ \section2 Deployment configuration - In \uicontrol Method, \uicontrol {Automatic Application Manager - Deploy Configuration} adds the necessary CMake and tool arguments, as well as + In \uicontrol {Active deployment configuration}, + \uicontrol {Automatic Application Manager Deploy Configuration} + adds the necessary CMake and tool arguments, as well as \uicontrol Targets to the effective \uicontrol Build command. You can select the targets in the kit selector to deploy and run applications on them. diff --git a/doc/qtcreator/src/cmake/creator-projects-cmake-building.qdoc b/doc/qtcreator/src/cmake/creator-projects-cmake-building.qdoc index f23ca680e28..3d3b20f3e48 100644 --- a/doc/qtcreator/src/cmake/creator-projects-cmake-building.qdoc +++ b/doc/qtcreator/src/cmake/creator-projects-cmake-building.qdoc @@ -11,9 +11,8 @@ \brief Settings for building applications with CMake. - Specify build settings for the selected \l{Kits}{kit} in - \uicontrol Projects > \uicontrol {Build & Run} > \uicontrol Build > - \uicontrol {Build Settings}. + To specify build settings for the selected \l{Kits}{kit}, go to + \uicontrol Projects > \uicontrol {Build Settings}. Configuring medium-sized to large CMake projects in \QC can be a challenge due to the number of variables that you need to pass to @@ -132,8 +131,8 @@ that if you remove the build directory, all the custom variables that are not part of the initial CMake configuration are also removed. - To reconfigure a project using the changed variable values, - select \uicontrol Build > \uicontrol {Clear CMake Configuration}, which + To reconfigure a project using the changed variable values, go to + \uicontrol Build and select \uicontrol {Clear CMake Configuration}, which removes the CMakeCache.txt file. This enables you to do a full rebuild. \section2 Removing Variables @@ -165,8 +164,9 @@ \section1 Viewing CMake Output - Output from CMake is displayed next to the \uicontrol {Build Settings} and - \uicontrol {Run Settings} panes in the \uicontrol Projects mode. + Output from CMake is displayed next to the \uicontrol {Build Settings}, + \uicontrol {Deploy Settings}, and \uicontrol {Run Settings} tabs in the + \uicontrol Projects mode. \image {qtcreator-build-cmake-output.png} {CMake output in Projects mode} @@ -190,7 +190,7 @@ \key Ctrl+-. To hide the output, select - \inlineimage {icons/rightsidebaricon.png} {Hide Right Sidebar}} + \inlineimage {icons/rightsidebaricon.png} {Hide Right Sidebar} (\uicontrol {Hide Right Sidebar}) or \key {Alt+Shift+0}. \section1 CLICOLOR_FORCE Environment Variable @@ -253,8 +253,8 @@ \li Install Ninja. \li Add the path to the Ninja executable to the value of the PATH system variable. - \li In \uicontrol Projects > \uicontrol {Build & Run} > \uicontrol Build - > \uicontrol {Build Settings}, select \uicontrol {Kit Configuration}. + \li Go to \uicontrol Projects > \uicontrol {Build Settings}, and select + \uicontrol {Kit Configuration}. \image {qtcreator-cmake-kit-configuration.png} {Kit CMake Configuration dialog} \li Select \uicontrol Change next to the \uicontrol {CMake generator} field to open the \uicontrol {CMake Generator} dialog. @@ -267,8 +267,8 @@ \endlist \note To make sure that old build artifacts don't get in the way - the first time you build the project after the change, select - \uicontrol Build > \uicontrol {Rebuild Project}. This cleans up the + the first time you build the project after the change, go to + \uicontrol Build and select \uicontrol {Rebuild Project}. This cleans up the build directory and performs a new build. \section1 Using CMake with Package Managers diff --git a/doc/qtcreator/src/cmake/creator-projects-cmake-presets.qdoc b/doc/qtcreator/src/cmake/creator-projects-cmake-presets.qdoc index 8e9dc19d16e..d68c1224ce5 100644 --- a/doc/qtcreator/src/cmake/creator-projects-cmake-presets.qdoc +++ b/doc/qtcreator/src/cmake/creator-projects-cmake-presets.qdoc @@ -35,8 +35,9 @@ \image {qtcreator-cmake-presets-environment.webp} {CMake environment configuration} - To update changes to the \c CMakePresets.json file, select \uicontrol Build > - \uicontrol {Reload CMake Presets}, and then select the presets file to load. + To update changes to the \c CMakePresets.json file, go to \uicontrol Build, + select \uicontrol {Reload CMake Presets}, and then select the presets file + to load. \section1 Configure Presets @@ -335,7 +336,7 @@ \li \l{View CMake project contents} \row \li \c {AskBeforePresetsReload} - \li Asks before acting when you select \uicontrol Build > + \li Asks before acting when you go to \uicontrol Build and select \uicontrol {Reload CMake Presets}. \li \l{CMake Presets} \row diff --git a/doc/qtcreator/src/cmake/creator-projects-cmake.qdoc b/doc/qtcreator/src/cmake/creator-projects-cmake.qdoc index 1de81513434..3cb9ffcba0e 100644 --- a/doc/qtcreator/src/cmake/creator-projects-cmake.qdoc +++ b/doc/qtcreator/src/cmake/creator-projects-cmake.qdoc @@ -84,8 +84,9 @@ To re-configure the project: \list 1 - \li Select \uicontrol Build > \uicontrol {Clear CMake Configuration}. - \li Select \uicontrol Build > \uicontrol {Run CMake}. + \li Go to \uicontrol Build, and select + \uicontrol {Clear CMake Configuration}. + \li Go to \uicontrol Build, and select \uicontrol {Run CMake}. \endlist \section1 Hide subfolder names in Projects view @@ -95,7 +96,7 @@ only according to their source group, select \preferences > \uicontrol CMake > \uicontrol General, and then clear the \uicontrol {Show subfolders inside source group folders} check - box. The change takes effect after you select \uicontrol Build > + box. The change takes effect after you go to \uicontrol Build and select \uicontrol {Run CMake}. \sa {Build with CMake}{How To: Build with CMake}, {CMake}, {Open projects}, @@ -212,8 +213,8 @@ You can also use the \c cmo filter in the \l {Navigate with locator}{locator} to open the CMakeLists.txt file for the current run configuration - in the editor. This is the same build target as when you select - \uicontrol Build > \uicontrol {Build for Run Configuration}. + in the editor. This is the same build target as when you go to + \uicontrol Build and select \uicontrol {Build for Run Configuration}. The following features are supported: diff --git a/doc/qtcreator/src/cmake/creator-projects-settings-cmake.qdoc b/doc/qtcreator/src/cmake/creator-projects-settings-cmake.qdoc index 309c50ac6e6..6257c764336 100644 --- a/doc/qtcreator/src/cmake/creator-projects-settings-cmake.qdoc +++ b/doc/qtcreator/src/cmake/creator-projects-settings-cmake.qdoc @@ -46,7 +46,7 @@ \li \l{Re-configuring with Initial Variables} \row \li Ask before reloading CMake presets - \li Asks before acting when you select \uicontrol Build > + \li Asks before acting when you go to \uicontrol Build and select \uicontrol {Reload CMake Presets}. \li \l{CMake Presets} \row diff --git a/doc/qtcreator/src/conan/creator-projects-conan-building.qdoc b/doc/qtcreator/src/conan/creator-projects-conan-building.qdoc index 771de7087c3..1a557ab8603 100644 --- a/doc/qtcreator/src/conan/creator-projects-conan-building.qdoc +++ b/doc/qtcreator/src/conan/creator-projects-conan-building.qdoc @@ -11,9 +11,8 @@ \brief Settings for building applications with the Conan package manager. - Specify build settings for the selected \l{Kits}{kit} in - \uicontrol Projects > \uicontrol {Build & Run} > \uicontrol Build > - \uicontrol {Build Settings}. + To specify build settings for the selected \l{Kits}{kit}, go to + \uicontrol Projects > \uicontrol {Build Settings}. To configure a project to be built using the Conan package manager, select \uicontrol {Add Build Step} > \uicontrol {Run Conan Install}. diff --git a/doc/qtcreator/src/debugger/creator-only/creator-debugger-setup.qdoc b/doc/qtcreator/src/debugger/creator-only/creator-debugger-setup.qdoc index 72eb3de8004..c696c30f656 100644 --- a/doc/qtcreator/src/debugger/creator-only/creator-debugger-setup.qdoc +++ b/doc/qtcreator/src/debugger/creator-only/creator-debugger-setup.qdoc @@ -143,8 +143,8 @@ \l {Tutorial: Qt Widgets and Python}{pyproject.toml} configuration file. - Install Python and set the interpreter to use in \uicontrol Projects - > \uicontrol Run. + To install Python and set the interpreter to use, go to \uicontrol Projects + > \uicontrol {Run Settings}. \image {qtcreator-run-settings-python.webp} {Run settings for a Python project} diff --git a/doc/qtcreator/src/debugger/qtquick-debugging.qdoc b/doc/qtcreator/src/debugger/qtquick-debugging.qdoc index ddd0611a67e..1fc90616bfb 100644 --- a/doc/qtcreator/src/debugger/qtquick-debugging.qdoc +++ b/doc/qtcreator/src/debugger/qtquick-debugging.qdoc @@ -58,7 +58,7 @@ \list 1 \li To create a build configuration that supports QML debugging, - go to \uicontrol {Projects} > \uicontrol {Build}. + go to \uicontrol {Projects} > \uicontrol {Build Settings}. \li In \uicontrol {QML debugging and profiling}, select \uicontrol Enable. @@ -78,8 +78,8 @@ time, also select \uicontrol Automatic or \uicontrol Enabled in \uicontrol {C++ debugger}. - \li Select \uicontrol Build > \uicontrol {Rebuild Project} to clean and - rebuild the project. + \li Go to \uicontrol Build, and select \uicontrol {Rebuild Project} to + clean and rebuild the project. \li To debug applications on \l{glossary-device}{devices}, check that Qt libraries are installed on the device and @@ -114,8 +114,8 @@ 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 + \uicontrol {Build Settings} > \uicontrol {QML debugging and profiling} + keeps 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 diff --git a/doc/qtcreator/src/docker/creator-docker.qdoc b/doc/qtcreator/src/docker/creator-docker.qdoc index f1b98e73377..49e97d5f21f 100644 --- a/doc/qtcreator/src/docker/creator-docker.qdoc +++ b/doc/qtcreator/src/docker/creator-docker.qdoc @@ -193,7 +193,7 @@ activate the kit for Docker devices. \endlist - Select \uicontrol Run to specify run settings. Usually, you can use + Go to \uicontrol {Run Settings} to specify run settings. Usually, you can use the default settings. \sa {Enable and disable plugins}, {Docker}{How To: Develop for Docker}, diff --git a/doc/qtcreator/src/editors/creator-only/creator-compilation-database.qdoc b/doc/qtcreator/src/editors/creator-only/creator-compilation-database.qdoc index c5f75cf64a8..0ee6cd1b62e 100644 --- a/doc/qtcreator/src/editors/creator-only/creator-compilation-database.qdoc +++ b/doc/qtcreator/src/editors/creator-only/creator-compilation-database.qdoc @@ -23,7 +23,8 @@ database JSON file. To generate a compilation database from the information that the code model - has, select \uicontrol Build > \uicontrol {Run Generator} > \uicontrol {Compilation Database}. + has, go to \uicontrol Build > \uicontrol {Run Generator} and select + \uicontrol {Compilation Database}. You can add files, such as non-C files, to the project in \e {compile_database.json.files}. diff --git a/doc/qtcreator/src/editors/creator-only/creator-copilot.qdoc b/doc/qtcreator/src/editors/creator-only/creator-copilot.qdoc index 151aa8ceb46..6a037ae9e67 100644 --- a/doc/qtcreator/src/editors/creator-only/creator-copilot.qdoc +++ b/doc/qtcreator/src/editors/creator-only/creator-copilot.qdoc @@ -37,8 +37,8 @@ To set preferences for using Copilot: \list 1 - \li Go to \preferences > \uicontrol Copilot. - \image {qtcreator-preferences-copilot.webp} {Copilot preferences} + \li Go to \preferences > \uicontrol AI > \uicontrol Copilot. + \image {qtcreator-preferences-copilot.webp} {Copilot tab in AI preferences} \li Select \uicontrol {Enable Copilot} to use Copilot. \li Select \uicontrol {Sign In} to sign into your subscription, activate your device, and authorize the GitHub Copilot plugin. @@ -50,22 +50,13 @@ language-server.js in the Copilot Neovim plugin installation folder. \li Select \uicontrol {Auto request} to receive suggestions for the current text cursor position when you make changes. - \li Select \uicontrol {Use proxy} to use a proxy server to - connect to Copilot servers. - \li In \uicontrol {Proxy host}, enter the host name of the proxy server. - \li In \uicontrol {Proxy port}, enter the port number of the - proxy server. + \li In \uicontrol {GitHub Enterprise URL}, enter the URL to Copilot + server that runs in the enterprise environment. + \li In \uicontrol {Proxy}, enter the host name and port number of the + proxy server to connect to Copilot servers. \li Select \uicontrol {Reject unauthorized} to prevent the security risk presented by accepting unauthorized certificates from the proxy server. - \li In \uicontrol {Proxy user}, enter the user name to - authenticate to the proxy server. - \li Select \uicontrol {Save proxy password} to save the - password to authenticate to the proxy server. - \note The password is saved insecurely. - \li In \uicontrol {Proxy password}, enter the password to save. - To see the password as you type, select - \inlineimage {icons/original-size.png} {Show Password}. \endlist \section1 Receive suggestions 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 9551c42f448..a10bc37b1a3 100644 --- a/doc/qtcreator/src/editors/creator-only/creator-language-server.qdoc +++ b/doc/qtcreator/src/editors/creator-only/creator-language-server.qdoc @@ -261,14 +261,20 @@ \section1 Select \QMLLS version - \QC tries to use \QMLLS shipped with the Qt version in your current - \l{Kits}{kit}. To override that behavior and always use - \QMLLS of the highest registered Qt version, select - \uicontrol {Use from latest Qt version}. + To use \QMLLS shipped with the Qt version in your current \l{Kits}{kit}, + select \uicontrol {Use qmlls from project Qt kit}. This is the default + option. + + To always use \QMLLS of the highest registered Qt version, select + \uicontrol {Use qmlls from latest Qt kit}. To use older \QMLLS versions, select \uicontrol{Allow versions below Qt 6.8}. + To use a specific \QMLLS version, set the path to the executable in + \uicontrol {Use custom qmlls executable}. To download the latest + \QMLLS version, select \uicontrol {Download latest standalone qmlls}. + \section1 Automatically configure new CMake projects To automatically configure new CMake projects, select diff --git a/doc/qtcreator/src/incredibuild/creator-projects-incredibuild-building.qdoc b/doc/qtcreator/src/incredibuild/creator-projects-incredibuild-building.qdoc index 04c8c7497d1..32368111a45 100644 --- a/doc/qtcreator/src/incredibuild/creator-projects-incredibuild-building.qdoc +++ b/doc/qtcreator/src/incredibuild/creator-projects-incredibuild-building.qdoc @@ -10,9 +10,8 @@ \brief Build and clean steps for Incredibuild. - Specify build settings for the selected \l{Kits}{kit} in - \uicontrol Projects > \uicontrol {Build & Run} > \uicontrol Build > - \uicontrol {Build Settings}. + To specify build settings for the selected \l{Kits}{kit}, go to + \uicontrol Projects > \uicontrol {Build Settings}. You can specify build steps and clean steps for IncrediBuild. diff --git a/doc/qtcreator/src/ios/creator-ios-dev.qdoc b/doc/qtcreator/src/ios/creator-ios-dev.qdoc index ec14e75555c..b7cbb5294f3 100644 --- a/doc/qtcreator/src/ios/creator-ios-dev.qdoc +++ b/doc/qtcreator/src/ios/creator-ios-dev.qdoc @@ -101,6 +101,8 @@ \image {qtcreator-ios-add-kit.png} {iOS kit selected for a project in Build & Run} + \li Go to \uicontrol {Build Settings}. + \li In \uicontrol {iOS Settings}, select the development team to use for signing and provisioning applications. You must configure development teams and provisioning profiles in Xcode using an @@ -115,7 +117,7 @@ \endlist - \li Select \uicontrol Run to specify run settings. + \li Go to \uicontrol {Run Settings} to specify run settings. Usually, you can use the default settings. @@ -163,8 +165,8 @@ {Simulator}, which is installed as part of Xcode. Each Xcode version simulates a predefined set of hardware devices and software versions. - You can change the simulated hardware and software version in the run - settings for the project. Go to \uicontrol Projects > \uicontrol Run, and then select + To change the simulated hardware and software version, go to + \uicontrol Projects > uicontrol {Run Settings} and select the device to simulate in the \uicontrol {Device type} field. \image {qtcreator-ios-simulator-deploy.png} {Deployment settings for iOS Simulator} diff --git a/doc/qtcreator/src/linux-mobile/b2qtdev.qdoc b/doc/qtcreator/src/linux-mobile/b2qtdev.qdoc index 48a555d0d56..54f7271212b 100644 --- a/doc/qtcreator/src/linux-mobile/b2qtdev.qdoc +++ b/doc/qtcreator/src/linux-mobile/b2qtdev.qdoc @@ -78,11 +78,11 @@ \li Go to \uicontrol Projects > \uicontrol {Build & Run} to activate the kit that you specified above. \endlist - \li Select \uicontrol Run to specify run settings. Usually, you can use - the default settings. + \li Go to \uicontrol {Run Settings} to specify run settings. + Usually, you can use the default settings. - When you run the project, \QC deploys the application as - specified by the deploy steps. By default, \QC copies the + \li When you run the project, \QC deploys the application as specified + in \uicontrol {Deploy Settings}. By default, \QC copies the application files to the device. \endlist diff --git a/doc/qtcreator/src/linux-mobile/creator-deployment-b2qt.qdoc b/doc/qtcreator/src/linux-mobile/creator-deployment-b2qt.qdoc index f99d0e43b3e..d7b41f8d35d 100644 --- a/doc/qtcreator/src/linux-mobile/creator-deployment-b2qt.qdoc +++ b/doc/qtcreator/src/linux-mobile/creator-deployment-b2qt.qdoc @@ -13,9 +13,9 @@ Specify settings for deploying applications to \l{\B2Q: Documentation} {\B2Q} devices in the project configuration file and in \uicontrol Projects - > \uicontrol {Run Settings} > \uicontrol Deployment. + > \uicontrol {Deploy Settings}. - \image {qtcreator-boot2qt-deployment-steps.png} {Boot to Qt deployment steps in Run Settings} + \image {qtcreator-boot2qt-deployment-steps.png} {Boot to Qt deployment settings} The deployment process is described in more detail in \l{Remote Linux Deploy Configuration}. diff --git a/doc/qtcreator/src/linux-mobile/creator-deployment-embedded-linux.qdoc b/doc/qtcreator/src/linux-mobile/creator-deployment-embedded-linux.qdoc index a661721d295..e71897089e0 100644 --- a/doc/qtcreator/src/linux-mobile/creator-deployment-embedded-linux.qdoc +++ b/doc/qtcreator/src/linux-mobile/creator-deployment-embedded-linux.qdoc @@ -20,7 +20,7 @@ Specify settings for deploying applications to generic remote Linux devices in the project configuration file and in \uicontrol Projects > - \uicontrol {Run Settings} > \uicontrol Deployment. + \uicontrol {Deploy Settings}. \image {qtcreator-embedded-linux-deployment-details.png} {Deployment to remote Linux devices} diff --git a/doc/qtcreator/src/linux-mobile/creator-developing-vxworks.qdoc b/doc/qtcreator/src/linux-mobile/creator-developing-vxworks.qdoc index a179b466907..5dc3d5cc670 100644 --- a/doc/qtcreator/src/linux-mobile/creator-developing-vxworks.qdoc +++ b/doc/qtcreator/src/linux-mobile/creator-developing-vxworks.qdoc @@ -85,9 +85,8 @@ \list 1 \li Activate a VxWorks kit for the project. - \li Go to \uicontrol Projects > \uicontrol Run. - \li In \uicontrol {Run Settings}, set \uicontrol Priority and - \uicontrol {Stack size}. + \li Go to \uicontrol Projects > \uicontrol {Run Settings}. + \li Set \uicontrol Priority and \uicontrol {Stack size}. \image {qtcreator-run-settings-vxworks.webp} {Run Settings for VxWorks} \li In \uicontrol Environment, set the \c LD_LIBRARY_PATH variable to the location of the libraries on the SD card. If the path to the diff --git a/doc/qtcreator/src/linux-mobile/creator-how-to-build-on-remote-devices.qdocinc b/doc/qtcreator/src/linux-mobile/creator-how-to-build-on-remote-devices.qdocinc index affd01fc93d..20c4ca12f43 100644 --- a/doc/qtcreator/src/linux-mobile/creator-how-to-build-on-remote-devices.qdocinc +++ b/doc/qtcreator/src/linux-mobile/creator-how-to-build-on-remote-devices.qdocinc @@ -51,11 +51,11 @@ \endlist - \li Select \uicontrol Run to specify run settings. Usually, you can use - the default settings. + \li Go to \uicontrol {Run Settings} to specify run settings. + Usually, you can use the default settings. - When you run the project, \QC deploys the application as specified by - the deploy steps. + \li When you run the project, \QC deploys the application as specified by + \uicontrol {Deploy Settings}. \endlist //! [build on remote devices] diff --git a/doc/qtcreator/src/linux-mobile/creator-projects-how-to-run-generic-linux.qdoc b/doc/qtcreator/src/linux-mobile/creator-projects-how-to-run-generic-linux.qdoc index 7fd9fa35764..d748f543730 100644 --- a/doc/qtcreator/src/linux-mobile/creator-projects-how-to-run-generic-linux.qdoc +++ b/doc/qtcreator/src/linux-mobile/creator-projects-how-to-run-generic-linux.qdoc @@ -26,9 +26,9 @@ displayed on the device. Command-line output is visible in the \QC \uicontrol {Application Output} view. - In the \uicontrol {Projects} mode, select the remote Linux kit and then - select \uicontrol {Run} to view the settings for deploying the application - to the connected device. For more information, see + In the \uicontrol {Projects} mode, select the remote Linux kit and go to + \uicontrol {Deploy Settings} to view the settings for deploying the + application to the connected device. For more information, see \l{Remote Linux Run Settings}. Debugging works transparently if GDB server is installed on the device and diff --git a/doc/qtcreator/src/linux-mobile/creator-projects-settings-run-b2qt.qdoc b/doc/qtcreator/src/linux-mobile/creator-projects-settings-run-b2qt.qdoc index ff05251fe30..f182b1fb5d4 100644 --- a/doc/qtcreator/src/linux-mobile/creator-projects-settings-run-b2qt.qdoc +++ b/doc/qtcreator/src/linux-mobile/creator-projects-settings-run-b2qt.qdoc @@ -11,9 +11,8 @@ \brief Settings for running applications on \B2Q devices. - Specify settings for running applications on the \l {Kits}{Run device} that - you select for a kit in \uicontrol Projects > \uicontrol {Build & Run} > - \uicontrol Run > \uicontrol {Run Settings}. + To specify settings for running applications on the \l {Kits}{Run device} that + you select for a kit, go to \uicontrol Projects > \uicontrol {Run Settings}. To run and debug an application on a \l{\B2Q: Documentation}{\B2Q} device (commercial only), create connections from the development host to the device diff --git a/doc/qtcreator/src/linux-mobile/creator-projects-settings-run-linux.qdoc b/doc/qtcreator/src/linux-mobile/creator-projects-settings-run-linux.qdoc index 89d0c260040..7fab23d626d 100644 --- a/doc/qtcreator/src/linux-mobile/creator-projects-settings-run-linux.qdoc +++ b/doc/qtcreator/src/linux-mobile/creator-projects-settings-run-linux.qdoc @@ -11,9 +11,8 @@ \brief Settings for running applications on Linux-based devices. - Specify run settings for the selected \l{Kits}{kit} in - \uicontrol Projects > \uicontrol {Build & Run} > \uicontrol Run > - \uicontrol {Run Settings}. + To specify run settings for the selected \l{Kits}{kit}, go to + \uicontrol Projects > \uicontrol {Run Settings}. To run and debug an application on a Linux-based device, you must create connections from the development host to the device and add the device diff --git a/doc/qtcreator/src/linux-mobile/linuxdev.qdoc b/doc/qtcreator/src/linux-mobile/linuxdev.qdoc index 0483c2f7858..daa8085c4ed 100644 --- a/doc/qtcreator/src/linux-mobile/linuxdev.qdoc +++ b/doc/qtcreator/src/linux-mobile/linuxdev.qdoc @@ -108,11 +108,11 @@ \endlist - \li Select \uicontrol Run to specify run settings. Usually, you can use - the default settings. + \li Go to \uicontrol {Run Settings} to specify run settings. + Usually, you can use the default settings. - When you run the project, \QC deploys the application as specified by - the deploy steps. + \li When you run the project, \QC deploys the application as specified by + \uicontrol {Deploy Settings}. \endlist diff --git a/doc/qtcreator/src/mcu/creator-mcu-dev.qdoc b/doc/qtcreator/src/mcu/creator-mcu-dev.qdoc index 232c2ddc6c2..fbea7920bb1 100644 --- a/doc/qtcreator/src/mcu/creator-mcu-dev.qdoc +++ b/doc/qtcreator/src/mcu/creator-mcu-dev.qdoc @@ -268,7 +268,7 @@ \li Select \uicontrol Projects > \uicontrol {Build & Run}, and then select the kit for building the application and running it on the MCU board specified in the kit. - \li Select \uicontrol Run to specify run settings. + \li Go to \uicontrol {Run Settings} to specify run settings. Usually, you can use the default settings. \endlist diff --git a/doc/qtcreator/src/meson/creator-projects-meson-building.qdoc b/doc/qtcreator/src/meson/creator-projects-meson-building.qdoc index 37edd13b7df..479c72e2574 100644 --- a/doc/qtcreator/src/meson/creator-projects-meson-building.qdoc +++ b/doc/qtcreator/src/meson/creator-projects-meson-building.qdoc @@ -10,9 +10,8 @@ \brief Settings for building applications with Meson. - Specify build settings for the selected \l{Kits}{kit} in - \uicontrol Projects > \uicontrol {Build & Run} > \uicontrol Build > - \uicontrol {Build Settings}. + To specify build settings for the selected \l{Kits}{kit}, go to + \uicontrol Projects > \uicontrol {Build Settings}. \image {qtcreator-meson-build-settings.png} {Meson build settings} diff --git a/doc/qtcreator/src/projects/creator-only/creator-build-settings-qmake.qdoc b/doc/qtcreator/src/projects/creator-only/creator-build-settings-qmake.qdoc index e7a5b5c73ab..49ed7ac2718 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-build-settings-qmake.qdoc +++ b/doc/qtcreator/src/projects/creator-only/creator-build-settings-qmake.qdoc @@ -11,9 +11,8 @@ \brief Settings for building applications with qmake. - Specify build settings for the selected \l{Kits}{kit} - in \uicontrol Projects > \uicontrol {Build & Run} - > \uicontrol Build > \uicontrol {Build Settings}. + To specify build settings for the selected \l{Kits}{kit}, go to + \uicontrol Projects > \uicontrol {Build Settings}. \image {qtcreator-projectpane.webp} {qmake build settings} diff --git a/doc/qtcreator/src/projects/creator-only/creator-custom-output-parser.qdoc b/doc/qtcreator/src/projects/creator-only/creator-custom-output-parser.qdoc index 1eaa1f06bce..be2489240db 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-custom-output-parser.qdoc +++ b/doc/qtcreator/src/projects/creator-only/creator-custom-output-parser.qdoc @@ -73,11 +73,11 @@ You can activate custom output parsers in the \uicontrol {Custom Output Parsers} section of - the \uicontrol Build and \uicontrol Run settings, + \uicontrol {Build Settings} and \uicontrol {Run Settings}, as well as in the custom compiler settings. - To activate a custom output parser in the \uicontrol Build or - \uicontrol Run settings of a project: + To activate a custom output parser, go to \uicontrol Projects > + \uicontrol {Build Settings} or \uicontrol {Run Settings}: \list 1 \li In the \uicontrol {Custom Output Parsers} section, select diff --git a/doc/qtcreator/src/projects/creator-only/creator-how-to-select-build-systems.qdoc b/doc/qtcreator/src/projects/creator-only/creator-how-to-select-build-systems.qdoc index 2458cb4db72..12747ec3964 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-how-to-select-build-systems.qdoc +++ b/doc/qtcreator/src/projects/creator-only/creator-how-to-select-build-systems.qdoc @@ -35,7 +35,7 @@ \section1 Migrate to another build system To export a project to some other build system, such as Microsoft Visual - Studio, select \uicontrol Build > \uicontrol {Run Generator}, and select + Studio, go to \uicontrol Build > \uicontrol {Run Generator} and select a generator in the list. \QC generates the build files, such as .vcxproj, in the project's build directory. diff --git a/doc/qtcreator/src/projects/creator-only/creator-projects-building.qdoc b/doc/qtcreator/src/projects/creator-only/creator-projects-building.qdoc index a6bb865376a..e5b48c0a75a 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-projects-building.qdoc +++ b/doc/qtcreator/src/projects/creator-only/creator-projects-building.qdoc @@ -34,12 +34,12 @@ \list 1 \li Select the \uicontrol {Build and Run Kit Selector} icon or go to - \uicontrol Build > \uicontrol {Open Build and Run Kit Selector} to + \uicontrol Build > \uicontrol {Open Build and Run Kit Selector} and select the build and run kit or an \l{Manage AVDs}{Android device}. \image {qtcreator-kit-selector.webp} {Kit selector} - \li Select \uicontrol Build > \uicontrol {Build Project} or press + \li Go to \uicontrol Build, and select \uicontrol {Build Project} or \key {Ctrl+B}. Or, select \inlineimage {icons/run_small.png} {Run} (\uicontrol Run) @@ -57,7 +57,7 @@ \list \li Select \inlineimage {icons/cancel-build.png} {Cancel Build}. \li Select \key {Alt+Backspace}. - \li Go to \uicontrol Build > \uicontrol {Cancel Build}. + \li Go to \uicontrol Build, and select \uicontrol {Cancel Build}. \endlist If you selected a build command and now would also like to run the @@ -66,7 +66,8 @@ \section1 Build projects in several configurations - Go to \uicontrol Build to build, rebuild, and clean projects. + Go to \uicontrol Build, and select menu commands to build, rebuild, and clean + projects. To build the current project in all its configurations, that is, for all build configurations in all enabled kits, select @@ -82,7 +83,7 @@ \section1 Build files or subprojects To quickly check the compile output for changes that you made in one file or - subproject, use the \uicontrol Build menu commands to build it. The available + subproject, select \uicontrol Build menu commands to build it. The available build menu commands depend on the build system you selected for the project: CMake, qmake, or Qbs. @@ -93,7 +94,8 @@ \section1 Remove build artifacts - To remove all build artifacts, go to \uicontrol Build > \uicontrol {Clean}. + To remove all build artifacts, go to \uicontrol Build and select + \uicontrol {Clean}. To clean the build directory and then build the project, select \uicontrol {Rebuild}. diff --git a/doc/qtcreator/src/projects/creator-only/creator-projects-custom-wizards-json.qdocinc b/doc/qtcreator/src/projects/creator-only/creator-projects-custom-wizards-json.qdocinc index 96cd3ae9060..60be82843ef 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-projects-custom-wizards-json.qdocinc +++ b/doc/qtcreator/src/projects/creator-only/creator-projects-custom-wizards-json.qdocinc @@ -8,7 +8,7 @@ If you are a \QC developer or build your own \QC version for delivery to others, you can integrate the wizard into \QC. To deliver the wizard as part of the \QC build, place the wizard files in the shared directory in - the \QC sources. Then select \uicontrol Build > \uicontrol {Run CMake}. + the \QC sources. Then go to \uicontrol Build, and select \uicontrol {Run CMake}. This ensures that the new files you added for your wizard are actually copied from the \QC source directory into the \QC build directory as part of the next \QC build. diff --git a/doc/qtcreator/src/projects/creator-only/creator-projects-generic.qdoc b/doc/qtcreator/src/projects/creator-only/creator-projects-generic.qdoc index 2a57fcfaac6..58428a6212d 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-projects-generic.qdoc +++ b/doc/qtcreator/src/projects/creator-only/creator-projects-generic.qdoc @@ -131,7 +131,7 @@ \section1 Create a run configuration \QC cannot automatically determine which executable to run, so you must - set the executable in \uicontrol {Projects} > \uicontrol {Run} > + set the executable in \uicontrol {Projects} > \uicontrol {Run Settings} > \uicontrol Add > \uicontrol {Custom Executable}. \sa {Specify a custom executable to run}, {Use project wizards}, diff --git a/doc/qtcreator/src/projects/creator-only/creator-projects-opening.qdoc b/doc/qtcreator/src/projects/creator-only/creator-projects-opening.qdoc index 29798bda454..f19a5f95e84 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-projects-opening.qdoc +++ b/doc/qtcreator/src/projects/creator-only/creator-projects-opening.qdoc @@ -43,7 +43,7 @@ \uicontrol {Open File} dialog, where you can select a project file. \li On Windows and Linux, in all modes except the \uicontrol Help mode, select \key Ctrl+Shift+O to open the \uicontrol {Load Project} dialog. - \li In the \uicontrol Welcome mode, \uicontrol Projects tab, press + \li In the \uicontrol Welcome mode, \uicontrol Projects tab, select \key Ctrl+Shift+number (\key Cmd+Shift+number on \macos), where the number is the number of a project in the list of recently opened projects (3). @@ -68,14 +68,13 @@ To add a build configuration to the workspace: \list 1 - \li Go to \uicontrol Projects > \uicontrol {Build & Run} > - \uicontrol Build. + \li Go to \uicontrol Projects > \uicontrol {Build Settings}. \li Select \uicontrol Add > \uicontrol Build. \li Specify build settings. \endlist To specify run settings for the workspace, go to \uicontrol Projects > - \uicontrol {Build & Run} > \uicontrol Run. + \uicontrol {Run Settings}. \section1 Re-configure projects diff --git a/doc/qtcreator/src/projects/creator-only/creator-projects-settings-build-qbs.qdoc b/doc/qtcreator/src/projects/creator-only/creator-projects-settings-build-qbs.qdoc index 057ceeb5b45..ea446f84a71 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-projects-settings-build-qbs.qdoc +++ b/doc/qtcreator/src/projects/creator-only/creator-projects-settings-build-qbs.qdoc @@ -11,9 +11,8 @@ \brief Settings for building applications with Qbs. - Specify build settings for the selected \l{Kits}{kit} in - \uicontrol Projects > \uicontrol {Build & Run} > \uicontrol Build > - \uicontrol {Build Settings}. + To specify build settings for the selected \l{Kits}{kit}, go to + \uicontrol Projects > \uicontrol {Build Settings}. \image {qtcreator-build-settings-qbs.png} {Qbs build settings} diff --git a/doc/qtcreator/src/projects/creator-only/creator-projects-settings-build.qdoc b/doc/qtcreator/src/projects/creator-only/creator-projects-settings-build.qdoc index abb09017e83..02861884833 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-projects-settings-build.qdoc +++ b/doc/qtcreator/src/projects/creator-only/creator-projects-settings-build.qdoc @@ -16,9 +16,8 @@ \title Configure projects for building - Specify build settings for the selected \l{Kits}{kit} in - \uicontrol Projects > \uicontrol {Build & Run} > \uicontrol Build > - \uicontrol {Build Settings}. + To specify build settings for the selected \l{Kits}{kit}, go to + \uicontrol Projects > \uicontrol {Build Settings}. \image {qtcreator-build-configurations.png} {Build Settings} diff --git a/doc/qtcreator/src/projects/creator-only/creator-projects-settings-building-and-running.qdoc b/doc/qtcreator/src/projects/creator-only/creator-projects-settings-building-and-running.qdoc new file mode 100644 index 00000000000..f463f746767 --- /dev/null +++ b/doc/qtcreator/src/projects/creator-only/creator-projects-settings-building-and-running.qdoc @@ -0,0 +1,74 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +// ********************************************************************** +// NOTE: the sections are not ordered by their logical order to avoid +// reshuffling the file each time the index order changes (i.e., often). +// Run the fixnavi.pl script to adjust the links to the index order. +// ********************************************************************** + +/*! + \page creator-building-running-settings.html + \previouspage creator-how-tos.html + + \ingroup creator-how-to-projects-configure + + \title Specify build and run settings + + To override the build and run settings for the current project: + + \list 1 + + \li Go to \uicontrol Projects > \uicontrol {Project Settings} > + \uicontrol {Building and Running}. + + \image {qtcreator-projects-building-and-running-settings.webp} {Building and Running settings in the Projects mode} + + \li Clear \uicontrol {Use global settings}. + + \li Specify build and run settings for the project. + + \endlist + + Your choices override some of the values you set in \preferences > + \uicontrol {Build & Run} > \uicontrol General. + + \table + \header + \li Setting + \li Value + \row + \li Add linker library search paths to run environment + \li Ensures that library paths used during linking are also available + when running the application. + \row + \li Create suitable run configurations automatically + \li Automatically generates run configurations based on the current + build settings. + \row + \li Start build processes with low priority + \li Launches build processes with a lower priority. + \row + \li Warn against build directories with spaces or non-ASCII characters + \li Displays warnings if the build directory contains spaces or + non-standard characters that may cause issues. + \row + \li Default for "Run in terminal" + \li Specifies whether applications should run in a terminal by default. + \row + \li Keep run configurations in sync + \li Specifies whether adding, removing, or editing a run configuration + in one build configuration should update other build configurations + accordingly. + \row + \li Time to wait before force-stopping applications + \li Specifies the number of seconds to wait between a \e {soft kill} and a + \e {hard kill} of a running application. + \endtable + + + Select \uicontrol {Restore Global} to revert to the global settings. + + \sa {Build and Run} + +*/ diff --git a/doc/qtcreator/src/projects/creator-only/creator-projects-settings-overview.qdoc b/doc/qtcreator/src/projects/creator-only/creator-projects-settings-overview.qdoc index 82788c39ec0..b941c513fe2 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-projects-settings-overview.qdoc +++ b/doc/qtcreator/src/projects/creator-only/creator-projects-settings-overview.qdoc @@ -27,8 +27,8 @@ \image {qtcreator-projects-kits.webp} {Sidebar in the Projects mode} - To specify build or run settings for a kit, select \uicontrol Build or - \uicontrol Run below the kit name. + To specify build or run settings for a kit, select the kit and go to + \uicontrol {Build Settings} or \uicontrol {Run Settings}. \section1 Specifying Build Settings @@ -84,6 +84,7 @@ the project: \list + \li \l{Specify building and running settings}{Building and Running} \li \l{Specify Clang tools settings}{Clang Tools} \li \l{Specify clangd settings}{Clangd} \li \l{Override CMake settings for a project}{CMake} @@ -143,7 +144,8 @@ \l{Add debuggers}{Debugging}, and \l{Add Qt versions}{Qt version}, as well as steps for building, deploying, and running applications. - To copy the build, deploy, and run steps from another kit, select + To copy the build, deploy, and run steps from another kit, go to + \uicontrol Projects > \uicontrol {Build & Run}, right-click a kit, and select \uicontrol {Copy Steps from Another Kit} in the context menu. To deactivate a kit, select \uicontrol {Disable Kit for Project} in the diff --git a/doc/qtcreator/src/projects/creator-only/creator-projects-settings-run-analyze.qdoc b/doc/qtcreator/src/projects/creator-only/creator-projects-settings-run-analyze.qdoc index 95045701ff3..16153d87ef1 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-projects-settings-run-analyze.qdoc +++ b/doc/qtcreator/src/projects/creator-only/creator-projects-settings-run-analyze.qdoc @@ -9,9 +9,8 @@ \title Specify Valgrind settings for a project - Specify settings for running applications on the \l {Kits}{Run device} that - you select for a kit in \uicontrol Projects > \uicontrol {Build & Run} > - \uicontrol Run > \uicontrol {Run Settings}. + To specify settings for running applications on the \l {Kits}{Run device} that + you select for a kit, go to \uicontrol Projects > \uicontrol {Run Settings}. With \l{Valgrind's Tool Suite}, you can detect memory leaks and profile function execution. diff --git a/doc/qtcreator/src/projects/creator-only/creator-projects-settings-run-debug.qdoc b/doc/qtcreator/src/projects/creator-only/creator-projects-settings-run-debug.qdoc index 4e6be268f30..93168872400 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-projects-settings-run-debug.qdoc +++ b/doc/qtcreator/src/projects/creator-only/creator-projects-settings-run-debug.qdoc @@ -10,9 +10,8 @@ \title Enable debugging - Specify settings for running applications on the \l {Kits}{Run device} that - you select for a kit in \uicontrol Projects > \uicontrol {Build & Run} > - \uicontrol Run > \uicontrol {Run Settings}. + To specify settings for running applications on the \l {Kits}{Run device} that + you select for a kit, go to \uicontrol Projects > \uicontrol {Run Settings}. \image {qtquick-debugger-settings.webp} {Debugger Settings section in Run Settings} diff --git a/doc/qtcreator/src/projects/creator-only/creator-projects-settings-run-desktop.qdoc b/doc/qtcreator/src/projects/creator-only/creator-projects-settings-run-desktop.qdoc index ad67feede47..e251d027bd6 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-projects-settings-run-desktop.qdoc +++ b/doc/qtcreator/src/projects/creator-only/creator-projects-settings-run-desktop.qdoc @@ -11,9 +11,8 @@ \brief Settings for running applications on desktop device types. - Specify settings for running applications on the \l {Kits}{Run device} that - you select for a kit in \uicontrol Projects > \uicontrol {Build & Run} > - \uicontrol Run > \uicontrol {Run Settings}. + To specify settings for running applications on the \l {Kits}{Run device} that + you select for a kit, go to \uicontrol Projects > \uicontrol {Run Settings}. \image {qtcreator-settings-run-desktop.webp} {Run Settings for desktop devices} diff --git a/doc/qtcreator/src/projects/creator-only/creator-projects-settings-run.qdoc b/doc/qtcreator/src/projects/creator-only/creator-projects-settings-run.qdoc index d5e92f7f2e5..803bee84c93 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-projects-settings-run.qdoc +++ b/doc/qtcreator/src/projects/creator-only/creator-projects-settings-run.qdoc @@ -16,9 +16,8 @@ \title Configure projects for running - Specify settings for running applications on the \l {Kits}{Run device} that - you select for a kit in \uicontrol Projects > \uicontrol {Build & Run} > - \uicontrol Run > \uicontrol {Run Settings}. + To specify settings for running applications on the \l {Kits}{Run device} that + you select for a kit, go to \uicontrol Projects > \uicontrol {Run Settings}. \image {qtcreator-settings-run-desktop.webp} {Run Settings in the Projects mode} diff --git a/doc/qtcreator/src/projects/creator-projects-running.qdoc b/doc/qtcreator/src/projects/creator-projects-running.qdoc index 22a62af3ced..d283bdaffbd 100644 --- a/doc/qtcreator/src/projects/creator-projects-running.qdoc +++ b/doc/qtcreator/src/projects/creator-projects-running.qdoc @@ -55,8 +55,8 @@ \section1 Run without deploying - To run executable files without deploying them first, select \uicontrol Build > - \uicontrol {Run Without Deployment}. + To run executable files without deploying them first, go to \uicontrol Build + and select \uicontrol {Run Without Deployment}. To make this the default option, go to \preferences > \uicontrol {Build & Run} > \uicontrol General, and clear diff --git a/doc/qtcreator/src/python/creator-python-run-settings.qdoc b/doc/qtcreator/src/python/creator-python-run-settings.qdoc index 658e5427945..6cab1e5688a 100644 --- a/doc/qtcreator/src/python/creator-python-run-settings.qdoc +++ b/doc/qtcreator/src/python/creator-python-run-settings.qdoc @@ -33,9 +33,8 @@ \brief Settings for running Qt for Python applications. - Specify settings for running applications on the \l {Kits}{Run device} that - you select for a kit in \uicontrol Projects > \uicontrol {Build & Run} > - \uicontrol Run > \uicontrol {Run Settings}. + To specify settings for running applications on the \l {Kits}{Run device} that + you select for a kit, go to \uicontrol Projects > \uicontrol {Run Settings}. \image {qtcreator-python-run-settings.webp } {Python run settings} diff --git a/doc/qtcreator/src/qnx/creator-deployment-qnx.qdoc b/doc/qtcreator/src/qnx/creator-deployment-qnx.qdoc index e1ee98096eb..7e0282af97d 100644 --- a/doc/qtcreator/src/qnx/creator-deployment-qnx.qdoc +++ b/doc/qtcreator/src/qnx/creator-deployment-qnx.qdoc @@ -20,7 +20,7 @@ Specify settings for deploying applications to QNX Neutrino devices in the project configuration file and in \uicontrol Projects - > \uicontrol {Run Settings} > \uicontrol Deployment. + > \uicontrol {Deploy Settings}. \image {qtcreator-qnx-deployment.png} {Deploy to device} diff --git a/doc/qtcreator/src/qnx/creator-projects-settings-run-qnx.qdoc b/doc/qtcreator/src/qnx/creator-projects-settings-run-qnx.qdoc index edd78c0a193..ed288b55d41 100644 --- a/doc/qtcreator/src/qnx/creator-projects-settings-run-qnx.qdoc +++ b/doc/qtcreator/src/qnx/creator-projects-settings-run-qnx.qdoc @@ -11,9 +11,8 @@ \brief Settings for running applications on Linux-based devices. - Specify settings for running applications on the \l {Kits}{Run device} that - you select for a kit in \uicontrol Projects > \uicontrol {Build & Run} > - \uicontrol Run > \uicontrol {Run Settings}. + To specify settings for running applications on the \l {Kits}{Run device} that + you select for a kit, go to \uicontrol Projects > \uicontrol {Run Settings}. To run and debug an application on a QNX device, select \uicontrol {Manage device configurations} to create a diff --git a/doc/qtcreator/src/qtquick/creator-only/creator-mobile-app-tutorial.qdoc b/doc/qtcreator/src/qtquick/creator-only/creator-mobile-app-tutorial.qdoc index 36c1f16647c..d29eaa8d21a 100644 --- a/doc/qtcreator/src/qtquick/creator-only/creator-mobile-app-tutorial.qdoc +++ b/doc/qtcreator/src/qtquick/creator-only/creator-mobile-app-tutorial.qdoc @@ -179,7 +179,7 @@ \skipto target_link_libraries(appaccelbubble \printuntil Qt6 - After adding the dependencies, select \uicontrol Build > + After adding the dependencies, go to \uicontrol Build and select \uicontrol {Run CMake} to apply configuration changes. For more information about the CMakeLists.txt file, see diff --git a/doc/qtcreator/src/qtquick/creator-only/creator-projects-settings-run-qtquick.qdoc b/doc/qtcreator/src/qtquick/creator-only/creator-projects-settings-run-qtquick.qdoc index fa3f25c3688..b5ec8d5f326 100644 --- a/doc/qtcreator/src/qtquick/creator-only/creator-projects-settings-run-qtquick.qdoc +++ b/doc/qtcreator/src/qtquick/creator-only/creator-projects-settings-run-qtquick.qdoc @@ -11,9 +11,8 @@ \brief Settings for running Qt Quick UI Prototype projects (.qmlproject). - Specify settings for running applications on the \l {Kits}{Run device} that - you select for a kit in \uicontrol Projects > \uicontrol {Build & Run} > - \uicontrol Run > \uicontrol {Run Settings}. + To specify settings for running applications on the \l {Kits}{Run device} that + you select for a kit, go to \uicontrol Projects > \uicontrol {Run Settings}. \note Select the \uicontrol Desktop device type for the \l{Kits}{Run device} in the kit. diff --git a/doc/qtcreator/src/qtquick/creator-only/qtquick-live-preview-desktop.qdoc b/doc/qtcreator/src/qtquick/creator-only/qtquick-live-preview-desktop.qdoc index f223e608e81..10aaf127a31 100644 --- a/doc/qtcreator/src/qtquick/creator-only/qtquick-live-preview-desktop.qdoc +++ b/doc/qtcreator/src/qtquick/creator-only/qtquick-live-preview-desktop.qdoc @@ -15,7 +15,7 @@ \li Select \inlineimage {icons/live-preview.png} {Live Preview} (\uicontrol {Live Preview}) on the \l{Edit Mode}{editor} toolbar. \image {qtcreator-live-preview.webp} {Application running on top of the editor view} - \li Go to \uicontrol Build > \uicontrol {QML Preview}. + \li Go to \uicontrol Build, and select \uicontrol {QML Preview}. \endlist \sa {Design UIs}{How To: Design UIs}, {UI Design} diff --git a/doc/qtcreator/src/qtquick/qtquick-converting-ui-projects-to-applications.qdocinc b/doc/qtcreator/src/qtquick/qtquick-converting-ui-projects-to-applications.qdocinc index b7f129f7872..918075b9cff 100644 --- a/doc/qtcreator/src/qtquick/qtquick-converting-ui-projects-to-applications.qdocinc +++ b/doc/qtcreator/src/qtquick/qtquick-converting-ui-projects-to-applications.qdocinc @@ -95,8 +95,8 @@ QML_IMPORT_PATH = qml/imports \endcode Where \c {qml/imports} is the import path. - \li Select \uicontrol Build > \uicontrol {Run qmake} to apply the - \c RESOURCES option to the build configuration. + \li Go to \uicontrol Build, and select \uicontrol {Run qmake} to + apply the \c RESOURCES option to the build configuration. \li Open the \e {main.cpp} file and replace the QQmlApplicationEngine object with a QQuickView object: \quotefromfile progressbar/main.cpp @@ -105,8 +105,8 @@ Where \c {qrc:/qml/imports} is the import path and \c {qrc:/qml/ProgressBar.ui.qml} is the path to and the name of the main QML file in the Qt Quick UI project. - \li Select \uicontrol Build > \uicontrol Run to build and run your - project. + \li Go to \uicontrol Build, and select \uicontrol Run to build and + run your project. \note If you get error messages related to modules, perfom the steps described in \l{Adding Qt Quick Studio Components to Qt Installations}. diff --git a/doc/qtcreator/src/qtquick/qtquick-live-preview-devices.qdoc b/doc/qtcreator/src/qtquick/qtquick-live-preview-devices.qdoc index 2ed9ddbf113..131b36ec08a 100644 --- a/doc/qtcreator/src/qtquick/qtquick-live-preview-devices.qdoc +++ b/doc/qtcreator/src/qtquick/qtquick-live-preview-devices.qdoc @@ -48,8 +48,8 @@ \li Select the kit for the device in the bottom toolbar. \image {design-studio-select-kit.webp} {Selecting a kit for a device.} \endif - \li Select \uicontrol Build > \uicontrol {QML Preview} or - select \key {Alt+P}. + \li Go to \uicontrol Build, and select \uicontrol {QML Preview} or + \key {Alt+P}. \endlist \section2 On Android diff --git a/doc/qtcreator/src/qtquick/qtquick-profiler.qdoc b/doc/qtcreator/src/qtquick/qtquick-profiler.qdoc index 501a8906221..d0730efd555 100644 --- a/doc/qtcreator/src/qtquick/qtquick-profiler.qdoc +++ b/doc/qtcreator/src/qtquick/qtquick-profiler.qdoc @@ -93,7 +93,7 @@ To specify custom QML Profiler settings for a particular project: \list 1 - \li Go to \uicontrol Projects > \uicontrol Run. + \li Go to \uicontrol Projects > \uicontrol {Run Settings}. \li In \uicontrol {QML Profiler Settings}, select \uicontrol Custom. \image {qml-profiler-settings.png} {QML Profiler Settings} \endlist diff --git a/doc/qtcreator/src/user-interface/creator-only/creator-ui.qdoc b/doc/qtcreator/src/user-interface/creator-only/creator-ui.qdoc index 9daf960a4ac..1e84d03256d 100644 --- a/doc/qtcreator/src/user-interface/creator-only/creator-ui.qdoc +++ b/doc/qtcreator/src/user-interface/creator-only/creator-ui.qdoc @@ -8,9 +8,17 @@ \title User interface - When you start \QC, it opens to the \uicontrol Welcome mode. + The first time you start \QC, it asks you about your level of experience as a + developer and the platforms you develop applications for. - \image {qtcreator-welcome.webp} {Welcome mode} + \image {qtcreator-personalize-learning.webp} {Personalize Learning dialog} + + \QC sets the contents of the \uicontrol Overview tab based on your answers. + + \image {qtcreator-welcome-overview.webp} {Overview tab in the Welcome mode} + + The following table describes the parts of the UI that are visible in all + \QC modes. \table \header diff --git a/doc/qtcreator/src/webassembly/creator-webassembly.qdoc b/doc/qtcreator/src/webassembly/creator-webassembly.qdoc index aa6f4604586..828d2524a10 100644 --- a/doc/qtcreator/src/webassembly/creator-webassembly.qdoc +++ b/doc/qtcreator/src/webassembly/creator-webassembly.qdoc @@ -63,7 +63,7 @@ \li Open a project for an application you want to run in a web browser. \li Go to \uicontrol Projects > \uicontrol {Build & Run}, and then select the WebAssembly kit as the build and run kit for the project. - \li Select \uicontrol Run to specify run settings. + \li Select \uicontrol {Run Settings} to specify run settings. \li In \uicontrol {Web browser}, select a browser. \image {qtcreator-settings-run-webassembly.png} {Selecting the browser to run in Qt for WebAssembly run settings} \endlist diff --git a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/qtquickapplication/wizard.json b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/qtquickapplication/wizard.json index 9805f991468..733217554ea 100644 --- a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/qtquickapplication/wizard.json +++ b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/qtquickapplication/wizard.json @@ -1,7 +1,7 @@ { "version": 1, "supportedProjectTypes": [ "PythonProject" ], - "id": "F.QtQuickQtForPythonApplicationEmpty", + "id": "E.QtQuickQtForPythonApplicationEmpty", "category": "F.ApplicationPySide", "trDescription": "Creates a Qt Quick application that contains an empty window.", "trDisplayName": "Qt Quick Application - Empty", diff --git a/share/qtcreator/templates/wizards/projects/qtquickapplication/wizard.json b/share/qtcreator/templates/wizards/projects/qtquickapplication/wizard.json index 85c3f2edca3..2b4042b302a 100644 --- a/share/qtcreator/templates/wizards/projects/qtquickapplication/wizard.json +++ b/share/qtcreator/templates/wizards/projects/qtquickapplication/wizard.json @@ -1,7 +1,7 @@ { "version": 1, "supportedProjectTypes": [ "CMakeProjectManager.CMakeProject" ], - "id": "U.QtQuickApplicationEmpty", + "id": "A.QtQuickApplicationEmpty", "category": "D.ApplicationQt", "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.", "trDisplayName": "Qt Quick Application", diff --git a/share/qtcreator/templates/wizards/projects/qtquickapplication_compat/empty/wizard.json b/share/qtcreator/templates/wizards/projects/qtquickapplication_compat/empty/wizard.json index 78af6f2118d..683c6be8c5e 100644 --- a/share/qtcreator/templates/wizards/projects/qtquickapplication_compat/empty/wizard.json +++ b/share/qtcreator/templates/wizards/projects/qtquickapplication_compat/empty/wizard.json @@ -1,7 +1,7 @@ { "version": 1, "supportedProjectTypes": [ "CMakeProjectManager.CMakeProject", "Qbs.QbsProject", "Qt4ProjectManager.Qt4Project" ], - "id": "V.QtQuickApplicationEmptyCompat", + "id": "B.QtQuickApplicationEmptyCompat", "category": "D.ApplicationQt", "trDescription": "Creates a Qt Quick application that contains an empty window.\n\nUse this \"compat\" version if you want to use other build systems than CMake or Qt versions lower than 6.", "trDisplayName": "Qt Quick Application (compat)", diff --git a/share/qtcreator/templates/wizards/qtcreatorplugin/github_workflows_build_cmake.yml b/share/qtcreator/templates/wizards/qtcreatorplugin/github_workflows_build_cmake.yml index f22f6424ec4..7bf7097c002 100644 --- a/share/qtcreator/templates/wizards/qtcreatorplugin/github_workflows_build_cmake.yml +++ b/share/qtcreator/templates/wizards/qtcreatorplugin/github_workflows_build_cmake.yml @@ -28,10 +28,10 @@ jobs: } - { name: "Windows Latest MSVC Arm64", artifact: "Windows-arm64", - os: windows-latest, + os: windows-11-arm, platform: windows_arm64, cc: "cl", cxx: "cl", - environment_script: "C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build/vcvarsamd64_arm64.bat", + environment_script: "C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build/vcvarsarm64.bat", } - { name: "Ubuntu Latest GCC", artifact: "Linux-x64", @@ -41,9 +41,9 @@ jobs: } - { name: "Ubuntu Latest GCC Arm64", artifact: "Linux-arm64", - os: ubuntu-latest, + os: ubuntu-24.04-arm, platform: linux_arm64, - cc: "aarch64-linux-gnu-gcc", cxx: "aarch64-linux-gnu-g++" + cc: "gcc", cxx: "g++" } - { name: "macOS Latest Clang", artifact: "macOS-universal", @@ -73,7 +73,7 @@ jobs: - name: Install system libs shell: cmake -P {0} run: | - if ("${{ matrix.config.platform }}" STREQUAL "linux_x64") + if ("${{ matrix.config.platform }}" MATCHES "linux") execute_process( COMMAND sudo apt-get update ) @@ -84,28 +84,6 @@ jobs: if (NOT result EQUAL 0) message(FATAL_ERROR "Failed to install dependencies") endif() - elseif ("${{ matrix.config.platform }}" STREQUAL "linux_arm64") - execute_process(COMMAND sudo dpkg --add-architecture arm64) - - # TODO: Check to see if the azure.ubuntu added arm64 support - execute_process(COMMAND sudo apt-get install -y bash curl apt-transport-https ca-certificates) - execute_process(COMMAND sudo curl https://raw.githubusercontent.com/vegardit/fast-apt-mirror.sh/v1/fast-apt-mirror.sh -o /usr/local/bin/fast-apt-mirror.sh) - execute_process(COMMAND sudo chmod 755 /usr/local/bin/fast-apt-mirror.sh) - - #execute_process(COMMAND sudo /usr/local/bin/fast-apt-mirror.sh set https://mirrors.ocf.berkeley.edu/ubuntu-ports/) - execute_process(COMMAND sudo /usr/local/bin/fast-apt-mirror.sh set https://mirror.kumi.systems/ubuntu-ports/) - - execute_process( - COMMAND sudo apt-get update - ) - execute_process( - COMMAND - sudo apt-get install -y crossbuild-essential-arm64 libgl1-mesa-dev:arm64 - RESULT_VARIABLE result - ) - if (NOT result EQUAL 0) - message(FATAL_ERROR "Failed to install dependencies") - endif() endif() - name: Download Qt @@ -120,8 +98,8 @@ jobs: set(url_os "windows_x86") set(compiler_id "win64_msvc2022_64") elseif (platform STREQUAL "windows_arm64") - set(url_os "windows_x86") - set(compiler_id "win64_msvc2022_arm64_cross_compiled") + set(url_os "windows_arm64") + set(compiler_id "win64_msvc2022_arm64") elseif (platform STREQUAL "linux_x64") set(url_os "linux_x64") set(compiler_id "linux_gcc_64") @@ -193,12 +171,10 @@ jobs: if ("${{ matrix.config.platform }}" STREQUAL "windows_x64") download_qt(windows_x64 TRUE) elseif ("${{ matrix.config.platform }}" STREQUAL "windows_arm64") - download_qt(windows_x64 FALSE) download_qt(windows_arm64 TRUE) elseif ("${{ matrix.config.platform }}" STREQUAL "linux_x64") download_qt(linux_x64 TRUE) elseif ("${{ matrix.config.platform }}" STREQUAL "linux_arm64") - download_qt(linux_x64 FALSE) download_qt(linux_arm64 TRUE) elseif ("${{ runner.os }}" STREQUAL "macOS") download_qt(mac_x64 TRUE) @@ -206,7 +182,7 @@ jobs: - name: Download Qt Creator id: qt_creator - uses: qt-creator/install-dev-package@v2.0 + uses: qt-creator/install-dev-package@v2.1 with: version: ${{ env.QT_CREATOR_VERSION }} unzip-to: 'qtcreator' @@ -245,14 +221,6 @@ jobs: endif() endforeach() - if ("${{ matrix.config.platform }}" STREQUAL "windows_arm64") - file(TO_CMAKE_PATH "$ENV{GITHUB_WORKSPACE}/qt/windows_x64" qt_host_dir) - set(additional_config "--add-config=-DQT_HOST_PATH=${qt_host_dir}") - elseif ("${{ matrix.config.platform }}" STREQUAL "linux_arm64") - file(TO_CMAKE_PATH "$ENV{GITHUB_WORKSPACE}/qt/linux_x64" qt_host_dir) - set(additional_config "--add-config=-DQT_HOST_PATH=${qt_host_dir}") - endif() - execute_process( COMMAND python -u @@ -319,18 +287,3 @@ jobs: release/${{ env.PLUGIN_NAME}}-${{ env.QT_CREATOR_VERSION }}-Windows-x64.7z draft: false prerelease: false - - - name: Release on Extension Store - continue-on-error: true - uses: qt-creator/deploy-qtc-plugin@v0.3 - with: - api: ${{ secrets.EXTENSION_STORE_API_URL }} - token: ${{ secrets.EXTENSION_STORE_API_TOKEN }} - publish: true - spec: release/${{ env.PLUGIN_NAME }}.json - download-url-linux-arm64: ${{ fromJSON(steps.create_release.outputs.assets)[0].browser_download_url }} - download-url-linux-x64: ${{ fromJSON(steps.create_release.outputs.assets)[1].browser_download_url }} - download-url-macos-arm64: ${{ fromJSON(steps.create_release.outputs.assets)[2].browser_download_url }} - download-url-macos-x64: ${{ fromJSON(steps.create_release.outputs.assets)[2].browser_download_url }} - download-url-win-arm64: ${{ fromJSON(steps.create_release.outputs.assets)[3].browser_download_url }} - download-url-win-x64: ${{ fromJSON(steps.create_release.outputs.assets)[4].browser_download_url }} diff --git a/src/libs/devcontainer/devcontainer.cpp b/src/libs/devcontainer/devcontainer.cpp index 55b30011ff7..28d3288d89e 100644 --- a/src/libs/devcontainer/devcontainer.cpp +++ b/src/libs/devcontainer/devcontainer.cpp @@ -50,8 +50,9 @@ QString InstanceConfig::devContainerId() const { const QByteArray workspace = workspaceFolder.toUrlishString().toUtf8(); const QByteArray config = configFilePath.toUrlishString().toUtf8(); + const QByteArray combined = workspace + config; QString id = QString::fromLatin1( - QCryptographicHash::hash(workspace, QCryptographicHash::Sha256).toHex()); + QCryptographicHash::hash(combined, QCryptographicHash::Sha256).toHex()); return id; } diff --git a/src/libs/devcontainer/devcontainerconfig.cpp b/src/libs/devcontainer/devcontainerconfig.cpp index 0df9c45117b..632bb1530be 100644 --- a/src/libs/devcontainer/devcontainerconfig.cpp +++ b/src/libs/devcontainer/devcontainerconfig.cpp @@ -1259,4 +1259,35 @@ FilePath Config::workspaceFolder(const Config &config) *config.containerConfig)); } +bool Config::isValidConfigPath( + const Utils::FilePath &workspaceFolder, const Utils::FilePath &configPath) +{ + /* + Possible locations: + .devcontainer.json + .devcontainer/devcontainer.json + .devcontainer/<folder>/devcontainer.json (where <folder> is a sub-folder, one level deep) +*/ + // .devcontainer.json + if (configPath.fileName() == ".devcontainer.json" && configPath.parentDir() == workspaceFolder) + return true; + + if (configPath.fileName() != "devcontainer.json") + return false; + + const FilePath parentDir = configPath.parentDir(); + const FilePath secondLvlParentDir = parentDir.parentDir(); + + // .devcontainer/devcontainer.json + if (parentDir.fileName() == ".devcontainer" && secondLvlParentDir == workspaceFolder) + return true; + + // .devcontainer/<folder>/devcontainer.json (where <folder> is a sub-folder, one level deep) + if (secondLvlParentDir.fileName() == ".devcontainer" + && secondLvlParentDir.parentDir() == workspaceFolder) + return true; + + return false; +} + } // namespace DevContainer diff --git a/src/libs/devcontainer/devcontainerconfig.h b/src/libs/devcontainer/devcontainerconfig.h index 23648e680f7..d12a04e724e 100644 --- a/src/libs/devcontainer/devcontainerconfig.h +++ b/src/libs/devcontainer/devcontainerconfig.h @@ -241,6 +241,9 @@ struct DEVCONTAINER_EXPORT Config const QJsonObject &json, JsonStringToString jsonStringToString); static Utils::Result<Config> fromJson( const QByteArray &data, const JsonStringToString &jsonStringToString); + + static bool isValidConfigPath( + const Utils::FilePath &workspaceFolder, const Utils::FilePath &configPath); }; //! Returns a QJsonValue for the specified path. e.g.: customization(config, "qt-creator/device/mount-cmd-bridge") diff --git a/src/libs/languageserverprotocol/lsputils.h b/src/libs/languageserverprotocol/lsputils.h index a5178f813fd..2b9d76775d0 100644 --- a/src/libs/languageserverprotocol/lsputils.h +++ b/src/libs/languageserverprotocol/lsputils.h @@ -113,7 +113,7 @@ public: LanguageClientValue(const T &value) : std::variant<T, std::nullptr_t>(value) { } LanguageClientValue(const QJsonValue &value) { - if (!QTC_GUARD(!value.isUndefined()) || value.isNull()) + if (QTC_UNEXPECTED(value.isUndefined()) || value.isNull()) *this = nullptr; else *this = fromJsonValue<T>(value); diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index cfb59f5ec5c..d424b2a0ee2 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -1419,6 +1419,11 @@ void StringAspect::addToLayoutImpl(Layout &parent) QString StringAspect::expandedValue() const { + return operator()(); +} + +QString StringAspect::operator()() const +{ if (!m_internal.isEmpty()) { if (auto expander = macroExpander()) return expander->expand(m_internal); diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index d9378d9966f..692e16f732c 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -630,8 +630,8 @@ public: StringAspect(AspectContainer *container = nullptr); ~StringAspect() override; - QString operator()() const { return expandedValue(); } - QString expandedValue() const; + QString operator()() const; + [[deprecated("Use operator()() instead")]] QString expandedValue() const; // Hook between UI and StringAspect: using ValueAcceptor = std::function<std::optional<QString>(const QString &, const QString &)>; diff --git a/src/libs/utils/camelcasecursor.cpp b/src/libs/utils/camelcasecursor.cpp index 98a68ee9755..88570cd889b 100644 --- a/src/libs/utils/camelcasecursor.cpp +++ b/src/libs/utils/camelcasecursor.cpp @@ -138,8 +138,7 @@ bool camelCaseLeft(C *cursor, QTextCursor::MoveMode mode) case Input::Upper: break; default: - moveCursor(cursor, QTextCursor::Right, mode); - return true; + return moveCursor(cursor, QTextCursor::Right, mode); } break; case 2: @@ -149,8 +148,7 @@ bool camelCaseLeft(C *cursor, QTextCursor::MoveMode mode) case Input::Lower: break; default: - moveCursor(cursor, QTextCursor::Right, mode); - return true; + return moveCursor(cursor, QTextCursor::Right, mode); } break; case 3: @@ -182,10 +180,7 @@ bool camelCaseLeft(C *cursor, QTextCursor::MoveMode mode) state = 3; break; default: - moveCursor(cursor, QTextCursor::Right, mode); - if (position(cursor) == 0) - return true; - return moveCursor(cursor, QTextCursor::WordLeft, mode); + return moveCursor(cursor, QTextCursor::Right, mode); } } diff --git a/src/libs/utils/fancylineedit.cpp b/src/libs/utils/fancylineedit.cpp index 6f28030d7f0..9477c39e721 100644 --- a/src/libs/utils/fancylineedit.cpp +++ b/src/libs/utils/fancylineedit.cpp @@ -107,7 +107,7 @@ public: FancyIconButton *m_iconbutton[2]; HistoryCompleter *m_historyCompleter = nullptr; QShortcut m_completionShortcut; - FancyLineEdit::ValidationFunction m_validationFunction = {}; + FancyLineEdit::ValidationFunction m_validationFunction = &FancyLineEdit::validateWithValidator; QString m_oldText; QMenu *m_menu[2]; FancyLineEdit::State m_state = FancyLineEdit::Invalid; @@ -141,15 +141,6 @@ FancyLineEditPrivate::FancyLineEditPrivate(FancyLineEdit *parent) , m_placeholderTextColor(QApplication::palette().color(QPalette::PlaceholderText)) , m_spinner(new SpinnerSolution::Spinner(SpinnerSolution::SpinnerSize::Small, m_lineEdit)) { - m_validationFunction = [this](const QString &text) -> Result<> { - if (const QValidator *v = m_lineEdit->validator()) { - QString tmp = text; - int pos = m_lineEdit->cursorPosition(); - if (v->validate(tmp, pos) != QValidator::Acceptable) - return ResultError(QString()); - } - return ResultOk; - }; m_spinner->setVisible(false); m_spinnerDelayTimer.setInterval(200); m_spinnerDelayTimer.setSingleShot(true); @@ -485,6 +476,28 @@ void FancyLineEdit::setValidationFunction(const FancyLineEdit::ValidationFunctio validate(); } +/*! + Returns the default validation function, which synchonously executes the line edit's + validator. + + \sa setValidationFunction() +*/ +FancyLineEdit::ValidationFunction FancyLineEdit::defaultValidationFunction() +{ + return &FancyLineEdit::validateWithValidator; +} + +Result<> FancyLineEdit::validateWithValidator(FancyLineEdit &edit) +{ + if (const QValidator *v = edit.validator()) { + QString tmp = edit.text(); + int pos = edit.cursorPosition(); + if (v->validate(tmp, pos) != QValidator::Acceptable) + return ResultError(QString()); + } + return ResultOk; +} + FancyLineEdit::State FancyLineEdit::state() const { return d->m_state; @@ -619,6 +632,30 @@ void FancyLineEdit::validate() Result<QString> result; + if (const Result<> validates = validationFunction(*this)) + result = t; + else + result = ResultError(validates.error()); + + handleValidationResult(result, t); + } + + if (d->m_validationFunction.index() == 2) { + SimpleSynchronousValidationFunction &validationFunction = std::get<2>(d->m_validationFunction); + if (!validationFunction) + return; + + const QString t = text(); + + if (d->m_isFiltering) { + if (t != d->m_lastFilterText) { + d->m_lastFilterText = t; + emit filterChanged(t); + } + } + + Result<QString> result; + if (const Result<> validates = validationFunction(t)) result = t; else diff --git a/src/libs/utils/fancylineedit.h b/src/libs/utils/fancylineedit.h index ece23a9dc93..b222ae88f37 100644 --- a/src/libs/utils/fancylineedit.h +++ b/src/libs/utils/fancylineedit.h @@ -105,9 +105,13 @@ public: using AsyncValidationResult = Result<QString>; using AsyncValidationFuture = QFuture<AsyncValidationResult>; using AsyncValidationFunction = std::function<AsyncValidationFuture(QString)>; - using SynchronousValidationFunction = std::function<Result<>(const QString &)>; - using ValidationFunction = std::variant<AsyncValidationFunction, - SynchronousValidationFunction>; + using SynchronousValidationFunction = std::function<Result<>(FancyLineEdit &)>; + using SimpleSynchronousValidationFunction = std::function<Result<>(const QString &)>; + using ValidationFunction = std::variant< + AsyncValidationFunction, + SynchronousValidationFunction, + SimpleSynchronousValidationFunction + >; enum State { Invalid, DisplayingPlaceholderText, Valid }; @@ -118,6 +122,7 @@ public: void setValidatePlaceHolder(bool on); void setValidationFunction(const ValidationFunction &fn); + static ValidationFunction defaultValidationFunction(); void validate(); void onEditingFinished(); @@ -151,6 +156,7 @@ private: void handleValidationResult(AsyncValidationResult result, const QString &oldText); + static Result<> validateWithValidator(FancyLineEdit &edit); // Unimplemented, to force the user to make a decision on // whether to use setHistoryCompleter() or setSpecialCompleter(). void setCompleter(QCompleter *); diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index 3ab50dd07d4..d4d70a85cdd 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -213,7 +213,7 @@ private: ++indexInRow; } // Finish the last row. - if (!testOnly) + if (!testOnly && !itemList.isEmpty()) setItemGeometries(/*count=*/indexInRow, /*beforeIndex=*/itemList.size(), lineHeight); return y + lineHeight - rect.y() + bottom; diff --git a/src/libs/utils/plaintextedit/plaintextedit.cpp b/src/libs/utils/plaintextedit/plaintextedit.cpp index 13ec180db4b..48a53ab3c3a 100644 --- a/src/libs/utils/plaintextedit/plaintextedit.cpp +++ b/src/libs/utils/plaintextedit/plaintextedit.cpp @@ -1051,8 +1051,6 @@ void PlainTextEditPrivate::repaintContents(const QRectF &contentsRect) void PlainTextEditPrivate::pageUpDown(QTextCursor::MoveOperation op, QTextCursor::MoveMode moveMode, bool moveCursor) { - - QTextCursor cursor = control->textCursor(); if (moveCursor) { ensureCursorVisible(); @@ -1060,100 +1058,33 @@ void PlainTextEditPrivate::pageUpDown(QTextCursor::MoveOperation op, QTextCursor pageUpDownLastCursorY = control->cursorRect(cursor).top() - verticalOffset(); } - qreal lastY = pageUpDownLastCursorY; + int scrollBarValue = vbar()->value(); + const bool atStart = scrollBarValue == 0; + const bool atEnd = scrollBarValue == vbar()->maximum(); - if (op == QTextCursor::Down) { - QRectF visible = QRectF(viewport()->rect()).translated(-q->contentOffset()); - QTextBlock firstVisibleBlock = q->firstVisibleBlock(); - QTextBlock block = firstVisibleBlock; - QRectF br = q->blockBoundingRect(block); - qreal h = 0; - int atEnd = false; - while (h + br.height() <= visible.bottom()) { - if (!block.next().isValid()) { - atEnd = true; - lastY = visible.bottom(); // set cursor to last line - break; - } - h += br.height(); - block = block.next(); - br = q->blockBoundingRect(block); - } + if (op == QTextCursor::Down && !atEnd) + scrollBarValue += vbar()->pageStep(); + else if (op == QTextCursor::Up && !atStart) + scrollBarValue -= vbar()->pageStep(); - if (!atEnd) { - int line = 0; - qreal diff = visible.bottom() - h; - int lineCount = editorLayout->blockLayout(block)->lineCount(); - while (line < lineCount - 1) { - if (editorLayout->blockLayout(block)->lineAt(line).naturalTextRect().bottom() > diff) { - // the first line that did not completely fit the screen - break; - } - ++line; - } - setTopBlock(block.blockNumber(), line); - } + vbar()->setValue(scrollBarValue); - if (moveCursor) { - // move using movePosition to keep the cursor's x - lastY += verticalOffset(); - bool moved = false; - do { - moved = cursor.movePosition(op, moveMode); - } while (moved && control->cursorRect(cursor).top() < lastY); - } - - } else if (op == QTextCursor::Up) { - - QRectF visible = QRectF(viewport()->rect()).translated(-q->contentOffset()); - visible.translate(0, -visible.height()); // previous page - QTextBlock block = q->firstVisibleBlock(); - qreal h = 0; - while (h >= visible.top()) { - if (!block.previous().isValid()) { - if (control->topBlock == 0 && topLine == 0) { - lastY = 0; // set cursor to first line - } - break; - } - block = block.previous(); - QRectF br = q->blockBoundingRect(block); - h -= br.height(); - } - - int line = 0; - if (block.isValid()) { - qreal diff = visible.top() - h; - int lineCount = editorLayout->blockLayout(block)->lineCount(); - while (line < lineCount) { - if (editorLayout->blockLayout(block)->lineAt(line).naturalTextRect().top() >= diff) + if (moveCursor) { + while (cursor.movePosition(op, moveMode)) { + if (op == QTextCursor::Down) { + if (atEnd) + continue; + if (control->cursorRect(cursor).top() >= pageUpDownLastCursorY) + break; + } else if (op == QTextCursor::Up) { + if (atStart) + continue; + if (control->cursorRect(cursor).top() <= pageUpDownLastCursorY) break; - ++line; - } - if (line == lineCount) { - if (block.next().isValid() && block.next() != q->firstVisibleBlock()) { - block = block.next(); - line = 0; - } else { - --line; - } } } - setTopBlock(block.blockNumber(), line); - if (moveCursor) { - cursor.setVisualNavigation(true); - // move using movePosition to keep the cursor's x - lastY += verticalOffset(); - bool moved = false; - do { - moved = cursor.movePosition(op, moveMode); - } while (moved && control->cursorRect(cursor).top() > lastY); - } - } - - if (moveCursor) { control->setTextCursor(cursor, moveMode == QTextCursor::KeepAnchor); pageUpDownLastCursorYIsValid = true; } @@ -1184,7 +1115,8 @@ void PlainTextEditPrivate::adjustScrollbars() vmax -= qMax(0, viewport()->height()); QSizeF documentSize = editorLayout->documentSize(); vbar()->setRange(0, qMax(0, vmax)); - vbar()->setPageStep(viewport()->height()); + int lineHeight = qCeil(QFontMetricsF(q->font()).height()); + vbar()->setPageStep(viewport()->height() / lineHeight * lineHeight); int visualTopLine = 0; QTextBlock firstVisibleBlock = q->firstVisibleBlock(); if (firstVisibleBlock.isValid()) diff --git a/src/libs/utils/plaintextedit/texteditorlayout.cpp b/src/libs/utils/plaintextedit/texteditorlayout.cpp index 612f310cdb0..811bbe54aca 100644 --- a/src/libs/utils/plaintextedit/texteditorlayout.cpp +++ b/src/libs/utils/plaintextedit/texteditorlayout.cpp @@ -526,7 +526,7 @@ QTextBlock TextEditorLayout::findBlockByLineNumber(int lineNumber) const int blockNumber = 0; if (!d->m_offsetCache.empty()) { const int cacheSize = int(d->m_offsetCache.size()); - int i = cacheSize < lineNumber ? cacheSize - 1 : lineNumber; + int i = cacheSize <= lineNumber ? cacheSize - 1 : lineNumber; for (; i > 0; --i) { if (d->m_offsetCache[i].firstLine >= 0 && d->m_offsetCache[i].firstLine <= lineNumber) { blockNumber = i; @@ -536,13 +536,12 @@ QTextBlock TextEditorLayout::findBlockByLineNumber(int lineNumber) const } QTextBlock b = document()->findBlockByNumber(blockNumber); - while (b.isValid() && firstLineNumberOf(b) < lineNumber) + while (b.isValid()) { + if (firstLineNumberOf(b) + blockLineCount(b) - 1 >= lineNumber) + return b; b = b.next(); - if (b.isValid()) - b = b.previous(); - if (!b.isValid()) - b = document()->firstBlock(); - return b; + } + return document()->lastBlock(); } bool TextEditorLayout::moveCursorImpl( @@ -672,13 +671,6 @@ qreal TextLayoutItem::width() const { QTC_ASSERT(m_textLayout, return 0); return PlainTextDocumentLayout::layoutWidth(m_textLayout.get()); - qreal blockWidth = 0; - for (int i = 0; i < m_textLayout->lineCount(); ++i) { - QTextLine line = m_textLayout->lineAt(i); - blockWidth = qMax(line.naturalTextWidth() + 8, blockWidth); - } - return blockWidth; - } void TextLayoutItem::ensureLayouted(QTextDocument *doc, const QFontMetrics & fm, qreal availableWidth) diff --git a/src/libs/utils/qtcassert.h b/src/libs/utils/qtcassert.h index 8f6b2ec5b86..4a5d29d3c37 100644 --- a/src/libs/utils/qtcassert.h +++ b/src/libs/utils/qtcassert.h @@ -21,3 +21,4 @@ QTCREATOR_UTILS_EXPORT void dumpBacktrace(int maxdepth); #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)) +#define QTC_UNEXPECTED(cond) ((Q_UNLIKELY(cond)) ? (QTC_ASSERT_STRING(#cond), true) : false) diff --git a/src/libs/utils/variablechooser.cpp b/src/libs/utils/variablechooser.cpp index 52fe3d0d9a9..56047a0b31f 100644 --- a/src/libs/utils/variablechooser.cpp +++ b/src/libs/utils/variablechooser.cpp @@ -121,7 +121,9 @@ public: class VariableGroupItem : public TreeItem { public: - VariableGroupItem() = default; + VariableGroupItem(VariableChooserPrivate *chooser, const MacroExpanderProvider &provider) + : m_chooser(chooser), m_provider(provider) + {} QVariant data(int column, int role) const override { @@ -148,7 +150,12 @@ public: void populateGroup(MacroExpander *expander); -public: + QByteArray currentVariableName() const + { + return m_chooser->m_currentVariableName; + } + +private: VariableChooserPrivate *m_chooser = nullptr; // Not owned. bool m_populated = false; MacroExpanderProvider m_provider; @@ -157,11 +164,13 @@ public: class VariableItem : public TypedTreeItem<TreeItem, VariableGroupItem> { public: - VariableItem() = default; + VariableItem(const QByteArray &variable, MacroExpander *expander) + : m_variable(variable), m_expander(expander) + {} Qt::ItemFlags flags(int) const override { - if (m_variable == parent()->m_chooser->m_currentVariableName) + if (m_variable == parent()->currentVariableName()) return Qt::ItemIsSelectable; return Qt::ItemIsSelectable|Qt::ItemIsEnabled; } @@ -198,9 +207,9 @@ public: return QVariant(); } -public: - MacroExpander *m_expander; +private: QByteArray m_variable; + MacroExpander *m_expander; }; void VariableTreeView::contextMenuEvent(QContextMenuEvent *ev) @@ -293,26 +302,19 @@ void VariableGroupItem::populateGroup(MacroExpander *expander) { if (!expander) return; + const QList<QByteArray> variables = expander->visibleVariables(); - for (const QByteArray &variable : variables) { - auto item = new VariableItem; - item->m_variable = variable; - item->m_expander = expander; - appendChild(item); - } + for (const QByteArray &variable : variables) + appendChild(new VariableItem(variable, expander)); const MacroExpanderProviders subProviders = expander->subProviders(); for (const MacroExpanderProvider &subProvider : subProviders) { if (!subProvider) continue; - if (expander->isAccumulating()) { + if (expander->isAccumulating()) populateGroup(subProvider()); - } else { - auto item = new VariableGroupItem; - item->m_chooser = m_chooser; - item->m_provider = subProvider; - appendChild(item); - } + else + appendChild(new VariableGroupItem(m_chooser, subProvider)); } } @@ -395,10 +397,7 @@ VariableChooser::~VariableChooser() */ void VariableChooser::addMacroExpanderProvider(const MacroExpanderProvider &provider) { - auto item = new VariableGroupItem; - item->m_chooser = d; - item->m_provider = provider; - d->m_model.rootItem()->prependChild(item); + d->m_model.rootItem()->prependChild(new VariableGroupItem(d, provider)); } /*! @@ -630,4 +629,4 @@ bool VariableChooser::eventFilter(QObject *obj, QEvent *event) return false; } -} // namespace Internal +} // namespace Utils diff --git a/src/plugins/android/androidsdkmanager.cpp b/src/plugins/android/androidsdkmanager.cpp index 65d9ff82f11..2df913467c9 100644 --- a/src/plugins/android/androidsdkmanager.cpp +++ b/src/plugins/android/androidsdkmanager.cpp @@ -45,8 +45,8 @@ class QuestionProgressDialog : public QDialog Q_OBJECT public: - QuestionProgressDialog(QWidget *parent) - : QDialog(parent) + QuestionProgressDialog() + : QDialog(Core::ICore::dialogParent()) , m_outputTextEdit(new QPlainTextEdit) , m_questionLabel(new QLabel(Tr::tr("Do you want to accept the Android SDK license?"))) , m_answerButtonBox(new QDialogButtonBox) @@ -158,7 +158,7 @@ static std::optional<int> parseProgress(const QString &out) struct DialogStorage { - DialogStorage() { m_dialog.reset(new QuestionProgressDialog(Core::ICore::dialogParent())); }; + DialogStorage() { m_dialog.reset(new QuestionProgressDialog); }; std::unique_ptr<QuestionProgressDialog> m_dialog; }; @@ -572,10 +572,10 @@ void AndroidSdkManagerPrivate::runDialogRecipe(const Storage<DialogStorage> &dia dialogStorage, Group { If (!Group { - licensesRecipe, - Sync([dialogStorage] { dialogStorage->m_dialog->setQuestionVisible(false); }), - continuationRecipe - }) >> Then { + licensesRecipe, + Sync([dialogStorage] { dialogStorage->m_dialog->setQuestionVisible(false); }), + continuationRecipe + }) >> Then { Sync(onError).withAccept(onAcceptSetup) } }.withCancel(onCancelSetup) diff --git a/src/plugins/autotest/testresultspane.cpp b/src/plugins/autotest/testresultspane.cpp index 46b452f63ea..fb40569913e 100644 --- a/src/plugins/autotest/testresultspane.cpp +++ b/src/plugins/autotest/testresultspane.cpp @@ -270,7 +270,7 @@ void TestResultsPane::addTestResult(const TestResult &result) void TestResultsPane::addOutputLine(const QByteArray &outputLine, OutputChannel channel) { - if (!QTC_GUARD(!outputLine.contains('\n'))) { + if (QTC_UNEXPECTED(outputLine.contains('\n'))) { for (const auto &line : outputLine.split('\n')) addOutputLine(line, channel); return; diff --git a/src/plugins/axivion/axivionperspective.cpp b/src/plugins/axivion/axivionperspective.cpp index 5f94f7166b5..5145273387a 100644 --- a/src/plugins/axivion/axivionperspective.cpp +++ b/src/plugins/axivion/axivionperspective.cpp @@ -215,6 +215,16 @@ static FilePath mappedPathForLink(const Link &link) return {}; } +static FilePath requestPathMapping(const Link &link, const QString &projectName) +{ + + if (projectName.isEmpty()) + return {}; // we fail to create a mapping for empty project name + if (!handleMissingPathMapping(link.targetFilePath, projectName)) // mapping still invalid + return {}; + return mappedPathForLink(link); +} + class IssueListItem final : public ListItem { public: @@ -251,11 +261,9 @@ public: FilePath targetFilePath = mappedPathForLink(link); if (targetFilePath.isEmpty()) { - std::optional<Dto::ProjectInfoDto> pi = projectInfo(); - QTC_ASSERT(pi, return true); - if (!handleMissingPathMapping(link.targetFilePath, pi->name)) - return true; - targetFilePath = mappedPathForLink(link); + auto pi = projectInfo(); + QTC_ASSERT(pi, return false); + targetFilePath = requestPathMapping(link, pi->name); } link.targetFilePath = targetFilePath; if (link.targetFilePath.exists()) { @@ -1880,7 +1888,11 @@ void AxivionPerspective::handleAnchorClicked(const QUrl &url) return; Link link; if (const QString path = query.queryItemValue("filename", QUrl::FullyDecoded); !path.isEmpty()) - link.targetFilePath = findFileForIssuePath(FilePath::fromUserInput(path)); + link.targetFilePath = FilePath::fromUserInput(path); + FilePath targetFilePath = mappedPathForLink(link); + if (targetFilePath.isEmpty()) + targetFilePath = requestPathMapping(link, m_issueDetails->projectName()); + link.targetFilePath = targetFilePath; if (const QString line = query.queryItemValue("line"); !line.isEmpty()) link.target.line = line.toInt(); // column entry is wrong - so, ignore it diff --git a/src/plugins/axivion/axivionplugin.cpp b/src/plugins/axivion/axivionplugin.cpp index 1f874810c27..9d2d7a8164a 100644 --- a/src/plugins/axivion/axivionplugin.cpp +++ b/src/plugins/axivion/axivionplugin.cpp @@ -181,6 +181,7 @@ struct PostDtoStorage { QUrl url; std::optional<QByteArray> credential; + QString password; QByteArray csrfToken; QByteArray writeData; std::optional<DtoType> dtoData; @@ -257,7 +258,6 @@ class AxivionPluginPrivate : public QObject public: AxivionPluginPrivate(); void handleSslErrors(QNetworkReply *reply, const QList<QSslError> &errors); - void onStartupProjectChanged(Project *project); void fetchLocalDashboardInfo(const DashboardInfoHandler &handler, const QString &projectName); void fetchDashboardAndProjectInfo(const DashboardInfoHandler &handler, @@ -296,14 +296,11 @@ public: std::optional<QString> m_analysisVersion; QList<Dto::NamedFilterInfoDto> m_globalNamedFilters; QList<Dto::NamedFilterInfoDto> m_userNamedFilters; - Project *m_project = nullptr; bool m_runningQuery = false; SingleTaskTreeRunner m_taskTreeRunner; MappedTaskTreeRunner<IDocument *> m_docMarksRunner; SingleTaskTreeRunner m_issueInfoRunner; SingleTaskTreeRunner m_namedFilterRunner; - FileInProjectFinder m_fileFinder; // FIXME maybe obsolete when path mapping is implemented - QMetaObject::Connection m_fileFinderConnection; QHash<FilePath, QSet<TextMark *>> m_allMarks; bool m_inlineIssuesEnabled = true; DashboardMode m_dashboardMode = DashboardMode::Global; @@ -471,29 +468,6 @@ void AxivionPluginPrivate::handleSslErrors(QNetworkReply *reply, const QList<QSs #endif // ssl } -void AxivionPluginPrivate::onStartupProjectChanged(Project *project) -{ - if (project == m_project) - return; - - if (m_project) - disconnect(m_fileFinderConnection); - - m_project = project; - - if (!m_project) { - m_fileFinder.setProjectDirectory({}); - m_fileFinder.setProjectFiles({}); - return; - } - - m_fileFinder.setProjectDirectory(m_project->projectDirectory()); - m_fileFinderConnection = connect(m_project, &Project::fileListChanged, this, [this] { - m_fileFinder.setProjectFiles(m_project->files(Project::AllFiles)); - handleOpenedDocs(); - }); -} - static QUrl constructUrl(DashboardMode dashboardMode, const QString &projectName, const QString &subPath, const QUrlQuery &query) { @@ -656,8 +630,40 @@ static Group dtoRecipe(const Storage<DtoStorageType<DtoType>> &dtoStorage) showFilterException(error->message); return DoneResult::Error; } - errorString = dashboardErrorMessage(reply->url(), statusCode, - reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(), *error); + + if constexpr (std::is_same_v<DtoStorageType<DtoType>, PostDtoStorage<DtoType>> + && std::is_same_v<DtoType, Dto::ApiTokenInfoDto>) { + if (statusCode == 400 && error->type == "PasswordVerificationException" && error->data) { + const auto it = error->data->find("passwordMayBeUsedAsApiToken"); + if (it != error->data->end()) { + const Dto::Any data = it->second; + if (data.isBool() && data.getBool()) { + Dto::ApiTokenInfoDto fakeDto{ + QString(), + QString(), + true, + QString(), + QString(), + dtoStorage->password, + QString(), + QString(), + QString(), + QString(), + std::optional<QString>(), + QString(), + false + }; + dtoStorage->dtoData = fakeDto; + return DoneResult::Success; + } + } + } + } + errorString = dashboardErrorMessage( + reply->url(), + statusCode, + reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(), + *error); } else { errorString = error.error(); } @@ -853,6 +859,7 @@ static Group authorizationRecipe(DashboardMode dashboardMode) const Dto::ApiTokenCreationRequestDto requestDto{*passwordStorage, "IdePlugin", apiTokenDescription(), 0}; apiTokenStorage->writeData = requestDto.serialize(); + apiTokenStorage->password = *passwordStorage; return SetupResult::Continue; }; @@ -917,10 +924,7 @@ static Group authorizationRecipe(DashboardMode dashboardMode) passwordStorage, dashboardStorage, onGroupSetup(onPasswordGroupSetup), - Group { // GET DashboardInfoDto - finishAllAndSuccess, - dtoRecipe(dashboardStorage) - }, + dtoRecipe(dashboardStorage) || successItem, // GET DashboardInfoDto Group { // POST ApiTokenCreationRequestDto, GET ApiTokenInfoDto. apiTokenStorage, onGroupSetup(onApiTokenGroupSetup), @@ -1261,9 +1265,6 @@ void AxivionPluginPrivate::onDocumentOpened(IDocument *doc) return; FilePath filePath = settings().mappedFilePath(docFilePath, m_currentProjectInfo->name); - if (filePath.isEmpty() && m_project && m_project->isKnownFile(docFilePath)) - filePath = docFilePath.relativeChildPath(m_project->projectDirectory()); - if (filePath.isEmpty()) return; @@ -1379,8 +1380,6 @@ class AxivionPlugin final : public ExtensionSystem::IPlugin dd = new AxivionPluginPrivate; - connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, - dd, &AxivionPluginPrivate::onStartupProjectChanged); connect(EditorManager::instance(), &EditorManager::documentOpened, dd, &AxivionPluginPrivate::onDocumentOpened); connect(EditorManager::instance(), &EditorManager::documentClosed, @@ -1446,11 +1445,18 @@ void enableInlineIssues(bool enable) Utils::FilePath findFileForIssuePath(const Utils::FilePath &issuePath) { QTC_ASSERT(dd, return {}); - if (!dd->m_project || !dd->m_currentProjectInfo) + if (!dd->m_currentProjectInfo) return {}; - const FilePaths result = dd->m_fileFinder.findFile(issuePath.toUrl()); + Project *startupProj = ProjectManager::startupProject(); + if (!startupProj) + return {}; + + FileInProjectFinder fileFinder; + fileFinder.setProjectDirectory(startupProj->projectDirectory()); + fileFinder.setProjectFiles(startupProj->files(Project::AllFiles)); + const FilePaths result = fileFinder.findFile(issuePath.toUrl()); if (result.size() == 1) - return dd->m_project->projectDirectory().resolvePath(result.first()); + return startupProj->projectDirectory().resolvePath(result.first()); return {}; } diff --git a/src/plugins/axivion/axivionsettings.cpp b/src/plugins/axivion/axivionsettings.cpp index 2a33eb7d4ea..b15fb1568a9 100644 --- a/src/plugins/axivion/axivionsettings.cpp +++ b/src/plugins/axivion/axivionsettings.cpp @@ -571,6 +571,9 @@ public: return ResultError(Tr::tr("Project name must be non-empty.")); return ResultOk; }); + m_projectName.setToolTip(Tr::tr("Project name as it appears in the global dashboard.")); + m_projectName.setShowToolTipOnLabel(true); + m_analysisPath.setLabelText(Tr::tr("Analysis path:")); m_analysisPath.setDisplayStyle(StringAspect::LineEditDisplay); m_analysisPath.setValidationFunction([](const QString &text) -> Result<> { @@ -579,9 +582,17 @@ public: const FilePath fp = FilePath::fromString(input.replace('\\', '/')); return analysisPathValid(fp); }); + m_analysisPath.setToolTip(Tr::tr("Root path of the analyzed project relative to the used" + "project path inside the dashboard.\nLeave empty if the " + "analyzed project refers to the basepath of the analyzed " + "project.")); + m_analysisPath.setShowToolTipOnLabel(true); + m_localPath.setLabelText(Tr::tr("Local path:")); m_localPath.setExpectedKind(PathChooser::ExistingDirectory); m_localPath.setAllowPathFromDevice(false); + m_localPath.setToolTip(Tr::tr("Local directory path corresponding to the analyis path.")); + m_localPath.setShowToolTipOnLabel(true); using namespace Layouting; setLayouter([this] { diff --git a/src/plugins/cmakeprojectmanager/CMakeLists.txt b/src/plugins/cmakeprojectmanager/CMakeLists.txt index 62060f0dd28..ec2113302b4 100644 --- a/src/plugins/cmakeprojectmanager/CMakeLists.txt +++ b/src/plugins/cmakeprojectmanager/CMakeLists.txt @@ -44,6 +44,7 @@ add_qtc_plugin(CMakeProjectManager presetsmacros.cpp presetsmacros.h projecttreehelper.cpp projecttreehelper.h targethelper.cpp targethelper.h + testpresetshelper.cpp testpresetshelper.h 3rdparty/cmake/cmListFileCache.cxx 3rdparty/cmake/cmListFileLexer.cxx 3rdparty/cmake/cmListFileCache.h @@ -58,6 +59,16 @@ add_qtc_plugin(CMakeProjectManager vitaut-rstparser ) +add_qtc_test(tst_cmake_test_presets + CONDITION WITH_TESTS + DEPENDS Utils ProjectExplorer CMakeProjectManager + SOURCES + tests/tst_cmake_test_presets.cpp + presetsparser.cpp + presetsmacros.cpp + testpresetshelper.cpp +) + file(GLOB_RECURSE test_cases RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} testcases/*) qtc_add_resources(CMakeProjectManager "testcases" CONDITION WITH_TESTS diff --git a/src/plugins/cmakeprojectmanager/cmakekitaspect.cpp b/src/plugins/cmakeprojectmanager/cmakekitaspect.cpp index a86351dca67..da151e717fa 100644 --- a/src/plugins/cmakeprojectmanager/cmakekitaspect.cpp +++ b/src/plugins/cmakeprojectmanager/cmakekitaspect.cpp @@ -70,6 +70,18 @@ static FilePath defaultCMakeExecutable() return defaultTool ? defaultTool->cmakeExecutable() : FilePath(); } +const QList<CMakeTool *> toolsForBuildDevice(const Kit *k) +{ + if (const IDevice::ConstPtr dev = BuildDeviceKitAspect::device(k)) { + return Utils::filtered( + CMakeToolManager::cmakeTools(), [rootPath = dev->rootPath()](CMakeTool *t) { + return t->cmakeExecutable().isSameDevice(rootPath); + }); + } + + return {}; +} + class CMakeToolListModel : public TreeModel<TreeItem, Internal::CMakeToolTreeItem> { public: @@ -82,17 +94,14 @@ public: { clear(); - if (const IDevice::ConstPtr dev = BuildDeviceKitAspect::device(&m_kit)) { - const FilePath rootPath = dev->rootPath(); - const QList<CMakeTool *> toolsForBuildDevice - = Utils::filtered(CMakeToolManager::cmakeTools(), [rootPath](CMakeTool *item) { - return item->cmakeExecutable().isSameDevice(rootPath); - }); - for (CMakeTool *item : toolsForBuildDevice) - rootItem()->appendChild(new CMakeToolTreeItem(item, false)); - } - // The "From Build Device" and "None" items. - rootItem()->appendChild(new CMakeToolTreeItem(ProjectExplorer::Constants::BUILD_DEVICE)); + for (CMakeTool *tool : toolsForBuildDevice(&m_kit)) + rootItem()->appendChild(new CMakeToolTreeItem(tool, false)); + + // "From Build Device" + if (BuildDeviceKitAspect::device(&m_kit)) + rootItem()->appendChild(new CMakeToolTreeItem(ProjectExplorer::Constants::BUILD_DEVICE)); + + // "None" rootItem()->appendChild(new CMakeToolTreeItem()); } @@ -280,8 +289,12 @@ CMakeKeywords CMakeKitAspect::cmakeKeywords(const Kit *k) static void setCMakeTool(Kit *k, const Id id) { QTC_ASSERT(!id.isValid() || CMakeToolManager::findById(id), return); - if (k) + if (!k) + return; + if (id.isValid()) k->setValue(Constants::TOOL_ID, id.toSetting()); + else + k->removeKey(Constants::TOOL_ID); } void CMakeKitAspect::setCMakeExecutable(Kit *k, const FilePath &cmakeExecutable) @@ -308,9 +321,11 @@ void CMakeKitAspectFactory::setup(Kit *k) if (cmakeTool(k)) return; + const QList<CMakeTool *> matchingTools = toolsForBuildDevice(k); // Look for a suitable auto-detected one: const QString kitSource = k->detectionSource().id; - for (CMakeTool *tool : CMakeToolManager::cmakeTools()) { + + for (CMakeTool *tool : matchingTools) { const QString toolSource = tool->detectionSource().id; if (!toolSource.isEmpty() && toolSource == kitSource) { CMakeKitAspect::setCMakeExecutable(k, tool->cmakeExecutable()); @@ -318,7 +333,8 @@ void CMakeKitAspectFactory::setup(Kit *k) } } - CMakeKitAspect::setCMakeExecutable(k, defaultCMakeExecutable()); + if (matchingTools.contains(CMakeToolManager::defaultCMakeTool())) + CMakeKitAspect::setCMakeExecutable(k, defaultCMakeExecutable()); } void CMakeKitAspectFactory::fix(Kit *k) @@ -326,8 +342,13 @@ void CMakeKitAspectFactory::fix(Kit *k) QTC_ASSERT(k, return); // TODO: Differentiate (centrally?) between "nothing set" and "actively set to nothing". Id id = ensureId(k, Id::fromSetting(k->value(Constants::TOOL_ID))); - if (id.isValid() && !CMakeToolManager::findById(id)) - setup(k); + if (id.isValid()) { + CMakeTool * const tool = CMakeToolManager::findById(id); + if (!tool || !toolsForBuildDevice(k).contains(tool)) { + setCMakeTool(k, {}); + setup(k); + } + } } KitAspectFactory::ItemList CMakeKitAspectFactory::toUserOutput(const Kit *k) const diff --git a/src/plugins/cmakeprojectmanager/cmakelocatorfilter.cpp b/src/plugins/cmakeprojectmanager/cmakelocatorfilter.cpp index af013d81d22..45fa46161f3 100644 --- a/src/plugins/cmakeprojectmanager/cmakelocatorfilter.cpp +++ b/src/plugins/cmakeprojectmanager/cmakelocatorfilter.cpp @@ -7,6 +7,7 @@ #include "cmakeproject.h" #include "cmakeprojectmanagertr.h" #include "targethelper.h" +#include "testpresetshelper.h" #include <autotest/autotestconstants.h> #include <coreplugin/actionmanager/actionmanager.h> @@ -177,10 +178,25 @@ private: if (!testMenu) { Core::MessageManager::writeFlashing( Tr::tr("AutoTest plugin needs to be loaded in order to execute tests.")); + return; } ProjectExplorer::TestCaseEnvironment testEnv; - emit buildSystem->testRunRequested(testInfo, {"--output-on-failure"}, testEnv); + QStringList additionalOptions; + if (testInfo.path.fileName() == "CMakePresets.json") { + const auto cbs = qobject_cast<CMakeBuildSystem *>(buildSystem); + auto preset = Utils::findOrDefault( + cbs->project()->presetsData().testPresets, + [testInfo](const auto &preset) { return preset.name == testInfo.name; }); + additionalOptions = presetToCTestArgs(preset); + + if (preset.environment) + testEnv.environment = *preset.environment; + } else { + additionalOptions << "--output-on-failure"; + } + + emit buildSystem->testRunRequested(testInfo, additionalOptions, testEnv); } using TestAcceptor = std::function<void(BuildSystem *, const TestCaseInfo &)>; @@ -198,15 +214,39 @@ private: const auto cmakeProject = qobject_cast<const CMakeProject *>(ProjectManager::startupProject()); if (!cmakeProject) return; + const auto bs = qobject_cast<CMakeBuildSystem *>(cmakeProject->activeBuildSystem()); if (!bs) return; - for (const TestCaseInfo &testInfo : bs->testcasesInfo()) { + // First the test presets + const auto testPresets = cmakeProject->presetsData().testPresets; + QList<TestCaseInfo> testCasesInfo; + for (const auto &testPreset : testPresets) { + TestCaseInfo testInfo; + testInfo.name = testPreset.name; + testInfo.path = cmakeProject->projectFilePath().parentDir().pathAppended( + "CMakePresets.json"); + testCasesInfo << testInfo; + } + auto presetDisplayName = [cmakeProject](const TestCaseInfo &testInfo) -> QString { + auto preset = Utils::findOrDefault( + cmakeProject->presetsData().testPresets, + [testInfo](const auto &preset) { return preset.name == testInfo.name; }); + if (preset.displayName) + return preset.displayName.value(); + return testInfo.name; + }; + + // Then the tests themselves + testCasesInfo << bs->testcasesInfo(); + + for (const TestCaseInfo &testInfo : testCasesInfo) { const QRegularExpressionMatch match = regexp.match(testInfo.name); if (match.hasMatch()) { - const FilePath projectPath = cmakeProject->projectFilePath(); - const QString displayName = testInfo.name; + const QString displayName = testInfo.path.fileName() == "CMakePresets.json" + ? presetDisplayName(testInfo) + : testInfo.name; LocatorFilterEntry entry; entry.displayName = displayName; if (acceptor) { diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.cpp b/src/plugins/cmakeprojectmanager/cmakeproject.cpp index 591b022c7e2..d8ddd5dfea8 100644 --- a/src/plugins/cmakeprojectmanager/cmakeproject.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeproject.cpp @@ -220,6 +220,7 @@ Internal::PresetsData CMakeProject::combinePresets(Internal::PresetsData &cmakeP QHash<QString, PresetsDetails::ConfigurePreset> configurePresetsHash; QHash<QString, PresetsDetails::BuildPreset> buildPresetsHash; + QHash<QString, PresetsDetails::TestPreset> testPresetsHash; result.configurePresets = combinePresetsInternal(configurePresetsHash, cmakePresetsData.configurePresets, @@ -229,6 +230,8 @@ Internal::PresetsData CMakeProject::combinePresets(Internal::PresetsData &cmakeP cmakePresetsData.buildPresets, cmakeUserPresetsData.buildPresets, "build"); + result.testPresets = combinePresetsInternal( + testPresetsHash, cmakePresetsData.testPresets, cmakeUserPresetsData.testPresets, "test"); return result; } @@ -257,6 +260,31 @@ void CMakeProject::setupBuildPresets(Internal::PresetsData &presetsData) } } +void CMakeProject::setupTestPresets(Internal::PresetsData &presetsData) +{ + for (auto &testPreset : presetsData.testPresets) { + if (testPreset.inheritConfigureEnvironment) { + if (!testPreset.configurePreset && !testPreset.hidden) { + TaskHub::addTask<BuildSystemTask>( + Task::TaskType::DisruptingError, + Tr::tr("Test preset %1 is missing a corresponding configure preset.") + .arg(testPreset.name)); + presetsData.hasValidPresets = false; + } + + const QString &configurePresetName = testPreset.configurePreset.value_or(QString()); + testPreset.environment + = Utils::findOrDefault(presetsData.configurePresets, + [configurePresetName]( + const PresetsDetails::ConfigurePreset &configurePreset) { + return configurePresetName == configurePreset.name; + }) + .environment; + } + } +} + + QString CMakeProject::projectDisplayName(const Utils::FilePath &projectFilePath) { const QString fallbackDisplayName = projectFilePath.absolutePath().fileName(); @@ -343,6 +371,7 @@ void CMakeProject::readPresets() presetData.configurePresets = includeData.configurePresets + presetData.configurePresets; presetData.buildPresets = includeData.buildPresets + presetData.buildPresets; + presetData.testPresets = includeData.testPresets + presetData.testPresets; presetData.hasValidPresets = includeData.hasValidPresets && presetData.hasValidPresets; includeStack << includePath; @@ -370,6 +399,7 @@ void CMakeProject::readPresets() m_presetsData = combinePresets(cmakePresetsData, cmakeUserPresetsData); setupBuildPresets(m_presetsData); + setupTestPresets(m_presetsData); if (!m_presetsData.hasValidPresets) { m_presetsData = {}; diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.h b/src/plugins/cmakeprojectmanager/cmakeproject.h index 862d5b9a56a..61b4e4c43b7 100644 --- a/src/plugins/cmakeprojectmanager/cmakeproject.h +++ b/src/plugins/cmakeprojectmanager/cmakeproject.h @@ -49,6 +49,7 @@ private: Internal::PresetsData combinePresets(Internal::PresetsData &cmakePresetsData, Internal::PresetsData &cmakeUserPresetsData); void setupBuildPresets(Internal::PresetsData &presetsData); + void setupTestPresets(Internal::PresetsData &presetsData); mutable Internal::CMakeProjectImporter *m_projectImporter = nullptr; mutable QList<ProjectExplorer::Kit*> m_oldPresetKits; diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs index 746e966320c..6a03ea8cf4b 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs @@ -90,6 +90,8 @@ QtcPlugin { "projecttreehelper.h", "targethelper.cpp", "targethelper.h", + "testpresetshelper.cpp", + "testpresetshelper.h" ] Group { diff --git a/src/plugins/cmakeprojectmanager/presetsmacros.cpp b/src/plugins/cmakeprojectmanager/presetsmacros.cpp index d0495398060..3643e2b0085 100644 --- a/src/plugins/cmakeprojectmanager/presetsmacros.cpp +++ b/src/plugins/cmakeprojectmanager/presetsmacros.cpp @@ -68,6 +68,24 @@ static void expandAllButEnv(const PresetsDetails::BuildPreset &preset, Utils::OsSpecificAspects::pathListSeparator(sourceDirectory.osType())); } +static void expandAllButEnv(const PresetsDetails::TestPreset &preset, + const Utils::FilePath &sourceDirectory, + QString &value) +{ + value.replace("${dollar}", "$"); + + value.replace("${sourceDir}", sourceDirectory.path()); + value.replace("${fileDir}", preset.fileDir.path()); + value.replace("${sourceParentDir}", sourceDirectory.parentDir().path()); + value.replace("${sourceDirName}", sourceDirectory.fileName()); + + value.replace("${presetName}", preset.name); + value.replace("${hostSystemName}", getHostSystemName(sourceDirectory.osType())); + value.replace("${pathListSep}", + Utils::OsSpecificAspects::pathListSeparator(sourceDirectory.osType())); +} + + static QString expandMacroEnv(const QString ¯oPrefix, const QString &value, const std::function<QString(const QString &)> &op) @@ -414,4 +432,22 @@ template void expand<PresetsDetails::BuildPreset>(const PresetsDetails::BuildPre template bool evaluatePresetCondition<PresetsDetails::BuildPreset>( const PresetsDetails::BuildPreset &preset, const Utils::FilePath &sourceDirectory); + +// Expand for PresetsDetails::TestPreset +template void expand<PresetsDetails::TestPreset>(const PresetsDetails::TestPreset &preset, + Utils::Environment &env, + const Utils::FilePath &sourceDirectory); + +template void expand<PresetsDetails::TestPreset>(const PresetsDetails::TestPreset &preset, + Utils::EnvironmentItems &envItems, + const Utils::FilePath &sourceDirectory); + +template void expand<PresetsDetails::TestPreset>(const PresetsDetails::TestPreset &preset, + const Utils::Environment &env, + const Utils::FilePath &sourceDirectory, + QString &value); + +template bool evaluatePresetCondition<PresetsDetails::TestPreset>( + const PresetsDetails::TestPreset &preset, const Utils::FilePath &sourceDirectory); + } // namespace CMakeProjectManager::Internal::CMakePresets::Macros diff --git a/src/plugins/cmakeprojectmanager/presetsparser.cpp b/src/plugins/cmakeprojectmanager/presetsparser.cpp index 7e4a6b5d5a6..87392e50d03 100644 --- a/src/plugins/cmakeprojectmanager/presetsparser.cpp +++ b/src/plugins/cmakeprojectmanager/presetsparser.cpp @@ -467,6 +467,233 @@ static bool parseBuildPresets(const QJsonValue &jsonValue, return true; } +static std::optional<PresetsDetails::Output> parseOutput(const QJsonValue &jsonValue) +{ + if (jsonValue.isUndefined()) + return std::nullopt; + + PresetsDetails::Output output; + + QJsonObject object = jsonValue.toObject(); + if (object.contains("shortProgress")) + output.shortProgress = object.value("shortProgress").toBool(); + if (object.contains("verbosity")) + output.verbosity = object.value("verbosity").toString(); + if (object.contains("debug")) + output.debug = object.value("debug").toBool(); + if (object.contains("outputOnFailure")) + output.outputOnFailure = object.value("outputOnFailure").toBool(); + if (object.contains("quiet")) + output.quiet = object.value("quiet").toBool(); + if (object.contains("oputputLogFile")) + output.outputLogFile = Utils::FilePath::fromUserInput( + object.value("oputputLogFile").toString()); + if (object.contains("outputJUnitFile")) + output.outputJUnitFile = Utils::FilePath::fromUserInput( + object.value("outputJUnitFile").toString()); + if (object.contains("labelSummary")) + output.labelSummary = object.value("labelSummary").toBool(); + if (object.contains("subprojectSummary")) + output.subprojectSummary = object.value("subprojectSummary").toBool(); + if (object.contains("maxPassedTestOutputSize")) + output.maxPassedTestOutputSize = object.value("maxPassedTestOutputSize").toInt(); + if (object.contains("maxFailedTestOutputSize")) + output.maxFailedTestOutputSize = object.value("maxFailedTestOutputSize").toInt(); + if (object.contains("testOutputTruncation")) + output.testOutputTruncation = object.value("testOutputTruncation").toString(); + if (object.contains("maxTestNameWidth")) + output.maxTestNameWidth = object.value("maxTestNameWidth").toInt(); + + return output; +} + +static std::optional<PresetsDetails::Filter> parseFilter(const QJsonValue &jsonValue) +{ + if (jsonValue.isUndefined()) + return std::nullopt; + + PresetsDetails::Filter filter; + + QJsonObject object = jsonValue.toObject(); + if (object.contains("include")) { + QJsonObject includeObj = object.value("include").toObject(); + if (!includeObj.isEmpty()) { + filter.include = PresetsDetails::Filter::Include(); + if (includeObj.contains("name")) + filter.include->name = includeObj.value("name").toString(); + if (includeObj.contains("label")) + filter.include->label = includeObj.value("label").toString(); + if (includeObj.contains("useUnion")) + filter.include->useUnion = includeObj.value("useUnion").toBool(); + + if (includeObj.contains("index")) { + QJsonObject indexObj = includeObj.value("index").toObject(); + if (!indexObj.isEmpty()) { + filter.include->index = PresetsDetails::Filter::Include::Index(); + if (indexObj.contains("start")) + filter.include->index->start = indexObj.value("start").toInt(); + if (indexObj.contains("end")) + filter.include->index->end = indexObj.value("end").toInt(); + if (indexObj.contains("stride")) + filter.include->index->stride = indexObj.value("stride").toInt(); + if (indexObj.contains("specificTests")) { + QJsonValue specificTestsValue = indexObj.value("specificTests"); + if (specificTestsValue.isArray()) { + filter.include->index->specificTests = QList<int>(); + const QJsonArray specificTestsArray = specificTestsValue.toArray(); + for (const auto &arrayVal : specificTestsArray) + filter.include->index->specificTests.value() << arrayVal.toInt(); + } + } + } + } + } + } + + if (object.contains("exclude")) { + QJsonObject excludeObj = object.value("exclude").toObject(); + if (!excludeObj.isEmpty()) { + filter.exclude = PresetsDetails::Filter::Exclude(); + if (excludeObj.contains("name")) + filter.exclude->name = excludeObj.value("name").toString(); + if (excludeObj.contains("label")) + filter.exclude->label = excludeObj.value("label").toString(); + + if (excludeObj.contains("fixtures")) { + QJsonObject fixturesObj = excludeObj.value("fixtures").toObject(); + if (!fixturesObj.isEmpty()) { + filter.exclude->fixtures = PresetsDetails::Filter::Exclude::Fixtures(); + if (fixturesObj.contains("any")) + filter.exclude->fixtures->any = fixturesObj.value("any").toString(); + if (fixturesObj.contains("setup")) + filter.exclude->fixtures->setup = fixturesObj.value("setup").toString(); + if (fixturesObj.contains("cleanup")) + filter.exclude->fixtures->cleanup = fixturesObj.value("cleanup").toString(); + } + } + } + } + + return filter; +} + +static std::optional<PresetsDetails::Execution> parseExecution(const QJsonValue &jsonValue) +{ + if (jsonValue.isUndefined()) + return std::nullopt; + PresetsDetails::Execution execution; + + QJsonObject object = jsonValue.toObject(); + if (object.contains("stopOnFailure")) + execution.stopOnFailure = object.value("stopOnFailure").toBool(); + if (object.contains("enableFailover")) + execution.enableFailover = object.value("enableFailover").toBool(); + if (object.contains("jobs")) + execution.jobs = object.value("jobs").toInt(); + if (object.contains("resourceSpecFile")) + execution.resourceSpecFile = Utils::FilePath::fromUserInput( + object.value("resourceSpecFile").toString()); + if (object.contains("testLoad")) + execution.testLoad = object.value("testLoad").toInt(); + if (object.contains("showOnly")) + execution.showOnly = object.value("showOnly").toString(); + if (object.contains("repeat")) { + QJsonObject repeatObj = object.value("repeat").toObject(); + if (!repeatObj.isEmpty()) { + execution.repeat = PresetsDetails::Execution::Repeat(); + execution.repeat->mode = repeatObj.value("mode").toString(); + execution.repeat->count = repeatObj.value("count").toInt(); + } + } + if (object.contains("interactiveDebugging")) + execution.interactiveDebugging = object.value("interactiveDebugging").toBool(); + if (object.contains("scheduleRandom")) + execution.scheduleRandom = object.value("scheduleRandom").toBool(); + if (object.contains("timeout")) + execution.timeout = object.value("timeout").toInt(); + if (object.contains("noTestsAction")) + execution.noTestsAction = object.value("noTestsAction").toString(); + + return execution; +} + +static bool parseTestPresets(const QJsonValue &jsonValue, + QList<PresetsDetails::TestPreset> &testPresets, + const FilePath &fileDir) +{ + // The whole section is optional + if (jsonValue.isUndefined()) + return true; + + if (!jsonValue.isArray()) + return false; + + const QJsonArray testPresetsArray = jsonValue.toArray(); + for (const auto &presetJson : testPresetsArray) { + if (!presetJson.isObject()) + continue; + + QJsonObject object = presetJson.toObject(); + PresetsDetails::TestPreset preset; + + preset.name = object.value("name").toString(); + preset.hidden = object.value("hidden").toBool(); + preset.fileDir = fileDir; + + QJsonValue inherits = object.value("inherits"); + if (!inherits.isUndefined()) { + preset.inherits = QStringList(); + if (inherits.isArray()) { + const QJsonArray inheritsArray = inherits.toArray(); + for (const auto &inheritsValue : inheritsArray) + preset.inherits.value() << inheritsValue.toString(); + } else { + QString inheritsValue = inherits.toString(); + if (!inheritsValue.isEmpty()) + preset.inherits.value() << inheritsValue; + } + } + if (object.contains("condition")) + preset.condition = parseCondition(object.value("condition")); + if (object.contains("vendor")) + parseVendor(object.value("vendor"), preset.vendor); + if (object.contains("displayName")) + preset.displayName = object.value("displayName").toString(); + if (object.contains("description")) + preset.description = object.value("description").toString(); + const QJsonObject environmentObj = object.value("environment").toObject(); + for (const QString &envKey : environmentObj.keys()) { + if (!preset.environment) + preset.environment = Utils::Environment(); + QJsonValue envValue = environmentObj.value(envKey); + preset.environment.value().set(envKey, envValue.toString()); + } + if (object.contains("configurePreset")) + preset.configurePreset = object.value("configurePreset").toString(); + if (object.contains("inheritConfigureEnvironment")) + preset.inheritConfigureEnvironment = object.value("inheritConfigureEnvironment").toBool(); + if (object.contains("configuration")) + preset.configuration = object.value("configuration").toString(); + if (object.contains("overwriteConfigurationFile")) { + preset.overwriteConfigurationFile = QStringList(); + if (object.value("overwriteConfigurationFile").isArray()) { + const QJsonArray overwriteArray = object.value("overwriteConfigurationFile").toArray(); + for (const auto &overwriteValue : overwriteArray) + preset.overwriteConfigurationFile.value() << overwriteValue.toString(); + } + } + if (object.contains("output")) + preset.output = parseOutput(object.value("output")); + if (object.contains("filter")) + preset.filter = parseFilter(object.value("filter")); + if (object.contains("execution")) + preset.execution = parseExecution(object.value("execution")); + testPresets.emplace_back(preset); + } + return true; +} + + const PresetsData &PresetsParser::presetsData() const { return m_presetsData; @@ -536,6 +763,16 @@ bool PresetsParser::parse(const FilePath &jsonFile, QString &errorMessage, int & } // optional + if (!parseTestPresets(root.value("testPresets"), + m_presetsData.testPresets, + jsonFile.parentDir())) { + errorMessage = ::CMakeProjectManager::Tr::tr( + "Invalid \"testPresets\" section in file \"%1\".") + .arg(jsonFile.fileName()); + return false; + } + + // optional if (!parseVendor(root.value("vendor"), m_presetsData.vendor)) { errorMessage = ::CMakeProjectManager::Tr::tr("Invalid \"vendor\" section in file \"%1\".") .arg(jsonFile.fileName()); @@ -710,4 +947,36 @@ bool PresetsDetails::Condition::evaluate() const return false; } +void PresetsDetails::TestPreset::inheritFrom(const TestPreset &other) +{ + if (!condition && other.condition && !other.condition->isNull()) + condition = other.condition; + + if (!vendor && other.vendor) + vendor = other.vendor; + else if (vendor && other.vendor) + vendor = merge(*other.vendor, *vendor); + + if (!environment && other.environment) + environment = other.environment; + else if (environment && other.environment) + environment = environment->appliedToEnvironment(*other.environment); + + if (!configurePreset && other.configurePreset) + configurePreset = other.configurePreset; + if (!inheritConfigureEnvironment && other.inheritConfigureEnvironment) + inheritConfigureEnvironment = other.inheritConfigureEnvironment; + + if (!configuration && other.configuration) + configuration = other.configuration; + if (!overwriteConfigurationFile && other.overwriteConfigurationFile) + overwriteConfigurationFile = other.overwriteConfigurationFile; + if (!output && other.output) + output = other.output; + if (!filter && other.filter) + filter = other.filter; + if (!execution && other.execution) + execution = other.execution; +} + } // CMakeProjectManager::Internal diff --git a/src/plugins/cmakeprojectmanager/presetsparser.h b/src/plugins/cmakeprojectmanager/presetsparser.h index a0e743f833a..381dfb3964d 100644 --- a/src/plugins/cmakeprojectmanager/presetsparser.h +++ b/src/plugins/cmakeprojectmanager/presetsparser.h @@ -85,6 +85,74 @@ public: std::optional<ConditionPtr> condition; }; +struct Output +{ + std::optional<bool> shortProgress; + std::optional<QString> verbosity; + std::optional<bool> debug; + std::optional<bool> outputOnFailure; + std::optional<bool> quiet; + std::optional<Utils::FilePath> outputLogFile; + std::optional<Utils::FilePath> outputJUnitFile; + std::optional<bool> labelSummary; + std::optional<bool> subprojectSummary; + std::optional<int> maxPassedTestOutputSize; + std::optional<int> maxFailedTestOutputSize; + std::optional<QString> testOutputTruncation; + std::optional<int> maxTestNameWidth; +}; + +struct Filter +{ + struct Include + { + std::optional<QString> name; + std::optional<QString> label; + std::optional<bool> useUnion; + struct Index { + std::optional<int> start; + std::optional<int> end; + std::optional<int> stride; + std::optional<QList<int>> specificTests; + }; + std::optional<Index> index; + }; + struct Exclude + { + std::optional<QString> name; + std::optional<QString> label; + struct Fixtures + { + std::optional<QString> any; + std::optional<QString> setup; + std::optional<QString> cleanup; + }; + std::optional<Fixtures> fixtures; + }; + + std::optional<Include> include; + std::optional<Exclude> exclude; +}; + +struct Execution { + std::optional<bool> stopOnFailure; + std::optional<bool> enableFailover; + std::optional<int> jobs; + std::optional<Utils::FilePath> resourceSpecFile; + std::optional<int> testLoad; + std::optional<QString> showOnly; + struct Repeat + { + QString mode; + int count; + }; + std::optional<Repeat> repeat; + std::optional<bool> interactiveDebugging; + std::optional<bool> scheduleRandom; + std::optional<int> timeout; + std::optional<QString> noTestsAction; +}; + class ConfigurePreset { public: void inheritFrom(const ConfigurePreset &other); @@ -134,6 +202,28 @@ public: std::optional<QStringList> nativeToolOptions; }; +class TestPreset { +public: + void inheritFrom(const TestPreset &other); + + QString name; + bool hidden = false; + Utils::FilePath fileDir; + std::optional<QStringList> inherits; + std::optional<Condition> condition; + std::optional<QVariantMap> vendor; + std::optional<QString> displayName; + std::optional<QString> description; + std::optional<Utils::Environment> environment; + std::optional<QString> configurePreset; + bool inheritConfigureEnvironment = true; + std::optional<QString> configuration; + std::optional<QStringList> overwriteConfigurationFile; + std::optional<Output> output; + std::optional<Filter> filter; + std::optional<Execution> execution; +}; + } // namespace PresetsDetails class PresetsData @@ -148,6 +238,7 @@ public: Utils::FilePath fileDir; QList<PresetsDetails::ConfigurePreset> configurePresets; QList<PresetsDetails::BuildPreset> buildPresets; + QList<PresetsDetails::TestPreset> testPresets; }; class PresetsParser diff --git a/src/plugins/cmakeprojectmanager/testpresetshelper.cpp b/src/plugins/cmakeprojectmanager/testpresetshelper.cpp new file mode 100644 index 00000000000..c487ca97d8e --- /dev/null +++ b/src/plugins/cmakeprojectmanager/testpresetshelper.cpp @@ -0,0 +1,153 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "presetsparser.h" + +namespace CMakeProjectManager::Internal { + +// Helper that converts a CMake test preset into a list of commandâline args. +QStringList presetToCTestArgs(const PresetsDetails::TestPreset &preset) +{ + QStringList args; + + if (preset.output) { + const PresetsDetails::Output &out = *preset.output; + + if (out.shortProgress.value_or(false)) + args << "--progress"; + + const QString verb = out.verbosity.value_or("default"); + if (verb == "verbose") + args << "--verbose"; + else if (verb == "extra") + args << "--extra-verbose"; + else if (verb == "debug") + args << "--debug"; + + if (out.outputOnFailure.value_or(false)) + args << "--output-on-failure"; + + if (out.quiet.value_or(false)) + args << "--quiet"; + + if (out.outputLogFile) + args << "--output-log" << out.outputLogFile->toFSPathString(); + + if (out.outputJUnitFile) + args << "--output-junit" << out.outputJUnitFile->toFSPathString(); + + if (out.labelSummary.has_value() && !out.labelSummary.value()) + args << "--no-label-summary"; + + if (out.subprojectSummary.has_value() && !out.subprojectSummary.value()) + args << "--no-subproject-summary"; + + if (out.maxPassedTestOutputSize) + args << "--test-output-size-passed" << QString::number(*out.maxPassedTestOutputSize); + + if (out.maxFailedTestOutputSize) + args << "--test-output-size-failed" << QString::number(*out.maxFailedTestOutputSize); + + if (out.testOutputTruncation) + args << "--test-output-truncation" << *out.testOutputTruncation; + + if (out.maxTestNameWidth) + args << "--max-width" << QString::number(*out.maxTestNameWidth); + } + + if (preset.filter) { + const PresetsDetails::Filter &filt = *preset.filter; + + if (filt.include) { + const PresetsDetails::Filter::Include &inc = *filt.include; + if (inc.name) + args << "--tests-regex" << *inc.name; + if (inc.label) + args << "--label-regex" << *inc.label; + if (inc.useUnion.value_or(false)) + args << "--union"; + + if (inc.index) { + const PresetsDetails::Filter::Include::Index &idx = *inc.index; + if (idx.start) + args << "--start" << QString::number(*idx.start); + if (idx.end) + args << "--end" << QString::number(*idx.end); + if (idx.stride) + args << "--stride" << QString::number(*idx.stride); + if (idx.specificTests) + for (int t : *idx.specificTests) + args << "--specific-test" << QString::number(t); + } + } + + if (filt.exclude) { + const PresetsDetails::Filter::Exclude &exc = *filt.exclude; + if (exc.name) + args << "--exclude-regex" << *exc.name; + if (exc.label) + args << "--label-exclude" << *exc.label; + + if (exc.fixtures) { + const PresetsDetails::Filter::Exclude::Fixtures &f = *exc.fixtures; + if (f.any) + args << "--fixture-exclude-any" << *f.any; + if (f.setup) + args << "--fixture-exclude-setup" << *f.setup; + if (f.cleanup) + args << "--fixture-exclude-cleanup" << *f.cleanup; + } + } + } + + if (preset.execution) { + const PresetsDetails::Execution &exe = *preset.execution; + + if (exe.stopOnFailure.value_or(false)) + args << "--stop-on-failure"; + + if (exe.enableFailover.value_or(false)) + args << "-F"; + + if (exe.jobs) + args << "--parallel" << QString::number(*exe.jobs); + + if (exe.resourceSpecFile) + args << "--resource-spec-file" << exe.resourceSpecFile->toFSPathString(); + + if (exe.testLoad) + args << "--test-load" << QString::number(*exe.testLoad); + + if (exe.showOnly) + args << QString("--show-only=%1").arg(*exe.showOnly); + + if (exe.repeat) { + const PresetsDetails::Execution::Repeat &r = *exe.repeat; + args << "--repeat" << r.mode << "--count" << QString::number(r.count); + } + + if (exe.interactiveDebugging.value_or(false)) + args << "--interactive-debug-mode=1"; + else + args << "--interactive-debug-mode=0"; + + if (exe.scheduleRandom.value_or(false)) + args << "--schedule-random"; + + if (exe.timeout) + args << "--timeout" << QString::number(*exe.timeout); + + if (exe.noTestsAction) { + if (*exe.noTestsAction == "error") + args << "--no-tests=error"; + else if (*exe.noTestsAction == "ignore") + args << "--no-tests=ignore"; + } + } + + if (preset.configuration) + args << "--build-config" << *preset.configuration; + + return args; +} +} // namespace CMakeProjectManager::Internal diff --git a/src/plugins/cmakeprojectmanager/testpresetshelper.h b/src/plugins/cmakeprojectmanager/testpresetshelper.h new file mode 100644 index 00000000000..a7d11404ba1 --- /dev/null +++ b/src/plugins/cmakeprojectmanager/testpresetshelper.h @@ -0,0 +1,16 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include <QString> + +namespace CMakeProjectManager::Internal { + +namespace PresetsDetails { +class TestPreset; +} + +QStringList presetToCTestArgs(const PresetsDetails::TestPreset &preset); + +} // namespace CMakeProjectManager::Internal diff --git a/src/plugins/cmakeprojectmanager/tests/tst_cmake_test_presets.cpp b/src/plugins/cmakeprojectmanager/tests/tst_cmake_test_presets.cpp new file mode 100644 index 00000000000..63bd5f056aa --- /dev/null +++ b/src/plugins/cmakeprojectmanager/tests/tst_cmake_test_presets.cpp @@ -0,0 +1,483 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <QDir> +#include <QFile> +#include <QJsonArray> +#include <QJsonDocument> +#include <QJsonObject> +#include <QtTest> + +#include <utils/filepath.h> +#include <utils/hostosinfo.h> + +#include "../presetsmacros.h" +#include "../presetsparser.h" +#include "../testpresetshelper.h" + +using namespace CMakeProjectManager; +using namespace CMakeProjectManager::Internal; +using namespace CMakeProjectManager::Internal::CMakePresets::Macros; + +using namespace CMakePresets::Macros; +using namespace Utils; + +namespace CMakeProjectManager::Internal { +std::optional<PresetsDetails::Condition> parseCondition(const QJsonValue &jsonValue); +} + +class TestPresetsTests : public QObject +{ + Q_OBJECT + +private slots: + + void testParseTestPresetsMinimal() + { + // Create a JSON containing only the minimal fields + QJsonObject root; + QJsonArray arr; + QJsonObject obj; + obj["name"] = "minimal"; + arr.append(obj); + root["version"] = 5; + root["testPresets"] = arr; + + QFile file(QDir::tempPath() + "/minimal.json"); + QVERIFY(file.open(QIODevice::WriteOnly)); + file.write(QJsonDocument(root).toJson()); + file.close(); + + PresetsParser parser; + QString error; + int errorLine{0}; + bool ok = parser.parse(Utils::FilePath::fromUserInput(file.fileName()), error, errorLine); + QVERIFY(ok); + QVERIFY(error.isEmpty()); + + const auto &data = parser.presetsData(); + QCOMPARE(data.testPresets.size(), 1); + const auto &tp = data.testPresets.at(0); + QCOMPARE(tp.name, QString("minimal")); + QCOMPARE(tp.hidden, false); + QCOMPARE(tp.fileDir.fileName(), QFileInfo(file.fileName()).dir().dirName()); + QVERIFY(!tp.inherits); + QVERIFY(!tp.condition); + QVERIFY(!tp.vendor); + QVERIFY(!tp.displayName); + QVERIFY(!tp.description); + QVERIFY(!tp.environment); + QVERIFY(!tp.configurePreset); + QCOMPARE(tp.inheritConfigureEnvironment, true); + QVERIFY(!tp.configuration); + QVERIFY(!tp.overwriteConfigurationFile); + QVERIFY(!tp.output); + QVERIFY(!tp.filter); + QVERIFY(!tp.execution); + } + + void testParseTestPresetsFull() + { + const QJsonObject root = QJsonDocument::fromJson(R"( + { + "version": 5, + "testPresets": [ + { + "name": "full", + "hidden": true, + "inherits": ["base1", "base2"], + "condition": { + "type": "matches", + "string": "Darwin" + }, + "vendor": { + "qt.io/QtCreator/1.0": { + "debugger": "/path/to/dbg" + } + }, + "displayName": "Full Test", + "description": "A test preset with all fields", + "environment": { + "PATH": "/custom/bin" + }, + "configurePreset": "config1", + "inheritConfigureEnvironment": false, + "configuration": "Debug", + "overwriteConfigurationFile": ["CMakeLists.txt"], + "output": { + "shortProgress": true, + "verbosity": "high", + "debug": false, + "outputOnFailure": true, + "quiet": false, + "oputputLogFile": "log.txt", + "outputJUnitFile": "results.xml", + "labelSummary": true, + "subprojectSummary": false, + "maxPassedTestOutputSize": 1024, + "maxFailedTestOutputSize": 2048, + "testOutputTruncation": "truncate", + "maxTestNameWidth": 80 + }, + "filter": { + "include": { + "name": "TestA", + "label": "unit", + "useUnion": true, + "index": { + "start": 1, + "end": 10, + "stride": 2, + "specificTests": [1, 3, 5] + } + }, + "exclude": { + "name": "TestB", + "label": "integration", + "fixtures": { + "any": "BaseFixture", + "setup": "SetupFixture", + "cleanup": "CleanupFixture" + } + } + }, + "execution": { + "stopOnFailure": true, + "enableFailover": false, + "jobs": 4, + "resourceSpecFile": "resources.json", + "testLoad": 2, + "showOnly": "TestA", + "repeat": { + "mode": "fixed", + "count": 3 + }, + "interactiveDebugging": true, + "scheduleRandom": false, + "timeout": 120, + "noTestsAction": "skip" + } + } + ] + } + )").object(); + + + QFile file(QDir::tempPath() + "/full.json"); + QVERIFY(file.open(QIODevice::WriteOnly)); + file.write(QJsonDocument(root).toJson()); + file.close(); + + PresetsParser parser; + QString error; + int errorLine; + bool ok = parser.parse(Utils::FilePath::fromUserInput(file.fileName()), error, errorLine); + QVERIFY(ok); + QVERIFY(error.isEmpty()); + + const auto &data = parser.presetsData(); + QCOMPARE(data.testPresets.size(), 1); + const auto &tp = data.testPresets.at(0); + QCOMPARE(tp.name, QString("full")); + QCOMPARE(tp.hidden, true); + QCOMPARE(tp.inherits, QStringList() << "base1" << "base2"); + QVERIFY(tp.condition); + QCOMPARE(tp.condition->isMatches(), true); + QCOMPARE(tp.condition->string, "Darwin"); + QVERIFY(tp.vendor); + QCOMPARE(tp.vendor->value("debugger").toString(), "/path/to/dbg"); + QCOMPARE(tp.displayName, QString("Full Test")); + QCOMPARE(tp.description, QString("A test preset with all fields")); + QVERIFY(tp.environment); + QCOMPARE(tp.environment->value("PATH"), QString("/custom/bin")); + QCOMPARE(tp.configurePreset, QString("config1")); + QCOMPARE(tp.inheritConfigureEnvironment, false); + QCOMPARE(tp.configuration, QString("Debug")); + QCOMPARE(tp.overwriteConfigurationFile, QStringList() << "CMakeLists.txt"); + QVERIFY(tp.output); + QCOMPARE(tp.output->shortProgress, true); + QCOMPARE(tp.output->verbosity, QString("high")); + QCOMPARE(tp.output->debug, false); + QCOMPARE(tp.output->outputOnFailure, true); + QCOMPARE(tp.output->quiet, false); + QCOMPARE(tp.output->outputLogFile, Utils::FilePath::fromUserInput("log.txt")); + QCOMPARE(tp.output->outputJUnitFile, Utils::FilePath::fromUserInput("results.xml")); + QCOMPARE(tp.output->labelSummary, true); + QCOMPARE(tp.output->subprojectSummary, false); + QCOMPARE(tp.output->maxPassedTestOutputSize, 1024); + QCOMPARE(tp.output->maxFailedTestOutputSize, 2048); + QCOMPARE(tp.output->testOutputTruncation, QString("truncate")); + QCOMPARE(tp.output->maxTestNameWidth, 80); + + QVERIFY(tp.filter); + const auto &inc = tp.filter->include.value(); + QCOMPARE(inc.name, QString("TestA")); + QCOMPARE(inc.label, QString("unit")); + QCOMPARE(inc.useUnion, true); + const auto &idx = inc.index.value(); + QCOMPARE(idx.start, 1); + QCOMPARE(idx.end, 10); + QCOMPARE(idx.stride, 2); + QCOMPARE(idx.specificTests, QList<int>() << 1 << 3 << 5); + + const auto &exc = tp.filter->exclude.value(); + QCOMPARE(exc.name, QString("TestB")); + QCOMPARE(exc.label, QString("integration")); + const auto &fx = exc.fixtures.value(); + QCOMPARE(fx.any, QString("BaseFixture")); + QCOMPARE(fx.setup, QString("SetupFixture")); + QCOMPARE(fx.cleanup, QString("CleanupFixture")); + + QVERIFY(tp.execution); + const auto &exe = tp.execution.value(); + QCOMPARE(exe.stopOnFailure, true); + QCOMPARE(exe.enableFailover, false); + QCOMPARE(exe.jobs, 4); + QCOMPARE(exe.resourceSpecFile, Utils::FilePath::fromUserInput("resources.json")); + QCOMPARE(exe.testLoad, 2); + QCOMPARE(exe.showOnly, QString("TestA")); + QCOMPARE(exe.repeat->mode, QString("fixed")); + QCOMPARE(exe.repeat->count, 3); + QCOMPARE(exe.interactiveDebugging, true); + QCOMPARE(exe.scheduleRandom, false); + QCOMPARE(exe.timeout, 120); + QCOMPARE(exe.noTestsAction, QString("skip")); + } + + void testParseTestPresetsInvalidArray_data() + { + QTest::addColumn<QString>("json"); + QTest::addColumn<QString>("errorContains"); + + QTest::newRow("not array") << R"({"version":5, "testPresets":"not an array"})" + << "Invalid \"testPresets\" section in file \"invalid.json\"."; + QTest::newRow("array of non objects") << R"({""version":5, testPresets":[1,2,3]})" + << "missing name separator"; + } + + void testParseTestPresetsInvalidArray() + { + QFETCH(QString, json); + QFETCH(QString, errorContains); + + QFile file(QDir::tempPath() + "/invalid.json"); + QVERIFY(file.open(QIODevice::WriteOnly)); + file.write(json.toUtf8()); + file.close(); + + PresetsParser parser; + QString error; + int errorLine; + bool ok = parser.parse(Utils::FilePath::fromUserInput(file.fileName()), error, errorLine); + QVERIFY(!ok); + QVERIFY(error.contains(errorContains)); + } + + void testTestPresetInheritFrom() + { + PresetsDetails::TestPreset parent; + parent.name = "parent"; + parent.hidden = true; + parent.inherits = QStringList() << "grandparent"; + parent.condition = CMakeProjectManager::Internal::parseCondition( + QJsonObject{{"matches", "Darwin"}}); + parent.vendor = QVariantMap{ + {"qt.io/QtCreator/1.0", QVariantMap{{"debugger", "/path/to/dbg"}}}}; + parent.displayName = "Parent Display"; + parent.description = "Parent description"; + parent.environment = Utils::Environment::systemEnvironment(); + parent.environment->set("PARENT_VAR", "value"); + parent.configurePreset = "configP"; + parent.inheritConfigureEnvironment = true; + parent.configuration = "Release"; + parent.overwriteConfigurationFile = QStringList() << "CMakeLists.txt"; + parent.output = PresetsDetails::Output{ + true, + "high", + false, + true, + false, + Utils::FilePath::fromUserInput("log.txt"), + Utils::FilePath::fromUserInput("out.xml"), + true, + false, + 100, + 200, + "trunc", + 80}; + parent.filter = PresetsDetails::Filter{ + PresetsDetails::Filter::Include{ + QString("TestA"), + QString("unit"), + true, + PresetsDetails::Filter::Include::Index{0, 10, 1, QList<int>{1, 2}}}, + PresetsDetails::Filter::Exclude{ + QString("TestB"), + QString("unit"), + PresetsDetails::Filter::Exclude::Fixtures{ + "BaseFixture", "SetupFixture", "CleanupFixture"}}}; + parent.execution = PresetsDetails::Execution{ + true, + false, + 4, + Utils::FilePath::fromUserInput("spec.json"), + 2, + "TestA", + PresetsDetails::Execution::Repeat{"fixed", 3}, + true, + false, + 120, + "skip"}; + + PresetsDetails::TestPreset child; + child.name = "child"; + // child should inherit all, except: name, hidden, inherits, description and displayName + + child.inheritFrom(parent); + + QCOMPARE(child.name, QString("child")); // name does not inherit + QCOMPARE(child.hidden, false); // unchanged + QCOMPARE(child.inherits, std::nullopt); + QVERIFY(child.condition); + QVERIFY(child.vendor); + QVERIFY(!child.displayName); // not inherited + QVERIFY(!child.description); // not inherited + QVERIFY(child.environment); + QVERIFY(child.configurePreset); + QCOMPARE(child.inheritConfigureEnvironment, true); + QVERIFY(child.configuration); + QVERIFY(child.overwriteConfigurationFile); + QVERIFY(child.output); + QVERIFY(child.filter); + QVERIFY(child.execution); + } + + void testTestPresetInheritFromPartial() + { + PresetsDetails::TestPreset parent; + parent.environment = Utils::Environment(); + parent.environment->set("VAR1", "A"); + + PresetsDetails::TestPreset child; + child.environment = Utils::Environment(); + child.environment->set("VAR2", "B"); + + child.inheritFrom(parent); + + // Environment should be merged: VAR2 remains B, VAR1 added + QVERIFY(child.environment); + QCOMPARE(child.environment->value("VAR1"), QString("A")); + QCOMPARE(child.environment->value("VAR2"), QString("B")); + } + + void testExpandMacrosTestPreset() + { + PresetsDetails::TestPreset preset; + preset.name = "preset1"; + preset.fileDir = Utils::FilePath::fromUserInput("/tmp/project"); + preset.environment = Utils::Environment(); + preset.environment->set("VAR", "VALUE"); + + Utils::Environment env = preset.environment.value(); + + // Test simple variable expansion + QString val1 = "$env{VAR}"; + expand(preset, env, FilePath::fromString("/tmp/project"), val1); + QCOMPARE(val1, QString("VALUE")); + + // Test sourceDir macro + QString val2 = "${sourceDir}"; + expand(preset, env, FilePath::fromString("/tmp/project"), val2); + QCOMPARE(val2, QString("/tmp/project")); + + // Test presetName macro + QString val3 = "Hello ${presetName}"; + expand(preset, env, FilePath::fromString("/tmp/project"), val3); + QCOMPARE(val3, QString("Hello preset1")); + + // Test hostSystemName macro + QString val4 = "${hostSystemName}"; + expand(preset, env, FilePath::fromString("/tmp/project"), val4); + // hostSystemName should be a non-empty string + QVERIFY(!val4.isEmpty()); + + // Test pathListSep macro + QString val5 = "a${pathListSep}b"; + expand(preset, env, FilePath::fromString("/tmp/project"), val5); + if (HostOsInfo::isWindowsHost()) + QCOMPARE(val5, QString("a;b")); + else + QCOMPARE(val5, QString("a:b")); + } + + void testPresetToCTestArgs() + { + PresetsDetails::TestPreset p; + PresetsDetails::Output output; + output.shortProgress = true; + output.verbosity = "debug"; + output.outputOnFailure = true; + output.outputLogFile = Utils::FilePath::fromString("/tmp/log.txt"); + output.outputJUnitFile = Utils::FilePath::fromString("/tmp/junit.xml"); + output.labelSummary = false; + output.subprojectSummary = false; + output.maxPassedTestOutputSize = 1024; + output.maxFailedTestOutputSize = 2048; + output.testOutputTruncation = "full"; + output.maxTestNameWidth = 80; + p.output = output; + + PresetsDetails::Filter filter; + PresetsDetails::Filter::Include include; + include = PresetsDetails::Filter::Include{}; + include.name = ".*Foo.*"; + include.label = "fast"; + include.useUnion = true; + include.index = PresetsDetails::Filter::Include::Index{ + 1, 10, 2, QList<int>{3, 5, 7} + }; + filter.include = include; + p.filter = filter; + + PresetsDetails::Execution execution; + execution.jobs = 4; + execution.showOnly = "human"; + execution.repeat = PresetsDetails::Execution::Repeat{"count", 3}; + execution.timeout = 30; + execution.noTestsAction = "error"; + p.execution = execution; + + p.configuration = "Debug"; + + const auto args = presetToCTestArgs(p); + qDebug() << args; + + QVERIFY(args.contains("--progress")); + QVERIFY(args.contains("--debug")); + QVERIFY(args.contains("--output-on-failure")); + QVERIFY(args.contains("--output-log")); + QVERIFY(args.contains("--output-junit")); + QVERIFY(args.contains("--no-label-summary")); + QVERIFY(args.contains("--no-subproject-summary")); + QVERIFY(args.contains("--test-output-size-passed")); + QVERIFY(args.contains("--test-output-size-failed")); + QVERIFY(args.contains("--test-output-truncation")); + QVERIFY(args.contains("--max-width")); + QVERIFY(args.contains("--tests-regex")); + QVERIFY(args.contains("--label-regex")); + QVERIFY(args.contains("--union")); + QVERIFY(args.contains("--start")); + QVERIFY(args.contains("--end")); + QVERIFY(args.contains("--stride")); + QVERIFY(args.contains("--specific-test")); + QVERIFY(args.contains("--parallel")); + QVERIFY(args.contains("--show-only=human")); + QVERIFY(args.contains("--repeat")); + QVERIFY(args.contains("--timeout")); + QVERIFY(args.contains("--build-config")); + } + +}; + +QTEST_GUILESS_MAIN(TestPresetsTests) +#include "tst_cmake_test_presets.moc" diff --git a/src/plugins/coreplugin/customlanguagemodels.cpp b/src/plugins/coreplugin/customlanguagemodels.cpp index faebb23297c..0d12d10a846 100644 --- a/src/plugins/coreplugin/customlanguagemodels.cpp +++ b/src/plugins/coreplugin/customlanguagemodels.cpp @@ -93,7 +93,7 @@ public: CommandLine CustomLanguageModel::commandLine() { - return CommandLine(executable.effectiveBinary(), arguments.expandedValue(), CommandLine::Raw); + return CommandLine(executable.effectiveBinary(), arguments(), CommandLine::Raw); } CustomLanguageModel &CustomLanguageModel::fromBaseAspect(BaseAspect &aspect) diff --git a/src/plugins/coreplugin/find/highlightscrollbarcontroller.cpp b/src/plugins/coreplugin/find/highlightscrollbarcontroller.cpp index 683e4d132fe..6b1332a7038 100644 --- a/src/plugins/coreplugin/find/highlightscrollbarcontroller.cpp +++ b/src/plugins/coreplugin/find/highlightscrollbarcontroller.cpp @@ -70,7 +70,7 @@ private: QRect overlayRect() const; QRect handleRect() const; - // line start to line end + // pos start to pos end QMap<Highlight::Priority, QMap<Utils::Theme::Color, QMap<int, int>>> m_highlightCache; inline QScrollBar *scrollBar() const { return m_highlightController->scrollBar(); } @@ -186,21 +186,19 @@ void HighlightScrollBarOverlay::drawHighlights(QPainter *painter, painter->save(); painter->setClipRect(viewport); - const double lineHeight = m_highlightController->lineHeight(); - for (const QMap<Utils::Theme::Color, QMap<int, int>> &colors : std::as_const(m_highlightCache)) { const auto itColorEnd = colors.constEnd(); for (auto itColor = colors.constBegin(); itColor != itColorEnd; ++itColor) { const QColor &color = creatorColor(itColor.key()); const QMap<int, int> &positions = itColor.value(); const auto itPosEnd = positions.constEnd(); - const auto firstPos = int(docStart / lineHeight); + const auto firstPos = int(docStart); auto itPos = positions.upperBound(firstPos); if (itPos != positions.constBegin()) --itPos; while (itPos != itPosEnd) { - const double posStart = itPos.key() * lineHeight; - const double posEnd = (itPos.value() + 1) * lineHeight; + const double posStart = itPos.key(); + const double posEnd = (itPos.value() + 1); if (posEnd < docStart) { ++itPos; continue; @@ -244,7 +242,7 @@ bool HighlightScrollBarOverlay::eventFilter(QObject *object, QEvent *event) return QWidget::eventFilter(object, event); } -static void insertPosition(QMap<int, int> *map, int position) +static void insertPosition(QMap<int, int> *map, int position, int length) { auto itNext = map->upperBound(position); @@ -253,17 +251,17 @@ static void insertPosition(QMap<int, int> *map, int position) auto itPrev = std::prev(itNext); const int keyStart = itPrev.key(); const int keyEnd = itPrev.value(); - if (position >= keyStart && position <= keyEnd) + if (position >= keyStart && position + length <= keyEnd) return; // pos is already included if (keyEnd + 1 == position) { // glue with prev - (*itPrev)++; + *itPrev = position + length; gluedWithPrev = true; } } - if (itNext != map->end() && itNext.key() == position + 1) { + if (itNext != map->end() && itNext.key() == position + length + 1) { const int keyEnd = itNext.value(); itNext = map->erase(itNext); if (gluedWithPrev) { @@ -280,7 +278,7 @@ static void insertPosition(QMap<int, int> *map, int position) if (gluedWithPrev) return; // glued - map->insert(position, position); + map->insert(position, position + length); } void HighlightScrollBarOverlay::updateCache() @@ -294,7 +292,7 @@ void HighlightScrollBarOverlay::updateCache() for (const QVector<Highlight> &highlights : highlightsForId) { for (const auto &highlight : highlights) { auto &highlightMap = m_highlightCache[highlight.priority][highlight.color]; - insertPosition(&highlightMap, highlight.position); + insertPosition(&highlightMap, highlight.position, highlight.length); } } @@ -316,10 +314,11 @@ QRect HighlightScrollBarOverlay::handleRect() const ///////////// -Highlight::Highlight(Id category_, int position_, +Highlight::Highlight(Id category_, int position_, int length_, Theme::Color color_, Highlight::Priority priority_) : category(category_) , position(position_) + , length(length_) , color(color_) , priority(priority_) { @@ -364,16 +363,6 @@ void HighlightScrollBarController::setScrollArea(QAbstractScrollArea *scrollArea } } -double HighlightScrollBarController::lineHeight() const -{ - return ceil(m_lineHeight); -} - -void HighlightScrollBarController::setLineHeight(double lineHeight) -{ - m_lineHeight = lineHeight; -} - double HighlightScrollBarController::visibleRange() const { return m_visibleRange; diff --git a/src/plugins/coreplugin/find/highlightscrollbarcontroller.h b/src/plugins/coreplugin/find/highlightscrollbarcontroller.h index 3b14c9269a4..e12123cd2e0 100644 --- a/src/plugins/coreplugin/find/highlightscrollbarcontroller.h +++ b/src/plugins/coreplugin/find/highlightscrollbarcontroller.h @@ -29,11 +29,12 @@ struct CORE_EXPORT Highlight HighestPriority = 3 }; - Highlight(Utils::Id category, int position, Utils::Theme::Color color, Priority priority); + Highlight(Utils::Id category, int position, int length, Utils::Theme::Color color, Priority priority); Highlight() = default; Utils::Id category; int position = -1; + int length = 0; Utils::Theme::Color color = Utils::Theme::TextColorNormal; Priority priority = Invalid; }; @@ -50,9 +51,6 @@ public: QAbstractScrollArea *scrollArea() const; void setScrollArea(QAbstractScrollArea *scrollArea); - double lineHeight() const; - void setLineHeight(double lineHeight); - double visibleRange() const; void setVisibleRange(double visibleRange); @@ -67,7 +65,6 @@ public: private: QHash<Utils::Id, QVector<Highlight> > m_highlights; - double m_lineHeight = 0.0; double m_visibleRange = 0.0; // in pixels double m_margin = 0.0; // in pixels QAbstractScrollArea *m_scrollArea = nullptr; diff --git a/src/plugins/coreplugin/outputwindow.cpp b/src/plugins/coreplugin/outputwindow.cpp index 7acead08a25..da561720f1e 100644 --- a/src/plugins/coreplugin/outputwindow.cpp +++ b/src/plugins/coreplugin/outputwindow.cpp @@ -147,7 +147,7 @@ OutputWindow::OutputWindow(Context context, const Key &settingsKey, QWidget *par connect(pasteAction, &QAction::triggered, this, &QPlainTextEdit::paste); connect(selectAllAction, &QAction::triggered, this, &QPlainTextEdit::selectAll); connect(this, &QPlainTextEdit::blockCountChanged, this, [this] { - if (!d->filterText.isEmpty()) + if (shouldFilterNewContentOnBlockCountChanged()) filterNewContent(); }); @@ -221,6 +221,16 @@ void OutputWindow::handleLink(const QPoint &pos) void OutputWindow::adaptContextMenu(QMenu *, const QPoint &) {} +void OutputWindow::resetLastFilteredBlockNumber() +{ + d->lastFilteredBlockNumber = -1; +} + +bool OutputWindow::shouldFilterNewContentOnBlockCountChanged() const +{ + return !d->filterText.isEmpty(); +} + void OutputWindow::mouseReleaseEvent(QMouseEvent *e) { if (d->linksActive && d->mouseButtonPressed == Qt::LeftButton) @@ -386,7 +396,7 @@ void OutputWindow::setWheelZoomEnabled(bool enabled) d->zoomEnabled = enabled; } -void OutputWindow::updateFilterProperties( +bool OutputWindow::updateFilterProperties( const QString &filterText, Qt::CaseSensitivity caseSensitivity, bool isRegexp, @@ -403,8 +413,8 @@ void OutputWindow::updateFilterProperties( && d->filterText == filterText && d->beforeContext == beforeContext && d->afterContext == afterContext) - return; - d->lastFilteredBlockNumber = -1; + return false; + resetLastFilteredBlockNumber(); if (d->filterText != filterText) { const bool filterTextWasEmpty = d->filterText.isEmpty(); d->filterText = filterText; @@ -432,6 +442,7 @@ void OutputWindow::updateFilterProperties( d->beforeContext = beforeContext; d->afterContext = afterContext; filterNewContent(); + return true; } void OutputWindow::setOutputFileNameHint(const QString &fileName) @@ -439,8 +450,11 @@ void OutputWindow::setOutputFileNameHint(const QString &fileName) d->outputFileNameHint = fileName; } -OutputWindow::TextMatchingFunction OutputWindow::makeMatchingFunction() const +OutputWindow::TextMatchingFunction OutputWindow::makeMatchingFilterFunction() const { + const bool normal = !d->filterMode.testFlag(FilterModeFlag::Inverted) + && !d->filterText.isEmpty(); + if (d->filterText.isEmpty()) { return [](const QString &) { return true; }; } else if (d->filterMode.testFlag(OutputWindow::FilterModeFlag::RegExp)) { @@ -450,13 +464,13 @@ OutputWindow::TextMatchingFunction OutputWindow::makeMatchingFunction() const if (!regExp.isValid()) return [](const QString &) { return false; }; - return [regExp](const QString &text) { return regExp.match(text).hasMatch(); }; + return [regExp, normal](const QString &text) { return regExp.match(text).hasMatch() == normal; }; } else { const auto cs = d->filterMode.testFlag(OutputWindow::FilterModeFlag::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive; - return [cs, filterText = d->filterText](const QString &text) { - return text.contains(filterText, cs); + return [cs, filterText = d->filterText, normal](const QString &text) { + return text.contains(filterText, cs) == normal; }; } @@ -465,10 +479,8 @@ OutputWindow::TextMatchingFunction OutputWindow::makeMatchingFunction() const void OutputWindow::filterNewContent() { - const auto findNextMatch = makeMatchingFunction(); - QTC_ASSERT(findNextMatch, return); - const bool invert = d->filterMode.testFlag(FilterModeFlag::Inverted) - && !d->filterText.isEmpty(); + const auto findNextMatchFilter = makeMatchingFilterFunction(); + QTC_ASSERT(findNextMatchFilter, return); const int requiredBacklog = std::max(d->beforeContext, d->afterContext); const int firstBlockIndex = d->lastFilteredBlockNumber - requiredBacklog; @@ -479,7 +491,7 @@ void OutputWindow::filterNewContent() // Find matching text blocks for the current filter. for (; lastBlock != document()->end(); lastBlock = lastBlock.next()) { - const bool isMatch = findNextMatch(lastBlock.text()) != invert; + const bool isMatch = findNextMatchFilter(lastBlock.text()); if (isMatch) matchedBlocks.emplace_back(lastBlock.blockNumber()); diff --git a/src/plugins/coreplugin/outputwindow.h b/src/plugins/coreplugin/outputwindow.h index d97fc4d74b0..2fc1f445944 100644 --- a/src/plugins/coreplugin/outputwindow.h +++ b/src/plugins/coreplugin/outputwindow.h @@ -63,19 +63,22 @@ public: void resetZoom() { setFontZoom(0); } void setWheelZoomEnabled(bool enabled); - void updateFilterProperties( - const QString &filterText, - Qt::CaseSensitivity caseSensitivity, - bool regexp, - bool isInverted, - int beforeContext, - int afterContext); + bool updateFilterProperties( + const QString &filterText, + Qt::CaseSensitivity caseSensitivity, + bool regexp, + bool isInverted, + int beforeContext, + int afterContext); void setOutputFileNameHint(const QString &fileName); + void filterNewContent(); + signals: void wheelZoom(); void outputDiscarded(); + void cleanOldOutput(); public slots: void setWordWrapEnabled(bool wrap); @@ -85,6 +88,12 @@ protected: virtual void handleLink(const QPoint &pos); virtual void adaptContextMenu(QMenu *menu, const QPoint &pos); + using TextMatchingFunction = std::function<bool(const QString &text)>; + virtual TextMatchingFunction makeMatchingFilterFunction() const; + void resetLastFilteredBlockNumber(); + + virtual bool shouldFilterNewContentOnBlockCountChanged() const; + private: QMimeData *createMimeDataFromSelection() const override; void keyPressEvent(QKeyEvent *ev) override; @@ -98,7 +107,6 @@ private: using QPlainTextEdit::setFont; // call setBaseFont instead, which respects the zoom factor void enableUndoRedo(); - void filterNewContent(); void handleNextOutputChunk(); enum class ChunkCompleteness { Complete, Split }; @@ -111,9 +119,6 @@ private: qsizetype totalQueuedSize() const; qsizetype totalQueuedLines() const; - using TextMatchingFunction = std::function<bool(const QString &text)>; - TextMatchingFunction makeMatchingFunction() const; - Internal::OutputWindowPrivate *d = nullptr; }; diff --git a/src/plugins/coreplugin/progressmanager/progressmanager.cpp b/src/plugins/coreplugin/progressmanager/progressmanager.cpp index ac625d935df..337915d57cc 100644 --- a/src/plugins/coreplugin/progressmanager/progressmanager.cpp +++ b/src/plugins/coreplugin/progressmanager/progressmanager.cpp @@ -152,7 +152,6 @@ InfoWidget::InfoWidget(const InfoBarEntry &info, QPointer<InfoBar> infoBar) using namespace Layouting; setMinimumWidth(ProgressManagerPrivate::infoMinWidth()); - setMaximumWidth(ProgressManagerPrivate::infoMaxWidth()); const InfoLabel::InfoType infoType = [&info] { const QString envString = qtcEnvironmentVariable("QTC_DEBUG_POPUPNOTIFICATION_TYPE", {}); diff --git a/src/plugins/cppeditor/cpphighlighter.cpp b/src/plugins/cppeditor/cpphighlighter.cpp index e0c6c44542b..2b5ea99a4a8 100644 --- a/src/plugins/cppeditor/cpphighlighter.cpp +++ b/src/plugins/cppeditor/cpphighlighter.cpp @@ -195,24 +195,26 @@ void CppHighlighter::highlightBlock(const QString &text) continue; // Handle attributes, i.e. identifiers in pairs of "[[" and "]]". - if (tk.is(T_LBRACKET) && !tk.isOperator()) { - attrState.lbrackets = !attrState.lbrackets; - if (attrState.lbrackets == 0) - ++attrState.opened; - continue; - } - if (tk.is(T_RBRACKET) && !tk.isOperator()) { - attrState.rbrackets = !attrState.rbrackets; - if (attrState.rbrackets == 0 && attrState.opened > 0) - --attrState.opened; - if (attrState.lbrackets) - attrState.lbrackets = 0; - continue; - } - attrState.lbrackets = attrState.rbrackets = 0; - if (attrState.opened && (tk.is(T_IDENTIFIER) || (tk.isKeyword() && !tk.is(T_USING)))) { - setFormat(tk.utf16charsBegin(), tk.utf16chars(), formatForCategory(C_ATTRIBUTE)); - continue; + if (m_languageFeatures.cxx11Enabled && !m_languageFeatures.objCEnabled) { + if (tk.is(T_LBRACKET)) { + attrState.lbrackets = !attrState.lbrackets; + if (attrState.lbrackets == 0) + ++attrState.opened; + continue; + } + if (tk.is(T_RBRACKET)) { + attrState.rbrackets = !attrState.rbrackets; + if (attrState.rbrackets == 0 && attrState.opened > 0) + --attrState.opened; + if (attrState.lbrackets) + attrState.lbrackets = 0; + continue; + } + attrState.lbrackets = attrState.rbrackets = 0; + if (attrState.opened && (tk.is(T_IDENTIFIER) || (tk.isKeyword() && !tk.is(T_USING)))) { + setFormat(tk.utf16charsBegin(), tk.utf16chars(), formatForCategory(C_ATTRIBUTE)); + continue; + } } if (i == 0 && tk.is(T_POUND)) { diff --git a/src/plugins/debugger/debuggerkitaspect.cpp b/src/plugins/debugger/debuggerkitaspect.cpp index 4f30194fe40..10983d22111 100644 --- a/src/plugins/debugger/debuggerkitaspect.cpp +++ b/src/plugins/debugger/debuggerkitaspect.cpp @@ -38,6 +38,19 @@ namespace Debugger { namespace Internal { +static const QList<DebuggerItem> debuggersForBuildDevice(const Kit *k) +{ + if (const IDeviceConstPtr device = BuildDeviceKitAspect::device(k)) { + const Utils::FilePath rootPath = device->rootPath(); + return Utils::filtered(DebuggerItemManager::debuggers(), [&](const DebuggerItem &item) { + if (item.isGeneric()) + return device->id() != ProjectExplorer::Constants::DESKTOP_DEVICE_ID; + return item.command().isSameDevice(rootPath); + }); + } + return {}; +} + class DebuggerItemListModel : public TreeModel<TreeItem, DebuggerTreeItem> { public: @@ -50,17 +63,8 @@ public: { clear(); - if (const IDeviceConstPtr device = BuildDeviceKitAspect::device(&m_kit)) { - const Utils::FilePath rootPath = device->rootPath(); - const QList<DebuggerItem> debuggersForBuildDevice - = Utils::filtered(DebuggerItemManager::debuggers(), [&](const DebuggerItem &item) { - if (item.isGeneric()) - return device->id() != ProjectExplorer::Constants::DESKTOP_DEVICE_ID; - return item.command().isSameDevice(rootPath); - }); - for (const DebuggerItem &item : debuggersForBuildDevice) - rootItem()->appendChild(new DebuggerTreeItem(item, false)); - } + for (const DebuggerItem &item : debuggersForBuildDevice(&m_kit)) + rootItem()->appendChild(new DebuggerTreeItem(item, false)); DebuggerItem noneItem; noneItem.setUnexpandedDisplayName(Tr::tr("None", "No debugger")); rootItem()->appendChild(new DebuggerTreeItem(noneItem, false)); @@ -279,7 +283,7 @@ public: DebuggerItem bestItem; DebuggerItem::MatchLevel bestLevel = DebuggerItem::DoesNotMatch; const Environment systemEnvironment = Environment::systemEnvironment(); - for (const DebuggerItem &item : DebuggerItemManager::debuggers()) { + for (const DebuggerItem &item : Internal::debuggersForBuildDevice(k)) { DebuggerItem::MatchLevel level = DebuggerItem::DoesNotMatch; if (rawId.isNull()) { @@ -358,14 +362,17 @@ public: void fix(Kit *k) override { const QVariant id = k->value(DebuggerKitAspect::id()); + const QList<DebuggerItem> debuggers = Internal::debuggersForBuildDevice(k); const DebuggerItem debugger = Utils::findOrDefault( - DebuggerItemManager::debuggers(), Utils::equal(&DebuggerItem::id, id)); + debuggers, Utils::equal(&DebuggerItem::id, id)); + if (id.isValid() && !debugger.isValid()) + return setup(k); if (debugger.isValid() && debugger.engineType() == CdbEngineType) { const int tcWordWidth = ToolchainKitAspect::targetAbi(k).wordWidth(); if (Utils::anyOf(debugger.abis(), Utils::equal(&Abi::wordWidth, tcWordWidth))) return; - for (const DebuggerItem &item : DebuggerItemManager::debuggers()) { + for (const DebuggerItem &item : debuggers) { if (item.engineType() == CdbEngineType && Utils::anyOf(item.abis(), Utils::equal(&Abi::wordWidth, tcWordWidth))) { k->setValue(DebuggerKitAspect::id(), item.id()); diff --git a/src/plugins/devcontainer/CMakeLists.txt b/src/plugins/devcontainer/CMakeLists.txt index f4df2dbbc48..ed3704b4f8a 100644 --- a/src/plugins/devcontainer/CMakeLists.txt +++ b/src/plugins/devcontainer/CMakeLists.txt @@ -1,5 +1,5 @@ add_qtc_plugin(DevContainerPlugin - PLUGIN_DEPENDS Core ProjectExplorer + PLUGIN_DEPENDS Core ProjectExplorer TextEditor PLUGIN_TEST_DEPENDS CMakeProjectManager DEPENDS DevContainer CmdBridgeClient LONG_DESCRIPTION_MD README.md @@ -11,6 +11,13 @@ add_qtc_plugin(DevContainerPlugin devcontainerdevice.h ) +qtc_add_resources(DevContainerPlugin "images" + PREFIX "/devcontainer" + BASE "." + FILES + images/container.png + images/container@2x.png +) extend_qtc_plugin(DevContainerPlugin CONDITION WITH_TESTS diff --git a/src/plugins/devcontainer/devcontainer.qbs b/src/plugins/devcontainer/devcontainer.qbs index d7b4244e776..84fadb814ef 100644 --- a/src/plugins/devcontainer/devcontainer.qbs +++ b/src/plugins/devcontainer/devcontainer.qbs @@ -6,6 +6,7 @@ QtcPlugin { Depends { name: "CmdBridgeClient" } Depends { name: "ProjectExplorer" } Depends { name: "QtSupport" } + Depends { name: "TextEditor" } Depends { name: "Utils" } Depends { name: "qtc" } @@ -41,4 +42,15 @@ QtcPlugin { ".**/*", ] } + + Group { + name: "images" + prefix: "images/" + files: [ + "container.png", + "container@2x.png", + ] + fileTags: "qt.core.resource_data" + Qt.core.resourcePrefix: "/devcontainer" + } } diff --git a/src/plugins/devcontainer/devcontainerplugin.cpp b/src/plugins/devcontainer/devcontainerplugin.cpp index dd7a07e0405..b6d38c5c53e 100644 --- a/src/plugins/devcontainer/devcontainerplugin.cpp +++ b/src/plugins/devcontainer/devcontainerplugin.cpp @@ -5,6 +5,7 @@ #include "devcontainerplugin_constants.h" #include "devcontainerplugintr.h" +#include <coreplugin/editormanager/editormanager.h> #include <coreplugin/icore.h> #include <coreplugin/messagemanager.h> @@ -22,10 +23,14 @@ #include <projectexplorer/projectmanager.h> #include <projectexplorer/target.h> +#include <texteditor/texteditor.h> + #include <utils/algorithm.h> #include <utils/fsengine/fsengine.h> #include <utils/guardedcallback.h> +#include <utils/icon.h> #include <utils/infobar.h> +#include <utils/theme/theme.h> #include <QMessageBox> @@ -34,6 +39,8 @@ using namespace Utils; namespace DevContainer::Internal { +const Icon DEVCONTAINER_ICON({{":/devcontainer/images/container.png", Theme::IconsBaseColor}}); + #ifdef WITH_TESTS QObject *createDevcontainerTest(); #endif @@ -79,6 +86,12 @@ public: this, &DevContainerPlugin::onProjectRemoved); + connect( + Core::EditorManager::instance(), + &Core::EditorManager::editorCreated, + this, + &DevContainerPlugin::onEditorCreated); + for (auto project : ProjectManager::instance()->projects()) onProjectAdded(project); @@ -90,9 +103,12 @@ public: }); #endif } + void onProjectAdded(Project *project); void onProjectRemoved(Project *project); + void onEditorCreated(Core::IEditor *editor, const Utils::FilePath &filePath); + void startDeviceForProject(Project *project, DevContainer::InstanceConfig instanceConfig); #ifdef WITH_TESTS @@ -129,7 +145,7 @@ void DevContainerPlugin::onProjectRemoved(Project *project) devices.erase(it); } -void DevContainerPlugin::onProjectAdded(Project *project) +static FilePaths devContainerFilesForProject(Project *project) { /* Possible locations: @@ -141,12 +157,17 @@ void DevContainerPlugin::onProjectAdded(Project *project) const FilePath rootDevcontainer = project->projectDirectory() / ".devcontainer.json"; const FilePaths paths{rootDevcontainer, containerFolder / ".devcontainer"}; - const FilePaths devContainerFiles = filtered( + return filtered( transform( containerFolder.dirEntries(QDir::Dirs | QDir::NoDotAndDotDot), [](const FilePath &path) { return path / "devcontainer.json"; }) << rootDevcontainer << containerFolder / "devcontainer.json", &FilePath::isFile); +} + +void DevContainerPlugin::onProjectAdded(Project *project) +{ + const FilePaths devContainerFiles = devContainerFilesForProject(project); if (!devContainerFiles.isEmpty()) { const QList<DevContainer::InstanceConfig> instanceConfigs @@ -167,6 +188,9 @@ void DevContainerPlugin::onProjectAdded(Project *project) InfoBar *infoBar = Core::ICore::popupInfoBar(); infoBar->removeInfo(infoBarId); + if (!infoBar->canInfoBeAdded(infoBarId)) + return; + if (instanceConfigs.size() == 1) { InfoBarEntry entry( infoBarId, @@ -248,6 +272,62 @@ void DevContainerPlugin::onProjectAdded(Project *project) }; } +void DevContainerPlugin::onEditorCreated(Core::IEditor *editor, const Utils::FilePath &filePath) +{ + if (filePath.fileName() != "devcontainer.json" && filePath.fileName() != ".devcontainer.json") + return; + + auto textEditor = qobject_cast<TextEditor::BaseTextEditor *>(editor); + if (!textEditor) + return; + + Project *project = ProjectManager::projectForFile(filePath); + if (!project) + return; + + if (!DevContainer::Config::isValidConfigPath(project->rootProjectDirectory(), filePath)) + return; + + TextEditor::TextEditorWidget *textEditorWidget = textEditor->editorWidget(); + if (!textEditorWidget) + return; + + QAction *restartAction = new QAction(textEditorWidget); + restartAction->setIcon(DEVCONTAINER_ICON.icon()); + restartAction->setText(Tr::tr("(Re-)Start Development Container")); + restartAction->setToolTip(Tr::tr("Start or Restart the development container.")); + + const FilePath workspaceFolder = project->rootProjectDirectory(); + + connect(restartAction, &QAction::triggered, [this, project, workspaceFolder, filePath]() { + auto it = devices.find(project); + if (it != devices.end()) { + std::shared_ptr<Device> existingDevice = it->second; + existingDevice->restart([](Result<> result) { + if (!result) { + QMessageBox box(Core::ICore::dialogParent()); + box.setWindowTitle(Tr::tr("Development Container Error")); + box.setIcon(QMessageBox::Critical); + box.setText(result.error()); + box.exec(); + } + }); + } else { + DevContainer::InstanceConfig instanceConfig{ + .dockerCli = "docker", + .workspaceFolder = workspaceFolder, + .configFilePath = filePath, + .mounts = {}, + }; + + startDeviceForProject(project, instanceConfig); + } + }); + + textEditorWidget + ->insertExtraToolBarAction(TextEditor::TextEditorWidget::Side::Left, restartAction); +} + void DevContainerPlugin::startDeviceForProject( Project *project, DevContainer::InstanceConfig instanceConfig) { diff --git a/src/plugins/devcontainer/devcontainerplugin_constants.h b/src/plugins/devcontainer/devcontainerplugin_constants.h index e21caa4daf4..81b2abb3bdb 100644 --- a/src/plugins/devcontainer/devcontainerplugin_constants.h +++ b/src/plugins/devcontainer/devcontainerplugin_constants.h @@ -6,4 +6,6 @@ namespace DevContainer::Constants { const char DEVCONTAINER_DEVICE_TYPE[] = "DevContainerDeviceType"; const char16_t DEVCONTAINER_FS_SCHEME[] = u"devcontainer"; + +const char ACTION_START_DEVCONTAINER[] = "devcontainer.start"; } // namespace DevContainer::Constants diff --git a/src/plugins/devcontainer/images/container.png b/src/plugins/devcontainer/images/container.png Binary files differnew file mode 100644 index 00000000000..22324bc44e4 --- /dev/null +++ b/src/plugins/devcontainer/images/container.png diff --git a/src/plugins/devcontainer/images/container@2x.png b/src/plugins/devcontainer/images/container@2x.png Binary files differnew file mode 100644 index 00000000000..8871fa7916f --- /dev/null +++ b/src/plugins/devcontainer/images/container@2x.png diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 3a6093ca642..23ffbfe179e 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -463,6 +463,9 @@ DockerDevice::DockerDevice() mounts.setDefaultValue({Core::DocumentManager::projectsDirectory().toUrlishString()}); mounts.setToolTip(Tr::tr("Maps paths in this list one-to-one to the docker container.")); mounts.setPlaceHolderText(Tr::tr("Host directories to mount into the container.")); + mounts.addOnChanged(DeviceManager::instance(), [this] { + DeviceManager::instance()->deviceUpdated(id()); + }); extraArgs.setSettingsKey(DockerDeviceExtraArgs); extraArgs.setLabelText(Tr::tr("Extra arguments:")); @@ -1308,10 +1311,6 @@ PortMapping::PortMapping() protocol.setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox); protocol.setLabelText(Tr::tr("Protocol:")); - for (const auto &aspect : aspects()) { - connect(aspect, &BaseAspect::changed, this, &PortMapping::changed); - } - setLayouter([this] { using namespace Layouting; return Row{ip, hostPort, containerPort, protocol}; diff --git a/src/plugins/ios/iosdevice.cpp b/src/plugins/ios/iosdevice.cpp index 2a452f2d635..d8d8642e7eb 100644 --- a/src/plugins/ios/iosdevice.cpp +++ b/src/plugins/ios/iosdevice.cpp @@ -421,8 +421,8 @@ void IosDeviceManager::deviceInfo(const QString &uid, switch (result) { case QMessageBox::Yes: Core::HelpManager::showHelpUrl( - QLatin1String("qthelp://org.qt-project.qtcreator/doc/" - "creator-developing-ios.html")); + "qthelp://org.qt-project.qtcreator/doc/" + "creator-how-to-connect-ios-devices.html"); break; case QMessageBox::No: break; diff --git a/src/plugins/languageclient/languageclientmanager.cpp b/src/plugins/languageclient/languageclientmanager.cpp index c2a4817864e..3f2ab720f8d 100644 --- a/src/plugins/languageclient/languageclientmanager.cpp +++ b/src/plugins/languageclient/languageclientmanager.cpp @@ -361,7 +361,6 @@ void LanguageClientManager::applySettings(BaseSettings *setting) } } else if (setting->m_startBehavior == BaseSettings::RequiresProject) { const QList<Core::IDocument *> &openedDocuments = Core::DocumentModel::openedDocuments(); - QHash<Project *, Client *> clientForProject; for (Core::IDocument *document : openedDocuments) { auto textDocument = qobject_cast<TextEditor::TextDocument *>(document); if (!textDocument || !setting->m_languageFilter.isSupported(textDocument)) @@ -369,6 +368,7 @@ void LanguageClientManager::applySettings(BaseSettings *setting) const Utils::FilePath filePath = textDocument->filePath(); for (Project *project : ProjectManager::projects()) { for (Target *target : project->targets()) { + const bool targetIsActive = project->activeTarget() == target; for (BuildConfiguration *bc : target->buildConfigurations()) { if (!setting->isValidOnBuildConfiguration(bc)) continue; @@ -378,16 +378,16 @@ void LanguageClientManager::applySettings(BaseSettings *setting) && !ProjectSettings(project).disabledSettings().contains(setting->m_id)); if (!settingIsEnabled) continue; - if (project->isKnownFile(filePath)) { - Client *client = clientForProject[project]; - if (!client) { - client = startClient(setting, bc); - if (!client) - continue; - clientForProject[project] = client; - } + if (!project->isKnownFile(filePath)) + continue; + Client *client = startClient(setting, bc); + if (!client) + continue; + if (targetIsActive && target->activeBuildConfiguration() == bc + && client->activatable()) { + openDocumentWithClient(textDocument, client); + } else client->openDocument(textDocument); - } } } } diff --git a/src/plugins/learning/overview/recommendations.json b/src/plugins/learning/overview/recommendations.json index dd32904d092..2a9013f84a4 100644 --- a/src/plugins/learning/overview/recommendations.json +++ b/src/plugins/learning/overview/recommendations.json @@ -470,7 +470,7 @@ "experience_advanced" ], "id": "3657611", - "id_url": "https://www.qt.io/academy/course-catalog?q=datavisualization#qt-datavisualization-to-qt-graphs", + "id_url": "https://www.qt.io/academy/course-catalog?q=qt+quick+3d%3A+views%2C+scenes+%26+nodes#qt-quick-3d:-views,-scenes-&-nodes", "name": "Qt Quick 3D: Views, Scenes & Nodes", "thumbnail": "courseqtquick3dviewsscenesandnodes.webp", "thumbnailurl": "https://learnupon.s3.eu-west-1.amazonaws.com/courseimages/1313024/large/8c4ef6f9-4734-4bf0-9d5a-8bacc453ad69-Course-Views-Scenes-Nodes.png", diff --git a/src/plugins/learning/overviewwelcomepage.cpp b/src/plugins/learning/overviewwelcomepage.cpp index adadea134b8..c463a94409b 100644 --- a/src/plugins/learning/overviewwelcomepage.cpp +++ b/src/plugins/learning/overviewwelcomepage.cpp @@ -107,8 +107,8 @@ public: qCWarning(qtWelcomeOverviewLog).noquote() << json.error() << OverviewItem::jsonFile(); return {}; } - qCWarning(qtWelcomeOverviewLog).noquote() << "Reading" << types << "from" << - OverviewItem::jsonFile(); + qCDebug(qtWelcomeOverviewLog).noquote() + << "Reading" << types << "from" << OverviewItem::jsonFile(); return itemsFromJson(json->data(), types); } @@ -594,7 +594,7 @@ protected: const QRectF badgeR(1, 1, SpacingTokens::PaddingHS + textWidth + SpacingTokens::PaddingHS, SpacingTokens::PaddingVXs + badgeTF.lineHeight() + SpacingTokens::PaddingVXs); - drawCardBg(painter, badgeR, creatorColor(Theme::Token_Notification_Neutral_Muted), + drawCardBg(painter, badgeR, creatorColor(Theme::Token_Notification_Success_Muted), Qt::NoPen, radiusL); painter->setFont(font); painter->setPen(badgeTF.color()); diff --git a/src/plugins/projectexplorer/appoutputpane.cpp b/src/plugins/projectexplorer/appoutputpane.cpp index c27ffb1a19a..6012b7dec89 100644 --- a/src/plugins/projectexplorer/appoutputpane.cpp +++ b/src/plugins/projectexplorer/appoutputpane.cpp @@ -11,12 +11,14 @@ #include "projectexplorertr.h" #include "projectmanager.h" #include "runcontrol.h" +#include "runconfigurationaspects.h" #include "showoutputtaskhandler.h" #include "windebuginterface.h" #include <coreplugin/actionmanager/actionmanager.h> #include <coreplugin/actionmanager/command.h> #include <coreplugin/coreconstants.h> +#include <coreplugin/coreicons.h> #include <coreplugin/icore.h> #include <coreplugin/outputwindow.h> #include <coreplugin/session.h> @@ -28,24 +30,33 @@ #include <extensionsystem/pluginmanager.h> #include <utils/algorithm.h> +#include <utils/async.h> +#include <utils/basetreeview.h> +#include <utils/layoutbuilder.h> #include <utils/outputformatter.h> #include <utils/qtcassert.h> #include <utils/qtcolorbutton.h> +#include <utils/storekey.h> #include <utils/stylehelper.h> #include <utils/utilsicons.h> +#include <QAbstractListModel> #include <QAction> #include <QCheckBox> #include <QComboBox> +#include <QColorDialog> #include <QFormLayout> #include <QHBoxLayout> #include <QLabel> #include <QLoggingCategory> #include <QMenu> #include <QPushButton> +#include <QSortFilterProxyModel> #include <QSpinBox> +#include <QSplitter> #include <QTabBar> #include <QTabWidget> +#include <QTextBlock> #include <QTimer> #include <QToolButton> #include <QVBoxLayout> @@ -83,13 +94,144 @@ static QString msgAttachDebuggerTooltip(const QString &handleDescription = QStri Tr::tr("Attach debugger to %1").arg(handleDescription); } +static inline QString messageTypeToString(QtMsgType type) +{ + switch (type) { + case QtDebugMsg: + return {"Debug"}; + case QtInfoMsg: + return {"Info"}; + case QtCriticalMsg: + return {"Critical"}; + case QtWarningMsg: + return {"Warning"}; + case QtFatalMsg: + return {"Fatal"}; + default: + return {"Unknown"}; + } +} + +class LoggingCategoryRegistry : public QObject +{ + Q_OBJECT +public: + using QObject::QObject; + + ~LoggingCategoryRegistry() { reset(); } + + QMap<QString, QLoggingCategory *> categories() { return m_categories; } + + void onNewCategory(const QString &data) + { + const QStringList catList = data.split(' '); + QTC_ASSERT(catList.size() == 5, return); + + const QString catName = catList.first(); + if (m_categories.contains(catName)) + return; + + const auto category = new QLoggingCategory(catName.toUtf8()); + category->setEnabled(QtDebugMsg, catList.at(1).toInt()); + category->setEnabled(QtWarningMsg, catList.at(2).toInt()); + category->setEnabled(QtCriticalMsg, catList.at(3).toInt()); + category->setEnabled(QtInfoMsg, catList.at(4).toInt()); + + m_categories[catName] = category; + emit newLogCategory(catName, category); + } + + void reset() + { + qDeleteAll(m_categories); + m_categories.clear(); + } + +signals: + void newLogCategory(QString name, QLoggingCategory *category); + +private: + QMap<QString, QLoggingCategory *> m_categories; +}; + +class AppOutputWindow : public Core::OutputWindow +{ + Q_OBJECT + +public: + using OutputWindow::OutputWindow; + + void updateCategoriesProperties(const QMap<QString, QLoggingCategory *> &categories) + { + resetLastFilteredBlockNumber(); + m_categories = categories; + } + + void setFilterEnabled(bool enabled) { m_filterEnabled = enabled; } + bool filterEnabled() const { return m_filterEnabled; } + + LoggingCategoryRegistry *registry() { return &m_registry; } + +private: + TextMatchingFunction makeMatchingFilterFunction() const override + { + auto parentFilter = OutputWindow::makeMatchingFilterFunction(); + + auto filter = [categories = m_categories](const QString &text) { + if (categories.isEmpty()) + return true; + + for (auto i = categories.cbegin(), end = categories.cend(); i != end; ++i) { + if (!text.contains(i.key())) + continue; + QLoggingCategory * const cat = i.value(); + if (text.contains("[F]")) + return true; + if (text.contains("[D]") && !cat->isDebugEnabled()) + return false; + if (text.contains("[W]") && !cat->isWarningEnabled()) + return false; + if (text.contains("[C]") && !cat->isCriticalEnabled()) + return false; + if (text.contains("[I]") && !cat->isInfoEnabled()) + return false; + return true; + } + return true; + }; + + return [filter, parentFilter](const QString &text) { + return filter(text) && parentFilter(text); + }; + } + + bool shouldFilterNewContentOnBlockCountChanged() const override + { + return m_filterEnabled || OutputWindow::shouldFilterNewContentOnBlockCountChanged(); + } + + LoggingCategoryRegistry m_registry{this}; + QMap<QString, QLoggingCategory *> m_categories; + bool m_filterEnabled = false; +}; + class TabWidget : public QTabWidget { public: TabWidget(QWidget *parent = nullptr); + int addTab(QWidget *ow, QWidget* cv, const QString &label); + + QWidget* currentWidget() const; + void setCurrentWidget(QWidget *widget); + int indexOf(const QWidget *w) const; + QWidget *widget(int index) const; + QWidget *filtersWidget(int index) const; + private: bool eventFilter(QObject *object, QEvent *event) override; + QWidget *getActualWidget(QWidget *w, int splitterIndex) const; + int m_tabIndexForMiddleClick = -1; }; @@ -100,6 +242,48 @@ TabWidget::TabWidget(QWidget *parent) setContextMenuPolicy(Qt::CustomContextMenu); } +int TabWidget::addTab(QWidget *ow, QWidget* cv, const QString &label) +{ + QSplitter * splitter = new QSplitter(Qt::Horizontal); + splitter->addWidget(ow); + splitter->setStretchFactor(0, 2); + splitter->addWidget(cv); + splitter->setStretchFactor(1, 1); + return insertTab(-1, splitter, label); +} + +QWidget *TabWidget::currentWidget() const +{ + return getActualWidget(QTabWidget::currentWidget(), 0); +} + +void TabWidget::setCurrentWidget(QWidget *w) +{ + for (int i = 0; i < count(); ++i) { + if (widget(i) == w) + setCurrentIndex(i); + } +} + +int TabWidget::indexOf(const QWidget *w) const +{ + for (int i = 0; i < count(); ++i) { + if (widget(i) == w) + return i; + } + return -1; +} + +QWidget *TabWidget::widget(int index) const +{ + return getActualWidget(QTabWidget::widget(index), 0); +} + +QWidget *TabWidget::filtersWidget(int index) const +{ + return getActualWidget(QTabWidget::widget(index), 1); +} + bool TabWidget::eventFilter(QObject *object, QEvent *event) { if (object == tabBar()) { @@ -125,6 +309,114 @@ bool TabWidget::eventFilter(QObject *object, QEvent *event) return QTabWidget::eventFilter(object, event); } +QWidget *TabWidget::getActualWidget(QWidget *w, int splitterIndex) const +{ + if (const auto splitter = qobject_cast<QSplitter*>(w)) + return splitter->widget(splitterIndex); + return nullptr; +} + +class LoggingCategoryModel : public QAbstractListModel +{ + Q_OBJECT +public: + using QAbstractListModel::QAbstractListModel; + enum Column { Name, Debug, Warning, Critical, Fatal, Info }; + + int columnCount(const QModelIndex &) const final { return 6; } + int rowCount(const QModelIndex & = QModelIndex()) const final { return m_categories.size(); } + + void append(QString name, QLoggingCategory *category) + { + beginInsertRows(QModelIndex(), m_categories.size(), m_categories.size() + 1); + m_categories.push_back({name, category}); + endInsertRows(); + } + + QVariant data(const QModelIndex &index, int role) const final + { + if (!index.isValid()) + return {}; + if (index.column() == Column::Name && role == Qt::DisplayRole) + return m_categories.at(index.row()).first; + if (index.column() >= Column::Debug && index.column() <= Column::Info + && role == Qt::CheckStateRole) { + auto entry = m_categories.at(index.row()).second; + const bool isEnabled = entry->isEnabled( + static_cast<QtMsgType>(index.column() - Column::Debug)); + return isEnabled ? Qt::Checked : Qt::Unchecked; + } + return {}; + } + + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) final + { + if (!index.isValid()) + return false; + if (role == Qt::CheckStateRole && index.column() >= Column::Debug + && index.column() <= Column::Info) { + QtMsgType msgType = static_cast<QtMsgType>(index.column() - Column::Debug); + QLoggingCategory * const cat = m_categories[index.row()].second; + bool isEnabled = cat->isEnabled(msgType); + const Qt::CheckState current = isEnabled ? Qt::Checked : Qt::Unchecked; + if (current != value.toInt()) { + cat->setEnabled(msgType, value.toInt() == Qt::Checked); + emit categoryChanged(m_categories[index.row()].first, cat); + return true; + } + } + return false; + } + + Qt::ItemFlags flags(const QModelIndex &index) const final + { + if (!index.isValid() || index.column() == LoggingCategoryModel::Column::Fatal) + return Qt::NoItemFlags; + if (index.column() == Column::Name) + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; + return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; + } + + QVariant headerData( + int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const final + { + if (role != Qt::DisplayRole || orientation != Qt::Horizontal) + return {}; + + switch (section) { + case Column::Name: + return Tr::tr("Category"); + case Column::Debug: + return Tr::tr("Debug"); + case Column::Warning: + return Tr::tr("Warning"); + case Column::Critical: + return Tr::tr("Critical"); + case Column::Fatal: + return Tr::tr("Fatal"); + case Column::Info: + return Tr::tr("Info"); + default: + break; + } + + return {}; + } + + void reset() + { + beginResetModel(); + m_categories.clear(); + endResetModel(); + } + +signals: + void categoryChanged(QString name, QLoggingCategory *category); + +private: + QList<QPair<QString, QLoggingCategory *>> m_categories; +}; + AppOutputPane::RunControlTab::RunControlTab(RunControl *runControl, Core::OutputWindow *w) : runControl(runControl), window(w) { @@ -338,9 +630,17 @@ void AppOutputPane::setFocus() void AppOutputPane::updateFilter() { if (RunControlTab * const tab = currentTab()) { - tab->window->updateFilterProperties(filterText(), filterCaseSensitivity(), - filterUsesRegexp(), filterIsInverted(), - beforeContext(), afterContext()); + auto appwindow = qobject_cast<AppOutputWindow*>(tab->window); + appwindow->updateCategoriesProperties(appwindow->registry()->categories()); + if (!tab->window->updateFilterProperties( + filterText(), + filterCaseSensitivity(), + filterUsesRegexp(), + filterIsInverted(), + beforeContext(), + afterContext())) { + tab->window->filterNewContent(); + } } } @@ -402,8 +702,14 @@ void AppOutputPane::createNewOutputWindow(RunControl *rc) }); const auto updateOutputFileName = [this](int index, RunControl *rc) { qobject_cast<OutputWindow *>(m_tabWidget->widget(index)) - //: file name suggested for saving application output, %1 = run configuration display name - ->setOutputFileNameHint(Tr::tr("application-output-%1.txt").arg(rc->displayName())); + //: file name suggested for saving application output, %1 = run configuration display name + ->setOutputFileNameHint(Tr::tr("application-output-%1.txt").arg(rc->displayName())); + }; + const auto updateOutputFiltersWidget = [this](int index, RunControl *rc) { + const auto aspect = rc->aspectData<EnableCategoriesFilterAspect>(); + const bool filterEnabled = aspect && aspect->value; + m_tabWidget->filtersWidget(index)->setVisible(filterEnabled); + qobject_cast<AppOutputWindow *>(m_tabWidget->widget(index))->setFilterEnabled(filterEnabled); }; if (tab != m_runControlTabs.end()) { // Reuse this tab @@ -421,6 +727,7 @@ void AppOutputPane::createNewOutputWindow(RunControl *rc) QTC_ASSERT(tabIndex != -1, return); m_tabWidget->setTabText(tabIndex, rc->displayName()); updateOutputFileName(tabIndex, rc); + updateOutputFiltersWidget(tabIndex, rc); tab->window->scrollToBottom(); qCDebug(appOutputLog) << "AppOutputPane::createNewOutputWindow: Reusing tab" @@ -431,7 +738,7 @@ void AppOutputPane::createNewOutputWindow(RunControl *rc) static int counter = 0; Id contextId = Id(C_APP_OUTPUT).withSuffix(counter++); Core::Context context(contextId); - Core::OutputWindow *ow = new Core::OutputWindow(context, SETTINGS_KEY, m_tabWidget); + AppOutputWindow *ow = new AppOutputWindow(context, SETTINGS_KEY, m_tabWidget); ow->setWindowTitle(Tr::tr("Application Output Window")); ow->setWindowIcon(Icons::WINDOW.icon()); ow->setWordWrapEnabled(m_settings.wrapOutput); @@ -464,9 +771,142 @@ void AppOutputPane::createNewOutputWindow(RunControl *rc) connect(TextEditor::TextEditorSettings::instance(), &TextEditor::TextEditorSettings::behaviorSettingsChanged, ow, updateBehaviorSettings); + auto qtInternal = new QToolButton; + qtInternal->setIcon(Core::Icons::QTLOGO.icon()); + qtInternal->setToolTip(Tr::tr("Filter Qt Internal Log Categories")); + qtInternal->setCheckable(false); + + LoggingCategoryModel *categoryModel = new LoggingCategoryModel(this); + QSortFilterProxyModel *sortFilterModel = new QSortFilterProxyModel(this); + sortFilterModel->setSourceModel(categoryModel); + sortFilterModel->sort(LoggingCategoryModel::Column::Name); + sortFilterModel->setFilterKeyColumn(LoggingCategoryModel::Column::Name); + + connect(ow->registry(), &LoggingCategoryRegistry::newLogCategory, + categoryModel, &LoggingCategoryModel::append); + connect(categoryModel,&LoggingCategoryModel::categoryChanged, + this, &AppOutputPane::updateFilter); + + BaseTreeView *categoryView = new BaseTreeView; + categoryView->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + categoryView->setFrameStyle(QFrame::Box); + categoryView->setAttribute(Qt::WA_MacShowFocusRect, false); + categoryView->setSelectionMode(QAbstractItemView::SingleSelection); + categoryView->setContextMenuPolicy(Qt::CustomContextMenu); + categoryView->setModel(sortFilterModel); + + for (int i = LoggingCategoryModel::Column::Name + 1; i < LoggingCategoryModel::Column::Info; i++) + categoryView->resizeColumnToContents(i); + + auto filterEdit = new Utils::FancyLineEdit; + filterEdit->setHistoryCompleter("LogFilterCompletionHistory"); + filterEdit->setFiltering(true); + filterEdit->setPlaceholderText(Tr::tr("Filter categories by regular expression")); + filterEdit->setValidationFunction( + [](const QString &input) { + return Utils::asyncRun([input]() -> Utils::Result<QString> { + QRegularExpression re(input); + if (re.isValid()) + return input; + + return ResultError( + Tr::tr("Invalid regular expression: %1").arg(re.errorString())); + }); + }); + connect(filterEdit, + &Utils::FancyLineEdit::textChanged, + sortFilterModel, + [sortFilterModel](const QString &f) { + QRegularExpression re(f); + if (re.isValid()) + sortFilterModel->setFilterRegularExpression(f); + }); + + connect(categoryView, + &QAbstractItemView::customContextMenuRequested, + this, + [=] (const QPoint &pos) { + QModelIndex idx = categoryView->indexAt(pos); + + QMenu m; + auto uncheckAll = new QAction(Tr::tr("Uncheck All"), &m); + + auto isTypeColumn = [](int column) { + return column >= LoggingCategoryModel::Column::Debug + && column <= LoggingCategoryModel::Column::Info; + }; + + auto setChecked = [sortFilterModel](std::initializer_list<LoggingCategoryModel::Column> columns, + Qt::CheckState checked) { + for (int row = 0, count = sortFilterModel->rowCount(); row < count; ++row) { + for (int column : columns) { + sortFilterModel->setData(sortFilterModel->index(row, column), + checked, + Qt::CheckStateRole); + } + } + }; + + if (idx.isValid() && isTypeColumn(idx.column())) { + const LoggingCategoryModel::Column column = static_cast<LoggingCategoryModel::Column>( + idx.column()); + bool isChecked = idx.data(Qt::CheckStateRole).toInt() == Qt::Checked; + const QString uncheckText = isChecked ? Tr::tr("Uncheck All %1") : Tr::tr("Check All %1"); + + uncheckAll->setText(uncheckText.arg(messageTypeToString( + static_cast<QtMsgType>(column - LoggingCategoryModel::Column::Debug)))); + + Qt::CheckState newState = isChecked ? Qt::Unchecked : Qt::Checked; + + connect(uncheckAll, + &QAction::triggered, + sortFilterModel, + [setChecked, column, newState]() { setChecked({column}, newState); }); + + } else { + // No need to add Fatal here, as it is read-only + static auto allColumns = {LoggingCategoryModel::Column::Debug, + LoggingCategoryModel::Column::Warning, + LoggingCategoryModel::Column::Critical, + LoggingCategoryModel::Column::Info}; + + connect(uncheckAll, &QAction::triggered, sortFilterModel, [setChecked]() { + setChecked(allColumns, Qt::Unchecked); + }); + } + + m.addAction(uncheckAll); + m.exec(categoryView->mapToGlobal(pos)); + }); + + connect(qtInternal, &QToolButton::clicked, filterEdit, [filterEdit] { + filterEdit->setText("^(qt\\.).+"); + }); + + connect(ow, &OutputWindow::cleanOldOutput, ow, [ow, categoryModel]() { + categoryModel->reset(); + ow->updateCategoriesProperties({}); + ow->registry()->reset(); + }); + + QWidget* cv = new QWidget; + + using namespace Layouting; + // clang-format off + Column { + noMargin, + Row { + qtInternal, + filterEdit, + }, + categoryView, + }.attachTo(cv); + // clang-format on + m_runControlTabs.push_back(RunControlTab(rc, ow)); - m_tabWidget->addTab(ow, rc->displayName()); + m_tabWidget->addTab(ow, cv, rc->displayName()); updateOutputFileName(m_tabWidget->count() - 1, rc); + updateOutputFiltersWidget(m_tabWidget->count() - 1, rc); qCDebug(appOutputLog) << "AppOutputPane::createNewOutputWindow: Adding tab for" << rc; updateCloseActions(); setFilteringEnabled(m_tabWidget->count() > 0); @@ -478,6 +918,8 @@ void AppOutputPane::handleOldOutput(Core::OutputWindow *window) const window->clear(); else window->grayOutOldContent(); + + emit window->cleanOldOutput(); } void AppOutputPane::updateFromSettings() @@ -498,6 +940,16 @@ void AppOutputPane::appendMessage(RunControl *rc, const QString &out, OutputForm if (!tab) return; + if (qobject_cast<AppOutputWindow *>(tab->window)->filterEnabled()) { + const QStringList lines = out.split('\n'); + for (const QString &line : lines) { + if (line.contains("_logging_categories") && line.contains("CATEGORY:")) { + auto appwindow = qobject_cast<AppOutputWindow*>(tab->window); + appwindow->registry()->onNewCategory(line.section("CATEGORY:", 1, 1).section('\n', 0, 0)); + } + } + } + QString stringToWrite; if (format == NormalMessageFormat || format == ErrorMessageFormat) { stringToWrite = QTime::currentTime().toString(); @@ -777,9 +1229,12 @@ void AppOutputPane::tabChanged(int i) { RunControlTab * const controlTab = tabFor(m_tabWidget->widget(i)); if (i != -1 && controlTab) { - controlTab->window->updateFilterProperties(filterText(), filterCaseSensitivity(), - filterUsesRegexp(), filterIsInverted(), - beforeContext(), afterContext()); + auto appwindow = qobject_cast<AppOutputWindow*>(controlTab->window); + appwindow->updateCategoriesProperties(appwindow->registry()->categories()); + if (!controlTab->window->updateFilterProperties(filterText(), filterCaseSensitivity(), + filterUsesRegexp(), filterIsInverted(), + beforeContext(), afterContext())) + controlTab->window->filterNewContent(); enableButtons(controlTab->runControl); } else { enableDefaultButtons(); @@ -1019,3 +1474,5 @@ QColor AppOutputSettings::effectiveBackgroundColor() const } // namespace Internal } // namespace ProjectExplorer + +#include "appoutputpane.moc" diff --git a/src/plugins/projectexplorer/desktoprunconfiguration.cpp b/src/plugins/projectexplorer/desktoprunconfiguration.cpp index bd9bab32e92..c77d8364f29 100644 --- a/src/plugins/projectexplorer/desktoprunconfiguration.cpp +++ b/src/plugins/projectexplorer/desktoprunconfiguration.cpp @@ -47,6 +47,7 @@ public: } runAsRoot.setVisible(HostOsInfo::isAnyUnixHost()); + enableCategoriesFilterAspect.setEnabled(kit()->supportsQtCategoryFilter()); environment.addModifier([this](Environment &env) { BuildTargetInfo bti = buildTargetInfo(); @@ -85,6 +86,7 @@ private: UseDyldSuffixAspect useDyldSuffix{this}; UseLibraryPathsAspect useLibraryPaths{this}; RunAsRootAspect runAsRoot{this}; + EnableCategoriesFilterAspect enableCategoriesFilterAspect{this}; }; void DesktopRunConfiguration::updateTargetInformation() diff --git a/src/plugins/projectexplorer/devicesupport/devicekitaspects.cpp b/src/plugins/projectexplorer/devicesupport/devicekitaspects.cpp index d9c9e60291c..9612b947182 100644 --- a/src/plugins/projectexplorer/devicesupport/devicekitaspects.cpp +++ b/src/plugins/projectexplorer/devicesupport/devicekitaspects.cpp @@ -298,11 +298,6 @@ private: connect(dm, &DeviceManager::deviceAdded, this, &DeviceKitAspectFactory::devicesChanged); connect(dm, &DeviceManager::deviceRemoved, this, &DeviceKitAspectFactory::devicesChanged); connect(dm, &DeviceManager::deviceUpdated, this, &DeviceKitAspectFactory::deviceUpdated); - - connect(KitManager::instance(), &KitManager::kitUpdated, - this, &DeviceKitAspectFactory::setup); - connect(KitManager::instance(), &KitManager::unmanagedKitUpdated, - this, &DeviceKitAspectFactory::setup); } void deviceUpdated(Id id) diff --git a/src/plugins/projectexplorer/devicesupport/sshparameters.cpp b/src/plugins/projectexplorer/devicesupport/sshparameters.cpp index 327f1ef9fe1..c4753bb62d1 100644 --- a/src/plugins/projectexplorer/devicesupport/sshparameters.cpp +++ b/src/plugins/projectexplorer/devicesupport/sshparameters.cpp @@ -237,9 +237,9 @@ SshParameters SshParametersAspectContainer::sshParameters() const QTC_ASSERT(QThread::currentThread() == thread(), return SshParameters()); SshParameters params; - params.setHost(host.expandedValue()); + params.setHost(host()); params.setPort(port.value()); - params.setUserName(userName.expandedValue()); + params.setUserName(userName()); params.setPrivateKeyFile(privateKeyFile.expandedValue()); params.setTimeout(timeout.value()); params.setAuthenticationType( diff --git a/src/plugins/projectexplorer/kit.cpp b/src/plugins/projectexplorer/kit.cpp index 665044f6cec..4eca94b4206 100644 --- a/src/plugins/projectexplorer/kit.cpp +++ b/src/plugins/projectexplorer/kit.cpp @@ -564,16 +564,26 @@ void Kit::addToRunEnvironment(Environment &env) const factory->addToRunEnvironment(this, env); } -QString Kit::moduleForHeader(const QString &headerFileName) const +template<typename T> static T getInfo(const Kit *k, const Id request, const QVariant &input) { for (KitAspectFactory *factory : KitManager::kitAspectFactories()) { - const QVariant module = factory->getInfo(this, "moduleForHeader", headerFileName); - if (module.canConvert<QString>()) - return module.toString(); + const QVariant module = factory->getInfo(k, request, input); + if (module.isValid()) + return module.value<T>(); } return {}; } +QString Kit::moduleForHeader(const QString &headerFileName) const +{ + return getInfo<QString>(this, "moduleForHeader", headerFileName); +} + +bool Kit::supportsQtCategoryFilter() const +{ + return getInfo<bool>(this, "supportsQtCategoryFilter", {}); +} + Environment Kit::buildEnvironment() const { IDevice::ConstPtr device = BuildDeviceKitAspect::device(this); diff --git a/src/plugins/projectexplorer/kit.h b/src/plugins/projectexplorer/kit.h index 4ce4416ce3a..d50ea398c49 100644 --- a/src/plugins/projectexplorer/kit.h +++ b/src/plugins/projectexplorer/kit.h @@ -106,6 +106,7 @@ public: QList<Utils::OutputLineParser *> createOutputParsers() const; QString moduleForHeader(const QString &className) const; + bool supportsQtCategoryFilter() const; QString toHtml(const Tasks &additional = Tasks(), const QString &extraText = QString()) const; Kit *clone(bool keepName = false) const; diff --git a/src/plugins/projectexplorer/kitaspect.cpp b/src/plugins/projectexplorer/kitaspect.cpp index dec40e152b2..732c264276f 100644 --- a/src/plugins/projectexplorer/kitaspect.cpp +++ b/src/plugins/projectexplorer/kitaspect.cpp @@ -210,8 +210,14 @@ void KitAspect::refresh() la.comboBox->model()->sort(0); const QVariant itemId = la.spec.getter(*k); int idx = la.comboBox->findData(itemId, IdRole); - if (idx == -1) + if (idx == -1) { idx = la.comboBox->count() - 1; + if (QTC_UNEXPECTED(itemId.isValid())) { + qWarning() << factory()->displayName(); + const QVariant newId = idx < 0 ? QVariant() : la.comboBox->itemData(idx, IdRole); + la.spec.setter(*kit(), newId); + } + } la.comboBox->setCurrentIndex(idx); la.comboBox->setEnabled(!d->readOnly && la.comboBox->count() > 1); } diff --git a/src/plugins/projectexplorer/project.cpp b/src/plugins/projectexplorer/project.cpp index 20f2481097b..860f91ec48c 100644 --- a/src/plugins/projectexplorer/project.cpp +++ b/src/plugins/projectexplorer/project.cpp @@ -1126,28 +1126,47 @@ bool Project::isKnownFile(const FilePath &filename) const const Node *Project::nodeForFilePath(const FilePath &filePath, const NodeMatcher &extraMatcher) const { + const QList<const Node *> nodes = nodesForFilePath(filePath, extraMatcher); + return nodes.isEmpty() ? nullptr : nodes.first(); +} + +QList<const Node *> Project::nodesForFilePath(const Utils::FilePath &filePath, + const NodeMatcher &extraMatcher) const +{ const FileNode dummy(filePath, FileType::Unknown); const auto range = std::equal_range(d->m_sortedNodeList.cbegin(), d->m_sortedNodeList.cend(), - &dummy, &nodeLessThan); + &dummy, &nodeLessThan); + QList<const Node *> nodes; for (auto it = range.first; it != range.second; ++it) { if ((*it)->filePath() == filePath && (!extraMatcher || extraMatcher(*it))) - return *it; + nodes << *it; } - return nullptr; + return nodes; } ProjectNode *Project::productNodeForFilePath( const Utils::FilePath &filePath, const NodeMatcher &extraMatcher) const { - const Node * const fileNode = nodeForFilePath(filePath, extraMatcher); - if (!fileNode) - return nullptr; - for (ProjectNode *projectNode = fileNode->parentProjectNode(); projectNode; - projectNode = projectNode->parentProjectNode()) { - if (projectNode->isProduct()) - return projectNode; + const QList<const Node *> fileNodes = nodesForFilePath(filePath, extraMatcher); + QList<ProjectNode *> candidates; + for (const Node * const fileNode : fileNodes) { + for (ProjectNode *projectNode = fileNode->parentProjectNode(); projectNode; + projectNode = projectNode->parentProjectNode()) { + if (projectNode->isProduct()) { + + // If there are several candidates, we prefer real products to pseudo-products. + // See QTCREATORBUG-33224. For now, we assume this is what all callers want. + // If the need arises, we can make this configurable. + if (projectNode->productType() == ProductType::App + || projectNode->productType() == ProductType::Lib) { + return projectNode; + } + + candidates << projectNode; + } + } } - return nullptr; + return candidates.isEmpty() ? nullptr : candidates.first(); } FilePaths Project::binariesForSourceFile(const FilePath &sourceFile) const diff --git a/src/plugins/projectexplorer/project.h b/src/plugins/projectexplorer/project.h index 69f59a8c7d6..d6992b9da2a 100644 --- a/src/plugins/projectexplorer/project.h +++ b/src/plugins/projectexplorer/project.h @@ -134,6 +134,8 @@ public: bool isKnownFile(const Utils::FilePath &filename) const; const Node *nodeForFilePath(const Utils::FilePath &filePath, const NodeMatcher &extraMatcher = {}) const; + QList<const Node *> nodesForFilePath(const Utils::FilePath &filePath, + const NodeMatcher &extraMatcher = {}) const; ProjectNode *productNodeForFilePath( const Utils::FilePath &filePath, const NodeMatcher &extraMatcher = {}) const; Utils::FilePaths binariesForSourceFile(const Utils::FilePath &sourceFile) const; diff --git a/src/plugins/projectexplorer/projectexplorersettings.cpp b/src/plugins/projectexplorer/projectexplorersettings.cpp index 938b84de6d1..7e47ce64772 100644 --- a/src/plugins/projectexplorer/projectexplorersettings.cpp +++ b/src/plugins/projectexplorer/projectexplorersettings.cpp @@ -437,7 +437,7 @@ public: { setPriority(10); setId("ProjectExplorer.BuildAndRunSettings"); - setDisplayName(Tr::tr("Building And Running")); + setDisplayName(Tr::tr("Building and Running")); setCreateWidgetFunction([](Project *project) { return project->projectExplorerSettings().createConfigWidget(); }); @@ -483,7 +483,7 @@ PerProjectProjectExplorerSettings::PerProjectProjectExplorerSettings(Project *pr st, }; }); - setDisplayName(Tr::tr("Building And Running")); + setDisplayName(Tr::tr("Building and Running")); setUsingGlobalSettings(true); resetProjectToGlobalSettings(); setConfigWidgetCreator([this] { return createGlobalOrProjectAspectWidget(this); }); diff --git a/src/plugins/projectexplorer/runconfiguration.cpp b/src/plugins/projectexplorer/runconfiguration.cpp index 7b038767238..8d62fd56338 100644 --- a/src/plugins/projectexplorer/runconfiguration.cpp +++ b/src/plugins/projectexplorer/runconfiguration.cpp @@ -631,7 +631,17 @@ ProcessRunData RunConfiguration::runnable() const if (auto workingDirectoryAspect = aspect<WorkingDirectoryAspect>()) r.workingDirectory = r.command.executable().withNewMappedPath(workingDirectoryAspect->workingDirectory()); if (auto environmentAspect = aspect<EnvironmentAspect>()) + { r.environment = environmentAspect->expandedEnvironment(*macroExpander()); + auto enableCategoriesFilterAspect = aspect<EnableCategoriesFilterAspect>(); + if (enableCategoriesFilterAspect && enableCategoriesFilterAspect->value()) { + r.environment.set("QT_LOGGING_RULES", "_logging_categories=true;*.debug=true"); + r.environment.set("QT_MESSAGE_PATTERN", + "%{category}:[%{if-debug}D%{endif}%{if-info}I%{endif}" + "%{if-warning}W%{endif}%{if-critical}C%{endif}" + "%{if-fatal}F%{endif}] %{message}"); + } + } if (m_runnableModifier) m_runnableModifier(r); diff --git a/src/plugins/projectexplorer/runconfigurationaspects.cpp b/src/plugins/projectexplorer/runconfigurationaspects.cpp index 0640d43924f..0d2b654f1ad 100644 --- a/src/plugins/projectexplorer/runconfigurationaspects.cpp +++ b/src/plugins/projectexplorer/runconfigurationaspects.cpp @@ -786,6 +786,26 @@ RunAsRootAspect::RunAsRootAspect(AspectContainer *container) setVisible(HostOsInfo::isAnyUnixHost()); } +/*! + \class ProjectExplorer::EnableCategoriesFilterAspect + \inmodule QtCreator + + \brief The EnableCategoriesFilterAspect class lets a user specify whether + the application output should show the categories filtering widget. +*/ + +EnableCategoriesFilterAspect::EnableCategoriesFilterAspect(AspectContainer *container) + : BoolAspect(container) +{ + setId("EnableCategoriesFilter"); + setSettingsKey("RunConfiguration.EnableCategoriesFilter"); + setLabel(Tr::tr("Enable logging category filtering"), LabelPlacement::AtCheckBox); + setToolTip( + Tr::tr( + "Enables filtering for logging categories (QLoggingCategory) in the Application " + "Output. Requires Qt 6.11 or later.")); +} + Interpreter::Interpreter() : id(QUuid::createUuid().toString()) {} diff --git a/src/plugins/projectexplorer/runconfigurationaspects.h b/src/plugins/projectexplorer/runconfigurationaspects.h index e31753077ed..285cadc3ff0 100644 --- a/src/plugins/projectexplorer/runconfigurationaspects.h +++ b/src/plugins/projectexplorer/runconfigurationaspects.h @@ -150,6 +150,14 @@ public: RunAsRootAspect(Utils::AspectContainer *container = nullptr); }; +class PROJECTEXPLORER_EXPORT EnableCategoriesFilterAspect : public Utils::BoolAspect +{ + Q_OBJECT + +public: + EnableCategoriesFilterAspect(Utils::AspectContainer *container = nullptr); +}; + class PROJECTEXPLORER_EXPORT ExecutableAspect : public Utils::BaseAspect { Q_OBJECT diff --git a/src/plugins/projectexplorer/toolchain.cpp b/src/plugins/projectexplorer/toolchain.cpp index 163600dd4b0..877b2ba5671 100644 --- a/src/plugins/projectexplorer/toolchain.cpp +++ b/src/plugins/projectexplorer/toolchain.cpp @@ -758,12 +758,6 @@ Id ToolchainFactory::supportedToolchainType() const return m_supportedToolchainType; } -std::optional<AsyncToolchainDetector> ToolchainFactory::asyncAutoDetector( - const ToolchainDetector &) const -{ - return {}; -} - void ToolchainFactory::setSupportedToolchainType(const Id &supportedToolchainType) { m_supportedToolchainType = supportedToolchainType; @@ -855,39 +849,6 @@ BadToolchains BadToolchains::fromVariant(const QVariant &v) [](const QVariant &e) { return BadToolchain::fromMap(storeFromVariant(e)); }); } -AsyncToolchainDetector::AsyncToolchainDetector( - const ToolchainDetector &detector, - const std::function<Toolchains(const ToolchainDetector &)> &func, - const std::function<bool(const Toolchain *, const Toolchains &)> &alreadyRegistered) - : m_detector(detector) - , m_func(func) - , m_alreadyRegistered(alreadyRegistered) -{ -} - -void AsyncToolchainDetector::run() -{ - auto watcher = new QFutureWatcher<Toolchains>(); - QObject::connect(watcher, - &QFutureWatcher<Toolchains>::finished, - [watcher, - alreadyRegistered = m_alreadyRegistered]() { - Toolchains existingTcs = ToolchainManager::toolchains(); - Toolchains toRegister; - for (Toolchain *tc : watcher->result()) { - if (tc->isValid() && !alreadyRegistered(tc, existingTcs)) { - toRegister << tc; - existingTcs << tc; - } else { - delete tc; - } - } - ToolchainManager::registerToolchains(toRegister); - watcher->deleteLater(); - }); - watcher->setFuture(Utils::asyncRun(m_func, m_detector)); -} - /* * PRE: * - The list of toolchains is not empty. diff --git a/src/plugins/projectexplorer/toolchain.h b/src/plugins/projectexplorer/toolchain.h index 188763bc3db..6352c1ab4cb 100644 --- a/src/plugins/projectexplorer/toolchain.h +++ b/src/plugins/projectexplorer/toolchain.h @@ -24,7 +24,6 @@ #include <functional> #include <memory> -#include <optional> namespace Utils { class OutputLineParser; } @@ -352,20 +351,6 @@ public: const Utils::FilePaths searchPaths; // If empty use device path and/or magic. }; -class PROJECTEXPLORER_EXPORT AsyncToolchainDetector -{ -public: - AsyncToolchainDetector( - const ToolchainDetector &detector, - const std::function<Toolchains(const ToolchainDetector &)> &func, - const std::function<bool(const Toolchain *, const Toolchains &)> &alreadyRegistered); - void run(); -private: - ToolchainDetector m_detector; - std::function<Toolchains(const ToolchainDetector &)> m_func; - std::function<bool(Toolchain *, const Toolchains &)> m_alreadyRegistered; -}; - class PROJECTEXPLORER_EXPORT ToolchainFactory { ToolchainFactory(const ToolchainFactory &) = delete; @@ -381,8 +366,6 @@ public: QString displayName() const { return m_displayName; } Utils::Id supportedToolchainType() const; - virtual std::optional<AsyncToolchainDetector> asyncAutoDetector( - const ToolchainDetector &detector) const; virtual Toolchains autoDetect(const ToolchainDetector &detector) const; virtual Toolchains detectForImport(const ToolchainDescription &tcd) const; virtual std::unique_ptr<ToolchainConfigWidget> createConfigurationWidget( diff --git a/src/plugins/projectexplorer/toolchainkitaspect.cpp b/src/plugins/projectexplorer/toolchainkitaspect.cpp index ad531c02fb7..cb6dea952b0 100644 --- a/src/plugins/projectexplorer/toolchainkitaspect.cpp +++ b/src/plugins/projectexplorer/toolchainkitaspect.cpp @@ -220,13 +220,19 @@ void ToolchainKitAspectFactory::fix(Kit *k) { QTC_ASSERT(ToolchainManager::isLoaded(), return); const QList<Id> languages = ToolchainManager::allLanguages(); + const IDeviceConstPtr dev = BuildDeviceKitAspect::device(k); for (const Id l : languages) { const QByteArray tcId = ToolchainKitAspect::toolchainId(k, l); - if (!tcId.isEmpty() && !ToolchainManager::findToolchain(tcId)) { + if (tcId.isEmpty()) + continue; + Toolchain * const tc = ToolchainManager::findToolchain(tcId); + if (!tc) { qWarning("Tool chain set up in kit \"%s\" for \"%s\" not found.", qPrintable(k->displayName()), qPrintable(ToolchainManager::displayNameOfLanguageId(l))); ToolchainKitAspect::clearToolchain(k, l); // make sure to clear out no longer known tool chains + } else if (!dev || !dev->rootPath().isSameDevice(tc->compilerCommand())) { + ToolchainKitAspect::clearToolchain(k, l); } } } @@ -260,6 +266,8 @@ static void setToolchainsFromAbis(Kit *k, const LanguagesAndAbis &abisByLanguage abisByCategory.insert(category, langAndAbi.second); } + const IDeviceConstPtr dev = BuildDeviceKitAspect::device(k); + // Get bundles. const QList<ToolchainBundle> bundles = ToolchainBundle::collectBundles( ToolchainBundle::HandleMissing::CreateAndRegister); @@ -267,7 +275,13 @@ static void setToolchainsFromAbis(Kit *k, const LanguagesAndAbis &abisByLanguage // Set a matching bundle for each LanguageCategory/Abi pair, if possible. for (auto it = abisByCategory.cbegin(); it != abisByCategory.cend(); ++it) { const QList<ToolchainBundle> matchingBundles - = Utils::filtered(bundles, [&it](const ToolchainBundle &b) { + = Utils::filtered(bundles, [&](const ToolchainBundle &b) { + if (!dev) + return false; + for (const Toolchain * const tc : b.toolchains()) { + if (!dev->rootPath().isSameDevice(tc->compilerCommand())) + return false; + } return b.factory() && b.factory()->languageCategory() == it.key() && b.targetAbi() == it.value(); }); diff --git a/src/plugins/projectexplorer/toolchainsettingsaccessor.cpp b/src/plugins/projectexplorer/toolchainsettingsaccessor.cpp index 0706639a063..b9dfe4fdcb1 100644 --- a/src/plugins/projectexplorer/toolchainsettingsaccessor.cpp +++ b/src/plugins/projectexplorer/toolchainsettingsaccessor.cpp @@ -69,18 +69,7 @@ static Toolchains autoDetectToolchains(const ToolchainDetector &detector) {"factory", f->displayName().toStdString()}); QElapsedTimer et; et.start(); - if (std::optional<AsyncToolchainDetector> asyncDetector = f->asyncAutoDetector(detector)) { - Toolchains known = Utils::filtered(detector.alreadyKnown, - [supportedType = f->supportedToolchainType()]( - const Toolchain *tc) { - return tc->typeId() == supportedType - && tc->isValid(); - }); - result.append(known); - asyncDetector->run(); - } else { - result.append(f->autoDetect(detector)); - } + result.append(f->autoDetect(detector)); qCDebug(Log) << f->displayName() << "auto detection took: " << et.elapsed() << "ms"; } diff --git a/src/plugins/python/pipsupport.cpp b/src/plugins/python/pipsupport.cpp index 5b55b8c5562..be5e0ed8f36 100644 --- a/src/plugins/python/pipsupport.cpp +++ b/src/plugins/python/pipsupport.cpp @@ -13,150 +13,106 @@ #include <projectexplorer/projectmanager.h> #include <projectexplorer/target.h> +#include <solutions/tasking/tasktree.h> + #include <utils/algorithm.h> #include <utils/mimeutils.h> #include <utils/qtcprocess.h> +using namespace Core; +using namespace Tasking; using namespace Utils; namespace Python::Internal { -PipInstallTask::PipInstallTask(const FilePath &python) - : m_python(python) -{ - 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); -} - -void PipInstallTask::setRequirements(const Utils::FilePath &requirementFile) -{ - m_requirementsFile = requirementFile; -} - -void PipInstallTask::setWorkingDirectory(const Utils::FilePath &workingDirectory) -{ - m_process.setWorkingDirectory(workingDirectory); -} - -void PipInstallTask::addPackage(const PipPackage &package) -{ - m_packages << package; -} - -void PipInstallTask::setPackages(const QList<PipPackage> &packages) -{ - m_packages = packages; -} - -void PipInstallTask::setTargetPath(const Utils::FilePath &targetPath) +QString PipInstallerData::packagesDisplayName() const { - m_targetPath = targetPath; + return requirementsFile.isEmpty() + ? Utils::transform(packages, &PipPackage::displayName).join(", ") + : requirementsFile.toUserOutput(); } -void PipInstallTask::run() +Group pipInstallerTask(const PipInstallerData &data) { - if (m_packages.isEmpty() && m_requirementsFile.isEmpty()) { - emit finished(false); - return; - } - QStringList arguments = {"-m", "pip", "install"}; - if (!m_requirementsFile.isEmpty()) { - arguments << "-r" << m_requirementsFile.toUrlishString(); - } else { - for (const PipPackage &package : std::as_const(m_packages)) { - QString pipPackage = package.packageName; - if (!package.version.isEmpty()) - pipPackage += "==" + package.version; - arguments << pipPackage; + const auto onSetup = [data](Process &process) { + if (data.packages.isEmpty() && data.requirementsFile.isEmpty()) + return SetupResult::StopWithError; + + QStringList arguments = {"-m", "pip", "install"}; + if (!data.requirementsFile.isEmpty()) { + arguments << "-r" << data.requirementsFile.toUrlishString(); + } else { + for (const PipPackage &package : std::as_const(data.packages)) { + QString pipPackage = package.packageName; + if (!package.version.isEmpty()) + pipPackage += "==" + package.version; + arguments << pipPackage; + } } - } - - if (!m_targetPath.isEmpty()) { - QTC_ASSERT(m_targetPath.isSameDevice(m_python), emit finished(false); return); - arguments << "-t" << m_targetPath.path(); - } else if (!isVenvPython(m_python)) { - arguments << "--user"; // add --user to global pythons, but skip it for venv pythons - } - - if (m_upgrade) - arguments << "--upgrade"; - - QString operation; - if (!m_requirementsFile.isEmpty()) { - operation = m_upgrade ? Tr::tr("Update Requirements") : Tr::tr("Install Requirements"); - } else if (m_packages.count() == 1) { - //: %1 = package name - operation = m_upgrade ? Tr::tr("Update %1") - //: %1 = package name - : Tr::tr("Install %1"); - operation = operation.arg(m_packages.first().displayName); - } else { - operation = m_upgrade ? Tr::tr("Update Packages") : Tr::tr("Install Packages"); - } - - m_process.setCommand({m_python, arguments}); - m_process.setTerminalMode(m_silent ? TerminalMode::Off : TerminalMode::Run); - auto progress = new Core::ProcessProgress(&m_process); - progress->setDisplayName(operation); - m_process.start(); - Core::MessageManager::writeSilently( - Tr::tr("Running \"%1\" to install %2.") - .arg(m_process.commandLine().toUserOutput(), packagesDisplayName())); - - m_killTimer.setSingleShot(true); - m_killTimer.start(5 /*minutes*/ * 60 * 1000); -} - -void PipInstallTask::cancel() -{ - m_process.close(); - Core::MessageManager::writeFlashing( - Tr::tr("The installation of \"%1\" was canceled by timeout.").arg(packagesDisplayName())); -} - -void PipInstallTask::handleDone() -{ - m_killTimer.stop(); - const bool success = m_process.result() == ProcessResult::FinishedWithSuccess; - if (!success) { - Core::MessageManager::writeFlashing(Tr::tr("Installing \"%1\" failed: %2") - .arg(packagesDisplayName(), m_process.exitMessage())); - } - emit finished(success); -} - -void PipInstallTask::handleOutput() -{ - const QString &stdOut = QString::fromLocal8Bit(m_process.readAllRawStandardOutput().trimmed()); - if (!stdOut.isEmpty()) - Core::MessageManager::writeSilently(stdOut); -} - -void PipInstallTask::handleError() -{ - const QString &stdErr = QString::fromLocal8Bit(m_process.readAllRawStandardError().trimmed()); - if (!stdErr.isEmpty()) - Core::MessageManager::writeSilently(stdErr); -} - -QString PipInstallTask::packagesDisplayName() const -{ - return m_requirementsFile.isEmpty() - ? Utils::transform(m_packages, &PipPackage::displayName).join(", ") - : m_requirementsFile.toUserOutput(); -} + if (!data.targetPath.isEmpty()) { + QTC_ASSERT(data.targetPath.isSameDevice(data.python), return SetupResult::StopWithError); + arguments << "-t" << data.targetPath.path(); + } else if (!isVenvPython(data.python)) { + arguments << "--user"; // add --user to global pythons, but skip it for venv pythons + } -void PipInstallTask::setUpgrade(bool upgrade) -{ - m_upgrade = upgrade; -} + if (data.upgrade) + arguments << "--upgrade"; + + QString operation; + if (!data.requirementsFile.isEmpty()) { + operation = data.upgrade ? Tr::tr("Update Requirements") : Tr::tr("Install Requirements"); + } else if (data.packages.count() == 1) { + //: %1 = package name + operation = data.upgrade ? Tr::tr("Update %1") + //: %1 = package name + : Tr::tr("Install %1"); + operation = operation.arg(data.packages.first().displayName); + } else { + operation = data.upgrade ? Tr::tr("Update Packages") : Tr::tr("Install Packages"); + } -void PipInstallTask::setSilent(bool silent) -{ - m_silent = silent; + process.setCommand({data.python, arguments}); + process.setTerminalMode(data.silent ? TerminalMode::Off : TerminalMode::Run); + if (!data.workingDirectory.isEmpty()) + process.setWorkingDirectory(data.workingDirectory); + auto progress = new ProcessProgress(&process); + progress->setDisplayName(operation); + + MessageManager::writeSilently(Tr::tr("Running \"%1\" to install %2.") + .arg(process.commandLine().toUserOutput(), data.packagesDisplayName())); + + QObject::connect(&process, &Process::readyReadStandardError, &process, [process = &process] { + const QString &stdOut = QString::fromLocal8Bit(process->readAllRawStandardOutput().trimmed()); + if (!stdOut.isEmpty()) + MessageManager::writeSilently(stdOut); + }); + QObject::connect(&process, &Process::readyReadStandardOutput, &process, [process = &process] { + const QString &stdErr = QString::fromLocal8Bit(process->readAllRawStandardError().trimmed()); + if (!stdErr.isEmpty()) + MessageManager::writeSilently(stdErr); + }); + return SetupResult::Continue; + }; + + const auto packagesDisplayName = data.packagesDisplayName(); + + const auto onDone = [packagesDisplayName](const Process &process) { + MessageManager::writeFlashing(Tr::tr("Installing \"%1\" failed: %2") + .arg(packagesDisplayName, process.exitMessage())); + }; + + const auto onTimeout = [packagesDisplayName] { + MessageManager::writeFlashing( + Tr::tr("The installation of \"%1\" was canceled by timeout.").arg(packagesDisplayName)); + }; + + using namespace std::literals::chrono_literals; + return { + ProcessTask(onSetup, onDone, CallDone::OnError) + .withTimeout(5min, onTimeout) + }; } void PipPackageInfo::parseField(const QString &field, const QStringList &data) diff --git a/src/plugins/python/pipsupport.h b/src/plugins/python/pipsupport.h index d254d2049ca..2bea8c4317b 100644 --- a/src/plugins/python/pipsupport.h +++ b/src/plugins/python/pipsupport.h @@ -9,6 +9,8 @@ #include <QTimer> #include <QUrl> +namespace Tasking { class Group; } + namespace Python::Internal { class PipPackageInfo @@ -44,39 +46,20 @@ public: QString version; }; -class PipInstallTask : public QObject +class PipInstallerData { - Q_OBJECT public: - explicit PipInstallTask(const Utils::FilePath &python); - void setRequirements(const Utils::FilePath &requirementFile); - void setWorkingDirectory(const Utils::FilePath &workingDirectory); - void addPackage(const PipPackage &package); - void setPackages(const QList<PipPackage> &packages); - void setTargetPath(const Utils::FilePath &targetPath); - void setUpgrade(bool upgrade); - void setSilent(bool silent); - void run(); - -signals: - void finished(bool success); - -private: - void cancel(); - void handleDone(); - void handleOutput(); - void handleError(); - QString packagesDisplayName() const; - const Utils::FilePath m_python; - QList<PipPackage> m_packages; - Utils::FilePath m_requirementsFile; - Utils::FilePath m_targetPath; - Utils::Process m_process; - bool m_upgrade = false; - bool m_silent = false; - QTimer m_killTimer; + Utils::FilePath python; + Utils::FilePath workingDirectory; + Utils::FilePath requirementsFile; + Utils::FilePath targetPath; + QList<PipPackage> packages; + bool upgrade = false; + bool silent = false; }; +Tasking::Group pipInstallerTask(const PipInstallerData &data); + } // Python::Internal diff --git a/src/plugins/python/pyside.cpp b/src/plugins/python/pyside.cpp index 960537a40ec..3feb92599fd 100644 --- a/src/plugins/python/pyside.cpp +++ b/src/plugins/python/pyside.cpp @@ -128,12 +128,8 @@ void PySideInstaller::installPySide(const FilePath &python, const QString &pySid } } - auto install = new PipInstallTask(python); - connect(install, &PipInstallTask::finished, install, &QObject::deleteLater); - connect(install, &PipInstallTask::finished, this, [this, python, pySide](bool success) { - if (success) - emit pySideInstalled(python, pySide); - }); + PipInstallerData data; + data.python = python; if (availablePySides.isEmpty()) { if (!quiet) { QMessageBox::StandardButton selected = CheckableMessageBox::question( @@ -146,7 +142,7 @@ void PySideInstaller::installPySide(const FilePath &python, const QString &pySid if (selected == QMessageBox::No) return; } - install->setPackages({PipPackage(pySide)}); + data.packages = {PipPackage(pySide)}; } else { QDialog dialog; dialog.setWindowTitle(Tr::tr("Select PySide Version")); @@ -194,13 +190,18 @@ void PySideInstaller::installPySide(const FilePath &python, const QString &pySid const FilePath requirementsFile = FilePath::fromVariant(pySideSelector->currentData()); if (requirementsFile.isEmpty()) { - install->setPackages({PipPackage(pySide)}); + data.packages = {PipPackage(pySide)}; } else { - install->setWorkingDirectory(requirementsFile.parentDir()); - install->setRequirements(requirementsFile); + data.workingDirectory = requirementsFile.parentDir(); + data.requirementsFile = requirementsFile; } } - install->run(); + + const auto onDone = [this, python, pySide] { + emit pySideInstalled(python, pySide); + }; + + m_pipInstallerRunner.start(pipInstallerTask(data), {}, onDone, CallDone::OnSuccess); } void PySideInstaller::handlePySideMissing(const FilePath &python, diff --git a/src/plugins/python/pyside.h b/src/plugins/python/pyside.h index 7c8d6c2043a..d3234d790fc 100644 --- a/src/plugins/python/pyside.h +++ b/src/plugins/python/pyside.h @@ -50,6 +50,7 @@ private: QHash<Utils::FilePath, QList<TextEditor::TextDocument *>> m_infoBarEntries; Tasking::MappedTaskTreeRunner<TextEditor::TextDocument *> m_taskTreeRunner; + Tasking::SingleTaskTreeRunner m_pipInstallerRunner; }; } // Python::Internal diff --git a/src/plugins/python/pythonlanguageclient.cpp b/src/plugins/python/pythonlanguageclient.cpp index c1a244fe88f..13381454988 100644 --- a/src/plugins/python/pythonlanguageclient.cpp +++ b/src/plugins/python/pythonlanguageclient.cpp @@ -27,6 +27,8 @@ #include <projectexplorer/projectmanager.h> #include <projectexplorer/target.h> +#include <solutions/tasking/tasktreerunner.h> + #include <texteditor/textdocument.h> #include <texteditor/texteditor.h> @@ -34,13 +36,13 @@ #include <utils/infobar.h> #include <utils/qtcprocess.h> -#include <QFutureWatcher> #include <QJsonDocument> -#include <QTimer> using namespace LanguageClient; using namespace LanguageServerProtocol; using namespace ProjectExplorer; +using namespace Tasking; +using namespace TextEditor; using namespace Utils; namespace Python::Internal { @@ -207,7 +209,7 @@ void PyLSClient::updateConfiguration() Client::updateConfiguration(doc.object()); } -void PyLSClient::openDocument(TextEditor::TextDocument *document) +void PyLSClient::openDocument(TextDocument *document) { using namespace LanguageServerProtocol; if (reachable()) { @@ -284,7 +286,7 @@ void PyLSClient::updateExtraCompilerContents(ExtraCompiler *compiler, const File target.writeFileContents(compiler->content(file)); } -void PyLSClient::closeExtraCompiler(ProjectExplorer::ExtraCompiler *compiler, const FilePath &file) +void PyLSClient::closeExtraCompiler(ExtraCompiler *compiler, const FilePath &file) { m_extraCompilerOutputDir.pathAppended(file.fileName()).removeFile(); compiler->disconnect(this); @@ -302,20 +304,20 @@ public: void handlePyLSState(const FilePath &python, const PythonLanguageServerState &state, - TextEditor::TextDocument *document); - void resetEditorInfoBar(TextEditor::TextDocument *document); + TextDocument *document); + void resetEditorInfoBar(TextDocument *document); void installPythonLanguageServer(const FilePath &python, - QPointer<TextEditor::TextDocument> document, + QPointer<TextDocument> document, const FilePath &pylsPath, bool silent, bool upgrade); - void openDocument(const FilePath &python, TextEditor::TextDocument *document); + void openDocument(const FilePath &python, TextDocument *document); - QHash<FilePath, QList<TextEditor::TextDocument *>> m_infoBarEntries; - QHash<TextEditor::TextDocument *, QPointer<QFutureWatcher<PythonLanguageServerState>>> - m_runningChecks; + QHash<FilePath, QList<TextDocument *>> m_infoBarEntries; + MappedTaskTreeRunner<TextDocument *> m_documentRunner; + SingleTaskTreeRunner m_pipInstallerRunner; }; void PyLSConfigureAssistant::installPythonLanguageServer(const FilePath &python, - QPointer<TextEditor::TextDocument> document, + QPointer<TextDocument> document, const FilePath &pylsPath, bool silent, bool upgrade) @@ -324,33 +326,32 @@ void PyLSConfigureAssistant::installPythonLanguageServer(const FilePath &python, // Hide all install info bar entries for this python, but keep them in the list // so the language server will be setup properly after the installation is done. - for (TextEditor::TextDocument *additionalDocument : m_infoBarEntries[python]) + for (TextDocument *additionalDocument : m_infoBarEntries[python]) additionalDocument->infoBar()->removeInfo(installPylsInfoBarId); - auto install = new PipInstallTask(python); + PipInstallerData data; + data.python = python; + data.targetPath = pylsPath; + data.packages = {PipPackage{"python-lsp-server[all]", "Python Language Server"}}; + data.upgrade = upgrade; + data.silent = silent; - connect(install, &PipInstallTask::finished, this, - [this, python, document, install](const bool success) { - const QList<TextEditor::TextDocument *> additionalDocuments = m_infoBarEntries.take(python); - if (success) { + const auto onDone = [this, python, document](DoneWith result) { + const QList<TextDocument *> additionalDocuments = m_infoBarEntries.take(python); + if (result == DoneWith::Success) { if (PyLSClient *client = clientForPython(python)) { if (document) LanguageClientManager::openDocumentWithClient(document, client); - for (TextEditor::TextDocument *additionalDocument : additionalDocuments) + for (TextDocument *additionalDocument : additionalDocuments) LanguageClientManager::openDocumentWithClient(additionalDocument, client); } } - install->deleteLater(); - }); - - install->setTargetPath(pylsPath); - install->setPackages({PipPackage{"python-lsp-server[all]", "Python Language Server"}}); - install->setUpgrade(upgrade); - install->setSilent(silent); - install->run(); + }; + + m_pipInstallerRunner.start(pipInstallerTask(data), {}, onDone); } -void PyLSConfigureAssistant::openDocument(const FilePath &python, TextEditor::TextDocument *document) +void PyLSConfigureAssistant::openDocument(const FilePath &python, TextDocument *document) { resetEditorInfoBar(document); if (!PythonSettings::pylsEnabled() || !python.exists() || document->isTemporary()) @@ -361,34 +362,24 @@ void PyLSConfigureAssistant::openDocument(const FilePath &python, TextEditor::Te return; } - using CheckPylsWatcher = QFutureWatcher<PythonLanguageServerState>; - QPointer<CheckPylsWatcher> watcher = new CheckPylsWatcher(); - - // cancel and delete watcher after a 10 second timeout - QTimer::singleShot(10000, this, [watcher]() { - if (watcher) { - watcher->cancel(); - watcher->deleteLater(); - } - }); - - connect(watcher, &CheckPylsWatcher::resultReadyAt, this, - [this, watcher, python, document = QPointer<TextEditor::TextDocument>(document)] { - if (!document || !watcher) - return; - handlePyLSState(python, watcher->result(), document); - }); - connect(watcher, &CheckPylsWatcher::finished, watcher, &CheckPylsWatcher::deleteLater); - connect(watcher, &CheckPylsWatcher::finished, this, [this, document] { - m_runningChecks.remove(document); - }); - watcher->setFuture(Utils::asyncRun(&checkPythonLanguageServer, python)); - m_runningChecks[document] = watcher; + const auto onSetup = [python](Async<PythonLanguageServerState> &task) { + task.setConcurrentCallData(&checkPythonLanguageServer, python); + }; + const auto onDone = [this, python, document = QPointer<TextDocument>(document)]( + const Async<PythonLanguageServerState> &task) { + if (!document || !task.isResultAvailable()) + return; + handlePyLSState(python, task.result(), document); + }; + + using namespace std::literals::chrono_literals; + const Group recipe { AsyncTask<PythonLanguageServerState>(onSetup, onDone).withTimeout(10s) }; + m_documentRunner.start(document, recipe); } void PyLSConfigureAssistant::handlePyLSState(const FilePath &python, const PythonLanguageServerState &state, - TextEditor::TextDocument *document) + TextDocument *document) { if (state.state == PythonLanguageServerState::NotInstallable) return; @@ -451,13 +442,12 @@ void PyLSConfigureAssistant::handlePyLSState(const FilePath &python, } } -void PyLSConfigureAssistant::resetEditorInfoBar(TextEditor::TextDocument *document) +void PyLSConfigureAssistant::resetEditorInfoBar(TextDocument *document) { - for (QList<TextEditor::TextDocument *> &documents : m_infoBarEntries) + for (QList<TextDocument *> &documents : m_infoBarEntries) documents.removeAll(document); document->infoBar()->removeInfo(installPylsInfoBarId); - if (auto watcher = m_runningChecks.value(document)) - watcher->cancel(); + m_documentRunner.resetKey(document); } PyLSConfigureAssistant::PyLSConfigureAssistant() @@ -468,7 +458,7 @@ PyLSConfigureAssistant::PyLSConfigureAssistant() &Core::EditorManager::documentClosed, this, [this](Core::IDocument *document) { - if (auto textDocument = qobject_cast<TextEditor::TextDocument *>(document)) + if (auto textDocument = qobject_cast<TextDocument *>(document)) resetEditorInfoBar(textDocument); }); } @@ -479,7 +469,7 @@ static PyLSConfigureAssistant &pyLSConfigureAssistant() return thePyLSConfigureAssistant; } -void openDocumentWithPython(const FilePath &python, TextEditor::TextDocument *document) +void openDocumentWithPython(const FilePath &python, TextDocument *document) { pyLSConfigureAssistant().openDocument(python, document); } diff --git a/src/plugins/qtsupport/qtkitaspect.cpp b/src/plugins/qtsupport/qtkitaspect.cpp index aaaa905e31d..3233f6bd96c 100644 --- a/src/plugins/qtsupport/qtkitaspect.cpp +++ b/src/plugins/qtsupport/qtkitaspect.cpp @@ -178,14 +178,21 @@ void QtKitAspectFactory::setup(Kit *k) { if (!k || k->hasValue(id())) return; - const Abi tcAbi = ToolchainKitAspect::targetAbi(k); - const Id deviceType = RunDeviceTypeKitAspect::deviceTypeId(k); - const QtVersions matches - = QtVersionManager::versions([&tcAbi, &deviceType](const QtVersion *qt) { - return qt->targetDeviceTypes().contains(deviceType) - && Utils::contains(qt->qtAbis(), [&tcAbi](const Abi &qtAbi) { - return qtAbi.isCompatibleWith(tcAbi); }); + const IDeviceConstPtr buildDev = BuildDeviceKitAspect::device(k); + if (!buildDev) + return; + + const Abi tcAbi = ToolchainKitAspect::targetAbi(k); + const Id runDeviceType = RunDeviceTypeKitAspect::deviceTypeId(k); + const FilePath buildDeviceRoot = buildDev->rootPath(); + + const QtVersions matches = QtVersionManager::versions([&](const QtVersion *qt) { + return buildDeviceRoot.isSameDevice(qt->qmakeFilePath()) + && qt->targetDeviceTypes().contains(runDeviceType) + && Utils::contains(qt->qtAbis(), [&tcAbi](const Abi &qtAbi) { + return qtAbi.isCompatibleWith(tcAbi); + }); }); if (matches.empty()) return; @@ -227,6 +234,10 @@ Tasks QtKitAspectFactory::validate(const Kit *k) const void QtKitAspectFactory::fix(Kit *k) { + const IDeviceConstPtr dev = BuildDeviceKitAspect::device(k); + if (!dev) + return QtKitAspect::setQtVersionId(k, -1); + QTC_ASSERT(QtVersionManager::isLoaded(), return); QtVersion *version = QtKitAspect::qtVersion(k); if (!version) { @@ -237,6 +248,8 @@ void QtKitAspectFactory::fix(Kit *k) } return; } + if (!version->qmakeFilePath().isSameDevice(dev->rootPath())) + return QtKitAspect::setQtVersionId(k, -1); // Set a matching toolchain if we don't have one. if (ToolchainKitAspect::cxxToolchain(k)) @@ -245,9 +258,13 @@ void QtKitAspectFactory::fix(Kit *k) QList<ToolchainBundle> bundles = ToolchainBundle::collectBundles( ToolchainBundle::HandleMissing::CreateAndRegister); using ProjectExplorer::Constants::CXX_LANGUAGE_ID; - bundles = Utils::filtered(bundles, [version](const ToolchainBundle &b) { + bundles = Utils::filtered(bundles, [&](const ToolchainBundle &b) { if (!b.isCompletelyValid() || !b.factory()->languageCategory().contains(CXX_LANGUAGE_ID)) return false; + for (const Toolchain * const tc : b.toolchains()) { + if (!dev->rootPath().isSameDevice(tc->compilerCommand())) + return false; + } return Utils::anyOf(version->qtAbis(), [&b](const Abi &qtAbi) { return b.supportedAbis().contains(qtAbi) && b.targetAbi().wordWidth() == qtAbi.wordWidth() @@ -641,10 +658,14 @@ int QtKitAspectFactory::weight(const Kit *k) const QVariant QtKitAspectFactory::getInfo(const Kit *k, Id request, const QVariant &input) const { - if (request == "moduleForHeader") { - if (const QtVersion * const v = QtKitAspect::qtVersion(k)) - return v->moduleForHeader(input.toString()); - } + QtVersion * const version = QtKitAspect::qtVersion(k); + if (!version || !version->isValid()) + return {}; + + if (request == "moduleForHeader") + return version->moduleForHeader(input.toString()); + if (request == "supportsQtCategoryFilter") + return version->qtVersion() >= QVersionNumber(6, 11); return {}; } diff --git a/src/plugins/remotelinux/linuxdevice.cpp b/src/plugins/remotelinux/linuxdevice.cpp index 55ca6bce3ac..2208a06bc9d 100644 --- a/src/plugins/remotelinux/linuxdevice.cpp +++ b/src/plugins/remotelinux/linuxdevice.cpp @@ -1429,7 +1429,7 @@ void LinuxDevicePrivate::announceConnectionAttempt() QTC_ASSERT(isMainThread(), return); InfoBarEntry info(announceId(), message); info.setTitle(Tr::tr("Establishing a Connection")); - info.setInfoType(InfoLabel::Ok); + info.setInfoType(InfoLabel::Warning); Core::ICore::popupInfoBar()->addInfo(info); Core::MessageManager::writeSilently(message); } @@ -1463,6 +1463,7 @@ void LinuxDevicePrivate::unannounceConnectionAttempt() info.setTitle(Tr::tr("Connection Attempt Finished")); info.setInfoType(infoType); InfoBar *infoBar = Core::ICore::popupInfoBar(); + infoBar->removeInfo(announceId()); infoBar->addInfo(info); Core::MessageManager::writeSilently(message); diff --git a/src/plugins/texteditor/formattexteditor.cpp b/src/plugins/texteditor/formattexteditor.cpp index d29b8d2fc3c..4e863deffc9 100644 --- a/src/plugins/texteditor/formattexteditor.cpp +++ b/src/plugins/texteditor/formattexteditor.cpp @@ -13,15 +13,16 @@ #include <utils/async.h> #include <utils/differ.h> #include <utils/fileutils.h> +#include <utils/globaltasktree.h> #include <utils/qtcassert.h> #include <utils/qtcprocess.h> #include <utils/temporarydirectory.h> #include <utils/textutils.h> -#include <QFutureWatcher> #include <QScrollBar> #include <QTextBlock> +using namespace Tasking; using namespace Utils; using namespace std::chrono_literals; @@ -312,20 +313,22 @@ void formatEditorAsync(TextEditorWidget *editor, const Command &command, int sta if (sd.isEmpty()) return; - auto watcher = new QFutureWatcher<FormatOutput>; const TextDocument *doc = editor->textDocument(); const FormatInput input{doc->filePath(), sd, command, startPos, endPos}; - QObject::connect(doc, &TextDocument::contentsChanged, watcher, - &QFutureWatcher<FormatOutput>::cancel); - QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, - [watcher, editor = QPointer<PlainTextEdit>(editor), input] { - if (watcher->isCanceled()) + const auto onSetup = [input](Async<FormatOutput> &task) { + task.setConcurrentCallData(format, input); + }; + const auto onDone = [editor = QPointer<PlainTextEdit>(editor), input]( + const Async<FormatOutput> &task, DoneWith result) { + if (result == DoneWith::Cancel) showError(Tr::tr("File was modified.")); else - checkAndApplyTask(editor, input, watcher->result()); - watcher->deleteLater(); - }); - watcher->setFuture(Utils::asyncRun(&format, input)); + checkAndApplyTask(editor, input, task.result()); + }; + const auto onTreeSetup = [doc](TaskTree &taskTree) { + QObject::connect(doc, &TextDocument::contentsChanged, &taskTree, &TaskTree::cancel); + }; + GlobalTaskTree::start({AsyncTask<FormatOutput>(onSetup, onDone)}, onTreeSetup); } } // namespace TextEditor diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index 36d8fbd3e85..245e61f6dc0 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -1566,6 +1566,11 @@ void TextEditorWidgetPrivate::setDocument(const QSharedPointer<TextDocument> &do this, &TextEditorWidgetPrivate::scheduleUpdateHighlightScrollBar); + m_documentConnections << connect(q->editorLayout(), + &QAbstractTextDocumentLayout::documentSizeChanged, + this, + &TextEditorWidgetPrivate::scheduleUpdateHighlightScrollBar); + m_documentConnections << connect(documentLayout, &QAbstractTextDocumentLayout::update, this, @@ -7109,11 +7114,16 @@ void TextEditorWidgetPrivate::updateCurrentLineInScrollbar() m_highlightScrollBarController->removeHighlights(Constants::SCROLL_BAR_CURRENT_LINE); for (const QTextCursor &tc : m_cursors) { if (QTextLayout *layout = q->editorLayout()->blockLayout(tc.block())) { - const int pos = q->editorLayout()->firstLineNumberOf(tc.block()) + + const int line = q->editorLayout()->firstLineNumberOf(tc.block()) + layout->lineForTextPosition(tc.positionInBlock()).lineNumber(); - m_highlightScrollBarController->addHighlight({Constants::SCROLL_BAR_CURRENT_LINE, pos, - Theme::TextEditor_CurrentLine_ScrollBarColor, - Highlight::HighestPriority}); + const int pos = q->editorLayout()->offsetForLine(line); + const int lineSpacing = q->editorLayout()->lineSpacing(); + m_highlightScrollBarController->addHighlight( + {Constants::SCROLL_BAR_CURRENT_LINE, + pos, + lineSpacing, + Theme::TextEditor_CurrentLine_ScrollBarColor, + Highlight::HighestPriority}); } } } @@ -8264,7 +8274,6 @@ void TextEditorWidgetPrivate::adjustScrollBarRanges() if (lineSpacing == 0) return; - m_highlightScrollBarController->setLineHeight(lineSpacing); m_highlightScrollBarController->setVisibleRange(q->viewport()->rect().height()); m_highlightScrollBarController->setMargin(q->textDocument()->document()->documentMargin()); } @@ -8301,7 +8310,7 @@ void TextEditorWidgetPrivate::highlightSearchResultsInScrollBar() if (start > end) std::swap(start, end); if (m_find->inScope(start, end)) - m_searchResults << SearchResult{start, start - end}; + m_searchResults << SearchResult{start, end - start}; } addSearchResultsToScrollBar(m_searchResults); }; @@ -8351,17 +8360,27 @@ void TextEditorWidgetPrivate::addSearchResultsToScrollBar( for (const SearchResult &result : results) { const QTextBlock &block = q->document()->findBlock(result.start); if (block.isValid() && block.isVisible()) { + const int lineSpacing = q->editorLayout()->lineSpacing(); if (q->lineWrapMode() == PlainTextEdit::WidgetWidth) { auto blockLayout = q->editorLayout()->blockLayout(block); - const int firstLine = blockLayout->lineForTextPosition(result.start - block.position()).lineNumber(); - const int lastLine = blockLayout->lineForTextPosition(result.start - block.position() + result.length).lineNumber(); - for (int line = firstLine; line <= lastLine; ++line) { - m_highlightScrollBarController->addHighlight( - {category, q->editorLayout()->firstLineNumberOf(block) + line, color, prio}); - } + auto blockLine = q->editorLayout()->firstLineNumberOf(block); + const int firstLine + = blockLayout->lineForTextPosition(result.start - block.position()).lineNumber(); + const int pos = q->editorLayout()->offsetForLine( + firstLine + blockLine); + const int lastLine = blockLayout + ->lineForTextPosition( + result.start - block.position() + result.length) + .lineNumber(); + const int length = q->editorLayout()->offsetForLine( + lastLine + blockLine) + - pos + lineSpacing; + m_highlightScrollBarController->addHighlight({category, pos, length, color, prio}); } else { + const int firstLine = q->editorLayout()->firstLineNumberOf(block); + const int pos = q->editorLayout()->offsetForLine(firstLine); m_highlightScrollBarController->addHighlight( - {category, q->editorLayout()->firstLineNumberOf(block), color, prio}); + {category, pos, lineSpacing, color, prio}); } } } @@ -8385,14 +8404,6 @@ void TextEditorWidgetPrivate::addSelectionHighlightToScrollBar(const QList<Searc Highlight::NormalPriority); } -Highlight markToHighlight(TextMark *mark, int lineNumber) -{ - return Highlight(mark->category().id, - lineNumber, - mark->color().value_or(Utils::Theme::TextColorNormal), - textMarkPrioToScrollBarPrio(mark->priority())); -} - void TextEditorWidgetPrivate::updateHighlightScrollBarNow() { m_scrollBarUpdateScheduled = false; @@ -8409,6 +8420,17 @@ void TextEditorWidgetPrivate::updateHighlightScrollBarNow() // update search selection addSelectionHighlightToScrollBar(m_selectionResults); + auto markToHighlight = [&](TextMark *mark, const QTextBlock &block) { + const int line = q->editorLayout()->firstLineNumberOf(block); + const int pos = q->editorLayout()->offsetForLine(line); + const int lineSpacing = q->editorLayout()->lineSpacing(); + return Highlight(mark->category().id, + pos, + lineSpacing, + mark->color().value_or(Utils::Theme::TextColorNormal), + textMarkPrioToScrollBarPrio(mark->priority())); + }; + // update text marks const TextMarks marks = m_document->marks(); for (TextMark *mark : marks) { @@ -8416,7 +8438,7 @@ void TextEditorWidgetPrivate::updateHighlightScrollBarNow() continue; const QTextBlock &block = q->document()->findBlockByNumber(mark->lineNumber() - 1); if (block.isVisible()) - m_highlightScrollBarController->addHighlight(markToHighlight(mark, q->editorLayout()->firstLineNumberOf(block))); + m_highlightScrollBarController->addHighlight(markToHighlight(mark, block)); } } diff --git a/src/plugins/texteditor/texteditoroverlay.cpp b/src/plugins/texteditor/texteditoroverlay.cpp index 2f3a35daa39..e7cc02107d3 100644 --- a/src/plugins/texteditor/texteditoroverlay.cpp +++ b/src/plugins/texteditor/texteditoroverlay.cpp @@ -11,6 +11,7 @@ #include <QTextBlock> #include <algorithm> +#include <utils/plaintextedit/texteditorlayout.h> #include <utils/qtcassert.h> using namespace TextEditor; @@ -126,12 +127,15 @@ QPainterPath TextEditorOverlay::createSelectionPath(const QTextCursor &begin, co if (block.blockNumber() < m_editor->firstVisibleBlock().blockNumber() - 1) block = document->findBlockByNumber(m_editor->firstVisibleBlock().blockNumber() - 1); + auto layout = [this](const QTextBlock &block){ + return m_editor->editorLayout()->blockLayout(block); + }; + if (begin.position() == end.position()) { // special case empty selections const QRectF blockGeometry = m_editor->blockBoundingGeometry(block); - QTextLayout *blockLayout = block.layout(); int pos = begin.position() - begin.block().position(); - QTextLine line = blockLayout->lineForTextPosition(pos); + QTextLine line = layout(block)->lineForTextPosition(pos); QTC_ASSERT(line.isValid(), return {}); QRectF lineRect = line.naturalTextRect(); lineRect = lineRect.translated(blockGeometry.topLeft()); @@ -155,15 +159,16 @@ QPainterPath TextEditorOverlay::createSelectionPath(const QTextCursor &begin, co continue; const QRectF blockGeometry = m_editor->blockBoundingGeometry(block); - QTextLayout *blockLayout = block.layout(); + QTextLayout *blockLayout = layout(block); int firstLine = 0; int beginChar = 0; if (block == begin.block()) { beginChar = begin.positionInBlock(); - const QString preeditAreaText = begin.block().layout()->preeditAreaText(); - if (!preeditAreaText.isEmpty() && beginChar >= begin.block().layout()->preeditAreaPosition()) + QTextLayout *beginLayout = layout(begin.block()); + const QString preeditAreaText = beginLayout->preeditAreaText(); + if (!preeditAreaText.isEmpty() && beginChar >= beginLayout->preeditAreaPosition()) beginChar += preeditAreaText.length(); QTextLine line = blockLayout->lineForTextPosition(beginChar); QTC_ASSERT(line.isValid(), return {}); @@ -177,8 +182,9 @@ QPainterPath TextEditorOverlay::createSelectionPath(const QTextCursor &begin, co int endChar = -1; if (block == end.block()) { endChar = end.positionInBlock(); - const QString preeditAreaText = end.block().layout()->preeditAreaText(); - if (!preeditAreaText.isEmpty() && endChar >= end.block().layout()->preeditAreaPosition()) + QTextLayout *endLayout = layout(end.block()); + const QString preeditAreaText = endLayout->preeditAreaText(); + if (!preeditAreaText.isEmpty() && endChar >= endLayout->preeditAreaPosition()) endChar += preeditAreaText.length(); QTextLine line = blockLayout->lineForTextPosition(endChar); QTC_ASSERT(line.isValid(), return {}); diff --git a/src/shared/qbs b/src/shared/qbs -Subproject b46289f1c29f94922c1d6839619314f625c1b8f +Subproject b3702e7e8ea2e8c7f23d741c43a0a9cfcfd02d4 diff --git a/src/tools/icons/qtcreatoricons.svg b/src/tools/icons/qtcreatoricons.svg index 9e74cb94265..7e8de5a96be 100644 --- a/src/tools/icons/qtcreatoricons.svg +++ b/src/tools/icons/qtcreatoricons.svg @@ -4178,6 +4178,21 @@ id="path17569" sodipodi:nodetypes="ccccccccc" /> </g> + <g + id="src/plugins/devcontainer/images/container"> + <use + transform="translate(2539,132)" + height="100%" + width="100%" + id="use153" + xlink:href="#backgroundRect" + y="0" + x="0" /> + <path + id="path51" + style="fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round" + d="m 2530.5,575.5 v 3 m -2.5,-4.25 2.5,1.25 2.5,-1.25 m -7.5,4.75 5,2.5 5,-2.5 v -6 l -5,-2.5 -5,2.5 z" /> + </g> </g> <g inkscape:groupmode="layer" |