mirror of
https://bitbucket.org/smil3y/kde-workspace.git
synced 2025-02-24 10:52:51 +00:00

m_textCodec is used only to probe, if BOM is detected m_textCodec may be null. fixes possible crash Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
873 lines
24 KiB
C++
873 lines
24 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 "config.h"
|
|
|
|
#include "katetextbuffer.h"
|
|
#include "katetextloader.h"
|
|
|
|
// this is unfortunate, but needed for performance
|
|
#include "katedocument.h"
|
|
#include "kateview.h"
|
|
|
|
// fdatasync
|
|
#include <kde_file.h>
|
|
|
|
#include <QTextStream>
|
|
#include <KSaveFile>
|
|
#include <kdeversion.h>
|
|
|
|
#if 0
|
|
#define EDIT_DEBUG kDebug()
|
|
#else
|
|
#define EDIT_DEBUG if (0) kDebug()
|
|
#endif
|
|
|
|
namespace Kate {
|
|
|
|
TextBuffer::TextBuffer (KateDocument *parent, int blockSize)
|
|
: QObject (parent)
|
|
, m_document (parent)
|
|
, m_history (*this)
|
|
, m_blockSize (blockSize)
|
|
, m_lines (0)
|
|
, m_lastUsedBlock (0)
|
|
, m_revision (0)
|
|
, m_editingTransactions (0)
|
|
, m_editingLastRevision (0)
|
|
, m_editingLastLines (0)
|
|
, m_editingMinimalLineChanged (-1)
|
|
, m_editingMaximalLineChanged (-1)
|
|
, m_fallbackTextCodec (0)
|
|
, m_textCodec (0)
|
|
, m_generateByteOrderMark (false)
|
|
, m_endOfLineMode (eolUnix)
|
|
, m_newLineAtEof (false)
|
|
, m_lineLengthLimit (4096)
|
|
{
|
|
// minimal block size must be > 0
|
|
Q_ASSERT (m_blockSize > 0);
|
|
|
|
// create initial state
|
|
clear ();
|
|
}
|
|
|
|
TextBuffer::~TextBuffer ()
|
|
{
|
|
// remove document pointer, this will avoid any notifyAboutRangeChange to have a effect
|
|
m_document = 0;
|
|
|
|
// not allowed during editing
|
|
Q_ASSERT (m_editingTransactions == 0);
|
|
|
|
// kill all ranges, work on copy, they will remove themself from the hash
|
|
QSet<TextRange *> copyRanges = m_ranges;
|
|
qDeleteAll (copyRanges);
|
|
Q_ASSERT (m_ranges.empty());
|
|
|
|
// clean out all cursors and lines, only cursors belonging to range will survive
|
|
foreach(TextBlock* block, m_blocks)
|
|
block->deleteBlockContent ();
|
|
|
|
// delete all blocks, now that all cursors are really deleted
|
|
// else asserts in destructor of blocks will fail!
|
|
qDeleteAll (m_blocks);
|
|
m_blocks.clear ();
|
|
|
|
// kill all invalid cursors, do this after block deletion, to uncover if they might be still linked in blocks
|
|
QSet<TextCursor *> copyCursors = m_invalidCursors;
|
|
qDeleteAll (copyCursors);
|
|
Q_ASSERT (m_invalidCursors.empty());
|
|
}
|
|
|
|
void TextBuffer::invalidateRanges()
|
|
{
|
|
// invalidate all ranges, work on copy, they might delete themself...
|
|
QSet<TextRange *> copyRanges = m_ranges;
|
|
foreach (TextRange *range, copyRanges)
|
|
range->setRange (KTextEditor::Cursor::invalid(), KTextEditor::Cursor::invalid());
|
|
}
|
|
|
|
void TextBuffer::clear ()
|
|
{
|
|
// not allowed during editing
|
|
Q_ASSERT (m_editingTransactions == 0);
|
|
|
|
invalidateRanges();
|
|
|
|
// new block for empty buffer
|
|
TextBlock *newBlock = new TextBlock (this, 0);
|
|
newBlock->appendLine (QString());
|
|
|
|
// clean out all cursors and lines, either move them to newBlock or invalidate them, if belonging to a range
|
|
foreach(TextBlock* block, m_blocks)
|
|
block->clearBlockContent (newBlock);
|
|
|
|
// kill all buffer blocks
|
|
qDeleteAll (m_blocks);
|
|
m_blocks.clear ();
|
|
|
|
// insert one block with one empty line
|
|
m_blocks.append (newBlock);
|
|
|
|
// reset lines and last used block
|
|
m_lines = 1;
|
|
m_lastUsedBlock = 0;
|
|
|
|
// reset revision
|
|
m_revision = 0;
|
|
|
|
// reset bom detection
|
|
m_generateByteOrderMark = false;
|
|
|
|
// clear edit history
|
|
m_history.clear ();
|
|
|
|
// we got cleared
|
|
emit cleared ();
|
|
}
|
|
|
|
TextLine TextBuffer::line (int line) const
|
|
{
|
|
// get block, this will assert on invalid line
|
|
int blockIndex = blockForLine (line);
|
|
|
|
// get line
|
|
return m_blocks.at(blockIndex)->line (line);
|
|
}
|
|
|
|
QString TextBuffer::text () const
|
|
{
|
|
QString text;
|
|
|
|
// combine all blocks
|
|
foreach(TextBlock* block, m_blocks)
|
|
block->text (text);
|
|
|
|
// return generated string
|
|
return text;
|
|
}
|
|
|
|
bool TextBuffer::startEditing ()
|
|
{
|
|
// increment transaction counter
|
|
++m_editingTransactions;
|
|
|
|
// if not first running transaction, do nothing
|
|
if (m_editingTransactions > 1)
|
|
return false;
|
|
|
|
// reset information about edit...
|
|
m_editingLastRevision = m_revision;
|
|
m_editingLastLines = m_lines;
|
|
m_editingMinimalLineChanged = -1;
|
|
m_editingMaximalLineChanged = -1;
|
|
|
|
// transaction has started
|
|
emit editingStarted ();
|
|
|
|
// first transaction started
|
|
return true;
|
|
}
|
|
|
|
bool TextBuffer::finishEditing ()
|
|
{
|
|
// only allowed if still transactions running
|
|
Q_ASSERT (m_editingTransactions > 0);
|
|
|
|
// decrement counter
|
|
--m_editingTransactions;
|
|
|
|
// if not last running transaction, do nothing
|
|
if (m_editingTransactions > 0)
|
|
return false;
|
|
|
|
// assert that if buffer changed, the line ranges are set and valid!
|
|
Q_ASSERT (!editingChangedBuffer() || (m_editingMinimalLineChanged != -1 && m_editingMaximalLineChanged != -1));
|
|
Q_ASSERT (!editingChangedBuffer() || (m_editingMinimalLineChanged <= m_editingMaximalLineChanged));
|
|
Q_ASSERT (!editingChangedBuffer() || (m_editingMinimalLineChanged >= 0 && m_editingMinimalLineChanged < m_lines));
|
|
Q_ASSERT (!editingChangedBuffer() || (m_editingMaximalLineChanged >= 0 && m_editingMaximalLineChanged < m_lines));
|
|
|
|
// transaction has finished
|
|
emit editingFinished ();
|
|
|
|
// last transaction finished
|
|
return true;
|
|
}
|
|
|
|
void TextBuffer::wrapLine (const KTextEditor::Cursor &position)
|
|
{
|
|
// debug output for REAL low-level debugging
|
|
EDIT_DEBUG << "wrapLine" << position;
|
|
|
|
// only allowed if editing transaction running
|
|
Q_ASSERT (m_editingTransactions > 0);
|
|
|
|
// get block, this will assert on invalid line
|
|
int blockIndex = blockForLine (position.line());
|
|
|
|
/**
|
|
* let the block handle the wrapLine
|
|
* this can only lead to one more line in this block
|
|
* no other blocks will change
|
|
* this call will trigger fixStartLines
|
|
*/
|
|
++m_lines; // first alter the line counter, as functions called will need the valid one
|
|
m_blocks.at(blockIndex)->wrapLine (position, blockIndex);
|
|
|
|
// remember changes
|
|
++m_revision;
|
|
|
|
// update changed line interval
|
|
if (position.line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1)
|
|
m_editingMinimalLineChanged = position.line();
|
|
|
|
if (position.line() <= m_editingMaximalLineChanged)
|
|
++m_editingMaximalLineChanged;
|
|
else
|
|
m_editingMaximalLineChanged = position.line() + 1;
|
|
|
|
// balance the changed block if needed
|
|
balanceBlock (blockIndex);
|
|
|
|
// emit signal about done change
|
|
emit lineWrapped (position);
|
|
}
|
|
|
|
void TextBuffer::unwrapLine (int line)
|
|
{
|
|
// debug output for REAL low-level debugging
|
|
EDIT_DEBUG << "unwrapLine" << line;
|
|
|
|
// only allowed if editing transaction running
|
|
Q_ASSERT (m_editingTransactions > 0);
|
|
|
|
// line 0 can't be unwrapped
|
|
Q_ASSERT (line > 0);
|
|
|
|
// get block, this will assert on invalid line
|
|
int blockIndex = blockForLine (line);
|
|
|
|
// is this the first line in the block?
|
|
bool firstLineInBlock = (line == m_blocks.at(blockIndex)->startLine());
|
|
|
|
/**
|
|
* let the block handle the unwrapLine
|
|
* this can either lead to one line less in this block or the previous one
|
|
* the previous one could even end up with zero lines
|
|
* this call will trigger fixStartLines
|
|
*/
|
|
m_blocks.at(blockIndex)->unwrapLine (line, (blockIndex > 0) ? m_blocks.at(blockIndex-1) : 0, firstLineInBlock ? (blockIndex - 1) : blockIndex);
|
|
--m_lines;
|
|
|
|
// decrement index for later fixup, if we modified the block in front of the found one
|
|
if (firstLineInBlock)
|
|
--blockIndex;
|
|
|
|
// remember changes
|
|
++m_revision;
|
|
|
|
// update changed line interval
|
|
if ((line - 1) < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1)
|
|
m_editingMinimalLineChanged = line - 1;
|
|
|
|
if (line <= m_editingMaximalLineChanged)
|
|
--m_editingMaximalLineChanged;
|
|
else
|
|
m_editingMaximalLineChanged = line -1;
|
|
|
|
// balance the changed block if needed
|
|
balanceBlock (blockIndex);
|
|
|
|
// emit signal about done change
|
|
emit lineUnwrapped (line);
|
|
}
|
|
|
|
void TextBuffer::insertText (const KTextEditor::Cursor &position, const QString &text)
|
|
{
|
|
// debug output for REAL low-level debugging
|
|
EDIT_DEBUG << "insertText" << position << text;
|
|
|
|
// only allowed if editing transaction running
|
|
Q_ASSERT (m_editingTransactions > 0);
|
|
|
|
// skip work, if no text to insert
|
|
if (text.isEmpty())
|
|
return;
|
|
|
|
// get block, this will assert on invalid line
|
|
int blockIndex = blockForLine (position.line());
|
|
|
|
// let the block handle the insertText
|
|
m_blocks.at(blockIndex)->insertText (position, text);
|
|
|
|
// remember changes
|
|
++m_revision;
|
|
|
|
// update changed line interval
|
|
if (position.line () < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1)
|
|
m_editingMinimalLineChanged = position.line ();
|
|
|
|
if (position.line () > m_editingMaximalLineChanged)
|
|
m_editingMaximalLineChanged = position.line ();
|
|
|
|
// emit signal about done change
|
|
emit textInserted (position, text);
|
|
}
|
|
|
|
void TextBuffer::removeText (const KTextEditor::Range &range)
|
|
{
|
|
// debug output for REAL low-level debugging
|
|
EDIT_DEBUG << "removeText" << range;
|
|
|
|
// only allowed if editing transaction running
|
|
Q_ASSERT (m_editingTransactions > 0);
|
|
|
|
// only ranges on one line are supported
|
|
Q_ASSERT (range.start().line() == range.end().line());
|
|
|
|
// start column <= end column and >= 0
|
|
Q_ASSERT (range.start().column() <= range.end().column());
|
|
Q_ASSERT (range.start().column() >= 0);
|
|
|
|
// skip work, if no text to remove
|
|
if (range.isEmpty())
|
|
return;
|
|
|
|
// get block, this will assert on invalid line
|
|
int blockIndex = blockForLine (range.start().line());
|
|
|
|
// let the block handle the removeText, retrieve removed text
|
|
QString text;
|
|
m_blocks.at(blockIndex)->removeText (range, text);
|
|
|
|
// remember changes
|
|
++m_revision;
|
|
|
|
// update changed line interval
|
|
if (range.start().line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1)
|
|
m_editingMinimalLineChanged = range.start().line();
|
|
|
|
if (range.start().line() > m_editingMaximalLineChanged)
|
|
m_editingMaximalLineChanged = range.start().line();
|
|
|
|
// emit signal about done change
|
|
emit textRemoved (range, text);
|
|
}
|
|
|
|
int TextBuffer::blockForLine (int line) const
|
|
{
|
|
// only allow valid lines
|
|
if ((line < 0) || (line >= lines()))
|
|
qFatal ("out of range line requested in text buffer (%d out of [0, %d[)", line, lines());
|
|
|
|
// we need blocks and last used block should not be negative
|
|
Q_ASSERT (!m_blocks.isEmpty());
|
|
Q_ASSERT (m_lastUsedBlock >= 0);
|
|
|
|
/**
|
|
* shortcut: try last block first
|
|
*/
|
|
if (m_lastUsedBlock < m_blocks.size()) {
|
|
/**
|
|
* check if block matches
|
|
* if yes, just return again this block
|
|
*/
|
|
TextBlock* block = m_blocks[m_lastUsedBlock];
|
|
const int start = block->startLine();
|
|
const int lines = block->lines ();
|
|
if (start <= line && line < (start + lines))
|
|
return m_lastUsedBlock;
|
|
}
|
|
|
|
/**
|
|
* search for right block
|
|
* use binary search
|
|
* if we leave this loop not by returning the found element we have an error
|
|
*/
|
|
int blockStart = 0;
|
|
int blockEnd = m_blocks.size() - 1;
|
|
while (blockEnd >= blockStart) {
|
|
// get middle and ensure it is OK
|
|
int middle = blockStart + ((blockEnd - blockStart) / 2);
|
|
Q_ASSERT (middle >= 0);
|
|
Q_ASSERT (middle < m_blocks.size());
|
|
|
|
// facts bout this block
|
|
TextBlock* block = m_blocks[middle];
|
|
const int start = block->startLine();
|
|
const int lines = block->lines ();
|
|
|
|
// right block found, remember it and return it
|
|
if (start <= line && line < (start + lines)) {
|
|
m_lastUsedBlock = middle;
|
|
return middle;
|
|
}
|
|
|
|
// half our stuff ;)
|
|
if (line < start)
|
|
blockEnd = middle - 1;
|
|
else
|
|
blockStart = middle + 1;
|
|
}
|
|
|
|
// we should always find a block
|
|
qFatal ("line requested in text buffer (%d out of [0, %d[), no block found", line, lines());
|
|
return -1;
|
|
}
|
|
|
|
void TextBuffer::fixStartLines (int startBlock)
|
|
{
|
|
// only allow valid start block
|
|
Q_ASSERT (startBlock >= 0);
|
|
Q_ASSERT (startBlock < m_blocks.size());
|
|
|
|
// new start line for next block
|
|
TextBlock* block = m_blocks.at(startBlock);
|
|
int newStartLine = block->startLine () + block->lines ();
|
|
|
|
// fixup block
|
|
for (int index = startBlock + 1; index < m_blocks.size(); ++index) {
|
|
// set new start line
|
|
block = m_blocks.at(index);
|
|
block->setStartLine (newStartLine);
|
|
|
|
// calculate next start line
|
|
newStartLine += block->lines ();
|
|
}
|
|
}
|
|
|
|
void TextBuffer::balanceBlock (int index)
|
|
{
|
|
/**
|
|
* two cases, too big or too small block
|
|
*/
|
|
TextBlock *blockToBalance = m_blocks.at(index);
|
|
|
|
// first case, too big one, split it
|
|
if (blockToBalance->lines () >= 2 * m_blockSize) {
|
|
// half the block
|
|
int halfSize = blockToBalance->lines () / 2;
|
|
|
|
// create and insert new block behind current one, already set right start line
|
|
TextBlock *newBlock = blockToBalance->splitBlock (halfSize);
|
|
Q_ASSERT (newBlock);
|
|
m_blocks.insert (m_blocks.begin() + index + 1, newBlock);
|
|
|
|
// split is done
|
|
return;
|
|
}
|
|
|
|
// second case: possibly too small block
|
|
|
|
// if only one block, no chance to unite
|
|
// same if this is first block, we always append to previous one
|
|
if (index == 0)
|
|
return;
|
|
|
|
// block still large enough, do nothing
|
|
if (2 * blockToBalance->lines () > m_blockSize)
|
|
return;
|
|
|
|
// unite small block with predecessor
|
|
TextBlock *targetBlock = m_blocks.at(index-1);
|
|
|
|
// merge block
|
|
blockToBalance->mergeBlock (targetBlock);
|
|
|
|
// delete old block
|
|
delete blockToBalance;
|
|
m_blocks.erase (m_blocks.begin() + index);
|
|
}
|
|
|
|
void TextBuffer::debugPrint (const QString &title) const
|
|
{
|
|
// print header with title
|
|
printf ("%s (lines: %d bs: %d)\n", qPrintable (title), m_lines, m_blockSize);
|
|
|
|
// print all blocks
|
|
for (int i = 0; i < m_blocks.size(); ++i)
|
|
m_blocks.at(i)->debugPrint (i);
|
|
}
|
|
|
|
bool TextBuffer::load (const QString &filename, bool &encodingErrors, bool &tooLongLinesWrapped, bool enforceTextCodec)
|
|
{
|
|
// fallback codec must exist
|
|
Q_ASSERT (m_fallbackTextCodec);
|
|
|
|
// codec must be set!
|
|
Q_ASSERT (m_textCodec);
|
|
|
|
/**
|
|
* first: clear buffer in any case!
|
|
*/
|
|
clear ();
|
|
|
|
/**
|
|
* construct the file loader for the given file, with correct prober type
|
|
*/
|
|
Kate::TextLoader file (filename);
|
|
|
|
/**
|
|
* triple play, maximal three loading rounds
|
|
* 0) use the given encoding, be done, if no encoding errors happen
|
|
* 1) use BOM to decided if unicode or if that fails, use encoding prober, if no encoding errors happen, be done
|
|
* 2) use fallback encoding, be done, if no encoding errors happen
|
|
* 3) use again given encoding, be done in any case
|
|
*/
|
|
for (int i = 0; i < (enforceTextCodec ? 1 : 4); ++i) {
|
|
/**
|
|
* kill all blocks beside first one
|
|
*/
|
|
for (int b = 1; b < m_blocks.size(); ++b) {
|
|
TextBlock* block = m_blocks.at(b);
|
|
block->clearLines ();
|
|
delete block;
|
|
}
|
|
m_blocks.resize (1);
|
|
|
|
/**
|
|
* remove lines in first block
|
|
*/
|
|
m_blocks.last()->clearLines ();
|
|
m_lines = 0;
|
|
|
|
/**
|
|
* try to open file, with given encoding
|
|
* in round 0 + 3 use the given encoding from user
|
|
* in round 1 use 0, to trigger detection
|
|
* in round 2 use fallback
|
|
*/
|
|
QTextCodec *codec = m_textCodec;
|
|
if (i == 1)
|
|
codec = 0;
|
|
else if (i == 2)
|
|
codec = m_fallbackTextCodec;
|
|
|
|
if (!file.open (codec)) {
|
|
// create one dummy textline, in any case
|
|
m_blocks.last()->appendLine (QString());
|
|
m_lines++;
|
|
return false;
|
|
}
|
|
|
|
// read in all lines...
|
|
encodingErrors = false;
|
|
while ( !file.eof() )
|
|
{
|
|
// read line
|
|
int offset = 0, length = 0;
|
|
bool currentError = !file.readLine (offset, length);
|
|
encodingErrors = encodingErrors || currentError;
|
|
|
|
// bail out on encoding error, if not last round!
|
|
if (encodingErrors && i < (enforceTextCodec ? 0 : 3)) {
|
|
kDebug (13020) << "Failed try to load file" << filename << "with codec" <<
|
|
(file.textCodec() ? file.textCodec()->name() : "(null)");
|
|
break;
|
|
}
|
|
|
|
// get unicode data for this line
|
|
const QChar *unicodeData = file.unicode () + offset;
|
|
|
|
/**
|
|
* split lines, if too large
|
|
*/
|
|
do {
|
|
/**
|
|
* calculate line length
|
|
*/
|
|
int lineLength = length;
|
|
if ((m_lineLengthLimit > 0) && (lineLength > m_lineLengthLimit)) {
|
|
/**
|
|
* search for place to wrap
|
|
*/
|
|
int spacePosition = m_lineLengthLimit-1;
|
|
for (int testPosition = m_lineLengthLimit-1; (testPosition >= 0) && (testPosition >= (m_lineLengthLimit - (m_lineLengthLimit/10))); --testPosition) {
|
|
/**
|
|
* wrap place found?
|
|
*/
|
|
if (unicodeData[testPosition].isSpace() || unicodeData[testPosition].isPunct()) {
|
|
spacePosition = testPosition;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* wrap the line
|
|
*/
|
|
lineLength = spacePosition+1;
|
|
length -= lineLength;
|
|
tooLongLinesWrapped = true;
|
|
} else {
|
|
/**
|
|
* be done after this round
|
|
*/
|
|
length = 0;
|
|
}
|
|
|
|
/**
|
|
* construct new text line with content from file
|
|
* move data pointer
|
|
*/
|
|
QString textLine (unicodeData, lineLength);
|
|
unicodeData += lineLength;
|
|
|
|
/**
|
|
* ensure blocks aren't too large
|
|
*/
|
|
if (m_blocks.last()->lines() >= m_blockSize)
|
|
m_blocks.append (new TextBlock (this, m_blocks.last()->startLine() + m_blocks.last()->lines()));
|
|
|
|
/**
|
|
* append line to last block
|
|
*/
|
|
m_blocks.last()->appendLine (textLine);
|
|
++m_lines;
|
|
} while (length > 0);
|
|
}
|
|
|
|
// if no encoding error, break out of reading loop
|
|
if (!encodingErrors) {
|
|
// remember used codec, might change bom setting
|
|
setTextCodec (file.textCodec ());
|
|
break;
|
|
}
|
|
}
|
|
|
|
// save sha1sum of file on disk
|
|
setDigest (file.digest ());
|
|
|
|
// remember if BOM was found
|
|
if (file.byteOrderMarkFound ())
|
|
setGenerateByteOrderMark (true);
|
|
|
|
// remember eol mode, if any found in file
|
|
if (file.eol() != eolUnknown)
|
|
setEndOfLineMode (file.eol());
|
|
|
|
// assert that one line is there!
|
|
Q_ASSERT (m_lines > 0);
|
|
|
|
// report CODEC + ERRORS
|
|
kDebug (13020) << "Loaded file " << filename << "with codec" << file.textCodec()->name()
|
|
<< (encodingErrors ? "with" : "without") << "encoding errors";
|
|
|
|
// report BOM
|
|
kDebug (13020) << (file.byteOrderMarkFound () ? "Found" : "Didn't find") << "byte order mark";
|
|
|
|
// emit success
|
|
emit loaded (filename, encodingErrors);
|
|
|
|
// file loading worked, modulo encoding problems
|
|
return true;
|
|
}
|
|
|
|
const QByteArray &TextBuffer::digest () const
|
|
{
|
|
return m_digest;
|
|
}
|
|
|
|
void TextBuffer::setDigest (const QByteArray & sha1sum)
|
|
{
|
|
m_digest = sha1sum;
|
|
}
|
|
|
|
void TextBuffer::setTextCodec (QTextCodec *codec)
|
|
{
|
|
m_textCodec = codec;
|
|
|
|
// enforce bom for some encodings
|
|
int mib = m_textCodec->mibEnum ();
|
|
if (mib == 1013 || mib == 1014 || mib == 1015) // utf16
|
|
setGenerateByteOrderMark (true);
|
|
if (mib == 1017 || mib == 1018 || mib == 1019) // utf32
|
|
setGenerateByteOrderMark (true);
|
|
}
|
|
|
|
bool TextBuffer::save (const QString &filename)
|
|
{
|
|
// codec must be set!
|
|
Q_ASSERT (m_textCodec);
|
|
|
|
/**
|
|
* use KSaveFile for save write + rename
|
|
*/
|
|
KSaveFile saveFile (filename);
|
|
|
|
/**
|
|
* allow fallback if directory not writable
|
|
* fixes bug 312415
|
|
*/
|
|
saveFile.setDirectWriteFallback (true);
|
|
|
|
/**
|
|
* try to open or fail
|
|
*/
|
|
if (!saveFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* construct stream
|
|
*/
|
|
QTextStream stream (&saveFile);
|
|
|
|
// set the correct codec
|
|
stream.setCodec (m_textCodec);
|
|
|
|
// generate byte order mark?
|
|
stream.setGenerateByteOrderMark (generateByteOrderMark());
|
|
|
|
// our loved eol string ;)
|
|
QString eol = "\n"; //m_doc->config()->eolString ();
|
|
if (endOfLineMode() == eolDos)
|
|
eol = QString ("\r\n");
|
|
else if (endOfLineMode() == eolMac)
|
|
eol = QString ("\r");
|
|
|
|
// just dump the lines out ;)
|
|
for (int i = 0; i < m_lines; ++i)
|
|
{
|
|
// get line to save
|
|
Kate::TextLine textline = line (i);
|
|
|
|
stream << textline->text();
|
|
|
|
// append correct end of line string
|
|
if ((i+1) < m_lines)
|
|
stream << eol;
|
|
}
|
|
|
|
if (m_newLineAtEof) {
|
|
Q_ASSERT(m_lines > 0); // see .h file
|
|
const Kate::TextLine lastLine = line (m_lines - 1);
|
|
const int firstChar = lastLine->firstChar();
|
|
if (firstChar > -1 || lastLine->length() > 0) {
|
|
stream << eol;
|
|
}
|
|
}
|
|
|
|
// flush stream
|
|
stream.flush ();
|
|
|
|
// flush file
|
|
if (!saveFile.flush())
|
|
return false;
|
|
|
|
// ensure that the file is written to disk
|
|
#ifdef HAVE_FDATASYNC
|
|
fdatasync (saveFile.handle());
|
|
#else
|
|
fsync (saveFile.handle());
|
|
#endif
|
|
|
|
// did save work?
|
|
// only finalize if stream status == OK
|
|
bool ok = (stream.status() == QTextStream::Ok) && saveFile.finalize();
|
|
|
|
// remember this revision as last saved if we had success!
|
|
if (ok)
|
|
m_history.setLastSavedRevision ();
|
|
|
|
// report CODEC + ERRORS
|
|
kDebug (13020) << "Saved file " << filename << "with codec" << m_textCodec->name()
|
|
<< (ok ? "without" : "with") << "errors";
|
|
|
|
if (ok)
|
|
markModifiedLinesAsSaved();
|
|
|
|
// emit signal on success
|
|
if (ok)
|
|
emit saved (filename);
|
|
|
|
// return success or not
|
|
return ok;
|
|
}
|
|
|
|
void TextBuffer::notifyAboutRangeChange (KTextEditor::View *view, int startLine, int endLine, bool rangeWithAttribute)
|
|
{
|
|
/**
|
|
* ignore calls if no document is around
|
|
*/
|
|
if (!m_document)
|
|
return;
|
|
|
|
/**
|
|
* update all views, this IS ugly and could be a signal, but I profiled and a signal is TOO slow, really
|
|
* just create 20k ranges in a go and you wait seconds on a decent machine
|
|
*/
|
|
const QList<KTextEditor::View *> &views = m_document->views ();
|
|
foreach(KTextEditor::View* curView, views) {
|
|
// filter wrong views
|
|
if (view && view != curView)
|
|
continue;
|
|
|
|
// notify view, it is really a kate view
|
|
static_cast<KateView *> (curView)->notifyAboutRangeChange (startLine, endLine, rangeWithAttribute);
|
|
}
|
|
}
|
|
|
|
void TextBuffer::markModifiedLinesAsSaved()
|
|
{
|
|
foreach(TextBlock* block, m_blocks)
|
|
block->markModifiedLinesAsSaved ();
|
|
}
|
|
|
|
QList<TextRange *> TextBuffer::rangesForLine (int line, KTextEditor::View *view, bool rangesWithAttributeOnly) const
|
|
{
|
|
// get block, this will assert on invalid line
|
|
const int blockIndex = blockForLine (line);
|
|
|
|
// get the ranges of the right block
|
|
QList<TextRange *> rightRanges;
|
|
foreach (const QSet<TextRange *> &ranges, m_blocks.at(blockIndex)->rangesForLine (line)) {
|
|
foreach (TextRange * const range, ranges) {
|
|
/**
|
|
* we want only ranges with attributes, but this one has none
|
|
*/
|
|
if (rangesWithAttributeOnly && !range->hasAttribute())
|
|
continue;
|
|
|
|
/**
|
|
* we want ranges for no view, but this one's attribute is only valid for views
|
|
*/
|
|
if (!view && range->attributeOnlyForViews())
|
|
continue;
|
|
|
|
/**
|
|
* the range's attribute is not valid for this view
|
|
*/
|
|
if (range->view() && range->view() != view)
|
|
continue;
|
|
|
|
/**
|
|
* if line is in the range, ok
|
|
*/
|
|
if (range->startInternal().lineInternal() <= line && line <= range->endInternal().lineInternal())
|
|
rightRanges.append (range);
|
|
}
|
|
}
|
|
|
|
// return right ranges
|
|
return rightRanges;
|
|
}
|
|
|
|
}
|