Commit af6f24dc authored by Waqar Ahmed's avatar Waqar Ahmed
Browse files

git checkout initial implementation

parent d6b129c0
......@@ -58,6 +58,7 @@ target_sources(
kateprojectinfoviewnotes.cpp
kateprojectconfigpage.cpp
kateprojectcodeanalysistool.cpp
branchesdialog.cpp
tools/kateprojectcodeanalysistoolcppcheck.cpp
tools/kateprojectcodeanalysistoolflake8.cpp
tools/kateprojectcodeanalysistoolshellcheck.cpp
......
/*
SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "branchesdialog.h"
#include "gitutils.h"
#include <QAction>
#include <QCoreApplication>
#include <QKeyEvent>
#include <QLineEdit>
#include <QPainter>
#include <QSortFilterProxyModel>
#include <QStandardItemModel>
#include <QStyledItemDelegate>
#include <QTextDocument>
#include <QTreeView>
#include <QVBoxLayout>
#include <KTextEditor/Message>
#include <KTextEditor/View>
#include <KLocalizedString>
#include <QDebug>
#include <KTextEditor/MainWindow>
#include <kfts_fuzzy_match.h>
class BranchFilterModel : public QSortFilterProxyModel
{
public:
BranchFilterModel(QObject *parent = nullptr)
: QSortFilterProxyModel(parent)
{
}
Q_SLOT void setFilterString(const QString &string)
{
beginResetModel();
m_pattern = string;
endResetModel();
}
protected:
bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override
{
const int l = sourceLeft.data(WeightRole).toInt();
const int r = sourceRight.data(WeightRole).toInt();
return l < r;
}
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
{
if (m_pattern.isEmpty()) {
return true;
}
int score = 0;
const auto idx = sourceModel()->index(sourceRow, 0, sourceParent);
const QString string = idx.data().toString();
const bool res = kfts::fuzzy_match(m_pattern, string, score);
sourceModel()->setData(idx, score, WeightRole);
return res;
}
private:
QString m_pattern;
static constexpr int WeightRole = Qt::UserRole + 1;
};
class StyleDelegate : public QStyledItemDelegate
{
public:
StyleDelegate(QObject *parent = nullptr)
: QStyledItemDelegate(parent)
{
}
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
QStyleOptionViewItem options = option;
initStyleOption(&options, index);
QTextDocument doc;
auto str = index.data().toString();
const QString nameColor = option.palette.color(QPalette::Link).name();
kfts::to_scored_fuzzy_matched_display_string(m_filterString, str, QStringLiteral("<b style=\"color:%1;\">").arg(nameColor), QStringLiteral("</b>"));
doc.setHtml(str);
doc.setDocumentMargin(2);
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());
}
options.text = QString(); // clear old text
options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter, options.widget);
// draw text
painter->translate(option.rect.x(), option.rect.y());
// leave space for icon
painter->translate(25, 0);
doc.drawContents(painter);
painter->restore();
}
public Q_SLOTS:
void setFilterString(const QString &text)
{
m_filterString = text;
}
private:
QString m_filterString;
};
BranchesDialog::BranchesDialog(QWidget *parent, KTextEditor::MainWindow *mainWindow, QString projectPath)
: QMenu(parent)
, m_mainWindow(mainWindow)
, m_projectPath(projectPath)
{
QVBoxLayout *layout = new QVBoxLayout();
layout->setSpacing(0);
layout->setContentsMargins(4, 4, 4, 4);
setLayout(layout);
m_lineEdit = new QLineEdit(this);
setFocusProxy(m_lineEdit);
layout->addWidget(m_lineEdit);
m_treeView = new QTreeView();
layout->addWidget(m_treeView, 1);
m_treeView->setTextElideMode(Qt::ElideLeft);
m_treeView->setUniformRowHeights(true);
m_model = new QStandardItemModel(this);
StyleDelegate *delegate = new StyleDelegate(this);
m_treeView->setItemDelegateForColumn(0, delegate);
m_proxyModel = new BranchFilterModel(this);
m_proxyModel->setFilterRole(Qt::DisplayRole);
m_proxyModel->setSortRole(Qt::UserRole + 1);
connect(m_lineEdit, &QLineEdit::returnPressed, this, &BranchesDialog::slotReturnPressed);
connect(m_lineEdit, &QLineEdit::textChanged, m_proxyModel, &BranchFilterModel::setFilterString);
connect(m_lineEdit, &QLineEdit::textChanged, delegate, &StyleDelegate::setFilterString);
connect(m_lineEdit, &QLineEdit::textChanged, this, [this]() {
m_treeView->viewport()->update();
reselectFirst();
});
connect(m_treeView, &QTreeView::clicked, this, &BranchesDialog::slotReturnPressed);
m_proxyModel->setSourceModel(m_model);
m_treeView->setSortingEnabled(true);
m_treeView->setModel(m_proxyModel);
m_treeView->installEventFilter(this);
m_lineEdit->installEventFilter(this);
m_treeView->setHeaderHidden(true);
m_treeView->setRootIsDecorated(false);
m_treeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_treeView->setSelectionMode(QTreeView::SingleSelection);
setHidden(true);
}
void BranchesDialog::openDialog()
{
const auto branches = GitUtils::getAllBranches(m_projectPath);
m_model->clear();
static const QIcon branchIcon = QIcon(QStringLiteral(":/kxmlgui5/kateproject/sc-apps-git.svg"));
for (const auto &branch : branches) {
m_model->appendRow(new QStandardItem(branchIcon, branch));
}
reselectFirst();
updateViewGeometry();
show();
setFocus();
}
bool BranchesDialog::eventFilter(QObject *obj, QEvent *event)
{
// catch key presses + shortcut overrides to allow to have ESC as application wide shortcut, too, see bug 409856
if (event->type() == QEvent::KeyPress || event->type() == QEvent::ShortcutOverride) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (obj == m_lineEdit) {
const bool forward2list = (keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down) || (keyEvent->key() == Qt::Key_PageUp)
|| (keyEvent->key() == Qt::Key_PageDown);
if (forward2list) {
QCoreApplication::sendEvent(m_treeView, event);
return true;
}
if (keyEvent->key() == Qt::Key_Escape) {
m_lineEdit->clear();
keyEvent->accept();
hide();
return true;
}
} else {
const bool forward2input = (keyEvent->key() != Qt::Key_Up) && (keyEvent->key() != Qt::Key_Down) && (keyEvent->key() != Qt::Key_PageUp)
&& (keyEvent->key() != Qt::Key_PageDown) && (keyEvent->key() != Qt::Key_Tab) && (keyEvent->key() != Qt::Key_Backtab);
if (forward2input) {
QCoreApplication::sendEvent(m_lineEdit, event);
return true;
}
}
}
// hide on focus out, if neither input field nor list have focus!
else if (event->type() == QEvent::FocusOut && !(m_lineEdit->hasFocus() || m_treeView->hasFocus())) {
m_lineEdit->clear();
hide();
return true;
}
return QWidget::eventFilter(obj, event);
}
void BranchesDialog::slotReturnPressed()
{
const auto branch = m_proxyModel->data(m_treeView->currentIndex()).toString();
int res = GitUtils::checkoutBranch(m_projectPath, branch);
auto msgType = KTextEditor::Message::Positive;
QString msgStr = i18n("Branch %1 checked out", branch);
if (res > 0) {
msgType = KTextEditor::Message::Warning;
msgStr = i18n("Failed to checkout branch: %1", branch);
} else {
Q_EMIT branchChanged(branch);
}
KTextEditor::Message *msg = new KTextEditor::Message(msgStr, msgType);
msg->setPosition(KTextEditor::Message::TopInView);
msg->setAutoHide(3000);
msg->setAutoHideMode(KTextEditor::Message::Immediate);
msg->setView(m_mainWindow->activeView());
m_mainWindow->activeView()->document()->postMessage(msg);
m_lineEdit->clear();
hide();
}
void BranchesDialog::reselectFirst()
{
QModelIndex index = m_proxyModel->index(0, 0);
m_treeView->setCurrentIndex(index);
}
void BranchesDialog::updateViewGeometry()
{
m_treeView->resizeColumnToContents(0);
m_treeView->resizeColumnToContents(1);
const QSize centralSize = m_mainWindow->window()->size();
// width: 2.4 of editor, height: 1/2 of editor
const QSize viewMaxSize(centralSize.width() / 2.4, centralSize.height() / 2);
// Position should be central over window
const int xPos = std::max(0, (centralSize.width() - viewMaxSize.width()) / 2);
const int yPos = std::max(0, (centralSize.height() - viewMaxSize.height()) * 1 / 4);
const QPoint p(xPos, yPos);
move(p + parentWidget()->pos());
this->setFixedSize(viewMaxSize);
}
/*
SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <QList>
#include <QMenu>
class QTreeView;
class QLineEdit;
class QStandardItemModel;
class QAction;
class BranchFilterModel;
class KActionCollection;
namespace KTextEditor
{
class MainWindow;
}
class BranchesDialog : public QMenu
{
Q_OBJECT
public:
BranchesDialog(QWidget *parent, KTextEditor::MainWindow *mainWindow, QString projectPath);
void openDialog();
void updateViewGeometry();
Q_SIGNAL void branchChanged(const QString &branch);
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
private Q_SLOTS:
void slotReturnPressed();
void reselectFirst();
private:
QTreeView *m_treeView;
QLineEdit *m_lineEdit;
QStandardItemModel *m_model;
BranchFilterModel *m_proxyModel;
KTextEditor::MainWindow *m_mainWindow;
QString m_projectPath;
};
#ifndef GITUTILS_H
#define GITUTILS_H
#include <QDebug>
#include <QDir>
#include <QProcess>
#include <QString>
namespace GitUtils
{
static bool isGitRepo(const QString &repo)
{
QProcess git;
git.setWorkingDirectory(repo);
QStringList args{QStringLiteral("rev-parse"), QStringLiteral("--is-inside-work-tree")};
git.start(QStringLiteral("git"), args);
if (git.waitForStarted() && git.waitForFinished(-1)) {
return git.readAll().trimmed() == "true";
}
return false;
}
static QString getCurrentBranchName(const QString &repo)
{
QProcess git;
git.setWorkingDirectory(repo);
QStringList args{QStringLiteral("rev-parse"), QStringLiteral("--abbrev-ref"), QStringLiteral("HEAD")};
git.start(QStringLiteral("git"), args);
if (git.waitForStarted() && git.waitForFinished(-1)) {
return QString::fromUtf8(git.readAllStandardOutput().trimmed());
}
return QString();
}
static int checkoutBranch(const QString &repo, const QString &branch)
{
QProcess git;
git.setWorkingDirectory(repo);
QStringList args{QStringLiteral("checkout"), branch};
git.start(QStringLiteral("git"), args);
if (git.waitForStarted() && git.waitForFinished(-1)) {
return git.exitCode();
}
return -1;
}
static QStringList getAllBranches(const QString &repo)
{
QProcess git;
QStringList args{QStringLiteral("branch"), QStringLiteral("--all")};
git.setWorkingDirectory(repo);
git.start(QStringLiteral("git"), args);
QStringList branches;
if (git.waitForStarted() && git.waitForFinished(-1)) {
QList<QByteArray> out = git.readAllStandardOutput().split('\n');
for (const QByteArray &br : out) {
auto branch = br.trimmed();
if (!branch.isEmpty() && !branch.startsWith("* ")) {
branches.append(QString::fromUtf8(branch.trimmed()));
}
}
}
return branches;
}
}
#endif // GITUTILS_H
......@@ -215,7 +215,7 @@ QPair<KateProjectView *, KateProjectInfoView *> KateProjectPluginView::viewForPr
/**
* create new views
*/
KateProjectView *view = new KateProjectView(this, project);
KateProjectView *view = new KateProjectView(this, project, m_mainWindow);
KateProjectInfoView *infoView = new KateProjectInfoView(this, project);
/**
......
......@@ -6,6 +6,8 @@
*/
#include "kateprojectview.h"
#include "branchesdialog.h"
#include "gitutils.h"
#include "kateprojectfiltermodel.h"
#include "kateprojectpluginview.h"
......@@ -15,15 +17,17 @@
#include <KLineEdit>
#include <KLocalizedString>
#include <QPushButton>
#include <QSortFilterProxyModel>
#include <QTimer>
#include <QVBoxLayout>
KateProjectView::KateProjectView(KateProjectPluginView *pluginView, KateProject *project)
KateProjectView::KateProjectView(KateProjectPluginView *pluginView, KateProject *project, KTextEditor::MainWindow *mainWindow)
: m_pluginView(pluginView)
, m_project(project)
, m_treeView(new KateProjectViewTree(pluginView, project))
, m_filter(new KLineEdit())
, m_branchBtn(new QPushButton)
{
/**
* layout tree view and co.
......@@ -32,18 +36,30 @@ KateProjectView::KateProjectView(KateProjectPluginView *pluginView, KateProject
layout->setSpacing(0);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(m_treeView);
layout->addWidget(m_branchBtn);
layout->addWidget(m_filter);
setLayout(layout);
m_branchBtn->setText(GitUtils::getCurrentBranchName(m_project->baseDir()));
m_branchBtn->setIcon(QIcon(QStringLiteral(":/kxmlgui5/kateproject/sc-apps-git.svg")));
// let tree get focus for keyboard selection of file to open
setFocusProxy(m_treeView);
m_branchesDialog = new BranchesDialog(this, mainWindow, m_project->baseDir());
/**
* setup filter line edit
*/
m_filter->setPlaceholderText(i18n("Filter..."));
m_filter->setClearButtonEnabled(true);
connect(m_filter, &KLineEdit::textChanged, this, &KateProjectView::filterTextChanged);
connect(m_branchBtn, &QPushButton::clicked, this, [this] {
m_branchesDialog->openDialog();
});
connect(m_branchesDialog, &BranchesDialog::branchChanged, this, [this](const QString &branch) {
m_branchBtn->setText(branch);
});
}
KateProjectView::~KateProjectView()
......
......@@ -12,7 +12,9 @@
#include "kateprojectviewtree.h"
class KLineEdit;
class QPushButton;
class KateProjectPluginView;
class BranchesDialog;
/**
* Class representing a view of a project.
......@@ -28,7 +30,7 @@ public:
* @param pluginView our plugin view
* @param project project this view is for
*/
KateProjectView(KateProjectPluginView *pluginView, KateProject *project);
KateProjectView(KateProjectPluginView *pluginView, KateProject *project, KTextEditor::MainWindow *mainWindow);
/**
* deconstruct project
......@@ -82,6 +84,13 @@ private:
* filter
*/
KLineEdit *m_filter;
/**
checkout branch button
*/
QPushButton *m_branchBtn;
BranchesDialog *m_branchesDialog;
};
#endif
......@@ -2,5 +2,6 @@
<RCC version="1.0">
<qresource prefix="/kxmlgui5/kateproject">
<file>ui.rc</file>
<file>sc-apps-git.svg</file>
</qresource>
</RCC>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 122.52 122.52"
height="122.52"
width="122.52"
xml:space="preserve"
id="svg2"
version="1.1"><metadata
id="metadata8"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs6" /><g
transform="matrix(1.3333333,0,0,-1.3333333,0,122.52)"
id="g10"><g
transform="scale(0.1)"
id="g12"><path
id="path14"
style="fill:#f05033;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="M 901.543,500.352 500.352,901.527 c -23.094,23.11 -60.567,23.11 -83.692,0 L 333.359,818.211 439.031,712.535 c 24.563,8.293 52.727,2.727 72.297,-16.847 19.688,-19.696 25.203,-48.102 16.699,-72.75 L 629.883,521.094 c 24.648,8.496 53.066,3.004 72.754,-16.711 27.5,-27.492 27.5,-72.059 0,-99.574 -27.52,-27.516 -72.078,-27.516 -99.61,0 -20.683,20.703 -25.8,51.097 -15.312,76.582 l -95,94.992 V 326.414 c 6.699,-3.32 13.027,-7.742 18.613,-13.312 27.5,-27.497 27.5,-72.059 0,-99.598 -27.5,-27.488 -72.09,-27.488 -99.57,0 -27.5,27.539 -27.5,72.101 0,99.598 6.797,6.789 14.668,11.925 23.066,15.363 v 252.281 c -8.398,3.438 -16.25,8.531 -23.066,15.367 -20.828,20.821 -25.84,51.395 -15.156,76.977 L 292.422,777.285 17.3242,502.211 c -23.10545,-23.129 -23.10545,-60.602 0,-83.711 L 418.535,17.3242 c 23.098,-23.10545 60.559,-23.10545 83.692,0 L 901.543,416.641 c 23.113,23.113 23.113,60.605 0,83.711" /></g></g></svg>
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment