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"