Index: src/document/katedocument.h =================================================================== --- src/document/katedocument.h +++ src/document/katedocument.h @@ -323,12 +323,34 @@ bool editRemoveLines(int from, int to); /** - * Remove a line + * Wrap the text touched by given line numbers. * @param startLine line to begin wrapping * @param endLine line to stop wrapping * @return true on success */ bool wrapText(int startLine, int endLine); + +private: + /** + * Examine the given line if a join & wrap with the lines above and below + * is needed by check the Kate::TextLine::flagAutoWraped attribute. If so is + * wrapLine called with the result of the scrutiny. + * Don't use this function, it's only a helper for wrapText! + * @param lineNumber line to wrap + * @return number of new lines added + */ + int scrutinizeToWrap(int lineNumber); + + /** + * Wrap the given line at the configured wrap column. + * This wrap is only done once. If a wrap was done may the new line still be longer + * than configured. The new line got the Kate::TextLine::flagAutoWraped attribute. + * Don't use this function, it's only a helper for wrapText! + * @param lineNumber line to wrap + * @return true when line was wrapped otherwise false + */ + bool wrapLine(int lineNumber); + //END LINE BASED INSERT/REMOVE STUFF Q_SIGNALS: Index: src/document/katedocument.cpp =================================================================== --- src/document/katedocument.cpp +++ src/document/katedocument.cpp @@ -1105,113 +1105,151 @@ return false; } - int col = config()->wordWrapAt(); + editStart(); - if (col == 0) { - return false; + while (startLine <= endLine) { + int newLines = scrutinizeToWrap(startLine); + startLine += newLines + 1; + endLine += newLines; } - editStart(); + editEnd(); - for (int line = startLine; (line <= endLine) && (line < lines()); line++) { - Kate::TextLine l = kateTextLine(line); + return true; +} - if (!l) { - break; +int KTextEditor::DocumentPrivate::scrutinizeToWrap(int lineNumber) +{ + const int wrapColumn = wordWrapAt(); + const int tabWidth = m_buffer->tabWidth(); + + int startLine = lineNumber; + + // Look backwards if a join with the previous line is possible + Kate::TextLine prevl = kateTextLine(lineNumber - 1); + Kate::TextLine line = kateTextLine(lineNumber); + if (prevl && line && line->isAutoWrapped()) { + int firstOnThis = line->string().indexOf(QLatin1Char(' ')); + if (firstOnThis < (wrapColumn - prevl->virtualLength(tabWidth) + 1)) { + --startLine; + --lineNumber; + editUnWrapLine(lineNumber); } + } - //qCDebug(LOG_KTE) << "try wrap line: " << line; + --lineNumber; + do { + ++lineNumber; + line = kateTextLine(lineNumber); + if (!line) { + break; // Should never happens + } - if (l->virtualLength(m_buffer->tabWidth()) > col) { - Kate::TextLine nextl = kateTextLine(line + 1); + // Look forward if a join with the next line is useful; In other words: + // Don't join dumb, be smart and avoid a lot of unneeded work + int firstOnNext = wrapColumn + 1; // Length of first word in next line + Kate::TextLine nextl = kateTextLine(lineNumber + 1); + if (nextl && nextl->isAutoWrapped()) { + firstOnNext = nextl->string().indexOf(QLatin1Char(' ')); + } + if (firstOnNext < (wrapColumn - line->virtualLength(tabWidth) + 1)) { + editUnWrapLine(lineNumber); + } - //qCDebug(LOG_KTE) << "do wrap line: " << line; + } while (wrapLine(lineNumber)); - int eolPosition = l->length() - 1; + return lineNumber - startLine; +} - // take tabs into account here, too - int x = 0; - const QString &t = l->string(); - int z2 = 0; - for (; z2 < l->length(); z2++) { - static const QChar tabChar(QLatin1Char('\t')); - if (t.at(z2) == tabChar) { - x += m_buffer->tabWidth() - (x % m_buffer->tabWidth()); - } else { - x++; - } +bool KTextEditor::DocumentPrivate::wrapLine(int lineNumber) +{ + Kate::TextLine line = kateTextLine(lineNumber); - if (x > col) { - break; - } - } + if (!line) { + return false; + } - const int colInChars = qMin(z2, l->length() - 1); - int searchStart = colInChars; + const int wrapColumn = wordWrapAt(); + const int tabWidth = m_buffer->tabWidth(); - // If where we are wrapping is an end of line and is a space we don't - // want to wrap there - if (searchStart == eolPosition && t.at(searchStart).isSpace()) { - searchStart--; - } + if (wrapColumn == 0) { // setWordWrapAt/config()->setWordWrapAt should avoid bad settings + return false; + } - // Scan backwards looking for a place to break the line - // We are not interested in breaking at the first char - // of the line (if it is a space), but we are at the second - // anders: if we can't find a space, try breaking on a word - // boundary, using KateHighlight::canBreakAt(). - // This could be a priority (setting) in the hl/filetype/document - int z = -1; - int nw = -1; // alternative position, a non word character - for (z = searchStart; z >= 0; z--) { - if (t.at(z).isSpace()) { - break; - } - if ((nw < 0) && highlight()->canBreakAt(t.at(z), l->attribute(z))) { - nw = z; - } - } + // Hack due to strange general behavior that an over size space is allowed (is not removed) + // when the line will wrapped but not when it's there on an 2nd invoke. + const int lastSpace = line->endsWith(QLatin1String(" ")) ? 1 : 0; - if (z >= 0) { - // So why don't we just remove the trailing space right away? - // Well, the (view's) cursor may be directly in front of that space - // (user typing text before the last word on the line), and if that - // happens, the cursor would be moved to the next line, which is not - // what we want (bug #106261) - z++; - } else { - // There was no space to break at so break at a nonword character if - // found, or at the wrapcolumn ( that needs be configurable ) - // Don't try and add any white space for the break - if ((nw >= 0) && nw < colInChars) { - nw++; // break on the right side of the character - } - z = (nw >= 0) ? nw : colInChars; - } + if (line->virtualLength(tabWidth) - lastSpace <= wrapColumn) { + return false; // Nothing to do; we are done + } - if (nextl && !nextl->isAutoWrapped()) { - editWrapLine(line, z, true); - editMarkLineAutoWrapped(line + 1, true); + const int eolPosition = line->length() - 1; - endLine++; - } else { - if (nextl && (nextl->length() > 0) && !nextl->at(0).isSpace() && ((l->length() < 1) || !l->at(l->length() - 1).isSpace())) { - editInsertText(line + 1, 0, QLatin1String(" ")); - } + // take tabs into account here, too + const QString &text = line->string(); + static const QChar tabChar(QLatin1Char('\t')); + int z2 = 0; + for (int x = 0; z2 < line->length(); z2++) { + if (text.at(z2) == tabChar) { + x += tabWidth - (x % tabWidth); + } else { + x++; + } - bool newLineAdded = false; - editWrapLine(line, z, false, &newLineAdded); + if (x > wrapColumn) { + break; + } + } - editMarkLineAutoWrapped(line + 1, true); + const int trueColumn = qMin(z2, eolPosition); + int searchStart = trueColumn; - endLine++; - } + // If where we are wrapping is an end of line and is a space we don't + // want to wrap there + if (searchStart == eolPosition && text.at(searchStart).isSpace()) { + searchStart--; + } + + // Scan backwards looking for a place to break the line + // We are not interested in breaking at the first char + // of the line (if it is a space), but we are at the second + // anders: if we can't find a space, try breaking on a word + // boundary, using KateHighlight::canBreakAt(). + // This could be a priority (setting) in the hl/filetype/document + int z = -1; + int nw = -1; // alternative position, a non word character + for (z = searchStart; z >= 0; z--) { + if (text.at(z).isSpace()) { + break; + } + if ((nw < 0) && highlight()->canBreakAt(text.at(z), line->attribute(z))) { + nw = z; } } - editEnd(); + if (z >= 0) { + // So why don't we just remove the trailing space right away? + // Well, the (view's) cursor may be directly in front of that space + // (user typing text before the last word on the line), and if that + // happens, the cursor would be moved to the next line, which is not + // what we want (bug #106261) + z++; + } else { + // There was no space to break at so break at a nonword character if + // found, or at the wrapcolumn ( that needs be configurable ) + // Don't try and add any white space for the break + if ((nw >= 0) && nw < trueColumn) { + nw++; // break on the right side of the character + } + z = (nw >= 0) ? nw : trueColumn; + } - return true; + bool newLineAdded = false; + editWrapLine(lineNumber, z, true, &newLineAdded); + editMarkLineAutoWrapped(lineNumber + 1, true); + + return newLineAdded; } bool KTextEditor::DocumentPrivate::editInsertText(int line, int col, const QString &s) @@ -1401,7 +1439,6 @@ } else { m_buffer->wrapLine(KTextEditor::Cursor(line, col)); m_buffer->unwrapLine(line + 2); - // no, no new line added ! if (newLineAdded) { (*newLineAdded) = false; @@ -3137,7 +3174,8 @@ // first: wrap line editWrapLine(c.line(), c.column()); - + // Ensure in static wrap mode not to re-join the new line + editMarkLineAutoWrapped(c.line(), false); // end edit session here, to have updated HL in userTypedChar! editEnd(); @@ -3237,10 +3275,13 @@ } else { beginCursor.setColumn(view->textLayout(c)->previousCursorPosition(c.column())); } - removeText(KTextEditor::Range(beginCursor, endCursor)); // in most cases cursor is moved by removeText, but we should do it manually // for past-end-of-line cursors in block mode - view->setCursorPosition(beginCursor); + Kate::TextLine textLine = plainKateTextLine(line); + if (textLine && textLine->length() < static_cast(col)) { + view->setCursorPosition(KTextEditor::Cursor(line, col - 1)); + } + removeText(KTextEditor::Range(beginCursor, endCursor)); } } else { // col == 0: wrap to previous line