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 | |
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(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 | |
32 | void 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 | |
50 | void 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 | |
68 | void WordCounter::recalculate(KTextEditor::Document *) |
69 | { |
70 | m_countByLine = std::vector<int>(m_document->lines(), -1); |
71 | m_timer.start(); |
72 | } |
73 | |
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 | |
95 | void 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 | |
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(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 | |