Commit c13307f5 authored by Kåre Särs's avatar Kåre Särs
Browse files

Add gitblametooltip to show the commit info

parent efa557be
......@@ -6,6 +6,7 @@ target_sources(
kategitblameplugin
PRIVATE
kategitblameplugin.cpp
gitblametooltip.cpp
)
kcoreaddons_desktop_to_json(kategitblameplugin kategitblameplugin.desktop)
......
/* SPDX-License-Identifier: MIT
SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
SPDX-License-Identifier: MIT
*/
#include "gitblametooltip.h"
#include <QApplication>
#include <QDebug>
#include <QEvent>
#include <QFontMetrics>
#include <QLabel>
#include <QMouseEvent>
#include <QScreen>
#include <QString>
#include <QTextBrowser>
#include <QTimer>
#include <QScrollBar>
#include <KTextEditor/ConfigInterface>
#include <KTextEditor/Editor>
#include <KTextEditor/View>
#include <KSyntaxHighlighting/AbstractHighlighter>
#include <KSyntaxHighlighting/Definition>
#include <KSyntaxHighlighting/Format>
#include <KSyntaxHighlighting/Repository>
#include <KSyntaxHighlighting/State>
using KSyntaxHighlighting::AbstractHighlighter;
using KSyntaxHighlighting::Format;
static QString toHtmlRgbaString(const QColor &color)
{
if (color.alpha() == 0xFF)
return color.name();
QString rgba = QStringLiteral("rgba(");
rgba.append(QString::number(color.red()));
rgba.append(QLatin1Char(','));
rgba.append(QString::number(color.green()));
rgba.append(QLatin1Char(','));
rgba.append(QString::number(color.blue()));
rgba.append(QLatin1Char(','));
// this must be alphaF
rgba.append(QString::number(color.alphaF()));
rgba.append(QLatin1Char(')'));
return rgba;
}
class HtmlHl : public AbstractHighlighter
{
public:
HtmlHl()
: out(&outputString)
{
}
void setText(const QString &txt)
{
text = txt;
QTextStream in(&text);
out.reset();
outputString.clear();
bool inDiff = false;
KSyntaxHighlighting::State state;
while (!in.atEnd()) {
currentLine = in.readLine();
// allow empty lines in code blocks, no ruler here
if (!inDiff && currentLine.isEmpty()) {
out << "<hr>";
continue;
}
// diff block
if (!inDiff && currentLine.startsWith(QLatin1String("diff"))) {
inDiff = true;
continue;
}
state = highlightLine(currentLine, state);
out << "\n<br>";
}
}
QString html() const
{
// while (!out.atEnd())
// qWarning() << out.readLine();
return outputString;
}
protected:
void applyFormat(int offset, int length, const Format &format) override
{
if (!length)
return;
QString formatOutput;
if (format.hasTextColor(theme())) {
formatOutput = toHtmlRgbaString(format.textColor(theme()));
}
if (!formatOutput.isEmpty()) {
out << "<span style=\"color:" << formatOutput << "\">";
}
out << currentLine.mid(offset, length).toHtmlEscaped();
if (!formatOutput.isEmpty()) {
out << "</span>";
}
}
private:
QString text;
QString currentLine;
QString outputString;
QTextStream out;
};
class Tooltip : public QTextBrowser
{
Q_OBJECT
public:
static Tooltip *self()
{
static Tooltip instance;
return &instance;
}
void setTooltipText(const QString &text)
{
if (text.isEmpty())
return;
m_htmlHl.setText(text);
setHtml(m_htmlHl.html());
}
void setView(KTextEditor::View *view)
{
// view changed?
// => update definition
// => update font
if (view != m_view) {
if (m_view && m_view->focusProxy()) {
m_view->focusProxy()->removeEventFilter(this);
}
m_view = view;
m_htmlHl.setDefinition(m_syntaxHlRepo.definitionForName(QStringLiteral("Diff")));
updateFont();
if (m_view && m_view->focusProxy()) {
m_view->focusProxy()->installEventFilter(this);
}
}
}
Tooltip(QWidget *parent = nullptr)
: QTextBrowser(parent)
{
setWindowFlags(Qt::FramelessWindowHint | Qt::BypassGraphicsProxyWidget | Qt::ToolTip);
document()->setDocumentMargin(5);
setFrameStyle(QFrame::Box | QFrame::Raised);
connect(&m_hideTimer, &QTimer::timeout, this, &Tooltip::hideTooltip);
setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
auto updateColors = [this](KTextEditor::Editor *e) {
auto theme = e->theme();
m_htmlHl.setTheme(theme);
auto pal = palette();
const QColor bg = theme.editorColor(KSyntaxHighlighting::Theme::BackgroundColor);
pal.setColor(QPalette::Base, bg);
const QColor normal = theme.textColor(KSyntaxHighlighting::Theme::Normal);
pal.setColor(QPalette::Text, normal);
setPalette(pal);
updateFont();
};
updateColors(KTextEditor::Editor::instance());
connect(KTextEditor::Editor::instance(), &KTextEditor::Editor::configChanged, this, updateColors);
}
bool eventFilter(QObject *, QEvent *e) override
{
switch (e->type()) {
case QEvent::KeyPress:
case QEvent::KeyRelease:
case QEvent::WindowActivate:
case QEvent::WindowDeactivate:
hideTooltip();
break;
default:
break;
}
return false;
}
void updateFont()
{
if (!m_view)
return;
auto ciface = qobject_cast<KTextEditor::ConfigInterface *>(m_view);
auto font = ciface->configValue(QStringLiteral("font")).value<QFont>();
setFont(font);
}
Q_SLOT void hideTooltip()
{
close();
setText(QString());
}
void fixGeometry()
{
static QScrollBar scrollBar(Qt::Horizontal);
QFontMetrics fm(font());
QSize size = fm.size(Qt::TextSingleLine, QStringLiteral("m"));
int fontHeight = size.height();
size.setHeight(m_view->height() - fontHeight * 2 - scrollBar.sizeHint().height());
size.setWidth(qRound(m_view->width() * 0.7));
resize(size);
QPoint p = m_view->mapToGlobal(m_view->pos());
p.setY(p.y() + fontHeight);
p.setX(p.x() + m_view->textAreaRect().left() + m_view->textAreaRect().width() - size.width() - fontHeight);
this->move(p);
}
protected:
void showEvent(QShowEvent *event) override
{
m_hideTimer.start(3000);
return QTextBrowser::showEvent(event);
}
void enterEvent(QEvent *event) override
{
inContextMenu = false;
m_hideTimer.stop();
return QTextBrowser::enterEvent(event);
}
void leaveEvent(QEvent *event) override
{
if (!m_hideTimer.isActive() && !inContextMenu) {
hideTooltip();
}
return QTextBrowser::leaveEvent(event);
}
void mouseMoveEvent(QMouseEvent *event) override
{
auto pos = event->pos();
if (rect().contains(pos)) {
return QTextBrowser::mouseMoveEvent(event);
}
hideTooltip();
}
void contextMenuEvent(QContextMenuEvent *e) override
{
inContextMenu = true;
return QTextBrowser::contextMenuEvent(e);
}
private:
bool inContextMenu = false;
KTextEditor::View *m_view;
QTimer m_hideTimer;
HtmlHl m_htmlHl;
KSyntaxHighlighting::Repository m_syntaxHlRepo;
};
void GitBlameTooltip::show(const QString &text, KTextEditor::View *v)
{
if (text.isEmpty() || !v || !v->document()) {
return;
}
Tooltip::self()->setView(v);
Tooltip::self()->setTooltipText(text);
Tooltip::self()->fixGeometry();
Tooltip::self()->raise();
Tooltip::self()->show();
}
#include "gitblametooltip.moc"
/* SPDX-License-Identifier: MIT
SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
SPDX-License-Identifier: MIT
*/
#ifndef LSPTOOLTIP_H
#define LSPTOOLTIP_H
#include <QPoint>
class QWidget;
class QString;
class Tooltip;
namespace KTextEditor
{
class View;
}
class GitBlameTooltip
{
public:
static void show(const QString &text, KTextEditor::View *v);
};
#endif // LSPTOOLTIP_H
......@@ -5,6 +5,7 @@
*/
#include "kategitblameplugin.h"
#include "gitblametooltip.h"
#include <algorithm>
......@@ -68,7 +69,7 @@ QVector<int> GitBlameInlineNoteProvider::inlineNotes(int line) const
int lineLen = m_doc->line(line).size();
for (const auto view: m_doc->views()) {
if (view->cursorPosition().line() == line) {
return QVector<int>{lineLen + 2};
return QVector<int>{qMax(lineLen + 2, 75)};
}
}
return QVector<int>();
......@@ -76,15 +77,11 @@ QVector<int> GitBlameInlineNoteProvider::inlineNotes(int line) const
QSize GitBlameInlineNoteProvider::inlineNoteSize(const KTextEditor::InlineNote &note) const
{
return QSize(note.lineHeight() * 10, note.lineHeight());
return QSize(note.lineHeight() * 50, note.lineHeight());
}
void GitBlameInlineNoteProvider::paintInlineNote(const KTextEditor::InlineNote &note, QPainter &painter) const
{
auto penColor = QColor("black");
penColor.setAlpha(note.underMouse() ? 130 : 90);
painter.setPen(penColor);
painter.setBrush(penColor);
QFont font = note.font();
painter.setFont(font);
const QFontMetrics fm(note.font());
......@@ -92,16 +89,31 @@ void GitBlameInlineNoteProvider::paintInlineNote(const KTextEditor::InlineNote &
int lineNr = note.position().line();
const KateGitBlameInfo &info = m_plugin->blameInfo(lineNr, m_doc->line(lineNr));
QString text = QStringLiteral("%1: %2").arg(info.name, info.date);
QString text = QStringLiteral(" %1: %2").arg(info.name, info.date);
QRect rectangle = fm.boundingRect(text);
rectangle.moveTo(0,0);
auto penColor = QColor("black");
penColor.setAlpha(20);
painter.setPen(penColor);
painter.setBrush(penColor);
painter.drawRect(0,0, rectangle.width(), note.lineHeight());
penColor.setAlpha(note.underMouse() ? 130 : 90);
painter.setPen(penColor);
painter.setBrush(penColor);
painter.drawRect(0,0, 1, note.lineHeight());
painter.drawText(rectangle, text);
}
void GitBlameInlineNoteProvider::inlineNoteActivated(const KTextEditor::InlineNote &note, Qt::MouseButtons buttons, const QPoint &point)
{
qDebug() << "pos:" << note.position() << buttons << point;
if ((buttons & Qt::LeftButton) != 0) {
int lineNr = note.position().line();
const KateGitBlameInfo &info = m_plugin->blameInfo(lineNr, m_doc->line(lineNr));
m_plugin->showCommitInfo(info.commitHash, point);
}
}
K_PLUGIN_FACTORY_WITH_JSON(KateGitBlamePluginFactory, "kategitblameplugin.json", registerPlugin<KateGitBlamePlugin>();)
......@@ -112,7 +124,10 @@ KateGitBlamePlugin::KateGitBlamePlugin(QObject *parent, const QList<QVariant> &)
{
m_blameInfoProc.setOutputChannelMode(KProcess::SeparateChannels);
connect(&m_blameInfoProc, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &KateGitBlamePlugin::finished);
connect(&m_blameInfoProc, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &KateGitBlamePlugin::blameFinished);
m_showProc.setOutputChannelMode(KProcess::SeparateChannels);
connect(&m_showProc, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &KateGitBlamePlugin::showFinished);
}
KateGitBlamePlugin::~KateGitBlamePlugin()
......@@ -175,7 +190,26 @@ void KateGitBlamePlugin::viewChanged(KTextEditor::View *view)
m_blameInfoProc.start();
}
void KateGitBlamePlugin::finished(int /*exitCode*/, QProcess::ExitStatus /*exitStatus*/)
void KateGitBlamePlugin::showCommitInfo(const QString &hash, const QPoint &point)
{
if (!m_mainWindow || !m_mainWindow->activeView() || !m_mainWindow->activeView()->document()) {
return;
}
QUrl url = m_mainWindow->activeView()->document()->url();
QDir dir{url.toLocalFile()};
dir.cdUp();
QString shellCmd = QStringLiteral("git show %1").arg(hash);
m_showProc.setWorkingDirectory(dir.absolutePath());
m_showProc.setShellCommand(shellCmd);
m_showProc.start();
m_showPos = point;
}
void KateGitBlamePlugin::blameFinished(int /*exitCode*/, QProcess::ExitStatus /*exitStatus*/)
{
QString stdErr = QString::fromUtf8(m_blameInfoProc.readAllStandardError());
const QStringList stdOut = QString::fromUtf8(m_blameInfoProc.readAllStandardOutput()).split(QLatin1Char('\n'));
......@@ -199,6 +233,18 @@ void KateGitBlamePlugin::finished(int /*exitCode*/, QProcess::ExitStatus /*exitS
}
}
void KateGitBlamePlugin::showFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
QString stdErr = QString::fromUtf8(m_showProc.readAllStandardError());
const QString stdOut = QString::fromUtf8(m_showProc.readAllStandardOutput());
if (exitCode != 0 || exitStatus != QProcess::NormalExit) {
return;
}
GitBlameTooltip::show(stdOut, m_mainWindow->activeView());
}
bool KateGitBlamePlugin::hasBlameInfo() const
{
return !m_blameInfo.isEmpty();
......
......@@ -63,10 +63,13 @@ public:
void readConfig();
void showCommitInfo(const QString &hash, const QPoint &point);
private Q_SLOTS:
void viewChanged(KTextEditor::View *view);
void finished(int exitCode, QProcess::ExitStatus exitStatus);
void blameFinished(int exitCode, QProcess::ExitStatus exitStatus);
void showFinished(int exitCode, QProcess::ExitStatus exitStatus);
private:
void addDocument(KTextEditor::Document *doc);
......@@ -74,9 +77,11 @@ private:
KTextEditor::MainWindow *m_mainWindow;
QHash<KTextEditor::Document *, GitBlameInlineNoteProvider *> m_inlineNoteProviders;
KProcess m_showProc;
QPoint m_showPos;
KProcess m_blameInfoProc;
QVector<KateGitBlameInfo> m_blameInfo;
KTextEditor::View *m_blameInfoView;
KTextEditor::View *m_blameInfoView = nullptr;
int m_lineOffset{0};
};
......
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