KTextEditor

wordcounter.cpp
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(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
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(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
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(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
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(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
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(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
173#include "moc_wordcounter.cpp"
The Cursor represents a position in a Document.
Definition cursor.h:75
constexpr int line() const noexcept
Retrieve the line on which this cursor is situated.
Definition cursor.h:174
void textRemoved(KTextEditor::Document *document, KTextEditor::Range range, const QString &oldText)
The document emits this signal whenever range was removed, i.e.
void textInsertedRange(KTextEditor::Document *document, KTextEditor::Range range)
The document emits this signal whenever text was inserted.
A KParts derived class representing a text document.
Definition document.h:284
virtual QString line(int line) const =0
Get a single text line.
virtual QString text() const =0
Get the document content.
virtual int lines() const =0
Get the count of lines of the document.
virtual int lineLength(int line) const =0
Get the length of a given line in characters.
An object representing a section of text, from one Cursor to another.
constexpr Cursor end() const noexcept
Get the end position of this range.
constexpr Cursor start() const noexcept
Get the start position of this range.
constexpr bool isEmpty() const noexcept
Returns true if this range contains no characters, ie.
A text widget with KXMLGUIClient that represents a Document.
Definition view.h:244
virtual Document * document() const =0
Get the view's document, that means the view is a view of the returned document.
virtual bool blockSelection() const =0
Get the status of the selection mode.
virtual Range selectionRange() const =0
Get the range occupied by the current selection.
void selectionChanged(KTextEditor::View *view)
This signal is emitted whenever the view's selection changes.
virtual QString selectionText() const =0
Get the view's selected text.
Q_EMITQ_EMIT
qsizetype size() const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void start()
void timeout()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:11:27 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.