katedocument: Use first line of text as document title (a981fa71) · Commits · Frameworks / KTextEditor · GitLab
Commit a981fa71 authored by Kai Uwe Broulik's avatar Kai Uwe Broulik 🍇 Committed by Christoph Cullmann
Browse files
parent c72c65c3
Loading
Loading
Loading
Loading
Loading
Original line number Diff line number Diff line
@@ -10,13 +10,18 @@

#include <kateconfig.h>
#include <katedocument.h>
#include <kateglobal.h>
#include <kateview.h>

#include <KLazyLocalizedString>
#include <KLocalizedString>

#include <QRegularExpression>
#include <QSignalSpy>
#include <QStandardPaths>
#include <QTemporaryFile>

#include <memory>
#include <stdio.h>

/// TODO: is there a FindValgrind cmake command we could use to
@@ -1047,3 +1052,86 @@ void KateDocumentTest::testBugTextInsertedRange()
    QCOMPARE(doc.text(), QStringLiteral("01234567\n01234567\n\n\n\n\n          x\nxxxx"));
    QVERIFY(doc.lines() == 8);
}

void KateDocumentTest::testDocumentName_data()
{
    QTest::addColumn<QString>("text");
    QTest::addColumn<QString>("documentName");

    const QString untitled = i18n("Untitled");
    const auto untitledTemplate = kli18n("Untitled (%1)");

    QTest::newRow("null") << QString() << untitled; // Tests the initial state without ever calling setText.
    QTest::newRow("empty") << QStringLiteral("") << untitled;
    QTest::newRow("only spaces") << QStringLiteral("     ") << untitled;
    QTest::newRow("ntfs disallowed") << QStringLiteral("<>:\"/\\|?*") << untitled;

    QTest::newRow("plain text") << QStringLiteral("Hello, world") << untitledTemplate.subs(QStringLiteral("Hello, world")).toString();
    QTest::newRow("plain text with excess spaces") << QStringLiteral("   Hello      world    ")
                                                   << untitledTemplate.subs(QStringLiteral("Hello world")).toString();
    QTest::newRow("plain text with exclamation mark") << QStringLiteral("Hello, world!") << untitledTemplate.subs(QStringLiteral("Hello, world!")).toString();
    QTest::newRow("markdown") << QStringLiteral("# Shopping List") << untitledTemplate.subs(QStringLiteral("Shopping List")).toString();
    QTest::newRow("multi line markdown") << QStringLiteral("# Shopping List\n* Fruits\n* Flour")
                                         << untitledTemplate.subs(QStringLiteral("Shopping List")).toString();
    QTest::newRow("blank before") << QStringLiteral("\n# Shopping List") << untitled;

    const QString lipsum = QStringLiteral("Lorem ipsum dolor sit amet, consectetur adipiscing elit. ");
    QTest::newRow("excess length") << lipsum.repeated(10) << untitledTemplate.subs(lipsum.left(32)).toString();

    QTest::newRow("gibberish") << QStringLiteral("<:]{%>;") << untitledTemplate.subs(QStringLiteral("%;")).toString(); // C++ santa.
}

void KateDocumentTest::testDocumentName()
{
    QFETCH(QString, text);
    QFETCH(QString, documentName);

    KTextEditor::DocumentPrivate doc;
    if (!text.isNull()) {
        doc.setText(text);
    }
    QCOMPARE(doc.documentName(), documentName);
}

void KateDocumentTest::testDocumentDeduplication()
{
    auto *editor = KTextEditor::EditorPrivate::self();
    const QString untitled = i18n("Untitled");
    const QString hello = QStringLiteral("Hello");
    const QString untitledHello = i18n("Untitled (%1)", hello);

    KTextEditor::DocumentPrivate untitled1;
    editor->registerDocument(&untitled1);
    QCOMPARE(untitled1.documentName(), untitled);

    KTextEditor::DocumentPrivate untitled2;
    editor->registerDocument(&untitled2);
    QCOMPARE(untitled2.documentName(), untitled + QLatin1String(" (2)"));

    KTextEditor::DocumentPrivate hello1;
    editor->registerDocument(&hello1);
    QCOMPARE(hello1.documentName(), untitled + QLatin1String(" (3)"));
    hello1.setText(hello);
    QCOMPARE(hello1.documentName(), untitledHello);

    KTextEditor::DocumentPrivate hello2;
    editor->registerDocument(&hello2);
    // 3 again because hello1 got renamed.
    QCOMPARE(hello2.documentName(), untitled + QLatin1String(" (3)"));
    hello2.setText(hello);
    QCOMPARE(hello2.documentName(), untitledHello + QLatin1String(" (2)"));

    KTextEditor::DocumentPrivate fileDoc;
    editor->registerDocument(&fileDoc);
    // 3 again because hello2 also got renamed.
    QCOMPARE(fileDoc.documentName(), untitled + QLatin1String(" (3)"));
    fileDoc.setUrl(QUrl(QStringLiteral("file:///tmp/test.txt")));
    QCOMPARE(fileDoc.documentName(), QStringLiteral("test.txt"));

    KTextEditor::DocumentPrivate fileDoc2;
    editor->registerDocument(&fileDoc2);
    // 3 again because fileDoc also got renamed.
    QCOMPARE(fileDoc2.documentName(), untitled + QLatin1String(" (3)"));
    fileDoc2.setUrl(QUrl(QStringLiteral("file:///elsewhere/test.txt")));
    QCOMPARE(fileDoc2.documentName(), QStringLiteral("test.txt - elsewhere"));
}
Original line number Diff line number Diff line
@@ -101,6 +101,9 @@ private Q_SLOTS:
    void testCursorToOffset();
    void testBug329247();
    void testBugTextInsertedRange();
    void testDocumentName_data();
    void testDocumentName();
    void testDocumentDeduplication();
};

#endif // KATE_DOCUMENT_TEST_H
Original line number Diff line number Diff line
@@ -1028,6 +1028,9 @@ bool KTextEditor::DocumentPrivate::editEnd()
    }

    if (m_buffer->editChanged()) {
        if (m_buffer->editTagStart() == 0 && url().isEmpty()) {
            updateDocName();
        }
        setModified(true);
        Q_EMIT textChanged(this);
    }
@@ -1824,7 +1827,12 @@ QUrl KTextEditor::DocumentPrivate::getSaveFileUrl(const QString &dialogTitle, QW
    // such, and using Save As here would result in the file not showing by default.
    dialog.selectMimeTypeFilter(allFiles);

    if (!startUrl.fileName().isEmpty()) {
    if (m_isUntitled) {
        // Pre-fill the suggested file name for new documents.
        if (!m_docName.isEmpty()) {
            dialog.selectFile(m_docName);
        }
    } else if (!startUrl.fileName().isEmpty()) {
        dialog.selectFile(startUrl.fileName());
    }

@@ -4624,21 +4632,57 @@ inline static QString removeNewLines(const QString &str)
    return tmp.replace(QLatin1String("\r\n"), QLatin1String(" ")).replace(QLatin1Char('\r'), QLatin1Char(' ')).replace(QLatin1Char('\n'), QLatin1Char(' '));
}

QString KTextEditor::DocumentPrivate::documentName() const
{
    QString name;

    if (m_isUntitled) {
        if (!m_docName.isEmpty()) {
            name.append(i18n("Untitled (%1)", m_docName));
        } else {
            name.append(i18n("Untitled"));
        }
    } else {
        name.append(m_docName);
    }

    if (m_docNameNumber > 0) {
        name.append(QLatin1String(" (%1)").arg(QString::number(m_docNameNumber + 1)));
    }

    return name;
}

void KTextEditor::DocumentPrivate::updateDocName()
{
    // if the name is set, and starts with FILENAME, it should not be changed!
    if (!url().isEmpty() && (m_docName == removeNewLines(url().fileName()) || m_docName.startsWith(removeNewLines(url().fileName()) + QLatin1String(" (")))) {
    if (!url().isEmpty() && m_docName == removeNewLines(url().fileName())) {
        return;
    }

    int count = -1;
    const QString oldName = documentName();

    m_isUntitled = url().fileName().isEmpty();

    if (m_isUntitled) {
        // Use the first line of text as a suggested file name, sanitized somewhat.
        m_docName = line(0).left(100);
        // Remove characters not allowed in Windows file systems as well as XML and Markdown stuff.
        // Could be more sophisticated, e.g. drop file name that is just special characters, etc...
        static const QRegularExpression s_fileNameRegExp(QStringLiteral(R"([<>\[\]\{\}:"/\\|?*#])"));
        m_docName.remove(s_fileNameRegExp);
        m_docName = m_docName.simplified().left(32);
    } else {
        m_docName = removeNewLines(url().fileName());
    }

    int count = -1;
    std::vector<KTextEditor::DocumentPrivate *> docsWithSameName;

    const auto docs = KTextEditor::EditorPrivate::self()->documents();
    for (KTextEditor::Document *kteDoc : docs) {
        auto doc = static_cast<KTextEditor::DocumentPrivate *>(kteDoc);
        if ((doc != this) && (doc->url().fileName() == url().fileName())) {
        if (doc != this && doc->m_docName == m_docName) {
            if (doc->m_docNameNumber > count) {
                count = doc->m_docNameNumber;
            }
@@ -4648,27 +4692,14 @@ void KTextEditor::DocumentPrivate::updateDocName()

    m_docNameNumber = count + 1;

    QString oldName = m_docName;
    m_docName = removeNewLines(url().fileName());

    m_isUntitled = m_docName.isEmpty();

    if (!m_isUntitled && !docsWithSameName.empty()) {
        docsWithSameName.push_back(this);
        uniquifyDocNames(docsWithSameName);
        return;
    }

    if (m_isUntitled) {
        m_docName = i18n("Untitled");
    }

    if (m_docNameNumber > 0) {
        m_docName = QString(m_docName + QLatin1String(" (%1)")).arg(m_docNameNumber + 1);
    }

    // avoid to emit this, if name doesn't change!
    if (oldName != m_docName) {
    if (oldName != documentName()) {
        Q_EMIT documentNameChanged(this);
    }
}
@@ -4737,15 +4768,16 @@ void KTextEditor::DocumentPrivate::uniquifyDocNames(const std::vector<KTextEdito
    for (const auto doc : docs) {
        const QString prefix = shortestPrefix(paths, doc);
        const QString fileName = doc->url().fileName();
        const QString oldName = doc->m_docName;
        const QString oldName = doc->documentName();

        if (!prefix.isEmpty()) {
            doc->m_docName = fileName + QStringLiteral(" - ") + prefix;
        } else {
            doc->m_docName = fileName;
        }
        doc->m_docNameNumber = 0;

        if (doc->m_docName != oldName) {
        if (doc->documentName() != oldName) {
            Q_EMIT doc->documentNameChanged(doc);
        }
    }
Original line number Diff line number Diff line
@@ -941,10 +941,7 @@ public:
    KTextEditor::Range findMatchingBracket(const KTextEditor::Cursor start, int maxLines);

public:
    QString documentName() const override
    {
        return m_docName;
    }
    QString documentName() const override;

private:
    KTEXTEDITOR_NO_EXPORT