mirror of
https://bitbucket.org/smil3y/kde-workspace.git
synced 2025-02-24 10:52:51 +00:00
688 lines
19 KiB
C++
688 lines
19 KiB
C++
/* This file is part of the Kate project.
|
|
*
|
|
* Copyright (C) 2010 Christoph Cullmann <cullmann@kde.org>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public License
|
|
* along with this library; see the file COPYING.LIB. If not, write to
|
|
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "katetextblock.h"
|
|
#include "katetextbuffer.h"
|
|
|
|
namespace Kate {
|
|
|
|
TextBlock::TextBlock (TextBuffer *buffer, int startLine)
|
|
: m_buffer (buffer)
|
|
, m_startLine (startLine)
|
|
{
|
|
// reserve the block size
|
|
m_lines.reserve (m_buffer->m_blockSize);
|
|
}
|
|
|
|
TextBlock::~TextBlock ()
|
|
{
|
|
// blocks should be empty before they are deleted!
|
|
Q_ASSERT (m_lines.empty());
|
|
Q_ASSERT (m_cursors.empty());
|
|
|
|
// it only is a hint for ranges for this block, not the storage of them
|
|
}
|
|
|
|
void TextBlock::setStartLine (int startLine)
|
|
{
|
|
// allow only valid lines
|
|
Q_ASSERT (startLine >= 0);
|
|
Q_ASSERT (startLine < m_buffer->lines ());
|
|
|
|
m_startLine = startLine;
|
|
}
|
|
|
|
TextLine TextBlock::line (int line) const
|
|
{
|
|
// right input
|
|
Q_ASSERT (line >= startLine ());
|
|
|
|
// calc internal line
|
|
line = line - startLine ();
|
|
|
|
// in range
|
|
Q_ASSERT (line < m_lines.size ());
|
|
|
|
// get text line
|
|
return m_lines.at(line);
|
|
}
|
|
|
|
void TextBlock::appendLine (const QString &textOfLine)
|
|
{
|
|
m_lines.append (TextLine (new TextLineData(textOfLine)));
|
|
}
|
|
|
|
void TextBlock::clearLines ()
|
|
{
|
|
m_lines.clear ();
|
|
}
|
|
|
|
void TextBlock::text (QString &text) const
|
|
{
|
|
// combine all lines
|
|
for (int i = 0; i < m_lines.size(); ++i) {
|
|
// not first line, insert \n
|
|
if (i > 0 || startLine() > 0)
|
|
text.append ('\n');
|
|
|
|
text.append (m_lines.at(i)->text ());
|
|
}
|
|
}
|
|
|
|
void TextBlock::wrapLine (const KTextEditor::Cursor &position, int fixStartLinesStartIndex)
|
|
{
|
|
// calc internal line
|
|
int line = position.line () - startLine ();
|
|
|
|
// get text
|
|
QString &text = m_lines.at(line)->textReadWrite ();
|
|
|
|
// check if valid column
|
|
Q_ASSERT (position.column() >= 0);
|
|
Q_ASSERT (position.column() <= text.size());
|
|
|
|
// create new line and insert it
|
|
m_lines.insert (m_lines.begin() + line + 1, TextLine (new TextLineData()));
|
|
|
|
// cases for modification:
|
|
// 1. line is wrapped in the middle
|
|
// 2. if empty line is wrapped, mark new line as modified
|
|
// 3. line-to-be-wrapped is already modified
|
|
if (position.column() > 0 || text.size() == 0 || m_lines.at(line)->markedAsModified()) {
|
|
m_lines.at(line + 1)->markAsModified(true);
|
|
} else if (m_lines.at(line)->markedAsSavedOnDisk()) {
|
|
m_lines.at(line + 1)->markAsSavedOnDisk(true);
|
|
}
|
|
|
|
// perhaps remove some text from previous line and append it
|
|
if (position.column() < text.size ()) {
|
|
// text from old line moved first to new one
|
|
m_lines.at(line+1)->textReadWrite() = text.right (text.size() - position.column());
|
|
|
|
// now remove wrapped text from old line
|
|
text.chop (text.size() - position.column());
|
|
|
|
// mark line as modified
|
|
m_lines.at(line)->markAsModified(true);
|
|
}
|
|
|
|
/**
|
|
* fix all start lines
|
|
* we need to do this NOW, else the range update will FAIL!
|
|
* bug 313759
|
|
*/
|
|
m_buffer->fixStartLines (fixStartLinesStartIndex);
|
|
|
|
/**
|
|
* notify the text history
|
|
*/
|
|
m_buffer->history().wrapLine (position);
|
|
|
|
/**
|
|
* cursor and range handling below
|
|
*/
|
|
|
|
// no cursors will leave or join this block
|
|
|
|
// no cursors in this block, no work to do..
|
|
if (m_cursors.empty())
|
|
return;
|
|
|
|
// move all cursors on the line which has the text inserted
|
|
// remember all ranges modified
|
|
QSet<TextRange *> changedRanges;
|
|
foreach (TextCursor *cursor, m_cursors) {
|
|
// skip cursors on lines in front of the wrapped one!
|
|
if (cursor->lineInBlock() < line)
|
|
continue;
|
|
|
|
// either this is simple, line behind the wrapped one
|
|
if (cursor->lineInBlock() > line) {
|
|
// patch line of cursor
|
|
cursor->m_line++;
|
|
}
|
|
|
|
// this is the wrapped line
|
|
else {
|
|
// skip cursors with too small column
|
|
if (cursor->column() <= position.column()) {
|
|
if (cursor->column() < position.column() || !cursor->m_moveOnInsert)
|
|
continue;
|
|
}
|
|
|
|
// move cursor
|
|
|
|
// patch line of cursor
|
|
cursor->m_line++;
|
|
|
|
// patch column
|
|
cursor->m_column -= position.column();
|
|
}
|
|
|
|
// remember range, if any
|
|
if (cursor->kateRange())
|
|
changedRanges.insert (cursor->kateRange());
|
|
}
|
|
|
|
// check validity of all ranges, might invalidate them...
|
|
foreach (TextRange *range, changedRanges)
|
|
range->checkValidity ();
|
|
}
|
|
|
|
void TextBlock::unwrapLine (int line, TextBlock *previousBlock, int fixStartLinesStartIndex)
|
|
{
|
|
// calc internal line
|
|
line = line - startLine ();
|
|
|
|
// two possiblities: either first line of this block or later line
|
|
if (line == 0) {
|
|
// we need previous block with at least one line
|
|
Q_ASSERT (previousBlock);
|
|
Q_ASSERT (previousBlock->lines () > 0);
|
|
|
|
// move last line of previous block to this one, might result in empty block
|
|
TextLine oldFirst = m_lines.at(0);
|
|
int lastLineOfPreviousBlock = previousBlock->lines ()-1;
|
|
TextLine newFirst = previousBlock->m_lines.last();
|
|
m_lines[0] = newFirst;
|
|
previousBlock->m_lines.erase (previousBlock->m_lines.begin() + (previousBlock->lines () - 1));
|
|
|
|
const int oldSizeOfPreviousLine = newFirst->text().size();
|
|
if (oldFirst->length() > 0) {
|
|
// append text
|
|
newFirst->textReadWrite().append (oldFirst->text());
|
|
|
|
// mark line as modified, since text was appended
|
|
newFirst->markAsModified(true);
|
|
}
|
|
|
|
// patch startLine of this block
|
|
--m_startLine;
|
|
|
|
/**
|
|
* fix all start lines
|
|
* we need to do this NOW, else the range update will FAIL!
|
|
* bug 313759
|
|
*/
|
|
m_buffer->fixStartLines (fixStartLinesStartIndex);
|
|
|
|
/**
|
|
* notify the text history in advance
|
|
*/
|
|
m_buffer->history().unwrapLine (startLine () + line, oldSizeOfPreviousLine);
|
|
|
|
/**
|
|
* cursor and range handling below
|
|
*/
|
|
|
|
// no cursors in this block and the previous one, no work to do..
|
|
if (m_cursors.empty() && previousBlock->m_cursors.empty())
|
|
return;
|
|
|
|
// move all cursors because of the unwrapped line
|
|
// remember all ranges modified
|
|
QSet<TextRange *> changedRanges;
|
|
foreach (TextCursor *cursor, m_cursors) {
|
|
// this is the unwrapped line
|
|
if (cursor->lineInBlock() == 0) {
|
|
// patch column
|
|
cursor->m_column += oldSizeOfPreviousLine;
|
|
|
|
// remember range, if any
|
|
if (cursor->kateRange())
|
|
changedRanges.insert (cursor->kateRange());
|
|
}
|
|
}
|
|
|
|
// move cursors of the moved line from previous block to this block now
|
|
QSet<TextCursor *> newPreviousCursors;
|
|
foreach (TextCursor *cursor, previousBlock->m_cursors) {
|
|
if (cursor->lineInBlock() == lastLineOfPreviousBlock) {
|
|
cursor->m_line = 0;
|
|
cursor->m_block = this;
|
|
m_cursors.insert (cursor);
|
|
|
|
// remember range, if any
|
|
if (cursor->kateRange())
|
|
changedRanges.insert (cursor->kateRange());
|
|
}
|
|
else
|
|
newPreviousCursors.insert (cursor);
|
|
}
|
|
previousBlock->m_cursors = newPreviousCursors;
|
|
|
|
// fixup the ranges that might be effected, because they moved from last line to this block
|
|
foreach (TextRange *range, changedRanges) {
|
|
// update both blocks
|
|
updateRange (range);
|
|
previousBlock->updateRange (range);
|
|
}
|
|
|
|
// check validity of all ranges, might invalidate them...
|
|
foreach (TextRange *range, changedRanges)
|
|
range->checkValidity ();
|
|
|
|
// be done
|
|
return;
|
|
}
|
|
|
|
// easy: just move text to previous line and remove current one
|
|
const int oldSizeOfPreviousLine = m_lines.at(line-1)->length();
|
|
const int sizeOfCurrentLine = m_lines.at(line)->length();
|
|
if (sizeOfCurrentLine > 0)
|
|
m_lines.at(line-1)->textReadWrite().append (m_lines.at(line)->text());
|
|
|
|
const bool lineChanged = (oldSizeOfPreviousLine > 0 && m_lines.at(line - 1)->markedAsModified())
|
|
|| (sizeOfCurrentLine > 0 && (oldSizeOfPreviousLine > 0 || m_lines.at(line)->markedAsModified()));
|
|
m_lines.at(line-1)->markAsModified(lineChanged);
|
|
if (oldSizeOfPreviousLine == 0 && m_lines.at(line)->markedAsSavedOnDisk())
|
|
m_lines.at(line-1)->markAsSavedOnDisk(true);
|
|
|
|
m_lines.erase (m_lines.begin () + line);
|
|
|
|
/**
|
|
* fix all start lines
|
|
* we need to do this NOW, else the range update will FAIL!
|
|
* bug 313759
|
|
*/
|
|
m_buffer->fixStartLines (fixStartLinesStartIndex);
|
|
|
|
/**
|
|
* notify the text history in advance
|
|
*/
|
|
m_buffer->history().unwrapLine (startLine () + line, oldSizeOfPreviousLine);
|
|
|
|
/**
|
|
* cursor and range handling below
|
|
*/
|
|
|
|
// no cursors in this block, no work to do..
|
|
if (m_cursors.empty())
|
|
return;
|
|
|
|
// move all cursors because of the unwrapped line
|
|
// remember all ranges modified
|
|
QSet<TextRange *> changedRanges;
|
|
foreach (TextCursor *cursor, m_cursors) {
|
|
// skip cursors in lines in front of removed one
|
|
if (cursor->lineInBlock() < line)
|
|
continue;
|
|
|
|
// this is the unwrapped line
|
|
if (cursor->lineInBlock() == line) {
|
|
// patch column
|
|
cursor->m_column += oldSizeOfPreviousLine;
|
|
}
|
|
|
|
// patch line of cursor
|
|
cursor->m_line--;
|
|
|
|
// remember range, if any
|
|
if (cursor->kateRange())
|
|
changedRanges.insert (cursor->kateRange());
|
|
}
|
|
|
|
// check validity of all ranges, might invalidate them...
|
|
foreach (TextRange *range, changedRanges)
|
|
range->checkValidity ();
|
|
}
|
|
|
|
void TextBlock::insertText (const KTextEditor::Cursor &position, const QString &text)
|
|
{
|
|
// calc internal line
|
|
int line = position.line () - startLine ();
|
|
|
|
// get text
|
|
QString &textOfLine = m_lines.at(line)->textReadWrite ();
|
|
int oldLength = textOfLine.size ();
|
|
m_lines.at(line)->markAsModified(true);
|
|
|
|
// check if valid column
|
|
Q_ASSERT (position.column() >= 0);
|
|
Q_ASSERT (position.column() <= textOfLine.size());
|
|
|
|
// insert text
|
|
textOfLine.insert (position.column(), text);
|
|
|
|
/**
|
|
* notify the text history
|
|
*/
|
|
m_buffer->history().insertText (position, text.size(), oldLength);
|
|
|
|
/**
|
|
* cursor and range handling below
|
|
*/
|
|
|
|
// no cursors in this block, no work to do..
|
|
if (m_cursors.empty())
|
|
return;
|
|
|
|
// move all cursors on the line which has the text inserted
|
|
// remember all ranges modified
|
|
QSet<TextRange *> changedRanges;
|
|
foreach (TextCursor *cursor, m_cursors) {
|
|
// skip cursors not on this line!
|
|
if (cursor->lineInBlock() != line)
|
|
continue;
|
|
|
|
// skip cursors with too small column
|
|
if (cursor->column() <= position.column()) {
|
|
if (cursor->column() < position.column() || !cursor->m_moveOnInsert)
|
|
continue;
|
|
}
|
|
|
|
// patch column of cursor
|
|
if (cursor->m_column <= oldLength)
|
|
cursor->m_column += text.size ();
|
|
|
|
// special handling if cursor behind the real line, e.g. non-wrapping cursor in block selection mode
|
|
else if (cursor->m_column < textOfLine.size())
|
|
cursor->m_column = textOfLine.size();
|
|
|
|
// remember range, if any
|
|
if (cursor->kateRange())
|
|
changedRanges.insert (cursor->kateRange());
|
|
}
|
|
|
|
// check validity of all ranges, might invalidate them...
|
|
foreach (TextRange *range, changedRanges)
|
|
range->checkValidity ();
|
|
}
|
|
|
|
void TextBlock::removeText (const KTextEditor::Range &range, QString &removedText)
|
|
{
|
|
// calc internal line
|
|
int line = range.start().line () - startLine ();
|
|
|
|
// get text
|
|
QString &textOfLine = m_lines.at(line)->textReadWrite ();
|
|
int oldLength = textOfLine.size ();
|
|
|
|
// check if valid column
|
|
Q_ASSERT (range.start().column() >= 0);
|
|
Q_ASSERT (range.start().column() <= textOfLine.size());
|
|
Q_ASSERT (range.end().column() >= 0);
|
|
Q_ASSERT (range.end().column() <= textOfLine.size());
|
|
|
|
// get text which will be removed
|
|
removedText = textOfLine.mid (range.start().column(), range.end().column() - range.start().column());
|
|
|
|
// remove text
|
|
textOfLine.remove (range.start().column(), range.end().column() - range.start().column());
|
|
m_lines.at(line)->markAsModified(true);
|
|
|
|
/**
|
|
* notify the text history
|
|
*/
|
|
m_buffer->history().removeText (range, oldLength);
|
|
|
|
/**
|
|
* cursor and range handling below
|
|
*/
|
|
|
|
// no cursors in this block, no work to do..
|
|
if (m_cursors.empty())
|
|
return;
|
|
|
|
// move all cursors on the line which has the text removed
|
|
// remember all ranges modified
|
|
QSet<TextRange *> changedRanges;
|
|
foreach (TextCursor *cursor, m_cursors) {
|
|
// skip cursors not on this line!
|
|
if (cursor->lineInBlock() != line)
|
|
continue;
|
|
|
|
// skip cursors with too small column
|
|
if (cursor->column() <= range.start().column())
|
|
continue;
|
|
|
|
// patch column of cursor
|
|
if (cursor->column() <= range.end().column())
|
|
cursor->m_column = range.start().column ();
|
|
else
|
|
cursor->m_column -= (range.end().column() - range.start().column());
|
|
|
|
// remember range, if any
|
|
if (cursor->kateRange())
|
|
changedRanges.insert (cursor->kateRange());
|
|
}
|
|
|
|
// check validity of all ranges, might invalidate them...
|
|
foreach (TextRange *range, changedRanges)
|
|
range->checkValidity ();
|
|
}
|
|
|
|
void TextBlock::debugPrint (int blockIndex) const
|
|
{
|
|
// print all blocks
|
|
for (int i = 0; i < m_lines.size(); ++i)
|
|
printf ("%4d - %4d : %4d : '%s'\n", blockIndex, startLine() + i
|
|
, m_lines.at(i)->text().size(), qPrintable (m_lines.at(i)->text()));
|
|
}
|
|
|
|
TextBlock *TextBlock::splitBlock (int fromLine)
|
|
{
|
|
// half the block
|
|
int linesOfNewBlock = lines () - fromLine;
|
|
|
|
// create and insert new block
|
|
TextBlock *newBlock = new TextBlock (m_buffer, startLine() + fromLine);
|
|
|
|
// move lines
|
|
newBlock->m_lines.reserve (linesOfNewBlock);
|
|
for (int i = fromLine; i < m_lines.size(); ++i)
|
|
newBlock->m_lines.append (m_lines.at(i));
|
|
m_lines.resize (fromLine);
|
|
|
|
// move cursors
|
|
QSet<TextCursor*> oldBlockSet;
|
|
foreach (TextCursor *cursor, m_cursors) {
|
|
if (cursor->lineInBlock() >= fromLine) {
|
|
cursor->m_line = cursor->lineInBlock() - fromLine;
|
|
cursor->m_block = newBlock;
|
|
newBlock->m_cursors.insert (cursor);
|
|
}
|
|
else
|
|
oldBlockSet.insert (cursor);
|
|
}
|
|
m_cursors = oldBlockSet;
|
|
|
|
// fix ALL ranges!
|
|
QList<TextRange*> allRanges = m_uncachedRanges.toList() + m_cachedLineForRanges.keys();
|
|
foreach (TextRange *range, allRanges) {
|
|
// update both blocks
|
|
updateRange (range);
|
|
newBlock->updateRange (range);
|
|
}
|
|
|
|
// return the new generated block
|
|
return newBlock;
|
|
}
|
|
|
|
void TextBlock::mergeBlock (TextBlock *targetBlock)
|
|
{
|
|
// move cursors, do this first, now still lines() count is correct for target
|
|
foreach (TextCursor *cursor, m_cursors) {
|
|
cursor->m_line = cursor->lineInBlock() + targetBlock->lines ();
|
|
cursor->m_block = targetBlock;
|
|
targetBlock->m_cursors.insert (cursor);
|
|
}
|
|
m_cursors.clear ();
|
|
|
|
// move lines
|
|
targetBlock->m_lines.reserve (targetBlock->lines() + lines ());
|
|
for (int i = 0; i < m_lines.size(); ++i)
|
|
targetBlock->m_lines.append (m_lines.at(i));
|
|
m_lines.clear ();
|
|
|
|
// fix ALL ranges!
|
|
QList<TextRange*> allRanges = m_uncachedRanges.toList() + m_cachedLineForRanges.keys();
|
|
foreach(TextRange* range, allRanges) {
|
|
// update both blocks
|
|
updateRange (range);
|
|
targetBlock->updateRange (range);
|
|
}
|
|
}
|
|
|
|
void TextBlock::deleteBlockContent ()
|
|
{
|
|
// kill cursors, if not belonging to a range
|
|
QSet<TextCursor *> copy = m_cursors;
|
|
foreach (TextCursor *cursor, copy)
|
|
if (!cursor->kateRange())
|
|
delete cursor;
|
|
|
|
// kill lines
|
|
m_lines.clear ();
|
|
}
|
|
|
|
void TextBlock::clearBlockContent (TextBlock *targetBlock)
|
|
{
|
|
// move cursors, if not belonging to a range
|
|
QSet<TextCursor *> copy = m_cursors;
|
|
foreach (TextCursor *cursor, copy) {
|
|
if (!cursor->kateRange()) {
|
|
cursor->m_column = 0;
|
|
cursor->m_line = 0;
|
|
cursor->m_block = targetBlock;
|
|
targetBlock->m_cursors.insert (cursor);
|
|
m_cursors.remove (cursor);
|
|
}
|
|
}
|
|
|
|
// kill lines
|
|
m_lines.clear ();
|
|
}
|
|
|
|
void TextBlock::markModifiedLinesAsSaved ()
|
|
{
|
|
// mark all modified lines as saved
|
|
for (int i = 0; i < m_lines.size(); ++i) {
|
|
TextLine textLine = m_lines[i];
|
|
if (textLine->markedAsModified())
|
|
textLine->markAsSavedOnDisk(true);
|
|
}
|
|
}
|
|
|
|
void TextBlock::updateRange (TextRange* range)
|
|
{
|
|
/**
|
|
* get some simple facts about our nice range
|
|
*/
|
|
const int startLine = range->startInternal().lineInternal();
|
|
const int endLine = range->endInternal().lineInternal();
|
|
const bool isSingleLine = startLine == endLine;
|
|
|
|
/**
|
|
* perhaps remove range and be done
|
|
*/
|
|
if ((endLine < m_startLine) || (startLine >= (m_startLine + lines()))) {
|
|
removeRange (range);
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* The range is still a single-line range, and is still cached to the correct line.
|
|
*/
|
|
if(isSingleLine && m_cachedLineForRanges.contains (range) && (m_cachedLineForRanges.value(range) == startLine - m_startLine))
|
|
return;
|
|
|
|
/**
|
|
* The range is still a multi-line range, and is already in the correct set.
|
|
*/
|
|
if(!isSingleLine && m_uncachedRanges.contains (range))
|
|
return;
|
|
|
|
/**
|
|
* remove, if already there!
|
|
*/
|
|
removeRange(range);
|
|
|
|
/**
|
|
* simple case: multi-line range
|
|
*/
|
|
if (!isSingleLine) {
|
|
/**
|
|
* The range cannot be cached per line, as it spans multiple lines
|
|
*/
|
|
m_uncachedRanges.insert(range);
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* The range is contained by a single line, put it into the line-cache
|
|
*/
|
|
const int lineOffset = startLine - m_startLine;
|
|
|
|
/**
|
|
* enlarge cache if needed
|
|
*/
|
|
if (m_cachedRangesForLine.size() <= lineOffset)
|
|
m_cachedRangesForLine.resize(lineOffset+1);
|
|
|
|
/**
|
|
* insert into mapping
|
|
*/
|
|
m_cachedRangesForLine[lineOffset].insert(range);
|
|
m_cachedLineForRanges[range] = lineOffset;
|
|
}
|
|
|
|
void TextBlock::removeRange (TextRange* range)
|
|
{
|
|
/**
|
|
* uncached range? remove it and be done
|
|
*/
|
|
if(m_uncachedRanges.remove (range)) {
|
|
/**
|
|
* must be only uncached!
|
|
*/
|
|
Q_ASSERT (!m_cachedLineForRanges.contains(range));
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* cached range?
|
|
*/
|
|
QHash<TextRange*, int>::iterator it = m_cachedLineForRanges.find(range);
|
|
if (it != m_cachedLineForRanges.end()) {
|
|
/**
|
|
* must be only cached!
|
|
*/
|
|
Q_ASSERT (!m_uncachedRanges.contains(range));
|
|
|
|
/**
|
|
* query the range from cache, must be there
|
|
*/
|
|
Q_ASSERT (m_cachedRangesForLine.at(*it).contains(range));
|
|
|
|
/**
|
|
* remove it and be done
|
|
*/
|
|
m_cachedRangesForLine[*it].remove(range);
|
|
m_cachedLineForRanges.erase(it);
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* else: range was not for this block, just do nothing, removeRange should be "safe" to use
|
|
*/
|
|
}
|
|
|
|
}
|