forked from qt-creator/qt-creator
Merge remote-tracking branch 'origin/master' into 8.0
Change-Id: I0ab7200a8696e52122b2739cbc740e940336f962
This commit is contained in:
@@ -837,6 +837,8 @@ void Qt5InformationNodeInstanceServer::updateActiveSceneToEditView3D(bool timerC
|
||||
auto helper = qobject_cast<QmlDesigner::Internal::GeneralHelper *>(m_3dHelper);
|
||||
if (helper)
|
||||
helper->storeToolState(helper->globalStateId(), helper->lastSceneIdKey(), QVariant(sceneId), 0);
|
||||
#else
|
||||
Q_UNUSED(timerCall)
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1157,6 +1159,8 @@ void Qt5InformationNodeInstanceServer::doRenderModelNode3DImageView(const Reques
|
||||
{PuppetToCreatorCommand::RenderModelNodePreviewImage,
|
||||
QVariant::fromValue(imgContainer)});
|
||||
}
|
||||
#else
|
||||
Q_UNUSED(cmd)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@@ -82,6 +82,9 @@ void Quick3DNodeInstance::initialize(const ObjectNodeInstance::Pointer &objectNo
|
||||
m_dummyRootViewCreateFunction = "createViewForNode";
|
||||
|
||||
Quick3DRenderableNodeInstance::initialize(objectNodeInstance, flags);
|
||||
#else
|
||||
Q_UNUSED(objectNodeInstance)
|
||||
Q_UNUSED(flags)
|
||||
#endif // QUICK3D_MODULE
|
||||
}
|
||||
|
||||
|
@@ -32121,16 +32121,16 @@ Spustil jste Qemu?</translation>
|
||||
<translation>Qt %1 (%2)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>qmake does not exist or is not executable</source>
|
||||
<translation>Soubor qmake neexistuje nebo není spustitelný</translation>
|
||||
<source>%1 does not exist or is not executable</source>
|
||||
<translation>Soubor %1 neexistuje nebo není spustitelný</translation>
|
||||
</message>
|
||||
<message>
|
||||
<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 "make install"</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake 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>
|
||||
<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 %1 chybná?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The default mkspec symlink is broken.</source>
|
||||
|
@@ -35382,16 +35382,16 @@ For flere detaljer, se /etc/sysctl.d/10-ptrace.conf
|
||||
<translation>Ingen qmake-sti sat</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>qmake does not exist or is not executable</source>
|
||||
<translation>qmake findes ikke eller er ikke eksekverbar</translation>
|
||||
<source>%1 does not exist or is not executable</source>
|
||||
<translation>%1 findes ikke eller er ikke eksekverbar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<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>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source>
|
||||
<translation>Kunne ikke beslutte stien til binærene af Qt installationen, måske er qmake-stien forkert?</translation>
|
||||
<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 %1-stien forkert?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The default mkspec symlink is broken.</source>
|
||||
|
@@ -10113,8 +10113,8 @@ Dies ist unabhängig vom Wert der Eigenschaft "visible" in QML.</trans
|
||||
<translation>Es ist keine qmake-Pfad gesetzt</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>qmake does not exist or is not executable</source>
|
||||
<translation>Die qmake-Datei existiert nicht oder ist nicht ausführbar</translation>
|
||||
<source>%1 does not exist or is not executable</source>
|
||||
<translation>Die %1-Datei existiert nicht oder ist nicht ausführbar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Qt version has no name</source>
|
||||
@@ -10141,8 +10141,8 @@ Dies ist unabhängig vom Wert der Eigenschaft "visible" in QML.</trans
|
||||
<translation>Die Qt-Version ist nicht richtig installiert, führen Sie bitte make install aus</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake 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>
|
||||
<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 %1 falsch?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The default mkspec symlink is broken.</source>
|
||||
|
@@ -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>
|
||||
</message>
|
||||
<message>
|
||||
<source>qmake does not exist or is not executable</source>
|
||||
<translation>qmake n'existe pas ou n'est pas exécutable</translation>
|
||||
<source>%1 does not exist or is not executable</source>
|
||||
<translation>%1 n'existe pas ou n'est pas exécutable</translation>
|
||||
</message>
|
||||
<message>
|
||||
<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'est pas correctement installée, veuillez exécuter make install</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake 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>
|
||||
<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 %1 est faux ?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The default mkspec symlink is broken.</source>
|
||||
|
@@ -41628,7 +41628,7 @@ Saving failed.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</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>
|
||||
</message>
|
||||
<message>
|
||||
@@ -41636,7 +41636,7 @@ Saving failed.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</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>
|
||||
</message>
|
||||
<message>
|
||||
|
@@ -26108,16 +26108,16 @@ Do you want to save the data first?</source>
|
||||
<translation>qmake のパスが設定されていません</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>qmake does not exist or is not executable</source>
|
||||
<translation>qmake が存在しないか実行可能ではありません</translation>
|
||||
<source>%1 does not exist or is not executable</source>
|
||||
<translation>%1 が存在しないか実行可能ではありません</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Qt version is not properly installed, please run make install</source>
|
||||
<translation>Qt バージョンが正しくインストールされていません。make install を実行してください</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source>
|
||||
<translation>Qt インストール先のパスが特定できませんでした。qmake のパスが間違っていませんか?</translation>
|
||||
<source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
|
||||
<translation>Qt インストール先のパスが特定できませんでした。%1 のパスが間違っていませんか?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The default mkspec symlink is broken.</source>
|
||||
|
@@ -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>
|
||||
</message>
|
||||
<message>
|
||||
<source>qmake does not exist or is not executable</source>
|
||||
<translation>Brak qmake lub nie jest on plikiem wykonywalnym</translation>
|
||||
<source>%1 does not exist or is not executable</source>
|
||||
<translation>Brak %1 lub nie jest on plikiem wykonywalnym</translation>
|
||||
</message>
|
||||
<message>
|
||||
<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>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source>
|
||||
<translation>Nie można określić ścieżki do plików binarnych instalacji Qt. Sprawdź ścieżkę do qmake.</translation>
|
||||
<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 %1.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The default mkspec symlink is broken.</source>
|
||||
|
@@ -45319,8 +45319,8 @@ For more details, see /etc/sysctl.d/10-ptrace.conf
|
||||
<translation>Путь к qmake не указан</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>qmake does not exist or is not executable</source>
|
||||
<translation>qmake отсутствует или не запускается</translation>
|
||||
<source>%1 does not exist or is not executable</source>
|
||||
<translation>%1 отсутствует или не запускается</translation>
|
||||
</message>
|
||||
<message>
|
||||
<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>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source>
|
||||
<translation>Не удалось определить путь к утилитам Qt. Может путь к qmake неверен?</translation>
|
||||
<source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
|
||||
<translation>Не удалось определить путь к утилитам Qt. Может путь к %1 неверен?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The default mkspec symlink is broken.</source>
|
||||
|
@@ -21803,8 +21803,8 @@ Projekte programov QML izvede pregledovalnik QML in jih ni potrebno zgraditi.</t
|
||||
</message>
|
||||
<message>
|
||||
<location line="+58"/>
|
||||
<source>qmake does not exist or is not executable</source>
|
||||
<translation>Datoteka »qmake« ne obstaja ali pa ni izvršljiva</translation>
|
||||
<source>%1 does not exist or is not executable</source>
|
||||
<translation>Datoteka »%1« ne obstaja ali pa ni izvršljiva</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
@@ -21813,8 +21813,8 @@ Projekte programov QML izvede pregledovalnik QML in jih ni potrebno zgraditi.</t
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source>
|
||||
<translation>Ni bilo moč ugotoviti poti do programov namestitve Qt. Morda je pot do qmake napačna?</translation>
|
||||
<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 %1 napačna?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
|
@@ -22711,8 +22711,8 @@ For more details, see /etc/sysctl.d/10-ptrace.conf
|
||||
<translation>Шлях до qmake не задано</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>qmake does not exist or is not executable</source>
|
||||
<translation>qmake не існує або не є виконуваним модулем</translation>
|
||||
<source>%1 does not exist or is not executable</source>
|
||||
<translation>%1 не існує або не є виконуваним модулем</translation>
|
||||
</message>
|
||||
<message>
|
||||
<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>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source>
|
||||
<translation>Не вдалось визначити шлях до виконуваних модулів встановлення Qt, можливо, шлях до qmake помилковий?</translation>
|
||||
<source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
|
||||
<translation>Не вдалось визначити шлях до виконуваних модулів встановлення Qt, можливо, шлях до %1 помилковий?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The default mkspec symlink is broken.</source>
|
||||
|
@@ -27948,8 +27948,8 @@ Did you start Qemu?</source>
|
||||
<translation>没有设置qmake路径</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>qmake does not exist or is not executable</source>
|
||||
<translation>qmake不存在或者不可执行</translation>
|
||||
<source>%1 does not exist or is not executable</source>
|
||||
<translation>%1不存在或者不可执行</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Qt version has no name</source>
|
||||
@@ -27976,8 +27976,8 @@ Did you start Qemu?</source>
|
||||
<translation>Qt没有被正确安装,请运行make install</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source>
|
||||
<translation>无法确定Qt安装的二进制所在的路径,或许qmake的路径设置出现了错误?</translation>
|
||||
<source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
|
||||
<translation>无法确定Qt安装的二进制所在的路径,或许%1的路径设置出现了错误?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The default mkspec symlink is broken.</source>
|
||||
|
@@ -15636,8 +15636,8 @@ Requires <b>Qt 4.7.4</b> or newer.</source>
|
||||
<translation>沒有設定 qmake 路徑</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>qmake does not exist or is not executable</source>
|
||||
<translation>qmake 不存在或無法執行</translation>
|
||||
<source>%1 does not exist or is not executable</source>
|
||||
<translation>%1 不存在或無法執行</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Qt version has no name</source>
|
||||
@@ -15664,8 +15664,8 @@ Requires <b>Qt 4.7.4</b> or newer.</source>
|
||||
<translation>Qt 沒有被正確安裝,請執行 make install</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source>
|
||||
<translation>無法決定 Qt 安裝版的路徑。也許是 qmake 的路徑設定有錯誤?</translation>
|
||||
<source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
|
||||
<translation>無法決定 Qt 安裝版的路徑。也許是 %1 的路徑設定有錯誤?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The default mkspec symlink is broken.</source>
|
||||
|
@@ -629,7 +629,7 @@ ClassOrNamespace *ClassOrNamespace::parent() const
|
||||
return _parent;
|
||||
}
|
||||
|
||||
QList<ClassOrNamespace *> ClassOrNamespace::usings() const
|
||||
const QList<ClassOrNamespace *> ClassOrNamespace::usings() const
|
||||
{
|
||||
const_cast<ClassOrNamespace *>(this)->flush();
|
||||
return _usings;
|
||||
@@ -641,7 +641,7 @@ QList<Enum *> ClassOrNamespace::unscopedEnums() const
|
||||
return _enums;
|
||||
}
|
||||
|
||||
QList<Symbol *> ClassOrNamespace::symbols() const
|
||||
const QList<Symbol *> ClassOrNamespace::symbols() const
|
||||
{
|
||||
const_cast<ClassOrNamespace *>(this)->flush();
|
||||
return _symbols;
|
||||
|
@@ -70,9 +70,9 @@ public:
|
||||
ClassOrNamespace *instantiationOrigin() const;
|
||||
|
||||
ClassOrNamespace *parent() const;
|
||||
QList<ClassOrNamespace *> usings() const;
|
||||
const QList<ClassOrNamespace *> usings() const;
|
||||
QList<Enum *> unscopedEnums() const;
|
||||
QList<Symbol *> symbols() const;
|
||||
const QList<Symbol *> symbols() const;
|
||||
|
||||
ClassOrNamespace *globalNamespace() const;
|
||||
|
||||
|
@@ -41,7 +41,61 @@ bool BuildableHelperLibrary::isQtChooser(const FilePath &filePath)
|
||||
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=\"");
|
||||
QtcProcess proc;
|
||||
@@ -51,6 +105,10 @@ FilePath BuildableHelperLibrary::qtChooserToQmakePath(const FilePath &qtChooser)
|
||||
if (proc.result() != ProcessResult::FinishedWithSuccess)
|
||||
return {};
|
||||
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);
|
||||
if (pos == -1)
|
||||
return {};
|
||||
@@ -59,44 +117,13 @@ FilePath BuildableHelperLibrary::qtChooserToQmakePath(const FilePath &qtChooser)
|
||||
if (end == -1)
|
||||
return {};
|
||||
|
||||
FilePath qmake = qtChooser;
|
||||
qmake.setPath(output.mid(pos, end - pos) + "/qmake");
|
||||
return qmake;
|
||||
}
|
||||
|
||||
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;
|
||||
FilePath queryToolPath = qtChooser;
|
||||
for (const QString &queryTool : queryToolNames()) {
|
||||
queryToolPath.setPath(output.mid(pos, end - pos) + "/" + queryTool);
|
||||
if (queryToolPath.exists())
|
||||
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 {};
|
||||
return queryToolPath;
|
||||
}
|
||||
|
||||
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 qmakeList;
|
||||
FilePaths queryToolList;
|
||||
std::set<QString> canonicalEnvPaths;
|
||||
const FilePaths paths = env.path();
|
||||
for (const FilePath &path : paths) {
|
||||
if (!canonicalEnvPaths.insert(path.toFileInfo().canonicalFilePath()).second)
|
||||
continue;
|
||||
const FilePath qmake = findQmakeInDir(path);
|
||||
if (qmake.isEmpty())
|
||||
const FilePath queryTool = findQueryToolInDir(path);
|
||||
if (queryTool.isEmpty())
|
||||
continue;
|
||||
qmakeList << qmake;
|
||||
if (maxCount != -1 && qmakeList.size() == maxCount)
|
||||
queryToolList << queryTool;
|
||||
if (maxCount != -1 && queryToolList.size() == maxCount)
|
||||
break;
|
||||
}
|
||||
return qmakeList;
|
||||
return queryToolList;
|
||||
}
|
||||
|
||||
QString BuildableHelperLibrary::qtVersionForQMake(const FilePath &qmakePath)
|
||||
QString BuildableHelperLibrary::filterForQtQueryToolsFileDialog()
|
||||
{
|
||||
if (qmakePath.isEmpty())
|
||||
return QString();
|
||||
|
||||
QtcProcess qmake;
|
||||
qmake.setTimeoutS(5);
|
||||
qmake.setCommand({qmakePath, {"--version"}});
|
||||
qmake.runBlocking();
|
||||
if (qmake.result() != ProcessResult::FinishedWithSuccess) {
|
||||
qWarning() << qmake.exitMessage();
|
||||
return QString();
|
||||
QStringList toolFilters;
|
||||
for (const QString &queryTool : queryToolNames()) {
|
||||
for (const QString &tool: BuildableHelperLibrary::possibleQtQueryTools(queryTool)) {
|
||||
QString toolFilter;
|
||||
if (HostOsInfo::isMacHost())
|
||||
// work around QTBUG-7739 that prohibits filters that don't start with *
|
||||
toolFilter += QLatin1Char('*');
|
||||
toolFilter += tool;
|
||||
if (HostOsInfo::isAnyUnixHost() && !HostOsInfo::isMacHost())
|
||||
// kde bug, we need at least one wildcard character
|
||||
// see QTCREATORBUG-7771
|
||||
toolFilter += QLatin1Char('*');
|
||||
toolFilters.append(toolFilter);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
return queryToolNames().join(", ") + " (" + toolFilters.join(" ") + ")";
|
||||
}
|
||||
|
||||
QString BuildableHelperLibrary::filterForQmakeFileDialog()
|
||||
QStringList BuildableHelperLibrary::possibleQtQueryTools(const QString &tool)
|
||||
{
|
||||
QString filter = QLatin1String("qmake (");
|
||||
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 Windows it is "<queryTool>.exe" or "<queryTool>.bat"
|
||||
// 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
|
||||
// named "qmake", but the file dialog always checks against resolved links (native Cocoa issue)
|
||||
QStringList commands(HostOsInfo::withExecutableSuffix("qmake*"));
|
||||
// named <tool>, but the file dialog always checks against resolved links (native Cocoa issue)
|
||||
QStringList tools(HostOsInfo::withExecutableSuffix(tool + "*"));
|
||||
|
||||
// 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())
|
||||
commands.append("qmake*.bat");
|
||||
return commands;
|
||||
tools.append(tool + "*.bat");
|
||||
return tools;
|
||||
}
|
||||
|
||||
} // namespace Utils
|
||||
|
@@ -45,12 +45,11 @@ public:
|
||||
static FilePath findSystemQt(const Environment &env);
|
||||
static FilePaths findQtsInEnvironment(const Environment &env, int maxCount = -1);
|
||||
static bool isQtChooser(const FilePath &filePath);
|
||||
static FilePath qtChooserToQmakePath(const FilePath &path);
|
||||
// return true if the qmake at qmakePath is a Qt (used by QtVersion)
|
||||
static QString qtVersionForQMake(const FilePath &qmakePath);
|
||||
// returns something like qmake4, qmake, qmake-qt4 or whatever distributions have chosen (used by QtVersion)
|
||||
static QStringList possibleQMakeCommands();
|
||||
static QString filterForQmakeFileDialog();
|
||||
static FilePath qtChooserToQueryToolPath(const FilePath &path);
|
||||
// returns something like qmake4, qmake, qmake-qt4, qtpaths
|
||||
// or whatever distributions have chosen (used by QtVersion)
|
||||
static QStringList possibleQtQueryTools(const QString &tool);
|
||||
static QString filterForQtQueryToolsFileDialog();
|
||||
};
|
||||
|
||||
} // Utils
|
||||
|
@@ -65,6 +65,7 @@ HostOsInfo::HostArchitecture HostOsInfo::hostArchitecture()
|
||||
case PROCESSOR_ARCHITECTURE_IA64:
|
||||
return HostOsInfo::HostArchitectureItanium;
|
||||
case PROCESSOR_ARCHITECTURE_ARM:
|
||||
case PROCESSOR_ARCHITECTURE_ARM64:
|
||||
return HostOsInfo::HostArchitectureArm;
|
||||
default:
|
||||
return HostOsInfo::HostArchitectureUnknown;
|
||||
|
@@ -192,7 +192,7 @@ public:
|
||||
EnvironmentChange m_environmentChange;
|
||||
BinaryVersionToolTipEventFilter *m_binaryVersionToolTipEventFilter = nullptr;
|
||||
QList<QAbstractButton *> m_buttons;
|
||||
MacroExpander *m_macroExpander = globalMacroExpander();
|
||||
const MacroExpander *m_macroExpander = globalMacroExpander();
|
||||
std::function<void()> m_openTerminal;
|
||||
};
|
||||
|
||||
@@ -732,7 +732,7 @@ void PathChooser::setHistoryCompleter(const QString &historyKey, bool restoreLas
|
||||
d->m_lineEdit->setHistoryCompleter(historyKey, restoreLastItemFromHistory);
|
||||
}
|
||||
|
||||
void PathChooser::setMacroExpander(MacroExpander *macroExpander)
|
||||
void PathChooser::setMacroExpander(const MacroExpander *macroExpander)
|
||||
{
|
||||
d->m_macroExpander = macroExpander;
|
||||
}
|
||||
|
@@ -136,7 +136,7 @@ public:
|
||||
// Sets a macro expander that is used when producing path and fileName.
|
||||
// By default, the global expander is used.
|
||||
// nullptr can be passed to disable macro expansion.
|
||||
void setMacroExpander(MacroExpander *macroExpander);
|
||||
void setMacroExpander(const MacroExpander *macroExpander);
|
||||
|
||||
bool isReadOnly() const;
|
||||
void setReadOnly(bool b);
|
||||
|
@@ -145,7 +145,8 @@ QTCREATOR_UTILS_EXPORT bool is64BitWindowsSystem()
|
||||
SYSTEM_INFO systemInfo;
|
||||
GetNativeSystemInfo(&systemInfo);
|
||||
return systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64
|
||||
|| systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64;
|
||||
|| systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64
|
||||
|| systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM64;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
|
@@ -28,11 +28,11 @@
|
||||
#include "androidconfigurations.h"
|
||||
#include "androidconstants.h"
|
||||
#include "androidmanager.h"
|
||||
#include "androidrunconfiguration.h"
|
||||
|
||||
#include <debugger/debuggerkitinformation.h>
|
||||
#include <debugger/debuggerrunconfigurationaspect.h>
|
||||
|
||||
#include <projectexplorer/buildconfiguration.h>
|
||||
#include <projectexplorer/projectexplorerconstants.h>
|
||||
#include <projectexplorer/environmentaspect.h>
|
||||
#include <projectexplorer/runconfigurationaspects.h>
|
||||
@@ -274,9 +274,8 @@ AndroidRunnerWorker::AndroidRunnerWorker(RunWorker *runner, const QString &packa
|
||||
qCDebug(androidRunWorkerLog) << "Environment variables for the app"
|
||||
<< m_extraEnvVars.toStringList();
|
||||
|
||||
if (target->buildConfigurations().first()->buildType() != BuildConfiguration::BuildType::Release) {
|
||||
m_extraAppParams = runControl->runnable().command.arguments();
|
||||
}
|
||||
if (target->buildConfigurations().first()->buildType() != BuildConfiguration::BuildType::Release)
|
||||
m_extraAppParams = runControl->commandLine().arguments();
|
||||
|
||||
if (auto aspect = runControl->aspect(Constants::ANDROID_AM_START_ARGS)) {
|
||||
QTC_CHECK(aspect->value.type() == QVariant::String);
|
||||
|
@@ -32,6 +32,7 @@
|
||||
#include "../itestframework.h"
|
||||
#include "../testsettings.h"
|
||||
|
||||
#include <projectexplorer/buildconfiguration.h>
|
||||
#include <projectexplorer/buildsystem.h>
|
||||
#include <projectexplorer/environmentaspect.h>
|
||||
#include <projectexplorer/project.h>
|
||||
|
@@ -53,7 +53,7 @@ public:
|
||||
exeAspect->setPlaceHolderText(tr("Unknown"));
|
||||
|
||||
addAspect<ArgumentsAspect>(macroExpander());
|
||||
addAspect<WorkingDirectoryAspect>(nullptr);
|
||||
addAspect<WorkingDirectoryAspect>(macroExpander(), nullptr);
|
||||
|
||||
setUpdater([this, exeAspect] {
|
||||
const BuildTargetInfo bti = buildTargetInfo();
|
||||
@@ -80,7 +80,7 @@ public:
|
||||
exeAspect->setExpectedKind(PathChooser::Any);
|
||||
|
||||
addAspect<ArgumentsAspect>(macroExpander());
|
||||
addAspect<WorkingDirectoryAspect>(nullptr);
|
||||
addAspect<WorkingDirectoryAspect>(macroExpander(), nullptr);
|
||||
|
||||
setDefaultDisplayName(RunConfigurationFactory::decoratedTargetName(tr("Custom Executable"), target));
|
||||
}
|
||||
|
@@ -179,8 +179,6 @@ bool GdbServerProvider::aboutToRun(DebuggerRunTool *runTool,
|
||||
|
||||
Runnable inferior;
|
||||
inferior.command.setExecutable(bin);
|
||||
inferior.extraData.insert(Debugger::Constants::kPeripheralDescriptionFile,
|
||||
m_peripheralDescriptionFile.toVariant());
|
||||
if (const auto argAspect = runControl->aspect<ArgumentsAspect>())
|
||||
inferior.command.setArguments(argAspect->arguments);
|
||||
runTool->setInferior(inferior);
|
||||
@@ -191,6 +189,7 @@ bool GdbServerProvider::aboutToRun(DebuggerRunTool *runTool,
|
||||
runTool->setRemoteChannel(channelString());
|
||||
runTool->setUseContinueInsteadOfRun(true);
|
||||
runTool->setUseExtendedRemote(useExtendedRemote());
|
||||
runTool->runParameters().peripheralDescriptionFile = m_peripheralDescriptionFile;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -216,11 +216,10 @@ bool UvscServerProvider::aboutToRun(DebuggerRunTool *runTool, QString &errorMess
|
||||
|
||||
Runnable inferior;
|
||||
inferior.command.setExecutable(bin);
|
||||
inferior.extraData.insert(Debugger::Constants::kPeripheralDescriptionFile,
|
||||
peripheralDescriptionFile.toVariant());
|
||||
inferior.extraData.insert(Debugger::Constants::kUVisionProjectFilePath, projFilePath.toString());
|
||||
inferior.extraData.insert(Debugger::Constants::kUVisionOptionsFilePath, optFilePath.toString());
|
||||
inferior.extraData.insert(Debugger::Constants::kUVisionSimulator, isSimulator());
|
||||
runTool->runParameters().peripheralDescriptionFile = peripheralDescriptionFile;
|
||||
runTool->runParameters().uVisionProjectFilePath = projFilePath;
|
||||
runTool->runParameters().uVisionOptionsFilePath = optFilePath;
|
||||
runTool->runParameters().uVisionSimulator = isSimulator();
|
||||
runTool->setInferior(inferior);
|
||||
runTool->setSymbolFile(bin);
|
||||
runTool->setStartMode(AttachToRemoteServer);
|
||||
|
@@ -83,8 +83,6 @@ public:
|
||||
int lowerPort = 0;
|
||||
int upperPort = 0;
|
||||
|
||||
Runnable r = runnable();
|
||||
|
||||
CommandLine cmd;
|
||||
cmd.setExecutable(device()->filePath(Constants::AppcontrollerFilepath));
|
||||
|
||||
@@ -118,11 +116,11 @@ public:
|
||||
}
|
||||
cmd.addArg("--port-range");
|
||||
cmd.addArg(QString("%1-%2").arg(lowerPort).arg(upperPort));
|
||||
cmd.addCommandLineAsArgs(r.command);
|
||||
cmd.addCommandLineAsArgs(runControl()->commandLine());
|
||||
|
||||
m_launcher.setCommand(cmd);
|
||||
m_launcher.setWorkingDirectory(r.workingDirectory);
|
||||
m_launcher.setEnvironment(r.environment);
|
||||
m_launcher.setWorkingDirectory(runControl()->workingDirectory());
|
||||
m_launcher.setEnvironment(runControl()->environment());
|
||||
m_launcher.start();
|
||||
}
|
||||
|
||||
|
@@ -102,7 +102,7 @@ QdbRunConfiguration::QdbRunConfiguration(Target *target, Id id)
|
||||
auto envAspect = addAspect<RemoteLinux::RemoteLinuxEnvironmentAspect>(target);
|
||||
|
||||
addAspect<ArgumentsAspect>(macroExpander());
|
||||
addAspect<WorkingDirectoryAspect>(envAspect);
|
||||
addAspect<WorkingDirectoryAspect>(macroExpander(), envAspect);
|
||||
addAspect<FullCommandLineAspect>(this);
|
||||
|
||||
setUpdater([this, target, exeAspect, symbolsAspect] {
|
||||
|
@@ -15,11 +15,12 @@ add_qtc_plugin(ClangCodeModel
|
||||
clangcodemodelplugin.cpp clangcodemodelplugin.h
|
||||
clangcompletioncontextanalyzer.cpp clangcompletioncontextanalyzer.h
|
||||
clangconstants.h
|
||||
clangdclient.cpp clangdclient.h
|
||||
clangdast.cpp clangdast.h
|
||||
clangdclient.cpp clangdclient.h
|
||||
clangdiagnostictooltipwidget.cpp clangdiagnostictooltipwidget.h
|
||||
clangdquickfixfactory.cpp clangdquickfixfactory.h
|
||||
clangdqpropertyhighlighter.cpp clangdqpropertyhighlighter.h
|
||||
clangdsemantichighlighting.cpp clangdsemantichighlighting.h
|
||||
clangeditordocumentprocessor.cpp clangeditordocumentprocessor.h
|
||||
clangfixitoperation.cpp clangfixitoperation.h
|
||||
clangdlocatorfilters.cpp clangdlocatorfilters.h
|
||||
@@ -28,6 +29,7 @@ add_qtc_plugin(ClangCodeModel
|
||||
clangtextmark.cpp clangtextmark.h
|
||||
clanguiheaderondiskmanager.cpp clanguiheaderondiskmanager.h
|
||||
clangutils.cpp clangutils.h
|
||||
tasktimers.cpp tasktimers.h
|
||||
EXPLICIT_MOC clangcodemodelplugin.h
|
||||
)
|
||||
|
||||
|
@@ -42,6 +42,8 @@ QtcPlugin {
|
||||
"clangdqpropertyhighlighter.h",
|
||||
"clangdquickfixfactory.cpp",
|
||||
"clangdquickfixfactory.h",
|
||||
"clangdsemantichighlighting.cpp",
|
||||
"clangdsemantichighlighting.h",
|
||||
"clangeditordocumentprocessor.cpp",
|
||||
"clangeditordocumentprocessor.h",
|
||||
"clangfixitoperation.cpp",
|
||||
@@ -56,6 +58,8 @@ QtcPlugin {
|
||||
"clanguiheaderondiskmanager.h",
|
||||
"clangutils.cpp",
|
||||
"clangutils.h",
|
||||
"tasktimers.cpp",
|
||||
"tasktimers.h",
|
||||
]
|
||||
|
||||
Group {
|
||||
|
File diff suppressed because it is too large
Load Diff
925
src/plugins/clangcodemodel/clangdsemantichighlighting.cpp
Normal file
925
src/plugins/clangcodemodel/clangdsemantichighlighting.cpp
Normal 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
|
@@ -1,6 +1,6 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2017 The Qt Company Ltd.
|
||||
** Copyright (C) 2022 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
@@ -25,34 +25,33 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "abstractremotelinuxdeployservice.h"
|
||||
#include <QFutureInterface>
|
||||
#include <QLoggingCategory>
|
||||
#include <QPointer>
|
||||
#include <QVersionNumber>
|
||||
|
||||
namespace RemoteLinux {
|
||||
namespace Internal { class RemoteLinuxKillAppServicePrivate; }
|
||||
namespace LanguageClient { class ExpandedSemanticToken; }
|
||||
namespace TextEditor {
|
||||
class HighlightingResult;
|
||||
class TextDocument;
|
||||
}
|
||||
namespace Utils { class FilePath; }
|
||||
|
||||
class REMOTELINUX_EXPORT RemoteLinuxKillAppService : public AbstractRemoteLinuxDeployService
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
RemoteLinuxKillAppService();
|
||||
~RemoteLinuxKillAppService() override;
|
||||
namespace ClangCodeModel::Internal {
|
||||
class ClangdAstNode;
|
||||
class TaskTimer;
|
||||
Q_DECLARE_LOGGING_CATEGORY(clangdLogHighlight);
|
||||
|
||||
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:
|
||||
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
|
||||
} // namespace ClangCodeModel::Internal
|
108
src/plugins/clangcodemodel/tasktimers.cpp
Normal file
108
src/plugins/clangcodemodel/tasktimers.cpp
Normal 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
|
90
src/plugins/clangcodemodel/tasktimers.h
Normal file
90
src/plugins/clangcodemodel/tasktimers.h
Normal 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
|
@@ -51,6 +51,7 @@
|
||||
|
||||
#include <debugger/analyzer/analyzermanager.h>
|
||||
|
||||
#include <projectexplorer/buildconfiguration.h>
|
||||
#include <projectexplorer/kitinformation.h>
|
||||
#include <projectexplorer/projectexplorer.h>
|
||||
#include <projectexplorer/projectexplorericons.h>
|
||||
|
@@ -253,10 +253,10 @@ void ClangToolRunWorker::start()
|
||||
|
||||
// Collect files
|
||||
const auto clangIncludeDirAndVersion =
|
||||
getClangIncludeDirAndVersion(runControl()->runnable().command.executable());
|
||||
getClangIncludeDirAndVersion(runControl()->commandLine().executable());
|
||||
const AnalyzeUnits unitsToProcess = unitsToAnalyze(clangIncludeDirAndVersion.first,
|
||||
clangIncludeDirAndVersion.second);
|
||||
qCDebug(LOG) << Q_FUNC_INFO << runControl()->runnable().command.executable()
|
||||
qCDebug(LOG) << Q_FUNC_INFO << runControl()->commandLine().executable()
|
||||
<< clangIncludeDirAndVersion.first << clangIncludeDirAndVersion.second;
|
||||
qCDebug(LOG) << "Files to process:" << unitsToProcess;
|
||||
|
||||
|
@@ -26,7 +26,6 @@
|
||||
#include "documentclangtoolrunner.h"
|
||||
|
||||
#include "clangfileinfo.h"
|
||||
#include "clangfixitsrefactoringchanges.h"
|
||||
#include "clangtidyclazyrunner.h"
|
||||
#include "clangtoolruncontrol.h"
|
||||
#include "clangtoolsconstants.h"
|
||||
@@ -39,13 +38,18 @@
|
||||
#include <coreplugin/documentmanager.h>
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
#include <coreplugin/editormanager/ieditor.h>
|
||||
|
||||
#include <cppeditor/cppmodelmanager.h>
|
||||
|
||||
#include <projectexplorer/buildconfiguration.h>
|
||||
#include <projectexplorer/buildtargettype.h>
|
||||
#include <projectexplorer/session.h>
|
||||
#include <projectexplorer/target.h>
|
||||
|
||||
#include <texteditor/textdocument.h>
|
||||
#include <texteditor/texteditor.h>
|
||||
#include <texteditor/textmark.h>
|
||||
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/utilsicons.h>
|
||||
|
||||
|
@@ -1196,7 +1196,7 @@ Tasks CMakeConfigurationKitAspect::validate(const Kit *k) const
|
||||
if (!version || !version->isValid()) {
|
||||
addWarning(tr("CMake configuration has a path to a qmake binary set, "
|
||||
"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 "
|
||||
"that does not match the qmake binary path "
|
||||
"configured in the Qt version."));
|
||||
@@ -1219,7 +1219,7 @@ Tasks CMakeConfigurationKitAspect::validate(const Kit *k) const
|
||||
if (!tcC || !tcC->isValid()) {
|
||||
addWarning(tr("CMake configuration has a path to a C compiler set, "
|
||||
"even though the kit has no valid tool chain."));
|
||||
} else if (tcCPath != tcC->compilerCommand()) {
|
||||
} else if (tcCPath != tcC->compilerCommand() && tcCPath != tcC->compilerCommand().onDevice(tcCPath)) {
|
||||
addWarning(tr("CMake configuration has a path to a C compiler set "
|
||||
"that does not match the compiler path "
|
||||
"configured in the tool chain of the kit."));
|
||||
@@ -1235,7 +1235,7 @@ Tasks CMakeConfigurationKitAspect::validate(const Kit *k) const
|
||||
if (!tcCxx || !tcCxx->isValid()) {
|
||||
addWarning(tr("CMake configuration has a path to a C++ compiler set, "
|
||||
"even though the kit has no valid tool chain."));
|
||||
} else if (tcCxxPath != tcCxx->compilerCommand()) {
|
||||
} else if (tcCxxPath != tcCxx->compilerCommand() && tcCxxPath != tcCxx->compilerCommand().onDevice(tcCxxPath)) {
|
||||
addWarning(tr("CMake configuration has a path to a C++ compiler set "
|
||||
"that does not match the compiler path "
|
||||
"configured in the tool chain of the kit."));
|
||||
|
@@ -129,18 +129,16 @@ void StartRemoteDialog::validate()
|
||||
d->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid);
|
||||
}
|
||||
|
||||
Runnable StartRemoteDialog::runnable() const
|
||||
CommandLine StartRemoteDialog::commandLine() const
|
||||
{
|
||||
Kit *kit = d->kitChooser->currentKit();
|
||||
IDevice::ConstPtr device = DeviceKitAspect::device(kit);
|
||||
FilePath filePath = FilePath::fromString(d->executable->text());
|
||||
if (device)
|
||||
filePath = device->filePath(d->arguments->text());
|
||||
const Kit *kit = d->kitChooser->currentKit();
|
||||
const FilePath filePath = DeviceKitAspect::deviceFilePath(kit, d->executable->text());
|
||||
return {filePath, d->arguments->text(), CommandLine::Raw};
|
||||
}
|
||||
|
||||
Runnable r;
|
||||
r.command = {filePath, d->arguments->text(), CommandLine::Raw};
|
||||
r.workingDirectory = FilePath::fromString(d->workingDirectory->text());
|
||||
return r;
|
||||
FilePath StartRemoteDialog::workingDirectory() const
|
||||
{
|
||||
return FilePath::fromString(d->workingDirectory->text());
|
||||
}
|
||||
|
||||
} // namespace Debugger
|
||||
|
@@ -29,7 +29,10 @@
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
namespace ProjectExplorer { class Runnable; }
|
||||
namespace Utils {
|
||||
class CommandLine;
|
||||
class FilePath;
|
||||
} // Utils
|
||||
|
||||
namespace Debugger {
|
||||
|
||||
@@ -43,7 +46,8 @@ public:
|
||||
explicit StartRemoteDialog(QWidget *parent = nullptr);
|
||||
~StartRemoteDialog() override;
|
||||
|
||||
ProjectExplorer::Runnable runnable() const;
|
||||
Utils::CommandLine commandLine() const;
|
||||
Utils::FilePath workingDirectory() const;
|
||||
|
||||
private:
|
||||
void validate();
|
||||
|
@@ -36,14 +36,6 @@ const char MODE_DEBUG[] = "Mode.Debug";
|
||||
// Debug mode context
|
||||
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
|
||||
|
||||
// Keep in sync with dumper.py
|
||||
|
@@ -206,6 +206,14 @@ public:
|
||||
|
||||
Utils::FilePath dumperPath;
|
||||
int fallbackQtVersion = 0x50200;
|
||||
|
||||
// Common debugger constants.
|
||||
Utils::FilePath peripheralDescriptionFile;
|
||||
|
||||
// UVSC-specific debugger constants.
|
||||
Utils::FilePath uVisionProjectFilePath;
|
||||
Utils::FilePath uVisionOptionsFilePath;
|
||||
bool uVisionSimulator = false;
|
||||
};
|
||||
|
||||
class UpdateParameters
|
||||
|
@@ -669,7 +669,9 @@ void DebuggerItemManagerPrivate::autoDetectCdbDebuggers()
|
||||
|
||||
for (const QFileInfo &kitFolderFi : kitFolders) {
|
||||
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) {
|
||||
const QFileInfo cdbBinary(path + "/Debuggers/" + abi + "/cdb.exe");
|
||||
if (cdbBinary.isExecutable())
|
||||
|
@@ -1736,8 +1736,7 @@ RunControl *DebuggerPluginPrivate::attachToRunningProcess(Kit *kit,
|
||||
runControl->setDisplayName(tr("Process %1").arg(processInfo.processId));
|
||||
auto debugger = new DebuggerRunTool(runControl);
|
||||
debugger->setAttachPid(ProcessHandle(processInfo.processId));
|
||||
debugger->setInferiorExecutable(FilePath::fromString(processInfo.executable));
|
||||
debugger->setInferiorDevice(device);
|
||||
debugger->setInferiorExecutable(device->filePath(processInfo.executable));
|
||||
debugger->setStartMode(AttachToLocalProcess);
|
||||
debugger->setCloseMode(DetachAtClose);
|
||||
debugger->setContinueAfterAttach(contAfterAttach);
|
||||
|
@@ -395,11 +395,6 @@ void DebuggerRunTool::setInferiorEnvironment(const Utils::Environment &env)
|
||||
m_runParameters.inferior.environment = env;
|
||||
}
|
||||
|
||||
void DebuggerRunTool::setInferiorDevice(IDevice::ConstPtr device)
|
||||
{
|
||||
m_runParameters.inferior.device = device;
|
||||
}
|
||||
|
||||
void DebuggerRunTool::setRunControlName(const QString &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();
|
||||
inferior.command.setExecutable(inferior.command.executable().onDevice(debuggerExecutable));
|
||||
inferior.workingDirectory = inferior.workingDirectory.onDevice(debuggerExecutable);
|
||||
|
@@ -76,7 +76,6 @@ public:
|
||||
void setInferior(const ProjectExplorer::Runnable &runnable);
|
||||
void setInferiorExecutable(const Utils::FilePath &executable);
|
||||
void setInferiorEnvironment(const Utils::Environment &env); // Used by GammaRay plugin
|
||||
void setInferiorDevice(ProjectExplorer::IDeviceConstPtr device); // Used by cdbengine
|
||||
void setRunControlName(const QString &name);
|
||||
void setStartMessage(const QString &msg);
|
||||
void addQmlServerInferiorCommandLineArgumentIfNeeded();
|
||||
@@ -132,6 +131,8 @@ public:
|
||||
Internal::TerminalRunner *terminalRunner() const;
|
||||
DebuggerEngineType cppEngineType() const;
|
||||
|
||||
Internal::DebuggerRunParameters &runParameters() { return m_runParameters; }
|
||||
|
||||
private:
|
||||
bool fixupParameters();
|
||||
void handleEngineStarted(Internal::DebuggerEngine *engine);
|
||||
|
@@ -25,23 +25,21 @@
|
||||
|
||||
#include "loadcoredialog.h"
|
||||
|
||||
#include "debuggerdialogs.h"
|
||||
#include "debuggerkitinformation.h"
|
||||
#include "gdb/gdbengine.h"
|
||||
|
||||
#include <projectexplorer/devicesupport/devicefilesystemmodel.h>
|
||||
#include <projectexplorer/devicesupport/filetransfer.h>
|
||||
#include <projectexplorer/devicesupport/idevice.h>
|
||||
#include <projectexplorer/kitinformation.h>
|
||||
#include <projectexplorer/projectexplorerconstants.h>
|
||||
#include <projectexplorer/kitchooser.h>
|
||||
#include <utils/pathchooser.h>
|
||||
#include <utils/processinterface.h>
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/runextensions.h>
|
||||
#include <utils/temporaryfile.h>
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFormLayout>
|
||||
#include <QFutureWatcher>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
@@ -69,7 +67,6 @@ class SelectRemoteFileDialog : public QDialog
|
||||
|
||||
public:
|
||||
explicit SelectRemoteFileDialog(QWidget *parent);
|
||||
~SelectRemoteFileDialog();
|
||||
|
||||
void attachToDevice(Kit *k);
|
||||
FilePath localFile() const { return m_localFile; }
|
||||
@@ -77,7 +74,6 @@ public:
|
||||
|
||||
private:
|
||||
void selectFile();
|
||||
void clearWatcher();
|
||||
|
||||
QSortFilterProxyModel m_model;
|
||||
DeviceFileSystemModel m_fileSystemModel;
|
||||
@@ -86,7 +82,7 @@ private:
|
||||
QDialogButtonBox *m_buttonBox;
|
||||
FilePath m_localFile;
|
||||
FilePath m_remoteFile;
|
||||
QFutureWatcher<bool> *m_watcher = nullptr;
|
||||
FileTransfer m_fileTransfer;
|
||||
};
|
||||
|
||||
SelectRemoteFileDialog::SelectRemoteFileDialog(QWidget *parent)
|
||||
@@ -119,11 +115,21 @@ SelectRemoteFileDialog::SelectRemoteFileDialog(QWidget *parent)
|
||||
|
||||
connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
connect(m_buttonBox, &QDialogButtonBox::accepted, this, &SelectRemoteFileDialog::selectFile);
|
||||
}
|
||||
|
||||
SelectRemoteFileDialog::~SelectRemoteFileDialog()
|
||||
{
|
||||
clearWatcher();
|
||||
connect(&m_fileTransfer, &FileTransfer::done, this, [this](const ProcessResultData &result) {
|
||||
const bool success = result.m_error == QProcess::UnknownError
|
||||
&& 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)
|
||||
@@ -135,22 +141,6 @@ void SelectRemoteFileDialog::attachToDevice(Kit *k)
|
||||
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()
|
||||
{
|
||||
QModelIndex idx = m_model.mapToSource(m_fileSystemView->currentIndex());
|
||||
@@ -161,7 +151,7 @@ void SelectRemoteFileDialog::selectFile()
|
||||
m_fileSystemView->setEnabled(false);
|
||||
|
||||
{
|
||||
Utils::TemporaryFile localFile("remotecore-XXXXXX");
|
||||
TemporaryFile localFile("remotecore-XXXXXX");
|
||||
localFile.open();
|
||||
m_localFile = FilePath::fromString(localFile.fileName());
|
||||
}
|
||||
@@ -169,33 +159,8 @@ void SelectRemoteFileDialog::selectFile()
|
||||
idx = idx.sibling(idx.row(), 1);
|
||||
m_remoteFile = FilePath::fromVariant(m_fileSystemModel.data(idx, DeviceFileSystemModel::PathRole));
|
||||
|
||||
clearWatcher();
|
||||
m_watcher = new QFutureWatcher<bool>(this);
|
||||
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;
|
||||
m_fileTransfer.setFilesToTransfer({{m_remoteFile, m_localFile}});
|
||||
m_fileTransfer.start();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
@@ -732,12 +732,9 @@ void PeripheralRegisterHandler::updateRegisterGroups()
|
||||
clear();
|
||||
|
||||
const DebuggerRunParameters &rp = m_engine->runParameters();
|
||||
const FilePath peripheralDescriptionFile = FilePath::fromVariant(
|
||||
rp.inferior.extraData.value(Debugger::Constants::kPeripheralDescriptionFile));
|
||||
|
||||
if (!peripheralDescriptionFile.exists())
|
||||
if (!rp.peripheralDescriptionFile.exists())
|
||||
return;
|
||||
m_peripheralRegisterGroups = availablePeripheralRegisterGroups(peripheralDescriptionFile);
|
||||
m_peripheralRegisterGroups = availablePeripheralRegisterGroups(rp.peripheralDescriptionFile);
|
||||
}
|
||||
|
||||
void PeripheralRegisterHandler::updateRegister(quint64 address, quint64 value)
|
||||
|
@@ -536,10 +536,8 @@ void UvscEngine::updateAll()
|
||||
bool UvscEngine::configureProject(const DebuggerRunParameters &rp)
|
||||
{
|
||||
// Fetch patchs for the generated uVision project files.
|
||||
const FilePath optionsPath = FilePath::fromString(rp.inferior.extraData.value(
|
||||
Constants::kUVisionOptionsFilePath).toString());
|
||||
const FilePath projectPath = FilePath::fromString(rp.inferior.extraData.value(
|
||||
Constants::kUVisionProjectFilePath).toString());
|
||||
const FilePath optionsPath = rp.uVisionOptionsFilePath;
|
||||
const FilePath projectPath = rp.uVisionProjectFilePath;
|
||||
|
||||
showMessage("UVSC: LOADING PROJECT...");
|
||||
if (!optionsPath.exists()) {
|
||||
@@ -557,7 +555,7 @@ bool UvscEngine::configureProject(const DebuggerRunParameters &rp)
|
||||
}
|
||||
|
||||
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)) {
|
||||
handleSetupFailure(tr("Internal error: Unable to set the uVision debug target: %1.")
|
||||
.arg(m_client->errorString()));
|
||||
@@ -606,8 +604,7 @@ void UvscEngine::handleProjectClosed()
|
||||
m_loadingRequired = false;
|
||||
|
||||
const DebuggerRunParameters &rp = runParameters();
|
||||
const FilePath projectPath = FilePath::fromString(rp.inferior.extraData.value(
|
||||
Constants::kUVisionProjectFilePath).toString());
|
||||
const FilePath projectPath = rp.uVisionProjectFilePath;
|
||||
|
||||
// This magic function removes specific files from the uVision
|
||||
// project directory. Without of this we can't enumerate the local
|
||||
|
@@ -27,6 +27,7 @@
|
||||
|
||||
#include <projectexplorer/devicesupport/idevice.h>
|
||||
#include <projectexplorer/devicesupport/idevicefactory.h>
|
||||
#include <coreplugin/documentmanager.h>
|
||||
|
||||
#include <utils/aspects.h>
|
||||
|
||||
@@ -44,7 +45,7 @@ public:
|
||||
QString tag;
|
||||
QString size;
|
||||
bool useLocalUidGid = true;
|
||||
QStringList mounts;
|
||||
QStringList mounts = { Core::DocumentManager::projectsDirectory().toString() };
|
||||
};
|
||||
|
||||
class DockerDevice : public ProjectExplorer::IDevice
|
||||
|
@@ -25,8 +25,6 @@
|
||||
|
||||
#include "kitdetector.h"
|
||||
|
||||
#include "dockerconstants.h"
|
||||
|
||||
#include <cmakeprojectmanager/cmakeprojectconstants.h>
|
||||
|
||||
#include <extensionsystem/pluginmanager.h>
|
||||
@@ -216,10 +214,10 @@ QtVersions KitDetectorPrivate::autoDetectQtVersions() const
|
||||
QString error;
|
||||
|
||||
const auto handleQmake = [this, &qtVersions, &error](const FilePath &qmake) {
|
||||
if (QtVersion *qtVersion = QtVersionFactory::createQtVersionFromQMakePath(qmake,
|
||||
false,
|
||||
m_sharedId,
|
||||
&error)) {
|
||||
if (QtVersion *qtVersion = QtVersionFactory::createQtVersionFromQueryToolPath(qmake,
|
||||
false,
|
||||
m_sharedId,
|
||||
&error)) {
|
||||
if (qtVersion->isValid()) {
|
||||
if (!Utils::anyOf(qtVersions,
|
||||
[qtVersion](QtVersion* other) {
|
||||
@@ -229,7 +227,7 @@ QtVersions KitDetectorPrivate::autoDetectQtVersions() const
|
||||
qtVersions.append(qtVersion);
|
||||
QtVersionManager::addVersion(qtVersion);
|
||||
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())
|
||||
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);
|
||||
BuildDeviceKitAspect::setDevice(k, m_device);
|
||||
|
||||
|
@@ -3,6 +3,8 @@ add_qtc_plugin(GitLab
|
||||
PLUGIN_DEPENDS Core ProjectExplorer Git VcsBase
|
||||
DEPENDS Utils
|
||||
SOURCES
|
||||
gitlabclonedialog.cpp gitlabclonedialog.h
|
||||
gitlabdialog.cpp gitlabdialog.h gitlabdialog.ui
|
||||
gitlaboptionspage.cpp gitlaboptionspage.h
|
||||
gitlabparameters.cpp gitlabparameters.h
|
||||
gitlabplugin.cpp gitlabplugin.h
|
||||
|
@@ -10,6 +10,11 @@ QtcPlugin {
|
||||
Depends { name: "Utils" }
|
||||
|
||||
files: [
|
||||
"gitlabclonedialog.cpp",
|
||||
"gitlabclonedialog.h",
|
||||
"gitlabdialog.cpp",
|
||||
"gitlabdialog.h",
|
||||
"gitlabdialog.ui",
|
||||
"gitlaboptionspage.cpp",
|
||||
"gitlaboptionspage.h",
|
||||
"gitlabparameters.cpp",
|
||||
|
263
src/plugins/gitlab/gitlabclonedialog.cpp
Normal file
263
src/plugins/gitlab/gitlabclonedialog.cpp
Normal 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
|
74
src/plugins/gitlab/gitlabclonedialog.h
Normal file
74
src/plugins/gitlab/gitlabclonedialog.h
Normal 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
|
294
src/plugins/gitlab/gitlabdialog.cpp
Normal file
294
src/plugins/gitlab/gitlabdialog.cpp
Normal 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
|
@@ -1,6 +1,6 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Copyright (C) 2022 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
@@ -22,49 +22,60 @@
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "abstractremotelinuxdeployservice.h"
|
||||
#include "remotelinux_export.h"
|
||||
#include "ui_gitlabdialog.h"
|
||||
|
||||
namespace Utils {
|
||||
class FilePath;
|
||||
class ProcessResultData;
|
||||
}
|
||||
#include "queryrunner.h"
|
||||
#include "resultparser.h"
|
||||
|
||||
namespace RemoteLinux {
|
||||
class AbstractRemoteLinuxPackageInstaller;
|
||||
#include <utils/filepath.h>
|
||||
#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
|
||||
|
||||
public:
|
||||
void setPackageFilePath(const Utils::FilePath &filePath);
|
||||
explicit GitLabDialog(QWidget *parent = nullptr);
|
||||
|
||||
void updateRemotes();
|
||||
|
||||
protected:
|
||||
AbstractUploadAndInstallPackageService();
|
||||
~AbstractUploadAndInstallPackageService() override;
|
||||
void keyPressEvent(QKeyEvent *event) override;
|
||||
|
||||
private:
|
||||
void handleUploadFinished(const Utils::ProcessResultData &resultData);
|
||||
void handleInstallationFinished(const QString &errorMsg);
|
||||
void resetTreeView(QTreeView *treeView, QAbstractItemModel *model);
|
||||
void requestMainViewUpdate();
|
||||
void updatePageButtons();
|
||||
|
||||
virtual AbstractRemoteLinuxPackageInstaller *packageInstaller() const = 0;
|
||||
virtual QString uploadDir() const; // Defaults to remote user's home directory.
|
||||
void queryFirstPage();
|
||||
void queryPreviousPage();
|
||||
void queryNextPage();
|
||||
void queryLastPage();
|
||||
void querySearch();
|
||||
void continuePageUpdate();
|
||||
|
||||
bool isDeploymentNecessary() const override;
|
||||
void doDeviceSetup() override;
|
||||
void stopDeviceSetup() override;
|
||||
void doDeploy() override;
|
||||
void stopDeployment() override;
|
||||
void handleUser(const User &user);
|
||||
void handleProjects(const Projects &projects);
|
||||
void fetchProjects();
|
||||
|
||||
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
|
268
src/plugins/gitlab/gitlabdialog.ui
Normal file
268
src/plugins/gitlab/gitlabdialog.ui
Normal 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">|<</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">>|</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>
|
@@ -25,23 +25,52 @@
|
||||
|
||||
#include "gitlabplugin.h"
|
||||
|
||||
#include "gitlabdialog.h"
|
||||
#include "gitlaboptionspage.h"
|
||||
#include "gitlabparameters.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 <git/gitplugin.h>
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/projectpanelfactory.h>
|
||||
#include <projectexplorer/session.h>
|
||||
#include <utils/qtcassert.h>
|
||||
#include <vcsbase/vcsoutputwindow.h>
|
||||
|
||||
#include <QAction>
|
||||
#include <QMessageBox>
|
||||
#include <QPointer>
|
||||
#include <QTimer>
|
||||
|
||||
namespace GitLab {
|
||||
namespace Constants {
|
||||
const char GITLAB_OPEN_VIEW[] = "GitLab.OpenView";
|
||||
} // namespace Constants
|
||||
|
||||
class GitLabPluginPrivate
|
||||
class GitLabPluginPrivate : public QObject
|
||||
{
|
||||
public:
|
||||
GitLabParameters parameters;
|
||||
GitLabOptionsPage optionsPage{¶meters};
|
||||
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;
|
||||
@@ -71,9 +100,180 @@ bool GitLabPlugin::initialize(const QStringList & /*arguments*/, QString * /*err
|
||||
return new GitLabProjectSettingsWidget(project);
|
||||
});
|
||||
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;
|
||||
}
|
||||
|
||||
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(¬ificationTimer, &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()
|
||||
{
|
||||
QTC_ASSERT(dd, return {});
|
||||
@@ -107,4 +307,28 @@ GitLabOptionsPage *GitLabPlugin::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
|
||||
|
@@ -33,6 +33,7 @@ namespace ProjectExplorer { class Project; }
|
||||
|
||||
namespace GitLab {
|
||||
|
||||
class Events;
|
||||
class GitLabProjectSettings;
|
||||
class GitLabOptionsPage;
|
||||
|
||||
@@ -52,6 +53,11 @@ public:
|
||||
static GitLabParameters *globalParameters();
|
||||
static GitLabProjectSettings *projectSettings(ProjectExplorer::Project *project);
|
||||
static GitLabOptionsPage *optionsPage();
|
||||
|
||||
static void linkedStateChanged(bool enabled);
|
||||
private:
|
||||
void openView();
|
||||
void onStartupProjectChanged();
|
||||
};
|
||||
|
||||
} // namespace GitLab
|
||||
|
@@ -49,6 +49,7 @@ namespace GitLab {
|
||||
const char PSK_LINKED_ID[] = "GitLab.LinkedId";
|
||||
const char PSK_SERVER[] = "GitLab.Server";
|
||||
const char PSK_PROJECT[] = "GitLab.Project";
|
||||
const char PSK_LAST_REQ[] = "GitLab.LastRequest";
|
||||
|
||||
static QString accessLevelString(int accessLevel)
|
||||
{
|
||||
@@ -106,6 +107,7 @@ void GitLabProjectSettings::load()
|
||||
m_id = Utils::Id::fromSetting(m_project->namedSettings(PSK_LINKED_ID));
|
||||
m_host = m_project->namedSettings(PSK_SERVER).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
|
||||
if (!m_id.isValid() || m_host.isEmpty())
|
||||
@@ -124,6 +126,7 @@ void GitLabProjectSettings::save()
|
||||
m_project->setNamedSettings(PSK_SERVER, QString());
|
||||
}
|
||||
m_project->setNamedSettings(PSK_PROJECT, m_currentProject);
|
||||
m_project->setNamedSettings(PSK_LAST_REQ, m_lastRequest);
|
||||
}
|
||||
|
||||
GitLabProjectSettingsWidget::GitLabProjectSettingsWidget(ProjectExplorer::Project *project,
|
||||
@@ -184,6 +187,7 @@ void GitLabProjectSettingsWidget::unlink()
|
||||
m_projectSettings->setLinked(false);
|
||||
m_projectSettings->setCurrentProject({});
|
||||
updateEnabledStates();
|
||||
GitLabPlugin::linkedStateChanged(false);
|
||||
}
|
||||
|
||||
void GitLabProjectSettingsWidget::checkConnection(CheckMode mode)
|
||||
@@ -245,6 +249,7 @@ void GitLabProjectSettingsWidget::onConnectionChecked(const Project &project,
|
||||
m_projectSettings->setCurrentServerHost(remote);
|
||||
m_projectSettings->setLinked(true);
|
||||
m_projectSettings->setCurrentProject(projectName);
|
||||
GitLabPlugin::linkedStateChanged(true);
|
||||
}
|
||||
updateEnabledStates();
|
||||
}
|
||||
@@ -282,8 +287,10 @@ void GitLabProjectSettingsWidget::updateUi()
|
||||
m_hostCB->setCurrentIndex(m_hostCB->findData(QVariant::fromValue(serverHost)));
|
||||
m_linkedGitLabServer->setCurrentIndex(
|
||||
m_linkedGitLabServer->findData(QVariant::fromValue(server)));
|
||||
GitLabPlugin::linkedStateChanged(true);
|
||||
} else {
|
||||
m_projectSettings->setLinked(false);
|
||||
GitLabPlugin::linkedStateChanged(false);
|
||||
}
|
||||
}
|
||||
updateEnabledStates();
|
||||
@@ -294,6 +301,7 @@ void GitLabProjectSettingsWidget::updateEnabledStates()
|
||||
const bool isGitRepository = m_hostCB->count() > 0;
|
||||
const bool hasGitLabServers = m_linkedGitLabServer->count();
|
||||
const bool linked = m_projectSettings->isLinked();
|
||||
|
||||
m_linkedGitLabServer->setEnabled(isGitRepository && !linked);
|
||||
m_hostCB->setEnabled(isGitRepository && !linked);
|
||||
m_linkWithGitLab->setEnabled(isGitRepository && !linked && hasGitLabServers);
|
||||
|
@@ -28,6 +28,7 @@
|
||||
#include <projectexplorer/projectsettingswidget.h>
|
||||
#include <utils/id.h>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
|
||||
@@ -59,9 +60,12 @@ public:
|
||||
QString currentProject() const { return m_currentProject; }
|
||||
bool isLinked() const { return m_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; }
|
||||
|
||||
static std::tuple<QString, QString, int> remotePartsFromRemote(const QString &remote);
|
||||
|
||||
private:
|
||||
void load();
|
||||
void save();
|
||||
@@ -69,6 +73,7 @@ private:
|
||||
ProjectExplorer::Project *m_project = nullptr;
|
||||
QString m_host;
|
||||
Utils::Id m_id;
|
||||
QDateTime m_lastRequest;
|
||||
QString m_currentProject;
|
||||
bool m_linked = false;
|
||||
};
|
||||
|
@@ -41,6 +41,9 @@ namespace GitLab {
|
||||
|
||||
const char API_PREFIX[] = "/api/v4";
|
||||
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 ¶meter)
|
||||
: m_type(type)
|
||||
@@ -48,6 +51,21 @@ Query::Query(Type type, const QStringList ¶meter)
|
||||
{
|
||||
}
|
||||
|
||||
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 = API_PREFIX;
|
||||
@@ -59,6 +77,25 @@ QString Query::toString() const
|
||||
query += QLatin1String(QUERY_PROJECT).arg(QLatin1String(
|
||||
QUrl::toPercentEncoding(m_parameter.at(0))));
|
||||
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;
|
||||
}
|
||||
@@ -69,6 +106,9 @@ QueryRunner::QueryRunner(const Query &query, const Utils::Id &id, QObject *paren
|
||||
const GitLabParameters *p = GitLabPlugin::globalParameters();
|
||||
const auto server = p->serverForId(id);
|
||||
QStringList args = server.curlArguments();
|
||||
m_paginated = query.hasPaginatedResults();
|
||||
if (m_paginated)
|
||||
args << "-i";
|
||||
if (!server.token.isEmpty())
|
||||
args << "--header" << "PRIVATE-TOKEN: " + server.token;
|
||||
QString url = "https://" + server.host;
|
||||
|
@@ -38,15 +38,24 @@ class Query
|
||||
public:
|
||||
enum Type {
|
||||
NoQuery,
|
||||
Project
|
||||
User,
|
||||
Project,
|
||||
Projects,
|
||||
Events
|
||||
};
|
||||
|
||||
explicit Query(Type type, const QStringList ¶meters = {});
|
||||
void setPageParameter(int page);
|
||||
void setAdditionalParameters(const QStringList &additional);
|
||||
bool hasPaginatedResults() const;
|
||||
Type type() const { return m_type; }
|
||||
QString toString() const;
|
||||
|
||||
private:
|
||||
Type m_type = NoQuery;
|
||||
QStringList m_parameter;
|
||||
QStringList m_additionalParameters;
|
||||
int m_pageParameter = -1;
|
||||
};
|
||||
|
||||
class QueryRunner : public QObject
|
||||
@@ -70,6 +79,7 @@ private:
|
||||
|
||||
Utils::QtcProcess m_process;
|
||||
bool m_running = false;
|
||||
bool m_paginated = false;
|
||||
};
|
||||
|
||||
} // namespace GitLab
|
||||
|
@@ -32,8 +32,59 @@
|
||||
#include <utility>
|
||||
|
||||
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 {
|
||||
|
||||
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)
|
||||
{
|
||||
Error result;
|
||||
@@ -59,6 +110,53 @@ static std::pair<Error, QJsonObject> preHandleSingle(const QByteArray &json)
|
||||
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)
|
||||
{
|
||||
Project project;
|
||||
@@ -67,6 +165,8 @@ static Project projectFromJson(const QJsonObject &jsonObj)
|
||||
project.pathName = jsonObj.value("path_with_namespace").toString();
|
||||
project.id = jsonObj.value("id").toInt(-1);
|
||||
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"))
|
||||
project.forkCount = jsonObj.value("forks_count").toInt();
|
||||
if (jsonObj.contains("star_count"))
|
||||
@@ -82,6 +182,42 @@ static Project projectFromJson(const QJsonObject &jsonObj)
|
||||
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)
|
||||
{
|
||||
auto [error, projectObj] = preHandleSingle(input);
|
||||
@@ -93,6 +229,47 @@ Project parseProject(const QByteArray &input)
|
||||
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 error;
|
||||
|
@@ -25,6 +25,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QList>
|
||||
#include <QMetaType>
|
||||
#include <QString>
|
||||
|
||||
namespace GitLab {
|
||||
@@ -35,6 +37,27 @@ struct Error
|
||||
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
|
||||
{
|
||||
public:
|
||||
@@ -42,18 +65,55 @@ public:
|
||||
QString displayName;
|
||||
QString pathName;
|
||||
QString visibility;
|
||||
QString httpUrl;
|
||||
QString sshUrl;
|
||||
Error error;
|
||||
int id = -1;
|
||||
int starCount = -1;
|
||||
int forkCount = -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 {
|
||||
|
||||
User parseUser(const QByteArray &input);
|
||||
Project parseProject(const QByteArray &input);
|
||||
Projects parseProjects(const QByteArray &input);
|
||||
Events parseEvents(const QByteArray &input);
|
||||
Error parseErrorMessage(const QString &message);
|
||||
|
||||
} // namespace ResultParser
|
||||
} // namespace GitLab
|
||||
|
||||
Q_DECLARE_METATYPE(GitLab::Project)
|
||||
|
@@ -197,8 +197,8 @@ void IosRunner::start()
|
||||
connect(m_toolHandler, &IosToolHandler::finished,
|
||||
this, &IosRunner::handleFinished);
|
||||
|
||||
const Runnable runnable = runControl()->runnable();
|
||||
QStringList args = ProcessArgs::splitArgs(runnable.command.arguments(), OsTypeMac);
|
||||
const CommandLine command = runControl()->commandLine();
|
||||
QStringList args = ProcessArgs::splitArgs(command.arguments(), OsTypeMac);
|
||||
if (m_qmlServerPort.isValid()) {
|
||||
QUrl qmlServer;
|
||||
qmlServer.setPort(m_qmlServerPort.number());
|
||||
|
@@ -918,6 +918,7 @@ void Client::documentContentsChanged(TextEditor::TextDocument *document,
|
||||
{
|
||||
if (!d->m_openedDocument.contains(document) || !reachable())
|
||||
return;
|
||||
d->m_diagnosticManager->disableDiagnostics(document);
|
||||
const QString method(DidChangeTextDocumentNotification::methodName);
|
||||
TextDocumentSyncKind syncKind = d->m_serverCapabilities.textDocumentSyncKindHelper();
|
||||
if (Utils::optional<bool> registered = d->m_dynamicCapabilities.isRegistered(method)) {
|
||||
|
@@ -90,7 +90,7 @@ void DiagnosticManager::hideDiagnostics(const Utils::FilePath &filePath)
|
||||
for (BaseTextEditor *editor : BaseTextEditor::textEditorsForDocument(doc))
|
||||
editor->editorWidget()->setExtraSelections(m_extraSelectionsId, {});
|
||||
}
|
||||
qDeleteAll(m_marks.take(filePath));
|
||||
m_marks.remove(filePath);
|
||||
}
|
||||
|
||||
QList<Diagnostic> DiagnosticManager::filteredDiagnostics(const QList<Diagnostic> &diagnostics) const
|
||||
@@ -98,6 +98,17 @@ QList<Diagnostic> DiagnosticManager::filteredDiagnostics(const QList<Diagnostic>
|
||||
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)
|
||||
{
|
||||
const FilePath &filePath = uri.toFilePath();
|
||||
@@ -106,7 +117,7 @@ void DiagnosticManager::showDiagnostics(const DocumentUri &uri, int version)
|
||||
const VersionedDiagnostics &versionedDiagnostics = m_diagnostics.value(uri);
|
||||
if (versionedDiagnostics.version.value_or(version) == version
|
||||
&& !versionedDiagnostics.diagnostics.isEmpty()) {
|
||||
QList<TextEditor::TextMark *> &marks = m_marks[filePath];
|
||||
Marks &marks = m_marks[filePath];
|
||||
const bool isProjectFile = m_client->project()
|
||||
&& m_client->project()->isKnownFile(filePath);
|
||||
for (const Diagnostic &diagnostic : versionedDiagnostics.diagnostics) {
|
||||
@@ -115,9 +126,9 @@ void DiagnosticManager::showDiagnostics(const DocumentUri &uri, int version)
|
||||
if (!selection.cursor.isNull())
|
||||
extraSelections << selection;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -170,11 +181,7 @@ void DiagnosticManager::clearDiagnostics()
|
||||
for (const DocumentUri &uri : m_diagnostics.keys())
|
||||
hideDiagnostics(uri.toFilePath());
|
||||
m_diagnostics.clear();
|
||||
if (!QTC_GUARD(m_marks.isEmpty())) {
|
||||
for (const QList<TextEditor::TextMark *> &marks : qAsConst(m_marks))
|
||||
qDeleteAll(marks);
|
||||
m_marks.clear();
|
||||
}
|
||||
QTC_ASSERT(m_marks.isEmpty(), m_marks.clear());
|
||||
}
|
||||
|
||||
QList<Diagnostic> DiagnosticManager::diagnosticsAt(const DocumentUri &uri,
|
||||
@@ -218,4 +225,9 @@ bool DiagnosticManager::hasDiagnostics(const TextDocument *doc) const
|
||||
return !it->diagnostics.isEmpty();
|
||||
}
|
||||
|
||||
DiagnosticManager::Marks::~Marks()
|
||||
{
|
||||
qDeleteAll(marks);
|
||||
}
|
||||
|
||||
} // namespace LanguageClient
|
||||
|
@@ -61,6 +61,7 @@ public:
|
||||
virtual QList<LanguageServerProtocol::Diagnostic> filteredDiagnostics(
|
||||
const QList<LanguageServerProtocol::Diagnostic> &diagnostics) const;
|
||||
|
||||
void disableDiagnostics(TextEditor::TextDocument *document);
|
||||
void clearDiagnostics();
|
||||
|
||||
QList<LanguageServerProtocol::Diagnostic> diagnosticsAt(
|
||||
@@ -91,7 +92,14 @@ private:
|
||||
QList<LanguageServerProtocol::Diagnostic> 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;
|
||||
Utils::Id m_extraSelectionsId;
|
||||
};
|
||||
|
@@ -46,7 +46,7 @@ MesonRunConfiguration::MesonRunConfiguration(ProjectExplorer::Target *target, Ut
|
||||
|
||||
addAspect<ProjectExplorer::ExecutableAspect>(target);
|
||||
addAspect<ProjectExplorer::ArgumentsAspect>(macroExpander());
|
||||
addAspect<ProjectExplorer::WorkingDirectoryAspect>(envAspect);
|
||||
addAspect<ProjectExplorer::WorkingDirectoryAspect>(macroExpander(), envAspect);
|
||||
addAspect<ProjectExplorer::TerminalAspect>();
|
||||
|
||||
auto libAspect = addAspect<ProjectExplorer::UseLibraryPathsAspect>();
|
||||
|
@@ -54,7 +54,7 @@ public:
|
||||
auto envAspect = addAspect<LocalEnvironmentAspect>(target);
|
||||
addAspect<ExecutableAspect>(target);
|
||||
addAspect<ArgumentsAspect>(macroExpander());
|
||||
addAspect<WorkingDirectoryAspect>(envAspect);
|
||||
addAspect<WorkingDirectoryAspect>(macroExpander(), envAspect);
|
||||
addAspect<TerminalAspect>();
|
||||
|
||||
setUpdater([this] {
|
||||
@@ -91,7 +91,8 @@ public:
|
||||
{
|
||||
addAspect<ExecutableAspect>(target)->setExecutable(Nim::nimblePathFromKit(target->kit()));
|
||||
addAspect<ArgumentsAspect>(macroExpander())->setArguments("test");
|
||||
addAspect<WorkingDirectoryAspect>(nullptr)->setDefaultWorkingDirectory(project()->projectDirectory());
|
||||
addAspect<WorkingDirectoryAspect>(macroExpander(), nullptr)
|
||||
->setDefaultWorkingDirectory(project()->projectDirectory());
|
||||
addAspect<TerminalAspect>();
|
||||
|
||||
setDisplayName(tr("Nimble Test"));
|
||||
|
@@ -52,7 +52,7 @@ public:
|
||||
auto envAspect = addAspect<LocalEnvironmentAspect>(target);
|
||||
addAspect<ExecutableAspect>(target);
|
||||
addAspect<ArgumentsAspect>(macroExpander());
|
||||
addAspect<WorkingDirectoryAspect>(envAspect);
|
||||
addAspect<WorkingDirectoryAspect>(macroExpander(), envAspect);
|
||||
addAspect<TerminalAspect>();
|
||||
|
||||
setDisplayName(tr("Current Build Target"));
|
||||
|
@@ -23,9 +23,9 @@
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "perfdatareader.h"
|
||||
#include "perfprofilerconstants.h"
|
||||
#include "perfprofilerruncontrol.h"
|
||||
|
||||
#include "perfdatareader.h"
|
||||
#include "perfprofilertool.h"
|
||||
#include "perfrunconfigurationaspect.h"
|
||||
#include "perfsettings.h"
|
||||
@@ -124,10 +124,6 @@ public:
|
||||
void start() override
|
||||
{
|
||||
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::done, this, [this] {
|
||||
@@ -144,12 +140,10 @@ public:
|
||||
reportStopped();
|
||||
});
|
||||
|
||||
Runnable perfRunnable = runnable();
|
||||
|
||||
CommandLine cmd({device()->filePath("perf"), {"record"}});
|
||||
cmd.addArgs(m_perfRecordArguments);
|
||||
cmd.addArgs({"-o", "-", "--"});
|
||||
cmd.addCommandLineAsArgs(perfRunnable.command, CommandLine::Raw);
|
||||
cmd.addCommandLineAsArgs(runControl()->commandLine(), CommandLine::Raw);
|
||||
|
||||
m_process->setCommand(cmd);
|
||||
m_process->start();
|
||||
|
@@ -60,6 +60,8 @@ add_qtc_plugin(ProjectExplorer
|
||||
devicesupport/devicesettingswidget.cpp devicesupport/devicesettingswidget.h devicesupport/devicesettingswidget.ui
|
||||
devicesupport/devicetestdialog.cpp devicesupport/devicetestdialog.h devicesupport/devicetestdialog.ui
|
||||
devicesupport/deviceusedportsgatherer.cpp devicesupport/deviceusedportsgatherer.h
|
||||
devicesupport/filetransfer.cpp devicesupport/filetransfer.h
|
||||
devicesupport/filetransferinterface.h
|
||||
devicesupport/idevice.cpp devicesupport/idevice.h
|
||||
devicesupport/idevicefactory.cpp devicesupport/idevicefactory.h
|
||||
devicesupport/idevicefwd.h
|
||||
|
@@ -44,6 +44,7 @@
|
||||
|
||||
#include <extensionsystem/invoker.h>
|
||||
#include <extensionsystem/pluginmanager.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/outputformatter.h>
|
||||
#include <utils/qtcassert.h>
|
||||
@@ -66,6 +67,8 @@
|
||||
|
||||
static Q_LOGGING_CATEGORY(appOutputLog, "qtc.projectexplorer.appoutput", QtWarningMsg);
|
||||
|
||||
using namespace Utils;
|
||||
|
||||
namespace ProjectExplorer {
|
||||
namespace Internal {
|
||||
|
||||
@@ -398,19 +401,20 @@ void AppOutputPane::createNewOutputWindow(RunControl *rc)
|
||||
connect(rc, &RunControl::applicationProcessHandleChanged,
|
||||
this, &AppOutputPane::enableDefaultButtons);
|
||||
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);
|
||||
});
|
||||
|
||||
// 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) {
|
||||
if (!tab.runControl || tab.runControl->isRunning())
|
||||
return false;
|
||||
const Runnable otherRunnable = tab.runControl->runnable();
|
||||
return thisRunnable.command == otherRunnable.command
|
||||
&& thisRunnable.workingDirectory == otherRunnable.workingDirectory
|
||||
&& thisRunnable.environment == otherRunnable.environment;
|
||||
return thisCommand == tab.runControl->commandLine()
|
||||
&& thisWorkingDirectory == tab.runControl->workingDirectory()
|
||||
&& thisEnvironment == tab.runControl->environment();
|
||||
});
|
||||
if (tabIndex != -1) {
|
||||
RunControlTab &tab = m_runControlTabs[tabIndex];
|
||||
@@ -433,7 +437,7 @@ void AppOutputPane::createNewOutputWindow(RunControl *rc)
|
||||
}
|
||||
// Create new
|
||||
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::OutputWindow *ow = new Core::OutputWindow(context, SETTINGS_KEY, m_tabWidget);
|
||||
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);
|
||||
if (index != -1) {
|
||||
Core::OutputWindow *window = m_runControlTabs.at(index).window;
|
||||
QString stringToWrite;
|
||||
if (format == Utils::NormalMessageFormat || format == Utils::ErrorMessageFormat) {
|
||||
if (format == NormalMessageFormat || format == ErrorMessageFormat) {
|
||||
stringToWrite = QTime::currentTime().toString();
|
||||
stringToWrite += ": ";
|
||||
}
|
||||
stringToWrite += out;
|
||||
window->appendMessage(stringToWrite, format);
|
||||
if (format != Utils::NormalMessageFormat) {
|
||||
if (format != NormalMessageFormat) {
|
||||
RunControlTab &tab = m_runControlTabs[index];
|
||||
switch (tab.behaviorOnOutput) {
|
||||
case AppOutputPaneMode::FlashOnOutput:
|
||||
@@ -530,7 +534,7 @@ const bool kWrapOutputDefault = true;
|
||||
|
||||
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,
|
||||
int(m_settings.runOutputMode),
|
||||
int(kRunOutputModeDefault));
|
||||
@@ -706,7 +710,7 @@ void AppOutputPane::enableButtons(const RunControl *rc)
|
||||
m_stopAction->setEnabled(isRunning);
|
||||
if (isRunning && debuggerPlugin() && rc->applicationProcessHandle().isValid()) {
|
||||
m_attachButton->setEnabled(true);
|
||||
Utils::ProcessHandle h = rc->applicationProcessHandle();
|
||||
ProcessHandle h = rc->applicationProcessHandle();
|
||||
QString tip = h.isValid() ? RunControl::tr("PID %1").arg(h.pid())
|
||||
: RunControl::tr("Invalid");
|
||||
m_attachButton->setToolTip(msgAttachDebuggerTooltip(tip));
|
||||
|
@@ -30,6 +30,7 @@
|
||||
#include "buildsystem.h"
|
||||
#include "compileoutputwindow.h"
|
||||
#include "deployconfiguration.h"
|
||||
#include "devicesupport/devicemanager.h"
|
||||
#include "kit.h"
|
||||
#include "kitinformation.h"
|
||||
#include "project.h"
|
||||
@@ -46,8 +47,9 @@
|
||||
#include <coreplugin/icore.h>
|
||||
#include <coreplugin/progressmanager/futureprogress.h>
|
||||
#include <coreplugin/progressmanager/progressmanager.h>
|
||||
|
||||
#include <extensionsystem/pluginmanager.h>
|
||||
#include <projectexplorer/devicesupport/idevice.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/outputformatter.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());
|
||||
case StopBeforeBuild::SameBuildDir:
|
||||
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)) {
|
||||
if (device.isNull())
|
||||
device = DeviceKitAspect::device(t->kit());
|
||||
@@ -126,7 +129,7 @@ static int queue(const QList<Project *> &projects, const QList<Id> &stepIds,
|
||||
continue;
|
||||
for (const BuildConfiguration * const bc
|
||||
: buildConfigsForSelection(t, configSelection)) {
|
||||
if (rc->runnable().command.executable().isChildOf(bc->buildDirectory()))
|
||||
if (executable.isChildOf(bc->buildDirectory()))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -53,7 +53,7 @@ CustomExecutableRunConfiguration::CustomExecutableRunConfiguration(Target *targe
|
||||
exeAspect->setEnvironmentChange(EnvironmentChange::fromFixedEnvironment(envAspect->environment()));
|
||||
|
||||
addAspect<ArgumentsAspect>(macroExpander());
|
||||
addAspect<WorkingDirectoryAspect>(envAspect);
|
||||
addAspect<WorkingDirectoryAspect>(macroExpander(), envAspect);
|
||||
addAspect<TerminalAspect>();
|
||||
|
||||
connect(envAspect, &EnvironmentAspect::environmentChanged, this, [exeAspect, envAspect] {
|
||||
|
@@ -70,7 +70,7 @@ DesktopRunConfiguration::DesktopRunConfiguration(Target *target, Id id, Kind kin
|
||||
|
||||
addAspect<ExecutableAspect>(target);
|
||||
addAspect<ArgumentsAspect>(macroExpander());
|
||||
addAspect<WorkingDirectoryAspect>(envAspect);
|
||||
addAspect<WorkingDirectoryAspect>(macroExpander(), envAspect);
|
||||
addAspect<TerminalAspect>();
|
||||
|
||||
auto libAspect = addAspect<UseLibraryPathsAspect>();
|
||||
|
227
src/plugins/projectexplorer/devicesupport/filetransfer.cpp
Normal file
227
src/plugins/projectexplorer/devicesupport/filetransfer.cpp
Normal 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"
|
@@ -25,40 +25,17 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "remotelinux_export.h"
|
||||
|
||||
#include <projectexplorer/devicesupport/idevicefwd.h>
|
||||
|
||||
#include <utils/filepath.h>
|
||||
#include "../projectexplorer_export.h"
|
||||
#include "filetransferinterface.h"
|
||||
#include "idevicefwd.h"
|
||||
|
||||
namespace Utils { class ProcessResultData; }
|
||||
|
||||
namespace RemoteLinux {
|
||||
|
||||
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
|
||||
};
|
||||
namespace ProjectExplorer {
|
||||
|
||||
class FileTransferPrivate;
|
||||
|
||||
class REMOTELINUX_EXPORT FileTransfer : public QObject
|
||||
class PROJECTEXPLORER_EXPORT FileTransfer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -66,19 +43,17 @@ public:
|
||||
FileTransfer();
|
||||
~FileTransfer();
|
||||
|
||||
void setDevice(const ProjectExplorer::IDeviceConstPtr &device);
|
||||
void setTransferMethod(FileTransferMethod method);
|
||||
void setFilesToTransfer(const FilesToTransfer &files);
|
||||
void setTransferMethod(FileTransferMethod method);
|
||||
void setRsyncFlags(const QString &flags);
|
||||
|
||||
FileTransferMethod transferMethod() const;
|
||||
|
||||
void test();
|
||||
void test(const ProjectExplorer::IDeviceConstPtr &onDevice);
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
static QString transferMethodName(FileTransferMethod method);
|
||||
static QString defaultRsyncFlags();
|
||||
|
||||
signals:
|
||||
void progress(const QString &progressMessage);
|
||||
@@ -88,6 +63,4 @@ private:
|
||||
FileTransferPrivate *d;
|
||||
};
|
||||
|
||||
} // namespace RemoteLinux
|
||||
|
||||
Q_DECLARE_METATYPE(RemoteLinux::FileTransferMethod)
|
||||
} // namespace ProjectExplorer
|
@@ -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
|
@@ -434,6 +434,14 @@ ProcessInterface *IDevice::createProcessInterface() const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FileTransferInterface *IDevice::createFileTransferInterface(
|
||||
const FileTransferSetupData &setup) const
|
||||
{
|
||||
Q_UNUSED(setup)
|
||||
QTC_CHECK(false);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Environment IDevice::systemEnvironment() const
|
||||
{
|
||||
QTC_CHECK(false);
|
||||
|
@@ -60,6 +60,8 @@ class QtcProcess;
|
||||
namespace ProjectExplorer {
|
||||
|
||||
class DeviceProcessList;
|
||||
class FileTransferInterface;
|
||||
class FileTransferSetupData;
|
||||
class Kit;
|
||||
class SshParameters;
|
||||
class Task;
|
||||
@@ -265,6 +267,8 @@ public:
|
||||
virtual QFile::Permissions permissions(const Utils::FilePath &filePath) const;
|
||||
virtual bool setPermissions(const Utils::FilePath &filePath, QFile::Permissions) const;
|
||||
virtual Utils::ProcessInterface *createProcessInterface() const;
|
||||
virtual FileTransferInterface *createFileTransferInterface(
|
||||
const FileTransferSetupData &setup) const;
|
||||
virtual Utils::Environment systemEnvironment() const;
|
||||
virtual qint64 fileSize(const Utils::FilePath &filePath) const;
|
||||
virtual qint64 bytesAvailable(const Utils::FilePath &filePath) const;
|
||||
|
@@ -64,24 +64,21 @@ QStringList SshParameters::connectionOptions(const FilePath &binary) const
|
||||
"-o", "Port=" + QString::number(port())};
|
||||
|
||||
if (!userName().isEmpty())
|
||||
args.append({"-o", "User=" + userName()});
|
||||
args << "-o" << "User=" + userName();
|
||||
|
||||
const bool keyOnly = authenticationType ==
|
||||
SshParameters::AuthenticationTypeSpecificKey;
|
||||
if (keyOnly) {
|
||||
args << "-o" << "IdentitiesOnly=yes";
|
||||
args << "-i" << privateKeyFile.path();
|
||||
}
|
||||
if (keyOnly || SshSettings::askpassFilePath().isEmpty())
|
||||
args << "-o" << "BatchMode=yes";
|
||||
const bool keyOnly = authenticationType == SshParameters::AuthenticationTypeSpecificKey;
|
||||
if (keyOnly)
|
||||
args << "-o" << "IdentitiesOnly=yes" << "-i" << privateKeyFile.path();
|
||||
|
||||
bool useTimeout = timeout != 0;
|
||||
if (useTimeout && HostOsInfo::isWindowsHost()
|
||||
&& binary.toString().toLower().contains("/system32/")) {
|
||||
useTimeout = false;
|
||||
}
|
||||
const QString batchModeEnabled = (keyOnly || SshSettings::askpassFilePath().isEmpty())
|
||||
? QLatin1String("yes") : QLatin1String("no");
|
||||
args << "-o" << "BatchMode=" + batchModeEnabled;
|
||||
|
||||
const bool isWindows = HostOsInfo::isWindowsHost()
|
||||
&& binary.toString().toLower().contains("/system32/");
|
||||
const bool useTimeout = (timeout != 0) && !isWindows;
|
||||
if (useTimeout)
|
||||
args << "-o" << ("ConnectTimeout=" + QString::number(timeout));
|
||||
args << "-o" << "ConnectTimeout=" + QString::number(timeout);
|
||||
|
||||
return args;
|
||||
}
|
||||
|
@@ -1110,6 +1110,13 @@ void DeviceKitAspect::setDeviceId(Kit *k, Utils::Id id)
|
||||
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()
|
||||
{
|
||||
const QList<Kit *> kits = KitManager::kits();
|
||||
|
@@ -161,6 +161,7 @@ public:
|
||||
static Utils::Id deviceId(const Kit *k);
|
||||
static void setDevice(Kit *k, IDeviceConstPtr dev);
|
||||
static void setDeviceId(Kit *k, Utils::Id dataId);
|
||||
static Utils::FilePath deviceFilePath(const Kit *k, const QString &pathOnDevice);
|
||||
|
||||
private:
|
||||
QVariant defaultValue(const Kit *k) const;
|
||||
|
@@ -158,12 +158,23 @@ static bool hostSupportsPlatform(MsvcToolChain::Platform platform)
|
||||
{
|
||||
if (hostPrefersPlatform(platform))
|
||||
return true;
|
||||
|
||||
switch (HostOsInfo::hostArchitecture()) {
|
||||
// The x86 host toolchains are not the preferred toolchains on amd64 but they are still
|
||||
// supported by that host
|
||||
return HostOsInfo::hostArchitecture() == HostOsInfo::HostArchitectureAMD64
|
||||
&& (platform == MsvcToolChain::x86 || platform == MsvcToolChain::x86_amd64
|
||||
case HostOsInfo::HostArchitectureAMD64:
|
||||
return platform == MsvcToolChain::x86 || platform == MsvcToolChain::x86_amd64
|
||||
|| 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)
|
||||
|
@@ -333,7 +333,8 @@ static bool canOpenTerminalWithRunEnv(const Project *project, const ProjectNode
|
||||
const RunConfiguration * const runConfig = runConfigForNode(target, node);
|
||||
if (!runConfig)
|
||||
return false;
|
||||
IDevice::ConstPtr device = runConfig->runnable().device;
|
||||
IDevice::ConstPtr device
|
||||
= DeviceManager::deviceForPath(runConfig->runnable().command.executable());
|
||||
if (!device)
|
||||
device = DeviceKitAspect::device(target->kit());
|
||||
return device && device->canOpenTerminal();
|
||||
@@ -3245,13 +3246,13 @@ void ProjectExplorerPlugin::runRunConfiguration(RunConfiguration *rc,
|
||||
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();
|
||||
for (RunControl *rc : runControls) {
|
||||
if (rc->isRunning())
|
||||
processes << qMakePair(rc->runnable(), rc->applicationProcessHandle());
|
||||
processes << qMakePair(rc->commandLine(), rc->applicationProcessHandle());
|
||||
}
|
||||
return processes;
|
||||
}
|
||||
@@ -3935,7 +3936,7 @@ void ProjectExplorerPluginPrivate::openTerminalHereWithRunEnv()
|
||||
QTC_ASSERT(runConfig, return);
|
||||
|
||||
const Runnable runnable = runConfig->runnable();
|
||||
IDevice::ConstPtr device = runnable.device;
|
||||
IDevice::ConstPtr device = DeviceManager::deviceForPath(runnable.command.executable());
|
||||
if (!device)
|
||||
device = DeviceKitAspect::device(target->kit());
|
||||
QTC_ASSERT(device && device->canOpenTerminal(), return);
|
||||
|
@@ -164,7 +164,7 @@ public:
|
||||
static void runStartupProject(Utils::Id runMode, bool forceSkipDeploy = false);
|
||||
static void runRunConfiguration(RunConfiguration *rc, Utils::Id runMode,
|
||||
const bool forceSkipDeploy = false);
|
||||
static QList<QPair<Runnable, Utils::ProcessHandle>> runningRunControlProcesses();
|
||||
static QList<QPair<Utils::CommandLine, Utils::ProcessHandle>> runningRunControlProcesses();
|
||||
static QList<RunControl *> allRunControls();
|
||||
|
||||
static void addExistingFiles(FolderNode *folderNode, const Utils::FilePaths &filePaths);
|
||||
|
@@ -219,6 +219,8 @@ Project {
|
||||
"devicesettingswidget.cpp", "devicesettingswidget.h", "devicesettingswidget.ui",
|
||||
"devicetestdialog.cpp", "devicetestdialog.h", "devicetestdialog.ui",
|
||||
"deviceusedportsgatherer.cpp", "deviceusedportsgatherer.h",
|
||||
"filetransfer.cpp", "filetransfer.h",
|
||||
"filetransferinterface.h",
|
||||
"idevice.cpp", "idevice.h",
|
||||
"idevicefactory.cpp", "idevicefactory.h",
|
||||
"idevicefwd.h",
|
||||
|
@@ -171,8 +171,9 @@ bool TerminalAspect::isUserSet() const
|
||||
working directory for running the executable.
|
||||
*/
|
||||
|
||||
WorkingDirectoryAspect::WorkingDirectoryAspect(EnvironmentAspect *envAspect)
|
||||
: m_envAspect(envAspect)
|
||||
WorkingDirectoryAspect::WorkingDirectoryAspect(const MacroExpander *expander,
|
||||
EnvironmentAspect *envAspect)
|
||||
: m_envAspect(envAspect), m_macroExpander(expander)
|
||||
{
|
||||
setDisplayName(tr("Working Directory"));
|
||||
setId("WorkingDirectoryAspect");
|
||||
|
@@ -81,7 +81,8 @@ class PROJECTEXPLORER_EXPORT WorkingDirectoryAspect : public Utils::BaseAspect
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WorkingDirectoryAspect(EnvironmentAspect *envAspect);
|
||||
explicit WorkingDirectoryAspect(const Utils::MacroExpander *expander,
|
||||
EnvironmentAspect *envAspect);
|
||||
|
||||
void addToLayout(Utils::LayoutBuilder &builder) override;
|
||||
|
||||
@@ -102,7 +103,7 @@ private:
|
||||
Utils::FilePath m_defaultWorkingDirectory;
|
||||
QPointer<Utils::PathChooser> m_chooser;
|
||||
QPointer<QToolButton> m_resetButton;
|
||||
Utils::MacroExpander *m_macroExpander = nullptr;
|
||||
const Utils::MacroExpander *m_macroExpander = nullptr;
|
||||
};
|
||||
|
||||
class PROJECTEXPLORER_EXPORT ArgumentsAspect : public Utils::BaseAspect
|
||||
|
@@ -28,6 +28,7 @@
|
||||
#include "buildconfiguration.h"
|
||||
#include "customparser.h"
|
||||
#include "devicesupport/desktopdevice.h"
|
||||
#include "devicesupport/devicemanager.h"
|
||||
#include "devicesupport/idevice.h"
|
||||
#include "devicesupport/sshsettings.h"
|
||||
#include "kitinformation.h"
|
||||
@@ -323,7 +324,6 @@ public:
|
||||
QMap<Utils::Id, QVariantMap> settingsData;
|
||||
Utils::Id runConfigId;
|
||||
BuildTargetInfo buildTargetInfo;
|
||||
BuildConfiguration::BuildType buildType = BuildConfiguration::Unknown;
|
||||
FilePath buildDirectory;
|
||||
Environment buildEnvironment;
|
||||
Kit *kit = nullptr; // Not owned.
|
||||
@@ -430,7 +430,6 @@ void RunControl::setTarget(Target *target)
|
||||
d->buildTargetInfo = target->buildTarget(d->buildKey);
|
||||
|
||||
if (auto bc = target->activeBuildConfiguration()) {
|
||||
d->buildType = bc->buildType();
|
||||
d->buildDirectory = bc->buildDirectory();
|
||||
d->buildEnvironment = bc->environment();
|
||||
}
|
||||
@@ -447,8 +446,8 @@ void RunControl::setKit(Kit *kit)
|
||||
d->kit = kit;
|
||||
d->macroExpander = kit->macroExpander();
|
||||
|
||||
if (d->runnable.device)
|
||||
setDevice(d->runnable.device);
|
||||
if (!d->runnable.command.isEmpty())
|
||||
setDevice(DeviceManager::deviceForPath(d->runnable.command.executable()));
|
||||
else
|
||||
setDevice(DeviceKitAspect::device(kit));
|
||||
}
|
||||
@@ -905,13 +904,50 @@ const Runnable &RunControl::runnable() const
|
||||
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
|
||||
{
|
||||
if (d->displayName.isEmpty())
|
||||
return d->runnable.command.executable().toUserOutput();
|
||||
return d->displayName;
|
||||
}
|
||||
|
||||
@@ -975,11 +1011,6 @@ QString RunControl::buildKey() const
|
||||
return d->buildKey;
|
||||
}
|
||||
|
||||
BuildConfiguration::BuildType RunControl::buildType() const
|
||||
{
|
||||
return d->buildType;
|
||||
}
|
||||
|
||||
FilePath RunControl::buildDirectory() const
|
||||
{
|
||||
return d->buildDirectory;
|
||||
@@ -1242,7 +1273,10 @@ public:
|
||||
State m_state = Inactive;
|
||||
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;
|
||||
|
||||
@@ -1398,12 +1432,12 @@ void SimpleTargetRunnerPrivate::handleStandardError()
|
||||
|
||||
void SimpleTargetRunnerPrivate::start()
|
||||
{
|
||||
m_isLocal = m_runnable.device.isNull() || m_runnable.device.dynamicCast<const DesktopDevice>();
|
||||
m_isLocal = !m_command.executable().needsDevice();
|
||||
|
||||
m_resultData = {};
|
||||
|
||||
if (m_isLocal) {
|
||||
Environment env = m_runnable.environment;
|
||||
Environment env = m_environment;
|
||||
if (m_runAsRoot)
|
||||
RunControl::provideAskPassEntry(env);
|
||||
|
||||
@@ -1413,7 +1447,7 @@ void SimpleTargetRunnerPrivate::start()
|
||||
|
||||
WinDebugInterface::startIfNeeded();
|
||||
|
||||
CommandLine cmdLine = m_runnable.command;
|
||||
CommandLine cmdLine = m_command;
|
||||
|
||||
if (HostOsInfo::isMacHost()) {
|
||||
CommandLine disclaim(Core::ICore::libexecPath("disclaim"));
|
||||
@@ -1426,7 +1460,8 @@ void SimpleTargetRunnerPrivate::start()
|
||||
} else {
|
||||
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_error = QProcess::FailedToStart;
|
||||
m_resultData.m_exitStatus = QProcess::CrashExit;
|
||||
@@ -1434,7 +1469,7 @@ void SimpleTargetRunnerPrivate::start()
|
||||
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_error = QProcess::FailedToStart;
|
||||
m_resultData.m_exitStatus = QProcess::CrashExit;
|
||||
@@ -1445,12 +1480,12 @@ void SimpleTargetRunnerPrivate::start()
|
||||
m_state = Run;
|
||||
m_stopRequested = false;
|
||||
|
||||
m_process.setCommand(m_runnable.command);
|
||||
m_process.setEnvironment(m_runnable.environment);
|
||||
m_process.setExtraData(m_runnable.extraData);
|
||||
m_process.setCommand(m_command);
|
||||
m_process.setEnvironment(m_environment);
|
||||
m_process.setExtraData(m_extraData);
|
||||
}
|
||||
|
||||
m_process.setWorkingDirectory(m_runnable.workingDirectory);
|
||||
m_process.setWorkingDirectory(m_workingDirectory);
|
||||
|
||||
if (m_isLocal)
|
||||
m_outputCodec = QTextCodec::codecForLocale();
|
||||
@@ -1485,14 +1520,14 @@ void SimpleTargetRunnerPrivate::forwardDone()
|
||||
{
|
||||
if (m_stopReported)
|
||||
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);
|
||||
if (m_resultData.m_exitStatus == QProcess::CrashExit)
|
||||
msg = tr("%1 crashed.").arg(executable);
|
||||
else if (m_stopForced)
|
||||
msg = tr("The process was ended forcefully.");
|
||||
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);
|
||||
m_stopReported = true;
|
||||
q->reportStopped();
|
||||
@@ -1500,8 +1535,7 @@ void SimpleTargetRunnerPrivate::forwardDone()
|
||||
|
||||
void SimpleTargetRunnerPrivate::forwardStarted()
|
||||
{
|
||||
const bool isDesktop = m_runnable.device.isNull()
|
||||
|| m_runnable.device.dynamicCast<const DesktopDevice>();
|
||||
const bool isDesktop = !m_command.executable().needsDevice();
|
||||
if (isDesktop) {
|
||||
// Console processes only know their pid after being started
|
||||
ProcessHandle pid{privateApplicationPID()};
|
||||
@@ -1514,8 +1548,10 @@ void SimpleTargetRunnerPrivate::forwardStarted()
|
||||
|
||||
void SimpleTargetRunner::start()
|
||||
{
|
||||
d->m_runnable = runControl()->runnable();
|
||||
d->m_runnable.device = runControl()->device();
|
||||
d->m_command = runControl()->commandLine();
|
||||
d->m_workingDirectory = runControl()->workingDirectory();
|
||||
d->m_environment = runControl()->environment();
|
||||
d->m_extraData = runControl()->extraData();
|
||||
|
||||
if (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_runAsRoot = runAsRoot;
|
||||
|
||||
const QString msg = RunControl::tr("Starting %1...").arg(d->m_runnable.command.toUserOutput());
|
||||
appendMessage(msg, Utils::NormalMessageFormat);
|
||||
const QString msg = RunControl::tr("Starting %1...").arg(d->m_command.toUserOutput());
|
||||
appendMessage(msg, NormalMessageFormat);
|
||||
|
||||
const bool isDesktop = d->m_runnable.device.isNull()
|
||||
|| d->m_runnable.device.dynamicCast<const DesktopDevice>();
|
||||
if (isDesktop && d->m_runnable.command.isEmpty()) {
|
||||
const bool isDesktop = !d->m_command.executable().needsDevice();
|
||||
if (isDesktop && d->m_command.isEmpty()) {
|
||||
reportFailure(RunControl::tr("No executable specified."));
|
||||
return;
|
||||
}
|
||||
@@ -1559,31 +1594,30 @@ void SimpleTargetRunner::setStartModifier(const std::function<void ()> &startMod
|
||||
|
||||
CommandLine SimpleTargetRunner::commandLine() const
|
||||
{
|
||||
return d->m_runnable.command;
|
||||
return d->m_command;
|
||||
}
|
||||
|
||||
void SimpleTargetRunner::setCommandLine(const Utils::CommandLine &commandLine)
|
||||
{
|
||||
d->m_runnable.command = commandLine;
|
||||
d->m_command = commandLine;
|
||||
}
|
||||
|
||||
void SimpleTargetRunner::setEnvironment(const Environment &environment)
|
||||
{
|
||||
d->m_runnable.environment = environment;
|
||||
d->m_environment = environment;
|
||||
}
|
||||
|
||||
void SimpleTargetRunner::setWorkingDirectory(const FilePath &workingDirectory)
|
||||
{
|
||||
d->m_runnable.workingDirectory = workingDirectory;
|
||||
d->m_workingDirectory = workingDirectory;
|
||||
}
|
||||
|
||||
void SimpleTargetRunner::forceRunOnHost()
|
||||
{
|
||||
d->m_runnable.device = {};
|
||||
const FilePath executable = d->m_runnable.command.executable();
|
||||
const FilePath executable = d->m_command.executable();
|
||||
if (executable.needsDevice()) {
|
||||
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();
|
||||
}
|
||||
|
||||
const Runnable &RunWorker::runnable() const
|
||||
{
|
||||
return d->runControl->runnable();
|
||||
}
|
||||
|
||||
void RunWorker::addStartDependency(RunWorker *dependency)
|
||||
{
|
||||
d->startDependencies.append(dependency);
|
||||
@@ -1906,11 +1935,6 @@ void RunWorker::stop()
|
||||
reportStopped();
|
||||
}
|
||||
|
||||
QString Runnable::displayName() const
|
||||
{
|
||||
return command.executable().toString();
|
||||
}
|
||||
|
||||
// OutputFormatterFactory
|
||||
|
||||
static QList<OutputFormatterFactory *> g_outputFormatterFactories;
|
||||
|
@@ -25,9 +25,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "buildconfiguration.h"
|
||||
#include "devicesupport/idevicefwd.h"
|
||||
#include "projectexplorerconstants.h"
|
||||
#include "runconfiguration.h"
|
||||
|
||||
#include <utils/commandline.h>
|
||||
@@ -50,8 +48,6 @@ class OutputLineParser;
|
||||
} // Utils
|
||||
|
||||
namespace ProjectExplorer {
|
||||
class GlobalOrProjectAspect;
|
||||
class Node;
|
||||
class RunConfiguration;
|
||||
class RunControl;
|
||||
class Target;
|
||||
@@ -71,11 +67,7 @@ public:
|
||||
Utils::CommandLine command;
|
||||
Utils::FilePath workingDirectory;
|
||||
Utils::Environment environment;
|
||||
IDeviceConstPtr device; // Override the kit's device. Keep unset by default.
|
||||
QVariantHash extraData;
|
||||
|
||||
// FIXME: Not necessarily a display name
|
||||
QString displayName() const;
|
||||
};
|
||||
|
||||
class PROJECTEXPLORER_EXPORT RunWorker : public QObject
|
||||
@@ -102,7 +94,6 @@ public:
|
||||
// Part of read-only interface of RunControl for convenience.
|
||||
void appendMessage(const QString &msg, Utils::OutputFormat format, bool appendNewLine = true);
|
||||
IDeviceConstPtr device() const;
|
||||
const Runnable &runnable() const;
|
||||
|
||||
// States
|
||||
void initiateStart();
|
||||
@@ -211,7 +202,7 @@ public:
|
||||
|
||||
bool supportsReRunning() const;
|
||||
|
||||
virtual QString displayName() const;
|
||||
QString displayName() const;
|
||||
void setDisplayName(const QString &displayName);
|
||||
|
||||
bool isRunning() const;
|
||||
@@ -239,7 +230,6 @@ public:
|
||||
}
|
||||
|
||||
QString buildKey() const;
|
||||
BuildConfiguration::BuildType buildType() const;
|
||||
Utils::FilePath buildDirectory() const;
|
||||
Utils::Environment buildEnvironment() const;
|
||||
|
||||
@@ -252,7 +242,18 @@ public:
|
||||
Utils::Id runMode() 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,
|
||||
const QString &stopButtonText = QString(),
|
||||
|
@@ -89,24 +89,9 @@ PySideBuildStep::PySideBuildStep(BuildStepList *bsl, Id id)
|
||||
setWorkingDirectoryProvider([this] { return target()->project()->projectDirectory(); });
|
||||
}
|
||||
|
||||
void PySideBuildStep::updateInterpreter(const Utils::FilePath &python)
|
||||
void PySideBuildStep::updatePySideProjectPath(const Utils::FilePath &pySideProjectPath)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pySideProjectPath.isExecutableFile())
|
||||
pySideProjectPath = Environment::systemEnvironment().searchInPath("pyside6-project");
|
||||
|
||||
if (pySideProjectPath.isExecutableFile())
|
||||
m_pysideProject->setFilePath(pySideProjectPath);
|
||||
m_pysideProject->setFilePath(pySideProjectPath);
|
||||
}
|
||||
|
||||
void PySideBuildStep::doRun()
|
||||
|
@@ -50,7 +50,7 @@ class PySideBuildStep : public ProjectExplorer::AbstractProcessStep
|
||||
Q_OBJECT
|
||||
public:
|
||||
PySideBuildStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id);
|
||||
void updateInterpreter(const Utils::FilePath &python);
|
||||
void updatePySideProjectPath(const Utils::FilePath &pySideProjectPath);
|
||||
|
||||
private:
|
||||
Utils::StringAspect *m_pysideProject;
|
||||
|
@@ -140,85 +140,99 @@ private:
|
||||
class PythonRunConfiguration : public RunConfiguration
|
||||
{
|
||||
public:
|
||||
PythonRunConfiguration(Target *target, Id id)
|
||||
: RunConfiguration(target, id)
|
||||
{
|
||||
auto interpreterAspect = addAspect<InterpreterAspect>();
|
||||
interpreterAspect->setSettingsKey("PythonEditor.RunConfiguation.Interpreter");
|
||||
interpreterAspect->setSettingsDialogId(Constants::C_PYTHONOPTIONS_PAGE_ID);
|
||||
PythonRunConfiguration(Target *target, Id id);
|
||||
void currentInterpreterChanged();
|
||||
};
|
||||
|
||||
connect(interpreterAspect, &InterpreterAspect::changed,
|
||||
this, &PythonRunConfiguration::currentInterpreterChanged);
|
||||
PythonRunConfiguration::PythonRunConfiguration(Target *target, Id id)
|
||||
: RunConfiguration(target, id)
|
||||
{
|
||||
auto interpreterAspect = addAspect<InterpreterAspect>();
|
||||
interpreterAspect->setSettingsKey("PythonEditor.RunConfiguation.Interpreter");
|
||||
interpreterAspect->setSettingsDialogId(Constants::C_PYTHONOPTIONS_PAGE_ID);
|
||||
|
||||
connect(PythonSettings::instance(), &PythonSettings::interpretersChanged,
|
||||
interpreterAspect, &InterpreterAspect::updateInterpreters);
|
||||
connect(interpreterAspect, &InterpreterAspect::changed,
|
||||
this, &PythonRunConfiguration::currentInterpreterChanged);
|
||||
|
||||
QList<Interpreter> interpreters = PythonSettings::detectPythonVenvs(
|
||||
project()->projectDirectory());
|
||||
interpreterAspect->updateInterpreters(PythonSettings::interpreters());
|
||||
Interpreter defaultInterpreter = interpreters.isEmpty()
|
||||
? PythonSettings::defaultInterpreter()
|
||||
: interpreters.first();
|
||||
if (!defaultInterpreter.command.isExecutableFile())
|
||||
defaultInterpreter = PythonSettings::interpreters().value(0);
|
||||
interpreterAspect->setDefaultInterpreter(defaultInterpreter);
|
||||
connect(PythonSettings::instance(), &PythonSettings::interpretersChanged,
|
||||
interpreterAspect, &InterpreterAspect::updateInterpreters);
|
||||
|
||||
auto bufferedAspect = addAspect<BoolAspect>();
|
||||
bufferedAspect->setSettingsKey("PythonEditor.RunConfiguation.Buffered");
|
||||
bufferedAspect->setLabel(tr("Buffered output"), BoolAspect::LabelPlacement::AtCheckBox);
|
||||
bufferedAspect->setToolTip(tr("Enabling improves output performance, "
|
||||
"but results in delayed output."));
|
||||
QList<Interpreter> interpreters = PythonSettings::detectPythonVenvs(
|
||||
project()->projectDirectory());
|
||||
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 scriptAspect = addAspect<MainScriptAspect>();
|
||||
scriptAspect->setSettingsKey("PythonEditor.RunConfiguation.Script");
|
||||
scriptAspect->setLabelText(tr("Script:"));
|
||||
scriptAspect->setDisplayStyle(StringAspect::LabelDisplay);
|
||||
auto bufferedAspect = addAspect<BoolAspect>();
|
||||
bufferedAspect->setSettingsKey("PythonEditor.RunConfiguation.Buffered");
|
||||
bufferedAspect->setLabel(tr("Buffered output"), BoolAspect::LabelPlacement::AtCheckBox);
|
||||
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);
|
||||
addAspect<TerminalAspect>();
|
||||
auto argumentsAspect = addAspect<ArgumentsAspect>(macroExpander());
|
||||
|
||||
setCommandLineGetter([bufferedAspect, interpreterAspect, argumentsAspect, scriptAspect] {
|
||||
CommandLine cmd{interpreterAspect->currentInterpreter().command};
|
||||
if (!bufferedAspect->value())
|
||||
cmd.addArg("-u");
|
||||
cmd.addArg(scriptAspect->filePath().fileName());
|
||||
cmd.addArgs(argumentsAspect->arguments(), CommandLine::Raw);
|
||||
return cmd;
|
||||
});
|
||||
addAspect<WorkingDirectoryAspect>(macroExpander(), nullptr);
|
||||
addAspect<TerminalAspect>();
|
||||
|
||||
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());
|
||||
});
|
||||
setCommandLineGetter([bufferedAspect, interpreterAspect, argumentsAspect, scriptAspect] {
|
||||
CommandLine cmd{interpreterAspect->currentInterpreter().command};
|
||||
if (!bufferedAspect->value())
|
||||
cmd.addArg("-u");
|
||||
cmd.addArg(scriptAspect->filePath().fileName());
|
||||
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()
|
||||
{
|
||||
const FilePath python = aspect<InterpreterAspect>()->currentInterpreter().command;
|
||||
if (auto pySideBuildStep = buildSteps->firstOfType<PySideBuildStep>())
|
||||
pySideBuildStep->updatePySideProjectPath(pySideProjectPath);
|
||||
|
||||
BuildStepList *buildSteps = target()->activeBuildConfiguration()->buildSteps();
|
||||
if (auto pySideBuildStep = buildSteps->firstOfType<PySideBuildStep>())
|
||||
pySideBuildStep->updateInterpreter(python);
|
||||
|
||||
for (FilePath &file : project()->files(Project::AllFiles)) {
|
||||
if (auto document = TextEditor::TextDocument::textDocumentForFilePath(file)) {
|
||||
if (document->mimeType() == Constants::C_PY_MIMETYPE) {
|
||||
PyLSConfigureAssistant::openDocumentWithPython(python, document);
|
||||
PySideInstaller::checkPySideInstallation(python, document);
|
||||
}
|
||||
for (FilePath &file : project()->files(Project::AllFiles)) {
|
||||
if (auto document = TextEditor::TextDocument::textDocumentForFilePath(file)) {
|
||||
if (document->mimeType() == Constants::C_PY_MIMETYPE) {
|
||||
PyLSConfigureAssistant::openDocumentWithPython(python, document);
|
||||
PySideInstaller::checkPySideInstallation(python, document);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
PythonRunConfigurationFactory::PythonRunConfigurationFactory()
|
||||
{
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user