Commit 69d8e21f authored by Christoph Cullmann's avatar Christoph Cullmann 🐮
Browse files

Merge branch 'master' into release/21.04

parents 85633810 514c1ead
......@@ -84,6 +84,8 @@ KateFileTree::KateFileTree(QWidget *parent)
setDragEnabled(true);
setDragDropMode(QAbstractItemView::DragOnly);
setSelectionBehavior(QAbstractItemView::SelectRows);
// for hover close button
viewport()->setAttribute(Qt::WA_Hover);
setItemDelegate(new StyleDelegate(this));
......
......@@ -318,16 +318,33 @@ public:
return doc->characterAt(KTextEditor::Cursor(range.end().line(), range.end().column()));
}
static bool isFunctionKind(LSPCompletionItemKind k)
{
return k == LSPCompletionItemKind::Function || k == LSPCompletionItemKind::Method;
}
void executeCompletionItem(KTextEditor::View *view, const KTextEditor::Range &word, const QModelIndex &index) const override
{
if (index.row() < m_matches.size()) {
auto next = peekNextChar(view->document(), word);
auto matching = m_matches.at(index.row()).insertText;
QChar next = peekNextChar(view->document(), word);
QString matching = m_matches.at(index.row()).insertText;
// if there is already a '"' or >, remove it, this happens with #include "xx.h"
if ((next == QLatin1Char('"') && matching.endsWith(QLatin1Char('"'))) || (next == QLatin1Char('>') && matching.endsWith(QLatin1Char('>')))) {
matching.chop(1);
}
// This is a function
const auto &m = m_matches.at(index.row());
if (isFunctionKind(m.kind)) {
matching += QStringLiteral("()");
}
view->document()->replaceText(word, matching);
if (isFunctionKind(m.kind)) {
// place the cursor in between (|)
view->setCursorPosition({view->cursorPosition().line(), view->cursorPosition().column() - 1});
}
}
}
......
......@@ -59,6 +59,7 @@ target_sources(
kateprojectconfigpage.cpp
kateprojectcodeanalysistool.cpp
branchesdialog.cpp
branchcheckoutdialog.cpp
branchesdialogmodel.cpp
gitwidget.cpp
gitstatusmodel.cpp
......@@ -67,6 +68,7 @@ target_sources(
filehistorywidget.cpp
quickdialog.cpp
pushpulldialog.cpp
comarebranchesview.cpp
tools/kateprojectcodeanalysistoolcppcheck.cpp
tools/kateprojectcodeanalysistoolflake8.cpp
......
/*
SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "branchcheckoutdialog.h"
#include "branchesdialogmodel.h"
#include <KLocalizedString>
#include <QtConcurrentRun>
BranchCheckoutDialog::BranchCheckoutDialog(QWidget *mainWindow, KateProjectPluginView *pluginView, QString projectPath)
: BranchesDialog(mainWindow, pluginView, projectPath)
{
connect(&m_checkoutWatcher, &QFutureWatcher<GitUtils::CheckoutResult>::finished, this, &BranchCheckoutDialog::onCheckoutDone);
}
BranchCheckoutDialog::~BranchCheckoutDialog()
{
if (m_checkoutWatcher.isRunning()) {
onCheckoutDone();
}
}
void BranchCheckoutDialog::resetValues()
{
m_checkoutBranchName.clear();
m_checkingOutFromBranch = false;
m_lineEdit.setPlaceholderText(i18n("Select branch to checkout. Press 'Esc' to cancel."));
}
void BranchCheckoutDialog::openDialog()
{
resetValues();
GitUtils::Branch newBranch;
newBranch.name = i18n("Create New Branch");
GitUtils::Branch newBranchFrom;
newBranchFrom.name = i18n("Create New Branch From...");
QVector<GitUtils::Branch> branches{newBranch, newBranchFrom};
branches << GitUtils::getAllBranches(m_projectPath);
m_model->refresh(branches, /*checkingOut:*/ true);
reselectFirst();
updateViewGeometry();
setFocus();
exec();
}
void BranchCheckoutDialog::onCheckoutDone()
{
const GitUtils::CheckoutResult res = m_checkoutWatcher.result();
bool warn = false;
QString msgStr = i18n("Branch %1 checked out", res.branch);
if (res.returnCode > 0) {
warn = true;
msgStr = i18n("Failed to checkout to branch %1, Error: %2", res.branch, res.error);
}
sendMessage(msgStr, warn);
}
void BranchCheckoutDialog::slotReturnPressed()
{
// we cleared the model to checkout new branch
if (m_model->rowCount() == 0) {
createNewBranch(m_lineEdit.text(), m_checkoutBranchName);
return;
}
// branch is selected, do actual checkout
if (m_checkingOutFromBranch) {
m_checkingOutFromBranch = false;
const auto fromBranch = m_proxyModel->data(m_treeView.currentIndex(), BranchesDialogModel::CheckoutName).toString();
m_checkoutBranchName = fromBranch;
m_model->clear();
clearLineEdit();
m_lineEdit.setPlaceholderText(i18n("Enter new branch name. Press 'Esc' to cancel."));
return;
}
const auto branch = m_proxyModel->data(m_treeView.currentIndex(), BranchesDialogModel::CheckoutName).toString();
const auto itemType = (BranchesDialogModel::ItemType)m_proxyModel->data(m_treeView.currentIndex(), BranchesDialogModel::ItemTypeRole).toInt();
if (itemType == BranchesDialogModel::BranchItem) {
QFuture<GitUtils::CheckoutResult> future = QtConcurrent::run(&GitUtils::checkoutBranch, m_projectPath, branch);
m_checkoutWatcher.setFuture(future);
} else if (itemType == BranchesDialogModel::CreateBranch) {
m_model->clear();
m_lineEdit.setPlaceholderText(i18n("Enter new branch name. Press 'Esc' to cancel."));
return;
} else if (itemType == BranchesDialogModel::CreateBranchFrom) {
m_model->clearBranchCreationItems();
clearLineEdit();
m_lineEdit.setPlaceholderText(i18n("Select branch to checkout from. Press 'Esc' to cancel."));
m_checkingOutFromBranch = true;
return;
}
clearLineEdit();
hide();
}
void BranchCheckoutDialog::reselectFirst()
{
QModelIndex index = m_proxyModel->index(0, 0);
m_treeView.setCurrentIndex(index);
}
void BranchCheckoutDialog::createNewBranch(const QString &branch, const QString &fromBranch)
{
if (branch.isEmpty()) {
clearLineEdit();
hide();
return;
}
// the branch name might be invalid, let git handle it
const GitUtils::CheckoutResult r = GitUtils::checkoutNewBranch(m_projectPath, branch, fromBranch);
const bool warn = true;
if (r.returnCode == 0) {
sendMessage(i18n("Checked out to new branch: %1", r.branch), !warn);
} else {
sendMessage(i18n("Failed to create new branch. Error \"%1\"", r.error), warn);
}
clearLineEdit();
hide();
}
/*
SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef BRANCHCHECKOUTDIALOG_H
#define BRANCHCHECKOUTDIALOG_H
#include "branchesdialog.h"
class BranchCheckoutDialog : public BranchesDialog
{
Q_OBJECT
public:
BranchCheckoutDialog(QWidget *mainWindow, KateProjectPluginView *pluginView, QString projectPath);
~BranchCheckoutDialog();
void openDialog();
private Q_SLOTS:
void slotReturnPressed() override;
void reselectFirst();
void onCheckoutDone();
private:
void resetValues();
void createNewBranch(const QString &branch, const QString &fromBranch = QString());
private:
QFutureWatcher<GitUtils::CheckoutResult> m_checkoutWatcher;
QString m_checkoutBranchName;
bool m_checkingOutFromBranch = false;
};
#endif // BRANCHCHECKOUTDIALOG_H
......@@ -154,8 +154,8 @@ private:
BranchesDialog::BranchesDialog(QWidget *window, KateProjectPluginView *pluginView, QString projectPath)
: QuickDialog(nullptr, window)
, m_pluginView(pluginView)
, m_projectPath(projectPath)
, m_pluginView(pluginView)
{
m_model = new BranchesDialogModel(this);
m_proxyModel = new BranchFilterModel(this);
......@@ -165,36 +165,16 @@ BranchesDialog::BranchesDialog(QWidget *window, KateProjectPluginView *pluginVie
auto delegate = new StyleDelegate(this);
connect(&m_lineEdit, &QLineEdit::textChanged, this, [this, delegate](const QString &s) {
m_proxyModel->setFilterString(s);
static_cast<BranchFilterModel *>(m_proxyModel)->setFilterString(s);
delegate->setFilterString(s);
});
connect(&m_checkoutWatcher, &QFutureWatcher<GitUtils::CheckoutResult>::finished, this, &BranchesDialog::onCheckoutDone);
}
BranchesDialog::~BranchesDialog()
{
if (m_checkoutWatcher.isRunning()) {
onCheckoutDone();
}
}
void BranchesDialog::resetValues()
void BranchesDialog::openDialog(GitUtils::RefType r)
{
m_checkoutBranchName.clear();
m_checkingOutFromBranch = false;
m_lineEdit.setPlaceholderText(i18n("Select branch to checkout. Press 'Esc' to cancel."));
}
m_lineEdit.setPlaceholderText(i18n("Select Branch..."));
void BranchesDialog::openDialog()
{
resetValues();
GitUtils::Branch newBranch;
newBranch.name = i18n("Create New Branch");
GitUtils::Branch newBranchFrom;
newBranchFrom.name = i18n("Create New Branch From...");
QVector<GitUtils::Branch> branches{newBranch, newBranchFrom};
/*QVector<GitUtils::Branch> */ branches << GitUtils::getAllBranches(m_projectPath);
QVector<GitUtils::Branch> branches = GitUtils::getAllBranchesAndTags(m_projectPath, r);
m_model->refresh(branches);
reselectFirst();
......@@ -203,55 +183,14 @@ void BranchesDialog::openDialog()
exec();
}
void BranchesDialog::onCheckoutDone()
{
const GitUtils::CheckoutResult res = m_checkoutWatcher.result();
auto msgType = KTextEditor::Message::Positive;
QString msgStr = i18n("Branch %1 checked out", res.branch);
if (res.returnCode > 0) {
msgType = KTextEditor::Message::Warning;
msgStr = i18n("Failed to checkout to branch %1, Error: %2", res.branch, res.error);
}
sendMessage(msgStr, msgType == KTextEditor::Message::Warning);
}
void BranchesDialog::slotReturnPressed()
{
// we cleared the model to checkout new branch
if (m_model->rowCount() == 0) {
createNewBranch(m_lineEdit.text(), m_checkoutBranchName);
return;
}
// branch is selected, do actual checkout
if (m_checkingOutFromBranch) {
m_checkingOutFromBranch = false;
const auto fromBranch = m_proxyModel->data(m_treeView.currentIndex(), BranchesDialogModel::CheckoutName).toString();
m_checkoutBranchName = fromBranch;
m_model->clear();
clearLineEdit();
m_lineEdit.setPlaceholderText(i18n("Enter new branch name. Press 'Esc' to cancel."));
return;
}
const auto branch = m_proxyModel->data(m_treeView.currentIndex(), BranchesDialogModel::CheckoutName).toString();
const auto itemType = (BranchesDialogModel::ItemType)m_proxyModel->data(m_treeView.currentIndex(), BranchesDialogModel::ItemTypeRole).toInt();
Q_ASSERT(itemType == BranchesDialogModel::BranchItem);
if (itemType == BranchesDialogModel::BranchItem) {
QFuture<GitUtils::CheckoutResult> future = QtConcurrent::run(&GitUtils::checkoutBranch, m_projectPath, branch);
m_checkoutWatcher.setFuture(future);
} else if (itemType == BranchesDialogModel::CreateBranch) {
m_model->clear();
m_lineEdit.setPlaceholderText(i18n("Enter new branch name. Press 'Esc' to cancel."));
return;
} else if (itemType == BranchesDialogModel::CreateBranchFrom) {
m_model->clearBranchCreationItems();
clearLineEdit();
m_lineEdit.setPlaceholderText(i18n("Select branch to checkout from. Press 'Esc' to cancel."));
m_checkingOutFromBranch = true;
return;
}
m_branch = branch;
Q_EMIT branchSelected(branch);
clearLineEdit();
hide();
......@@ -273,24 +212,3 @@ void BranchesDialog::sendMessage(const QString &plainText, bool warn)
genericMessage.insert(QStringLiteral("text"), plainText);
Q_EMIT m_pluginView->message(genericMessage);
}
void BranchesDialog::createNewBranch(const QString &branch, const QString &fromBranch)
{
if (branch.isEmpty()) {
clearLineEdit();
hide();
return;
}
// the branch name might be invalid, let git handle it
const GitUtils::CheckoutResult r = GitUtils::checkoutNewBranch(m_projectPath, branch, fromBranch);
const bool warn = true;
if (r.returnCode == 0) {
sendMessage(i18n("Checked out to new branch: %1", r.branch), !warn);
} else {
sendMessage(i18n("Failed to create new branch. Error \"%1\"", r.error), warn);
}
clearLineEdit();
hide();
}
......@@ -3,6 +3,9 @@
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef BRANCHES_DIALOG_H
#define BRANCHES_DIALOG_H
#include <QFutureWatcher>
#include <QMenu>
......@@ -27,25 +30,28 @@ class BranchesDialog : public QuickDialog
Q_OBJECT
public:
BranchesDialog(QWidget *window, KateProjectPluginView *pluginView, QString projectPath);
~BranchesDialog();
void openDialog();
void openDialog(GitUtils::RefType r);
void sendMessage(const QString &message, bool warn);
QString branch() const
{
return m_branch;
}
Q_SIGNALS:
void branchSelected(const QString &branch);
private Q_SLOTS:
void slotReturnPressed() override;
void reselectFirst();
void onCheckoutDone();
private:
void resetValues();
void sendMessage(const QString &message, bool warn);
void createNewBranch(const QString &branch, const QString &fromBranch = QString());
protected:
BranchesDialogModel *m_model;
QSortFilterProxyModel *m_proxyModel;
QString m_projectPath;
private:
BranchesDialogModel *m_model;
BranchFilterModel *m_proxyModel;
KateProjectPluginView *m_pluginView;
QString m_projectPath;
QFutureWatcher<GitUtils::CheckoutResult> m_checkoutWatcher;
QString m_checkoutBranchName;
bool m_checkingOutFromBranch = false;
QString m_branch;
};
#endif
......@@ -65,15 +65,18 @@ QVariant BranchesDialogModel::data(const QModelIndex &idx, int role) const
return {};
}
void BranchesDialogModel::refresh(QVector<GitUtils::Branch> branches)
void BranchesDialogModel::refresh(const QVector<GitUtils::Branch> &branches, bool checkingOut)
{
// clear
Branch create{branches.at(0).name, {}, {}, 0, 0, ItemType::CreateBranch};
Branch createFrom{branches.at(1).name, {}, {}, 0, 1, ItemType::CreateBranchFrom};
QVector<Branch> temp{create, createFrom};
QVector<Branch> temp;
if (checkingOut) {
Branch create{branches.at(0).name, {}, {}, 0, 0, ItemType::CreateBranch};
Branch createFrom{branches.at(1).name, {}, {}, 0, 1, ItemType::CreateBranchFrom};
temp.push_back(create);
temp.push_back(createFrom);
}
for (int i = 2; i < branches.size(); ++i) {
int i = checkingOut ? 2 : 0;
for (; i < branches.size(); ++i) {
temp.append({branches.at(i).name, branches.at(i).remote, branches.at(i).type, -1, i, ItemType::BranchItem});
}
......
......@@ -24,7 +24,7 @@ public:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &idx, int role) const override;
void refresh(QVector<GitUtils::Branch> branches);
void refresh(const QVector<GitUtils::Branch> &branches, bool checkingOut = false);
void clear();
void clearBranchCreationItems();
......
/*
SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "comarebranchesview.h"
#include "kateprojectpluginview.h"
#include "kateprojectworker.h"
#include <QPainter>
#include <QProcess>
#include <QStyledItemDelegate>
#include <QVBoxLayout>
#include <KLocalizedString>
#include <QDebug>
class DiffStyleDelegate : public QStyledItemDelegate
{
public:
DiffStyleDelegate(QObject *parent)
: QStyledItemDelegate(parent)
{
}
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
if (index.data(KateProjectItem::TypeRole).toInt() == KateProjectItem::Directory) {
QStyledItemDelegate::paint(painter, option, index);
return;
}
QStyleOptionViewItem options = option;
initStyleOption(&options, index);
painter->save();
// paint background
if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, option.palette.highlight());
} else {
painter->fillRect(option.rect, option.palette.base());
}
int add = index.data(Qt::UserRole + 2).toInt();
int sub = index.data(Qt::UserRole + 3).toInt();
QString adds = QString(QStringLiteral("+") + QString::number(add));
QString subs = QString(QStringLiteral(" -") + QString::number(sub));
QString file = options.text;
options.text = QString(); // clear old text
options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter, options.widget);
QRect r = options.rect;
// don't draw over icon
r.setX(r.x() + option.decorationSize.width() + 5);
auto &fm = options.fontMetrics;
// adds width
int aw = fm.horizontalAdvance(adds);
// subs width
int sw = fm.horizontalAdvance(subs);
// subtract this from total width of rect
int totalw = r.width();
totalw = totalw - (aw + sw);
// get file name, elide if necessary
QString filename = fm.elidedText(file, Qt::ElideRight, totalw);
painter->drawText(r, Qt::AlignVCenter, filename);
static constexpr auto red = QColor(237, 21, 21); // Breeze Danger Red
static constexpr auto green = QColor(17, 209, 27); // Breeze Verdant Green
r.setX(r.x() + totalw);
painter->setPen(green);
painter->drawText(r, Qt::AlignVCenter, adds);
painter->setPen(red);
r.setX(r.x() + aw);
painter->drawText(r, Qt::AlignVCenter, subs);
painter->restore();
}
};
static void createFileTree(QStandardItem *parent, const QString &basePath, const QVector<GitUtils::StatusItem> &files)
{
QDir dir(basePath);
const QString dirPath = dir.path() + QLatin1Char('/');
QHash<QString, QStandardItem *> dir2Item;
dir2Item[QString()] = parent;
for (const auto &file : qAsConst(files)) {
const QString filePath = QString::fromUtf8(file.file);
/**
* cheap file name computation
* we do this A LOT, QFileInfo is very expensive just for this operation
*/
const int slashIndex = filePath.lastIndexOf(QLatin1Char('/'));
const QString fileName = (slashIndex < 0) ? filePath : filePath.mid(slashIndex + 1);
const QString filePathName = (slashIndex < 0) ? QString() : filePath.left(slashIndex);
const QString fullFilePath = dirPath + filePath;
/**
* construct the item with right directory prefix
* already hang in directories in tree
*/
KateProjectItem *fileItem = new KateProjectItem(KateProjectItem::File, fileName);
fileItem->setData(fullFilePath, Qt::UserRole);
fileItem->setData(file.statusChar, Qt::UserRole + 1);
fileItem->setData(file.linesAdded, Qt::UserRole + 2);
fileItem->setData(file.linesRemoved, Qt::UserRole + 3);
// put in our item to the right directory parent
KateProjectWorker::directoryParent(dir, dir2Item, filePathName)->appendRow(fileItem);
}
}
CompareBranchesView::CompareBranchesView(QWidget *parent, const QString &gitPath, const QString fromB, const QString &toBr, QVector<GitUtils::StatusItem> items)
: QWidget(parent)
, m_gitDir(gitPath)
, m_fromBr(fromB)
, m_toBr(toBr)
{
setLayout(new QVBoxLayout);
QStandardItem *root = new QStandardItem;
createFileTree(root, m_gitDir, items);
m_model.clear();
m_model.invisibleRootItem()->appendColumn(root->takeColumn(0));
m_backBtn.setText(i18n("Back"));
m_backBtn.setIcon(QIcon::fromTheme(QStringLiteral("draw-arrow-back.svg")));
connect(&m_backBtn, &QPushButton::clicked, this, &CompareBranchesView::backClicked);
layout()->addWidget(&m_backBtn);
m_tree.setModel(&m_model);