1/*
2 SPDX-FileCopyrightText: 2010 Christoph Cullmann <cullmann@kde.org>
3 SPDX-FileCopyrightText: 2012 Dominik Haumann <dhaumann@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "documentcursor.h"
9
10namespace KTextEditor
11{
12DocumentCursor::DocumentCursor(KTextEditor::Document *document)
13 : m_document(document)
14 , m_cursor(KTextEditor::Cursor::invalid())
15{
16 // we require a valid document
17 Q_ASSERT(m_document);
18}
19
20DocumentCursor::DocumentCursor(KTextEditor::Document *document, KTextEditor::Cursor position)
21 : m_document(document)
22 , m_cursor(position)
23{
24 // we require a valid document
25 Q_ASSERT(m_document);
26}
27
28DocumentCursor::DocumentCursor(KTextEditor::Document *document, int line, int column)
29 : m_document(document)
30 , m_cursor(line, column)
31{
32 // we require a valid document
33 Q_ASSERT(m_document);
34}
35
36DocumentCursor::DocumentCursor(const DocumentCursor &other)
37 : m_document(other.m_document)
38 , m_cursor(other.m_cursor)
39{
40}
41
42void DocumentCursor::makeValid()
43{
44 const int line = m_cursor.line();
45 const int col = m_cursor.line();
46
47 if (line < 0) {
48 m_cursor.setPosition(line: 0, column: 0);
49 } else if (line >= m_document->lines()) {
50 m_cursor = m_document->documentEnd();
51 } else if (col > m_document->lineLength(line)) {
52 m_cursor.setColumn(m_document->lineLength(line));
53 } else if (col < 0) {
54 m_cursor.setColumn(0);
55 } else if (!isValidTextPosition()) {
56 // inside a unicode surrogate (utf-32 character)
57 // -> move half one char left to the start of the utf-32 char
58 m_cursor.setColumn(col - 1);
59 }
60
61 Q_ASSERT(isValidTextPosition());
62}
63
64void DocumentCursor::setPosition(int line, int column)
65{
66 m_cursor.setPosition(line, column);
67}
68
69void DocumentCursor::setLine(int line)
70{
71 setPosition(line, column: column());
72}
73
74void DocumentCursor::setColumn(int column)
75{
76 setPosition(line: line(), column);
77}
78
79bool DocumentCursor::atStartOfLine() const
80{
81 return isValidTextPosition() && column() == 0;
82}
83
84bool DocumentCursor::atEndOfLine() const
85{
86 return isValidTextPosition() && column() == document()->lineLength(line: line());
87}
88
89bool DocumentCursor::atStartOfDocument() const
90{
91 return line() == 0 && column() == 0;
92}
93
94bool DocumentCursor::atEndOfDocument() const
95{
96 // avoid costly lineLength computation if we are not in the last line
97 // this is called often e.g. during search & replace, >> 2% of the total costs
98 const auto lastLine = document()->lines() - 1;
99 return line() == lastLine && column() == document()->lineLength(line: lastLine);
100}
101
102bool DocumentCursor::gotoNextLine()
103{
104 // only allow valid cursors
105 const bool ok = isValid() && (line() + 1 < document()->lines());
106
107 if (ok) {
108 setPosition(Cursor(line() + 1, 0));
109 }
110
111 return ok;
112}
113
114bool DocumentCursor::gotoPreviousLine()
115{
116 // only allow valid cursors
117 bool ok = (line() > 0) && (column() >= 0);
118
119 if (ok) {
120 setPosition(Cursor(line() - 1, 0));
121 }
122
123 return ok;
124}
125
126bool DocumentCursor::move(int chars, WrapBehavior wrapBehavior)
127{
128 if (!isValid()) {
129 return false;
130 }
131
132 // create temporary cursor to modify
133 Cursor c(m_cursor);
134
135 // forwards?
136 if (chars > 0) {
137 // cache lineLength to minimize calls of KTextEditor::DocumentPrivate::lineLength(), as
138 // results in locating the correct block in the text buffer every time,
139 // which is relatively slow
140 int lineLength = document()->lineLength(line: c.line());
141
142 // special case: cursor position is not in valid text, then the algo does
143 // not work for Wrap mode. Hence, catch this special case by setting
144 // c.column() to the lineLength()
145 if (wrapBehavior == Wrap && c.column() > lineLength) {
146 c.setColumn(lineLength);
147 }
148
149 while (chars != 0) {
150 if (wrapBehavior == Wrap) {
151 const int advance = qMin(a: lineLength - c.column(), b: chars);
152
153 if (chars > advance) {
154 if (c.line() + 1 >= document()->lines()) {
155 return false;
156 }
157
158 c.setPosition(line: c.line() + 1, column: 0);
159 chars -= advance + 1; // +1 because of end-of-line wrap
160
161 // advanced one line, so cache correct line length again
162 lineLength = document()->lineLength(line: c.line());
163 } else {
164 c.setColumn(c.column() + chars);
165 chars = 0;
166 }
167 } else { // NoWrap
168 c.setColumn(c.column() + chars);
169 chars = 0;
170 }
171 }
172 }
173
174 // backwards?
175 else {
176 while (chars != 0) {
177 const int back = qMin(a: c.column(), b: -chars);
178 if (-chars > back) {
179 if (c.line() == 0) {
180 return false;
181 }
182
183 c.setPosition(line: c.line() - 1, column: document()->lineLength(line: c.line() - 1));
184 chars += back + 1; // +1 because of wrap-around at start-of-line
185 } else {
186 c.setColumn(c.column() + chars);
187 chars = 0;
188 }
189 }
190 }
191
192 if (c != m_cursor) {
193 setPosition(c);
194 }
195 return true;
196}
197
198}
199

source code of ktexteditor/src/utils/documentcursor.cpp