Merge remote-tracking branch 'origin/11.0'

Change-Id: I8d1c9720a868da02b3157a48954eb4e262539c84
This commit is contained in:
Eike Ziller
2023-06-15 09:48:58 +02:00
67 changed files with 1046 additions and 376 deletions

76
dist/changelog/changes-10.0.2.md vendored Normal file
View File

@@ -0,0 +1,76 @@
Qt Creator 10.0.2
=================
Qt Creator version 10.0.2 contains bug fixes.
The most important changes are listed in this document. For a complete list of
changes, see the Git log for the Qt Creator sources that you can check out from
the public Git repository. For example:
git clone git://code.qt.io/qt-creator/qt-creator.git
git log --cherry-pick --pretty=oneline origin/v10.0.1..v10.0.2
General
-------
* Fixed freezes due to excessive file watching (QTCREATORBUG-28957)
Editing
-------
### C++
* Fixed a crash when following symbols (QTCREATORBUG-28989)
* Fixed the highlighting of raw string literals with empty lines
(QTCREATORBUG-29200)
* Clang Format
* Fixed the editing of custom code styles (QTCREATORBUG-29129)
* Fixed that the wrong code style could be used (QTCREATORBUG-29145)
Projects
--------
* Fixed a crash when triggering a build with unconfigured projects present
(QTCREATORBUG-29207)
### CMake
* Fixed that the global `Autorun CMake` option could be overridden by old
settings
* Fixed the `Build CMake Target` locator filter in case a build is already
running (QTCREATORBUG-26699)
* Presets
* Added the expansion of `${hostSystemName}` (QTCREATORBUG-28935)
* Fixed the Qt detection when `CMAKE_TOOLCHAIN_FILE` and `CMAKE_PREFIX_PATH`
are set
Debugging
---------
* Fixed that debugger tooltips in the editor vanished after expanding
(QTCREATORBUG-29083)
Test Integration
----------------
* GoogleTest
* Fixed the reporting of failed tests (QTCREATORBUG-29146)
Credits for these changes go to:
--------------------------------
Alessandro Portale
André Pönitz
Artem Sokolovskii
Björn Schäpers
Christian Kandeler
Christian Stenger
Cristian Adam
David Schulz
Eike Ziller
Jaroslaw Kobus
Karim Abdelrahman
Leena Miettinen
Miikka Heikkinen
Patrik Teivonen
Robert Löhning
Sivert Krøvel

View File

@@ -1,10 +1,10 @@
// Copyright (C) 2022 The Qt Company Ltd.
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\previouspage creator-build-settings.html
\page creator-build-settings-cmake.html
\nextpage creator-build-settings-qmake.html
\nextpage creator-build-settings-cmake-presets.html
\title CMake Build Configuration
@@ -30,6 +30,9 @@
Select \uicontrol {Kit Configuration} to edit the CMake settings for the
build and run kit selected for the project.
You can use \l{CMake Presets}{CMake presets} files to specify common
configure, build, and test options and share them with others.
\section1 Initial Configuration
\image qtcreator-build-settings-cmake-initial.webp {Initial CMake configuration}
@@ -57,224 +60,6 @@
\l{CMake: cmake-variables(7)}. For more information about Qt-specific
variables, see \l{CMake Variable Reference}.
\section1 CMake Presets
You can use CMake presets files to specify common configure, build, and test
options and share them with others. \c CMakePresets.json has options for
project-wide builds, whereas \c CMakeUserPresets.json has options for
your local builds.
Create the presets files in the format described in
\l{https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html}
{cmake-presets(7)} and store them in project's root directory.
\QC supports presets up to version 3 (introduced in CMake 3.21), but does not
enforce version checking. It reads and uses all the fields from version 3 if
present. It does not support test presets.
You can import the presets the first time you \l {Opening Projects}
{open a project}, when no \c CMakeLists.txt.user file exists or you have
disabled all kits in the project. To update changes to the
\c CMakePresets.json file, delete the \c CMakeLists.txt.user file.
\image qtcreator-cmake-presets-configure.webp {Opening a project that has CMake presets}
You can view the presets in the \uicontrol {Initial Configuration} field and
in the environment configuration field below it.
\image qtcreator-cmake-presets-environment.webp {CMake environment configuration}
\section2 Configure Presets
The following configure presets instruct CMake to use the default generator
on the platform and specify the build directory for all build types.
\c NOT_COMMON_VALUE is displayed in \uicontrol {Initial Parameters}
and \c AN_ENVIRONMENT_FLAG in the environment configuration field.
\badcode
{
"version": 1,
"configurePresets": [
{
"name": "preset",
"displayName": "preset",
"binaryDir": "${sourceDir}/build/preset",
"cacheVariables": {
"NOT_COMMON_VALUE": "NOT_COMMON_VALUE"
},
"environment": {
"AN_ENVIRONMENT_FLAG": "1"
}
}
]
}
\endcode
\section2 MinGW Example
The following example configures a Qt project with:
\list
\li MinGW compiler
\li build directory \c <sourceDir>/build-release
\li build type \c CMAKE_BUILD_TYPE as \c Release
\li generator MinGW Makefiles
\li path to a CMake executable
\li path to the Qt installation via \c CMAKE_PREFIX_PATH
\endlist
\badcode
{
"version": 1,
"configurePresets": [
{
"name": "mingw",
"displayName": "MinGW 11.2.0",
"generator": "MinGW Makefiles",
"binaryDir": "${sourceDir}/build-release",
"cmakeExecutable": "C:/Qt/Tools/CMake_64/bin/cmake.exe",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_PREFIX_PATH": "C:/Qt/6.4.0/mingw_64"
},
"environment": {
"PATH": "C:/Qt/Tools/mingw1120_64/bin;$penv{PATH}"
}
}
]
}
\endcode
To speed up the process on Windows, specify the \c CMAKE_C_COMPILER and
\c CMAKE_CXX_COMPILER in the \c cacheVariables section.
\section2 Ninja Generator Example
The following configure and build presets set Ninja Multi-Config as the
generator, add \c Debug and \c Release build steps, and specify the path
to \c ninja.exe as a value of the \c CMAKE_MAKE_PROGRAM variable:
\badcode
{
"version": 2,
"configurePresets": [
{
"name": "ninja-nmc",
"displayName": "Ninja Multi-Config MinGW",
"generator": "Ninja Multi-Config",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug;Release",
"CMAKE_PREFIX_PATH": "C:/Qt/6.4.0/mingw_64"
"CMAKE_MAKE_PROGRAM": "C:/Qt/Tools/Ninja/ninja.exe"
},
"environment": {
"PATH": "c:/Qt/Tools/mingw1120_64/bin;$penv{PATH}"
}
}
],
"buildPresets": [
{
"name": "release",
"displayName": "Ninja Release",
"configurePreset": "ninja-nmc",
"configuration": "Release"
},
{
"name": "debug",
"displayName": "Ninja Debug",
"configurePreset": "ninja-nmc",
"configuration": "Debug"
}
]
}
\endcode
This example assumes that the CMake executable path is set in
\uicontrol Edit > \uicontrol Preferences > \uicontrol CMake >
\uicontrol Tools.
\section2 MSVC Example
When using MSVC compilers with NMAKE Makefiles, Ninja, or Ninja
Multi-Config generators, you can use the \c external strategy for
the \c architecture and \c toolset fields. This lets \QC set up
the Visual C++ environment before invoking CMake.
For example:
\badcode
"generator": "Ninja Multi-Config",
"toolset": {
"value": "v142,host=x64",
"strategy": "external"
},
"architecture": {
"value": "x64",
"strategy": "external"
},
\endcode
If you use MSVC compilers with non-VS generators and have several compilers
in the \c PATH, you might also have to specify the compiler to use in
\c cacheVariables or \c environmentVariables:
\badcode
"generator": "Ninja Multi-Config",
"toolset": {
"value": "v142,host=x64",
"strategy": "external"
},
"architecture": {
"value": "x64",
"strategy": "external"
},
"cacheVariables": {
"CMAKE_C_COMPILER": "cl.exe",
"CMAKE_CXX_COMPILER": "cl.exe"
}
\endcode
\section2 Using Conditions
The following configure presets are used if they match \c condition. That is,
if the \c hostSystemName equals \c Linux, the \c linux presets are used and
if it equals \c Windows, the \c windows presets are used.
\badcode
{
"version": 3,
"configurePresets": [
{
"name": "linux",
"displayName": "Linux GCC",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_PREFIX_PATH": "$env{HOME}/Qt/6.4.0/gcc_64"
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Linux"
}
},
{
"name": "windows",
"displayName": "Windows MSVC",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_PREFIX_PATH": "$env{SYSTEMDRIVE}/Qt/6.4.0/msvc2019_64"
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
}
}
]
}
\endcode
\section1 Multi-Config Support
\QC supports

View File

@@ -0,0 +1,227 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\previouspage creator-build-settings-cmake.html
\page creator-build-settings-cmake-presets.html
\nextpage creator-build-settings-qmake.html
\title CMake Presets
\c CMakePresets.json has options for project-wide builds, whereas
\c CMakeUserPresets.json has options for your local builds.
Create the presets files in the format described in
\l{https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html}
{cmake-presets(7)} and store them in the project's root directory.
You can then see them in the \l {Projects} view.
\QC supports presets up to version 3 (introduced in CMake 3.21), but does not
enforce version checking. It reads and uses all the fields from version 3 if
present. It does not support test presets.
You can import the presets the first time you \l {Opening Projects}
{open a project}, when no \c CMakeLists.txt.user file exists or you have
disabled all kits in the project.
\image qtcreator-cmake-presets-configure.webp {Opening a project that has CMake presets}
You can view the presets in the \uicontrol {Initial Configuration} field and
in the environment configuration field below it.
\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.
\section1 Configure Presets
The following configure presets instruct CMake to use the default generator
on the platform and specify the build directory for all build types.
\c NOT_COMMON_VALUE is displayed in \uicontrol {Initial Parameters}
and \c AN_ENVIRONMENT_FLAG in the environment configuration field.
\badcode
{
"version": 1,
"configurePresets": [
{
"name": "preset",
"displayName": "preset",
"binaryDir": "${sourceDir}/build/preset",
"cacheVariables": {
"NOT_COMMON_VALUE": "NOT_COMMON_VALUE"
},
"environment": {
"AN_ENVIRONMENT_FLAG": "1"
}
}
]
}
\endcode
\section1 MinGW Example
The following example configures a Qt project with:
\list
\li MinGW compiler
\li build directory \c <sourceDir>/build-release
\li build type \c CMAKE_BUILD_TYPE as \c Release
\li generator MinGW Makefiles
\li path to a CMake executable
\li path to the Qt installation via \c CMAKE_PREFIX_PATH
\endlist
\badcode
{
"version": 1,
"configurePresets": [
{
"name": "mingw",
"displayName": "MinGW 11.2.0",
"generator": "MinGW Makefiles",
"binaryDir": "${sourceDir}/build-release",
"cmakeExecutable": "C:/Qt/Tools/CMake_64/bin/cmake.exe",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_PREFIX_PATH": "C:/Qt/6.4.0/mingw_64"
},
"environment": {
"PATH": "C:/Qt/Tools/mingw1120_64/bin;$penv{PATH}"
}
}
]
}
\endcode
To speed up the process on Windows, specify the \c CMAKE_C_COMPILER and
\c CMAKE_CXX_COMPILER in the \c cacheVariables section.
\section1 Ninja Generator Example
The following configure and build presets set Ninja Multi-Config as the
generator, add \c Debug and \c Release build steps, and specify the path
to \c ninja.exe as a value of the \c CMAKE_MAKE_PROGRAM variable:
\badcode
{
"version": 2,
"configurePresets": [
{
"name": "ninja-nmc",
"displayName": "Ninja Multi-Config MinGW",
"generator": "Ninja Multi-Config",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug;Release",
"CMAKE_PREFIX_PATH": "C:/Qt/6.4.0/mingw_64"
"CMAKE_MAKE_PROGRAM": "C:/Qt/Tools/Ninja/ninja.exe"
},
"environment": {
"PATH": "c:/Qt/Tools/mingw1120_64/bin;$penv{PATH}"
}
}
],
"buildPresets": [
{
"name": "release",
"displayName": "Ninja Release",
"configurePreset": "ninja-nmc",
"configuration": "Release"
},
{
"name": "debug",
"displayName": "Ninja Debug",
"configurePreset": "ninja-nmc",
"configuration": "Debug"
}
]
}
\endcode
This example assumes that the CMake executable path is set in
\uicontrol Edit > \uicontrol Preferences > \uicontrol CMake >
\uicontrol Tools.
\section1 MSVC Example
When using MSVC compilers with NMAKE Makefiles, Ninja, or Ninja
Multi-Config generators, you can use the \c external strategy for
the \c architecture and \c toolset fields. This lets \QC set up
the Visual C++ environment before invoking CMake.
For example:
\badcode
"generator": "Ninja Multi-Config",
"toolset": {
"value": "v142,host=x64",
"strategy": "external"
},
"architecture": {
"value": "x64",
"strategy": "external"
},
\endcode
If you use MSVC compilers with non-VS generators and have several compilers
in the \c PATH, you might also have to specify the compiler to use in
\c cacheVariables or \c environmentVariables:
\badcode
"generator": "Ninja Multi-Config",
"toolset": {
"value": "v142,host=x64",
"strategy": "external"
},
"architecture": {
"value": "x64",
"strategy": "external"
},
"cacheVariables": {
"CMAKE_C_COMPILER": "cl.exe",
"CMAKE_CXX_COMPILER": "cl.exe"
}
\endcode
\section1 Using Conditions
The following configure presets are used if they match \c condition. That is,
if the \c hostSystemName equals \c Linux, the \c linux presets are used and
if it equals \c Windows, the \c windows presets are used.
\badcode
{
"version": 3,
"configurePresets": [
{
"name": "linux",
"displayName": "Linux GCC",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_PREFIX_PATH": "$env{HOME}/Qt/6.4.0/gcc_64"
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Linux"
}
},
{
"name": "windows",
"displayName": "Windows MSVC",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_PREFIX_PATH": "$env{SYSTEMDRIVE}/Qt/6.4.0/msvc2019_64"
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
}
}
]
}
\endcode
*/

View File

@@ -181,6 +181,18 @@
current project.
\endlist
\section1 Managing Files
When you use project wizard templates to \l{Creating Files}{add files} to
a project, \QC automatically adds them to the \c {qt_add_executable()},
\c {add_executable()}, or \c {qt_add_library()} function in the
CMakeLists.txt file.
If you use custom API, \QC uses \c {target_sources()} to add the files.
When you rename or remove files in the \l {Projects} or \l {File System}
view, \QC renames them in the CMakeLists.txt file or removes them from it.
\section1 Adding External Libraries to CMake Projects
Through external libraries, \QC can support code completion and syntax

View File

@@ -13,8 +13,8 @@
container operates like a virtual machine but uses less system resources at
the cost of being less flexible.
Docker support is experimental. While Linux, \macos, and Windows hosts are
supported in principle, Linux is the recommended platform.
While Linux, \macos, and Windows hosts are supported in principle, Linux is
the recommended platform.
Currently, only CMake is supported for building applications in the Docker
container.
@@ -29,16 +29,6 @@
\l{https://docs.docker.com/engine/reference/commandline/pull/}{docker pull}
command.
\section1 Enabling Docker Plugin
To enable the experimental Docker plugin:
\list 1
\li In \QC, select \uicontrol Help > \uicontrol {About Plugins} >
\uicontrol Utilities > \uicontrol {Docker (experimental)}.
\li Select \uicontrol {Restart Now} to restart \QC and load the plugin.
\endlist
\section1 Adding Docker Images as Devices
To add a Docker image as a device:

View File

@@ -433,15 +433,19 @@
\row
\li Find references to symbol under cursor
\li Ctrl+Shift+U
\if defined(qtcreator)
\note If this keyboard shortcut does not work on Linux, see
\l {Editing Issues}.
\endif
\row
\li Follow symbol under cursor
Works with namespaces, classes, functions, variables, include
statements, and macros. Also, opens URLs in the default browser
\if defined(qtcreator)
and Qt resource files (.qrc) in the \l{Resource Files}
{resource editor}
\endif
\li F2
\row
\li Rename symbol under cursor

View File

@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\previouspage creator-build-settings-cmake.html
\previouspage creator-build-settings-cmake-presets.html
\page creator-build-settings-qmake.html
\nextpage creator-build-settings-qbs.html

View File

@@ -68,6 +68,9 @@
\li \l{Specifying Build Settings}
\list
\li \l{Cmake Build Configuration}
\list
\li \l{CMake Presets}
\endlist
\li \l{qmake Build Configuration}
\li \l{Qbs Build Configuration}
\li \l{Meson Build Configuration}

View File

@@ -60,9 +60,12 @@
\li Open a terminal window in the selected directory or in the directory
that has the file. To specify the terminal to use on Linux and
\macos, select \uicontrol Edit > \uicontrol Preferences >
\uicontrol Environment > \uicontrol System. To use an \l{Terminal}
{internal terminal}, select \uicontrol Edit > \uicontrol Preferences
> \uicontrol Terminal > \uicontrol {Use internal terminal}.
\uicontrol Environment > \uicontrol System.
\if defined(qtcreator)
To use an \l{Terminal} {internal terminal}, select \uicontrol Edit >
\uicontrol Preferences > \uicontrol Terminal >
\uicontrol {Use internal terminal}.
\endif
\li Search from the selected directory.
\li View file properties, such as name, path, MIME type, default editor,
line endings, indentation, owner, size, last read and modified
@@ -73,7 +76,10 @@
\else
\l{Creating Files}.
\endif
\li Rename or remove existing files.
\li Rename existing files. To move the file to another directory, enter
the relative or absolute path to its new location in addition to the
new filename.
\li Remove existing files.
\li Create new folders.
\li Compare the selected file with the currently open file in the diff
editor. For more information, see \l{Comparing Files}.

View File

@@ -66,10 +66,11 @@
\else
\l{Creating Files}.
\endif
\li Rename or remove existing files. If you change the base name of a
\li Rename existing files. If you change the base name of a
file, \QC displays a list of other files with the same base name
and offers to rename them as well. If you rename a UI file (.ui),
\QC also changes corresponding include statements accordingly.
\li Remove existing files.
\if defined(qtcreator)
\li Remove existing directories from \l{Setting Up a Generic Project}
{generic projects}.
@@ -79,14 +80,16 @@
\li Add and remove subprojects.
\li Find unused functions.
\endif
\li Search from the selected directory.
\li Open a terminal window in the project directory. To specify the
terminal to use on Linux and \macos, select \uicontrol Edit >
\uicontrol Preferences > \uicontrol Environment > \uicontrol System.
\if defined(qtcreator)
To use an \l{Terminal}{internal terminal}, select \uicontrol Edit >
\uicontrol Preferences > \uicontrol Terminal >
\uicontrol {Use internal terminal}.
\endif
\li Open a terminal window in the project directory that you configured
for building or running the project.
\li Expand or collapse the tree view to show or hide all files and

View File

@@ -278,7 +278,6 @@ static Utils::QtcSettings *createUserSettings()
static void setHighDpiEnvironmentVariable()
{
if (Utils::HostOsInfo::isMacHost())
return;
@@ -293,10 +292,12 @@ static void setHighDpiEnvironmentVariable()
&& !qEnvironmentVariableIsSet("QT_AUTO_SCREEN_SCALE_FACTOR")
&& !qEnvironmentVariableIsSet("QT_SCALE_FACTOR")
&& !qEnvironmentVariableIsSet("QT_SCREEN_SCALE_FACTORS")) {
} else {
return;
}
if (!qEnvironmentVariableIsSet("QT_SCALE_FACTOR_ROUNDING_POLICY"))
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(
Qt::HighDpiScaleFactorRoundingPolicy::Floor);
}
}
void setPixmapCacheLimit()

View File

@@ -1284,7 +1284,7 @@ void TaskNode::invokeEndHandler(bool success)
\li Starts asynchronous task, runs in separate thread.
\row
\li TaskTreeTask
\li Utils::TaskTree
\li Tasking::TaskTree
\li Starts a nested task tree.
\row
\li FileTransferTask
@@ -1513,7 +1513,7 @@ void TaskNode::invokeEndHandler(bool success)
The execution mode element in a Group specifies how the direct child tasks of
the Group are started. The most common execution modes are \l sequential and
\l parallel. It's also possible to specify the limit of tasks running
in parallel by using the parallelLimit function.
in parallel by using the parallelLimit() function.
In all execution modes, a group starts tasks in the oder in which they appear.
@@ -1745,16 +1745,50 @@ void TaskNode::invokeEndHandler(bool success)
to finish (that is, safe non-blocking destructor of a running task).
*/
/*!
Constructs an empty task tree. Use setRecipe() to pass a declarative description
on how the task tree should execute the tasks and how it should handle the finished tasks.
Starting an empty task tree is no-op and the relevant warning message is issued.
\sa setRecipe(), start()
*/
TaskTree::TaskTree()
: d(new TaskTreePrivate(this))
{
}
{}
/*!
Constructs a task tree with a given \a recipe. After the task tree is started,
it executes the tasks contained inside the \a recipe and
handles finished tasks according to the passed description.
\sa setRecipe(), start()
*/
TaskTree::TaskTree(const Group &recipe) : TaskTree()
{
setRecipe(recipe);
}
/*!
Destroys the task tree.
When the task tree is running while being destructed, it stops all the running tasks
immediately. In this case, no handlers are called, not even the groups' and
tasks' error handlers or onStorageDone() handlers. The task tree also doesn't emit any
signals from the destructor, not even errorOccurred() or progressValueChanged() signals.
This behavior may always be relied on.
It is completely safe to destruct the running task tree.
It's a usual pattern to destruct the running task tree, even from the main thread.
It's guaranteed that the destruction will run quickly, without having to wait for
the currently running tasks to finish, provided that the used tasks implement
their destructors in a non-blocking way.
\note Do not call the destructor directly from any of the running task's handlers
or task tree's signals. In these cases, use \l deleteLater() instead.
\sa stop()
*/
TaskTree::~TaskTree()
{
QTC_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting TaskTree instance directly from "
@@ -1763,6 +1797,15 @@ TaskTree::~TaskTree()
delete d;
}
/*!
Sets a given \a recipe for the task tree. After the task tree is started,
it executes the tasks contained inside the \a recipe and
handles finished tasks according to the passed description.
\note When called for a running task tree, the call is ignored.
\sa TaskTree(const Tasking::Group &recipe), start()
*/
void TaskTree::setRecipe(const Group &recipe)
{
QTC_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return);
@@ -1772,6 +1815,32 @@ void TaskTree::setRecipe(const Group &recipe)
d->m_root.reset(new TaskNode(d, recipe, nullptr));
}
/*!
Starts the task tree.
Use setRecipe() or the constructor to set the declarative description according to which
the task tree will execute the contained tasks and handle finished tasks.
When the task tree is empty, that is, constructed with a default constructor,
a call to \e start is no-op and the relevant warning message is issued.
Otherwise, when the task tree is already running, a call to \e start is ignored and the
relevant warning message is issued.
Otherwise, the task tree is started.
The started task tree may finish synchronously,
for example when the main group's start handler returns TaskAction::StopWithError.
For this reason, the connections to the done and errorOccurred signals should be
established before calling start. Use isRunning() in order to detect whether
the task tree is still running after a call to start().
The task tree implementation relies on the running event loop for listening to the tasks'
done signals. Make sure you have a QEventLoop or QCoreApplication or one of its
subclasses running (or about to be run) when calling this method.
\sa TaskTree(const Tasking::Group &recipe), setRecipe(), isRunning(), stop()
*/
void TaskTree::start()
{
QTC_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return);
@@ -1780,6 +1849,65 @@ void TaskTree::start()
d->start();
}
/*!
\fn void TaskTree::started()
This signal is emitted when the task tree is started. The emission of this signal is
followed synchronously by the progressValueChanged() signal with an initial \c 0 value.
\sa start(), done(), errorOccurred()
*/
/*!
\fn void TaskTree::done()
This signal is emitted when the task tree finished with success.
The task tree neither calls any handler, nor emits any signal anymore after this signal
was emitted.
Don't delete the task tree directly from this signal's handler. Use deleteLater() instead.
\sa started(), errorOccurred()
*/
/*!
\fn void TaskTree::errorOccurred()
This signal is emitted when the task tree finished with an error.
The task tree neither calls any handler, nor emits any signal anymore after this signal
was emitted.
Don't delete the task tree directly from this signal's handler. Use deleteLater() instead.
\sa started(), done()
*/
/*!
Stops the running task tree.
Stops all the running tasks immediately.
All running tasks finish with an error, invoking their error handlers.
All running groups dispatch their handlers according to their workflow policies,
invoking one of their end handlers. The storages' onStorageDone() handlers are invoked, too.
The \l progressValueChanged signals are also being sent.
This behavior may always be relied on.
The \l stop is executed synchronously, so that after a call to \e stop
all running tasks are finished and the tree is already stopped.
It's guaranteed that the stop will run quickly, without any blocking wait for
the currently running tasks to finish, provided the used tasks implement their destructors
in a non-blocking way.
When the task tree is empty, that is, constructed with a default constructor,
a call to \e stop is no-op and the relevant warning message is issued.
Otherwise, when the task tree wasn't started, a call to stop is ignored.
\note Do not call this function directly from any of the running task's handlers
or task tree's signals.
\sa ~TaskTree()
*/
void TaskTree::stop()
{
QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The stop() is called from one of the"
@@ -1787,11 +1915,26 @@ void TaskTree::stop()
d->stop();
}
/*!
Returns \c true if the task tree is currently running; otherwise returns \c false.
\sa start(), stop()
*/
bool TaskTree::isRunning() const
{
return d->m_root && d->m_root->isRunning();
}
/*!
Executes a local event loop with QEventLoop::ExcludeUserInputEvents and starts the task tree.
Returns \c true if the task tree finished successfully; otherwise returns \c false.
\note Avoid using this method from the main thread. Use asynchronous start() instead.
This method is to be used in non-main threads or in auto tests.
\sa start()
*/
bool TaskTree::runBlocking()
{
QPromise<void> dummy;
@@ -1799,6 +1942,12 @@ bool TaskTree::runBlocking()
return runBlocking(dummy.future());
}
/*!
\overload runBlocking()
The passed \a future is used for listening to the cancel event.
When the task tree finishes with an error, this method cancels the passed \a future.
*/
bool TaskTree::runBlocking(const QFuture<void> &future)
{
if (future.isCanceled())
@@ -1830,6 +1979,19 @@ bool TaskTree::runBlocking(const QFuture<void> &future)
return ok;
}
/*!
Constructs a temporary task tree using the passed \a recipe and runs it blocking.
The optionally provided \a timeout is used to stop the tree automatically after
\a timeout milliseconds have passed.
Returns \c true if the task tree finished successfully; otherwise returns \c false.
\note Avoid using this method from the main thread. Use asynchronous start() instead.
This method is to be used in non-main threads or in auto tests.
\sa start()
*/
bool TaskTree::runBlocking(const Group &recipe, milliseconds timeout)
{
QPromise<void> dummy;
@@ -1837,6 +1999,12 @@ bool TaskTree::runBlocking(const Group &recipe, milliseconds timeout)
return TaskTree::runBlocking(recipe, dummy.future(), timeout);
}
/*!
\overload runBlocking(const Group &recipe, milliseconds timeout)
The passed \a future is used for listening to the cancel event.
When the task tree finishes with an error, this method cancels the passed \a future.
*/
bool TaskTree::runBlocking(const Group &recipe, const QFuture<void> &future, milliseconds timeout)
{
const Group root = timeout == milliseconds::max() ? recipe
@@ -1845,16 +2013,145 @@ bool TaskTree::runBlocking(const Group &recipe, const QFuture<void> &future, mil
return taskTree.runBlocking(future);
}
/*!
Returns the number of asynchronous tasks contained in the stored recipe.
\note The returned number doesn't include Sync tasks.
\note Any task or group that was set up using withTimeout() increases the total number of
tasks by \c 1.
\sa setRecipe(), progressMaximum()
*/
int TaskTree::taskCount() const
{
return d->m_root ? d->m_root->taskCount() : 0;
}
/*!
\fn void TaskTree::progressValueChanged(int value)
This signal is emitted when the running task tree finished, stopped, or skipped some tasks.
The \a value gives the current total number of finished, stopped or skipped tasks.
When the task tree is started, and after the started() signal was emitted,
this signal is emitted with an initial \a value of \c 0.
When the task tree is about to finish, and before the done() or errorOccurred() signal
is emitted, this signal is emitted with the final \a value of progressMaximum().
\sa progressValue(), progressMaximum()
*/
/*!
\fn int TaskTree::progressMaximum() const
Returns the maximum progressValue().
\note Currently, it's the same as taskCount(). This might change in the future.
\sa progressValue()
*/
/*!
Returns the current progress value, which is between the \c 0 and progressMaximum().
The returned number indicates how many tasks have been already finished, stopped, or skipped
while the task tree is running.
When the task tree is started, this number is set to \c 0.
When the task tree is finished, this number always equals progressMaximum().
\sa progressMaximum()
*/
int TaskTree::progressValue() const
{
return d->m_progressValue;
}
/*!
\fn template <typename StorageStruct, typename StorageHandler> void TaskTree::onStorageSetup(const TreeStorage<StorageStruct> &storage, StorageHandler &&handler)
Installs a storage setup \a handler for the \a storage to pass the initial data
dynamically to the running task tree.
The \c StorageHandler takes the pointer to the \c StorageStruct instance:
\code
static void save(const QString &fileName, const QByteArray &array) { ... }
TreeStorage<QByteArray> storage;
const auto onSaverSetup = [storage](ConcurrentCall<void> &concurrent) {
concurrent.setConcurrentCallData(&save, "foo.txt", *storage);
};
const Group root {
Storage(storage),
ConcurrentCallTask(onSaverSetup)
};
TaskTree taskTree(root);
auto initStorage = [](QByteArray *storage){
*storage = "initial content";
};
taskTree.onStorageSetup(storage, initStorage);
taskTree.start();
\endcode
When the running task tree enters a Group where the \a storage is placed in,
it creates a \c StorageStruct instance, ready to be used inside this group.
Just after the \c StorageStruct instance is created, and before any handler of this group
is called, the task tree invokes the passed \a handler. This enables setting up
initial content for the given storage dynamically. Later, when any group's handler is invoked,
the task tree activates the created and initialized storage, so that it's available inside
any group's handler.
\sa onStorageDone()
*/
/*!
\fn template <typename StorageStruct, typename StorageHandler> void TaskTree::onStorageDone(const TreeStorage<StorageStruct> &storage, StorageHandler &&handler)
Installs a storage done \a handler for the \a storage to retrie the final data
dynamically from the running task tree.
The \c StorageHandler takes the pointer to the \c StorageStruct instance:
\code
static QByteArray load(const QString &fileName) { ... }
TreeStorage<QByteArray> storage;
const auto onLoaderSetup = [storage](ConcurrentCall<void> &concurrent) {
concurrent.setConcurrentCallData(&load, "foo.txt");
};
const auto onLoaderDone = [storage](const ConcurrentCall<void> &concurrent) {
*storage = concurrent.result();
};
const Group root {
Storage(storage),
ConcurrentCallTask(onLoaderDone, onLoaderDone)
};
TaskTree taskTree(root);
auto collectStorage = [](QByteArray *storage){
qDebug() << "final content" << *storage;
};
taskTree.onStorageDone(storage, collectStorage);
taskTree.start();
\endcode
When the running task tree is about to leave a Group where the \a storage is placed in,
it destructs a \c StorageStruct instance.
Just before the \c StorageStruct instance is destructed, and after all possible handlers from
this group were called, the task tree invokes the passed \a handler. This enables reading
the final content of the given storage dynamically and processing it further outside of
the task tree.
This handler is called also when the running tree is stopped. However, it's not called
when the running tree is destructed.
\sa onStorageSetup()
*/
void TaskTree::setupStorageHandler(const TreeStorageBase &storage,
StorageVoidHandler setupHandler,
StorageVoidHandler doneHandler)

View File

@@ -108,6 +108,8 @@ expected_str<qint64> ProcessStubCreator::startStubProcess(const ProcessSetupData
cmdLine.addCommandLineAsArgs(setupData.m_commandLine, CommandLine::Raw);
process->setCommand(cmdLine);
}
process->setEnvironment(
setupData.m_environment.appliedToEnvironment(Environment::systemEnvironment()));
process->setEnvironment(setupData.m_environment);

View File

@@ -384,9 +384,13 @@ void TerminalInterface::start()
QTC_ASSERT(d->stubCreator, return);
ProcessSetupData stubSetupData = m_setup;
ProcessSetupData stubSetupData;
stubSetupData.m_commandLine = cmd;
stubSetupData.m_extraData[TERMINAL_SHELL_NAME]
= m_setup.m_extraData.value(TERMINAL_SHELL_NAME,
m_setup.m_commandLine.executable().fileName());
if (m_setup.m_runAsRoot && !HostOsInfo::isWindowsHost()) {
CommandLine rootCommand(FilePath("sudo").searchInPath(), {"-A"});
rootCommand.addCommandLineAsArgs(cmd);

View File

@@ -11,6 +11,8 @@ namespace Utils {
class TerminalInterfacePrivate;
const char TERMINAL_SHELL_NAME[] = "Terminal.ShellName";
class StubCreator : public QObject
{
public:

View File

@@ -25,10 +25,12 @@ AndroidManifestDocument::AndroidManifestDocument(AndroidManifestEditorWidget *ed
this, &Core::IDocument::changed);
}
bool AndroidManifestDocument::save(QString *errorString, const Utils::FilePath &filePath, bool autoSave)
bool AndroidManifestDocument::saveImpl(QString *errorString,
const Utils::FilePath &filePath,
bool autoSave)
{
m_editorWidget->preSave();
bool result = TextDocument::save(errorString, filePath, autoSave);
bool result = TextDocument::saveImpl(errorString, filePath, autoSave);
m_editorWidget->postSave();
return result;
}

View File

@@ -14,12 +14,15 @@ class AndroidManifestDocument : public TextEditor::TextDocument
{
public:
explicit AndroidManifestDocument(AndroidManifestEditorWidget *editorWidget);
bool save(QString *errorString, const Utils::FilePath &filePath,
bool autoSave = false) override;
bool isModified() const override;
bool isSaveAsAllowed() const override;
protected:
bool saveImpl(QString *errorString,
const Utils::FilePath &filePath,
bool autoSave = false) override;
private:
AndroidManifestEditorWidget *m_editorWidget;
};

View File

@@ -217,17 +217,6 @@ public:
return type == TypeRemoved ? BehaviorSilent : IDocument::reloadBehavior(state, type);
}
bool save(QString *errorString, const Utils::FilePath &filePath, bool autoSave) override
{
QTC_ASSERT(!autoSave, return true); // bineditor does not support autosave - it would be a bit expensive
const FilePath &fileNameToUse = filePath.isEmpty() ? this->filePath() : filePath;
if (m_widget->save(errorString, this->filePath(), fileNameToUse)) {
setFilePath(fileNameToUse);
return true;
}
return false;
}
OpenResult open(QString *errorString, const FilePath &filePath,
const FilePath &realFilePath) override
{
@@ -320,6 +309,18 @@ public:
return success;
}
protected:
bool saveImpl(QString *errorString, const Utils::FilePath &filePath, bool autoSave) override
{
QTC_ASSERT(!autoSave, return true); // bineditor does not support autosave - it would be a bit expensive
const FilePath &fileNameToUse = filePath.isEmpty() ? this->filePath() : filePath;
if (m_widget->save(errorString, this->filePath(), fileNameToUse)) {
setFilePath(fileNameToUse);
return true;
}
return false;
}
private:
BinEditorWidget *m_widget;
};

View File

@@ -711,11 +711,11 @@ class ClangdDiagnosticManager : public LanguageClient::DiagnosticManager
});
}
TextMark *createTextMark(const Utils::FilePath &filePath,
TextMark *createTextMark(TextDocument *doc,
const Diagnostic &diagnostic,
bool isProjectFile) const override
{
return new ClangdTextMark(filePath, diagnostic, isProjectFile, getClient());
return new ClangdTextMark(doc, diagnostic, isProjectFile, getClient());
}
};

View File

@@ -263,16 +263,16 @@ Task createTask(const ClangDiagnostic &diagnostic)
} // anonymous namespace
ClangdTextMark::ClangdTextMark(const FilePath &filePath,
ClangdTextMark::ClangdTextMark(TextEditor::TextDocument *doc,
const Diagnostic &diagnostic,
bool isProjectFile,
ClangdClient *client)
: TextEditor::TextMark(filePath,
: TextEditor::TextMark(doc,
int(diagnostic.range().start().line() + 1),
{client->name(), client->id()})
, m_lspDiagnostic(diagnostic)
, m_diagnostic(
convertDiagnostic(ClangdDiagnostic(diagnostic), filePath, client->hostPathMapper()))
convertDiagnostic(ClangdDiagnostic(diagnostic), doc->filePath(), client->hostPathMapper()))
, m_client(client)
{
setSettingsPage(CppEditor::Constants::CPP_CLANGD_SETTINGS_ID);

View File

@@ -23,7 +23,7 @@ class ClangdClient;
class ClangdTextMark : public TextEditor::TextMark
{
public:
ClangdTextMark(const ::Utils::FilePath &filePath,
ClangdTextMark(TextEditor::TextDocument *doc,
const LanguageServerProtocol::Diagnostic &diagnostic,
bool isProjectFile,
ClangdClient *client);

View File

@@ -32,7 +32,7 @@ static bool isBeautifierPluginActivated()
return std::find_if(specs.begin(),
specs.end(),
[](ExtensionSystem::PluginSpec *spec) {
return spec->name() == "Beautifier";
return spec->name() == "Beautifier" && spec->isEffectivelyEnabled();
})
!= specs.end();
}

View File

@@ -50,7 +50,7 @@ public:
Q_UNUSED(RequiresNullTerminator);
Q_UNUSED(IsVolatile);
const FilePath path = FilePath::fromString(QString::fromStdString(Name.str()));
const FilePath path = FilePath::fromUserInput(QString::fromStdString(Name.str()));
const expected_str<QByteArray> contents = path.fileContents(FileSize, 0);
QTC_ASSERT_EXPECTED(contents, return std::error_code());
@@ -72,7 +72,7 @@ public:
ErrorOr<Status> status(const Twine &Path) override
{
const Utils::FilePath path = FilePath::fromString(QString::fromStdString(Path.str()));
const FilePath path = FilePath::fromUserInput(QString::fromStdString(Path.str()));
QFileInfo fInfo(QString::fromStdString(Path.str()));
if (!fInfo.exists())

View File

@@ -426,8 +426,10 @@ CommandLine CMakeBuildStep::cmakeCommand() const
}
return s;
}));
if (m_useStaging->value())
cmd.addArg("install");
auto bs = qobject_cast<CMakeBuildSystem*>(buildSystem());
auto bs = qobject_cast<CMakeBuildSystem *>(buildSystem());
if (bs && bs->isMultiConfigReader()) {
cmd.addArg("--config");
if (m_configuration)
@@ -439,9 +441,6 @@ CommandLine CMakeBuildStep::cmakeCommand() const
if (!m_cmakeArguments->value().isEmpty())
cmd.addArgs(m_cmakeArguments->value(), CommandLine::Raw);
if (m_useStaging->value())
cmd.addArg("install");
bool toolArgumentsSpecified = false;
if (!m_toolArguments->value().isEmpty()) {
cmd.addArg("--");

View File

@@ -130,8 +130,8 @@ public:
class CocoTextMark : public TextEditor::TextMark
{
public:
CocoTextMark(const FilePath &fileName, const CocoDiagnostic &diag, const Id &clientId)
: TextEditor::TextMark(fileName, diag.range().start().line() + 1, {"Coco", clientId})
CocoTextMark(TextEditor::TextDocument *doc, const CocoDiagnostic &diag, const Id &clientId)
: TextEditor::TextMark(doc, diag.range().start().line() + 1, {"Coco", clientId})
, m_severity(diag.cocoSeverity())
{
setLineAnnotation(diag.message());
@@ -180,13 +180,13 @@ private:
});
}
TextEditor::TextMark *createTextMark(const FilePath &filePath,
TextEditor::TextMark *createTextMark(TextEditor::TextDocument *doc,
const Diagnostic &diagnostic,
bool /*isProjectFile*/) const override
{
const CocoDiagnostic cocoDiagnostic(diagnostic);
if (std::optional<CocoDiagnosticSeverity> severity = cocoDiagnostic.cocoSeverity())
return new CocoTextMark(filePath, cocoDiagnostic, client()->id());
return new CocoTextMark(doc, cocoDiagnostic, client()->id());
return nullptr;
}

View File

@@ -183,8 +183,24 @@ void CopilotClient::handleCompletions(const GetCompletionRequest::Response &resp
auto isValidCompletion = [](const Completion &completion) {
return completion.isValid() && !completion.text().trimmed().isEmpty();
};
const QList<Completion> completions = Utils::filtered(result->completions().toListOrEmpty(),
QList<Completion> completions = Utils::filtered(result->completions().toListOrEmpty(),
isValidCompletion);
// remove trailing whitespaces from the end of the completions
for (Completion &completion : completions) {
const LanguageServerProtocol::Range range = completion.range();
if (range.start().line() != range.end().line())
continue; // do not remove trailing whitespaces for multi-line replacements
const QString completionText = completion.text();
const int end = int(completionText.size()) - 1; // empty strings have been removed above
int delta = 0;
while (delta <= end && completionText[end - delta].isSpace())
++delta;
if (delta > 0)
completion.setText(completionText.chopped(delta));
}
if (completions.isEmpty())
return;
editor->insertSuggestion(

View File

@@ -27,6 +27,7 @@ public:
return typedValue<LanguageServerProtocol::Range>(LanguageServerProtocol::rangeKey);
}
QString text() const { return typedValue<QString>(LanguageServerProtocol::textKey); }
void setText(const QString &text) { insert(LanguageServerProtocol::textKey, text); }
QString uuid() const { return typedValue<QString>(uuidKey); }
bool isValid() const override

View File

@@ -181,6 +181,26 @@
\sa reload()
*/
/*!
\fn Core::IDocument::aboutToSave(const Utils::FilePath &filePath, bool autoSave)
This signal is emitted before the document is saved to \a filePath.
\a autoSave indicates whether this save was triggered by the auto save timer.
\sa save()
*/
/*!
\fn Core::IDocument::saved(const Utils::FilePath &filePath, bool autoSave)
This signal is emitted after the document was saved to \a filePath.
\a autoSave indicates whether this save was triggered by the auto save timer.
\sa save()
*/
/*!
\fn Core::IDocument::filePathChanged(const Utils::FilePath &oldName, const Utils::FilePath &newName)
@@ -315,11 +335,35 @@ IDocument::OpenResult IDocument::open(QString *errorString, const Utils::FilePat
Returns whether saving was successful.
The default implementation does nothing and returns \c false.
If saving was successful saved is emitted.
\sa shouldAutoSave()
\sa aboutToSave()
\sa saved()
*/
bool IDocument::save(QString *errorString, const Utils::FilePath &filePath, bool autoSave)
{
emit aboutToSave(filePath, autoSave);
const bool success = saveImpl(errorString, filePath, autoSave);
if (success)
emit saved(filePath, autoSave);
return success;
}
/*!
Implementation of saving the contents of the document to the \a filePath on disk.
If \a autoSave is \c true, the saving is done for an auto-save, so the
document should avoid cleanups or other operations that it does for
user-requested saves.
Use \a errorString to return an error message if saving failed.
Returns whether saving was successful.
The default implementation does nothing and returns \c false.
*/
bool IDocument::saveImpl(QString *errorString, const Utils::FilePath &filePath, bool autoSave)
{
Q_UNUSED(errorString)
Q_UNUSED(filePath)

View File

@@ -68,7 +68,7 @@ public:
virtual OpenResult open(QString *errorString, const Utils::FilePath &filePath, const Utils::FilePath &realFilePath);
virtual bool save(QString *errorString, const Utils::FilePath &filePath = Utils::FilePath(), bool autoSave = false);
bool save(QString *errorString, const Utils::FilePath &filePath = Utils::FilePath(), bool autoSave = false);
virtual QByteArray contents() const;
virtual bool setContents(const QByteArray &contents);
@@ -124,9 +124,16 @@ signals:
void aboutToReload();
void reloadFinished(bool success);
void aboutToSave(const Utils::FilePath &filePath, bool autoSave);
void saved(const Utils::FilePath &filePath, bool autoSave);
void filePathChanged(const Utils::FilePath &oldName, const Utils::FilePath &newName);
protected:
virtual bool saveImpl(QString *errorString,
const Utils::FilePath &filePath = Utils::FilePath(),
bool autoSave = false);
private:
Internal::IDocumentPrivate *d;
};

View File

@@ -435,10 +435,10 @@ TextEditor::TabSettings CppEditorDocument::tabSettings() const
return indenter()->tabSettings().value_or(TextEditor::TextDocument::tabSettings());
}
bool CppEditorDocument::save(QString *errorString, const FilePath &filePath, bool autoSave)
bool CppEditorDocument::saveImpl(QString *errorString, const FilePath &filePath, bool autoSave)
{
if (!indenter()->formatOnSave() || autoSave)
return TextEditor::TextDocument::save(errorString, filePath, autoSave);
return TextEditor::TextDocument::saveImpl(errorString, filePath, autoSave);
auto *layout = qobject_cast<TextEditor::TextDocumentLayout *>(document()->documentLayout());
const int documentRevision = layout->lastSaveRevision;
@@ -476,7 +476,7 @@ bool CppEditorDocument::save(QString *errorString, const FilePath &filePath, boo
settings.m_cleanWhitespace = false;
setStorageSettings(settings);
return TextEditor::TextDocument::save(errorString, filePath, autoSave);
return TextEditor::TextDocument::saveImpl(errorString, filePath, autoSave);
}
bool CppEditorDocument::usesClangd() const

View File

@@ -47,10 +47,6 @@ public:
QFuture<CursorInfo> cursorInfo(const CursorInfoParams &params);
TextEditor::TabSettings tabSettings() const override;
bool save(QString *errorString,
const Utils::FilePath &filePath = Utils::FilePath(),
bool autoSave = false) override;
bool usesClangd() const;
signals:
@@ -68,8 +64,12 @@ signals:
protected:
void applyFontSettings() override;
bool saveImpl(QString *errorString,
const Utils::FilePath &filePath = Utils::FilePath(),
bool autoSave = false) override;
private:
void invalidateFormatterCache();
void onFilePathChanged(const Utils::FilePath &oldPath, const Utils::FilePath &newPath);
void onMimeTypeChanged();

View File

@@ -82,7 +82,7 @@ Core::IDocument::OpenResult FormWindowFile::open(QString *errorString,
return OpenResult::Success;
}
bool FormWindowFile::save(QString *errorString, const FilePath &filePath, bool autoSave)
bool FormWindowFile::saveImpl(QString *errorString, const FilePath &filePath, bool autoSave)
{
const FilePath &actualName = filePath.isEmpty() ? this->filePath() : filePath;

View File

@@ -28,7 +28,6 @@ public:
// IDocument
OpenResult open(QString *errorString, const Utils::FilePath &filePath,
const Utils::FilePath &realFilePath) override;
bool save(QString *errorString, const Utils::FilePath &filePath, bool autoSave) override;
QByteArray contents() const override;
bool setContents(const QByteArray &contents) override;
bool shouldAutoSave() const override;
@@ -52,6 +51,9 @@ public:
void setShouldAutoSave(bool sad = true) { m_shouldAutoSave = sad; }
void updateIsModified();
protected:
bool saveImpl(QString *errorString, const Utils::FilePath &filePath, bool autoSave) override;
private:
void slotFormWindowRemoved(QDesignerFormWindowInterface *w);

View File

@@ -235,7 +235,7 @@ bool DiffEditorDocument::isSaveAsAllowed() const
return state() == LoadOK;
}
bool DiffEditorDocument::save(QString *errorString, const FilePath &filePath, bool autoSave)
bool DiffEditorDocument::saveImpl(QString *errorString, const FilePath &filePath, bool autoSave)
{
Q_UNUSED(errorString)
Q_UNUSED(autoSave)

View File

@@ -60,7 +60,6 @@ public:
QString fallbackSaveAsFileName() const override;
bool isSaveAsAllowed() const override;
bool save(QString *errorString, const Utils::FilePath &filePath, bool autoSave) override;
void reload();
bool reload(QString *errorString, ReloadFlag flag, ChangeType type) override;
OpenResult open(QString *errorString, const Utils::FilePath &filePath,
@@ -75,6 +74,9 @@ signals:
void documentChanged();
void descriptionChanged();
protected:
bool saveImpl(QString *errorString, const Utils::FilePath &filePath, bool autoSave) override;
private:
void beginReload();
void endReload(bool success);

View File

@@ -277,10 +277,14 @@ public:
{
QObject::disconnect(contentsChangedConnection);
QObject::disconnect(filePathChangedConnection);
QObject::disconnect(aboutToSaveConnection);
QObject::disconnect(savedConnection);
delete document;
}
QMetaObject::Connection contentsChangedConnection;
QMetaObject::Connection filePathChangedConnection;
QMetaObject::Connection aboutToSaveConnection;
QMetaObject::Connection savedConnection;
QTextDocument *document = nullptr;
};
QMap<TextEditor::TextDocument *, OpenedDocument> m_openedDocument;
@@ -648,6 +652,22 @@ void Client::openDocument(TextEditor::TextDocument *document)
if (isSupportedDocument(document))
openDocument(document);
});
d->m_openedDocument[document].savedConnection
= connect(document,
&TextDocument::saved,
this,
[this, document](const FilePath &saveFilePath) {
if (saveFilePath == document->filePath())
documentContentsSaved(document);
});
d->m_openedDocument[document].aboutToSaveConnection
= connect(document,
&TextDocument::aboutToSave,
this,
[this, document](const FilePath &saveFilePath) {
if (saveFilePath == document->filePath())
documentWillSave(document);
});
if (!d->m_documentVersions.contains(filePath))
d->m_documentVersions[filePath] = 0;
d->sendOpenNotification(filePath, document->mimeType(), document->plainText(),

View File

@@ -31,10 +31,8 @@ namespace LanguageClient {
class TextMark : public TextEditor::TextMark
{
public:
TextMark(const FilePath &fileName, const Diagnostic &diag, const Client *client)
: TextEditor::TextMark(fileName,
diag.range().start().line() + 1,
{client->name(), client->id()})
TextMark(TextDocument *doc, const Diagnostic &diag, const Client *client)
: TextEditor::TextMark(doc, diag.range().start().line() + 1, {client->name(), client->id()})
{
setLineAnnotation(diag.message());
setToolTip(diag.message());
@@ -106,7 +104,7 @@ void DiagnosticManager::showDiagnostics(const FilePath &filePath, int version)
= createDiagnosticSelection(diagnostic, doc->document());
if (!selection.cursor.isNull())
extraSelections << selection;
if (TextEditor::TextMark *mark = createTextMark(filePath, diagnostic, isProjectFile))
if (TextEditor::TextMark *mark = createTextMark(doc, diagnostic, isProjectFile))
marks.marks.append(mark);
}
if (!marks.marks.isEmpty())
@@ -118,13 +116,13 @@ void DiagnosticManager::showDiagnostics(const FilePath &filePath, int version)
}
}
TextEditor::TextMark *DiagnosticManager::createTextMark(const FilePath &filePath,
TextEditor::TextMark *DiagnosticManager::createTextMark(TextDocument *doc,
const Diagnostic &diagnostic,
bool /*isProjectFile*/) const
{
static const auto icon = QIcon::fromTheme("edit-copy", Utils::Icons::COPY.icon());
static const QString tooltip = Tr::tr("Copy to Clipboard");
auto mark = new TextMark(filePath, diagnostic, m_client);
auto mark = new TextMark(doc, diagnostic, m_client);
mark->setActionsProvider([text = diagnostic.message()] {
QAction *action = new QAction();
action->setIcon(icon);

View File

@@ -55,7 +55,7 @@ signals:
protected:
Client *client() const { return m_client; }
virtual TextEditor::TextMark *createTextMark(const Utils::FilePath &filePath,
virtual TextEditor::TextMark *createTextMark(TextEditor::TextDocument *doc,
const LanguageServerProtocol::Diagnostic &diagnostic,
bool isProjectFile) const;
virtual QTextEdit::ExtraSelection createDiagnosticSelection(

View File

@@ -63,10 +63,6 @@ LanguageClientManager::LanguageClientManager(QObject *parent)
this, &LanguageClientManager::documentOpened);
connect(EditorManager::instance(), &EditorManager::documentClosed,
this, &LanguageClientManager::documentClosed);
connect(EditorManager::instance(), &EditorManager::saved,
this, &LanguageClientManager::documentContentsSaved);
connect(EditorManager::instance(), &EditorManager::aboutToSave,
this, &LanguageClientManager::documentWillSave);
connect(ProjectManager::instance(), &ProjectManager::projectAdded,
this, &LanguageClientManager::projectAdded);
connect(ProjectManager::instance(), &ProjectManager::projectRemoved,
@@ -550,24 +546,6 @@ void LanguageClientManager::documentClosed(Core::IDocument *document)
m_clientForDocument.remove(textDocument);
}
void LanguageClientManager::documentContentsSaved(Core::IDocument *document)
{
if (auto textDocument = qobject_cast<TextEditor::TextDocument *>(document)) {
const QList<Client *> &clients = reachableClients();
for (Client *client : clients)
client->documentContentsSaved(textDocument);
}
}
void LanguageClientManager::documentWillSave(Core::IDocument *document)
{
if (auto textDocument = qobject_cast<TextEditor::TextDocument *>(document)) {
const QList<Client *> &clients = reachableClients();
for (Client *client : clients)
client->documentWillSave(textDocument);
}
}
void LanguageClientManager::updateProject(ProjectExplorer::Project *project)
{
for (BaseSettings *setting : std::as_const(m_currentSettings)) {

View File

@@ -91,8 +91,6 @@ private:
void editorOpened(Core::IEditor *editor);
void documentOpened(Core::IDocument *document);
void documentClosed(Core::IDocument *document);
void documentContentsSaved(Core::IDocument *document);
void documentWillSave(Core::IDocument *document);
void updateProject(ProjectExplorer::Project *project);
void projectAdded(ProjectExplorer::Project *project);

View File

@@ -146,9 +146,9 @@ const QString stmCubeProgrammerDetectionPath{HostOsInfo::isWindowsHost()
? QString("bin/STM32_Programmer_CLI.exe")
: QString("bin/STM32_Programmer.sh")};
const char renesasProgrammerSetting[]{"FlashProgrammerPath"};
const char renesasProgrammerSetting[]{"RenesasFlashProgrammer"};
const char renesasProgrammerCmakeVar[]{"RENESAS_FLASH_PROGRAMMER_PATH"};
const QString renesasProgrammerEnvVar{"RenesasFlashProgrammer_PATH"};
const char renesasProgrammerEnvVar[]{"RENESAS_FLASH_PROGRAMMER_PATH"};
const char renesasProgrammerLabel[]{"Renesas Flash Programmer"};
const QString renesasProgrammerDetectionPath{HostOsInfo::withExecutableSuffix("rfp-cli")};
@@ -1539,9 +1539,9 @@ void McuSupportTest::test_legacy_createThirdPartyPackage_data()
<< PackageCreator{[this]() {
return Legacy::createRenesasProgrammerPackage(settingsMockPtr);
}}
<< ghs_rh850_d1m1a_baremetal_json << defaultToolPath << defaultToolPath
<< renesasProgrammerSetting << renesasProgrammerCmakeVar << renesasProgrammerEnvVar
<< renesasProgrammerLabel << renesasProgrammerDetectionPath;
<< ghs_rh850_d1m1a_baremetal_json << empty << empty << renesasProgrammerSetting
<< renesasProgrammerCmakeVar << renesasProgrammerEnvVar << renesasProgrammerLabel
<< renesasProgrammerDetectionPath;
}
void McuSupportTest::test_legacy_createThirdPartyPackage()
@@ -1574,7 +1574,53 @@ void McuSupportTest::test_legacy_createThirdPartyPackage()
void McuSupportTest::test_createThirdPartyPackage_data()
{
test_legacy_createThirdPartyPackage_data();
QTest::addColumn<QString>("json");
QTest::addColumn<QString>("path");
QTest::addColumn<QString>("defaultPath");
QTest::addColumn<QString>("setting");
QTest::addColumn<QString>("cmakeVar");
QTest::addColumn<QString>("envVar");
QTest::addColumn<QString>("label");
QTest::addColumn<QString>("detectionPath");
// Sometimes the jsons have different values than the legacy packages
// Enter the expected values from the jsons here when they diverge from legacy values
QString programFiles = qtcEnvironmentVariable("Env:PROGRAMFILES(x86)");
const QString renesasProgrammerDefaultPath = {
HostOsInfo::isWindowsHost()
? QString("%1/Renesas Electronics/Programming Tools/Renesas "
"Flash Programmer V3.09").arg(programFiles)
: QString("")};
QTest::newRow("armgcc_mimxrt1050_evk_freertos_json mcuXpresso")
<< armgcc_mimxrt1050_evk_freertos_json << xpressoIdePath << xpressoIdePath
<< xpressoIdeSetting << xpressoIdeCmakeVar << xpressoIdeEnvVar << xpressoIdeLabel
<< xpressoIdeDetectionPath;
QTest::newRow("armgcc_mimxrt1064_evk_freertos_json mcuXpresso")
<< armgcc_mimxrt1064_evk_freertos_json << xpressoIdePath << xpressoIdePath
<< xpressoIdeSetting << xpressoIdeCmakeVar << xpressoIdeEnvVar << xpressoIdeLabel
<< xpressoIdeDetectionPath;
QTest::newRow("armgcc_mimxrt1170_evk_freertos_json mcuXpresso")
<< armgcc_mimxrt1170_evk_freertos_json << xpressoIdePath << xpressoIdePath
<< xpressoIdeSetting << xpressoIdeCmakeVar << xpressoIdeEnvVar << xpressoIdeLabel
<< xpressoIdeDetectionPath;
QTest::newRow("armgcc_stm32h750b_discovery_baremetal_json stmCubeProgrammer")
<< armgcc_stm32h750b_discovery_baremetal_json << stmCubeProgrammerPath
<< stmCubeProgrammerPath << stmCubeProgrammerSetting << empty << empty
<< stmCubeProgrammerLabel << stmCubeProgrammerDetectionPath;
QTest::newRow("armgcc_stm32f769i_discovery_freertos_json stmCubeProgrammer")
<< armgcc_stm32f769i_discovery_freertos_json << stmCubeProgrammerPath
<< stmCubeProgrammerPath << stmCubeProgrammerSetting << empty << empty
<< stmCubeProgrammerLabel << stmCubeProgrammerDetectionPath;
QTest::newRow("ghs_rh850_d1m1a_baremetal_json renesasProgrammer")
<< ghs_rh850_d1m1a_baremetal_json << renesasProgrammerDefaultPath << empty
<< "FlashProgrammerPath" << renesasProgrammerCmakeVar << "RenesasFlashProgrammer_PATH"
<< renesasProgrammerLabel << renesasProgrammerDetectionPath;
}
void McuSupportTest::test_createThirdPartyPackage()

View File

@@ -55,7 +55,7 @@ Core::IDocument::OpenResult ModelDocument::open(QString *errorString,
return result;
}
bool ModelDocument::save(QString *errorString, const Utils::FilePath &filePath, bool autoSave)
bool ModelDocument::saveImpl(QString *errorString, const Utils::FilePath &filePath, bool autoSave)
{
if (!d->documentController) {
*errorString = Tr::tr("No model loaded. Cannot save.");

View File

@@ -29,7 +29,6 @@ public:
OpenResult open(QString *errorString,
const Utils::FilePath &filePath,
const Utils::FilePath &realFilePath) override;
bool save(QString *errorString, const Utils::FilePath &filePath, bool autoSave) override;
bool shouldAutoSave() const override;
bool isModified() const override;
bool isSaveAsAllowed() const override;
@@ -39,6 +38,9 @@ public:
OpenResult load(QString *errorString, const QString &fileName);
protected:
bool saveImpl(QString *errorString, const Utils::FilePath &filePath, bool autoSave) override;
private:
ModelDocumentPrivate *d;
};

View File

@@ -61,7 +61,9 @@ static const QList<Target *> targetsForSelection(const Project *project,
{
if (targetSelection == ConfigSelection::All)
return project->targets();
return {project->activeTarget()};
if (project->activeTarget())
return {project->activeTarget()};
return {};
}
static const QList<BuildConfiguration *> buildConfigsForSelection(const Target *target,

View File

@@ -28,6 +28,7 @@
#include <utils/process.h>
#include <utils/processinterface.h>
#include <utils/qtcassert.h>
#include <utils/terminalinterface.h>
#include <utils/utilsicons.h>
#include <coreplugin/icontext.h>
@@ -1348,9 +1349,12 @@ void SimpleTargetRunnerPrivate::start()
m_stopRequested = false;
QVariantHash extraData = m_extraData;
extraData[TERMINAL_SHELL_NAME] = m_command.executable().fileName();
m_process.setCommand(cmdLine);
m_process.setEnvironment(env);
m_process.setExtraData(m_extraData);
m_process.setExtraData(extraData);
m_state = Run;
m_process.setWorkingDirectory(m_workingDirectory);

View File

@@ -120,9 +120,8 @@ static QJsonObject readObjJson(const FilePath &projectFile, QString *errorMessag
static QStringList readLines(const FilePath &projectFile)
{
const QString projectFileName = projectFile.fileName();
QSet<QString> visited = { projectFileName };
QStringList lines = { projectFileName };
QSet<QString> visited;
QStringList lines;
const expected_str<QByteArray> contents = projectFile.fileContents();
if (contents) {
@@ -144,9 +143,8 @@ static QStringList readLines(const FilePath &projectFile)
static QStringList readLinesJson(const FilePath &projectFile, QString *errorMessage)
{
const QString projectFileName = projectFile.fileName();
QSet<QString> visited = { projectFileName };
QStringList lines = { projectFileName };
QSet<QString> visited;
QStringList lines;
const QJsonObject obj = readObjJson(projectFile, errorMessage);
for (const QJsonValue &file : obj.value("files").toArray()) {
@@ -205,8 +203,6 @@ static FileType getFileType(const FilePath &f)
{
if (f.endsWith(".py"))
return FileType::Source;
if (f.endsWith(".pyproject") || f.endsWith(".pyqtc"))
return FileType::Project;
if (f.endsWith(".qrc"))
return FileType::Resource;
if (f.endsWith(".ui"))
@@ -221,11 +217,16 @@ void PythonBuildSystem::triggerParsing()
ParseGuard guard = guardParsingRun();
parse();
const QDir baseDir(projectDirectory().toString());
QList<BuildTargetInfo> appTargets;
auto newRoot = std::make_unique<PythonProjectNode>(projectDirectory());
for (const FileEntry &entry: std::as_const(m_files)) {
const FilePath projectFile = projectFilePath();
const QString displayName = projectFile.relativePathFrom(projectDirectory()).toUserOutput();
newRoot->addNestedNode(
std::make_unique<PythonFileNode>(projectFile, displayName, FileType::Project));
for (const FileEntry &entry : std::as_const(m_files)) {
const QString displayName = entry.filePath.relativePathFrom(projectDirectory()).toUserOutput();
const FileType fileType = getFileType(entry.filePath);
@@ -236,7 +237,7 @@ void PythonBuildSystem::triggerParsing()
bti.displayName = displayName;
bti.buildKey = entry.filePath.toString();
bti.targetFilePath = entry.filePath;
bti.projectFilePath = projectFilePath();
bti.projectFilePath = projectFile;
bti.isQtcRunnable = entry.filePath.fileName() == "main.py";
appTargets.append(bti);
}
@@ -301,12 +302,21 @@ bool PythonBuildSystem::addFiles(Node *, const FilePaths &filePaths, FilePaths *
{
const Utils::FilePath projectDir = projectDirectory();
auto comp = [](const FileEntry &left, const FileEntry &right) {
return left.rawEntry < right.rawEntry;
};
const bool isSorted = std::is_sorted(m_files.begin(), m_files.end(), comp);
for (const FilePath &filePath : filePaths) {
if (!projectDir.isSameDevice(filePath))
return false;
m_files.append(FileEntry{filePath.relativePathFrom(projectDir).toString(), filePath});
}
if (isSorted)
std::sort(m_files.begin(), m_files.end(), comp);
return save();
}

View File

@@ -120,10 +120,10 @@ Core::IDocument::OpenResult ResourceEditorDocument::open(QString *errorString,
return OpenResult::Success;
}
bool ResourceEditorDocument::save(QString *errorString, const FilePath &filePath, bool autoSave)
bool ResourceEditorDocument::saveImpl(QString *errorString, const FilePath &filePath, bool autoSave)
{
if (debugResourceEditorW)
qDebug() << ">ResourceEditorW::save: " << filePath;
qDebug() << ">ResourceEditorW::saveImpl: " << filePath;
const FilePath &actualName = filePath.isEmpty() ? this->filePath() : filePath;
if (actualName.isEmpty())

View File

@@ -30,7 +30,6 @@ public:
//IDocument
OpenResult open(QString *errorString, const Utils::FilePath &filePath,
const Utils::FilePath &realFilePath) override;
bool save(QString *errorString, const Utils::FilePath &filePath, bool autoSave) override;
QString plainText() const;
QByteArray contents() const override;
bool setContents(const QByteArray &contents) override;
@@ -47,6 +46,9 @@ public:
signals:
void loaded(bool success);
protected:
bool saveImpl(QString *errorString, const Utils::FilePath &filePath, bool autoSave) override;
private:
void dirtyChanged(bool);

View File

@@ -58,7 +58,7 @@ Core::IDocument::OpenResult ScxmlEditorDocument::open(QString *errorString,
return OpenResult::Success;
}
bool ScxmlEditorDocument::save(QString *errorString, const FilePath &filePath, bool autoSave)
bool ScxmlEditorDocument::saveImpl(QString *errorString, const FilePath &filePath, bool autoSave)
{
const FilePath oldFileName = this->filePath();
const FilePath actualName = filePath.isEmpty() ? oldFileName : filePath;

View File

@@ -30,7 +30,6 @@ public:
OpenResult open(QString *errorString,
const Utils::FilePath &filePath,
const Utils::FilePath &realFilePath) override;
bool save(QString *errorString, const Utils::FilePath &filePath, bool autoSave) override;
bool shouldAutoSave() const override;
bool isSaveAsAllowed() const override;
bool isModified() const override;
@@ -46,6 +45,9 @@ public:
signals:
void reloadRequested(QString *errorString, const QString &);
protected:
bool saveImpl(QString *errorString, const Utils::FilePath &filePath, bool autoSave) override;
private:
QPointer<Common::MainWidget> m_designWidget;
};

View File

@@ -41,7 +41,9 @@ Core::IDocument::OpenResult ObjectsMapDocument::open(QString *errorString,
return result;
}
bool ObjectsMapDocument::save(QString *errorString, const Utils::FilePath &fileName, bool autoSave)
bool ObjectsMapDocument::saveImpl(QString *errorString,
const Utils::FilePath &fileName,
bool autoSave)
{
const Utils::FilePath actual = fileName.isEmpty() ? filePath() : fileName;
if (actual.isEmpty())

View File

@@ -21,7 +21,6 @@ public:
OpenResult open(QString *errorString,
const Utils::FilePath &fileName,
const Utils::FilePath &realFileName) override;
bool save(QString *errorString, const Utils::FilePath &fileName, bool autoSave) override;
Utils::FilePath fallbackSaveAsPath() const override;
QString fallbackSaveAsFileName() const override;
bool isModified() const override { return m_isModified; }
@@ -34,6 +33,9 @@ public:
QByteArray contents() const override;
ObjectsMapModel *model() const { return m_contentModel; }
protected:
bool saveImpl(QString *errorString, const Utils::FilePath &fileName, bool autoSave) override;
private:
OpenResult openImpl(QString *error,
const Utils::FilePath &fileName,

View File

@@ -49,7 +49,11 @@ public:
if (!terminal) {
terminal = new TerminalWidget(nullptr, openParameters);
terminal->setShellName(setup.m_commandLine.executable().fileName());
terminal->setShellName(
setup.m_extraData
.value(TERMINAL_SHELL_NAME, setup.m_commandLine.executable().fileName())
.toString());
m_terminalPane->addTerminal(terminal, "App");
} else {
terminal->restart(openParameters);

View File

@@ -146,8 +146,8 @@ void TerminalWidget::setupPty()
TerminalSettings::instance().shellArguments.value(),
CommandLine::Raw});
Environment env = m_openParameters.environment.value_or(
shellCommand.executable().deviceEnvironment());
Environment env = m_openParameters.environment.value_or(Environment{})
.appliedToEnvironment(shellCommand.executable().deviceEnvironment());
// For git bash on Windows
env.prependOrSetPath(shellCommand.executable().parentDir());

View File

@@ -185,6 +185,28 @@ void GenerigHighlighterTests::testChange()
compareFormats(actualFormats.at(i), formatRanges.at(i));
}
void GenerigHighlighterTests::testPreeditText()
{
QTextBlock block = m_editor->textDocument()->document()->findBlockByNumber(2);
QVERIFY(block.isValid());
block.layout()->setPreeditArea(7, "uaf");
m_editor->textDocument()->syntaxHighlighter()->rehighlight();
const FormatRanges formatRanges = {{0, 4, toFormat(C_VISUAL_WHITESPACE)},
{4, 3, toFormat(C_TYPE)},
{10, 3, toFormat(C_TYPE)},
{13, 1, toFormat(C_FUNCTION)},
{14, 1, toFormat(C_VISUAL_WHITESPACE)},
{15, 6, toFormat(C_STRING)},
{21, 1, toFormat(C_FUNCTION)}};
const QList<QTextLayout::FormatRange> actualFormats = block.layout()->formats();
// full hash calculation for QTextCharFormat fails so just check the important entries of format
QCOMPARE(actualFormats.size(), formatRanges.size());
for (int i = 0; i < formatRanges.size(); ++i)
compareFormats(actualFormats.at(i), formatRanges.at(i));
}
void GenerigHighlighterTests::cleanupTestCase()
{
if (m_editor)

View File

@@ -17,6 +17,7 @@ private slots:
void testHighlight_data();
void testHighlight();
void testChange();
void testPreeditText();
void cleanupTestCase();
private:

View File

@@ -17,6 +17,12 @@
namespace TextEditor {
enum HighlighterTypeProperty
{
SyntaxHighlight = QTextFormat::UserProperty + 1,
SemanticHighlight = QTextFormat::UserProperty + 2
};
class SyntaxHighlighterPrivate
{
SyntaxHighlighter *q_ptr = nullptr;
@@ -98,9 +104,9 @@ void SyntaxHighlighterPrivate::applyFormatChanges(int from, int charsRemoved, in
QVector<QTextLayout::FormatRange> ranges;
QVector<QTextLayout::FormatRange> oldRanges;
std::tie(ranges, oldRanges)
std::tie(oldRanges, ranges)
= Utils::partition(layout->formats(), [](const QTextLayout::FormatRange &range) {
return range.format.property(QTextFormat::UserProperty).toBool();
return range.format.property(SyntaxHighlight).toBool();
});
if (currentBlock.contains(from)) {
@@ -129,8 +135,23 @@ void SyntaxHighlighterPrivate::applyFormatChanges(int from, int charsRemoved, in
while (i < formatChanges.count() && formatChanges.at(i) == r.format)
++i;
r.format.setProperty(SyntaxHighlight, true);
r.length = i - r.start;
const QString preeditText = currentBlock.layout()->preeditAreaText();
if (!preeditText.isEmpty()) {
const int preeditPosition = currentBlock.layout()->preeditAreaPosition();
if (r.start >= preeditPosition) {
r.start += preeditText.length();
} else if (r.start + r.length > preeditPosition) {
QTextLayout::FormatRange beforePreeditRange = r;
r.start = preeditPosition + preeditText.length();
r.length = r.length - (r.start - preeditPosition);
beforePreeditRange.length = preeditPosition - beforePreeditRange.start;
newRanges << beforePreeditRange;
}
}
newRanges << r;
}
@@ -656,6 +677,24 @@ void SyntaxHighlighter::setExtraFormats(const QTextBlock &block,
if (block.layout() == nullptr || blockLength == 0)
return;
const QString preeditText = block.layout()->preeditAreaText();
if (!preeditText.isEmpty()) {
QVector<QTextLayout::FormatRange> additionalRanges;
const int preeditPosition = block.layout()->preeditAreaPosition();
for (QTextLayout::FormatRange &r : formats) {
if (r.start >= preeditPosition) {
r.start += preeditText.length();
} else if (r.start + r.length > preeditPosition) {
QTextLayout::FormatRange afterPreeditRange = r;
afterPreeditRange.start = preeditPosition + preeditText.length();
afterPreeditRange.length = r.length - (preeditPosition - r.start);
additionalRanges << afterPreeditRange;
r.length = preeditPosition - r.start;
}
}
formats << additionalRanges;
}
Utils::sort(formats, byStartOfRange);
const QVector<QTextLayout::FormatRange> all = block.layout()->formats();
@@ -663,11 +702,11 @@ void SyntaxHighlighter::setExtraFormats(const QTextBlock &block,
QVector<QTextLayout::FormatRange> formatsToApply;
std::tie(previousSemanticFormats, formatsToApply)
= Utils::partition(all, [](const QTextLayout::FormatRange &r) {
return r.format.hasProperty(QTextFormat::UserProperty);
return r.format.property(SemanticHighlight).toBool();
});
for (auto &format : formats)
format.format.setProperty(QTextFormat::UserProperty, true);
format.format.setProperty(SemanticHighlight, true);
if (formats.size() == previousSemanticFormats.size()) {
Utils::sort(previousSemanticFormats, byStartOfRange);
@@ -694,7 +733,7 @@ void SyntaxHighlighter::clearExtraFormats(const QTextBlock &block)
const QVector<QTextLayout::FormatRange> formatsToApply
= Utils::filtered(block.layout()->formats(), [](const QTextLayout::FormatRange &r) {
return !r.format.hasProperty(QTextFormat::UserProperty);
return !r.format.property(SemanticHighlight).toBool();
});
bool wasInReformatBlocks = d->inReformatBlocks;

View File

@@ -622,7 +622,7 @@ SyntaxHighlighter *TextDocument::syntaxHighlighter() const
* If \a autoSave is true, the cursor will be restored and some signals suppressed
* and we do not clean up the text file (cleanWhitespace(), ensureFinalNewLine()).
*/
bool TextDocument::save(QString *errorString, const FilePath &filePath, bool autoSave)
bool TextDocument::saveImpl(QString *errorString, const FilePath &filePath, bool autoSave)
{
QTextCursor cursor(&d->m_document);

View File

@@ -101,7 +101,6 @@ public:
static bool marksAnnotationHidden(const Utils::Id &category);
// IDocument implementation.
bool save(QString *errorString, const Utils::FilePath &filePath, bool autoSave) override;
QByteArray contents() const override;
bool setContents(const QByteArray &contents) override;
bool shouldAutoSave() const override;
@@ -166,6 +165,7 @@ signals:
protected:
virtual void applyFontSettings();
bool saveImpl(QString *errorString, const Utils::FilePath &filePath, bool autoSave) override;
private:
OpenResult openImpl(QString *errorString,

View File

@@ -4947,15 +4947,15 @@ void TextEditorWidget::paintEvent(QPaintEvent *e)
paintBlock(&painter, data.block, data.offset, blockData.selections, data.eventRect);
if (data.isEditable && data.context.cursorPosition < -1
&& !blockData.layout->preeditAreaText().isEmpty()) {
const int cursorPos = blockData.layout->preeditAreaPosition()
- (data.context.cursorPosition + 2);
data.cursors.append(generateCursorData(cursorPos, data, blockData, painter));
}
if (drawCursor && !drawCursorAsBlock)
if (data.isEditable && !blockData.layout->preeditAreaText().isEmpty()) {
if (data.context.cursorPosition < -1) {
const int cursorPos = blockData.layout->preeditAreaPosition()
- (data.context.cursorPosition + 2);
data.cursors = {generateCursorData(cursorPos, data, blockData, painter)};
}
} else if (drawCursor && !drawCursorAsBlock) {
d->addCursorsPosition(data, painter, blockData);
}
d->paintIndentDepth(data, painter, blockData);
d->paintAdditionalVisualWhitespaces(data, painter, blockData.boundingRect.top());
d->paintReplacement(data, painter, blockData.boundingRect.top());

View File

@@ -159,6 +159,9 @@ QPainterPath TextEditorOverlay::createSelectionPath(const QTextCursor &begin, co
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())
beginChar += preeditAreaText.length();
QTextLine line = blockLayout->lineForTextPosition(beginChar);
QTC_ASSERT(line.isValid(), return {});
firstLine = line.lineNumber();
@@ -171,6 +174,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())
endChar += preeditAreaText.length();
QTextLine line = blockLayout->lineForTextPosition(endChar);
QTC_ASSERT(line.isValid(), return {});
lastLine = line.lineNumber();

View File

@@ -34,6 +34,7 @@ class TextMarkRegistry : public QObject
Q_OBJECT
public:
static void add(TextMark *mark);
static void add(TextMark *mark, TextDocument *document);
static bool remove(TextMark *mark);
private:
@@ -75,6 +76,16 @@ TextMark::TextMark(const FilePath &filePath, int lineNumber, TextMarkCategory ca
TextMarkRegistry::add(this);
}
TextMark::TextMark(TextDocument *document, int lineNumber, TextMarkCategory category)
: m_fileName(QTC_GUARD(document) ? document->filePath() : FilePath())
, m_lineNumber(lineNumber)
, m_visible(true)
, m_category(category)
{
if (!m_fileName.isEmpty())
TextMarkRegistry::add(this, document);
}
TextMark::~TextMark()
{
if (!m_fileName.isEmpty())
@@ -461,9 +472,14 @@ TextMarkRegistry::TextMarkRegistry(QObject *parent)
}
void TextMarkRegistry::add(TextMark *mark)
{
add(mark, TextDocument::textDocumentForFilePath(mark->filePath()));
}
void TextMarkRegistry::add(TextMark *mark, TextDocument *document)
{
instance()->m_marks[mark->filePath()].insert(mark);
if (TextDocument *document = TextDocument::textDocumentForFilePath(mark->filePath()))
if (document)
document->addMark(mark);
}

View File

@@ -40,6 +40,7 @@ class TEXTEDITOR_EXPORT TextMark
public:
TextMark() = delete;
TextMark(const Utils::FilePath &filePath, int lineNumber, TextMarkCategory category);
TextMark(TextDocument *document, int lineNumber, TextMarkCategory category);
virtual ~TextMark();
// determine order on markers on the same line.

View File

@@ -67,7 +67,7 @@ void SubmitEditorFile::setModified(bool modified)
emit changed();
}
bool SubmitEditorFile::save(QString *errorString, const FilePath &filePath, bool autoSave)
bool SubmitEditorFile::saveImpl(QString *errorString, const FilePath &filePath, bool autoSave)
{
const FilePath &fName = filePath.isEmpty() ? this->filePath() : filePath;
FileSaver saver(fName, QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text);

View File

@@ -23,11 +23,13 @@ public:
bool setContents(const QByteArray &contents) override;
bool isModified() const override { return m_modified; }
bool save(QString *errorString, const Utils::FilePath &filePath, bool autoSave) override;
ReloadBehavior reloadBehavior(ChangeTrigger state, ChangeType type) const override;
void setModified(bool modified = true);
protected:
bool saveImpl(QString *errorString, const Utils::FilePath &filePath, bool autoSave) override;
private:
bool m_modified;
VcsBaseSubmitEditor *m_editor;

View File

@@ -26,6 +26,7 @@ private slots:
void test_setExtraAdditionalFormats();
void test_clearExtraFormats();
void test_incrementalApplyAdditionalFormats();
void test_preeditText();
void cleanup();
private:
@@ -235,6 +236,7 @@ void tst_highlighter::test_incrementalApplyAdditionalFormats()
formatHash);
formats = firstBlock.layout()->formats();
QCOMPARE(formats.size(), 1);
QCOMPARE(formats.at(0).format.fontItalic(), true);
QCOMPARE(formats.at(0).format.fontOverline(), false);
QCOMPARE(formats.at(0).start, 0);
@@ -338,6 +340,28 @@ void tst_highlighter::test_incrementalApplyAdditionalFormats()
QCOMPARE(formats.at(1).length, 2);
}
void tst_highlighter::test_preeditText()
{
QCOMPARE(doc->blockCount(), 4);
QTextBlock firstBlock = doc->findBlockByNumber(0);
firstBlock.layout()->setPreeditArea(2, "uaf");
SemanticHighlighter::setExtraAdditionalFormats(highlighter, highlightingResults(), formatHash);
auto formats = firstBlock.layout()->formats();
QCOMPARE(formats.size(), 2);
QCOMPARE(formats.at(0).format.fontItalic(), true);
QCOMPARE(formats.at(0).format.fontOverline(), false);
QCOMPARE(formats.at(0).start, 0);
QCOMPARE(formats.at(0).length, 2);
QCOMPARE(formats.at(1).format.fontItalic(), true);
QCOMPARE(formats.at(1).format.fontOverline(), false);
QCOMPARE(formats.at(1).start, 5);
QCOMPARE(formats.at(1).length, 3);
}
void tst_highlighter::cleanup()
{
delete doc;

View File

@@ -25,8 +25,8 @@ def main():
test.warning("Parsing project timed out")
compareProjectTree(rootNodeTemplate % "Qt Creator", "projecttree_creator.tsv")
buildIssuesTexts = map(lambda i: str(i[0]), getBuildIssues())
deprecationWarnings = filter(lambda s: "deprecated" in s, buildIssuesTexts)
deprecationWarnings = "\n".join(set(filter(lambda s: "deprecated" in s, buildIssuesTexts)))
if deprecationWarnings:
test.warning("Creator claims that the .qbs file uses deprecated features.",
"\n".join(set(deprecationWarnings)))
deprecationWarnings)
invokeMenuItem("File", "Exit")