Merge remote-tracking branch 'origin/master' into 8.0

Change-Id: I0ab7200a8696e52122b2739cbc740e940336f962
This commit is contained in:
Eike Ziller
2022-06-01 12:23:29 +02:00
175 changed files with 4579 additions and 2922 deletions

View File

@@ -837,6 +837,8 @@ void Qt5InformationNodeInstanceServer::updateActiveSceneToEditView3D(bool timerC
auto helper = qobject_cast<QmlDesigner::Internal::GeneralHelper *>(m_3dHelper); auto helper = qobject_cast<QmlDesigner::Internal::GeneralHelper *>(m_3dHelper);
if (helper) if (helper)
helper->storeToolState(helper->globalStateId(), helper->lastSceneIdKey(), QVariant(sceneId), 0); helper->storeToolState(helper->globalStateId(), helper->lastSceneIdKey(), QVariant(sceneId), 0);
#else
Q_UNUSED(timerCall)
#endif #endif
} }
@@ -1157,6 +1159,8 @@ void Qt5InformationNodeInstanceServer::doRenderModelNode3DImageView(const Reques
{PuppetToCreatorCommand::RenderModelNodePreviewImage, {PuppetToCreatorCommand::RenderModelNodePreviewImage,
QVariant::fromValue(imgContainer)}); QVariant::fromValue(imgContainer)});
} }
#else
Q_UNUSED(cmd)
#endif #endif
} }

View File

@@ -82,6 +82,9 @@ void Quick3DNodeInstance::initialize(const ObjectNodeInstance::Pointer &objectNo
m_dummyRootViewCreateFunction = "createViewForNode"; m_dummyRootViewCreateFunction = "createViewForNode";
Quick3DRenderableNodeInstance::initialize(objectNodeInstance, flags); Quick3DRenderableNodeInstance::initialize(objectNodeInstance, flags);
#else
Q_UNUSED(objectNodeInstance)
Q_UNUSED(flags)
#endif // QUICK3D_MODULE #endif // QUICK3D_MODULE
} }

View File

@@ -32121,16 +32121,16 @@ Spustil jste Qemu?</translation>
<translation>Qt %1 (%2)</translation> <translation>Qt %1 (%2)</translation>
</message> </message>
<message> <message>
<source>qmake does not exist or is not executable</source> <source>%1 does not exist or is not executable</source>
<translation>Soubor qmake neexistuje nebo není spustitelný</translation> <translation>Soubor %1 neexistuje nebo není spustitelný</translation>
</message> </message>
<message> <message>
<source>Qt version is not properly installed, please run make install</source> <source>Qt version is not properly installed, please run make install</source>
<translation>Verze Qt není správně nainstalována. Proveďte, prosím, příkaz &quot;make install&quot;</translation> <translation>Verze Qt není správně nainstalována. Proveďte, prosím, příkaz &quot;make install&quot;</translation>
</message> </message>
<message> <message>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source> <source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation>Cestu ke spustitelným souborům instalace Qt se nepodařilo určit. Možná je cesta k qmake chybná?</translation> <translation>Cestu ke spustitelným souborům instalace Qt se nepodařilo určit. Možná je cesta k %1 chybná?</translation>
</message> </message>
<message> <message>
<source>The default mkspec symlink is broken.</source> <source>The default mkspec symlink is broken.</source>

View File

@@ -35382,16 +35382,16 @@ For flere detaljer, se /etc/sysctl.d/10-ptrace.conf
<translation>Ingen qmake-sti sat</translation> <translation>Ingen qmake-sti sat</translation>
</message> </message>
<message> <message>
<source>qmake does not exist or is not executable</source> <source>%1 does not exist or is not executable</source>
<translation>qmake findes ikke eller er ikke eksekverbar</translation> <translation>%1 findes ikke eller er ikke eksekverbar</translation>
</message> </message>
<message> <message>
<source>Qt version is not properly installed, please run make install</source> <source>Qt version is not properly installed, please run make install</source>
<translation>Qt version er ikke ordentligt installeret, kør venligst make install</translation> <translation>Qt version er ikke ordentligt installeret, kør venligst make install</translation>
</message> </message>
<message> <message>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source> <source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation>Kunne ikke beslutte stien til binærene af Qt installationen, måske er qmake-stien forkert?</translation> <translation>Kunne ikke beslutte stien til binærene af Qt installationen, måske er %1-stien forkert?</translation>
</message> </message>
<message> <message>
<source>The default mkspec symlink is broken.</source> <source>The default mkspec symlink is broken.</source>

View File

@@ -10113,8 +10113,8 @@ Dies ist unabhängig vom Wert der Eigenschaft &quot;visible&quot; in QML.</trans
<translation>Es ist keine qmake-Pfad gesetzt</translation> <translation>Es ist keine qmake-Pfad gesetzt</translation>
</message> </message>
<message> <message>
<source>qmake does not exist or is not executable</source> <source>%1 does not exist or is not executable</source>
<translation>Die qmake-Datei existiert nicht oder ist nicht ausführbar</translation> <translation>Die %1-Datei existiert nicht oder ist nicht ausführbar</translation>
</message> </message>
<message> <message>
<source>Qt version has no name</source> <source>Qt version has no name</source>
@@ -10141,8 +10141,8 @@ Dies ist unabhängig vom Wert der Eigenschaft &quot;visible&quot; in QML.</trans
<translation>Die Qt-Version ist nicht richtig installiert, führen Sie bitte make install aus</translation> <translation>Die Qt-Version ist nicht richtig installiert, führen Sie bitte make install aus</translation>
</message> </message>
<message> <message>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source> <source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation>Der Pfad zu den ausführbaren Dateien der Qt-Installation konnte nicht bestimmt werden, möglicherweise ist der Pfad zu qmake falsch?</translation> <translation>Der Pfad zu den ausführbaren Dateien der Qt-Installation konnte nicht bestimmt werden, möglicherweise ist der Pfad zu %1 falsch?</translation>
</message> </message>
<message> <message>
<source>The default mkspec symlink is broken.</source> <source>The default mkspec symlink is broken.</source>

View File

@@ -33604,8 +33604,8 @@ Nous allons essayer de travailler avec cela mais vous pourrez rencontrer des pro
<translation>Chemin de qmake non spécifié</translation> <translation>Chemin de qmake non spécifié</translation>
</message> </message>
<message> <message>
<source>qmake does not exist or is not executable</source> <source>%1 does not exist or is not executable</source>
<translation>qmake n&apos;existe pas ou n&apos;est pas exécutable</translation> <translation>%1 n&apos;existe pas ou n&apos;est pas exécutable</translation>
</message> </message>
<message> <message>
<source>Qt version has no name</source> <source>Qt version has no name</source>
@@ -33633,8 +33633,8 @@ Nous allons essayer de travailler avec cela mais vous pourrez rencontrer des pro
<translation>La version de Qt n&apos;est pas correctement installée, veuillez exécuter make install</translation> <translation>La version de Qt n&apos;est pas correctement installée, veuillez exécuter make install</translation>
</message> </message>
<message> <message>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source> <source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation>Impossible de déterminer le chemin vers les programmes de Qt, peut-être que le chemin vers qmake est faux ?</translation> <translation>Impossible de déterminer le chemin vers les programmes de Qt, peut-être que le chemin vers %1 est faux ?</translation>
</message> </message>
<message> <message>
<source>The default mkspec symlink is broken.</source> <source>The default mkspec symlink is broken.</source>

View File

@@ -41628,7 +41628,7 @@ Saving failed.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source>qmake does not exist or is not executable</source> <source>%1 does not exist or is not executable</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
@@ -41636,7 +41636,7 @@ Saving failed.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source> <source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>

View File

@@ -26108,16 +26108,16 @@ Do you want to save the data first?</source>
<translation>qmake </translation> <translation>qmake </translation>
</message> </message>
<message> <message>
<source>qmake does not exist or is not executable</source> <source>%1 does not exist or is not executable</source>
<translation>qmake </translation> <translation>%1 </translation>
</message> </message>
<message> <message>
<source>Qt version is not properly installed, please run make install</source> <source>Qt version is not properly installed, please run make install</source>
<translation>Qt make install </translation> <translation>Qt make install </translation>
</message> </message>
<message> <message>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source> <source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation>Qt qmake </translation> <translation>Qt %1 </translation>
</message> </message>
<message> <message>
<source>The default mkspec symlink is broken.</source> <source>The default mkspec symlink is broken.</source>

View File

@@ -12382,8 +12382,8 @@ Dla projektów CMake, upewnij się, że zmienna QML_IMPORT_PATH jest obecna w CM
<translation>Nie ustawiono ścieżki do qmake</translation> <translation>Nie ustawiono ścieżki do qmake</translation>
</message> </message>
<message> <message>
<source>qmake does not exist or is not executable</source> <source>%1 does not exist or is not executable</source>
<translation>Brak qmake lub nie jest on plikiem wykonywalnym</translation> <translation>Brak %1 lub nie jest on plikiem wykonywalnym</translation>
</message> </message>
<message> <message>
<source>Qt version has no name</source> <source>Qt version has no name</source>
@@ -12410,8 +12410,8 @@ Dla projektów CMake, upewnij się, że zmienna QML_IMPORT_PATH jest obecna w CM
<translation>Wersja Qt zainstalowana niepoprawnie, uruchom komendę: make install</translation> <translation>Wersja Qt zainstalowana niepoprawnie, uruchom komendę: make install</translation>
</message> </message>
<message> <message>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source> <source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation>Nie można określić ścieżki do plików binarnych instalacji Qt. Sprawdź ścieżkę do qmake.</translation> <translation>Nie można określić ścieżki do plików binarnych instalacji Qt. Sprawdź ścieżkę do %1.</translation>
</message> </message>
<message> <message>
<source>The default mkspec symlink is broken.</source> <source>The default mkspec symlink is broken.</source>

View File

@@ -45319,8 +45319,8 @@ For more details, see /etc/sysctl.d/10-ptrace.conf
<translation>Путь к qmake не указан</translation> <translation>Путь к qmake не указан</translation>
</message> </message>
<message> <message>
<source>qmake does not exist or is not executable</source> <source>%1 does not exist or is not executable</source>
<translation>qmake отсутствует или не запускается</translation> <translation>%1 отсутствует или не запускается</translation>
</message> </message>
<message> <message>
<source>Qt version has no name</source> <source>Qt version has no name</source>
@@ -45347,8 +45347,8 @@ For more details, see /etc/sysctl.d/10-ptrace.conf
<translation>Профиль Qt не установлен, пожалуйста выполните make install</translation> <translation>Профиль Qt не установлен, пожалуйста выполните make install</translation>
</message> </message>
<message> <message>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source> <source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation>Не удалось определить путь к утилитам Qt. Может путь к qmake неверен?</translation> <translation>Не удалось определить путь к утилитам Qt. Может путь к %1 неверен?</translation>
</message> </message>
<message> <message>
<source>The default mkspec symlink is broken.</source> <source>The default mkspec symlink is broken.</source>

View File

@@ -21803,8 +21803,8 @@ Projekte programov QML izvede pregledovalnik QML in jih ni potrebno zgraditi.</t
</message> </message>
<message> <message>
<location line="+58"/> <location line="+58"/>
<source>qmake does not exist or is not executable</source> <source>%1 does not exist or is not executable</source>
<translation>Datoteka »qmake« ne obstaja ali pa ni izvršljiva</translation> <translation>Datoteka »%1« ne obstaja ali pa ni izvršljiva</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
@@ -21813,8 +21813,8 @@ Projekte programov QML izvede pregledovalnik QML in jih ni potrebno zgraditi.</t
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source> <source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation>Ni bilo moč ugotoviti poti do programov namestitve Qt. Morda je pot do qmake napačna?</translation> <translation>Ni bilo moč ugotoviti poti do programov namestitve Qt. Morda je pot do %1 napačna?</translation>
</message> </message>
<message> <message>
<location line="+3"/> <location line="+3"/>

View File

@@ -22711,8 +22711,8 @@ For more details, see /etc/sysctl.d/10-ptrace.conf
<translation>Шлях до qmake не задано</translation> <translation>Шлях до qmake не задано</translation>
</message> </message>
<message> <message>
<source>qmake does not exist or is not executable</source> <source>%1 does not exist or is not executable</source>
<translation>qmake не існує або не є виконуваним модулем</translation> <translation>%1 не існує або не є виконуваним модулем</translation>
</message> </message>
<message> <message>
<source>ABI detection failed: Make sure to use a matching compiler when building.</source> <source>ABI detection failed: Make sure to use a matching compiler when building.</source>
@@ -22763,8 +22763,8 @@ For more details, see /etc/sysctl.d/10-ptrace.conf
<translation>Версія Qt не встановлена як слід, будь ласка, запустіть make install</translation> <translation>Версія Qt не встановлена як слід, будь ласка, запустіть make install</translation>
</message> </message>
<message> <message>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source> <source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation>Не вдалось визначити шлях до виконуваних модулів встановлення Qt, можливо, шлях до qmake помилковий?</translation> <translation>Не вдалось визначити шлях до виконуваних модулів встановлення Qt, можливо, шлях до %1 помилковий?</translation>
</message> </message>
<message> <message>
<source>The default mkspec symlink is broken.</source> <source>The default mkspec symlink is broken.</source>

View File

@@ -27948,8 +27948,8 @@ Did you start Qemu?</source>
<translation>qmake路径</translation> <translation>qmake路径</translation>
</message> </message>
<message> <message>
<source>qmake does not exist or is not executable</source> <source>%1 does not exist or is not executable</source>
<translation>qmake不存在或者不可执</translation> <translation>%1</translation>
</message> </message>
<message> <message>
<source>Qt version has no name</source> <source>Qt version has no name</source>
@@ -27976,8 +27976,8 @@ Did you start Qemu?</source>
<translation>Qt没有被正确安装make install</translation> <translation>Qt没有被正确安装make install</translation>
</message> </message>
<message> <message>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source> <source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation>Qt安装的二进制所在的路径qmake的路径设置出现了错?</translation> <translation>Qt安装的二进制所在的路径%1?</translation>
</message> </message>
<message> <message>
<source>The default mkspec symlink is broken.</source> <source>The default mkspec symlink is broken.</source>

View File

@@ -15636,8 +15636,8 @@ Requires &lt;b&gt;Qt 4.7.4&lt;/b&gt; or newer.</source>
<translation> qmake </translation> <translation> qmake </translation>
</message> </message>
<message> <message>
<source>qmake does not exist or is not executable</source> <source>%1 does not exist or is not executable</source>
<translation>qmake </translation> <translation>%1 </translation>
</message> </message>
<message> <message>
<source>Qt version has no name</source> <source>Qt version has no name</source>
@@ -15664,8 +15664,8 @@ Requires &lt;b&gt;Qt 4.7.4&lt;/b&gt; or newer.</source>
<translation>Qt make install</translation> <translation>Qt make install</translation>
</message> </message>
<message> <message>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source> <source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation> Qt qmake </translation> <translation> Qt %1 </translation>
</message> </message>
<message> <message>
<source>The default mkspec symlink is broken.</source> <source>The default mkspec symlink is broken.</source>

View File

@@ -629,7 +629,7 @@ ClassOrNamespace *ClassOrNamespace::parent() const
return _parent; return _parent;
} }
QList<ClassOrNamespace *> ClassOrNamespace::usings() const const QList<ClassOrNamespace *> ClassOrNamespace::usings() const
{ {
const_cast<ClassOrNamespace *>(this)->flush(); const_cast<ClassOrNamespace *>(this)->flush();
return _usings; return _usings;
@@ -641,7 +641,7 @@ QList<Enum *> ClassOrNamespace::unscopedEnums() const
return _enums; return _enums;
} }
QList<Symbol *> ClassOrNamespace::symbols() const const QList<Symbol *> ClassOrNamespace::symbols() const
{ {
const_cast<ClassOrNamespace *>(this)->flush(); const_cast<ClassOrNamespace *>(this)->flush();
return _symbols; return _symbols;

View File

@@ -70,9 +70,9 @@ public:
ClassOrNamespace *instantiationOrigin() const; ClassOrNamespace *instantiationOrigin() const;
ClassOrNamespace *parent() const; ClassOrNamespace *parent() const;
QList<ClassOrNamespace *> usings() const; const QList<ClassOrNamespace *> usings() const;
QList<Enum *> unscopedEnums() const; QList<Enum *> unscopedEnums() const;
QList<Symbol *> symbols() const; const QList<Symbol *> symbols() const;
ClassOrNamespace *globalNamespace() const; ClassOrNamespace *globalNamespace() const;

View File

@@ -41,7 +41,61 @@ bool BuildableHelperLibrary::isQtChooser(const FilePath &filePath)
return filePath.symLinkTarget().endsWith("/qtchooser"); return filePath.symLinkTarget().endsWith("/qtchooser");
} }
FilePath BuildableHelperLibrary::qtChooserToQmakePath(const FilePath &qtChooser) static const QStringList &queryToolNames()
{
static const QStringList names = {"qmake", "qtpaths"};
return names;
}
static bool isQueryTool(FilePath path)
{
if (path.isEmpty())
return false;
if (BuildableHelperLibrary::isQtChooser(path))
path = BuildableHelperLibrary::qtChooserToQueryToolPath(path.symLinkTarget());
if (!path.exists())
return false;
QtcProcess proc;
proc.setCommand({path, {"-query"}});
proc.runBlocking();
if (proc.result() != ProcessResult::FinishedWithSuccess)
return false;
const QString output = proc.stdOut();
// Exemplary output of "[qmake|qtpaths] -query":
// QT_SYSROOT:...
// QT_INSTALL_PREFIX:...
// [...]
// QT_VERSION:6.4.0
return output.contains("QT_VERSION:");
}
static FilePath findQueryToolInDir(const FilePath &dir)
{
if (dir.isEmpty())
return {};
for (const QString &queryTool : queryToolNames()) {
FilePath queryToolPath = dir.pathAppended(queryTool).withExecutableSuffix();
if (queryToolPath.exists()) {
if (isQueryTool(queryToolPath))
return queryToolPath;
}
// Prefer qmake-qt5 to qmake-qt4 by sorting the filenames in reverse order.
const FilePaths candidates = dir.dirEntries(
{BuildableHelperLibrary::possibleQtQueryTools(queryTool), QDir::Files},
QDir::Name | QDir::Reversed);
for (const FilePath &candidate : candidates) {
if (candidate == queryToolPath)
continue;
if (isQueryTool(candidate))
return candidate;
}
}
return {};
}
FilePath BuildableHelperLibrary::qtChooserToQueryToolPath(const FilePath &qtChooser)
{ {
const QString toolDir = QLatin1String("QTTOOLDIR=\""); const QString toolDir = QLatin1String("QTTOOLDIR=\"");
QtcProcess proc; QtcProcess proc;
@@ -51,6 +105,10 @@ FilePath BuildableHelperLibrary::qtChooserToQmakePath(const FilePath &qtChooser)
if (proc.result() != ProcessResult::FinishedWithSuccess) if (proc.result() != ProcessResult::FinishedWithSuccess)
return {}; return {};
const QString output = proc.stdOut(); const QString output = proc.stdOut();
// Exemplary output of "qtchooser -print-env":
// QT_SELECT="default"
// QTTOOLDIR="/usr/lib/qt5/bin"
// QTLIBDIR="/usr/lib/x86_64-linux-gnu"
int pos = output.indexOf(toolDir); int pos = output.indexOf(toolDir);
if (pos == -1) if (pos == -1)
return {}; return {};
@@ -59,44 +117,13 @@ FilePath BuildableHelperLibrary::qtChooserToQmakePath(const FilePath &qtChooser)
if (end == -1) if (end == -1)
return {}; return {};
FilePath qmake = qtChooser; FilePath queryToolPath = qtChooser;
qmake.setPath(output.mid(pos, end - pos) + "/qmake"); for (const QString &queryTool : queryToolNames()) {
return qmake; queryToolPath.setPath(output.mid(pos, end - pos) + "/" + queryTool);
} if (queryToolPath.exists())
return queryToolPath;
static bool isQmake(FilePath path)
{
if (path.isEmpty())
return false;
if (BuildableHelperLibrary::isQtChooser(path))
path = BuildableHelperLibrary::qtChooserToQmakePath(path.symLinkTarget());
if (!path.exists())
return false;
return !BuildableHelperLibrary::qtVersionForQMake(path).isEmpty();
}
static FilePath findQmakeInDir(const FilePath &dir)
{
if (dir.isEmpty())
return {};
FilePath qmakePath = dir.pathAppended("qmake").withExecutableSuffix();
if (qmakePath.exists()) {
if (isQmake(qmakePath))
return qmakePath;
} }
return queryToolPath;
// Prefer qmake-qt5 to qmake-qt4 by sorting the filenames in reverse order.
const FilePaths candidates = dir.dirEntries(
{BuildableHelperLibrary::possibleQMakeCommands(), QDir::Files},
QDir::Name | QDir::Reversed);
for (const FilePath &candidate : candidates) {
if (candidate == qmakePath)
continue;
if (isQmake(candidate))
return candidate;
}
return {};
} }
FilePath BuildableHelperLibrary::findSystemQt(const Environment &env) FilePath BuildableHelperLibrary::findSystemQt(const Environment &env)
@@ -107,86 +134,55 @@ FilePath BuildableHelperLibrary::findSystemQt(const Environment &env)
FilePaths BuildableHelperLibrary::findQtsInEnvironment(const Environment &env, int maxCount) FilePaths BuildableHelperLibrary::findQtsInEnvironment(const Environment &env, int maxCount)
{ {
FilePaths qmakeList; FilePaths queryToolList;
std::set<QString> canonicalEnvPaths; std::set<QString> canonicalEnvPaths;
const FilePaths paths = env.path(); const FilePaths paths = env.path();
for (const FilePath &path : paths) { for (const FilePath &path : paths) {
if (!canonicalEnvPaths.insert(path.toFileInfo().canonicalFilePath()).second) if (!canonicalEnvPaths.insert(path.toFileInfo().canonicalFilePath()).second)
continue; continue;
const FilePath qmake = findQmakeInDir(path); const FilePath queryTool = findQueryToolInDir(path);
if (qmake.isEmpty()) if (queryTool.isEmpty())
continue; continue;
qmakeList << qmake; queryToolList << queryTool;
if (maxCount != -1 && qmakeList.size() == maxCount) if (maxCount != -1 && queryToolList.size() == maxCount)
break; break;
} }
return qmakeList; return queryToolList;
} }
QString BuildableHelperLibrary::qtVersionForQMake(const FilePath &qmakePath) QString BuildableHelperLibrary::filterForQtQueryToolsFileDialog()
{ {
if (qmakePath.isEmpty()) QStringList toolFilters;
return QString(); for (const QString &queryTool : queryToolNames()) {
for (const QString &tool: BuildableHelperLibrary::possibleQtQueryTools(queryTool)) {
QtcProcess qmake; QString toolFilter;
qmake.setTimeoutS(5); if (HostOsInfo::isMacHost())
qmake.setCommand({qmakePath, {"--version"}}); // work around QTBUG-7739 that prohibits filters that don't start with *
qmake.runBlocking(); toolFilter += QLatin1Char('*');
if (qmake.result() != ProcessResult::FinishedWithSuccess) { toolFilter += tool;
qWarning() << qmake.exitMessage(); if (HostOsInfo::isAnyUnixHost() && !HostOsInfo::isMacHost())
return QString(); // kde bug, we need at least one wildcard character
// see QTCREATORBUG-7771
toolFilter += QLatin1Char('*');
toolFilters.append(toolFilter);
}
} }
return queryToolNames().join(", ") + " (" + toolFilters.join(" ") + ")";
const QString output = qmake.allOutput();
static const QRegularExpression regexp("(QMake version:?)[\\s]*([\\d.]*)",
QRegularExpression::CaseInsensitiveOption);
const QRegularExpressionMatch match = regexp.match(output);
const QString qmakeVersion = match.captured(2);
if (qmakeVersion.startsWith(QLatin1String("2."))
|| qmakeVersion.startsWith(QLatin1String("3."))) {
static const QRegularExpression regexp2("Using Qt version[\\s]*([\\d\\.]*)",
QRegularExpression::CaseInsensitiveOption);
const QRegularExpressionMatch match2 = regexp2.match(output);
const QString version = match2.captured(1);
return version;
}
return QString();
} }
QString BuildableHelperLibrary::filterForQmakeFileDialog() QStringList BuildableHelperLibrary::possibleQtQueryTools(const QString &tool)
{ {
QString filter = QLatin1String("qmake ("); // On Windows it is "<queryTool>.exe" or "<queryTool>.bat"
const QStringList commands = possibleQMakeCommands();
for (int i = 0; i < commands.size(); ++i) {
if (i)
filter += QLatin1Char(' ');
if (HostOsInfo::isMacHost())
// work around QTBUG-7739 that prohibits filters that don't start with *
filter += QLatin1Char('*');
filter += commands.at(i);
if (HostOsInfo::isAnyUnixHost() && !HostOsInfo::isMacHost())
// kde bug, we need at least one wildcard character
// see QTCREATORBUG-7771
filter += QLatin1Char('*');
}
filter += QLatin1Char(')');
return filter;
}
QStringList BuildableHelperLibrary::possibleQMakeCommands()
{
// On Windows it is always "qmake.exe"
// On Unix some distributions renamed qmake with a postfix to avoid clashes // On Unix some distributions renamed qmake with a postfix to avoid clashes
// On OS X, Qt 4 binary packages also has renamed qmake. There are also symbolic links that are // On OS X, Qt 4 binary packages also has renamed qmake. There are also symbolic links that are
// named "qmake", but the file dialog always checks against resolved links (native Cocoa issue) // named <tool>, but the file dialog always checks against resolved links (native Cocoa issue)
QStringList commands(HostOsInfo::withExecutableSuffix("qmake*")); QStringList tools(HostOsInfo::withExecutableSuffix(tool + "*"));
// Qt 6 CMake built targets, such as Android, are dependent on the host installation // Qt 6 CMake built targets, such as Android, are dependent on the host installation
// and use a script wrapper around the host qmake executable // and use a script wrapper around the host queryTool executable
if (HostOsInfo::isWindowsHost()) if (HostOsInfo::isWindowsHost())
commands.append("qmake*.bat"); tools.append(tool + "*.bat");
return commands; return tools;
} }
} // namespace Utils } // namespace Utils

View File

@@ -45,12 +45,11 @@ public:
static FilePath findSystemQt(const Environment &env); static FilePath findSystemQt(const Environment &env);
static FilePaths findQtsInEnvironment(const Environment &env, int maxCount = -1); static FilePaths findQtsInEnvironment(const Environment &env, int maxCount = -1);
static bool isQtChooser(const FilePath &filePath); static bool isQtChooser(const FilePath &filePath);
static FilePath qtChooserToQmakePath(const FilePath &path); static FilePath qtChooserToQueryToolPath(const FilePath &path);
// return true if the qmake at qmakePath is a Qt (used by QtVersion) // returns something like qmake4, qmake, qmake-qt4, qtpaths
static QString qtVersionForQMake(const FilePath &qmakePath); // or whatever distributions have chosen (used by QtVersion)
// returns something like qmake4, qmake, qmake-qt4 or whatever distributions have chosen (used by QtVersion) static QStringList possibleQtQueryTools(const QString &tool);
static QStringList possibleQMakeCommands(); static QString filterForQtQueryToolsFileDialog();
static QString filterForQmakeFileDialog();
}; };
} // Utils } // Utils

View File

@@ -65,6 +65,7 @@ HostOsInfo::HostArchitecture HostOsInfo::hostArchitecture()
case PROCESSOR_ARCHITECTURE_IA64: case PROCESSOR_ARCHITECTURE_IA64:
return HostOsInfo::HostArchitectureItanium; return HostOsInfo::HostArchitectureItanium;
case PROCESSOR_ARCHITECTURE_ARM: case PROCESSOR_ARCHITECTURE_ARM:
case PROCESSOR_ARCHITECTURE_ARM64:
return HostOsInfo::HostArchitectureArm; return HostOsInfo::HostArchitectureArm;
default: default:
return HostOsInfo::HostArchitectureUnknown; return HostOsInfo::HostArchitectureUnknown;

View File

@@ -192,7 +192,7 @@ public:
EnvironmentChange m_environmentChange; EnvironmentChange m_environmentChange;
BinaryVersionToolTipEventFilter *m_binaryVersionToolTipEventFilter = nullptr; BinaryVersionToolTipEventFilter *m_binaryVersionToolTipEventFilter = nullptr;
QList<QAbstractButton *> m_buttons; QList<QAbstractButton *> m_buttons;
MacroExpander *m_macroExpander = globalMacroExpander(); const MacroExpander *m_macroExpander = globalMacroExpander();
std::function<void()> m_openTerminal; std::function<void()> m_openTerminal;
}; };
@@ -732,7 +732,7 @@ void PathChooser::setHistoryCompleter(const QString &historyKey, bool restoreLas
d->m_lineEdit->setHistoryCompleter(historyKey, restoreLastItemFromHistory); d->m_lineEdit->setHistoryCompleter(historyKey, restoreLastItemFromHistory);
} }
void PathChooser::setMacroExpander(MacroExpander *macroExpander) void PathChooser::setMacroExpander(const MacroExpander *macroExpander)
{ {
d->m_macroExpander = macroExpander; d->m_macroExpander = macroExpander;
} }

View File

@@ -136,7 +136,7 @@ public:
// Sets a macro expander that is used when producing path and fileName. // Sets a macro expander that is used when producing path and fileName.
// By default, the global expander is used. // By default, the global expander is used.
// nullptr can be passed to disable macro expansion. // nullptr can be passed to disable macro expansion.
void setMacroExpander(MacroExpander *macroExpander); void setMacroExpander(const MacroExpander *macroExpander);
bool isReadOnly() const; bool isReadOnly() const;
void setReadOnly(bool b); void setReadOnly(bool b);

View File

@@ -145,7 +145,8 @@ QTCREATOR_UTILS_EXPORT bool is64BitWindowsSystem()
SYSTEM_INFO systemInfo; SYSTEM_INFO systemInfo;
GetNativeSystemInfo(&systemInfo); GetNativeSystemInfo(&systemInfo);
return systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 return systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64
|| systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64; || systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64
|| systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM64;
#else #else
return false; return false;
#endif #endif

View File

@@ -28,11 +28,11 @@
#include "androidconfigurations.h" #include "androidconfigurations.h"
#include "androidconstants.h" #include "androidconstants.h"
#include "androidmanager.h" #include "androidmanager.h"
#include "androidrunconfiguration.h"
#include <debugger/debuggerkitinformation.h> #include <debugger/debuggerkitinformation.h>
#include <debugger/debuggerrunconfigurationaspect.h> #include <debugger/debuggerrunconfigurationaspect.h>
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/projectexplorerconstants.h> #include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/environmentaspect.h> #include <projectexplorer/environmentaspect.h>
#include <projectexplorer/runconfigurationaspects.h> #include <projectexplorer/runconfigurationaspects.h>
@@ -274,9 +274,8 @@ AndroidRunnerWorker::AndroidRunnerWorker(RunWorker *runner, const QString &packa
qCDebug(androidRunWorkerLog) << "Environment variables for the app" qCDebug(androidRunWorkerLog) << "Environment variables for the app"
<< m_extraEnvVars.toStringList(); << m_extraEnvVars.toStringList();
if (target->buildConfigurations().first()->buildType() != BuildConfiguration::BuildType::Release) { if (target->buildConfigurations().first()->buildType() != BuildConfiguration::BuildType::Release)
m_extraAppParams = runControl->runnable().command.arguments(); m_extraAppParams = runControl->commandLine().arguments();
}
if (auto aspect = runControl->aspect(Constants::ANDROID_AM_START_ARGS)) { if (auto aspect = runControl->aspect(Constants::ANDROID_AM_START_ARGS)) {
QTC_CHECK(aspect->value.type() == QVariant::String); QTC_CHECK(aspect->value.type() == QVariant::String);

View File

@@ -32,6 +32,7 @@
#include "../itestframework.h" #include "../itestframework.h"
#include "../testsettings.h" #include "../testsettings.h"
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/buildsystem.h> #include <projectexplorer/buildsystem.h>
#include <projectexplorer/environmentaspect.h> #include <projectexplorer/environmentaspect.h>
#include <projectexplorer/project.h> #include <projectexplorer/project.h>

View File

@@ -53,7 +53,7 @@ public:
exeAspect->setPlaceHolderText(tr("Unknown")); exeAspect->setPlaceHolderText(tr("Unknown"));
addAspect<ArgumentsAspect>(macroExpander()); addAspect<ArgumentsAspect>(macroExpander());
addAspect<WorkingDirectoryAspect>(nullptr); addAspect<WorkingDirectoryAspect>(macroExpander(), nullptr);
setUpdater([this, exeAspect] { setUpdater([this, exeAspect] {
const BuildTargetInfo bti = buildTargetInfo(); const BuildTargetInfo bti = buildTargetInfo();
@@ -80,7 +80,7 @@ public:
exeAspect->setExpectedKind(PathChooser::Any); exeAspect->setExpectedKind(PathChooser::Any);
addAspect<ArgumentsAspect>(macroExpander()); addAspect<ArgumentsAspect>(macroExpander());
addAspect<WorkingDirectoryAspect>(nullptr); addAspect<WorkingDirectoryAspect>(macroExpander(), nullptr);
setDefaultDisplayName(RunConfigurationFactory::decoratedTargetName(tr("Custom Executable"), target)); setDefaultDisplayName(RunConfigurationFactory::decoratedTargetName(tr("Custom Executable"), target));
} }

View File

@@ -179,8 +179,6 @@ bool GdbServerProvider::aboutToRun(DebuggerRunTool *runTool,
Runnable inferior; Runnable inferior;
inferior.command.setExecutable(bin); inferior.command.setExecutable(bin);
inferior.extraData.insert(Debugger::Constants::kPeripheralDescriptionFile,
m_peripheralDescriptionFile.toVariant());
if (const auto argAspect = runControl->aspect<ArgumentsAspect>()) if (const auto argAspect = runControl->aspect<ArgumentsAspect>())
inferior.command.setArguments(argAspect->arguments); inferior.command.setArguments(argAspect->arguments);
runTool->setInferior(inferior); runTool->setInferior(inferior);
@@ -191,6 +189,7 @@ bool GdbServerProvider::aboutToRun(DebuggerRunTool *runTool,
runTool->setRemoteChannel(channelString()); runTool->setRemoteChannel(channelString());
runTool->setUseContinueInsteadOfRun(true); runTool->setUseContinueInsteadOfRun(true);
runTool->setUseExtendedRemote(useExtendedRemote()); runTool->setUseExtendedRemote(useExtendedRemote());
runTool->runParameters().peripheralDescriptionFile = m_peripheralDescriptionFile;
return true; return true;
} }

View File

@@ -216,11 +216,10 @@ bool UvscServerProvider::aboutToRun(DebuggerRunTool *runTool, QString &errorMess
Runnable inferior; Runnable inferior;
inferior.command.setExecutable(bin); inferior.command.setExecutable(bin);
inferior.extraData.insert(Debugger::Constants::kPeripheralDescriptionFile, runTool->runParameters().peripheralDescriptionFile = peripheralDescriptionFile;
peripheralDescriptionFile.toVariant()); runTool->runParameters().uVisionProjectFilePath = projFilePath;
inferior.extraData.insert(Debugger::Constants::kUVisionProjectFilePath, projFilePath.toString()); runTool->runParameters().uVisionOptionsFilePath = optFilePath;
inferior.extraData.insert(Debugger::Constants::kUVisionOptionsFilePath, optFilePath.toString()); runTool->runParameters().uVisionSimulator = isSimulator();
inferior.extraData.insert(Debugger::Constants::kUVisionSimulator, isSimulator());
runTool->setInferior(inferior); runTool->setInferior(inferior);
runTool->setSymbolFile(bin); runTool->setSymbolFile(bin);
runTool->setStartMode(AttachToRemoteServer); runTool->setStartMode(AttachToRemoteServer);

View File

@@ -83,8 +83,6 @@ public:
int lowerPort = 0; int lowerPort = 0;
int upperPort = 0; int upperPort = 0;
Runnable r = runnable();
CommandLine cmd; CommandLine cmd;
cmd.setExecutable(device()->filePath(Constants::AppcontrollerFilepath)); cmd.setExecutable(device()->filePath(Constants::AppcontrollerFilepath));
@@ -118,11 +116,11 @@ public:
} }
cmd.addArg("--port-range"); cmd.addArg("--port-range");
cmd.addArg(QString("%1-%2").arg(lowerPort).arg(upperPort)); cmd.addArg(QString("%1-%2").arg(lowerPort).arg(upperPort));
cmd.addCommandLineAsArgs(r.command); cmd.addCommandLineAsArgs(runControl()->commandLine());
m_launcher.setCommand(cmd); m_launcher.setCommand(cmd);
m_launcher.setWorkingDirectory(r.workingDirectory); m_launcher.setWorkingDirectory(runControl()->workingDirectory());
m_launcher.setEnvironment(r.environment); m_launcher.setEnvironment(runControl()->environment());
m_launcher.start(); m_launcher.start();
} }

View File

@@ -102,7 +102,7 @@ QdbRunConfiguration::QdbRunConfiguration(Target *target, Id id)
auto envAspect = addAspect<RemoteLinux::RemoteLinuxEnvironmentAspect>(target); auto envAspect = addAspect<RemoteLinux::RemoteLinuxEnvironmentAspect>(target);
addAspect<ArgumentsAspect>(macroExpander()); addAspect<ArgumentsAspect>(macroExpander());
addAspect<WorkingDirectoryAspect>(envAspect); addAspect<WorkingDirectoryAspect>(macroExpander(), envAspect);
addAspect<FullCommandLineAspect>(this); addAspect<FullCommandLineAspect>(this);
setUpdater([this, target, exeAspect, symbolsAspect] { setUpdater([this, target, exeAspect, symbolsAspect] {

View File

@@ -15,11 +15,12 @@ add_qtc_plugin(ClangCodeModel
clangcodemodelplugin.cpp clangcodemodelplugin.h clangcodemodelplugin.cpp clangcodemodelplugin.h
clangcompletioncontextanalyzer.cpp clangcompletioncontextanalyzer.h clangcompletioncontextanalyzer.cpp clangcompletioncontextanalyzer.h
clangconstants.h clangconstants.h
clangdclient.cpp clangdclient.h
clangdast.cpp clangdast.h clangdast.cpp clangdast.h
clangdclient.cpp clangdclient.h
clangdiagnostictooltipwidget.cpp clangdiagnostictooltipwidget.h clangdiagnostictooltipwidget.cpp clangdiagnostictooltipwidget.h
clangdquickfixfactory.cpp clangdquickfixfactory.h clangdquickfixfactory.cpp clangdquickfixfactory.h
clangdqpropertyhighlighter.cpp clangdqpropertyhighlighter.h clangdqpropertyhighlighter.cpp clangdqpropertyhighlighter.h
clangdsemantichighlighting.cpp clangdsemantichighlighting.h
clangeditordocumentprocessor.cpp clangeditordocumentprocessor.h clangeditordocumentprocessor.cpp clangeditordocumentprocessor.h
clangfixitoperation.cpp clangfixitoperation.h clangfixitoperation.cpp clangfixitoperation.h
clangdlocatorfilters.cpp clangdlocatorfilters.h clangdlocatorfilters.cpp clangdlocatorfilters.h
@@ -28,6 +29,7 @@ add_qtc_plugin(ClangCodeModel
clangtextmark.cpp clangtextmark.h clangtextmark.cpp clangtextmark.h
clanguiheaderondiskmanager.cpp clanguiheaderondiskmanager.h clanguiheaderondiskmanager.cpp clanguiheaderondiskmanager.h
clangutils.cpp clangutils.h clangutils.cpp clangutils.h
tasktimers.cpp tasktimers.h
EXPLICIT_MOC clangcodemodelplugin.h EXPLICIT_MOC clangcodemodelplugin.h
) )

View File

@@ -42,6 +42,8 @@ QtcPlugin {
"clangdqpropertyhighlighter.h", "clangdqpropertyhighlighter.h",
"clangdquickfixfactory.cpp", "clangdquickfixfactory.cpp",
"clangdquickfixfactory.h", "clangdquickfixfactory.h",
"clangdsemantichighlighting.cpp",
"clangdsemantichighlighting.h",
"clangeditordocumentprocessor.cpp", "clangeditordocumentprocessor.cpp",
"clangeditordocumentprocessor.h", "clangeditordocumentprocessor.h",
"clangfixitoperation.cpp", "clangfixitoperation.cpp",
@@ -56,6 +58,8 @@ QtcPlugin {
"clanguiheaderondiskmanager.h", "clanguiheaderondiskmanager.h",
"clangutils.cpp", "clangutils.cpp",
"clangutils.h", "clangutils.h",
"tasktimers.cpp",
"tasktimers.h",
] ]
Group { Group {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,925 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "clangdsemantichighlighting.h"
#include "clangdast.h"
#include "clangdclient.h"
#include "clangdqpropertyhighlighter.h"
#include "clangmodelmanagersupport.h"
#include "tasktimers.h"
#include <cppeditor/semantichighlighter.h>
#include <languageclient/semantichighlightsupport.h>
#include <languageserverprotocol/lsptypes.h>
#include <texteditor/blockrange.h>
#include <texteditor/textstyles.h>
#include <QtConcurrent>
#include <QTextDocument>
using namespace LanguageClient;
using namespace LanguageServerProtocol;
using namespace TextEditor;
namespace ClangCodeModel::Internal {
Q_LOGGING_CATEGORY(clangdLogHighlight, "qtc.clangcodemodel.clangd.highlight", QtWarningMsg);
// clangd reports also the #ifs, #elses and #endifs around the disabled code as disabled,
// and not even in a consistent manner. We don't want this, so we have to clean up here.
// But note that we require this behavior, as otherwise we would not be able to grey out
// e.g. empty lines after an #ifdef, due to the lack of symbols.
static QList<BlockRange> cleanupDisabledCode(HighlightingResults &results, const QTextDocument *doc,
const QString &docContent)
{
QList<BlockRange> ifdefedOutRanges;
int rangeStartPos = -1;
for (auto it = results.begin(); it != results.end();) {
const bool wasIfdefedOut = rangeStartPos != -1;
const bool isIfDefedOut = it->textStyles.mainStyle == C_DISABLED_CODE;
if (!isIfDefedOut) {
if (wasIfdefedOut) {
const QTextBlock block = doc->findBlockByNumber(it->line - 1);
ifdefedOutRanges << BlockRange(rangeStartPos, block.position());
rangeStartPos = -1;
}
++it;
continue;
}
if (!wasIfdefedOut)
rangeStartPos = doc->findBlockByNumber(it->line - 1).position();
// Does the current line contain a potential "ifdefed-out switcher"?
// If not, no state change is possible and we continue with the next line.
const auto isPreprocessorControlStatement = [&] {
const int pos = Utils::Text::positionInText(doc, it->line, it->column);
const QStringView content = subViewLen(docContent, pos, it->length).trimmed();
if (content.isEmpty() || content.first() != '#')
return false;
int offset = 1;
while (offset < content.size() && content.at(offset).isSpace())
++offset;
if (offset == content.size())
return false;
const QStringView ppDirective = content.mid(offset);
return ppDirective.startsWith(QLatin1String("if"))
|| ppDirective.startsWith(QLatin1String("elif"))
|| ppDirective.startsWith(QLatin1String("else"))
|| ppDirective.startsWith(QLatin1String("endif"));
};
if (!isPreprocessorControlStatement()) {
++it;
continue;
}
if (!wasIfdefedOut) {
// The #if or #else that starts disabled code should not be disabled.
const QTextBlock nextBlock = doc->findBlockByNumber(it->line);
rangeStartPos = nextBlock.isValid() ? nextBlock.position() : -1;
it = results.erase(it);
continue;
}
if (wasIfdefedOut && (it + 1 == results.end()
|| (it + 1)->textStyles.mainStyle != C_DISABLED_CODE
|| (it + 1)->line != it->line + 1)) {
// The #else or #endif that ends disabled code should not be disabled.
const QTextBlock block = doc->findBlockByNumber(it->line - 1);
ifdefedOutRanges << BlockRange(rangeStartPos, block.position());
rangeStartPos = -1;
it = results.erase(it);
continue;
}
++it;
}
if (rangeStartPos != -1)
ifdefedOutRanges << BlockRange(rangeStartPos, doc->characterCount());
qCDebug(clangdLogHighlight) << "found" << ifdefedOutRanges.size() << "ifdefed-out ranges";
if (clangdLogHighlight().isDebugEnabled()) {
for (const BlockRange &r : qAsConst(ifdefedOutRanges))
qCDebug(clangdLogHighlight) << r.first() << r.last();
}
return ifdefedOutRanges;
}
class ExtraHighlightingResultsCollector
{
public:
ExtraHighlightingResultsCollector(QFutureInterface<HighlightingResult> &future,
HighlightingResults &results,
const Utils::FilePath &filePath, const ClangdAstNode &ast,
const QTextDocument *doc, const QString &docContent);
void collect();
private:
static bool lessThan(const HighlightingResult &r1, const HighlightingResult &r2);
static int onlyIndexOf(const QStringView &text, const QStringView &subString, int from = 0);
int posForNodeStart(const ClangdAstNode &node) const;
int posForNodeEnd(const ClangdAstNode &node) const;
void insertResult(const HighlightingResult &result);
void insertResult(const ClangdAstNode &node, TextStyle style);
void insertAngleBracketInfo(int searchStart1, int searchEnd1, int searchStart2, int searchEnd2);
void setResultPosFromRange(HighlightingResult &result, const Range &range);
void collectFromNode(const ClangdAstNode &node);
void visitNode(const ClangdAstNode&node);
QFutureInterface<HighlightingResult> &m_future;
HighlightingResults &m_results;
const Utils::FilePath m_filePath;
const ClangdAstNode &m_ast;
const QTextDocument * const m_doc;
const QString &m_docContent;
ClangdAstNode::FileStatus m_currentFileStatus = ClangdAstNode::FileStatus::Unknown;
};
void doSemanticHighlighting(
QFutureInterface<HighlightingResult> &future,
const Utils::FilePath &filePath,
const QList<ExpandedSemanticToken> &tokens,
const QString &docContents,
const ClangdAstNode &ast,
const QPointer<TextDocument> &textDocument,
int docRevision,
const QVersionNumber &clangdVersion,
const TaskTimer &taskTimer)
{
ThreadedSubtaskTimer t("highlighting", taskTimer);
if (future.isCanceled()) {
future.reportFinished();
return;
}
const QTextDocument doc(docContents);
const auto tokenRange = [&doc](const ExpandedSemanticToken &token) {
const Position startPos(token.line - 1, token.column - 1);
const Position endPos = startPos.withOffset(token.length, &doc);
return Range(startPos, endPos);
};
const auto isOutputParameter = [&ast, &tokenRange](const ExpandedSemanticToken &token) {
if (token.modifiers.contains(QLatin1String("usedAsMutableReference")))
return true;
if (token.type != "variable" && token.type != "property" && token.type != "parameter")
return false;
const Range range = tokenRange(token);
const ClangdAstPath path = getAstPath(ast, range);
if (path.size() < 2)
return false;
if (token.type == "property"
&& (path.rbegin()->kind() == "MemberInitializer"
|| path.rbegin()->kind() == "CXXConstruct")) {
return false;
}
if (path.rbegin()->hasConstType())
return false;
for (auto it = path.rbegin() + 1; it != path.rend(); ++it) {
if (it->kind() == "CXXConstruct" || it->kind() == "MemberInitializer")
return true;
if (it->kind() == "Call") {
// The first child is e.g. a called lambda or an object on which
// the call happens, and should not be highlighted as an output argument.
// If the call is not fully resolved (as in templates), we don't
// know whether the argument is passed as const or not.
if (it->arcanaContains("dependent type"))
return false;
const QList<ClangdAstNode> children = it->children().value_or(QList<ClangdAstNode>());
return children.isEmpty()
|| (children.first().range() != (it - 1)->range()
&& children.first().kind() != "UnresolvedLookup");
}
// The token should get marked for e.g. lambdas, but not for assignment operators,
// where the user sees that it's being written.
if (it->kind() == "CXXOperatorCall") {
const QList<ClangdAstNode> children = it->children().value_or(QList<ClangdAstNode>());
// Child 1 is the call itself, Child 2 is the named entity on which the call happens
// (a lambda or a class instance), after that follow the actual call arguments.
if (children.size() < 2)
return false;
// The call itself is never modifiable.
if (children.first().range() == range)
return false;
// The callable is never displayed as an output parameter.
// TODO: A good argument can be made to display objects on which a non-const
// operator or function is called as output parameters.
if (children.at(1).range().contains(range))
return false;
QList<ClangdAstNode> firstChildTree{children.first()};
while (!firstChildTree.isEmpty()) {
const ClangdAstNode n = firstChildTree.takeFirst();
const QString detail = n.detail().value_or(QString());
if (detail.startsWith("operator")) {
return !detail.contains('=')
&& !detail.contains("++") && !detail.contains("--")
&& !detail.contains("<<") && !detail.contains(">>")
&& !detail.contains("*");
}
firstChildTree << n.children().value_or(QList<ClangdAstNode>());
}
return true;
}
if (it->kind() == "Lambda")
return false;
if (it->kind() == "BinaryOperator")
return false;
if (it->hasConstType())
return false;
if (it->kind() == "CXXMemberCall") {
if (it == path.rbegin())
return false;
const QList<ClangdAstNode> children = it->children().value_or(QList<ClangdAstNode>());
QTC_ASSERT(!children.isEmpty(), return false);
// The called object is never displayed as an output parameter.
// TODO: A good argument can be made to display objects on which a non-const
// operator or function is called as output parameters.
return (it - 1)->range() != children.first().range();
}
if (it->kind() == "Member" && it->arcanaContains("(")
&& !it->arcanaContains("bound member function type")) {
return false;
}
}
return false;
};
const std::function<HighlightingResult(const ExpandedSemanticToken &)> toResult
= [&ast, &isOutputParameter, &clangdVersion, &tokenRange]
(const ExpandedSemanticToken &token) {
TextStyles styles;
if (token.type == "variable") {
if (token.modifiers.contains(QLatin1String("functionScope"))) {
styles.mainStyle = C_LOCAL;
} else if (token.modifiers.contains(QLatin1String("classScope"))) {
styles.mainStyle = C_FIELD;
} else if (token.modifiers.contains(QLatin1String("fileScope"))
|| token.modifiers.contains(QLatin1String("globalScope"))) {
styles.mainStyle = C_GLOBAL;
}
} else if (token.type == "function" || token.type == "method") {
styles.mainStyle = token.modifiers.contains(QLatin1String("virtual"))
? C_VIRTUAL_METHOD : C_FUNCTION;
if (ast.isValid()) {
const ClangdAstPath path = getAstPath(ast, tokenRange(token));
if (path.length() > 1) {
const ClangdAstNode declNode = path.at(path.length() - 2);
if (declNode.kind() == "Function" || declNode.kind() == "CXXMethod") {
if (clangdVersion < QVersionNumber(14)
&& declNode.arcanaContains("' virtual")) {
styles.mainStyle = C_VIRTUAL_METHOD;
}
if (declNode.hasChildWithRole("statement"))
styles.mixinStyles.push_back(C_FUNCTION_DEFINITION);
}
}
}
} else if (token.type == "class") {
styles.mainStyle = C_TYPE;
// clang hardly ever differentiates between constructors and the associated class,
// whereas we highlight constructors as functions.
if (ast.isValid()) {
const ClangdAstPath path = getAstPath(ast, tokenRange(token));
if (!path.isEmpty()) {
if (path.last().kind() == "CXXConstructor") {
if (!path.last().arcanaContains("implicit"))
styles.mainStyle = C_FUNCTION;
} else if (path.last().kind() == "Record" && path.length() > 1) {
const ClangdAstNode node = path.at(path.length() - 2);
if (node.kind() == "CXXDestructor" && !node.arcanaContains("implicit")) {
styles.mainStyle = C_FUNCTION;
// https://github.com/clangd/clangd/issues/872
if (node.role() == "declaration")
styles.mixinStyles.push_back(C_DECLARATION);
}
}
}
}
} else if (token.type == "comment") { // "comment" means code disabled via the preprocessor
styles.mainStyle = C_DISABLED_CODE;
} else if (token.type == "namespace") {
styles.mainStyle = C_NAMESPACE;
} else if (token.type == "property") {
styles.mainStyle = C_FIELD;
} else if (token.type == "enum") {
styles.mainStyle = C_TYPE;
} else if (token.type == "enumMember") {
styles.mainStyle = C_ENUMERATION;
} else if (token.type == "parameter") {
styles.mainStyle = C_PARAMETER;
} else if (token.type == "macro") {
styles.mainStyle = C_PREPROCESSOR;
} else if (token.type == "type") {
styles.mainStyle = C_TYPE;
} else if (token.type == "typeParameter") {
// clangd reports both type and non-type template parameters as type parameters,
// but the latter can be distinguished by the readonly modifier.
styles.mainStyle = token.modifiers.contains(QLatin1String("readonly"))
? C_PARAMETER : C_TYPE;
}
if (token.modifiers.contains(QLatin1String("declaration")))
styles.mixinStyles.push_back(C_DECLARATION);
if (token.modifiers.contains(QLatin1String("static"))) {
if (styles.mainStyle != C_FIELD && styles.mainStyle != C_TEXT)
styles.mixinStyles.push_back(styles.mainStyle);
styles.mainStyle = C_STATIC_MEMBER;
}
if (isOutputParameter(token))
styles.mixinStyles.push_back(C_OUTPUT_ARGUMENT);
qCDebug(clangdLogHighlight) << "adding highlighting result"
<< token.line << token.column << token.length << int(styles.mainStyle);
return HighlightingResult(token.line, token.column, token.length, styles);
};
auto results = QtConcurrent::blockingMapped<HighlightingResults>(tokens, toResult);
const QList<BlockRange> ifdefedOutBlocks = cleanupDisabledCode(results, &doc, docContents);
ExtraHighlightingResultsCollector(future, results, filePath, ast, &doc, docContents).collect();
if (!future.isCanceled()) {
qCInfo(clangdLogHighlight) << "reporting" << results.size() << "highlighting results";
QMetaObject::invokeMethod(textDocument, [textDocument, ifdefedOutBlocks, docRevision] {
if (textDocument && textDocument->document()->revision() == docRevision)
textDocument->setIfdefedOutBlocks(ifdefedOutBlocks);
}, Qt::QueuedConnection);
QList<Range> virtualRanges;
for (const HighlightingResult &r : results) {
if (r.textStyles.mainStyle != C_VIRTUAL_METHOD)
continue;
const Position startPos(r.line - 1, r.column - 1);
virtualRanges << Range(startPos, startPos.withOffset(r.length, &doc));
}
QMetaObject::invokeMethod(ClangModelManagerSupport::instance(),
[filePath, virtualRanges, docRevision] {
if (ClangdClient * const client
= ClangModelManagerSupport::instance()->clientForFile(filePath)) {
client->setVirtualRanges(filePath, virtualRanges, docRevision);
}
}, Qt::QueuedConnection);
future.reportResults(QVector<HighlightingResult>(results.cbegin(), results.cend()));
}
future.reportFinished();
}
ExtraHighlightingResultsCollector::ExtraHighlightingResultsCollector(
QFutureInterface<HighlightingResult> &future, HighlightingResults &results,
const Utils::FilePath &filePath, const ClangdAstNode &ast, const QTextDocument *doc,
const QString &docContent)
: m_future(future), m_results(results), m_filePath(filePath), m_ast(ast), m_doc(doc),
m_docContent(docContent)
{
}
void ExtraHighlightingResultsCollector::collect()
{
for (int i = 0; i < m_results.length(); ++i) {
const HighlightingResult res = m_results.at(i);
if (res.textStyles.mainStyle != C_PREPROCESSOR || res.length != 10)
continue;
const int pos = Utils::Text::positionInText(m_doc, res.line, res.column);
if (subViewLen(m_docContent, pos, 10) != QLatin1String("Q_PROPERTY"))
continue;
int endPos;
if (i < m_results.length() - 1) {
const HighlightingResult nextRes = m_results.at(i + 1);
endPos = Utils::Text::positionInText(m_doc, nextRes.line, nextRes.column);
} else {
endPos = m_docContent.length();
}
const QString qPropertyString = m_docContent.mid(pos, endPos - pos);
QPropertyHighlighter propHighlighter(m_doc, qPropertyString, pos);
for (const HighlightingResult &newRes : propHighlighter.highlight())
m_results.insert(++i, newRes);
}
if (!m_ast.isValid())
return;
visitNode(m_ast);
}
bool ExtraHighlightingResultsCollector::lessThan(const HighlightingResult &r1,
const HighlightingResult &r2)
{
return r1.line < r2.line || (r1.line == r2.line && r1.column < r2.column)
|| (r1.line == r2.line && r1.column == r2.column && r1.length < r2.length);
}
int ExtraHighlightingResultsCollector::onlyIndexOf(const QStringView &text,
const QStringView &subString, int from)
{
const int firstIndex = text.indexOf(subString, from);
if (firstIndex == -1)
return -1;
const int nextIndex = text.indexOf(subString, firstIndex + 1);
// The second condion deals with the off-by-one error in TemplateSpecialization nodes;
// see collectFromNode().
return nextIndex == -1 || nextIndex == firstIndex + 1 ? firstIndex : -1;
}
// Unfortunately, the exact position of a specific token is usually not
// recorded in the AST, so if we need that, we have to search for it textually.
// In corner cases, this might get sabotaged by e.g. comments, in which case we give up.
int ExtraHighlightingResultsCollector::posForNodeStart(const ClangdAstNode &node) const
{
return Utils::Text::positionInText(m_doc, node.range().start().line() + 1,
node.range().start().character() + 1);
}
int ExtraHighlightingResultsCollector::posForNodeEnd(const ClangdAstNode &node) const
{
return Utils::Text::positionInText(m_doc, node.range().end().line() + 1,
node.range().end().character() + 1);
}
void ExtraHighlightingResultsCollector::insertResult(const HighlightingResult &result)
{
if (!result.isValid()) // Some nodes don't have a range.
return;
const auto it = std::lower_bound(m_results.begin(), m_results.end(), result, lessThan);
if (it == m_results.end() || *it != result) {
// Prevent inserting expansions for function-like macros. For instance:
// #define TEST() "blubb"
// const char *s = TEST();
// The macro name is always shorter than the expansion and starts at the same
// location, so it should occur right before the insertion position.
if (it > m_results.begin() && (it - 1)->line == result.line
&& (it - 1)->column == result.column
&& (it - 1)->textStyles.mainStyle == C_PREPROCESSOR) {
return;
}
// Bogus ranges; e.g. QTCREATORBUG-27601
if (it != m_results.end()) {
const int nextStartPos = Utils::Text::positionInText(m_doc, it->line, it->column);
const int resultEndPos = Utils::Text::positionInText(m_doc, result.line, result.column)
+ result.length;
if (resultEndPos > nextStartPos)
return;
}
qCDebug(clangdLogHighlight) << "adding additional highlighting result"
<< result.line << result.column << result.length;
m_results.insert(it, result);
return;
}
// This is for conversion operators, whose type part is only reported as a type by clangd.
if ((it->textStyles.mainStyle == C_TYPE
|| it->textStyles.mainStyle == C_PRIMITIVE_TYPE)
&& !result.textStyles.mixinStyles.empty()
&& result.textStyles.mixinStyles.at(0) == C_OPERATOR) {
it->textStyles.mixinStyles = result.textStyles.mixinStyles;
}
}
void ExtraHighlightingResultsCollector::insertResult(const ClangdAstNode &node, TextStyle style)
{
HighlightingResult result;
result.useTextSyles = true;
result.textStyles.mainStyle = style;
setResultPosFromRange(result, node.range());
insertResult(result);
return;
}
// For matching the "<" and ">" brackets of template declarations, specializations
// and instantiations.
void ExtraHighlightingResultsCollector::insertAngleBracketInfo(int searchStart1, int searchEnd1,
int searchStart2, int searchEnd2)
{
const int openingAngleBracketPos = onlyIndexOf(
subViewEnd(m_docContent, searchStart1, searchEnd1),
QStringView(QStringLiteral("<")));
if (openingAngleBracketPos == -1)
return;
const int absOpeningAngleBracketPos = searchStart1 + openingAngleBracketPos;
if (absOpeningAngleBracketPos > searchStart2)
searchStart2 = absOpeningAngleBracketPos + 1;
if (searchStart2 >= searchEnd2)
return;
const int closingAngleBracketPos = onlyIndexOf(
subViewEnd(m_docContent, searchStart2, searchEnd2),
QStringView(QStringLiteral(">")));
if (closingAngleBracketPos == -1)
return;
const int absClosingAngleBracketPos = searchStart2 + closingAngleBracketPos;
if (absOpeningAngleBracketPos > absClosingAngleBracketPos)
return;
HighlightingResult result;
result.useTextSyles = true;
result.textStyles.mainStyle = C_PUNCTUATION;
Utils::Text::convertPosition(m_doc, absOpeningAngleBracketPos, &result.line, &result.column);
result.length = 1;
result.kind = CppEditor::SemanticHighlighter::AngleBracketOpen;
insertResult(result);
Utils::Text::convertPosition(m_doc, absClosingAngleBracketPos, &result.line, &result.column);
result.kind = CppEditor::SemanticHighlighter::AngleBracketClose;
insertResult(result);
}
void ExtraHighlightingResultsCollector::setResultPosFromRange(HighlightingResult &result,
const Range &range)
{
if (!range.isValid())
return;
const Position startPos = range.start();
const Position endPos = range.end();
result.line = startPos.line() + 1;
result.column = startPos.character() + 1;
result.length = endPos.toPositionInDocument(m_doc) - startPos.toPositionInDocument(m_doc);
}
void ExtraHighlightingResultsCollector::collectFromNode(const ClangdAstNode &node)
{
if (node.kind() == "UserDefinedLiteral")
return;
if (node.kind().endsWith("Literal")) {
const bool isKeyword = node.kind() == "CXXBoolLiteral"
|| node.kind() == "CXXNullPtrLiteral";
const bool isStringLike = !isKeyword && (node.kind().startsWith("String")
|| node.kind().startsWith("Character"));
const TextStyle style = isKeyword ? C_KEYWORD : isStringLike ? C_STRING : C_NUMBER;
insertResult(node, style);
return;
}
if (node.role() == "type" && node.kind() == "Builtin") {
insertResult(node, C_PRIMITIVE_TYPE);
return;
}
if (node.role() == "attribute" && (node.kind() == "Override" || node.kind() == "Final")) {
insertResult(node, C_KEYWORD);
return;
}
const bool isExpression = node.role() == "expression";
if (isExpression && node.kind() == "Predefined") {
insertResult(node, C_PREPROCESSOR);
return;
}
const bool isDeclaration = node.role() == "declaration";
const int nodeStartPos = posForNodeStart(node);
const int nodeEndPos = posForNodeEnd(node);
const QList<ClangdAstNode> children = node.children().value_or(QList<ClangdAstNode>());
// Match question mark and colon in ternary operators.
if (isExpression && node.kind() == "ConditionalOperator") {
if (children.size() != 3)
return;
// The question mark is between sub-expressions 1 and 2, the colon is between
// sub-expressions 2 and 3.
const int searchStartPosQuestionMark = posForNodeEnd(children.first());
const int searchEndPosQuestionMark = posForNodeStart(children.at(1));
QStringView content = subViewEnd(m_docContent, searchStartPosQuestionMark,
searchEndPosQuestionMark);
const int questionMarkPos = onlyIndexOf(content, QStringView(QStringLiteral("?")));
if (questionMarkPos == -1)
return;
const int searchStartPosColon = posForNodeEnd(children.at(1));
const int searchEndPosColon = posForNodeStart(children.at(2));
content = subViewEnd(m_docContent, searchStartPosColon, searchEndPosColon);
const int colonPos = onlyIndexOf(content, QStringView(QStringLiteral(":")));
if (colonPos == -1)
return;
const int absQuestionMarkPos = searchStartPosQuestionMark + questionMarkPos;
const int absColonPos = searchStartPosColon + colonPos;
if (absQuestionMarkPos > absColonPos)
return;
HighlightingResult result;
result.useTextSyles = true;
result.textStyles.mainStyle = C_PUNCTUATION;
result.textStyles.mixinStyles.push_back(C_OPERATOR);
Utils::Text::convertPosition(m_doc, absQuestionMarkPos, &result.line, &result.column);
result.length = 1;
result.kind = CppEditor::SemanticHighlighter::TernaryIf;
insertResult(result);
Utils::Text::convertPosition(m_doc, absColonPos, &result.line, &result.column);
result.kind = CppEditor::SemanticHighlighter::TernaryElse;
insertResult(result);
return;
}
if (isDeclaration && (node.kind() == "FunctionTemplate"
|| node.kind() == "ClassTemplate")) {
// The child nodes are the template parameters and and the function or class.
// The opening angle bracket is before the first child node, the closing angle
// bracket is before the function child node and after the last param node.
const QString classOrFunctionKind = QLatin1String(node.kind() == "FunctionTemplate"
? "Function" : "CXXRecord");
const auto functionOrClassIt = std::find_if(children.begin(), children.end(),
[&classOrFunctionKind](const ClangdAstNode &n) {
return n.role() == "declaration" && n.kind() == classOrFunctionKind;
});
if (functionOrClassIt == children.end() || functionOrClassIt == children.begin())
return;
const int firstTemplateParamStartPos = posForNodeStart(children.first());
const int lastTemplateParamEndPos = posForNodeEnd(*(functionOrClassIt - 1));
const int functionOrClassStartPos = posForNodeStart(*functionOrClassIt);
insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos,
lastTemplateParamEndPos, functionOrClassStartPos);
return;
}
const auto isTemplateParamDecl = [](const ClangdAstNode &node) {
return node.isTemplateParameterDeclaration();
};
if (isDeclaration && node.kind() == "TypeAliasTemplate") {
// Children are one node of type TypeAlias and the template parameters.
// The opening angle bracket is before the first parameter and the closing
// angle bracket is after the last parameter.
// The TypeAlias node seems to appear first in the AST, even though lexically
// is comes after the parameters. We don't rely on the order here.
// Note that there is a second pair of angle brackets. That one is part of
// a TemplateSpecialization, which is handled further below.
const auto firstTemplateParam = std::find_if(children.begin(), children.end(),
isTemplateParamDecl);
if (firstTemplateParam == children.end())
return;
const auto lastTemplateParam = std::find_if(children.rbegin(), children.rend(),
isTemplateParamDecl);
QTC_ASSERT(lastTemplateParam != children.rend(), return);
const auto typeAlias = std::find_if(children.begin(), children.end(),
[](const ClangdAstNode &n) { return n.kind() == "TypeAlias"; });
if (typeAlias == children.end())
return;
const int firstTemplateParamStartPos = posForNodeStart(*firstTemplateParam);
const int lastTemplateParamEndPos = posForNodeEnd(*lastTemplateParam);
const int searchEndPos = posForNodeStart(*typeAlias);
insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos,
lastTemplateParamEndPos, searchEndPos);
return;
}
if (isDeclaration && node.kind() == "ClassTemplateSpecialization") {
// There is one child of kind TemplateSpecialization. The first pair
// of angle brackets comes before that.
if (children.size() == 1) {
const int childNodePos = posForNodeStart(children.first());
insertAngleBracketInfo(nodeStartPos, childNodePos, nodeStartPos, childNodePos);
}
return;
}
if (isDeclaration && node.kind() == "TemplateTemplateParm") {
// The child nodes are template arguments and template parameters.
// Arguments seem to appear before parameters in the AST, even though they
// come after them in the source code. We don't rely on the order here.
const auto firstTemplateParam = std::find_if(children.begin(), children.end(),
isTemplateParamDecl);
if (firstTemplateParam == children.end())
return;
const auto lastTemplateParam = std::find_if(children.rbegin(), children.rend(),
isTemplateParamDecl);
QTC_ASSERT(lastTemplateParam != children.rend(), return);
const auto templateArg = std::find_if(children.begin(), children.end(),
[](const ClangdAstNode &n) { return n.role() == "template argument"; });
const int firstTemplateParamStartPos = posForNodeStart(*firstTemplateParam);
const int lastTemplateParamEndPos = posForNodeEnd(*lastTemplateParam);
const int searchEndPos = templateArg == children.end()
? nodeEndPos : posForNodeStart(*templateArg);
insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos,
lastTemplateParamEndPos, searchEndPos);
return;
}
// {static,dynamic,reinterpret}_cast<>().
if (isExpression && node.kind().startsWith("CXX") && node.kind().endsWith("Cast")) {
// First child is type, second child is expression.
// The opening angle bracket is before the first child, the closing angle bracket
// is between the two children.
if (children.size() == 2) {
insertAngleBracketInfo(nodeStartPos, posForNodeStart(children.first()),
posForNodeEnd(children.first()),
posForNodeStart(children.last()));
}
return;
}
if (node.kind() == "TemplateSpecialization") {
// First comes the template type, then the template arguments.
// The opening angle bracket is before the first template argument,
// the closing angle bracket is after the last template argument.
// The first child node has no range, so we start searching at the parent node.
if (children.size() >= 2) {
int searchStart2 = posForNodeEnd(children.last());
int searchEnd2 = nodeEndPos;
// There is a weird off-by-one error on the clang side: If there is a
// nested template instantiation *and* there is no space between
// the closing angle brackets, then the inner TemplateSpecialization node's range
// will extend one character too far, covering the outer's closing angle bracket.
// This is what we are correcting for here.
// This issue is tracked at https://github.com/clangd/clangd/issues/871.
if (searchStart2 == searchEnd2)
--searchStart2;
insertAngleBracketInfo(nodeStartPos, posForNodeStart(children.at(1)),
searchStart2, searchEnd2);
}
return;
}
if (!isExpression && !isDeclaration)
return;
// Operators, overloaded ones in particular.
static const QString operatorPrefix = "operator";
QString detail = node.detail().value_or(QString());
const bool isCallToNew = node.kind() == "CXXNew";
const bool isCallToDelete = node.kind() == "CXXDelete";
const auto isProperOperator = [&] {
if (isCallToNew || isCallToDelete)
return true;
if (!detail.startsWith(operatorPrefix))
return false;
if (detail == operatorPrefix)
return false;
const QChar nextChar = detail.at(operatorPrefix.length());
return !nextChar.isLetterOrNumber() && nextChar != '_';
};
if (!isProperOperator())
return;
if (!isCallToNew && !isCallToDelete)
detail.remove(0, operatorPrefix.length());
HighlightingResult result;
result.useTextSyles = true;
const bool isConversionOp = node.kind() == "CXXConversion";
const bool isOverloaded = !isConversionOp
&& (isDeclaration || ((!isCallToNew && !isCallToDelete)
|| node.arcanaContains("CXXMethod")));
result.textStyles.mainStyle = isConversionOp
? C_PRIMITIVE_TYPE
: isCallToNew || isCallToDelete || detail.at(0).isSpace()
? C_KEYWORD : C_PUNCTUATION;
result.textStyles.mixinStyles.push_back(C_OPERATOR);
if (isOverloaded)
result.textStyles.mixinStyles.push_back(C_OVERLOADED_OPERATOR);
if (isDeclaration)
result.textStyles.mixinStyles.push_back(C_DECLARATION);
const QStringView nodeText = subViewEnd(m_docContent, nodeStartPos, nodeEndPos);
if (isCallToNew || isCallToDelete) {
result.line = node.range().start().line() + 1;
result.column = node.range().start().character() + 1;
result.length = isCallToNew ? 3 : 6;
insertResult(result);
if (node.arcanaContains("array")) {
const int openingBracketOffset = nodeText.indexOf('[');
if (openingBracketOffset == -1)
return;
const int closingBracketOffset = nodeText.lastIndexOf(']');
if (closingBracketOffset == -1 || closingBracketOffset < openingBracketOffset)
return;
result.textStyles.mainStyle = C_PUNCTUATION;
result.length = 1;
Utils::Text::convertPosition(m_doc,
nodeStartPos + openingBracketOffset,
&result.line, &result.column);
insertResult(result);
Utils::Text::convertPosition(m_doc,
nodeStartPos + closingBracketOffset,
&result.line, &result.column);
insertResult(result);
}
return;
}
if (isExpression && (detail == QLatin1String("()") || detail == QLatin1String("[]"))) {
result.line = node.range().start().line() + 1;
result.column = node.range().start().character() + 1;
result.length = 1;
insertResult(result);
result.line = node.range().end().line() + 1;
result.column = node.range().end().character();
insertResult(result);
return;
}
const int opStringLen = detail.at(0).isSpace() ? detail.length() - 1 : detail.length();
// The simple case: Call to operator+, +=, * etc.
if (nodeEndPos - nodeStartPos == opStringLen) {
setResultPosFromRange(result, node.range());
insertResult(result);
return;
}
const int prefixOffset = nodeText.indexOf(operatorPrefix);
if (prefixOffset == -1)
return;
const bool isArray = detail == "[]";
const bool isCall = detail == "()";
const bool isArrayNew = detail == " new[]";
const bool isArrayDelete = detail == " delete[]";
const QStringView searchTerm = isArray || isCall
? QStringView(detail).chopped(1) : isArrayNew || isArrayDelete
? QStringView(detail).chopped(2) : detail;
const int opStringOffset = nodeText.indexOf(searchTerm, prefixOffset
+ operatorPrefix.length());
if (opStringOffset == -1 || nodeText.indexOf(operatorPrefix, opStringOffset) != -1)
return;
const int opStringOffsetInDoc = nodeStartPos + opStringOffset
+ detail.length() - opStringLen;
Utils::Text::convertPosition(m_doc, opStringOffsetInDoc, &result.line, &result.column);
result.length = opStringLen;
if (isArray || isCall)
result.length = 1;
else if (isArrayNew || isArrayDelete)
result.length -= 2;
if (!isArray && !isCall)
insertResult(result);
if (!isArray && !isCall && !isArrayNew && !isArrayDelete)
return;
result.textStyles.mainStyle = C_PUNCTUATION;
result.length = 1;
const int openingParenOffset = nodeText.indexOf(
isCall ? '(' : '[', prefixOffset + operatorPrefix.length());
if (openingParenOffset == -1)
return;
const int closingParenOffset = nodeText.indexOf(isCall ? ')' : ']', openingParenOffset + 1);
if (closingParenOffset == -1 || closingParenOffset < openingParenOffset)
return;
Utils::Text::convertPosition(m_doc, nodeStartPos + openingParenOffset,
&result.line, &result.column);
insertResult(result);
Utils::Text::convertPosition(m_doc, nodeStartPos + closingParenOffset,
&result.line, &result.column);
insertResult(result);
}
void ExtraHighlightingResultsCollector::visitNode(const ClangdAstNode &node)
{
if (m_future.isCanceled())
return;
const ClangdAstNode::FileStatus prevFileStatus = m_currentFileStatus;
m_currentFileStatus = node.fileStatus(m_filePath);
if (m_currentFileStatus == ClangdAstNode::FileStatus::Unknown
&& prevFileStatus != ClangdAstNode::FileStatus::Ours) {
m_currentFileStatus = prevFileStatus;
}
switch (m_currentFileStatus) {
case ClangdAstNode::FileStatus::Ours:
case ClangdAstNode::FileStatus::Unknown:
collectFromNode(node);
[[fallthrough]];
case ClangdAstNode::FileStatus::Foreign:
case ClangCodeModel::Internal::ClangdAstNode::FileStatus::Mixed: {
const auto children = node.children();
if (!children)
return;
for (const ClangdAstNode &childNode : *children)
visitNode(childNode);
break;
}
}
m_currentFileStatus = prevFileStatus;
}
} // namespace ClangCodeModel::Internal

View File

@@ -1,6 +1,6 @@
/**************************************************************************** /****************************************************************************
** **
** Copyright (C) 2017 The Qt Company Ltd. ** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/ ** Contact: https://www.qt.io/licensing/
** **
** This file is part of Qt Creator. ** This file is part of Qt Creator.
@@ -25,34 +25,33 @@
#pragma once #pragma once
#include "abstractremotelinuxdeployservice.h" #include <QFutureInterface>
#include <QLoggingCategory>
#include <QPointer>
#include <QVersionNumber>
namespace RemoteLinux { namespace LanguageClient { class ExpandedSemanticToken; }
namespace Internal { class RemoteLinuxKillAppServicePrivate; } namespace TextEditor {
class HighlightingResult;
class TextDocument;
}
namespace Utils { class FilePath; }
class REMOTELINUX_EXPORT RemoteLinuxKillAppService : public AbstractRemoteLinuxDeployService namespace ClangCodeModel::Internal {
{ class ClangdAstNode;
Q_OBJECT class TaskTimer;
public: Q_DECLARE_LOGGING_CATEGORY(clangdLogHighlight);
RemoteLinuxKillAppService();
~RemoteLinuxKillAppService() override;
void setRemoteExecutable(const QString &filePath); void doSemanticHighlighting(
QFutureInterface<TextEditor::HighlightingResult> &future,
const Utils::FilePath &filePath,
const QList<LanguageClient::ExpandedSemanticToken> &tokens,
const QString &docContents,
const ClangdAstNode &ast,
const QPointer<TextEditor::TextDocument> &textDocument,
int docRevision,
const QVersionNumber &clangdVersion,
const TaskTimer &taskTimer
);
private: } // namespace ClangCodeModel::Internal
void handleStdErr();
void handleProcessFinished();
bool isDeploymentNecessary() const override;
void doDeploy() override;
void stopDeployment() override;
void handleSignalOpFinished(const QString &errorMessage);
void cleanup();
void finishDeployment();
Internal::RemoteLinuxKillAppServicePrivate * const d;
};
} // namespace RemoteLinux

View File

@@ -0,0 +1,108 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "tasktimers.h"
#include <utils/qtcassert.h>
#include <QDateTime>
namespace ClangCodeModel::Internal {
Q_LOGGING_CATEGORY(clangdLogTiming, "qtc.clangcodemodel.clangd.timing", QtWarningMsg);
void ClangCodeModel::Internal::TaskTimer::stopTask()
{
// This can happen due to the RAII mechanism employed with SubtaskTimer.
// The subtask timers will expire immediately after, so this does not distort
// the timing data.
if (m_subtasks > 0) {
QTC_CHECK(m_timer.isValid());
m_elapsedMs += m_timer.elapsed();
m_timer.invalidate();
m_subtasks = 0;
}
m_started = false;
qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": took " << m_elapsedMs
<< " ms in UI thread";
m_elapsedMs = 0;
}
void TaskTimer::startSubtask()
{
// We have some callbacks that are either synchronous or asynchronous, depending on
// dynamic conditions. In the sync case, we will have nested subtasks, in which case
// the inner ones must not collect timing data, as their code blocks are already covered.
if (++m_subtasks > 1)
return;
if (!m_started) {
QTC_ASSERT(m_elapsedMs == 0, m_elapsedMs = 0);
m_started = true;
m_finalized = false;
qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": starting";
// Used by ThreadedSubtaskTimer to mark the end of the whole highlighting operation
m_startTimer.restart();
}
qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": subtask started at "
<< QDateTime::currentDateTime().time().toString("hh:mm:ss.zzz");
QTC_CHECK(!m_timer.isValid());
m_timer.start();
}
void TaskTimer::stopSubtask(bool isFinalizing)
{
if (m_subtasks == 0) // See stopTask().
return;
if (isFinalizing)
m_finalized = true;
if (--m_subtasks > 0) // See startSubtask().
return;
qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": subtask stopped at "
<< QDateTime::currentDateTime().time().toString("hh:mm:ss.zzz");
QTC_CHECK(m_timer.isValid());
m_elapsedMs += m_timer.elapsed();
m_timer.invalidate();
if (m_finalized)
stopTask();
}
ThreadedSubtaskTimer::ThreadedSubtaskTimer(const QString &task, const TaskTimer &taskTimer)
: m_task(task), m_taskTimer(taskTimer)
{
qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": starting thread";
m_timer.start();
}
ThreadedSubtaskTimer::~ThreadedSubtaskTimer()
{
qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": took " << m_timer.elapsed()
<< " ms in dedicated thread";
qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": Start to end: "
<< m_taskTimer.startTimer().elapsed() << " ms";
}
} // namespace ClangCodeModel::Internal

View File

@@ -0,0 +1,90 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <QElapsedTimer>
#include <QLoggingCategory>
#include <QString>
namespace ClangCodeModel::Internal {
Q_DECLARE_LOGGING_CATEGORY(clangdLogTiming);
// TODO: Generalize, document interface and move to Utils?
class TaskTimer
{
public:
TaskTimer(const QString &task) : m_task(task) {}
void stopTask();
void startSubtask();
void stopSubtask(bool isFinalizing);
QElapsedTimer startTimer() const { return m_startTimer; }
private:
const QString m_task;
QElapsedTimer m_timer;
QElapsedTimer m_startTimer;
qint64 m_elapsedMs = 0;
int m_subtasks = 0;
bool m_started = false;
bool m_finalized = false;
};
class SubtaskTimer
{
public:
SubtaskTimer(TaskTimer &timer) : m_timer(timer) { m_timer.startSubtask(); }
~SubtaskTimer() { m_timer.stopSubtask(m_isFinalizing); }
protected:
void makeFinalizing() { m_isFinalizing = true; }
private:
TaskTimer &m_timer;
bool m_isFinalizing = false;
};
class FinalizingSubtaskTimer : public SubtaskTimer
{
public:
FinalizingSubtaskTimer(TaskTimer &timer) : SubtaskTimer(timer) { makeFinalizing(); }
};
class ThreadedSubtaskTimer
{
public:
ThreadedSubtaskTimer(const QString &task, const TaskTimer &taskTimer);
~ThreadedSubtaskTimer();
private:
const QString m_task;
QElapsedTimer m_timer;
const TaskTimer &m_taskTimer;
};
} // namespace ClangCodeModel::Internal

View File

@@ -51,6 +51,7 @@
#include <debugger/analyzer/analyzermanager.h> #include <debugger/analyzer/analyzermanager.h>
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/kitinformation.h> #include <projectexplorer/kitinformation.h>
#include <projectexplorer/projectexplorer.h> #include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorericons.h> #include <projectexplorer/projectexplorericons.h>

View File

@@ -253,10 +253,10 @@ void ClangToolRunWorker::start()
// Collect files // Collect files
const auto clangIncludeDirAndVersion = const auto clangIncludeDirAndVersion =
getClangIncludeDirAndVersion(runControl()->runnable().command.executable()); getClangIncludeDirAndVersion(runControl()->commandLine().executable());
const AnalyzeUnits unitsToProcess = unitsToAnalyze(clangIncludeDirAndVersion.first, const AnalyzeUnits unitsToProcess = unitsToAnalyze(clangIncludeDirAndVersion.first,
clangIncludeDirAndVersion.second); clangIncludeDirAndVersion.second);
qCDebug(LOG) << Q_FUNC_INFO << runControl()->runnable().command.executable() qCDebug(LOG) << Q_FUNC_INFO << runControl()->commandLine().executable()
<< clangIncludeDirAndVersion.first << clangIncludeDirAndVersion.second; << clangIncludeDirAndVersion.first << clangIncludeDirAndVersion.second;
qCDebug(LOG) << "Files to process:" << unitsToProcess; qCDebug(LOG) << "Files to process:" << unitsToProcess;

View File

@@ -26,7 +26,6 @@
#include "documentclangtoolrunner.h" #include "documentclangtoolrunner.h"
#include "clangfileinfo.h" #include "clangfileinfo.h"
#include "clangfixitsrefactoringchanges.h"
#include "clangtidyclazyrunner.h" #include "clangtidyclazyrunner.h"
#include "clangtoolruncontrol.h" #include "clangtoolruncontrol.h"
#include "clangtoolsconstants.h" #include "clangtoolsconstants.h"
@@ -39,13 +38,18 @@
#include <coreplugin/documentmanager.h> #include <coreplugin/documentmanager.h>
#include <coreplugin/editormanager/editormanager.h> #include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/ieditor.h> #include <coreplugin/editormanager/ieditor.h>
#include <cppeditor/cppmodelmanager.h> #include <cppeditor/cppmodelmanager.h>
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/buildtargettype.h> #include <projectexplorer/buildtargettype.h>
#include <projectexplorer/session.h> #include <projectexplorer/session.h>
#include <projectexplorer/target.h> #include <projectexplorer/target.h>
#include <texteditor/textdocument.h> #include <texteditor/textdocument.h>
#include <texteditor/texteditor.h> #include <texteditor/texteditor.h>
#include <texteditor/textmark.h> #include <texteditor/textmark.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/utilsicons.h> #include <utils/utilsicons.h>

View File

@@ -1196,7 +1196,7 @@ Tasks CMakeConfigurationKitAspect::validate(const Kit *k) const
if (!version || !version->isValid()) { if (!version || !version->isValid()) {
addWarning(tr("CMake configuration has a path to a qmake binary set, " addWarning(tr("CMake configuration has a path to a qmake binary set, "
"even though the kit has no valid Qt version.")); "even though the kit has no valid Qt version."));
} else if (qmakePath != version->qmakeFilePath() && isQt4) { } else if (qmakePath != version->queryToolFilePath() && isQt4) {
addWarning(tr("CMake configuration has a path to a qmake binary set " addWarning(tr("CMake configuration has a path to a qmake binary set "
"that does not match the qmake binary path " "that does not match the qmake binary path "
"configured in the Qt version.")); "configured in the Qt version."));
@@ -1219,7 +1219,7 @@ Tasks CMakeConfigurationKitAspect::validate(const Kit *k) const
if (!tcC || !tcC->isValid()) { if (!tcC || !tcC->isValid()) {
addWarning(tr("CMake configuration has a path to a C compiler set, " addWarning(tr("CMake configuration has a path to a C compiler set, "
"even though the kit has no valid tool chain.")); "even though the kit has no valid tool chain."));
} else if (tcCPath != tcC->compilerCommand()) { } else if (tcCPath != tcC->compilerCommand() && tcCPath != tcC->compilerCommand().onDevice(tcCPath)) {
addWarning(tr("CMake configuration has a path to a C compiler set " addWarning(tr("CMake configuration has a path to a C compiler set "
"that does not match the compiler path " "that does not match the compiler path "
"configured in the tool chain of the kit.")); "configured in the tool chain of the kit."));
@@ -1235,7 +1235,7 @@ Tasks CMakeConfigurationKitAspect::validate(const Kit *k) const
if (!tcCxx || !tcCxx->isValid()) { if (!tcCxx || !tcCxx->isValid()) {
addWarning(tr("CMake configuration has a path to a C++ compiler set, " addWarning(tr("CMake configuration has a path to a C++ compiler set, "
"even though the kit has no valid tool chain.")); "even though the kit has no valid tool chain."));
} else if (tcCxxPath != tcCxx->compilerCommand()) { } else if (tcCxxPath != tcCxx->compilerCommand() && tcCxxPath != tcCxx->compilerCommand().onDevice(tcCxxPath)) {
addWarning(tr("CMake configuration has a path to a C++ compiler set " addWarning(tr("CMake configuration has a path to a C++ compiler set "
"that does not match the compiler path " "that does not match the compiler path "
"configured in the tool chain of the kit.")); "configured in the tool chain of the kit."));

View File

@@ -129,18 +129,16 @@ void StartRemoteDialog::validate()
d->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid); d->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid);
} }
Runnable StartRemoteDialog::runnable() const CommandLine StartRemoteDialog::commandLine() const
{ {
Kit *kit = d->kitChooser->currentKit(); const Kit *kit = d->kitChooser->currentKit();
IDevice::ConstPtr device = DeviceKitAspect::device(kit); const FilePath filePath = DeviceKitAspect::deviceFilePath(kit, d->executable->text());
FilePath filePath = FilePath::fromString(d->executable->text()); return {filePath, d->arguments->text(), CommandLine::Raw};
if (device) }
filePath = device->filePath(d->arguments->text());
Runnable r; FilePath StartRemoteDialog::workingDirectory() const
r.command = {filePath, d->arguments->text(), CommandLine::Raw}; {
r.workingDirectory = FilePath::fromString(d->workingDirectory->text()); return FilePath::fromString(d->workingDirectory->text());
return r;
} }
} // namespace Debugger } // namespace Debugger

View File

@@ -29,7 +29,10 @@
#include <QDialog> #include <QDialog>
namespace ProjectExplorer { class Runnable; } namespace Utils {
class CommandLine;
class FilePath;
} // Utils
namespace Debugger { namespace Debugger {
@@ -43,7 +46,8 @@ public:
explicit StartRemoteDialog(QWidget *parent = nullptr); explicit StartRemoteDialog(QWidget *parent = nullptr);
~StartRemoteDialog() override; ~StartRemoteDialog() override;
ProjectExplorer::Runnable runnable() const; Utils::CommandLine commandLine() const;
Utils::FilePath workingDirectory() const;
private: private:
void validate(); void validate();

View File

@@ -36,14 +36,6 @@ const char MODE_DEBUG[] = "Mode.Debug";
// Debug mode context // Debug mode context
const char C_DEBUGMODE[] = "Debugger.DebugMode"; const char C_DEBUGMODE[] = "Debugger.DebugMode";
// Common debugger constants.
const char kPeripheralDescriptionFile[] = "PeripheralDescriptionFile";
// UVSC-specific debugger constants.
const char kUVisionProjectFilePath[] = "UVisionProjectFilePath";
const char kUVisionOptionsFilePath[] = "UVisionOptionsFilePath";
const char kUVisionSimulator[] = "UVisionSimulator";
} // namespace Constants } // namespace Constants
// Keep in sync with dumper.py // Keep in sync with dumper.py

View File

@@ -206,6 +206,14 @@ public:
Utils::FilePath dumperPath; Utils::FilePath dumperPath;
int fallbackQtVersion = 0x50200; int fallbackQtVersion = 0x50200;
// Common debugger constants.
Utils::FilePath peripheralDescriptionFile;
// UVSC-specific debugger constants.
Utils::FilePath uVisionProjectFilePath;
Utils::FilePath uVisionOptionsFilePath;
bool uVisionSimulator = false;
}; };
class UpdateParameters class UpdateParameters

View File

@@ -669,7 +669,9 @@ void DebuggerItemManagerPrivate::autoDetectCdbDebuggers()
for (const QFileInfo &kitFolderFi : kitFolders) { for (const QFileInfo &kitFolderFi : kitFolders) {
const QString path = kitFolderFi.absoluteFilePath(); const QString path = kitFolderFi.absoluteFilePath();
const QStringList abis = {"x86", "x64", "arm64"}; QStringList abis = {"x86", "x64"};
if (HostOsInfo::hostArchitecture() == HostOsInfo::HostArchitectureArm)
abis << "arm64";
for (const QString &abi: abis) { for (const QString &abi: abis) {
const QFileInfo cdbBinary(path + "/Debuggers/" + abi + "/cdb.exe"); const QFileInfo cdbBinary(path + "/Debuggers/" + abi + "/cdb.exe");
if (cdbBinary.isExecutable()) if (cdbBinary.isExecutable())

View File

@@ -1736,8 +1736,7 @@ RunControl *DebuggerPluginPrivate::attachToRunningProcess(Kit *kit,
runControl->setDisplayName(tr("Process %1").arg(processInfo.processId)); runControl->setDisplayName(tr("Process %1").arg(processInfo.processId));
auto debugger = new DebuggerRunTool(runControl); auto debugger = new DebuggerRunTool(runControl);
debugger->setAttachPid(ProcessHandle(processInfo.processId)); debugger->setAttachPid(ProcessHandle(processInfo.processId));
debugger->setInferiorExecutable(FilePath::fromString(processInfo.executable)); debugger->setInferiorExecutable(device->filePath(processInfo.executable));
debugger->setInferiorDevice(device);
debugger->setStartMode(AttachToLocalProcess); debugger->setStartMode(AttachToLocalProcess);
debugger->setCloseMode(DetachAtClose); debugger->setCloseMode(DetachAtClose);
debugger->setContinueAfterAttach(contAfterAttach); debugger->setContinueAfterAttach(contAfterAttach);

View File

@@ -395,11 +395,6 @@ void DebuggerRunTool::setInferiorEnvironment(const Utils::Environment &env)
m_runParameters.inferior.environment = env; m_runParameters.inferior.environment = env;
} }
void DebuggerRunTool::setInferiorDevice(IDevice::ConstPtr device)
{
m_runParameters.inferior.device = device;
}
void DebuggerRunTool::setRunControlName(const QString &name) void DebuggerRunTool::setRunControlName(const QString &name)
{ {
m_runParameters.displayName = name; m_runParameters.displayName = name;
@@ -910,7 +905,7 @@ DebuggerRunTool::DebuggerRunTool(RunControl *runControl, AllowTerminal allowTerm
} }
} }
Runnable inferior = runnable(); Runnable inferior = runControl->runnable();
const FilePath &debuggerExecutable = m_runParameters.debugger.command.executable(); const FilePath &debuggerExecutable = m_runParameters.debugger.command.executable();
inferior.command.setExecutable(inferior.command.executable().onDevice(debuggerExecutable)); inferior.command.setExecutable(inferior.command.executable().onDevice(debuggerExecutable));
inferior.workingDirectory = inferior.workingDirectory.onDevice(debuggerExecutable); inferior.workingDirectory = inferior.workingDirectory.onDevice(debuggerExecutable);

View File

@@ -76,7 +76,6 @@ public:
void setInferior(const ProjectExplorer::Runnable &runnable); void setInferior(const ProjectExplorer::Runnable &runnable);
void setInferiorExecutable(const Utils::FilePath &executable); void setInferiorExecutable(const Utils::FilePath &executable);
void setInferiorEnvironment(const Utils::Environment &env); // Used by GammaRay plugin void setInferiorEnvironment(const Utils::Environment &env); // Used by GammaRay plugin
void setInferiorDevice(ProjectExplorer::IDeviceConstPtr device); // Used by cdbengine
void setRunControlName(const QString &name); void setRunControlName(const QString &name);
void setStartMessage(const QString &msg); void setStartMessage(const QString &msg);
void addQmlServerInferiorCommandLineArgumentIfNeeded(); void addQmlServerInferiorCommandLineArgumentIfNeeded();
@@ -132,6 +131,8 @@ public:
Internal::TerminalRunner *terminalRunner() const; Internal::TerminalRunner *terminalRunner() const;
DebuggerEngineType cppEngineType() const; DebuggerEngineType cppEngineType() const;
Internal::DebuggerRunParameters &runParameters() { return m_runParameters; }
private: private:
bool fixupParameters(); bool fixupParameters();
void handleEngineStarted(Internal::DebuggerEngine *engine); void handleEngineStarted(Internal::DebuggerEngine *engine);

View File

@@ -25,23 +25,21 @@
#include "loadcoredialog.h" #include "loadcoredialog.h"
#include "debuggerdialogs.h"
#include "debuggerkitinformation.h" #include "debuggerkitinformation.h"
#include "gdb/gdbengine.h" #include "gdb/gdbengine.h"
#include <projectexplorer/devicesupport/devicefilesystemmodel.h> #include <projectexplorer/devicesupport/devicefilesystemmodel.h>
#include <projectexplorer/devicesupport/filetransfer.h>
#include <projectexplorer/devicesupport/idevice.h> #include <projectexplorer/devicesupport/idevice.h>
#include <projectexplorer/kitinformation.h> #include <projectexplorer/kitchooser.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <utils/pathchooser.h> #include <utils/pathchooser.h>
#include <utils/processinterface.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/runextensions.h>
#include <utils/temporaryfile.h> #include <utils/temporaryfile.h>
#include <QCheckBox> #include <QCheckBox>
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QFormLayout> #include <QFormLayout>
#include <QFutureWatcher>
#include <QHeaderView> #include <QHeaderView>
#include <QLabel> #include <QLabel>
#include <QLineEdit> #include <QLineEdit>
@@ -69,7 +67,6 @@ class SelectRemoteFileDialog : public QDialog
public: public:
explicit SelectRemoteFileDialog(QWidget *parent); explicit SelectRemoteFileDialog(QWidget *parent);
~SelectRemoteFileDialog();
void attachToDevice(Kit *k); void attachToDevice(Kit *k);
FilePath localFile() const { return m_localFile; } FilePath localFile() const { return m_localFile; }
@@ -77,7 +74,6 @@ public:
private: private:
void selectFile(); void selectFile();
void clearWatcher();
QSortFilterProxyModel m_model; QSortFilterProxyModel m_model;
DeviceFileSystemModel m_fileSystemModel; DeviceFileSystemModel m_fileSystemModel;
@@ -86,7 +82,7 @@ private:
QDialogButtonBox *m_buttonBox; QDialogButtonBox *m_buttonBox;
FilePath m_localFile; FilePath m_localFile;
FilePath m_remoteFile; FilePath m_remoteFile;
QFutureWatcher<bool> *m_watcher = nullptr; FileTransfer m_fileTransfer;
}; };
SelectRemoteFileDialog::SelectRemoteFileDialog(QWidget *parent) SelectRemoteFileDialog::SelectRemoteFileDialog(QWidget *parent)
@@ -119,11 +115,21 @@ SelectRemoteFileDialog::SelectRemoteFileDialog(QWidget *parent)
connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(m_buttonBox, &QDialogButtonBox::accepted, this, &SelectRemoteFileDialog::selectFile); connect(m_buttonBox, &QDialogButtonBox::accepted, this, &SelectRemoteFileDialog::selectFile);
}
SelectRemoteFileDialog::~SelectRemoteFileDialog() connect(&m_fileTransfer, &FileTransfer::done, this, [this](const ProcessResultData &result) {
{ const bool success = result.m_error == QProcess::UnknownError
clearWatcher(); && result.m_exitStatus == QProcess::NormalExit
&& result.m_exitCode == 0;
if (success) {
m_textBrowser->append(tr("Download of remote file succeeded."));
accept();
} else {
m_textBrowser->append(tr("Download of remote file failed: %1")
.arg(result.m_errorString));
m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
m_fileSystemView->setEnabled(true);
}
});
} }
void SelectRemoteFileDialog::attachToDevice(Kit *k) void SelectRemoteFileDialog::attachToDevice(Kit *k)
@@ -135,22 +141,6 @@ void SelectRemoteFileDialog::attachToDevice(Kit *k)
m_fileSystemModel.setDevice(device); m_fileSystemModel.setDevice(device);
} }
static void copyFile(QFutureInterface<bool> &futureInterface, const FilePath &remoteSource,
const FilePath &localDestination)
{
// TODO: this should be implemented transparently in FilePath::copyFile()
// The code here should be just:
//
// futureInterface.reportResult(remoteSource.copyFile(localDestination));
//
// The implementation below won't handle really big files, like core files.
const QByteArray data = remoteSource.fileContents();
if (futureInterface.isCanceled())
return;
futureInterface.reportResult(localDestination.writeFileContents(data));
}
void SelectRemoteFileDialog::selectFile() void SelectRemoteFileDialog::selectFile()
{ {
QModelIndex idx = m_model.mapToSource(m_fileSystemView->currentIndex()); QModelIndex idx = m_model.mapToSource(m_fileSystemView->currentIndex());
@@ -161,7 +151,7 @@ void SelectRemoteFileDialog::selectFile()
m_fileSystemView->setEnabled(false); m_fileSystemView->setEnabled(false);
{ {
Utils::TemporaryFile localFile("remotecore-XXXXXX"); TemporaryFile localFile("remotecore-XXXXXX");
localFile.open(); localFile.open();
m_localFile = FilePath::fromString(localFile.fileName()); m_localFile = FilePath::fromString(localFile.fileName());
} }
@@ -169,33 +159,8 @@ void SelectRemoteFileDialog::selectFile()
idx = idx.sibling(idx.row(), 1); idx = idx.sibling(idx.row(), 1);
m_remoteFile = FilePath::fromVariant(m_fileSystemModel.data(idx, DeviceFileSystemModel::PathRole)); m_remoteFile = FilePath::fromVariant(m_fileSystemModel.data(idx, DeviceFileSystemModel::PathRole));
clearWatcher(); m_fileTransfer.setFilesToTransfer({{m_remoteFile, m_localFile}});
m_watcher = new QFutureWatcher<bool>(this); m_fileTransfer.start();
auto future = runAsync(copyFile, m_remoteFile, m_localFile);
connect(m_watcher, &QFutureWatcher<bool>::finished, this, [this] {
const bool success = m_watcher->result();
if (success) {
m_textBrowser->append(tr("Download of remote file succeeded."));
accept();
} else {
m_textBrowser->append(tr("Download of remote file failed."));
m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
m_fileSystemView->setEnabled(true);
}
});
m_watcher->setFuture(future);
}
void SelectRemoteFileDialog::clearWatcher()
{
if (!m_watcher)
return;
m_watcher->disconnect();
m_watcher->future().cancel();
m_watcher->future().waitForFinished();
delete m_watcher;
m_watcher = nullptr;
} }
/////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////

View File

@@ -732,12 +732,9 @@ void PeripheralRegisterHandler::updateRegisterGroups()
clear(); clear();
const DebuggerRunParameters &rp = m_engine->runParameters(); const DebuggerRunParameters &rp = m_engine->runParameters();
const FilePath peripheralDescriptionFile = FilePath::fromVariant( if (!rp.peripheralDescriptionFile.exists())
rp.inferior.extraData.value(Debugger::Constants::kPeripheralDescriptionFile));
if (!peripheralDescriptionFile.exists())
return; return;
m_peripheralRegisterGroups = availablePeripheralRegisterGroups(peripheralDescriptionFile); m_peripheralRegisterGroups = availablePeripheralRegisterGroups(rp.peripheralDescriptionFile);
} }
void PeripheralRegisterHandler::updateRegister(quint64 address, quint64 value) void PeripheralRegisterHandler::updateRegister(quint64 address, quint64 value)

View File

@@ -536,10 +536,8 @@ void UvscEngine::updateAll()
bool UvscEngine::configureProject(const DebuggerRunParameters &rp) bool UvscEngine::configureProject(const DebuggerRunParameters &rp)
{ {
// Fetch patchs for the generated uVision project files. // Fetch patchs for the generated uVision project files.
const FilePath optionsPath = FilePath::fromString(rp.inferior.extraData.value( const FilePath optionsPath = rp.uVisionOptionsFilePath;
Constants::kUVisionOptionsFilePath).toString()); const FilePath projectPath = rp.uVisionProjectFilePath;
const FilePath projectPath = FilePath::fromString(rp.inferior.extraData.value(
Constants::kUVisionProjectFilePath).toString());
showMessage("UVSC: LOADING PROJECT..."); showMessage("UVSC: LOADING PROJECT...");
if (!optionsPath.exists()) { if (!optionsPath.exists()) {
@@ -557,7 +555,7 @@ bool UvscEngine::configureProject(const DebuggerRunParameters &rp)
} }
showMessage("UVSC: SETTING PROJECT DEBUG TARGET..."); showMessage("UVSC: SETTING PROJECT DEBUG TARGET...");
m_simulator = rp.inferior.extraData.value(Constants::kUVisionSimulator).toBool(); m_simulator = rp.uVisionSimulator;
if (!m_client->setProjectDebugTarget(m_simulator)) { if (!m_client->setProjectDebugTarget(m_simulator)) {
handleSetupFailure(tr("Internal error: Unable to set the uVision debug target: %1.") handleSetupFailure(tr("Internal error: Unable to set the uVision debug target: %1.")
.arg(m_client->errorString())); .arg(m_client->errorString()));
@@ -606,8 +604,7 @@ void UvscEngine::handleProjectClosed()
m_loadingRequired = false; m_loadingRequired = false;
const DebuggerRunParameters &rp = runParameters(); const DebuggerRunParameters &rp = runParameters();
const FilePath projectPath = FilePath::fromString(rp.inferior.extraData.value( const FilePath projectPath = rp.uVisionProjectFilePath;
Constants::kUVisionProjectFilePath).toString());
// This magic function removes specific files from the uVision // This magic function removes specific files from the uVision
// project directory. Without of this we can't enumerate the local // project directory. Without of this we can't enumerate the local

View File

@@ -27,6 +27,7 @@
#include <projectexplorer/devicesupport/idevice.h> #include <projectexplorer/devicesupport/idevice.h>
#include <projectexplorer/devicesupport/idevicefactory.h> #include <projectexplorer/devicesupport/idevicefactory.h>
#include <coreplugin/documentmanager.h>
#include <utils/aspects.h> #include <utils/aspects.h>
@@ -44,7 +45,7 @@ public:
QString tag; QString tag;
QString size; QString size;
bool useLocalUidGid = true; bool useLocalUidGid = true;
QStringList mounts; QStringList mounts = { Core::DocumentManager::projectsDirectory().toString() };
}; };
class DockerDevice : public ProjectExplorer::IDevice class DockerDevice : public ProjectExplorer::IDevice

View File

@@ -25,8 +25,6 @@
#include "kitdetector.h" #include "kitdetector.h"
#include "dockerconstants.h"
#include <cmakeprojectmanager/cmakeprojectconstants.h> #include <cmakeprojectmanager/cmakeprojectconstants.h>
#include <extensionsystem/pluginmanager.h> #include <extensionsystem/pluginmanager.h>
@@ -216,10 +214,10 @@ QtVersions KitDetectorPrivate::autoDetectQtVersions() const
QString error; QString error;
const auto handleQmake = [this, &qtVersions, &error](const FilePath &qmake) { const auto handleQmake = [this, &qtVersions, &error](const FilePath &qmake) {
if (QtVersion *qtVersion = QtVersionFactory::createQtVersionFromQMakePath(qmake, if (QtVersion *qtVersion = QtVersionFactory::createQtVersionFromQueryToolPath(qmake,
false, false,
m_sharedId, m_sharedId,
&error)) { &error)) {
if (qtVersion->isValid()) { if (qtVersion->isValid()) {
if (!Utils::anyOf(qtVersions, if (!Utils::anyOf(qtVersions,
[qtVersion](QtVersion* other) { [qtVersion](QtVersion* other) {
@@ -229,7 +227,7 @@ QtVersions KitDetectorPrivate::autoDetectQtVersions() const
qtVersions.append(qtVersion); qtVersions.append(qtVersion);
QtVersionManager::addVersion(qtVersion); QtVersionManager::addVersion(qtVersion);
emit q->logOutput( emit q->logOutput(
tr("Found \"%1\"").arg(qtVersion->qmakeFilePath().toUserOutput())); tr("Found \"%1\"").arg(qtVersion->queryToolFilePath().toUserOutput()));
} }
} }
} }
@@ -337,7 +335,7 @@ void KitDetectorPrivate::autoDetect()
if (cmakeId.isValid()) if (cmakeId.isValid())
k->setValue(CMakeProjectManager::Constants::TOOL_ID, cmakeId.toSetting()); k->setValue(CMakeProjectManager::Constants::TOOL_ID, cmakeId.toSetting());
DeviceTypeKitAspect::setDeviceTypeId(k, Constants::DOCKER_DEVICE_TYPE); DeviceTypeKitAspect::setDeviceTypeId(k, m_device->type());
DeviceKitAspect::setDevice(k, m_device); DeviceKitAspect::setDevice(k, m_device);
BuildDeviceKitAspect::setDevice(k, m_device); BuildDeviceKitAspect::setDevice(k, m_device);

View File

@@ -3,6 +3,8 @@ add_qtc_plugin(GitLab
PLUGIN_DEPENDS Core ProjectExplorer Git VcsBase PLUGIN_DEPENDS Core ProjectExplorer Git VcsBase
DEPENDS Utils DEPENDS Utils
SOURCES SOURCES
gitlabclonedialog.cpp gitlabclonedialog.h
gitlabdialog.cpp gitlabdialog.h gitlabdialog.ui
gitlaboptionspage.cpp gitlaboptionspage.h gitlaboptionspage.cpp gitlaboptionspage.h
gitlabparameters.cpp gitlabparameters.h gitlabparameters.cpp gitlabparameters.h
gitlabplugin.cpp gitlabplugin.h gitlabplugin.cpp gitlabplugin.h

View File

@@ -10,6 +10,11 @@ QtcPlugin {
Depends { name: "Utils" } Depends { name: "Utils" }
files: [ files: [
"gitlabclonedialog.cpp",
"gitlabclonedialog.h",
"gitlabdialog.cpp",
"gitlabdialog.h",
"gitlabdialog.ui",
"gitlaboptionspage.cpp", "gitlaboptionspage.cpp",
"gitlaboptionspage.h", "gitlaboptionspage.h",
"gitlabparameters.cpp", "gitlabparameters.cpp",

View File

@@ -0,0 +1,263 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "gitlabclonedialog.h"
#include "gitlabprojectsettings.h"
#include "resultparser.h"
#include <coreplugin/documentmanager.h>
#include <coreplugin/shellcommand.h>
#include <coreplugin/vcsmanager.h>
#include <git/gitclient.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectmanager.h>
#include <utils/algorithm.h>
#include <utils/commandline.h>
#include <utils/environment.h>
#include <utils/fancylineedit.h>
#include <utils/filepath.h>
#include <utils/infolabel.h>
#include <utils/mimeutils.h>
#include <utils/pathchooser.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
#include <QApplication>
#include <QCheckBox>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QInputDialog>
#include <QMessageBox>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QVBoxLayout>
namespace GitLab {
GitLabCloneDialog::GitLabCloneDialog(const Project &project, QWidget *parent)
: QDialog(parent)
{
setWindowTitle(tr("Clone Repository"));
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(new QLabel(tr("Specify repository URL, checkout path and directory.")));
QHBoxLayout *centerLayout = new QHBoxLayout;
QFormLayout *form = new QFormLayout;
m_repositoryCB = new QComboBox(this);
m_repositoryCB->addItems({project.sshUrl, project.httpUrl});
form->addRow(tr("Repository"), m_repositoryCB);
m_pathChooser = new Utils::PathChooser(this);
m_pathChooser->setExpectedKind(Utils::PathChooser::ExistingDirectory);
form->addRow(tr("Path"), m_pathChooser);
m_directoryLE = new Utils::FancyLineEdit(this);
m_directoryLE->setValidationFunction([this](Utils::FancyLineEdit *e, QString *msg) {
const Utils::FilePath fullPath = m_pathChooser->filePath().pathAppended(e->text());
bool alreadyExists = fullPath.exists();
if (alreadyExists && msg)
*msg = tr("Path \"%1\" already exists.").arg(fullPath.toUserOutput());
return !alreadyExists;
});
form->addRow(tr("Directory"), m_directoryLE);
m_submodulesCB = new QCheckBox(this);
form->addRow(tr("Recursive"), m_submodulesCB);
centerLayout->addLayout(form);
m_cloneOutput = new QPlainTextEdit(this);
m_cloneOutput->setReadOnly(true);
centerLayout->addWidget(m_cloneOutput);
layout->addLayout(centerLayout);
m_infoLabel = new Utils::InfoLabel(this);
layout->addWidget(m_infoLabel);
layout->addStretch(1);
auto buttons = new QDialogButtonBox(QDialogButtonBox::Cancel, this);
m_cloneButton = new QPushButton(tr("Clone"), this);
buttons->addButton(m_cloneButton, QDialogButtonBox::ActionRole);
m_cancelButton = buttons->button(QDialogButtonBox::Cancel);
layout->addWidget(buttons);
setLayout(layout);
m_pathChooser->setFilePath(Core::DocumentManager::projectsDirectory());
auto [host, path, port]
= GitLabProjectSettings::remotePartsFromRemote(m_repositoryCB->currentText());
int slashIndex = path.indexOf('/');
QTC_ASSERT(slashIndex > 0, return);
m_directoryLE->setText(path.mid(slashIndex + 1));
connect(m_pathChooser, &Utils::PathChooser::pathChanged, this, [this]() {
m_directoryLE->validate();
GitLabCloneDialog::updateUi();
});
connect(m_directoryLE, &Utils::FancyLineEdit::textChanged, this, &GitLabCloneDialog::updateUi);
connect(m_cloneButton, &QPushButton::clicked, this, &GitLabCloneDialog::cloneProject);
connect(m_cancelButton, &QPushButton::clicked,
this, &GitLabCloneDialog::cancel);
connect(this, &QDialog::rejected, this, [this]() {
if (m_commandRunning) {
cancel();
QApplication::restoreOverrideCursor();
return;
}
});
updateUi();
resize(575, 265);
}
void GitLabCloneDialog::updateUi()
{
bool pathValid = m_pathChooser->isValid();
bool directoryValid = m_directoryLE->isValid();
m_cloneButton->setEnabled(pathValid && directoryValid);
if (!pathValid) {
m_infoLabel->setText(m_pathChooser->errorMessage());
m_infoLabel->setType(Utils::InfoLabel::Error);
} else if (!directoryValid) {
m_infoLabel->setText(m_directoryLE->errorMessage());
m_infoLabel->setType(Utils::InfoLabel::Error);
}
m_infoLabel->setVisible(!pathValid || !directoryValid);
}
void GitLabCloneDialog::cloneProject()
{
Core::IVersionControl *vc = Core::VcsManager::versionControl(Utils::Id::fromString("G.Git"));
QTC_ASSERT(vc, return);
const QStringList extraArgs = m_submodulesCB->isChecked() ? QStringList{ "--recursive" }
: QStringList{};
m_command = vc->createInitialCheckoutCommand(m_repositoryCB->currentText(),
m_pathChooser->absoluteFilePath(),
m_directoryLE->text(), extraArgs);
const Utils::FilePath workingDirectory = m_pathChooser->absoluteFilePath();
m_command->setProgressiveOutput(true);
connect(m_command, &Utils::ShellCommand::stdOutText, this, [this](const QString &text) {
m_cloneOutput->appendPlainText(text);
});
connect(m_command, &Utils::ShellCommand::stdErrText, this, [this](const QString &text) {
m_cloneOutput->appendPlainText(text);
});
connect(m_command, &Utils::ShellCommand::finished, this, &GitLabCloneDialog::cloneFinished);
QApplication::setOverrideCursor(Qt::WaitCursor);
m_cloneOutput->clear();
m_cloneButton->setEnabled(false);
m_pathChooser->setReadOnly(true);
m_directoryLE->setReadOnly(true);
m_commandRunning = true;
m_command->execute();
}
void GitLabCloneDialog::cancel()
{
if (m_commandRunning) {
m_cloneOutput->appendPlainText(tr("User canceled process."));
m_cancelButton->setEnabled(false);
m_command->cancel(); // FIXME does not cancel the git processes... QTCREATORBUG-27567
} else {
reject();
}
}
static Utils::FilePaths scanDirectoryForFiles(const Utils::FilePath &directory)
{
Utils::FilePaths result;
const Utils::FilePaths entries = directory.dirEntries(QDir::AllEntries | QDir::NoDotAndDotDot);
for (const Utils::FilePath &entry : entries) {
if (entry.isDir())
result.append(scanDirectoryForFiles(entry));
else
result.append(entry);
}
return result;
}
void GitLabCloneDialog::cloneFinished(bool ok, int exitCode)
{
const bool success = (ok && exitCode == 0);
m_commandRunning = false;
delete m_command;
m_command = nullptr;
const QString emptyLine("\n\n");
m_cloneOutput->appendPlainText(emptyLine);
QApplication::restoreOverrideCursor();
if (success) {
m_cloneOutput->appendPlainText(tr("Cloning succeeded.") + emptyLine);
m_cloneButton->setEnabled(false);
const Utils::FilePath base = m_pathChooser->filePath().pathAppended(m_directoryLE->text());
Utils::FilePaths filesWeMayOpen
= Utils::filtered(scanDirectoryForFiles(base), [](const Utils::FilePath &f) {
return ProjectExplorer::ProjectManager::canOpenProjectForMimeType(
Utils::mimeTypeForFile(f));
});
// limit the files to the most top-level item(s)
int minimum = std::numeric_limits<int>::max();
for (const Utils::FilePath &f : filesWeMayOpen) {
int parentCount = f.toString().count('/');
if (parentCount < minimum)
minimum = parentCount;
}
filesWeMayOpen = Utils::filtered(filesWeMayOpen, [minimum](const Utils::FilePath &f) {
return f.toString().count('/') == minimum;
});
hide(); // avoid to many dialogs.. FIXME: maybe change to some wizard approach?
if (filesWeMayOpen.isEmpty()) {
QMessageBox::warning(this, tr("Warning"),
tr("Cloned project does not have a project file that can be "
"opened. Try importing the project as a generic project."));
accept();
} else {
const QStringList pFiles = Utils::transform(filesWeMayOpen,
[base](const Utils::FilePath &f) {
return f.relativePath(base).toUserOutput();
});
bool ok = false;
const QString fileToOpen
= QInputDialog::getItem(this, tr("Open Project"),
tr("Choose the project file to be opened."),
pFiles, 0, false, &ok);
accept();
if (ok && !fileToOpen.isEmpty())
ProjectExplorer::ProjectExplorerPlugin::openProject(base.pathAppended(fileToOpen));
}
} else {
m_cloneOutput->appendPlainText(tr("Cloning failed.") + emptyLine);
const Utils::FilePath fullPath = m_pathChooser->filePath()
.pathAppended(m_directoryLE->text());
fullPath.removeRecursively();
m_cloneButton->setEnabled(true);
m_cancelButton->setEnabled(true);
m_pathChooser->setReadOnly(false);
m_directoryLE->setReadOnly(false);
m_directoryLE->validate();
}
}
} // namespace GitLab

View File

@@ -0,0 +1,74 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <QCoreApplication>
#include <QDialog>
QT_BEGIN_NAMESPACE
class QCheckBox;
class QComboBox;
class QPlainTextEdit;
class QPushButton;
QT_END_NAMESPACE
namespace Core { class ShellCommand; }
namespace Utils {
class FancyLineEdit;
class InfoLabel;
class PathChooser;
}
namespace GitLab {
class Project;
class GitLabCloneDialog : public QDialog
{
Q_DECLARE_TR_FUNCTIONS(GitLab::GitLabCloneDialog)
public:
explicit GitLabCloneDialog(const Project &project, QWidget *parent = nullptr);
private:
void updateUi();
void cloneProject();
void cancel();
void cloneFinished(bool ok, int exitCode);
QComboBox * m_repositoryCB = nullptr;
QCheckBox *m_submodulesCB = nullptr;
QPushButton *m_cloneButton = nullptr;
QPushButton *m_cancelButton = nullptr;
QPlainTextEdit *m_cloneOutput = nullptr;
Utils::PathChooser *m_pathChooser = nullptr;
Utils::FancyLineEdit *m_directoryLE = nullptr;
Utils::InfoLabel *m_infoLabel = nullptr;
Core::ShellCommand *m_command = nullptr;
bool m_commandRunning = false;
};
} // namespace GitLab

View File

@@ -0,0 +1,294 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "gitlabdialog.h"
#include "gitlabclonedialog.h"
#include "gitlabparameters.h"
#include "gitlabplugin.h"
#include "gitlabprojectsettings.h"
#include <projectexplorer/session.h>
#include <texteditor/fontsettings.h>
#include <texteditor/texteditorsettings.h>
#include <utils/listmodel.h>
#include <utils/qtcassert.h>
#include <utils/utilsicons.h>
#include <QKeyEvent>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QRegularExpression>
#include <QSyntaxHighlighter>
namespace GitLab {
GitLabDialog::GitLabDialog(QWidget *parent)
: QDialog(parent)
, m_lastTreeViewQuery(Query::NoQuery)
{
m_ui.setupUi(this);
m_clonePB = new QPushButton(Utils::Icons::DOWNLOAD.icon(), tr("Clone..."), this);
m_ui.buttonBox->addButton(m_clonePB, QDialogButtonBox::ActionRole);
m_clonePB->setEnabled(false);
updateRemotes();
connect(m_ui.remoteCB, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &GitLabDialog::requestMainViewUpdate);
connect(m_ui.searchLE, &QLineEdit::returnPressed, this, &GitLabDialog::querySearch);
connect(m_ui.searchPB, &QPushButton::clicked, this, &GitLabDialog::querySearch);
connect(m_clonePB, &QPushButton::clicked, this, &GitLabDialog::cloneSelected);
connect(m_ui.firstTB, &QToolButton::clicked, this, &GitLabDialog::queryFirstPage);
connect(m_ui.previousTB, &QToolButton::clicked, this, &GitLabDialog::queryPreviousPage);
connect(m_ui.nextTB, &QToolButton::clicked, this, &GitLabDialog::queryNextPage);
connect(m_ui.lastTB, &QToolButton::clicked, this, &GitLabDialog::queryLastPage);
requestMainViewUpdate();
}
void GitLabDialog::resetTreeView(QTreeView *treeView, QAbstractItemModel *model)
{
auto oldModel = treeView->model();
treeView->setModel(model);
delete oldModel;
if (model) {
connect(treeView->selectionModel(), &QItemSelectionModel::selectionChanged,
this, [this](const QItemSelection &selected, const QItemSelection &) {
m_clonePB->setEnabled(!selected.isEmpty());
});
m_clonePB->setEnabled(!treeView->selectionModel()->selectedIndexes().isEmpty());
}
}
void GitLabDialog::updateRemotes()
{
m_ui.remoteCB->clear();
const GitLabParameters *global = GitLabPlugin::globalParameters();
for (const GitLabServer &server : qAsConst(global->gitLabServers))
m_ui.remoteCB->addItem(server.displayString(), QVariant::fromValue(server));
m_ui.remoteCB->setCurrentIndex(m_ui.remoteCB->findData(
QVariant::fromValue(global->currentDefaultServer())));
}
void GitLabDialog::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)
return;
QDialog::keyPressEvent(event);
}
void GitLabDialog::requestMainViewUpdate()
{
m_lastPageInformation = PageInformation();
m_lastTreeViewQuery = Query(Query::NoQuery);
m_ui.mainLabel->setText({});
m_ui.detailsLabel->setText({});
m_ui.treeViewTitle->setText({});
m_ui.searchLE->setText({});
resetTreeView(m_ui.treeView, nullptr);
updatePageButtons();
bool linked = false;
m_currentServerId = Utils::Id();
if (auto project = ProjectExplorer::SessionManager::startupProject()) {
GitLabProjectSettings *projSettings = GitLabPlugin::projectSettings(project);
if (projSettings->isLinked()) {
m_currentServerId = projSettings->currentServer();
linked = true;
}
}
if (!m_currentServerId.isValid())
m_currentServerId = m_ui.remoteCB->currentData().value<GitLabServer>().id;
if (m_currentServerId.isValid()) {
const GitLabParameters *global = GitLabPlugin::globalParameters();
const GitLabServer server = global->serverForId(m_currentServerId);
m_ui.remoteCB->setCurrentIndex(m_ui.remoteCB->findData(QVariant::fromValue(server)));
}
m_ui.remoteCB->setEnabled(!linked);
const Query query(Query::User);
QueryRunner *runner = new QueryRunner(query, m_currentServerId, this);
connect(runner, &QueryRunner::resultRetrieved, this, [this](const QByteArray &result) {
handleUser(ResultParser::parseUser(result));
});
connect(runner, &QueryRunner::finished, [runner]() { runner->deleteLater(); });
runner->start();
}
void GitLabDialog::updatePageButtons()
{
if (m_lastPageInformation.currentPage == -1) {
m_ui.currentPage->setVisible(false);
m_ui.firstTB->setVisible(false);
m_ui.lastTB->setVisible(false);
m_ui.previousTB->setVisible(false);
m_ui.nextTB->setVisible(false);
} else {
m_ui.currentPage->setText(QString::number(m_lastPageInformation.currentPage));
m_ui.currentPage->setVisible(true);
m_ui.firstTB->setVisible(true);
m_ui.lastTB->setVisible(true);
}
if (m_lastPageInformation.currentPage > 1) {
m_ui.firstTB->setEnabled(true);
m_ui.previousTB->setText(QString::number(m_lastPageInformation.currentPage - 1));
m_ui.previousTB->setVisible(true);
} else {
m_ui.firstTB->setEnabled(false);
m_ui.previousTB->setVisible(false);
}
if (m_lastPageInformation.currentPage < m_lastPageInformation.totalPages) {
m_ui.lastTB->setEnabled(true);
m_ui.nextTB->setText(QString::number(m_lastPageInformation.currentPage + 1));
m_ui.nextTB->setVisible(true);
} else {
m_ui.lastTB->setEnabled(false);
m_ui.nextTB->setVisible(false);
}
}
void GitLabDialog::queryFirstPage()
{
QTC_ASSERT(m_lastTreeViewQuery.type() != Query::NoQuery, return);
QTC_ASSERT(m_lastPageInformation.currentPage != -1, return);
m_lastTreeViewQuery.setPageParameter(1);
fetchProjects();
}
void GitLabDialog::queryPreviousPage()
{
QTC_ASSERT(m_lastTreeViewQuery.type() != Query::NoQuery, return);
QTC_ASSERT(m_lastPageInformation.currentPage != -1, return);
m_lastTreeViewQuery.setPageParameter(m_lastPageInformation.currentPage - 1);
fetchProjects();
}
void GitLabDialog::queryNextPage()
{
QTC_ASSERT(m_lastTreeViewQuery.type() != Query::NoQuery, return);
QTC_ASSERT(m_lastPageInformation.currentPage != -1, return);
m_lastTreeViewQuery.setPageParameter(m_lastPageInformation.currentPage + 1);
fetchProjects();
}
void GitLabDialog::queryLastPage()
{
QTC_ASSERT(m_lastTreeViewQuery.type() != Query::NoQuery, return);
QTC_ASSERT(m_lastPageInformation.currentPage != -1, return);
m_lastTreeViewQuery.setPageParameter(m_lastPageInformation.totalPages);
fetchProjects();
}
void GitLabDialog::querySearch()
{
QTC_ASSERT(m_lastTreeViewQuery.type() != Query::NoQuery, return);
m_lastTreeViewQuery.setPageParameter(-1);
m_lastTreeViewQuery.setAdditionalParameters({"search=" + m_ui.searchLE->text()});
fetchProjects();
}
void GitLabDialog::handleUser(const User &user)
{
m_lastPageInformation = {};
m_currentUserId = user.id;
if (!user.error.message.isEmpty()) {
// TODO
if (user.error.code == 1) {
m_ui.mainLabel->setText(tr("Not logged in."));
m_ui.detailsLabel->setText(tr("Insufficient access token."));
m_ui.detailsLabel->setToolTip(user.error.message + QLatin1Char('\n')
+ tr("Permission scope read_api or api needed."));
updatePageButtons();
m_ui.treeViewTitle->setText(tr("Projects (%1)").arg(0));
return;
}
}
if (user.id != -1) {
if (user.bot) {
m_ui.mainLabel->setText(tr("Using project access token."));
m_ui.detailsLabel->setText({});
} else {
m_ui.mainLabel->setText(tr("Logged in as %1").arg(user.name));
m_ui.detailsLabel->setText(tr("Id: %1 (%2)").arg(user.id).arg(user.email));
}
m_ui.detailsLabel->setToolTip({});
} else {
m_ui.mainLabel->setText(tr("Not logged in."));
m_ui.detailsLabel->setText({});
m_ui.detailsLabel->setToolTip({});
}
m_lastTreeViewQuery = Query(Query::Projects);
fetchProjects();
}
void GitLabDialog::handleProjects(const Projects &projects)
{
Utils::ListModel<Project *> *listModel = new Utils::ListModel<Project *>(this);
for (const Project &project : projects.projects)
listModel->appendItem(new Project(project));
// TODO use a real model / delegate..?
listModel->setDataAccessor([](Project *data, int /*column*/, int role) -> QVariant {
if (role == Qt::DisplayRole)
return QString(data->displayName + " (" + data->visibility + ')');
if (role == Qt::UserRole)
return QVariant::fromValue(*data);
return QVariant();
});
resetTreeView(m_ui.treeView, listModel);
int count = projects.error.message.isEmpty() ? projects.pageInfo.total : 0;
m_ui.treeViewTitle->setText(tr("Projects (%1)").arg(count));
m_lastPageInformation = projects.pageInfo;
updatePageButtons();
}
void GitLabDialog::fetchProjects()
{
QueryRunner *runner = new QueryRunner(m_lastTreeViewQuery, m_currentServerId, this);
connect(runner, &QueryRunner::resultRetrieved, this, [this](const QByteArray &result) {
handleProjects(ResultParser::parseProjects(result));
});
connect(runner, &QueryRunner::finished, [runner]() { runner->deleteLater(); });
runner->start();
}
void GitLabDialog::cloneSelected()
{
const QModelIndexList indexes = m_ui.treeView->selectionModel()->selectedIndexes();
QTC_ASSERT(indexes.size() == 1, return);
const Project project = indexes.first().data(Qt::UserRole).value<Project>();
QTC_ASSERT(!project.sshUrl.isEmpty() && !project.httpUrl.isEmpty(), return);
GitLabCloneDialog dialog(project, this);
if (dialog.exec() == QDialog::Accepted)
reject();
}
} // namespace GitLab

View File

@@ -1,6 +1,6 @@
/**************************************************************************** /****************************************************************************
** **
** Copyright (C) 2016 The Qt Company Ltd. ** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/ ** Contact: https://www.qt.io/licensing/
** **
** This file is part of Qt Creator. ** This file is part of Qt Creator.
@@ -22,49 +22,60 @@
** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
** **
****************************************************************************/ ****************************************************************************/
#pragma once #pragma once
#include "abstractremotelinuxdeployservice.h" #include "ui_gitlabdialog.h"
#include "remotelinux_export.h"
namespace Utils { #include "queryrunner.h"
class FilePath; #include "resultparser.h"
class ProcessResultData;
}
namespace RemoteLinux { #include <utils/filepath.h>
class AbstractRemoteLinuxPackageInstaller; #include <utils/id.h>
namespace Internal { class AbstractUploadAndInstallPackageServicePrivate; } #include <QDialog>
class REMOTELINUX_EXPORT AbstractUploadAndInstallPackageService : public AbstractRemoteLinuxDeployService QT_BEGIN_NAMESPACE
class QPushButton;
QT_END_NAMESPACE
namespace GitLab {
class GitLabParameters;
class GitLabDialog : public QDialog
{ {
Q_OBJECT Q_OBJECT
public: public:
void setPackageFilePath(const Utils::FilePath &filePath); explicit GitLabDialog(QWidget *parent = nullptr);
void updateRemotes();
protected: protected:
AbstractUploadAndInstallPackageService(); void keyPressEvent(QKeyEvent *event) override;
~AbstractUploadAndInstallPackageService() override;
private: private:
void handleUploadFinished(const Utils::ProcessResultData &resultData); void resetTreeView(QTreeView *treeView, QAbstractItemModel *model);
void handleInstallationFinished(const QString &errorMsg); void requestMainViewUpdate();
void updatePageButtons();
virtual AbstractRemoteLinuxPackageInstaller *packageInstaller() const = 0; void queryFirstPage();
virtual QString uploadDir() const; // Defaults to remote user's home directory. void queryPreviousPage();
void queryNextPage();
void queryLastPage();
void querySearch();
void continuePageUpdate();
bool isDeploymentNecessary() const override; void handleUser(const User &user);
void doDeviceSetup() override; void handleProjects(const Projects &projects);
void stopDeviceSetup() override; void fetchProjects();
void doDeploy() override;
void stopDeployment() override;
void setFinished(); void cloneSelected();
Internal::AbstractUploadAndInstallPackageServicePrivate * const d; Ui::GitLabDialog m_ui;
QPushButton *m_clonePB = nullptr;
Utils::Id m_currentServerId;
Query m_lastTreeViewQuery;
PageInformation m_lastPageInformation;
int m_currentUserId = -1;
}; };
} // namespace RemoteLinux } // namespace GitLab

View File

@@ -0,0 +1,268 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>GitLab::GitLabDialog</class>
<widget class="QDialog" name="GitLab::GitLabDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>665</width>
<height>530</height>
</rect>
</property>
<property name="windowTitle">
<string>GitLab</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_0">
<item>
<layout class="QVBoxLayout" name="verticalLayout_1">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_0">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="mainLabel">
<property name="text">
<string>Login</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="detailsLabel">
<property name="text">
<string>Details</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer_0">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Remote:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="remoteCB">
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="treeViewTitle">
<property name="text">
<string>Projects</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_1">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLineEdit" name="searchLE">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="placeholderText">
<string>Search</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="searchPB">
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QTreeView" name="treeView">
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<property name="itemsExpandable">
<bool>false</bool>
</property>
<property name="expandsOnDoubleClick">
<bool>false</bool>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>200</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QToolButton" name="firstTB">
<property name="text">
<string notr="true">|&lt;</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="previousTB">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="currentPage">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="nextTB">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="lastTB">
<property name="text">
<string notr="true">&gt;|</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>200</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>GitLab::GitLabDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -25,23 +25,52 @@
#include "gitlabplugin.h" #include "gitlabplugin.h"
#include "gitlabdialog.h"
#include "gitlaboptionspage.h" #include "gitlaboptionspage.h"
#include "gitlabparameters.h" #include "gitlabparameters.h"
#include "gitlabprojectsettings.h" #include "gitlabprojectsettings.h"
#include "queryrunner.h"
#include "resultparser.h"
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <git/gitplugin.h>
#include <projectexplorer/project.h> #include <projectexplorer/project.h>
#include <projectexplorer/projectpanelfactory.h> #include <projectexplorer/projectpanelfactory.h>
#include <projectexplorer/session.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <vcsbase/vcsoutputwindow.h>
#include <QAction>
#include <QMessageBox>
#include <QPointer>
#include <QTimer>
namespace GitLab { namespace GitLab {
namespace Constants {
const char GITLAB_OPEN_VIEW[] = "GitLab.OpenView";
} // namespace Constants
class GitLabPluginPrivate class GitLabPluginPrivate : public QObject
{ {
public: public:
GitLabParameters parameters; GitLabParameters parameters;
GitLabOptionsPage optionsPage{&parameters}; GitLabOptionsPage optionsPage{&parameters};
QHash<ProjectExplorer::Project *, GitLabProjectSettings *> projectSettings; QHash<ProjectExplorer::Project *, GitLabProjectSettings *> projectSettings;
QPointer<GitLabDialog> dialog;
QTimer notificationTimer;
QString projectName;
Utils::Id serverId;
bool runningQuery = false;
void setupNotificationTimer();
void fetchEvents();
void fetchUser();
void createAndSendEventsRequest(const QDateTime timeStamp, int page = -1);
void handleUser(const User &user);
void handleEvents(const Events &events, const QDateTime &timeStamp);
}; };
static GitLabPluginPrivate *dd = nullptr; static GitLabPluginPrivate *dd = nullptr;
@@ -71,9 +100,180 @@ bool GitLabPlugin::initialize(const QStringList & /*arguments*/, QString * /*err
return new GitLabProjectSettingsWidget(project); return new GitLabProjectSettingsWidget(project);
}); });
ProjectExplorer::ProjectPanelFactory::registerFactory(panelFactory); ProjectExplorer::ProjectPanelFactory::registerFactory(panelFactory);
QAction *openViewAction = new QAction(tr("GitLab..."), this);
auto gitlabCommand = Core::ActionManager::registerAction(openViewAction,
Constants::GITLAB_OPEN_VIEW);
connect(openViewAction, &QAction::triggered, this, &GitLabPlugin::openView);
Core::ActionContainer *ac = Core::ActionManager::actionContainer(Core::Constants::M_TOOLS);
ac->addAction(gitlabCommand);
connect(&dd->optionsPage, &GitLabOptionsPage::settingsChanged, this, [this] {
if (dd->dialog)
dd->dialog->updateRemotes();
});
connect(ProjectExplorer::SessionManager::instance(),
&ProjectExplorer::SessionManager::startupProjectChanged,
this, &GitLabPlugin::onStartupProjectChanged);
return true; return true;
} }
void GitLabPlugin::openView()
{
if (dd->dialog.isNull()) {
while (!dd->parameters.isValid()) {
QMessageBox::warning(Core::ICore::dialogParent(), tr("Error"),
tr("Invalid GitLab configuration. For a fully functional "
"configuration, you need to set up host name or address and "
"an access token. Providing the path to curl is mandatory."));
if (!Core::ICore::showOptionsDialog("GitLab"))
return;
}
GitLabDialog *gitlabD = new GitLabDialog(Core::ICore::dialogParent());
gitlabD->setModal(true);
Core::ICore::registerWindow(gitlabD, Core::Context("Git.GitLab"));
dd->dialog = gitlabD;
}
const Qt::WindowStates state = dd->dialog->windowState();
if (state & Qt::WindowMinimized)
dd->dialog->setWindowState(state & ~Qt::WindowMinimized);
dd->dialog->show();
dd->dialog->raise();
}
void GitLabPlugin::onStartupProjectChanged()
{
QTC_ASSERT(dd, return);
disconnect(&dd->notificationTimer);
ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
if (!project) {
dd->notificationTimer.stop();
return;
}
const GitLabProjectSettings *projSettings = projectSettings(project);
if (!projSettings->isLinked()) {
dd->notificationTimer.stop();
return;
}
dd->fetchEvents();
dd->setupNotificationTimer();
}
void GitLabPluginPrivate::setupNotificationTimer()
{
// make interval configurable?
notificationTimer.setInterval(15 * 60 * 1000);
QObject::connect(&notificationTimer, &QTimer::timeout, this, &GitLabPluginPrivate::fetchEvents);
notificationTimer.start();
}
void GitLabPluginPrivate::fetchEvents()
{
ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
QTC_ASSERT(project, return);
if (runningQuery)
return;
const GitLabProjectSettings *projSettings = GitLabPlugin::projectSettings(project);
projectName = projSettings->currentProject();
serverId = projSettings->currentServer();
const QDateTime lastRequest = projSettings->lastRequest();
if (!lastRequest.isValid()) { // we haven't queried events for this project yet
fetchUser();
return;
}
createAndSendEventsRequest(lastRequest);
}
void GitLabPluginPrivate::fetchUser()
{
if (runningQuery)
return;
const Query query(Query::User);
QueryRunner *runner = new QueryRunner(query, serverId, this);
QObject::connect(runner, &QueryRunner::resultRetrieved, this, [this](const QByteArray &result) {
handleUser(ResultParser::parseUser(result));
});
QObject::connect(runner, &QueryRunner::finished, [runner]() { runner->deleteLater(); });
runningQuery = true;
runner->start();
}
void GitLabPluginPrivate::createAndSendEventsRequest(const QDateTime timeStamp, int page)
{
if (runningQuery)
return;
Query query(Query::Events, {projectName});
QStringList additional = {"sort=asc"};
QDateTime after = timeStamp.addDays(-1);
additional.append(QLatin1String("after=%1").arg(after.toString("yyyy-MM-dd")));
query.setAdditionalParameters(additional);
if (page > 1)
query.setPageParameter(page);
QueryRunner *runner = new QueryRunner(query, serverId, this);
QObject::connect(runner, &QueryRunner::resultRetrieved, this,
[this, timeStamp](const QByteArray &result) {
handleEvents(ResultParser::parseEvents(result), timeStamp);
});
QObject::connect(runner, &QueryRunner::finished, [runner]() { runner->deleteLater(); });
runningQuery = true;
runner->start();
}
void GitLabPluginPrivate::handleUser(const User &user)
{
runningQuery = false;
QTC_ASSERT(user.error.message.isEmpty(), return);
const QDateTime timeStamp = QDateTime::fromString(user.lastLogin, Qt::ISODateWithMs);
createAndSendEventsRequest(timeStamp);
}
void GitLabPluginPrivate::handleEvents(const Events &events, const QDateTime &timeStamp)
{
runningQuery = false;
ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
QTC_ASSERT(project, return);
GitLabProjectSettings *projSettings = GitLabPlugin::projectSettings(project);
QTC_ASSERT(projSettings->currentProject() == projectName, return);
if (!projSettings->isLinked()) // link state has changed meanwhile - ignore the request
return;
if (!events.error.message.isEmpty()) {
VcsBase::VcsOutputWindow::appendError("GitLab: Error while fetching events. "
+ events.error.message + '\n');
return;
}
QDateTime lastTimeStamp;
for (const Event &event : events.events) {
const QDateTime eventTimeStamp = QDateTime::fromString(event.timeStamp, Qt::ISODateWithMs);
if (!timeStamp.isValid() || timeStamp < eventTimeStamp) {
VcsBase::VcsOutputWindow::appendMessage("GitLab: " + event.toMessage());
if (!lastTimeStamp.isValid() || lastTimeStamp < eventTimeStamp)
lastTimeStamp = eventTimeStamp;
}
}
if (lastTimeStamp.isValid()) {
if (auto outputWindow = VcsBase::VcsOutputWindow::instance())
outputWindow->flash();
projSettings->setLastRequest(lastTimeStamp);
}
if (events.pageInfo.currentPage < events.pageInfo.totalPages)
createAndSendEventsRequest(timeStamp, events.pageInfo.currentPage + 1);
}
QList<GitLabServer> GitLabPlugin::allGitLabServers() QList<GitLabServer> GitLabPlugin::allGitLabServers()
{ {
QTC_ASSERT(dd, return {}); QTC_ASSERT(dd, return {});
@@ -107,4 +307,28 @@ GitLabOptionsPage *GitLabPlugin::optionsPage()
return &dd->optionsPage; return &dd->optionsPage;
} }
void GitLabPlugin::linkedStateChanged(bool enabled)
{
QTC_ASSERT(dd, return);
ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
if (project) {
const GitLabProjectSettings *pSettings = projectSettings(project);
dd->serverId = pSettings->currentServer();
dd->projectName = pSettings->currentProject();
} else {
dd->serverId = Utils::Id();
dd->projectName = QString();
}
if (enabled) {
dd->fetchEvents();
dd->setupNotificationTimer();
} else {
QObject::disconnect(&dd->notificationTimer, &QTimer::timeout,
dd, &GitLabPluginPrivate::fetchEvents);
dd->notificationTimer.stop();
}
}
} // namespace GitLab } // namespace GitLab

View File

@@ -33,6 +33,7 @@ namespace ProjectExplorer { class Project; }
namespace GitLab { namespace GitLab {
class Events;
class GitLabProjectSettings; class GitLabProjectSettings;
class GitLabOptionsPage; class GitLabOptionsPage;
@@ -52,6 +53,11 @@ public:
static GitLabParameters *globalParameters(); static GitLabParameters *globalParameters();
static GitLabProjectSettings *projectSettings(ProjectExplorer::Project *project); static GitLabProjectSettings *projectSettings(ProjectExplorer::Project *project);
static GitLabOptionsPage *optionsPage(); static GitLabOptionsPage *optionsPage();
static void linkedStateChanged(bool enabled);
private:
void openView();
void onStartupProjectChanged();
}; };
} // namespace GitLab } // namespace GitLab

View File

@@ -49,6 +49,7 @@ namespace GitLab {
const char PSK_LINKED_ID[] = "GitLab.LinkedId"; const char PSK_LINKED_ID[] = "GitLab.LinkedId";
const char PSK_SERVER[] = "GitLab.Server"; const char PSK_SERVER[] = "GitLab.Server";
const char PSK_PROJECT[] = "GitLab.Project"; const char PSK_PROJECT[] = "GitLab.Project";
const char PSK_LAST_REQ[] = "GitLab.LastRequest";
static QString accessLevelString(int accessLevel) static QString accessLevelString(int accessLevel)
{ {
@@ -106,6 +107,7 @@ void GitLabProjectSettings::load()
m_id = Utils::Id::fromSetting(m_project->namedSettings(PSK_LINKED_ID)); m_id = Utils::Id::fromSetting(m_project->namedSettings(PSK_LINKED_ID));
m_host = m_project->namedSettings(PSK_SERVER).toString(); m_host = m_project->namedSettings(PSK_SERVER).toString();
m_currentProject = m_project->namedSettings(PSK_PROJECT).toString(); m_currentProject = m_project->namedSettings(PSK_PROJECT).toString();
m_lastRequest = m_project->namedSettings(PSK_LAST_REQ).toDateTime();
// may still be wrong, but we avoid an additional request by just doing sanity check here // may still be wrong, but we avoid an additional request by just doing sanity check here
if (!m_id.isValid() || m_host.isEmpty()) if (!m_id.isValid() || m_host.isEmpty())
@@ -124,6 +126,7 @@ void GitLabProjectSettings::save()
m_project->setNamedSettings(PSK_SERVER, QString()); m_project->setNamedSettings(PSK_SERVER, QString());
} }
m_project->setNamedSettings(PSK_PROJECT, m_currentProject); m_project->setNamedSettings(PSK_PROJECT, m_currentProject);
m_project->setNamedSettings(PSK_LAST_REQ, m_lastRequest);
} }
GitLabProjectSettingsWidget::GitLabProjectSettingsWidget(ProjectExplorer::Project *project, GitLabProjectSettingsWidget::GitLabProjectSettingsWidget(ProjectExplorer::Project *project,
@@ -184,6 +187,7 @@ void GitLabProjectSettingsWidget::unlink()
m_projectSettings->setLinked(false); m_projectSettings->setLinked(false);
m_projectSettings->setCurrentProject({}); m_projectSettings->setCurrentProject({});
updateEnabledStates(); updateEnabledStates();
GitLabPlugin::linkedStateChanged(false);
} }
void GitLabProjectSettingsWidget::checkConnection(CheckMode mode) void GitLabProjectSettingsWidget::checkConnection(CheckMode mode)
@@ -245,6 +249,7 @@ void GitLabProjectSettingsWidget::onConnectionChecked(const Project &project,
m_projectSettings->setCurrentServerHost(remote); m_projectSettings->setCurrentServerHost(remote);
m_projectSettings->setLinked(true); m_projectSettings->setLinked(true);
m_projectSettings->setCurrentProject(projectName); m_projectSettings->setCurrentProject(projectName);
GitLabPlugin::linkedStateChanged(true);
} }
updateEnabledStates(); updateEnabledStates();
} }
@@ -282,8 +287,10 @@ void GitLabProjectSettingsWidget::updateUi()
m_hostCB->setCurrentIndex(m_hostCB->findData(QVariant::fromValue(serverHost))); m_hostCB->setCurrentIndex(m_hostCB->findData(QVariant::fromValue(serverHost)));
m_linkedGitLabServer->setCurrentIndex( m_linkedGitLabServer->setCurrentIndex(
m_linkedGitLabServer->findData(QVariant::fromValue(server))); m_linkedGitLabServer->findData(QVariant::fromValue(server)));
GitLabPlugin::linkedStateChanged(true);
} else { } else {
m_projectSettings->setLinked(false); m_projectSettings->setLinked(false);
GitLabPlugin::linkedStateChanged(false);
} }
} }
updateEnabledStates(); updateEnabledStates();
@@ -294,6 +301,7 @@ void GitLabProjectSettingsWidget::updateEnabledStates()
const bool isGitRepository = m_hostCB->count() > 0; const bool isGitRepository = m_hostCB->count() > 0;
const bool hasGitLabServers = m_linkedGitLabServer->count(); const bool hasGitLabServers = m_linkedGitLabServer->count();
const bool linked = m_projectSettings->isLinked(); const bool linked = m_projectSettings->isLinked();
m_linkedGitLabServer->setEnabled(isGitRepository && !linked); m_linkedGitLabServer->setEnabled(isGitRepository && !linked);
m_hostCB->setEnabled(isGitRepository && !linked); m_hostCB->setEnabled(isGitRepository && !linked);
m_linkWithGitLab->setEnabled(isGitRepository && !linked && hasGitLabServers); m_linkWithGitLab->setEnabled(isGitRepository && !linked && hasGitLabServers);

View File

@@ -28,6 +28,7 @@
#include <projectexplorer/projectsettingswidget.h> #include <projectexplorer/projectsettingswidget.h>
#include <utils/id.h> #include <utils/id.h>
#include <QDateTime>
#include <QObject> #include <QObject>
#include <QWidget> #include <QWidget>
@@ -59,9 +60,12 @@ public:
QString currentProject() const { return m_currentProject; } QString currentProject() const { return m_currentProject; }
bool isLinked() const { return m_linked; } bool isLinked() const { return m_linked; }
void setLinked(bool linked); void setLinked(bool linked);
QDateTime lastRequest() const { return m_lastRequest; }
void setLastRequest(const QDateTime &lastRequest) { m_lastRequest = lastRequest; }
ProjectExplorer::Project *project() const { return m_project; } ProjectExplorer::Project *project() const { return m_project; }
static std::tuple<QString, QString, int> remotePartsFromRemote(const QString &remote); static std::tuple<QString, QString, int> remotePartsFromRemote(const QString &remote);
private: private:
void load(); void load();
void save(); void save();
@@ -69,6 +73,7 @@ private:
ProjectExplorer::Project *m_project = nullptr; ProjectExplorer::Project *m_project = nullptr;
QString m_host; QString m_host;
Utils::Id m_id; Utils::Id m_id;
QDateTime m_lastRequest;
QString m_currentProject; QString m_currentProject;
bool m_linked = false; bool m_linked = false;
}; };

View File

@@ -41,6 +41,9 @@ namespace GitLab {
const char API_PREFIX[] = "/api/v4"; const char API_PREFIX[] = "/api/v4";
const char QUERY_PROJECT[] = "/projects/%1"; const char QUERY_PROJECT[] = "/projects/%1";
const char QUERY_PROJECTS[] = "/projects?simple=true";
const char QUERY_USER[] = "/user";
const char QUERY_EVENTS[] = "/projects/%1/events";
Query::Query(Type type, const QStringList &parameter) Query::Query(Type type, const QStringList &parameter)
: m_type(type) : m_type(type)
@@ -48,6 +51,21 @@ Query::Query(Type type, const QStringList &parameter)
{ {
} }
void Query::setPageParameter(int page)
{
m_pageParameter = page;
}
void Query::setAdditionalParameters(const QStringList &additional)
{
m_additionalParameters = additional;
}
bool Query::hasPaginatedResults() const
{
return m_type == Query::Projects || m_type == Query::Events;
}
QString Query::toString() const QString Query::toString() const
{ {
QString query = API_PREFIX; QString query = API_PREFIX;
@@ -59,6 +77,25 @@ QString Query::toString() const
query += QLatin1String(QUERY_PROJECT).arg(QLatin1String( query += QLatin1String(QUERY_PROJECT).arg(QLatin1String(
QUrl::toPercentEncoding(m_parameter.at(0)))); QUrl::toPercentEncoding(m_parameter.at(0))));
break; break;
case Query::Projects:
query += QLatin1String(QUERY_PROJECTS);
break;
case Query::User:
query += QUERY_USER;
break;
case Query::Events:
QTC_ASSERT(!m_parameter.isEmpty(), return {});
query += QLatin1String(QUERY_EVENTS).arg(QLatin1String(
QUrl::toPercentEncoding(m_parameter.at(0))));
break;
}
if (m_pageParameter > 0) {
query.append(m_type == Query::Projects ? '&' : '?');
query.append("page=").append(QString::number(m_pageParameter));
}
if (!m_additionalParameters.isEmpty()) {
query.append((m_type == Query::Projects || m_pageParameter > 0) ? '&' : '?');
query.append(m_additionalParameters.join('&'));
} }
return query; return query;
} }
@@ -69,6 +106,9 @@ QueryRunner::QueryRunner(const Query &query, const Utils::Id &id, QObject *paren
const GitLabParameters *p = GitLabPlugin::globalParameters(); const GitLabParameters *p = GitLabPlugin::globalParameters();
const auto server = p->serverForId(id); const auto server = p->serverForId(id);
QStringList args = server.curlArguments(); QStringList args = server.curlArguments();
m_paginated = query.hasPaginatedResults();
if (m_paginated)
args << "-i";
if (!server.token.isEmpty()) if (!server.token.isEmpty())
args << "--header" << "PRIVATE-TOKEN: " + server.token; args << "--header" << "PRIVATE-TOKEN: " + server.token;
QString url = "https://" + server.host; QString url = "https://" + server.host;

View File

@@ -38,15 +38,24 @@ class Query
public: public:
enum Type { enum Type {
NoQuery, NoQuery,
Project User,
Project,
Projects,
Events
}; };
explicit Query(Type type, const QStringList &parameters = {}); explicit Query(Type type, const QStringList &parameters = {});
void setPageParameter(int page);
void setAdditionalParameters(const QStringList &additional);
bool hasPaginatedResults() const;
Type type() const { return m_type; }
QString toString() const; QString toString() const;
private: private:
Type m_type = NoQuery; Type m_type = NoQuery;
QStringList m_parameter; QStringList m_parameter;
QStringList m_additionalParameters;
int m_pageParameter = -1;
}; };
class QueryRunner : public QObject class QueryRunner : public QObject
@@ -70,6 +79,7 @@ private:
Utils::QtcProcess m_process; Utils::QtcProcess m_process;
bool m_running = false; bool m_running = false;
bool m_paginated = false;
}; };
} // namespace GitLab } // namespace GitLab

View File

@@ -32,8 +32,59 @@
#include <utility> #include <utility>
namespace GitLab { namespace GitLab {
QString Event::toMessage() const
{
QString message;
if (author.realname.isEmpty())
message.append(author.name);
else
message.append(author.realname + " (" + author.name + ')');
message.append(' ');
if (!pushData.isEmpty())
message.append(pushData);
else if (!targetTitle.isEmpty())
message.append(action + ' ' + targetType + " '" + targetTitle +'\'');
else
message.append(action + ' ' + targetType);
return message;
}
namespace ResultParser { namespace ResultParser {
static PageInformation paginationInformation(const QByteArray &header)
{
PageInformation result;
const QByteArrayList lines = header.split('\n');
for (const QByteArray &line : lines) {
const QByteArray lower = line.toLower(); // depending on OS this may be capitalized
if (lower.startsWith("x-page: "))
result.currentPage = line.mid(8).toInt();
else if (lower.startsWith("x-per-page: "))
result.perPage = line.mid(12).toInt();
else if (lower.startsWith("x-total: "))
result.total = line.mid(9).toInt();
else if (lower.startsWith("x-total-pages: "))
result.totalPages = line.mid(15).toInt();
}
return result;
}
static std::pair<QByteArray, QByteArray> splitHeaderAndBody(const QByteArray &input)
{
QByteArray header;
QByteArray json;
int emptyLine = input.indexOf("\r\n\r\n"); // we always get \r\n as line separator?
if (emptyLine != -1) {
header = input.left(emptyLine);
json = input.mid(emptyLine + 4);
} else {
json = input;
}
return std::make_pair(header, json);
}
static std::pair<Error, QJsonObject> preHandleSingle(const QByteArray &json) static std::pair<Error, QJsonObject> preHandleSingle(const QByteArray &json)
{ {
Error result; Error result;
@@ -59,6 +110,53 @@ static std::pair<Error, QJsonObject> preHandleSingle(const QByteArray &json)
return std::make_pair(result, object); return std::make_pair(result, object);
} }
static std::pair<Error, QJsonDocument> preHandleHeaderAndBody(const QByteArray &header,
const QByteArray &json)
{
Error result;
if (header.isEmpty()) {
result.message = "Missing Expected Header";
return std::make_pair(result, QJsonDocument());
}
QJsonParseError error;
const QJsonDocument doc = QJsonDocument::fromJson(json, &error);
if (error.error != QJsonParseError::NoError) {
result.message = error.errorString();
return std::make_pair(result, doc);
}
if (doc.isObject()) {
const QJsonObject obj = doc.object();
if (obj.contains("message")) {
result = parseErrorMessage(obj.value("message").toString());
return std::make_pair(result, doc);
} else if (obj.contains("error")) {
if (obj.value("error").toString() == "insufficient_scope")
result.code = 1;
result.message = obj.value("error_description").toString();
return std::make_pair(result, doc);
}
}
if (!doc.isArray())
result.message = "Not an Array";
return std::make_pair(result, doc);
}
static User userFromJson(const QJsonObject &jsonObj)
{
User user;
user.name = jsonObj.value("username").toString();
user.realname = jsonObj.value("name").toString();
user.id = jsonObj.value("id").toInt(-1);
user.email = jsonObj.value("email").toString();
user.lastLogin = jsonObj.value("last_sign_in_at").toString();
user.bot = jsonObj.value("bot").toBool();
return user;
}
static Project projectFromJson(const QJsonObject &jsonObj) static Project projectFromJson(const QJsonObject &jsonObj)
{ {
Project project; Project project;
@@ -67,6 +165,8 @@ static Project projectFromJson(const QJsonObject &jsonObj)
project.pathName = jsonObj.value("path_with_namespace").toString(); project.pathName = jsonObj.value("path_with_namespace").toString();
project.id = jsonObj.value("id").toInt(-1); project.id = jsonObj.value("id").toInt(-1);
project.visibility = jsonObj.value("visibility").toString("public"); project.visibility = jsonObj.value("visibility").toString("public");
project.httpUrl = jsonObj.value("http_url_to_repo").toString();
project.sshUrl = jsonObj.value("ssh_url_to_repo").toString();
if (jsonObj.contains("forks_count")) if (jsonObj.contains("forks_count"))
project.forkCount = jsonObj.value("forks_count").toInt(); project.forkCount = jsonObj.value("forks_count").toInt();
if (jsonObj.contains("star_count")) if (jsonObj.contains("star_count"))
@@ -82,6 +182,42 @@ static Project projectFromJson(const QJsonObject &jsonObj)
return project; return project;
} }
static Event eventFromJson(const QJsonObject &jsonObj)
{
Event event;
event.action = jsonObj.value("action_name").toString();
const QJsonValue value = jsonObj.value("target_type");
event.targetType = value.isNull() ? "project" : jsonObj.value("target_type").toString();
if (event.targetType == "DiffNote") {
const QJsonObject noteObject = jsonObj.value("note").toObject();
event.targetType = noteObject.value("noteable_type").toString();
}
event.targetTitle = jsonObj.value("target_title").toString();
event.author = userFromJson(jsonObj.value("author").toObject());
event.timeStamp = jsonObj.value("created_at").toString();
if (jsonObj.contains("push_data")) {
const QJsonObject pushDataObj = jsonObj.value("push_data").toObject();
if (!pushDataObj.isEmpty()) {
const QString action = pushDataObj.value("action").toString();
const QString ref = pushDataObj.value("ref").toString();
const QString refType = pushDataObj.value("ref_type").toString();
event.pushData = action + ' ' + refType + " '" + ref + '\'';
}
}
return event;
}
User parseUser(const QByteArray &input)
{
auto [error, userObj] = preHandleSingle(input);
if (!error.message.isEmpty()) {
User result;
result.error = error;
return result;
}
return userFromJson(userObj);
}
Project parseProject(const QByteArray &input) Project parseProject(const QByteArray &input)
{ {
auto [error, projectObj] = preHandleSingle(input); auto [error, projectObj] = preHandleSingle(input);
@@ -93,6 +229,47 @@ Project parseProject(const QByteArray &input)
return projectFromJson(projectObj); return projectFromJson(projectObj);
} }
Projects parseProjects(const QByteArray &input)
{
auto [header, json] = splitHeaderAndBody(input);
auto [error, jsonDoc] = preHandleHeaderAndBody(header, json);
Projects result;
if (!error.message.isEmpty()) {
result.error = error;
return result;
}
result.pageInfo = paginationInformation(header);
const QJsonArray projectsArray = jsonDoc.array();
for (const QJsonValue &value : projectsArray) {
if (!value.isObject())
continue;
const QJsonObject projectObj = value.toObject();
result.projects.append(projectFromJson(projectObj));
}
return result;
}
Events parseEvents(const QByteArray &input)
{
auto [header, json] = splitHeaderAndBody(input);
auto [error, jsonDoc] = preHandleHeaderAndBody(header, json);
Events result;
if (!error.message.isEmpty()) {
result.error = error;
return result;
}
result.pageInfo = paginationInformation(header);
const QJsonArray eventsArray = jsonDoc.array();
for (const QJsonValue &value : eventsArray) {
if (!value.isObject())
continue;
const QJsonObject eventObj = value.toObject();
result.events.append(eventFromJson(eventObj));
}
return result;
}
Error parseErrorMessage(const QString &message) Error parseErrorMessage(const QString &message)
{ {
Error error; Error error;

View File

@@ -25,6 +25,8 @@
#pragma once #pragma once
#include <QList>
#include <QMetaType>
#include <QString> #include <QString>
namespace GitLab { namespace GitLab {
@@ -35,6 +37,27 @@ struct Error
QString message; QString message;
}; };
class PageInformation
{
public:
int currentPage = -1;
int totalPages = -1;
int perPage = -1;
int total = -1;
};
class User
{
public:
QString name;
QString realname;
QString email;
QString lastLogin;
Error error;
int id = -1;
bool bot = false;
};
class Project class Project
{ {
public: public:
@@ -42,18 +65,55 @@ public:
QString displayName; QString displayName;
QString pathName; QString pathName;
QString visibility; QString visibility;
QString httpUrl;
QString sshUrl;
Error error; Error error;
int id = -1; int id = -1;
int starCount = -1; int starCount = -1;
int forkCount = -1; int forkCount = -1;
int issuesCount = -1; int issuesCount = -1;
int accessLevel = -1; // 40 maintainer, 30 developer, 20 reporter, 10 guest int accessLevel = -1; // 50 owner, 40 maintainer, 30 developer, 20 reporter, 10 guest
};
class Projects
{
public:
QList<Project> projects;
Error error;
PageInformation pageInfo;
};
class Event
{
public:
QString action;
QString targetType;
QString targetTitle;
QString timeStamp;
QString pushData;
User author;
Error error;
QString toMessage() const;
};
class Events
{
public:
QList<Event> events;
Error error;
PageInformation pageInfo;
}; };
namespace ResultParser { namespace ResultParser {
User parseUser(const QByteArray &input);
Project parseProject(const QByteArray &input); Project parseProject(const QByteArray &input);
Projects parseProjects(const QByteArray &input);
Events parseEvents(const QByteArray &input);
Error parseErrorMessage(const QString &message); Error parseErrorMessage(const QString &message);
} // namespace ResultParser } // namespace ResultParser
} // namespace GitLab } // namespace GitLab
Q_DECLARE_METATYPE(GitLab::Project)

View File

@@ -197,8 +197,8 @@ void IosRunner::start()
connect(m_toolHandler, &IosToolHandler::finished, connect(m_toolHandler, &IosToolHandler::finished,
this, &IosRunner::handleFinished); this, &IosRunner::handleFinished);
const Runnable runnable = runControl()->runnable(); const CommandLine command = runControl()->commandLine();
QStringList args = ProcessArgs::splitArgs(runnable.command.arguments(), OsTypeMac); QStringList args = ProcessArgs::splitArgs(command.arguments(), OsTypeMac);
if (m_qmlServerPort.isValid()) { if (m_qmlServerPort.isValid()) {
QUrl qmlServer; QUrl qmlServer;
qmlServer.setPort(m_qmlServerPort.number()); qmlServer.setPort(m_qmlServerPort.number());

View File

@@ -918,6 +918,7 @@ void Client::documentContentsChanged(TextEditor::TextDocument *document,
{ {
if (!d->m_openedDocument.contains(document) || !reachable()) if (!d->m_openedDocument.contains(document) || !reachable())
return; return;
d->m_diagnosticManager->disableDiagnostics(document);
const QString method(DidChangeTextDocumentNotification::methodName); const QString method(DidChangeTextDocumentNotification::methodName);
TextDocumentSyncKind syncKind = d->m_serverCapabilities.textDocumentSyncKindHelper(); TextDocumentSyncKind syncKind = d->m_serverCapabilities.textDocumentSyncKindHelper();
if (Utils::optional<bool> registered = d->m_dynamicCapabilities.isRegistered(method)) { if (Utils::optional<bool> registered = d->m_dynamicCapabilities.isRegistered(method)) {

View File

@@ -90,7 +90,7 @@ void DiagnosticManager::hideDiagnostics(const Utils::FilePath &filePath)
for (BaseTextEditor *editor : BaseTextEditor::textEditorsForDocument(doc)) for (BaseTextEditor *editor : BaseTextEditor::textEditorsForDocument(doc))
editor->editorWidget()->setExtraSelections(m_extraSelectionsId, {}); editor->editorWidget()->setExtraSelections(m_extraSelectionsId, {});
} }
qDeleteAll(m_marks.take(filePath)); m_marks.remove(filePath);
} }
QList<Diagnostic> DiagnosticManager::filteredDiagnostics(const QList<Diagnostic> &diagnostics) const QList<Diagnostic> DiagnosticManager::filteredDiagnostics(const QList<Diagnostic> &diagnostics) const
@@ -98,6 +98,17 @@ QList<Diagnostic> DiagnosticManager::filteredDiagnostics(const QList<Diagnostic>
return diagnostics; return diagnostics;
} }
void DiagnosticManager::disableDiagnostics(TextEditor::TextDocument *document)
{
Marks &marks = m_marks[document->filePath()];
if (!marks.enabled)
return;
for (TextEditor::TextMark *mark : marks.marks)
mark->setColor(Utils::Theme::Color::IconsDisabledColor);
marks.enabled = false;
}
void DiagnosticManager::showDiagnostics(const DocumentUri &uri, int version) void DiagnosticManager::showDiagnostics(const DocumentUri &uri, int version)
{ {
const FilePath &filePath = uri.toFilePath(); const FilePath &filePath = uri.toFilePath();
@@ -106,7 +117,7 @@ void DiagnosticManager::showDiagnostics(const DocumentUri &uri, int version)
const VersionedDiagnostics &versionedDiagnostics = m_diagnostics.value(uri); const VersionedDiagnostics &versionedDiagnostics = m_diagnostics.value(uri);
if (versionedDiagnostics.version.value_or(version) == version if (versionedDiagnostics.version.value_or(version) == version
&& !versionedDiagnostics.diagnostics.isEmpty()) { && !versionedDiagnostics.diagnostics.isEmpty()) {
QList<TextEditor::TextMark *> &marks = m_marks[filePath]; Marks &marks = m_marks[filePath];
const bool isProjectFile = m_client->project() const bool isProjectFile = m_client->project()
&& m_client->project()->isKnownFile(filePath); && m_client->project()->isKnownFile(filePath);
for (const Diagnostic &diagnostic : versionedDiagnostics.diagnostics) { for (const Diagnostic &diagnostic : versionedDiagnostics.diagnostics) {
@@ -115,9 +126,9 @@ void DiagnosticManager::showDiagnostics(const DocumentUri &uri, int version)
if (!selection.cursor.isNull()) if (!selection.cursor.isNull())
extraSelections << selection; extraSelections << selection;
if (TextEditor::TextMark *mark = createTextMark(filePath, diagnostic, isProjectFile)) if (TextEditor::TextMark *mark = createTextMark(filePath, diagnostic, isProjectFile))
marks.append(mark); marks.marks.append(mark);
} }
if (!marks.isEmpty()) if (!marks.marks.isEmpty())
emit textMarkCreated(filePath); emit textMarkCreated(filePath);
} }
@@ -170,11 +181,7 @@ void DiagnosticManager::clearDiagnostics()
for (const DocumentUri &uri : m_diagnostics.keys()) for (const DocumentUri &uri : m_diagnostics.keys())
hideDiagnostics(uri.toFilePath()); hideDiagnostics(uri.toFilePath());
m_diagnostics.clear(); m_diagnostics.clear();
if (!QTC_GUARD(m_marks.isEmpty())) { QTC_ASSERT(m_marks.isEmpty(), m_marks.clear());
for (const QList<TextEditor::TextMark *> &marks : qAsConst(m_marks))
qDeleteAll(marks);
m_marks.clear();
}
} }
QList<Diagnostic> DiagnosticManager::diagnosticsAt(const DocumentUri &uri, QList<Diagnostic> DiagnosticManager::diagnosticsAt(const DocumentUri &uri,
@@ -218,4 +225,9 @@ bool DiagnosticManager::hasDiagnostics(const TextDocument *doc) const
return !it->diagnostics.isEmpty(); return !it->diagnostics.isEmpty();
} }
DiagnosticManager::Marks::~Marks()
{
qDeleteAll(marks);
}
} // namespace LanguageClient } // namespace LanguageClient

View File

@@ -61,6 +61,7 @@ public:
virtual QList<LanguageServerProtocol::Diagnostic> filteredDiagnostics( virtual QList<LanguageServerProtocol::Diagnostic> filteredDiagnostics(
const QList<LanguageServerProtocol::Diagnostic> &diagnostics) const; const QList<LanguageServerProtocol::Diagnostic> &diagnostics) const;
void disableDiagnostics(TextEditor::TextDocument *document);
void clearDiagnostics(); void clearDiagnostics();
QList<LanguageServerProtocol::Diagnostic> diagnosticsAt( QList<LanguageServerProtocol::Diagnostic> diagnosticsAt(
@@ -91,7 +92,14 @@ private:
QList<LanguageServerProtocol::Diagnostic> diagnostics; QList<LanguageServerProtocol::Diagnostic> diagnostics;
}; };
QMap<LanguageServerProtocol::DocumentUri, VersionedDiagnostics> m_diagnostics; QMap<LanguageServerProtocol::DocumentUri, VersionedDiagnostics> m_diagnostics;
QMap<Utils::FilePath, QList<TextEditor::TextMark *>> m_marks; class Marks
{
public:
~Marks();
bool enabled = true;
QList<TextEditor::TextMark *> marks;
};
QMap<Utils::FilePath, Marks> m_marks;
Client *m_client; Client *m_client;
Utils::Id m_extraSelectionsId; Utils::Id m_extraSelectionsId;
}; };

View File

@@ -46,7 +46,7 @@ MesonRunConfiguration::MesonRunConfiguration(ProjectExplorer::Target *target, Ut
addAspect<ProjectExplorer::ExecutableAspect>(target); addAspect<ProjectExplorer::ExecutableAspect>(target);
addAspect<ProjectExplorer::ArgumentsAspect>(macroExpander()); addAspect<ProjectExplorer::ArgumentsAspect>(macroExpander());
addAspect<ProjectExplorer::WorkingDirectoryAspect>(envAspect); addAspect<ProjectExplorer::WorkingDirectoryAspect>(macroExpander(), envAspect);
addAspect<ProjectExplorer::TerminalAspect>(); addAspect<ProjectExplorer::TerminalAspect>();
auto libAspect = addAspect<ProjectExplorer::UseLibraryPathsAspect>(); auto libAspect = addAspect<ProjectExplorer::UseLibraryPathsAspect>();

View File

@@ -54,7 +54,7 @@ public:
auto envAspect = addAspect<LocalEnvironmentAspect>(target); auto envAspect = addAspect<LocalEnvironmentAspect>(target);
addAspect<ExecutableAspect>(target); addAspect<ExecutableAspect>(target);
addAspect<ArgumentsAspect>(macroExpander()); addAspect<ArgumentsAspect>(macroExpander());
addAspect<WorkingDirectoryAspect>(envAspect); addAspect<WorkingDirectoryAspect>(macroExpander(), envAspect);
addAspect<TerminalAspect>(); addAspect<TerminalAspect>();
setUpdater([this] { setUpdater([this] {
@@ -91,7 +91,8 @@ public:
{ {
addAspect<ExecutableAspect>(target)->setExecutable(Nim::nimblePathFromKit(target->kit())); addAspect<ExecutableAspect>(target)->setExecutable(Nim::nimblePathFromKit(target->kit()));
addAspect<ArgumentsAspect>(macroExpander())->setArguments("test"); addAspect<ArgumentsAspect>(macroExpander())->setArguments("test");
addAspect<WorkingDirectoryAspect>(nullptr)->setDefaultWorkingDirectory(project()->projectDirectory()); addAspect<WorkingDirectoryAspect>(macroExpander(), nullptr)
->setDefaultWorkingDirectory(project()->projectDirectory());
addAspect<TerminalAspect>(); addAspect<TerminalAspect>();
setDisplayName(tr("Nimble Test")); setDisplayName(tr("Nimble Test"));

View File

@@ -52,7 +52,7 @@ public:
auto envAspect = addAspect<LocalEnvironmentAspect>(target); auto envAspect = addAspect<LocalEnvironmentAspect>(target);
addAspect<ExecutableAspect>(target); addAspect<ExecutableAspect>(target);
addAspect<ArgumentsAspect>(macroExpander()); addAspect<ArgumentsAspect>(macroExpander());
addAspect<WorkingDirectoryAspect>(envAspect); addAspect<WorkingDirectoryAspect>(macroExpander(), envAspect);
addAspect<TerminalAspect>(); addAspect<TerminalAspect>();
setDisplayName(tr("Current Build Target")); setDisplayName(tr("Current Build Target"));

View File

@@ -23,9 +23,9 @@
** **
****************************************************************************/ ****************************************************************************/
#include "perfdatareader.h"
#include "perfprofilerconstants.h"
#include "perfprofilerruncontrol.h" #include "perfprofilerruncontrol.h"
#include "perfdatareader.h"
#include "perfprofilertool.h" #include "perfprofilertool.h"
#include "perfrunconfigurationaspect.h" #include "perfrunconfigurationaspect.h"
#include "perfsettings.h" #include "perfsettings.h"
@@ -124,10 +124,6 @@ public:
void start() override void start() override
{ {
m_process = new QtcProcess(this); m_process = new QtcProcess(this);
if (!m_process) {
reportFailure(tr("Could not start device process."));
return;
}
connect(m_process, &QtcProcess::started, this, &RunWorker::reportStarted); connect(m_process, &QtcProcess::started, this, &RunWorker::reportStarted);
connect(m_process, &QtcProcess::done, this, [this] { connect(m_process, &QtcProcess::done, this, [this] {
@@ -144,12 +140,10 @@ public:
reportStopped(); reportStopped();
}); });
Runnable perfRunnable = runnable();
CommandLine cmd({device()->filePath("perf"), {"record"}}); CommandLine cmd({device()->filePath("perf"), {"record"}});
cmd.addArgs(m_perfRecordArguments); cmd.addArgs(m_perfRecordArguments);
cmd.addArgs({"-o", "-", "--"}); cmd.addArgs({"-o", "-", "--"});
cmd.addCommandLineAsArgs(perfRunnable.command, CommandLine::Raw); cmd.addCommandLineAsArgs(runControl()->commandLine(), CommandLine::Raw);
m_process->setCommand(cmd); m_process->setCommand(cmd);
m_process->start(); m_process->start();

View File

@@ -60,6 +60,8 @@ add_qtc_plugin(ProjectExplorer
devicesupport/devicesettingswidget.cpp devicesupport/devicesettingswidget.h devicesupport/devicesettingswidget.ui devicesupport/devicesettingswidget.cpp devicesupport/devicesettingswidget.h devicesupport/devicesettingswidget.ui
devicesupport/devicetestdialog.cpp devicesupport/devicetestdialog.h devicesupport/devicetestdialog.ui devicesupport/devicetestdialog.cpp devicesupport/devicetestdialog.h devicesupport/devicetestdialog.ui
devicesupport/deviceusedportsgatherer.cpp devicesupport/deviceusedportsgatherer.h devicesupport/deviceusedportsgatherer.cpp devicesupport/deviceusedportsgatherer.h
devicesupport/filetransfer.cpp devicesupport/filetransfer.h
devicesupport/filetransferinterface.h
devicesupport/idevice.cpp devicesupport/idevice.h devicesupport/idevice.cpp devicesupport/idevice.h
devicesupport/idevicefactory.cpp devicesupport/idevicefactory.h devicesupport/idevicefactory.cpp devicesupport/idevicefactory.h
devicesupport/idevicefwd.h devicesupport/idevicefwd.h

View File

@@ -44,6 +44,7 @@
#include <extensionsystem/invoker.h> #include <extensionsystem/invoker.h>
#include <extensionsystem/pluginmanager.h> #include <extensionsystem/pluginmanager.h>
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/outputformatter.h> #include <utils/outputformatter.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
@@ -66,6 +67,8 @@
static Q_LOGGING_CATEGORY(appOutputLog, "qtc.projectexplorer.appoutput", QtWarningMsg); static Q_LOGGING_CATEGORY(appOutputLog, "qtc.projectexplorer.appoutput", QtWarningMsg);
using namespace Utils;
namespace ProjectExplorer { namespace ProjectExplorer {
namespace Internal { namespace Internal {
@@ -398,19 +401,20 @@ void AppOutputPane::createNewOutputWindow(RunControl *rc)
connect(rc, &RunControl::applicationProcessHandleChanged, connect(rc, &RunControl::applicationProcessHandleChanged,
this, &AppOutputPane::enableDefaultButtons); this, &AppOutputPane::enableDefaultButtons);
connect(rc, &RunControl::appendMessage, connect(rc, &RunControl::appendMessage,
this, [this, rc](const QString &out, Utils::OutputFormat format) { this, [this, rc](const QString &out, OutputFormat format) {
appendMessage(rc, out, format); appendMessage(rc, out, format);
}); });
// First look if we can reuse a tab // First look if we can reuse a tab
const Runnable thisRunnable = rc->runnable(); const CommandLine thisCommand = rc->commandLine();
const FilePath thisWorkingDirectory = rc->workingDirectory();
const Environment thisEnvironment = rc->environment();
const int tabIndex = Utils::indexOf(m_runControlTabs, [&](const RunControlTab &tab) { const int tabIndex = Utils::indexOf(m_runControlTabs, [&](const RunControlTab &tab) {
if (!tab.runControl || tab.runControl->isRunning()) if (!tab.runControl || tab.runControl->isRunning())
return false; return false;
const Runnable otherRunnable = tab.runControl->runnable(); return thisCommand == tab.runControl->commandLine()
return thisRunnable.command == otherRunnable.command && thisWorkingDirectory == tab.runControl->workingDirectory()
&& thisRunnable.workingDirectory == otherRunnable.workingDirectory && thisEnvironment == tab.runControl->environment();
&& thisRunnable.environment == otherRunnable.environment;
}); });
if (tabIndex != -1) { if (tabIndex != -1) {
RunControlTab &tab = m_runControlTabs[tabIndex]; RunControlTab &tab = m_runControlTabs[tabIndex];
@@ -433,7 +437,7 @@ void AppOutputPane::createNewOutputWindow(RunControl *rc)
} }
// Create new // Create new
static int counter = 0; static int counter = 0;
Utils::Id contextId = Utils::Id(C_APP_OUTPUT).withSuffix(counter++); Id contextId = Id(C_APP_OUTPUT).withSuffix(counter++);
Core::Context context(contextId); Core::Context context(contextId);
Core::OutputWindow *ow = new Core::OutputWindow(context, SETTINGS_KEY, m_tabWidget); Core::OutputWindow *ow = new Core::OutputWindow(context, SETTINGS_KEY, m_tabWidget);
ow->setWindowTitle(tr("Application Output Window")); ow->setWindowTitle(tr("Application Output Window"));
@@ -486,19 +490,19 @@ void AppOutputPane::updateFromSettings()
} }
} }
void AppOutputPane::appendMessage(RunControl *rc, const QString &out, Utils::OutputFormat format) void AppOutputPane::appendMessage(RunControl *rc, const QString &out, OutputFormat format)
{ {
const int index = indexOf(rc); const int index = indexOf(rc);
if (index != -1) { if (index != -1) {
Core::OutputWindow *window = m_runControlTabs.at(index).window; Core::OutputWindow *window = m_runControlTabs.at(index).window;
QString stringToWrite; QString stringToWrite;
if (format == Utils::NormalMessageFormat || format == Utils::ErrorMessageFormat) { if (format == NormalMessageFormat || format == ErrorMessageFormat) {
stringToWrite = QTime::currentTime().toString(); stringToWrite = QTime::currentTime().toString();
stringToWrite += ": "; stringToWrite += ": ";
} }
stringToWrite += out; stringToWrite += out;
window->appendMessage(stringToWrite, format); window->appendMessage(stringToWrite, format);
if (format != Utils::NormalMessageFormat) { if (format != NormalMessageFormat) {
RunControlTab &tab = m_runControlTabs[index]; RunControlTab &tab = m_runControlTabs[index];
switch (tab.behaviorOnOutput) { switch (tab.behaviorOnOutput) {
case AppOutputPaneMode::FlashOnOutput: case AppOutputPaneMode::FlashOnOutput:
@@ -530,7 +534,7 @@ const bool kWrapOutputDefault = true;
void AppOutputPane::storeSettings() const void AppOutputPane::storeSettings() const
{ {
Utils::QtcSettings *const s = Core::ICore::settings(); QtcSettings *const s = Core::ICore::settings();
s->setValueWithDefault(POP_UP_FOR_RUN_OUTPUT_KEY, s->setValueWithDefault(POP_UP_FOR_RUN_OUTPUT_KEY,
int(m_settings.runOutputMode), int(m_settings.runOutputMode),
int(kRunOutputModeDefault)); int(kRunOutputModeDefault));
@@ -706,7 +710,7 @@ void AppOutputPane::enableButtons(const RunControl *rc)
m_stopAction->setEnabled(isRunning); m_stopAction->setEnabled(isRunning);
if (isRunning && debuggerPlugin() && rc->applicationProcessHandle().isValid()) { if (isRunning && debuggerPlugin() && rc->applicationProcessHandle().isValid()) {
m_attachButton->setEnabled(true); m_attachButton->setEnabled(true);
Utils::ProcessHandle h = rc->applicationProcessHandle(); ProcessHandle h = rc->applicationProcessHandle();
QString tip = h.isValid() ? RunControl::tr("PID %1").arg(h.pid()) QString tip = h.isValid() ? RunControl::tr("PID %1").arg(h.pid())
: RunControl::tr("Invalid"); : RunControl::tr("Invalid");
m_attachButton->setToolTip(msgAttachDebuggerTooltip(tip)); m_attachButton->setToolTip(msgAttachDebuggerTooltip(tip));

View File

@@ -30,6 +30,7 @@
#include "buildsystem.h" #include "buildsystem.h"
#include "compileoutputwindow.h" #include "compileoutputwindow.h"
#include "deployconfiguration.h" #include "deployconfiguration.h"
#include "devicesupport/devicemanager.h"
#include "kit.h" #include "kit.h"
#include "kitinformation.h" #include "kitinformation.h"
#include "project.h" #include "project.h"
@@ -46,8 +47,9 @@
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <coreplugin/progressmanager/futureprogress.h> #include <coreplugin/progressmanager/futureprogress.h>
#include <coreplugin/progressmanager/progressmanager.h> #include <coreplugin/progressmanager/progressmanager.h>
#include <extensionsystem/pluginmanager.h> #include <extensionsystem/pluginmanager.h>
#include <projectexplorer/devicesupport/idevice.h>
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/outputformatter.h> #include <utils/outputformatter.h>
#include <utils/runextensions.h> #include <utils/runextensions.h>
@@ -118,7 +120,8 @@ static int queue(const QList<Project *> &projects, const QList<Id> &stepIds,
return projects.contains(rc->project()); return projects.contains(rc->project());
case StopBeforeBuild::SameBuildDir: case StopBeforeBuild::SameBuildDir:
return Utils::contains(projects, [rc, configSelection](Project *p) { return Utils::contains(projects, [rc, configSelection](Project *p) {
IDevice::ConstPtr device = rc->runnable().device; const FilePath executable = rc->commandLine().executable();
IDevice::ConstPtr device = DeviceManager::deviceForPath(executable);
for (const Target * const t : targetsForSelection(p, configSelection)) { for (const Target * const t : targetsForSelection(p, configSelection)) {
if (device.isNull()) if (device.isNull())
device = DeviceKitAspect::device(t->kit()); device = DeviceKitAspect::device(t->kit());
@@ -126,7 +129,7 @@ static int queue(const QList<Project *> &projects, const QList<Id> &stepIds,
continue; continue;
for (const BuildConfiguration * const bc for (const BuildConfiguration * const bc
: buildConfigsForSelection(t, configSelection)) { : buildConfigsForSelection(t, configSelection)) {
if (rc->runnable().command.executable().isChildOf(bc->buildDirectory())) if (executable.isChildOf(bc->buildDirectory()))
return true; return true;
} }
} }

View File

@@ -53,7 +53,7 @@ CustomExecutableRunConfiguration::CustomExecutableRunConfiguration(Target *targe
exeAspect->setEnvironmentChange(EnvironmentChange::fromFixedEnvironment(envAspect->environment())); exeAspect->setEnvironmentChange(EnvironmentChange::fromFixedEnvironment(envAspect->environment()));
addAspect<ArgumentsAspect>(macroExpander()); addAspect<ArgumentsAspect>(macroExpander());
addAspect<WorkingDirectoryAspect>(envAspect); addAspect<WorkingDirectoryAspect>(macroExpander(), envAspect);
addAspect<TerminalAspect>(); addAspect<TerminalAspect>();
connect(envAspect, &EnvironmentAspect::environmentChanged, this, [exeAspect, envAspect] { connect(envAspect, &EnvironmentAspect::environmentChanged, this, [exeAspect, envAspect] {

View File

@@ -70,7 +70,7 @@ DesktopRunConfiguration::DesktopRunConfiguration(Target *target, Id id, Kind kin
addAspect<ExecutableAspect>(target); addAspect<ExecutableAspect>(target);
addAspect<ArgumentsAspect>(macroExpander()); addAspect<ArgumentsAspect>(macroExpander());
addAspect<WorkingDirectoryAspect>(envAspect); addAspect<WorkingDirectoryAspect>(macroExpander(), envAspect);
addAspect<TerminalAspect>(); addAspect<TerminalAspect>();
auto libAspect = addAspect<UseLibraryPathsAspect>(); auto libAspect = addAspect<UseLibraryPathsAspect>();

View File

@@ -0,0 +1,227 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "filetransfer.h"
#include "devicemanager.h"
#include "idevice.h"
#include <utils/processinterface.h>
#include <utils/qtcassert.h>
#include <QProcess>
using namespace Utils;
namespace ProjectExplorer {
FileTransferDirection FileToTransfer::direction() const
{
if (m_source.needsDevice() == m_target.needsDevice())
return FileTransferDirection::Invalid;
return m_source.needsDevice() ? FileTransferDirection::Download : FileTransferDirection::Upload;
}
QString FileTransferSetupData::defaultRsyncFlags()
{
return "-av";
}
static FileTransferDirection transferDirection(const FilesToTransfer &files)
{
if (files.isEmpty())
return FileTransferDirection::Invalid;
const FileTransferDirection direction = files.first().direction();
for (const FileToTransfer &file : files) {
if (file.direction() != direction)
return FileTransferDirection::Invalid;
}
return direction;
}
static const FilePath &remoteFile(FileTransferDirection direction, const FileToTransfer &file)
{
return direction == FileTransferDirection::Upload ? file.m_target : file.m_source;
}
static bool isSameDevice(const FilePath &first, const FilePath &second)
{
return (first.scheme() == second.scheme()) && (first.host() == second.host());
}
static IDeviceConstPtr matchedDevice(FileTransferDirection direction, const FilesToTransfer &files)
{
if (files.isEmpty())
return {};
const FilePath &filePath = remoteFile(direction, files.first());
for (const FileToTransfer &file : files) {
if (!isSameDevice(filePath, remoteFile(direction, file)))
return {};
}
return DeviceManager::deviceForPath(filePath);
}
void FileTransferInterface::startFailed(const QString &errorString)
{
emit done({0, QProcess::NormalExit, QProcess::FailedToStart, errorString});
}
class FileTransferPrivate : public QObject
{
Q_OBJECT
public:
void test(const ProjectExplorer::IDeviceConstPtr &onDevice);
void start();
void stop();
FileTransferSetupData m_setup;
signals:
void progress(const QString &progressMessage);
void done(const ProcessResultData &resultData);
private:
void startFailed(const QString &errorString);
void run(const FileTransferSetupData &setup, const IDeviceConstPtr &device);
std::unique_ptr<FileTransferInterface> m_transfer;
};
void FileTransferPrivate::test(const IDeviceConstPtr &onDevice)
{
if (!onDevice)
return startFailed(tr("No device set for test transfer."));
run({{}, m_setup.m_method, m_setup.m_rsyncFlags}, onDevice);
}
void FileTransferPrivate::start()
{
if (m_setup.m_files.isEmpty())
return startFailed(tr("No files to transfer."));
const FileTransferDirection direction = transferDirection(m_setup.m_files);
if (direction == FileTransferDirection::Invalid)
return startFailed(tr("Mixing different types of transfer in one go."));
const IDeviceConstPtr device = matchedDevice(direction, m_setup.m_files);
if (!device)
return startFailed(tr("Trying to transfer into / from not matching device."));
run(m_setup, device);
}
void FileTransferPrivate::stop()
{
if (!m_transfer)
return;
m_transfer->disconnect();
m_transfer.release()->deleteLater();
}
void FileTransferPrivate::run(const FileTransferSetupData &setup, const IDeviceConstPtr &device)
{
stop();
m_transfer.reset(device->createFileTransferInterface(setup));
QTC_ASSERT(m_transfer, startFailed(tr("Missing transfer implementation.")); return);
m_transfer->setParent(this);
connect(m_transfer.get(), &FileTransferInterface::progress,
this, &FileTransferPrivate::progress);
connect(m_transfer.get(), &FileTransferInterface::done,
this, &FileTransferPrivate::done);
m_transfer->start();
}
void FileTransferPrivate::startFailed(const QString &errorString)
{
emit done({0, QProcess::NormalExit, QProcess::FailedToStart, errorString});
}
FileTransfer::FileTransfer()
: d(new FileTransferPrivate)
{
d->setParent(this);
connect(d, &FileTransferPrivate::progress, this, &FileTransfer::progress);
connect(d, &FileTransferPrivate::done, this, &FileTransfer::done);
}
FileTransfer::~FileTransfer()
{
stop();
delete d;
}
void FileTransfer::setFilesToTransfer(const FilesToTransfer &files)
{
d->m_setup.m_files = files;
}
void FileTransfer::setTransferMethod(FileTransferMethod method)
{
d->m_setup.m_method = method;
}
void FileTransfer::setRsyncFlags(const QString &flags)
{
d->m_setup.m_rsyncFlags = flags;
}
void FileTransfer::test(const ProjectExplorer::IDeviceConstPtr &onDevice)
{
d->test(onDevice);
}
FileTransferMethod FileTransfer::transferMethod() const
{
return d->m_setup.m_method;
}
void FileTransfer::start()
{
d->start();
}
void FileTransfer::stop()
{
d->stop();
}
QString FileTransfer::transferMethodName(FileTransferMethod method)
{
switch (method) {
case FileTransferMethod::Sftp: return FileTransfer::tr("sftp");
case FileTransferMethod::Rsync: return FileTransfer::tr("rsync");
}
QTC_CHECK(false);
return {};
}
} // namespace ProjectExplorer
#include "filetransfer.moc"

View File

@@ -25,40 +25,17 @@
#pragma once #pragma once
#include "remotelinux_export.h" #include "../projectexplorer_export.h"
#include "filetransferinterface.h"
#include <projectexplorer/devicesupport/idevicefwd.h> #include "idevicefwd.h"
#include <utils/filepath.h>
namespace Utils { class ProcessResultData; } namespace Utils { class ProcessResultData; }
namespace RemoteLinux { namespace ProjectExplorer {
enum class TransferDirection {
Upload,
Download,
Invalid
};
class REMOTELINUX_EXPORT FileToTransfer
{
public:
Utils::FilePath m_source;
Utils::FilePath m_target;
TransferDirection transferDirection() const;
};
using FilesToTransfer = QList<FileToTransfer>;
enum class FileTransferMethod {
Sftp,
Rsync,
Default = Sftp
};
class FileTransferPrivate; class FileTransferPrivate;
class REMOTELINUX_EXPORT FileTransfer : public QObject class PROJECTEXPLORER_EXPORT FileTransfer : public QObject
{ {
Q_OBJECT Q_OBJECT
@@ -66,19 +43,17 @@ public:
FileTransfer(); FileTransfer();
~FileTransfer(); ~FileTransfer();
void setDevice(const ProjectExplorer::IDeviceConstPtr &device);
void setTransferMethod(FileTransferMethod method);
void setFilesToTransfer(const FilesToTransfer &files); void setFilesToTransfer(const FilesToTransfer &files);
void setTransferMethod(FileTransferMethod method);
void setRsyncFlags(const QString &flags); void setRsyncFlags(const QString &flags);
FileTransferMethod transferMethod() const; FileTransferMethod transferMethod() const;
void test(); void test(const ProjectExplorer::IDeviceConstPtr &onDevice);
void start(); void start();
void stop(); void stop();
static QString transferMethodName(FileTransferMethod method); static QString transferMethodName(FileTransferMethod method);
static QString defaultRsyncFlags();
signals: signals:
void progress(const QString &progressMessage); void progress(const QString &progressMessage);
@@ -88,6 +63,4 @@ private:
FileTransferPrivate *d; FileTransferPrivate *d;
}; };
} // namespace RemoteLinux } // namespace ProjectExplorer
Q_DECLARE_METATYPE(RemoteLinux::FileTransferMethod)

View File

@@ -0,0 +1,93 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "../projectexplorer_export.h"
#include <utils/filepath.h>
namespace Utils { class ProcessResultData; }
namespace ProjectExplorer {
enum class FileTransferDirection {
Invalid,
Upload,
Download
};
enum class FileTransferMethod {
Sftp,
Rsync,
Default = Sftp
};
class PROJECTEXPLORER_EXPORT FileToTransfer
{
public:
Utils::FilePath m_source;
Utils::FilePath m_target;
FileTransferDirection direction() const;
};
using FilesToTransfer = QList<FileToTransfer>;
class PROJECTEXPLORER_EXPORT FileTransferSetupData
{
public:
FilesToTransfer m_files; // When empty, do test instead of a real transfer
FileTransferMethod m_method = FileTransferMethod::Default;
QString m_rsyncFlags = defaultRsyncFlags();
static QString defaultRsyncFlags();
};
class PROJECTEXPLORER_EXPORT FileTransferInterface : public QObject
{
Q_OBJECT
signals:
void progress(const QString &progressMessage);
void done(const Utils::ProcessResultData &resultData);
protected:
FileTransferInterface(const FileTransferSetupData &setupData)
: m_setup(setupData) {}
void startFailed(const QString &errorString);
const FileTransferSetupData m_setup;
private:
FileTransferInterface() = delete;
virtual void start() = 0;
friend class FileTransferPrivate;
};
} // namespace ProjectExplorer

View File

@@ -434,6 +434,14 @@ ProcessInterface *IDevice::createProcessInterface() const
return nullptr; return nullptr;
} }
FileTransferInterface *IDevice::createFileTransferInterface(
const FileTransferSetupData &setup) const
{
Q_UNUSED(setup)
QTC_CHECK(false);
return nullptr;
}
Environment IDevice::systemEnvironment() const Environment IDevice::systemEnvironment() const
{ {
QTC_CHECK(false); QTC_CHECK(false);

View File

@@ -60,6 +60,8 @@ class QtcProcess;
namespace ProjectExplorer { namespace ProjectExplorer {
class DeviceProcessList; class DeviceProcessList;
class FileTransferInterface;
class FileTransferSetupData;
class Kit; class Kit;
class SshParameters; class SshParameters;
class Task; class Task;
@@ -265,6 +267,8 @@ public:
virtual QFile::Permissions permissions(const Utils::FilePath &filePath) const; virtual QFile::Permissions permissions(const Utils::FilePath &filePath) const;
virtual bool setPermissions(const Utils::FilePath &filePath, QFile::Permissions) const; virtual bool setPermissions(const Utils::FilePath &filePath, QFile::Permissions) const;
virtual Utils::ProcessInterface *createProcessInterface() const; virtual Utils::ProcessInterface *createProcessInterface() const;
virtual FileTransferInterface *createFileTransferInterface(
const FileTransferSetupData &setup) const;
virtual Utils::Environment systemEnvironment() const; virtual Utils::Environment systemEnvironment() const;
virtual qint64 fileSize(const Utils::FilePath &filePath) const; virtual qint64 fileSize(const Utils::FilePath &filePath) const;
virtual qint64 bytesAvailable(const Utils::FilePath &filePath) const; virtual qint64 bytesAvailable(const Utils::FilePath &filePath) const;

View File

@@ -64,24 +64,21 @@ QStringList SshParameters::connectionOptions(const FilePath &binary) const
"-o", "Port=" + QString::number(port())}; "-o", "Port=" + QString::number(port())};
if (!userName().isEmpty()) if (!userName().isEmpty())
args.append({"-o", "User=" + userName()}); args << "-o" << "User=" + userName();
const bool keyOnly = authenticationType == const bool keyOnly = authenticationType == SshParameters::AuthenticationTypeSpecificKey;
SshParameters::AuthenticationTypeSpecificKey; if (keyOnly)
if (keyOnly) { args << "-o" << "IdentitiesOnly=yes" << "-i" << privateKeyFile.path();
args << "-o" << "IdentitiesOnly=yes";
args << "-i" << privateKeyFile.path();
}
if (keyOnly || SshSettings::askpassFilePath().isEmpty())
args << "-o" << "BatchMode=yes";
bool useTimeout = timeout != 0; const QString batchModeEnabled = (keyOnly || SshSettings::askpassFilePath().isEmpty())
if (useTimeout && HostOsInfo::isWindowsHost() ? QLatin1String("yes") : QLatin1String("no");
&& binary.toString().toLower().contains("/system32/")) { args << "-o" << "BatchMode=" + batchModeEnabled;
useTimeout = false;
} const bool isWindows = HostOsInfo::isWindowsHost()
&& binary.toString().toLower().contains("/system32/");
const bool useTimeout = (timeout != 0) && !isWindows;
if (useTimeout) if (useTimeout)
args << "-o" << ("ConnectTimeout=" + QString::number(timeout)); args << "-o" << "ConnectTimeout=" + QString::number(timeout);
return args; return args;
} }

View File

@@ -1110,6 +1110,13 @@ void DeviceKitAspect::setDeviceId(Kit *k, Utils::Id id)
k->setValue(DeviceKitAspect::id(), id.toSetting()); k->setValue(DeviceKitAspect::id(), id.toSetting());
} }
FilePath DeviceKitAspect::deviceFilePath(const Kit *k, const QString &pathOnDevice)
{
if (IDevice::ConstPtr dev = device(k))
return dev->filePath(pathOnDevice);
return FilePath::fromString(pathOnDevice);
}
void DeviceKitAspect::kitsWereLoaded() void DeviceKitAspect::kitsWereLoaded()
{ {
const QList<Kit *> kits = KitManager::kits(); const QList<Kit *> kits = KitManager::kits();

View File

@@ -161,6 +161,7 @@ public:
static Utils::Id deviceId(const Kit *k); static Utils::Id deviceId(const Kit *k);
static void setDevice(Kit *k, IDeviceConstPtr dev); static void setDevice(Kit *k, IDeviceConstPtr dev);
static void setDeviceId(Kit *k, Utils::Id dataId); static void setDeviceId(Kit *k, Utils::Id dataId);
static Utils::FilePath deviceFilePath(const Kit *k, const QString &pathOnDevice);
private: private:
QVariant defaultValue(const Kit *k) const; QVariant defaultValue(const Kit *k) const;

View File

@@ -158,12 +158,23 @@ static bool hostSupportsPlatform(MsvcToolChain::Platform platform)
{ {
if (hostPrefersPlatform(platform)) if (hostPrefersPlatform(platform))
return true; return true;
switch (HostOsInfo::hostArchitecture()) {
// The x86 host toolchains are not the preferred toolchains on amd64 but they are still // The x86 host toolchains are not the preferred toolchains on amd64 but they are still
// supported by that host // supported by that host
return HostOsInfo::hostArchitecture() == HostOsInfo::HostArchitectureAMD64 case HostOsInfo::HostArchitectureAMD64:
&& (platform == MsvcToolChain::x86 || platform == MsvcToolChain::x86_amd64 return platform == MsvcToolChain::x86 || platform == MsvcToolChain::x86_amd64
|| platform == MsvcToolChain::x86_ia64 || platform == MsvcToolChain::x86_arm || platform == MsvcToolChain::x86_ia64 || platform == MsvcToolChain::x86_arm
|| platform == MsvcToolChain::x86_arm64); || platform == MsvcToolChain::x86_arm64;
// The Arm64 host can run the cross-compilers via emulation of x86 and amd64
case HostOsInfo::HostArchitectureArm:
return platform == MsvcToolChain::x86_arm || platform == MsvcToolChain::x86_arm64
|| platform == MsvcToolChain::amd64_arm || platform == MsvcToolChain::amd64_arm64
|| platform == MsvcToolChain::x86 || platform == MsvcToolChain::x86_amd64
|| platform == MsvcToolChain::amd64 || platform == MsvcToolChain::amd64_x86;
default:
return false;
}
} }
static QString fixRegistryPath(const QString &path) static QString fixRegistryPath(const QString &path)

View File

@@ -333,7 +333,8 @@ static bool canOpenTerminalWithRunEnv(const Project *project, const ProjectNode
const RunConfiguration * const runConfig = runConfigForNode(target, node); const RunConfiguration * const runConfig = runConfigForNode(target, node);
if (!runConfig) if (!runConfig)
return false; return false;
IDevice::ConstPtr device = runConfig->runnable().device; IDevice::ConstPtr device
= DeviceManager::deviceForPath(runConfig->runnable().command.executable());
if (!device) if (!device)
device = DeviceKitAspect::device(target->kit()); device = DeviceKitAspect::device(target->kit());
return device && device->canOpenTerminal(); return device && device->canOpenTerminal();
@@ -3245,13 +3246,13 @@ void ProjectExplorerPlugin::runRunConfiguration(RunConfiguration *rc,
dd->doUpdateRunActions(); dd->doUpdateRunActions();
} }
QList<QPair<Runnable, ProcessHandle>> ProjectExplorerPlugin::runningRunControlProcesses() QList<QPair<CommandLine, ProcessHandle>> ProjectExplorerPlugin::runningRunControlProcesses()
{ {
QList<QPair<Runnable, ProcessHandle>> processes; QList<QPair<CommandLine, ProcessHandle>> processes;
const QList<RunControl *> runControls = allRunControls(); const QList<RunControl *> runControls = allRunControls();
for (RunControl *rc : runControls) { for (RunControl *rc : runControls) {
if (rc->isRunning()) if (rc->isRunning())
processes << qMakePair(rc->runnable(), rc->applicationProcessHandle()); processes << qMakePair(rc->commandLine(), rc->applicationProcessHandle());
} }
return processes; return processes;
} }
@@ -3935,7 +3936,7 @@ void ProjectExplorerPluginPrivate::openTerminalHereWithRunEnv()
QTC_ASSERT(runConfig, return); QTC_ASSERT(runConfig, return);
const Runnable runnable = runConfig->runnable(); const Runnable runnable = runConfig->runnable();
IDevice::ConstPtr device = runnable.device; IDevice::ConstPtr device = DeviceManager::deviceForPath(runnable.command.executable());
if (!device) if (!device)
device = DeviceKitAspect::device(target->kit()); device = DeviceKitAspect::device(target->kit());
QTC_ASSERT(device && device->canOpenTerminal(), return); QTC_ASSERT(device && device->canOpenTerminal(), return);

View File

@@ -164,7 +164,7 @@ public:
static void runStartupProject(Utils::Id runMode, bool forceSkipDeploy = false); static void runStartupProject(Utils::Id runMode, bool forceSkipDeploy = false);
static void runRunConfiguration(RunConfiguration *rc, Utils::Id runMode, static void runRunConfiguration(RunConfiguration *rc, Utils::Id runMode,
const bool forceSkipDeploy = false); const bool forceSkipDeploy = false);
static QList<QPair<Runnable, Utils::ProcessHandle>> runningRunControlProcesses(); static QList<QPair<Utils::CommandLine, Utils::ProcessHandle>> runningRunControlProcesses();
static QList<RunControl *> allRunControls(); static QList<RunControl *> allRunControls();
static void addExistingFiles(FolderNode *folderNode, const Utils::FilePaths &filePaths); static void addExistingFiles(FolderNode *folderNode, const Utils::FilePaths &filePaths);

View File

@@ -219,6 +219,8 @@ Project {
"devicesettingswidget.cpp", "devicesettingswidget.h", "devicesettingswidget.ui", "devicesettingswidget.cpp", "devicesettingswidget.h", "devicesettingswidget.ui",
"devicetestdialog.cpp", "devicetestdialog.h", "devicetestdialog.ui", "devicetestdialog.cpp", "devicetestdialog.h", "devicetestdialog.ui",
"deviceusedportsgatherer.cpp", "deviceusedportsgatherer.h", "deviceusedportsgatherer.cpp", "deviceusedportsgatherer.h",
"filetransfer.cpp", "filetransfer.h",
"filetransferinterface.h",
"idevice.cpp", "idevice.h", "idevice.cpp", "idevice.h",
"idevicefactory.cpp", "idevicefactory.h", "idevicefactory.cpp", "idevicefactory.h",
"idevicefwd.h", "idevicefwd.h",

View File

@@ -171,8 +171,9 @@ bool TerminalAspect::isUserSet() const
working directory for running the executable. working directory for running the executable.
*/ */
WorkingDirectoryAspect::WorkingDirectoryAspect(EnvironmentAspect *envAspect) WorkingDirectoryAspect::WorkingDirectoryAspect(const MacroExpander *expander,
: m_envAspect(envAspect) EnvironmentAspect *envAspect)
: m_envAspect(envAspect), m_macroExpander(expander)
{ {
setDisplayName(tr("Working Directory")); setDisplayName(tr("Working Directory"));
setId("WorkingDirectoryAspect"); setId("WorkingDirectoryAspect");

View File

@@ -81,7 +81,8 @@ class PROJECTEXPLORER_EXPORT WorkingDirectoryAspect : public Utils::BaseAspect
Q_OBJECT Q_OBJECT
public: public:
explicit WorkingDirectoryAspect(EnvironmentAspect *envAspect); explicit WorkingDirectoryAspect(const Utils::MacroExpander *expander,
EnvironmentAspect *envAspect);
void addToLayout(Utils::LayoutBuilder &builder) override; void addToLayout(Utils::LayoutBuilder &builder) override;
@@ -102,7 +103,7 @@ private:
Utils::FilePath m_defaultWorkingDirectory; Utils::FilePath m_defaultWorkingDirectory;
QPointer<Utils::PathChooser> m_chooser; QPointer<Utils::PathChooser> m_chooser;
QPointer<QToolButton> m_resetButton; QPointer<QToolButton> m_resetButton;
Utils::MacroExpander *m_macroExpander = nullptr; const Utils::MacroExpander *m_macroExpander = nullptr;
}; };
class PROJECTEXPLORER_EXPORT ArgumentsAspect : public Utils::BaseAspect class PROJECTEXPLORER_EXPORT ArgumentsAspect : public Utils::BaseAspect

View File

@@ -28,6 +28,7 @@
#include "buildconfiguration.h" #include "buildconfiguration.h"
#include "customparser.h" #include "customparser.h"
#include "devicesupport/desktopdevice.h" #include "devicesupport/desktopdevice.h"
#include "devicesupport/devicemanager.h"
#include "devicesupport/idevice.h" #include "devicesupport/idevice.h"
#include "devicesupport/sshsettings.h" #include "devicesupport/sshsettings.h"
#include "kitinformation.h" #include "kitinformation.h"
@@ -323,7 +324,6 @@ public:
QMap<Utils::Id, QVariantMap> settingsData; QMap<Utils::Id, QVariantMap> settingsData;
Utils::Id runConfigId; Utils::Id runConfigId;
BuildTargetInfo buildTargetInfo; BuildTargetInfo buildTargetInfo;
BuildConfiguration::BuildType buildType = BuildConfiguration::Unknown;
FilePath buildDirectory; FilePath buildDirectory;
Environment buildEnvironment; Environment buildEnvironment;
Kit *kit = nullptr; // Not owned. Kit *kit = nullptr; // Not owned.
@@ -430,7 +430,6 @@ void RunControl::setTarget(Target *target)
d->buildTargetInfo = target->buildTarget(d->buildKey); d->buildTargetInfo = target->buildTarget(d->buildKey);
if (auto bc = target->activeBuildConfiguration()) { if (auto bc = target->activeBuildConfiguration()) {
d->buildType = bc->buildType();
d->buildDirectory = bc->buildDirectory(); d->buildDirectory = bc->buildDirectory();
d->buildEnvironment = bc->environment(); d->buildEnvironment = bc->environment();
} }
@@ -447,8 +446,8 @@ void RunControl::setKit(Kit *kit)
d->kit = kit; d->kit = kit;
d->macroExpander = kit->macroExpander(); d->macroExpander = kit->macroExpander();
if (d->runnable.device) if (!d->runnable.command.isEmpty())
setDevice(d->runnable.device); setDevice(DeviceManager::deviceForPath(d->runnable.command.executable()));
else else
setDevice(DeviceKitAspect::device(kit)); setDevice(DeviceKitAspect::device(kit));
} }
@@ -905,13 +904,50 @@ const Runnable &RunControl::runnable() const
return d->runnable; return d->runnable;
} }
void RunControl::setRunnable(const Runnable &runnable) const CommandLine &RunControl::commandLine() const
{ {
d->runnable = runnable; return d->runnable.command;
}
void RunControl::setCommandLine(const CommandLine &command)
{
d->runnable.command = command;
}
const FilePath &RunControl::workingDirectory() const
{
return d->runnable.workingDirectory;
}
void RunControl::setWorkingDirectory(const FilePath &workingDirectory)
{
d->runnable.workingDirectory = workingDirectory;
}
const Environment &RunControl::environment() const
{
return d->runnable.environment;
}
void RunControl::setEnvironment(const Environment &environment)
{
d->runnable.environment = environment;
}
const QVariantHash &RunControl::extraData() const
{
return d->runnable.extraData;
}
void RunControl::setExtraData(const QVariantHash &extraData)
{
d->runnable.extraData = extraData;
} }
QString RunControl::displayName() const QString RunControl::displayName() const
{ {
if (d->displayName.isEmpty())
return d->runnable.command.executable().toUserOutput();
return d->displayName; return d->displayName;
} }
@@ -975,11 +1011,6 @@ QString RunControl::buildKey() const
return d->buildKey; return d->buildKey;
} }
BuildConfiguration::BuildType RunControl::buildType() const
{
return d->buildType;
}
FilePath RunControl::buildDirectory() const FilePath RunControl::buildDirectory() const
{ {
return d->buildDirectory; return d->buildDirectory;
@@ -1242,7 +1273,10 @@ public:
State m_state = Inactive; State m_state = Inactive;
bool m_stopRequested = false; bool m_stopRequested = false;
Runnable m_runnable; Utils::CommandLine m_command;
Utils::FilePath m_workingDirectory;
Utils::Environment m_environment;
QVariantHash m_extraData;
ProcessResultData m_resultData; ProcessResultData m_resultData;
@@ -1398,12 +1432,12 @@ void SimpleTargetRunnerPrivate::handleStandardError()
void SimpleTargetRunnerPrivate::start() void SimpleTargetRunnerPrivate::start()
{ {
m_isLocal = m_runnable.device.isNull() || m_runnable.device.dynamicCast<const DesktopDevice>(); m_isLocal = !m_command.executable().needsDevice();
m_resultData = {}; m_resultData = {};
if (m_isLocal) { if (m_isLocal) {
Environment env = m_runnable.environment; Environment env = m_environment;
if (m_runAsRoot) if (m_runAsRoot)
RunControl::provideAskPassEntry(env); RunControl::provideAskPassEntry(env);
@@ -1413,7 +1447,7 @@ void SimpleTargetRunnerPrivate::start()
WinDebugInterface::startIfNeeded(); WinDebugInterface::startIfNeeded();
CommandLine cmdLine = m_runnable.command; CommandLine cmdLine = m_command;
if (HostOsInfo::isMacHost()) { if (HostOsInfo::isMacHost()) {
CommandLine disclaim(Core::ICore::libexecPath("disclaim")); CommandLine disclaim(Core::ICore::libexecPath("disclaim"));
@@ -1426,7 +1460,8 @@ void SimpleTargetRunnerPrivate::start()
} else { } else {
QTC_ASSERT(m_state == Inactive, return); QTC_ASSERT(m_state == Inactive, return);
if (!m_runnable.device) { const IDevice::ConstPtr device = DeviceManager::deviceForPath(m_command.executable());
if (!device) {
m_resultData.m_errorString = tr("Cannot run: No device."); m_resultData.m_errorString = tr("Cannot run: No device.");
m_resultData.m_error = QProcess::FailedToStart; m_resultData.m_error = QProcess::FailedToStart;
m_resultData.m_exitStatus = QProcess::CrashExit; m_resultData.m_exitStatus = QProcess::CrashExit;
@@ -1434,7 +1469,7 @@ void SimpleTargetRunnerPrivate::start()
return; return;
} }
if (!m_runnable.device->isEmptyCommandAllowed() && m_runnable.command.isEmpty()) { if (!device->isEmptyCommandAllowed() && m_command.isEmpty()) {
m_resultData.m_errorString = tr("Cannot run: No command given."); m_resultData.m_errorString = tr("Cannot run: No command given.");
m_resultData.m_error = QProcess::FailedToStart; m_resultData.m_error = QProcess::FailedToStart;
m_resultData.m_exitStatus = QProcess::CrashExit; m_resultData.m_exitStatus = QProcess::CrashExit;
@@ -1445,12 +1480,12 @@ void SimpleTargetRunnerPrivate::start()
m_state = Run; m_state = Run;
m_stopRequested = false; m_stopRequested = false;
m_process.setCommand(m_runnable.command); m_process.setCommand(m_command);
m_process.setEnvironment(m_runnable.environment); m_process.setEnvironment(m_environment);
m_process.setExtraData(m_runnable.extraData); m_process.setExtraData(m_extraData);
} }
m_process.setWorkingDirectory(m_runnable.workingDirectory); m_process.setWorkingDirectory(m_workingDirectory);
if (m_isLocal) if (m_isLocal)
m_outputCodec = QTextCodec::codecForLocale(); m_outputCodec = QTextCodec::codecForLocale();
@@ -1485,14 +1520,14 @@ void SimpleTargetRunnerPrivate::forwardDone()
{ {
if (m_stopReported) if (m_stopReported)
return; return;
const QString executable = m_runnable.command.executable().toUserOutput(); const QString executable = m_command.executable().toUserOutput();
QString msg = tr("%1 exited with code %2").arg(executable).arg(m_resultData.m_exitCode); QString msg = tr("%1 exited with code %2").arg(executable).arg(m_resultData.m_exitCode);
if (m_resultData.m_exitStatus == QProcess::CrashExit) if (m_resultData.m_exitStatus == QProcess::CrashExit)
msg = tr("%1 crashed.").arg(executable); msg = tr("%1 crashed.").arg(executable);
else if (m_stopForced) else if (m_stopForced)
msg = tr("The process was ended forcefully."); msg = tr("The process was ended forcefully.");
else if (m_resultData.m_error != QProcess::UnknownError) else if (m_resultData.m_error != QProcess::UnknownError)
msg = RunWorker::userMessageForProcessError(m_resultData.m_error, m_runnable.command.executable()); msg = RunWorker::userMessageForProcessError(m_resultData.m_error, m_command.executable());
q->appendMessage(msg, NormalMessageFormat); q->appendMessage(msg, NormalMessageFormat);
m_stopReported = true; m_stopReported = true;
q->reportStopped(); q->reportStopped();
@@ -1500,8 +1535,7 @@ void SimpleTargetRunnerPrivate::forwardDone()
void SimpleTargetRunnerPrivate::forwardStarted() void SimpleTargetRunnerPrivate::forwardStarted()
{ {
const bool isDesktop = m_runnable.device.isNull() const bool isDesktop = !m_command.executable().needsDevice();
|| m_runnable.device.dynamicCast<const DesktopDevice>();
if (isDesktop) { if (isDesktop) {
// Console processes only know their pid after being started // Console processes only know their pid after being started
ProcessHandle pid{privateApplicationPID()}; ProcessHandle pid{privateApplicationPID()};
@@ -1514,8 +1548,10 @@ void SimpleTargetRunnerPrivate::forwardStarted()
void SimpleTargetRunner::start() void SimpleTargetRunner::start()
{ {
d->m_runnable = runControl()->runnable(); d->m_command = runControl()->commandLine();
d->m_runnable.device = runControl()->device(); d->m_workingDirectory = runControl()->workingDirectory();
d->m_environment = runControl()->environment();
d->m_extraData = runControl()->extraData();
if (d->m_startModifier) if (d->m_startModifier)
d->m_startModifier(); d->m_startModifier();
@@ -1534,12 +1570,11 @@ void SimpleTargetRunner::start()
d->m_process.setTerminalMode(useTerminal ? Utils::TerminalMode::On : Utils::TerminalMode::Off); d->m_process.setTerminalMode(useTerminal ? Utils::TerminalMode::On : Utils::TerminalMode::Off);
d->m_runAsRoot = runAsRoot; d->m_runAsRoot = runAsRoot;
const QString msg = RunControl::tr("Starting %1...").arg(d->m_runnable.command.toUserOutput()); const QString msg = RunControl::tr("Starting %1...").arg(d->m_command.toUserOutput());
appendMessage(msg, Utils::NormalMessageFormat); appendMessage(msg, NormalMessageFormat);
const bool isDesktop = d->m_runnable.device.isNull() const bool isDesktop = !d->m_command.executable().needsDevice();
|| d->m_runnable.device.dynamicCast<const DesktopDevice>(); if (isDesktop && d->m_command.isEmpty()) {
if (isDesktop && d->m_runnable.command.isEmpty()) {
reportFailure(RunControl::tr("No executable specified.")); reportFailure(RunControl::tr("No executable specified."));
return; return;
} }
@@ -1559,31 +1594,30 @@ void SimpleTargetRunner::setStartModifier(const std::function<void ()> &startMod
CommandLine SimpleTargetRunner::commandLine() const CommandLine SimpleTargetRunner::commandLine() const
{ {
return d->m_runnable.command; return d->m_command;
} }
void SimpleTargetRunner::setCommandLine(const Utils::CommandLine &commandLine) void SimpleTargetRunner::setCommandLine(const Utils::CommandLine &commandLine)
{ {
d->m_runnable.command = commandLine; d->m_command = commandLine;
} }
void SimpleTargetRunner::setEnvironment(const Environment &environment) void SimpleTargetRunner::setEnvironment(const Environment &environment)
{ {
d->m_runnable.environment = environment; d->m_environment = environment;
} }
void SimpleTargetRunner::setWorkingDirectory(const FilePath &workingDirectory) void SimpleTargetRunner::setWorkingDirectory(const FilePath &workingDirectory)
{ {
d->m_runnable.workingDirectory = workingDirectory; d->m_workingDirectory = workingDirectory;
} }
void SimpleTargetRunner::forceRunOnHost() void SimpleTargetRunner::forceRunOnHost()
{ {
d->m_runnable.device = {}; const FilePath executable = d->m_command.executable();
const FilePath executable = d->m_runnable.command.executable();
if (executable.needsDevice()) { if (executable.needsDevice()) {
QTC_CHECK(false); QTC_CHECK(false);
d->m_runnable.command.setExecutable(FilePath::fromString(executable.path())); d->m_command.setExecutable(FilePath::fromString(executable.path()));
} }
} }
@@ -1796,11 +1830,6 @@ IDevice::ConstPtr RunWorker::device() const
return d->runControl->device(); return d->runControl->device();
} }
const Runnable &RunWorker::runnable() const
{
return d->runControl->runnable();
}
void RunWorker::addStartDependency(RunWorker *dependency) void RunWorker::addStartDependency(RunWorker *dependency)
{ {
d->startDependencies.append(dependency); d->startDependencies.append(dependency);
@@ -1906,11 +1935,6 @@ void RunWorker::stop()
reportStopped(); reportStopped();
} }
QString Runnable::displayName() const
{
return command.executable().toString();
}
// OutputFormatterFactory // OutputFormatterFactory
static QList<OutputFormatterFactory *> g_outputFormatterFactories; static QList<OutputFormatterFactory *> g_outputFormatterFactories;

View File

@@ -25,9 +25,7 @@
#pragma once #pragma once
#include "buildconfiguration.h"
#include "devicesupport/idevicefwd.h" #include "devicesupport/idevicefwd.h"
#include "projectexplorerconstants.h"
#include "runconfiguration.h" #include "runconfiguration.h"
#include <utils/commandline.h> #include <utils/commandline.h>
@@ -50,8 +48,6 @@ class OutputLineParser;
} // Utils } // Utils
namespace ProjectExplorer { namespace ProjectExplorer {
class GlobalOrProjectAspect;
class Node;
class RunConfiguration; class RunConfiguration;
class RunControl; class RunControl;
class Target; class Target;
@@ -71,11 +67,7 @@ public:
Utils::CommandLine command; Utils::CommandLine command;
Utils::FilePath workingDirectory; Utils::FilePath workingDirectory;
Utils::Environment environment; Utils::Environment environment;
IDeviceConstPtr device; // Override the kit's device. Keep unset by default.
QVariantHash extraData; QVariantHash extraData;
// FIXME: Not necessarily a display name
QString displayName() const;
}; };
class PROJECTEXPLORER_EXPORT RunWorker : public QObject class PROJECTEXPLORER_EXPORT RunWorker : public QObject
@@ -102,7 +94,6 @@ public:
// Part of read-only interface of RunControl for convenience. // Part of read-only interface of RunControl for convenience.
void appendMessage(const QString &msg, Utils::OutputFormat format, bool appendNewLine = true); void appendMessage(const QString &msg, Utils::OutputFormat format, bool appendNewLine = true);
IDeviceConstPtr device() const; IDeviceConstPtr device() const;
const Runnable &runnable() const;
// States // States
void initiateStart(); void initiateStart();
@@ -211,7 +202,7 @@ public:
bool supportsReRunning() const; bool supportsReRunning() const;
virtual QString displayName() const; QString displayName() const;
void setDisplayName(const QString &displayName); void setDisplayName(const QString &displayName);
bool isRunning() const; bool isRunning() const;
@@ -239,7 +230,6 @@ public:
} }
QString buildKey() const; QString buildKey() const;
BuildConfiguration::BuildType buildType() const;
Utils::FilePath buildDirectory() const; Utils::FilePath buildDirectory() const;
Utils::Environment buildEnvironment() const; Utils::Environment buildEnvironment() const;
@@ -252,7 +242,18 @@ public:
Utils::Id runMode() const; Utils::Id runMode() const;
const Runnable &runnable() const; const Runnable &runnable() const;
void setRunnable(const Runnable &runnable);
const Utils::CommandLine &commandLine() const;
void setCommandLine(const Utils::CommandLine &command);
const Utils::FilePath &workingDirectory() const;
void setWorkingDirectory(const Utils::FilePath &workingDirectory);
const Utils::Environment &environment() const;
void setEnvironment(const Utils::Environment &environment);
const QVariantHash &extraData() const;
void setExtraData(const QVariantHash &extraData);
static bool showPromptToStopDialog(const QString &title, const QString &text, static bool showPromptToStopDialog(const QString &title, const QString &text,
const QString &stopButtonText = QString(), const QString &stopButtonText = QString(),

View File

@@ -89,24 +89,9 @@ PySideBuildStep::PySideBuildStep(BuildStepList *bsl, Id id)
setWorkingDirectoryProvider([this] { return target()->project()->projectDirectory(); }); setWorkingDirectoryProvider([this] { return target()->project()->projectDirectory(); });
} }
void PySideBuildStep::updateInterpreter(const Utils::FilePath &python) void PySideBuildStep::updatePySideProjectPath(const Utils::FilePath &pySideProjectPath)
{ {
Utils::FilePath pySideProjectPath; m_pysideProject->setFilePath(pySideProjectPath);
const PipPackage pySide6Package("PySide6");
const PipPackageInfo info = pySide6Package.info(python);
for (const FilePath &file : qAsConst(info.files)) {
if (file.fileName() == HostOsInfo::withExecutableSuffix("pyside6-project")) {
pySideProjectPath = info.location.resolvePath(file);
pySideProjectPath = pySideProjectPath.cleanPath();
break;
}
}
if (!pySideProjectPath.isExecutableFile())
pySideProjectPath = Environment::systemEnvironment().searchInPath("pyside6-project");
if (pySideProjectPath.isExecutableFile())
m_pysideProject->setFilePath(pySideProjectPath);
} }
void PySideBuildStep::doRun() void PySideBuildStep::doRun()

View File

@@ -50,7 +50,7 @@ class PySideBuildStep : public ProjectExplorer::AbstractProcessStep
Q_OBJECT Q_OBJECT
public: public:
PySideBuildStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id); PySideBuildStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id);
void updateInterpreter(const Utils::FilePath &python); void updatePySideProjectPath(const Utils::FilePath &pySideProjectPath);
private: private:
Utils::StringAspect *m_pysideProject; Utils::StringAspect *m_pysideProject;

View File

@@ -140,85 +140,99 @@ private:
class PythonRunConfiguration : public RunConfiguration class PythonRunConfiguration : public RunConfiguration
{ {
public: public:
PythonRunConfiguration(Target *target, Id id) PythonRunConfiguration(Target *target, Id id);
: RunConfiguration(target, id) void currentInterpreterChanged();
{ };
auto interpreterAspect = addAspect<InterpreterAspect>();
interpreterAspect->setSettingsKey("PythonEditor.RunConfiguation.Interpreter");
interpreterAspect->setSettingsDialogId(Constants::C_PYTHONOPTIONS_PAGE_ID);
connect(interpreterAspect, &InterpreterAspect::changed, PythonRunConfiguration::PythonRunConfiguration(Target *target, Id id)
this, &PythonRunConfiguration::currentInterpreterChanged); : RunConfiguration(target, id)
{
auto interpreterAspect = addAspect<InterpreterAspect>();
interpreterAspect->setSettingsKey("PythonEditor.RunConfiguation.Interpreter");
interpreterAspect->setSettingsDialogId(Constants::C_PYTHONOPTIONS_PAGE_ID);
connect(PythonSettings::instance(), &PythonSettings::interpretersChanged, connect(interpreterAspect, &InterpreterAspect::changed,
interpreterAspect, &InterpreterAspect::updateInterpreters); this, &PythonRunConfiguration::currentInterpreterChanged);
QList<Interpreter> interpreters = PythonSettings::detectPythonVenvs( connect(PythonSettings::instance(), &PythonSettings::interpretersChanged,
project()->projectDirectory()); interpreterAspect, &InterpreterAspect::updateInterpreters);
interpreterAspect->updateInterpreters(PythonSettings::interpreters());
Interpreter defaultInterpreter = interpreters.isEmpty()
? PythonSettings::defaultInterpreter()
: interpreters.first();
if (!defaultInterpreter.command.isExecutableFile())
defaultInterpreter = PythonSettings::interpreters().value(0);
interpreterAspect->setDefaultInterpreter(defaultInterpreter);
auto bufferedAspect = addAspect<BoolAspect>(); QList<Interpreter> interpreters = PythonSettings::detectPythonVenvs(
bufferedAspect->setSettingsKey("PythonEditor.RunConfiguation.Buffered"); project()->projectDirectory());
bufferedAspect->setLabel(tr("Buffered output"), BoolAspect::LabelPlacement::AtCheckBox); interpreterAspect->updateInterpreters(PythonSettings::interpreters());
bufferedAspect->setToolTip(tr("Enabling improves output performance, " Interpreter defaultInterpreter = interpreters.isEmpty() ? PythonSettings::defaultInterpreter()
"but results in delayed output.")); : interpreters.first();
if (!defaultInterpreter.command.isExecutableFile())
defaultInterpreter = PythonSettings::interpreters().value(0);
interpreterAspect->setDefaultInterpreter(defaultInterpreter);
auto scriptAspect = addAspect<MainScriptAspect>(); auto bufferedAspect = addAspect<BoolAspect>();
scriptAspect->setSettingsKey("PythonEditor.RunConfiguation.Script"); bufferedAspect->setSettingsKey("PythonEditor.RunConfiguation.Buffered");
scriptAspect->setLabelText(tr("Script:")); bufferedAspect->setLabel(tr("Buffered output"), BoolAspect::LabelPlacement::AtCheckBox);
scriptAspect->setDisplayStyle(StringAspect::LabelDisplay); bufferedAspect->setToolTip(tr("Enabling improves output performance, "
"but results in delayed output."));
addAspect<LocalEnvironmentAspect>(target); auto scriptAspect = addAspect<MainScriptAspect>();
scriptAspect->setSettingsKey("PythonEditor.RunConfiguation.Script");
scriptAspect->setLabelText(tr("Script:"));
scriptAspect->setDisplayStyle(StringAspect::LabelDisplay);
auto argumentsAspect = addAspect<ArgumentsAspect>(macroExpander()); addAspect<LocalEnvironmentAspect>(target);
addAspect<WorkingDirectoryAspect>(nullptr); auto argumentsAspect = addAspect<ArgumentsAspect>(macroExpander());
addAspect<TerminalAspect>();
setCommandLineGetter([bufferedAspect, interpreterAspect, argumentsAspect, scriptAspect] { addAspect<WorkingDirectoryAspect>(macroExpander(), nullptr);
CommandLine cmd{interpreterAspect->currentInterpreter().command}; addAspect<TerminalAspect>();
if (!bufferedAspect->value())
cmd.addArg("-u");
cmd.addArg(scriptAspect->filePath().fileName());
cmd.addArgs(argumentsAspect->arguments(), CommandLine::Raw);
return cmd;
});
setUpdater([this, scriptAspect] { setCommandLineGetter([bufferedAspect, interpreterAspect, argumentsAspect, scriptAspect] {
const BuildTargetInfo bti = buildTargetInfo(); CommandLine cmd{interpreterAspect->currentInterpreter().command};
const QString script = bti.targetFilePath.toUserOutput(); if (!bufferedAspect->value())
setDefaultDisplayName(tr("Run %1").arg(script)); cmd.addArg("-u");
scriptAspect->setValue(script); cmd.addArg(scriptAspect->filePath().fileName());
aspect<WorkingDirectoryAspect>()->setDefaultWorkingDirectory(bti.targetFilePath.parentDir()); cmd.addArgs(argumentsAspect->arguments(), CommandLine::Raw);
}); return cmd;
});
connect(target, &Target::buildSystemUpdated, this, &RunConfiguration::update); setUpdater([this, scriptAspect] {
const BuildTargetInfo bti = buildTargetInfo();
const QString script = bti.targetFilePath.toUserOutput();
setDefaultDisplayName(tr("Run %1").arg(script));
scriptAspect->setValue(script);
aspect<WorkingDirectoryAspect>()->setDefaultWorkingDirectory(bti.targetFilePath.parentDir());
});
connect(target, &Target::buildSystemUpdated, this, &RunConfiguration::update);
}
void PythonRunConfiguration::currentInterpreterChanged()
{
const FilePath python = aspect<InterpreterAspect>()->currentInterpreter().command;
BuildStepList *buildSteps = target()->activeBuildConfiguration()->buildSteps();
Utils::FilePath pySideProjectPath;
const PipPackage pySide6Package("PySide6");
const PipPackageInfo info = pySide6Package.info(python);
for (const FilePath &file : qAsConst(info.files)) {
if (file.fileName() == HostOsInfo::withExecutableSuffix("pyside6-project")) {
pySideProjectPath = info.location.resolvePath(file);
pySideProjectPath = pySideProjectPath.cleanPath();
break;
}
} }
void currentInterpreterChanged() if (auto pySideBuildStep = buildSteps->firstOfType<PySideBuildStep>())
{ pySideBuildStep->updatePySideProjectPath(pySideProjectPath);
const FilePath python = aspect<InterpreterAspect>()->currentInterpreter().command;
BuildStepList *buildSteps = target()->activeBuildConfiguration()->buildSteps(); for (FilePath &file : project()->files(Project::AllFiles)) {
if (auto pySideBuildStep = buildSteps->firstOfType<PySideBuildStep>()) if (auto document = TextEditor::TextDocument::textDocumentForFilePath(file)) {
pySideBuildStep->updateInterpreter(python); if (document->mimeType() == Constants::C_PY_MIMETYPE) {
PyLSConfigureAssistant::openDocumentWithPython(python, document);
for (FilePath &file : project()->files(Project::AllFiles)) { PySideInstaller::checkPySideInstallation(python, document);
if (auto document = TextEditor::TextDocument::textDocumentForFilePath(file)) {
if (document->mimeType() == Constants::C_PY_MIMETYPE) {
PyLSConfigureAssistant::openDocumentWithPython(python, document);
PySideInstaller::checkPySideInstallation(python, document);
}
} }
} }
} }
}; }
PythonRunConfigurationFactory::PythonRunConfigurationFactory() PythonRunConfigurationFactory::PythonRunConfigurationFactory()
{ {

Some files were not shown because too many files have changed in this diff Show More