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: @@ -805,6 +827,11 @@ void del(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &); void transpose(const KTextEditor::Cursor &); void paste(KTextEditor::ViewPrivate *view, const QString &text); +private: + /** + * Set in some cases with block selection to avoid wrapping in editEnd() + */ + bool m_doNotWrap = false; public: void indent(KTextEditor::Range range, int change); Index: src/document/katedocument.cpp =================================================================== --- src/document/katedocument.cpp +++ src/document/katedocument.cpp @@ -1026,17 +1026,32 @@ } // wrap the new/changed text, if something really changed! - if (m_buffer->editChanged() && (editSessionNumber == 1)) - if (m_undoManager->isActive() && config()->wordWrap()) { + if (m_buffer->editChanged() && m_undoManager->isActive() && (editSessionNumber == 1) && !m_doNotWrap) { + bool wrap = config()->wordWrap(); + if (!wrap) { + // Even when static wrap is not enabled, may the user like that a once manually + // wrapped paragraph/line is again adjusted + for (int l = m_buffer->editTagStart(); l <= m_buffer->editTagEnd(); ++l) { + Kate::TextLine line = kateTextLine(l); + if (line->isAutoWrapped()) { + wrap = true; + break; + } + } + } + if (wrap) { wrapText(m_buffer->editTagStart(), m_buffer->editTagEnd()); } + } editSessionNumber--; if (editSessionNumber > 0) { return false; } + m_doNotWrap = false; + // end buffer edit, will trigger hl update // this will cause some possible adjustment of tagline start/end m_buffer->editEnd(); @@ -1105,113 +1120,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++; + } + + if (x > wrapColumn) { + break; + } + } - bool newLineAdded = false; - editWrapLine(line, z, false, &newLineAdded); + const int trueColumn = qMin(z2, eolPosition); + int searchStart = trueColumn; - editMarkLineAutoWrapped(line + 1, true); + // 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--; + } - endLine++; - } + // 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 +1454,6 @@ } else { m_buffer->wrapLine(KTextEditor::Cursor(line, col)); m_buffer->unwrapLine(line + 2); - // no, no new line added ! if (newLineAdded) { (*newLineAdded) = false; @@ -3036,6 +3088,7 @@ } if (multiLineBlockMode) { + m_doNotWrap = true; KTextEditor::Range selectionRange = view->selectionRange(); const int startLine = qMax(0, selectionRange.start().line()); const int endLine = qMin(selectionRange.end().line(), lastLine()); @@ -3137,7 +3190,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(); @@ -3182,11 +3236,12 @@ void KTextEditor::DocumentPrivate::backspace(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &c) { if (!view->config()->persistentSelection() && view->selection()) { - if (view->blockSelection() && view->selection() && toVirtualColumn(view->selectionRange().start()) == toVirtualColumn(view->selectionRange().end())) { - // Remove one character after selection line + if (view->blockSelection() && toVirtualColumn(view->selectionRange().start()) == toVirtualColumn(view->selectionRange().end())) { + // Range has no width! Expand selection over the character before the vertical line KTextEditor::Range range = view->selectionRange(); range.setStart(KTextEditor::Cursor(range.start().line(), range.start().column() - 1)); view->setSelection(range); + m_doNotWrap = true; } view->removeSelectedText(); return; @@ -3237,10 +3292,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 @@ -3273,11 +3331,12 @@ void KTextEditor::DocumentPrivate::del(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &c) { if (!view->config()->persistentSelection() && view->selection()) { - if (view->blockSelection() && view->selection() && toVirtualColumn(view->selectionRange().start()) == toVirtualColumn(view->selectionRange().end())) { - // Remove one character after selection line + if (view->blockSelection() && toVirtualColumn(view->selectionRange().start()) == toVirtualColumn(view->selectionRange().end())) { + // Range has no width! Expand selection over the character after the vertical line KTextEditor::Range range = view->selectionRange(); range.setEnd(KTextEditor::Cursor(range.end().line(), range.end().column() + 1)); view->setSelection(range); + m_doNotWrap = true; } view->removeSelectedText(); return; @@ -3311,6 +3370,7 @@ if (!view->config()->persistentSelection() && view->selection()) { pos = view->selectionRange().start(); if (view->blockSelection()) { + m_doNotWrap = true; pos = rangeOnLine(view->selectionRange(), pos.line()).start(); if (lines == 0) { s += newLineChar;