File indexing completed on 2024-04-21 11:36:37

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, const 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 }