1 /*
2 SPDX-FileCopyrightText: 2015 Michal Humpula <michal.humpula@hudrydum.cz>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7 #include "wordcounter.h"
8 #include "katedocument.h"
9 #include "kateview.h"
10
WordCounter(KTextEditor::ViewPrivate * view)11 WordCounter::WordCounter(KTextEditor::ViewPrivate *view)
12 : QObject(view)
13 , m_wordsInDocument(0)
14 , m_wordsInSelection(0)
15 , m_charsInDocument(0)
16 , m_charsInSelection(0)
17 , m_startRecalculationFrom(0)
18 , m_document(view->document())
19 {
20 connect(view->doc(), &KTextEditor::DocumentPrivate::textInsertedRange, this, &WordCounter::textInserted);
21 connect(view->doc(), &KTextEditor::DocumentPrivate::textRemoved, this, &WordCounter::textRemoved);
22 connect(view->doc(), &KTextEditor::DocumentPrivate::loaded, this, &WordCounter::recalculate);
23 connect(view, &KTextEditor::View::selectionChanged, this, &WordCounter::selectionChanged);
24
25 m_timer.setInterval(500);
26 m_timer.setSingleShot(true);
27 connect(&m_timer, &QTimer::timeout, this, &WordCounter::recalculateLines);
28
29 recalculate(m_document);
30 }
31
textInserted(KTextEditor::Document *,const KTextEditor::Range & range)32 void WordCounter::textInserted(KTextEditor::Document *, const KTextEditor::Range &range)
33 {
34 auto startLine = m_countByLine.begin() + range.start().line();
35 auto endLine = m_countByLine.begin() + range.end().line();
36 size_t newLines = std::distance(startLine, endLine);
37
38 if (m_countByLine.empty()) { // was empty document before insert
39 newLines++;
40 }
41
42 if (newLines > 0) {
43 m_countByLine.insert(startLine, newLines, -1);
44 }
45
46 m_countByLine[range.end().line()] = -1;
47 m_timer.start();
48 }
49
textRemoved(KTextEditor::Document *,const KTextEditor::Range & range,const QString &)50 void WordCounter::textRemoved(KTextEditor::Document *, const KTextEditor::Range &range, const QString &)
51 {
52 const auto startLine = m_countByLine.begin() + range.start().line();
53 const auto endLine = m_countByLine.begin() + range.end().line();
54 const int removedLines = endLine - startLine;
55
56 if (removedLines > 0) {
57 m_countByLine.erase(startLine, endLine);
58 }
59
60 if (!m_countByLine.empty()) {
61 m_countByLine[range.start().line()] = -1;
62 m_timer.start();
63 } else {
64 Q_EMIT changed(0, 0, 0, 0);
65 }
66 }
67
recalculate(KTextEditor::Document *)68 void WordCounter::recalculate(KTextEditor::Document *)
69 {
70 m_countByLine = std::vector<int>(m_document->lines(), -1);
71 m_timer.start();
72 }
73
countWords(const QString & text)74 static int countWords(const QString &text)
75 {
76 int count = 0;
77 bool inWord = false;
78
79 for (const QChar c : text) {
80 if (c.isLetterOrNumber()) {
81 if (!inWord) {
82 inWord = true;
83 }
84 } else {
85 if (inWord) {
86 inWord = false;
87 count++;
88 }
89 }
90 }
91
92 return inWord ? count + 1 : count;
93 }
94
selectionChanged(KTextEditor::View * view)95 void WordCounter::selectionChanged(KTextEditor::View *view)
96 {
97 if (view->selectionRange().isEmpty()) {
98 m_wordsInSelection = m_charsInSelection = 0;
99 Q_EMIT changed(m_wordsInDocument, 0, m_charsInDocument, 0);
100 return;
101 }
102
103 const int firstLine = view->selectionRange().start().line();
104 const int lastLine = view->selectionRange().end().line();
105
106 if (firstLine == lastLine || view->blockSelection()) {
107 const QString text = view->selectionText();
108 m_wordsInSelection = countWords(text);
109 m_charsInSelection = text.size();
110 } else {
111 m_wordsInSelection = m_charsInSelection = 0;
112
113 const KTextEditor::Range firstLineRange(view->selectionRange().start(), firstLine, view->document()->lineLength(firstLine));
114 const QString firstLineText = view->document()->text(firstLineRange);
115 m_wordsInSelection += countWords(firstLineText);
116 m_charsInSelection += firstLineText.size();
117
118 // whole lines
119 for (int i = firstLine + 1; i < lastLine; i++) {
120 m_wordsInSelection += m_countByLine[i];
121 m_charsInSelection += m_document->lineLength(i);
122 }
123
124 const KTextEditor::Range lastLineRange(KTextEditor::Cursor(lastLine, 0), view->selectionRange().end());
125 const QString lastLineText = view->document()->text(lastLineRange);
126 m_wordsInSelection += countWords(lastLineText);
127 m_charsInSelection += lastLineText.size();
128 }
129
130 Q_EMIT changed(m_wordsInDocument, m_wordsInSelection, m_charsInDocument, m_charsInSelection);
131 }
132
recalculateLines()133 void WordCounter::recalculateLines()
134 {
135 if ((size_t)m_startRecalculationFrom >= m_countByLine.size()) {
136 m_startRecalculationFrom = 0;
137 }
138
139 int wordsCount = 0;
140 int charsCount = 0;
141 int calculated = 0;
142 size_t i = m_startRecalculationFrom;
143 constexpr int MaximumLinesToRecalculate = 100;
144
145 // stay in bounds, vector might be empty, even 0 is too large then
146 while (i < m_countByLine.size()) {
147 if (m_countByLine[i] == -1) {
148 m_countByLine[i] = countWords(m_document->line(i));
149 if (++calculated > MaximumLinesToRecalculate) {
150 m_startRecalculationFrom = i;
151 m_timer.start();
152 return;
153 }
154 }
155
156 wordsCount += m_countByLine[i];
157 charsCount += m_document->lineLength(i);
158
159 if (++i == m_countByLine.size()) { // array cycle
160 i = 0;
161 }
162
163 if (i == (size_t)m_startRecalculationFrom) {
164 break;
165 }
166 }
167
168 m_wordsInDocument = wordsCount;
169 m_charsInDocument = charsCount;
170 Q_EMIT changed(m_wordsInDocument, m_wordsInSelection, m_charsInDocument, m_charsInSelection);
171 }
172