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
11WordCounter::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(sender: view->doc(), signal: &KTextEditor::DocumentPrivate::textInsertedRange, context: this, slot: &WordCounter::textInserted);
21 connect(sender: view->doc(), signal: &KTextEditor::DocumentPrivate::textRemoved, context: this, slot: &WordCounter::textRemoved);
22 connect(sender: view->doc(), signal: &KTextEditor::DocumentPrivate::loaded, context: this, slot: &WordCounter::recalculate);
23 connect(sender: view, signal: &KTextEditor::View::selectionChanged, context: this, slot: &WordCounter::selectionChanged);
24
25 m_timer.setInterval(500);
26 m_timer.setSingleShot(true);
27 connect(sender: &m_timer, signal: &QTimer::timeout, context: this, slot: &WordCounter::recalculateLines);
28
29 recalculate(document: m_document);
30}
31
32void WordCounter::textInserted(KTextEditor::Document *, 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(first: startLine, last: endLine);
37
38 if (m_countByLine.empty()) { // was empty document before insert
39 newLines++;
40 }
41
42 if (newLines > 0) {
43 m_countByLine.insert(position: startLine, n: newLines, x: -1);
44 }
45
46 m_countByLine[range.end().line()] = -1;
47 m_timer.start();
48}
49
50void WordCounter::textRemoved(KTextEditor::Document *, 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(first: startLine, last: 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(wordsInDocument: 0, wordsInSelection: 0, charsInDocument: 0, charsInSelection: 0);
65 }
66}
67
68void WordCounter::recalculate(KTextEditor::Document *)
69{
70 m_countByLine = std::vector<int>(m_document->lines(), -1);
71 m_timer.start();
72}
73
74static 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
95void WordCounter::selectionChanged(KTextEditor::View *view)
96{
97 if (view->selectionRange().isEmpty()) {
98 m_wordsInSelection = m_charsInSelection = 0;
99 Q_EMIT changed(wordsInDocument: m_wordsInDocument, wordsInSelection: 0, charsInDocument: m_charsInDocument, charsInSelection: 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(line: firstLine));
114 const QString firstLineText = view->document()->text(range: firstLineRange);
115 m_wordsInSelection += countWords(text: 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(line: i);
122 }
123
124 const KTextEditor::Range lastLineRange(KTextEditor::Cursor(lastLine, 0), view->selectionRange().end());
125 const QString lastLineText = view->document()->text(range: lastLineRange);
126 m_wordsInSelection += countWords(text: lastLineText);
127 m_charsInSelection += lastLineText.size();
128 }
129
130 Q_EMIT changed(wordsInDocument: m_wordsInDocument, wordsInSelection: m_wordsInSelection, charsInDocument: m_charsInDocument, charsInSelection: m_charsInSelection);
131}
132
133void 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(text: m_document->line(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(line: 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(wordsInDocument: m_wordsInDocument, wordsInSelection: m_wordsInSelection, charsInDocument: m_charsInDocument, charsInSelection: m_charsInSelection);
171}
172
173#include "moc_wordcounter.cpp"
174

source code of ktexteditor/src/view/wordcounter.cpp