File indexing completed on 2024-03-24 04:00:35
0001 /* 0002 SPDX-FileCopyrightText: 2010 Christoph Cullmann <cullmann@kde.org> 0003 SPDX-FileCopyrightText: 2012 Dominik Haumann <dhaumann@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "documentcursor.h" 0009 0010 namespace KTextEditor 0011 { 0012 DocumentCursor::DocumentCursor(KTextEditor::Document *document) 0013 : m_document(document) 0014 , m_cursor(KTextEditor::Cursor::invalid()) 0015 { 0016 // we require a valid document 0017 Q_ASSERT(m_document); 0018 } 0019 0020 DocumentCursor::DocumentCursor(KTextEditor::Document *document, KTextEditor::Cursor position) 0021 : m_document(document) 0022 , m_cursor(position) 0023 { 0024 // we require a valid document 0025 Q_ASSERT(m_document); 0026 } 0027 0028 DocumentCursor::DocumentCursor(KTextEditor::Document *document, int line, int column) 0029 : m_document(document) 0030 , m_cursor(line, column) 0031 { 0032 // we require a valid document 0033 Q_ASSERT(m_document); 0034 } 0035 0036 DocumentCursor::DocumentCursor(const DocumentCursor &other) 0037 : m_document(other.m_document) 0038 , m_cursor(other.m_cursor) 0039 { 0040 } 0041 0042 void DocumentCursor::makeValid() 0043 { 0044 const int line = m_cursor.line(); 0045 const int col = m_cursor.line(); 0046 0047 if (line < 0) { 0048 m_cursor.setPosition(0, 0); 0049 } else if (line >= m_document->lines()) { 0050 m_cursor = m_document->documentEnd(); 0051 } else if (col > m_document->lineLength(line)) { 0052 m_cursor.setColumn(m_document->lineLength(line)); 0053 } else if (col < 0) { 0054 m_cursor.setColumn(0); 0055 } else if (!isValidTextPosition()) { 0056 // inside a unicode surrogate (utf-32 character) 0057 // -> move half one char left to the start of the utf-32 char 0058 m_cursor.setColumn(col - 1); 0059 } 0060 0061 Q_ASSERT(isValidTextPosition()); 0062 } 0063 0064 void DocumentCursor::setPosition(int line, int column) 0065 { 0066 m_cursor.setPosition(line, column); 0067 } 0068 0069 void DocumentCursor::setLine(int line) 0070 { 0071 setPosition(line, column()); 0072 } 0073 0074 void DocumentCursor::setColumn(int column) 0075 { 0076 setPosition(line(), column); 0077 } 0078 0079 bool DocumentCursor::atStartOfLine() const 0080 { 0081 return isValidTextPosition() && column() == 0; 0082 } 0083 0084 bool DocumentCursor::atEndOfLine() const 0085 { 0086 return isValidTextPosition() && column() == document()->lineLength(line()); 0087 } 0088 0089 bool DocumentCursor::atStartOfDocument() const 0090 { 0091 return line() == 0 && column() == 0; 0092 } 0093 0094 bool DocumentCursor::atEndOfDocument() const 0095 { 0096 // avoid costly lineLength computation if we are not in the last line 0097 // this is called often e.g. during search & replace, >> 2% of the total costs 0098 const auto lastLine = document()->lines() - 1; 0099 return line() == lastLine && column() == document()->lineLength(lastLine); 0100 } 0101 0102 bool DocumentCursor::gotoNextLine() 0103 { 0104 // only allow valid cursors 0105 const bool ok = isValid() && (line() + 1 < document()->lines()); 0106 0107 if (ok) { 0108 setPosition(Cursor(line() + 1, 0)); 0109 } 0110 0111 return ok; 0112 } 0113 0114 bool DocumentCursor::gotoPreviousLine() 0115 { 0116 // only allow valid cursors 0117 bool ok = (line() > 0) && (column() >= 0); 0118 0119 if (ok) { 0120 setPosition(Cursor(line() - 1, 0)); 0121 } 0122 0123 return ok; 0124 } 0125 0126 bool DocumentCursor::move(int chars, WrapBehavior wrapBehavior) 0127 { 0128 if (!isValid()) { 0129 return false; 0130 } 0131 0132 // create temporary cursor to modify 0133 Cursor c(m_cursor); 0134 0135 // forwards? 0136 if (chars > 0) { 0137 // cache lineLength to minimize calls of KTextEditor::DocumentPrivate::lineLength(), as 0138 // results in locating the correct block in the text buffer every time, 0139 // which is relatively slow 0140 int lineLength = document()->lineLength(c.line()); 0141 0142 // special case: cursor position is not in valid text, then the algo does 0143 // not work for Wrap mode. Hence, catch this special case by setting 0144 // c.column() to the lineLength() 0145 if (wrapBehavior == Wrap && c.column() > lineLength) { 0146 c.setColumn(lineLength); 0147 } 0148 0149 while (chars != 0) { 0150 if (wrapBehavior == Wrap) { 0151 const int advance = qMin(lineLength - c.column(), chars); 0152 0153 if (chars > advance) { 0154 if (c.line() + 1 >= document()->lines()) { 0155 return false; 0156 } 0157 0158 c.setPosition(c.line() + 1, 0); 0159 chars -= advance + 1; // +1 because of end-of-line wrap 0160 0161 // advanced one line, so cache correct line length again 0162 lineLength = document()->lineLength(c.line()); 0163 } else { 0164 c.setColumn(c.column() + chars); 0165 chars = 0; 0166 } 0167 } else { // NoWrap 0168 c.setColumn(c.column() + chars); 0169 chars = 0; 0170 } 0171 } 0172 } 0173 0174 // backwards? 0175 else { 0176 while (chars != 0) { 0177 const int back = qMin(c.column(), -chars); 0178 if (-chars > back) { 0179 if (c.line() == 0) { 0180 return false; 0181 } 0182 0183 c.setPosition(c.line() - 1, document()->lineLength(c.line() - 1)); 0184 chars += back + 1; // +1 because of wrap-around at start-of-line 0185 } else { 0186 c.setColumn(c.column() + chars); 0187 chars = 0; 0188 } 0189 } 0190 } 0191 0192 if (c != m_cursor) { 0193 setPosition(c); 0194 } 0195 return true; 0196 } 0197 0198 }