File indexing completed on 2024-04-28 11:45:49

0001 /*
0002     SPDX-FileCopyrightText: 2015 Michal Humpula <michal.humpula@hudrydum.cz>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "wordcounter.h"
0008 #include "katedocument.h"
0009 #include "kateview.h"
0010 
0011 WordCounter::WordCounter(KTextEditor::ViewPrivate *view)
0012     : QObject(view)
0013     , m_wordsInDocument(0)
0014     , m_wordsInSelection(0)
0015     , m_charsInDocument(0)
0016     , m_charsInSelection(0)
0017     , m_startRecalculationFrom(0)
0018     , m_document(view->document())
0019 {
0020     connect(view->doc(), &KTextEditor::DocumentPrivate::textInsertedRange, this, &WordCounter::textInserted);
0021     connect(view->doc(), &KTextEditor::DocumentPrivate::textRemoved, this, &WordCounter::textRemoved);
0022     connect(view->doc(), &KTextEditor::DocumentPrivate::loaded, this, &WordCounter::recalculate);
0023     connect(view, &KTextEditor::View::selectionChanged, this, &WordCounter::selectionChanged);
0024 
0025     m_timer.setInterval(500);
0026     m_timer.setSingleShot(true);
0027     connect(&m_timer, &QTimer::timeout, this, &WordCounter::recalculateLines);
0028 
0029     recalculate(m_document);
0030 }
0031 
0032 void WordCounter::textInserted(KTextEditor::Document *, KTextEditor::Range range)
0033 {
0034     auto startLine = m_countByLine.begin() + range.start().line();
0035     auto endLine = m_countByLine.begin() + range.end().line();
0036     size_t newLines = std::distance(startLine, endLine);
0037 
0038     if (m_countByLine.empty()) { // was empty document before insert
0039         newLines++;
0040     }
0041 
0042     if (newLines > 0) {
0043         m_countByLine.insert(startLine, newLines, -1);
0044     }
0045 
0046     m_countByLine[range.end().line()] = -1;
0047     m_timer.start();
0048 }
0049 
0050 void WordCounter::textRemoved(KTextEditor::Document *, KTextEditor::Range range, const QString &)
0051 {
0052     const auto startLine = m_countByLine.begin() + range.start().line();
0053     const auto endLine = m_countByLine.begin() + range.end().line();
0054     const int removedLines = endLine - startLine;
0055 
0056     if (removedLines > 0) {
0057         m_countByLine.erase(startLine, endLine);
0058     }
0059 
0060     if (!m_countByLine.empty()) {
0061         m_countByLine[range.start().line()] = -1;
0062         m_timer.start();
0063     } else {
0064         Q_EMIT changed(0, 0, 0, 0);
0065     }
0066 }
0067 
0068 void WordCounter::recalculate(KTextEditor::Document *)
0069 {
0070     m_countByLine = std::vector<int>(m_document->lines(), -1);
0071     m_timer.start();
0072 }
0073 
0074 static int countWords(const QString &text)
0075 {
0076     int count = 0;
0077     bool inWord = false;
0078 
0079     for (const QChar c : text) {
0080         if (c.isLetterOrNumber()) {
0081             if (!inWord) {
0082                 inWord = true;
0083             }
0084         } else {
0085             if (inWord) {
0086                 inWord = false;
0087                 count++;
0088             }
0089         }
0090     }
0091 
0092     return inWord ? count + 1 : count;
0093 }
0094 
0095 void WordCounter::selectionChanged(KTextEditor::View *view)
0096 {
0097     if (view->selectionRange().isEmpty()) {
0098         m_wordsInSelection = m_charsInSelection = 0;
0099         Q_EMIT changed(m_wordsInDocument, 0, m_charsInDocument, 0);
0100         return;
0101     }
0102 
0103     const int firstLine = view->selectionRange().start().line();
0104     const int lastLine = view->selectionRange().end().line();
0105 
0106     if (firstLine == lastLine || view->blockSelection()) {
0107         const QString text = view->selectionText();
0108         m_wordsInSelection = countWords(text);
0109         m_charsInSelection = text.size();
0110     } else {
0111         m_wordsInSelection = m_charsInSelection = 0;
0112 
0113         const KTextEditor::Range firstLineRange(view->selectionRange().start(), firstLine, view->document()->lineLength(firstLine));
0114         const QString firstLineText = view->document()->text(firstLineRange);
0115         m_wordsInSelection += countWords(firstLineText);
0116         m_charsInSelection += firstLineText.size();
0117 
0118         // whole lines
0119         for (int i = firstLine + 1; i < lastLine; i++) {
0120             m_wordsInSelection += m_countByLine[i];
0121             m_charsInSelection += m_document->lineLength(i);
0122         }
0123 
0124         const KTextEditor::Range lastLineRange(KTextEditor::Cursor(lastLine, 0), view->selectionRange().end());
0125         const QString lastLineText = view->document()->text(lastLineRange);
0126         m_wordsInSelection += countWords(lastLineText);
0127         m_charsInSelection += lastLineText.size();
0128     }
0129 
0130     Q_EMIT changed(m_wordsInDocument, m_wordsInSelection, m_charsInDocument, m_charsInSelection);
0131 }
0132 
0133 void WordCounter::recalculateLines()
0134 {
0135     if ((size_t)m_startRecalculationFrom >= m_countByLine.size()) {
0136         m_startRecalculationFrom = 0;
0137     }
0138 
0139     int wordsCount = 0;
0140     int charsCount = 0;
0141     int calculated = 0;
0142     size_t i = m_startRecalculationFrom;
0143     constexpr int MaximumLinesToRecalculate = 100;
0144 
0145     // stay in bounds, vector might be empty, even 0 is too large then
0146     while (i < m_countByLine.size()) {
0147         if (m_countByLine[i] == -1) {
0148             m_countByLine[i] = countWords(m_document->line(i));
0149             if (++calculated > MaximumLinesToRecalculate) {
0150                 m_startRecalculationFrom = i;
0151                 m_timer.start();
0152                 return;
0153             }
0154         }
0155 
0156         wordsCount += m_countByLine[i];
0157         charsCount += m_document->lineLength(i);
0158 
0159         if (++i == m_countByLine.size()) { // array cycle
0160             i = 0;
0161         }
0162 
0163         if (i == (size_t)m_startRecalculationFrom) {
0164             break;
0165         }
0166     }
0167 
0168     m_wordsInDocument = wordsCount;
0169     m_charsInDocument = charsCount;
0170     Q_EMIT changed(m_wordsInDocument, m_wordsInSelection, m_charsInDocument, m_charsInSelection);
0171 }
0172 
0173 #include "moc_wordcounter.cpp"