File indexing completed on 2024-05-26 05:51:57

0001 /*
0002     SPDX-FileCopyrightText: 2022 Waqar Ahmed <waqar.17a@gmail.com>
0003     SPDX-License-Identifier: LGPL-2.0-or-later
0004 */
0005 #pragma once
0006 
0007 #include "hostprocess.h"
0008 #include <KLocalizedString>
0009 #include <KTextEditor/Document>
0010 #include <KTextEditor/MovingCursor>
0011 #include <QFileInfo>
0012 #include <QIcon>
0013 #include <QRegularExpression>
0014 #include <QTemporaryFile>
0015 #include <gitprocess.h>
0016 #include <ktexteditor_utils.h>
0017 
0018 [[maybe_unused]] static QString diff(KTextEditor::Document *doc, const QByteArray &formatted)
0019 {
0020     QTemporaryFile f;
0021     if (!f.open()) {
0022         Utils::showMessage(i18n("Failed to write a temp file"), {}, i18n("Format"), MessageType::Warning);
0023         return {};
0024     }
0025     f.write(formatted);
0026     f.close();
0027 
0028     QProcess p;
0029     QStringList args = {QStringLiteral("diff"), QStringLiteral("--no-color"), QStringLiteral("--no-index")};
0030     args << doc->url().toString(QUrl::PreferLocalFile);
0031     args << f.fileName();
0032     setupGitProcess(p, QFileInfo(doc->url().toString(QUrl::PreferLocalFile)).absolutePath(), args);
0033     startHostProcess(p);
0034     if (!p.waitForStarted() || !p.waitForFinished()) {
0035         Utils::showMessage(i18n("Failed to run git diff: %1", p.errorString()), {}, i18n("Format"), MessageType::Warning);
0036         return {};
0037     }
0038 
0039     return QString::fromUtf8(p.readAllStandardOutput());
0040 }
0041 
0042 struct PatchLine {
0043     KTextEditor::MovingCursor *pos = nullptr;
0044     KTextEditor::Cursor inPos;
0045     enum { Remove, Add } type;
0046     QString text;
0047 };
0048 Q_DECLARE_METATYPE(PatchLine)
0049 Q_DECLARE_METATYPE(std::vector<PatchLine>)
0050 
0051 [[maybe_unused]] static std::pair<uint, uint> parseRange(const QString &range)
0052 {
0053     int commaPos = range.indexOf(QLatin1Char(','));
0054     if (commaPos > -1) {
0055         return {QStringView(range).sliced(0, commaPos).toInt(), QStringView(range).sliced(commaPos + 1).toInt()};
0056     }
0057     return {range.toInt(), 1};
0058 }
0059 
0060 [[maybe_unused]] static std::vector<PatchLine> parseDiff(KTextEditor::Document *doc, const QString &diff)
0061 {
0062     static const QRegularExpression HUNK_HEADER_RE(QStringLiteral("^@@ -([0-9,]+) \\+([0-9,]+) @@(.*)"));
0063 
0064     std::vector<PatchLine> lines;
0065     const QStringList d = diff.split(QStringLiteral("\n"));
0066     for (int i = 0; i < d.size(); ++i) {
0067         const QString &l = d.at(i);
0068         const auto match = HUNK_HEADER_RE.match(l);
0069         if (!match.hasMatch()) {
0070             continue;
0071         }
0072 
0073         const std::pair<int, int> src = parseRange(match.captured(1));
0074         const std::pair<int, int> tgt = parseRange(match.captured(2));
0075 
0076         // unroll into the hunk
0077         int srcline = src.first - 1;
0078         int tgtline = tgt.first - 1;
0079         // qDebug() << "NEW HUNK: " << l << "------------" << srcline << tgtline;
0080         for (int j = i + 1; j < d.size(); ++j) {
0081             const QString &hl = d.at(j);
0082             if (hl.startsWith(QLatin1Char(' '))) {
0083                 srcline++;
0084                 tgtline++;
0085             } else if (hl.startsWith(QLatin1Char('+'))) {
0086                 PatchLine p;
0087                 p.type = PatchLine::Add;
0088                 p.text = hl.mid(1);
0089                 p.inPos = KTextEditor::Cursor(tgtline, 0);
0090                 // p.pos = iface->newMovingCursor(KTextEditor::Cursor(tgtline, 0));
0091                 lines.push_back(p);
0092                 // qDebug() << "insert line" << tgtline << p.text << p.inPos.line();
0093                 tgtline++;
0094             } else if (hl.startsWith(QLatin1Char('-'))) {
0095                 PatchLine p;
0096                 p.type = PatchLine::Remove;
0097                 p.pos = doc->newMovingCursor(KTextEditor::Cursor(srcline, 0));
0098                 // qDebug() << "remove line" << srcline << hl.mid(1) << p.pos->line();
0099                 lines.push_back(p);
0100                 srcline++;
0101             } else if (hl.startsWith(QStringLiteral("@@ "))) {
0102                 i = j - 1; // advance i to next hunk
0103                 break;
0104             }
0105         }
0106     }
0107     // qDebug() << "================";
0108 
0109     return lines;
0110 }
0111 
0112 [[maybe_unused]] static void applyPatch(KTextEditor::Document *doc, const std::vector<PatchLine> &edits)
0113 {
0114     // EditingTransaction scope
0115     KTextEditor::Document::EditingTransaction t(doc);
0116     for (const auto &p : edits) {
0117         if (p.type == PatchLine::Add) {
0118             // qDebug() << "insert at " << p.inPos.line() << "text: " << p.text;
0119             doc->insertLine(p.inPos.line(), p.text /*+ QStringLiteral("\n")*/);
0120         } else if (p.type == PatchLine::Remove) {
0121             // qDebug() << "remove line" << p.pos->line() << doc->line(p.pos->line());
0122             doc->removeLine(p.pos->line());
0123         }
0124     }
0125     for (const auto &p : edits) {
0126         delete p.pos;
0127     }
0128 }