kde-workspace/kate/part/document/katedocument.cpp
Ivailo Monev fe52270c14 kate: explicitly convert to range and cursor objects
lots of opportunities for optimizations by caching the converted objects

Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2022-05-28 11:08:27 +03:00

5565 lines
151 KiB
C++

/* This file is part of the KDE libraries
Copyright (C) 2001-2004 Christoph Cullmann <cullmann@kde.org>
Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org>
Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de>
Copyright (C) 2006 Hamish Rodda <rodda@kde.org>
Copyright (C) 2007 Mirko Stocker <me@misto.ch>
Copyright (C) 2009-2010 Michel Ludwig <michel.ludwig@kdemail.net>
Copyright (C) 2013 Gerald Senarclens de Grancy <oss@senarclens.eu>
Copyright (C) 2013 Andrey Matveyakin <a.matveyakin@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
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 02111-13020, USA.
*/
//BEGIN includes
#include "katedocument.h"
#include "moc_katedocument.cpp"
#include "kateglobal.h"
#include "katedialogs.h"
#include "katehighlight.h"
#include "kateview.h"
#include "kateautoindent.h"
#include "katetextline.h"
#include "katedocumenthelpers.h"
#include "katehighlighthelpers.h"
#include "kateprinter.h"
#include "katerenderer.h"
#include "kateregexp.h"
#include "kateplaintextsearch.h"
#include "kateregexpsearch.h"
#include "kateconfig.h"
#include "katemodemanager.h"
#include "kateschema.h"
#include "katebuffer.h"
#include "kateundomanager.h"
#include "katepartpluginmanager.h"
#include "spellcheck/prefixstore.h"
#include "spellcheck/ontheflycheck.h"
#include "spellcheck/spellcheck.h"
#include "kateswapfile.h"
#include "documentcursor.h"
#include <ktexteditor/attribute.h>
#include <ktexteditor/plugin.h>
#include <ktexteditor/loadsavefiltercheckplugin.h>
#include <kio/job.h>
#include <kio/jobuidelegate.h>
#include <kio/netaccess.h>
#include <kfileitem.h>
#include <klocale.h>
#include <kglobal.h>
#include <kmenu.h>
#include <kconfig.h>
#include <kfiledialog.h>
#include <kmessagebox.h>
#include <kstandardaction.h>
#include <kxmlguifactory.h>
#include <kdebug.h>
#include <kglobalsettings.h>
#include <kdirwatch.h>
#include <kencodingfiledialog.h>
#include <kstandarddirs.h>
#include <kstringhandler.h>
#include <kservicetypetrader.h>
#include <QtDBus/QtDBus>
#include <QtGui/QApplication>
#include <QtCore/QTimer>
#include <QtCore/QFile>
#include <QtGui/QClipboard>
#include <QtCore/QTextStream>
#include <QtCore/QTextCodec>
#include <QtCore/QMap>
#include <QCryptographicHash>
//END includes
#if 0
#define EDIT_DEBUG kDebug()
#else
#define EDIT_DEBUG if (0) kDebug()
#endif
static int dummy = 0;
inline bool isStartBracket( const QChar& c ) { return c == '{' || c == '[' || c == '('; }
inline bool isEndBracket ( const QChar& c ) { return c == '}' || c == ']' || c == ')'; }
inline bool isBracket ( const QChar& c ) { return isStartBracket( c ) || isEndBracket( c ); }
//BEGIN d'tor, c'tor
//
// KateDocument Constructor
//
KateDocument::KateDocument ( bool bSingleViewMode, bool bBrowserView,
bool bReadOnly, QWidget *parentWidget,
QObject *parent)
: KTextEditor::Document (parent),
m_bSingleViewMode(bSingleViewMode),
m_bBrowserView(bBrowserView),
m_bReadOnly(bReadOnly),
m_activeView(0),
editSessionNumber(0),
editIsRunning(false),
m_undoMergeAllEdits(false),
m_undoManager(new KateUndoManager(this)),
m_editableMarks(markType01),
m_annotationModel(0),
m_isasking(0),
m_buffer(new KateBuffer(this)),
m_indenter(new KateAutoIndent(this)),
m_hlSetByUser(false),
m_bomSetByUser(false),
m_indenterSetByUser(false),
m_userSetEncodingForNextReload(false),
m_modOnHd(false),
m_modOnHdReason(OnDiskUnmodified),
m_docName("need init"),
m_docNameNumber(0),
m_fileTypeSetByUser(false),
m_reloading(false),
m_config(new KateDocumentConfig(this)),
m_fileChangedDialogsActivated(false),
m_onTheFlyChecker(0),
m_documentState (DocumentIdle),
m_readWriteStateBeforeLoading (false),
m_isUntitled(true)
{
setComponentData ( KateGlobal::self()->componentData () );
/**
* avoid spamming plasma and other window managers with progress dialogs
* we show such stuff inline in the views!
*/
setProgressInfoEnabled (false);
QString pathName ("/Kate/Document/%1");
pathName = pathName.arg (++dummy);
// my dbus object
QDBusConnection::sessionBus().registerObject (pathName, this, QDBusConnection::ExportAdaptors | QDBusConnection::ExportScriptableSlots);
// register doc at factory
KateGlobal::self()->registerDocument(this);
// normal hl
m_buffer->setHighlight (0);
// swap file
m_swapfile = new Kate::SwapFile(this);
new KateBrowserExtension( this ); // deleted by QObject memory management
// important, fill in the config into the indenter we use...
m_indenter->updateConfig ();
// some nice signals from the buffer
connect(m_buffer, SIGNAL(tagLines(int,int)), this, SLOT(tagLines(int,int)));
// if the user changes the highlight with the dialog, notify the doc
connect(KateHlManager::self(),SIGNAL(changed()),SLOT(internalHlChanged()));
// signals for mod on hd
connect( KateGlobal::self()->dirWatch(), SIGNAL(dirty(QString)),
this, SLOT(slotModOnHdDirty(QString)) );
connect( KateGlobal::self()->dirWatch(), SIGNAL(created(QString)),
this, SLOT(slotModOnHdCreated(QString)) );
connect( KateGlobal::self()->dirWatch(), SIGNAL(deleted(QString)),
this, SLOT(slotModOnHdDeleted(QString)) );
/**
* load handling
* this is needed to ensure we signal the user if a file ist still loading
* and to disallow him to edit in that time
*/
connect (this, SIGNAL(started(KIO::Job*)), this, SLOT(slotStarted(KIO::Job*)));
connect (this, SIGNAL(completed()), this, SLOT(slotCompleted()));
connect (this, SIGNAL(canceled(QString)), this, SLOT(slotCanceled()));
// update doc name
updateDocName ();
// if single view mode, like in the konqui embedding, create a default view ;)
// be lazy, only create it now, if any parentWidget is given, otherwise widget()
// will create it on demand...
if ( m_bSingleViewMode && parentWidget )
{
KTextEditor::View *view = (KTextEditor::View*)createView( parentWidget );
insertChildClient( view );
view->show();
setWidget( view );
}
connect(m_undoManager, SIGNAL(undoChanged()), this, SIGNAL(undoChanged()));
connect(m_undoManager, SIGNAL(undoStart(KTextEditor::Document*)), this, SIGNAL(exclusiveEditStart(KTextEditor::Document*)));
connect(m_undoManager, SIGNAL(undoEnd(KTextEditor::Document*)), this, SIGNAL(exclusiveEditEnd(KTextEditor::Document*)));
connect(m_undoManager, SIGNAL(redoStart(KTextEditor::Document*)), this, SIGNAL(exclusiveEditStart(KTextEditor::Document*)));
connect(m_undoManager, SIGNAL(redoEnd(KTextEditor::Document*)), this, SIGNAL(exclusiveEditEnd(KTextEditor::Document*)));
connect(this,SIGNAL(sigQueryClose(bool*,bool*)),this,SLOT(slotQueryClose_save(bool*,bool*)));
onTheFlySpellCheckingEnabled(config()->onTheFlySpellCheck());
// register document in plugins
KatePartPluginManager::self()->addDocument(this);
}
//
// KateDocument Destructor
//
KateDocument::~KateDocument()
{
/**
* we are about to delete cursors/ranges/...
*/
emit aboutToDeleteMovingInterfaceContent (this);
// kill it early, it has ranges!
delete m_onTheFlyChecker;
m_onTheFlyChecker = NULL;
clearDictionaryRanges();
// Tell the world that we're about to close (== destruct)
// Apps must receive this in a direct signal-slot connection, and prevent
// any further use of interfaces once they return.
emit aboutToClose(this);
// remove file from dirwatch
deactivateDirWatch ();
// thanks for offering, KPart, but we're already self-destructing
setAutoDeleteWidget(false);
setAutoDeletePart(false);
// clean up remaining views
while (!m_views.isEmpty()) {
delete m_views.takeFirst();
}
// de-register from plugin
KatePartPluginManager::self()->removeDocument(this);
// cu marks
for (QHash<int, KTextEditor::Mark*>::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i)
delete i.value();
m_marks.clear();
delete m_config;
KateGlobal::self()->deregisterDocument (this);
}
//END
// on-demand view creation
QWidget *KateDocument::widget()
{
// no singleViewMode -> no widget()...
if (!singleViewMode())
return 0;
// does a widget exist already? use it!
if (KTextEditor::Document::widget())
return KTextEditor::Document::widget();
// create and return one...
KTextEditor::View *view = (KTextEditor::View*)createView(0);
insertChildClient( view );
setWidget( view );
return view;
}
//BEGIN KTextEditor::Document stuff
KTextEditor::View *KateDocument::createView( QWidget *parent )
{
KateView* newView = new KateView( this, parent);
if ( m_fileChangedDialogsActivated )
connect( newView, SIGNAL(focusIn(KTextEditor::View*)), this, SLOT(slotModifiedOnDisk()) );
emit viewCreated (this, newView);
// post existing messages to the new view, if no specific view is given
foreach (KTextEditor::Message *message, m_messageHash.keys()) {
if (!message->view())
newView->postMessage(message, m_messageHash[message]);
}
return newView;
}
const QList<KTextEditor::View*> &KateDocument::views () const
{
return m_textEditViews;
}
KTextEditor::Editor *KateDocument::editor ()
{
return KateGlobal::self();
}
KTextEditor::Range KateDocument::rangeOnLine(KTextEditor::Range range, int line) const
{
int col1 = toVirtualColumn(range.start());
int col2 = toVirtualColumn(range.end());
col1 = fromVirtualColumn(line, col1);
col2 = fromVirtualColumn(line, col2);
return KTextEditor::Range(line, col1, line, col2);
}
//BEGIN KTextEditor::EditInterface stuff
QString KateDocument::text() const
{
return m_buffer->text ();
}
QString KateDocument::text( const KTextEditor::Range& range, bool blockwise ) const
{
if (!range.isValid()) {
kWarning() << "Text requested for invalid range" << range;
return QString();
}
QString s;
if (range.start().line() == range.end().line())
{
if (range.start().column() > range.end().column())
return QString ();
Kate::TextLine textLine = m_buffer->plainLine(range.start().line());
if ( !textLine )
return QString ();
return textLine->string(range.start().column(), range.end().column()-range.start().column());
}
else
{
for (int i = range.start().line(); (i <= range.end().line()) && (i < m_buffer->count()); ++i)
{
Kate::TextLine textLine = m_buffer->plainLine(i);
if ( !blockwise )
{
if (i == range.start().line())
s.append (textLine->string(range.start().column(), textLine->length()-range.start().column()));
else if (i == range.end().line())
s.append (textLine->string(0, range.end().column()));
else
s.append (textLine->string());
}
else
{
KTextEditor::Range subRange = rangeOnLine(range, i);
s.append(textLine->string(subRange.start().column(), subRange.columnWidth()));
}
if ( i < range.end().line() )
s.append(QChar::fromAscii('\n'));
}
}
return s;
}
QChar KateDocument::character( const KTextEditor::Cursor & position ) const
{
Kate::TextLine textLine = m_buffer->plainLine(position.line());
if ( !textLine )
return QChar();
return textLine->at(position.column());
}
QStringList KateDocument::textLines( const KTextEditor::Range & range, bool blockwise ) const
{
QStringList ret;
if (!range.isValid()) {
kWarning() << "Text requested for invalid range" << range;
return ret;
}
if ( blockwise && (range.start().column() > range.end().column()) )
return ret;
if (range.start().line() == range.end().line())
{
Q_ASSERT(range.start() <= range.end());
Kate::TextLine textLine = m_buffer->plainLine(range.start().line());
if ( !textLine )
return ret;
ret << textLine->string(range.start().column(), range.end().column() - range.start().column());
}
else
{
for (int i = range.start().line(); (i <= range.end().line()) && (i < m_buffer->count()); ++i)
{
Kate::TextLine textLine = m_buffer->plainLine(i);
if ( !blockwise )
{
if (i == range.start().line())
ret << textLine->string(range.start().column(), textLine->length() - range.start().column());
else if (i == range.end().line())
ret << textLine->string(0, range.end().column());
else
ret << textLine->string();
}
else
{
KTextEditor::Range subRange = rangeOnLine(range, i);
ret << textLine->string(subRange.start().column(), subRange.columnWidth());
}
}
}
return ret;
}
QString KateDocument::line( int line ) const
{
Kate::TextLine l = m_buffer->plainLine(line);
if (!l)
return QString();
return l->string();
}
bool KateDocument::setText(const QString &s)
{
if (!isReadWrite())
return false;
QList<KTextEditor::Mark> msave;
foreach (KTextEditor::Mark* mark, m_marks)
msave.append(*mark);
editStart ();
// delete the text
clear();
// insert the new text
insertText (KTextEditor::Cursor(), s);
editEnd ();
foreach (const KTextEditor::Mark& mark, msave)
setMark (mark.line, mark.type);
return true;
}
bool KateDocument::setText( const QStringList & text )
{
if (!isReadWrite())
return false;
QList<KTextEditor::Mark> msave;
foreach (KTextEditor::Mark* mark, m_marks)
msave.append(*mark);
editStart ();
// delete the text
clear();
// insert the new text
insertText (KTextEditor::Cursor::start(), text);
editEnd ();
foreach (const KTextEditor::Mark& mark, msave)
setMark (mark.line, mark.type);
return true;
}
bool KateDocument::clear()
{
if (!isReadWrite())
return false;
foreach (KateView *view, m_views) {
view->clear();
view->tagAll();
view->update();
}
clearMarks ();
emit aboutToInvalidateMovingInterfaceContent(this);
m_buffer->invalidateRanges();
emit aboutToRemoveText(documentRange());
return editRemoveLines(0, lastLine());
}
bool KateDocument::insertText( const KTextEditor::Cursor& position, const QString& text, bool block )
{
if (!isReadWrite())
return false;
if (text.isEmpty())
return true;
editStart();
int currentLine = position.line();
int currentLineStart = 0;
int totalLength = text.length();
int insertColumn = position.column();
if (position.line() > lines())
{
int line = lines();
while (line != position.line() + totalLength + 1)
{
editInsertLine(line,QString());
line++;
}
}
bool replacetabs = ( config()->replaceTabsDyn() );
int tabWidth = config()->tabWidth();
static const QChar newLineChar('\n');
static const QChar tabChar('\t');
static const QChar spaceChar(' ');
int insertColumnExpanded = insertColumn;
Kate::TextLine l = plainKateTextLine( currentLine );
if (l)
insertColumnExpanded = l->toVirtualColumn( insertColumn, tabWidth );
int positionColumnExpanded = insertColumnExpanded;
int pos = 0;
for (; pos < totalLength; pos++)
{
const QChar& ch = text.at(pos);
if (ch == newLineChar)
{
// Only perform the text insert if there is text to insert
if (currentLineStart < pos)
editInsertText(currentLine, insertColumn, text.mid(currentLineStart, pos - currentLineStart));
if ( !block )
{
editWrapLine(currentLine, insertColumn + pos - currentLineStart);
insertColumn = 0;
}
currentLine++;
l = plainKateTextLine( currentLine );
if ( block )
{
if ( currentLine == lastLine() + 1 )
editInsertLine(currentLine, QString());
insertColumn = positionColumnExpanded;
if (l)
insertColumn = l->fromVirtualColumn( insertColumn, tabWidth );
}
currentLineStart = pos + 1;
if (l)
insertColumnExpanded = l->toVirtualColumn( insertColumn, tabWidth );
}
else
{
if ( replacetabs && ch == tabChar )
{
int spacesRequired = tabWidth - ( (insertColumnExpanded + pos - currentLineStart) % tabWidth );
editInsertText(currentLine, insertColumn, text.mid(currentLineStart, pos - currentLineStart) + QString(spacesRequired, spaceChar));
insertColumn += pos - currentLineStart + spacesRequired;
currentLineStart = pos + 1;
if (l)
insertColumnExpanded = l->toVirtualColumn( insertColumn, tabWidth );
}
}
}
// Only perform the text insert if there is text to insert
if (currentLineStart < pos)
editInsertText(currentLine, insertColumn, text.mid(currentLineStart, pos - currentLineStart));
editEnd();
return true;
}
bool KateDocument::insertText( const KTextEditor::Cursor & position, const QStringList & textLines, bool block )
{
if (!isReadWrite())
return false;
// just reuse normal function
return insertText (position, textLines.join ("\n"), block);
}
bool KateDocument::removeText ( const KTextEditor::Range &_range, bool block )
{
KTextEditor::Range range = _range;
if (!isReadWrite())
return false;
// Should now be impossible to trigger with the new Range class
Q_ASSERT( range.start().line() <= range.end().line() );
if ( range.start().line() > lastLine() )
return false;
if (!block)
emit aboutToRemoveText(range);
editStart();
if ( !block )
{
if ( range.end().line() > lastLine() )
{
range.end().setPosition(lastLine()+1, 0);
}
if (range.onSingleLine())
{
editRemoveText(range.start().line(), range.start().column(), range.columnWidth());
}
else
{
int from = range.start().line();
int to = range.end().line();
// remove last line
if (to <= lastLine())
editRemoveText(to, 0, range.end().column());
// editRemoveLines() will be called on first line (to remove bookmark)
if (range.start().column() == 0 && from > 0)
--from;
// remove middle lines
editRemoveLines(from+1, to-1);
// remove first line if not already removed by editRemoveLines()
if (range.start().column() > 0 || range.start().line() == 0) {
editRemoveText(from, range.start().column(), m_buffer->plainLine(from)->length() - range.start().column());
editUnWrapLine(from);
}
}
} // if ( ! block )
else
{
int startLine = qMax(0, range.start().line());
int vc1 = toVirtualColumn(range.start());
int vc2 = toVirtualColumn(range.end());
for (int line = qMin(range.end().line(), lastLine()); line >= startLine; --line) {
int col1 = fromVirtualColumn(line, vc1);
int col2 = fromVirtualColumn(line, vc2);
editRemoveText(line, qMin(col1, col2), qAbs(col2 - col1));
}
}
editEnd ();
return true;
}
bool KateDocument::insertLine( int l, const QString &str )
{
if (!isReadWrite())
return false;
if (l < 0 || l > lines())
return false;
return editInsertLine (l, str);
}
bool KateDocument::insertLines( int line, const QStringList & text )
{
if (!isReadWrite())
return false;
if (line < 0 || line > lines())
return false;
bool success = true;
foreach (const QString &string, text)
success &= editInsertLine (line++, string);
return success;
}
bool KateDocument::removeLine( int line )
{
if (!isReadWrite())
return false;
if (line < 0 || line > lastLine())
return false;
return editRemoveLine (line);
}
int KateDocument::totalCharacters() const
{
int l = 0;
for (int i = 0; i < m_buffer->count(); ++i)
{
Kate::TextLine line = m_buffer->plainLine(i);
if (line)
l += line->length();
}
return l;
}
int KateDocument::lines() const
{
return m_buffer->count();
}
int KateDocument::lineLength ( int line ) const
{
if (line < 0 || line > lastLine())
return -1;
Kate::TextLine l = m_buffer->plainLine(line);
if (!l)
return -1;
return l->length();
}
//END
//BEGIN KTextEditor::EditInterface internal stuff
//
// Starts an edit session with (or without) undo, update of view disabled during session
//
void KateDocument::editStart ()
{
editSessionNumber++;
if (editSessionNumber > 1)
return;
editIsRunning = true;
m_undoManager->editStart();
foreach(KateView *view,m_views)
view->editStart ();
m_buffer->editStart ();
}
//
// End edit session and update Views
//
void KateDocument::editEnd ()
{
if (editSessionNumber == 0) {
Q_ASSERT(0);
return;
}
// wrap the new/changed text, if something really changed!
if (m_buffer->editChanged() && (editSessionNumber == 1))
if (m_undoManager->isActive() && config()->wordWrap())
wrapText (m_buffer->editTagStart(), m_buffer->editTagEnd());
editSessionNumber--;
if (editSessionNumber > 0)
return;
// end buffer edit, will trigger hl update
// this will cause some possible adjustment of tagline start/end
m_buffer->editEnd ();
m_undoManager->editEnd();
// edit end for all views !!!!!!!!!
foreach(KateView *view, m_views)
view->editEnd (m_buffer->editTagStart(), m_buffer->editTagEnd(), m_buffer->editTagFrom());
if (m_buffer->editChanged()) {
setModified(true);
emit textChanged (this);
}
editIsRunning = false;
}
void KateDocument::pushEditState ()
{
editStateStack.push(editSessionNumber);
}
void KateDocument::popEditState ()
{
if (editStateStack.isEmpty()) return;
int count = editStateStack.pop() - editSessionNumber;
while (count < 0) { ++count; editEnd(); }
while (count > 0) { --count; editStart(); }
}
void KateDocument::inputMethodStart()
{
m_undoManager->inputMethodStart();
}
void KateDocument::inputMethodEnd()
{
m_undoManager->inputMethodEnd();
}
bool KateDocument::wrapText(int startLine, int endLine)
{
if (startLine < 0 || endLine < 0)
return false;
if (!isReadWrite())
return false;
int col = config()->wordWrapAt();
if (col == 0)
return false;
editStart ();
for (int line = startLine; (line <= endLine) && (line < lines()); line++)
{
Kate::TextLine l = kateTextLine(line);
if (!l)
break;
//kDebug (13020) << "try wrap line: " << line;
if (l->virtualLength(m_buffer->tabWidth()) > col)
{
Kate::TextLine nextl = kateTextLine(line+1);
//kDebug (13020) << "do wrap line: " << line;
int eolPosition = l->length()-1;
// 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('\t');
if (t.at(z2) == tabChar)
x += m_buffer->tabWidth() - (x % m_buffer->tabWidth());
else
x++;
if (x > col)
break;
}
const int colInChars = qMin (z2, l->length()-1);
int searchStart = colInChars;
// 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--;
// 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;
}
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 (nextl && !nextl->isAutoWrapped())
{
editWrapLine (line, z, true);
editMarkLineAutoWrapped (line+1, true);
endLine++;
}
else
{
if (nextl && (nextl->length() > 0) && !nextl->at(0).isSpace() && ((l->length() < 1) || !l->at(l->length()-1).isSpace()))
editInsertText (line+1, 0, QString (" "));
bool newLineAdded = false;
editWrapLine (line, z, false, &newLineAdded);
editMarkLineAutoWrapped (line+1, true);
endLine++;
}
}
}
editEnd ();
return true;
}
bool KateDocument::editInsertText ( int line, int col, const QString &s )
{
// verbose debug
EDIT_DEBUG << "editInsertText" << line << col << s;
if (line < 0 || col < 0)
return false;
if (!isReadWrite())
return false;
Kate::TextLine l = kateTextLine(line);
if (!l)
return false;
// nothing to do, do nothing!
if (s.isEmpty())
return true;
editStart ();
QString s2 = s;
int col2 = col;
if (col2 > l->length()) {
s2 = QString(col2 - l->length(), QLatin1Char(' ')) + s;
col2 = l->length();
}
m_undoManager->slotTextInserted(line, col2, s2);
// insert text into line
m_buffer->insertText (KTextEditor::Cursor (line, col2), s2);
emit KTextEditor::Document::textInserted(this, KTextEditor::Range(line, col2, line, col2 + s2.length()));
editEnd();
return true;
}
bool KateDocument::editRemoveText ( int line, int col, int len )
{
// verbose debug
EDIT_DEBUG << "editRemoveText" << line << col << len;
if (line < 0 || col < 0 || len < 0)
return false;
if (!isReadWrite())
return false;
Kate::TextLine l = kateTextLine(line);
if (!l)
return false;
// nothing to do, do nothing!
if (len == 0)
return true;
// wrong column
if (col >= l->text().size())
return false;
// don't try to remove what's not there
len = qMin(len, l->text().size() - col);
editStart ();
QString oldText = l->string().mid(col, len);
m_undoManager->slotTextRemoved(line, col, oldText);
// remove text from line
m_buffer->removeText (KTextEditor::Range (KTextEditor::Cursor (line, col), KTextEditor::Cursor (line, col+len)));
emit KTextEditor::Document::textRemoved(this, KTextEditor::Range(line, col, line, col + len));
emit KTextEditor::Document::textRemoved(this, KTextEditor::Range(line, col, line, col + len), oldText);
editEnd ();
return true;
}
bool KateDocument::editMarkLineAutoWrapped ( int line, bool autowrapped )
{
// verbose debug
EDIT_DEBUG << "editMarkLineAutoWrapped" << line << autowrapped;
if (line < 0)
return false;
if (!isReadWrite())
return false;
Kate::TextLine l = kateTextLine(line);
if (!l)
return false;
editStart ();
m_undoManager->slotMarkLineAutoWrapped(line, autowrapped);
l->setAutoWrapped (autowrapped);
editEnd ();
return true;
}
bool KateDocument::editWrapLine ( int line, int col, bool newLine, bool *newLineAdded)
{
// verbose debug
EDIT_DEBUG << "editWrapLine" << line << col << newLine;
if (line < 0 || col < 0)
return false;
if (!isReadWrite())
return false;
Kate::TextLine l = kateTextLine(line);
if (!l)
return false;
editStart ();
Kate::TextLine nextLine = kateTextLine(line+1);
const int length = l->length();
m_undoManager->slotLineWrapped(line, col, length - col, (!nextLine || newLine));
if (!nextLine || newLine)
{
m_buffer->wrapLine (KTextEditor::Cursor (line, col));
QList<KTextEditor::Mark*> list;
for (QHash<int, KTextEditor::Mark*>::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i)
{
if( i.value()->line >= line )
{
if ((col == 0) || (i.value()->line > line))
list.append( i.value() );
}
}
for( int i=0; i < list.size(); ++i )
m_marks.take( list.at(i)->line );
for( int i=0; i < list.size(); ++i )
{
list.at(i)->line++;
m_marks.insert( list.at(i)->line, list.at(i) );
}
if( !list.isEmpty() )
emit marksChanged( this );
// yes, we added a new line !
if (newLineAdded)
(*newLineAdded) = true;
}
else
{
m_buffer->wrapLine (KTextEditor::Cursor (line, col));
m_buffer->unwrapLine (line + 2);
// no, no new line added !
if (newLineAdded)
(*newLineAdded) = false;
}
emit KTextEditor::Document::textInserted(this, KTextEditor::Range(line, col, line+1, 0));
editEnd ();
return true;
}
bool KateDocument::editUnWrapLine ( int line, bool removeLine, int length )
{
// verbose debug
EDIT_DEBUG << "editUnWrapLine" << line << removeLine << length;
if (line < 0 || length < 0)
return false;
if (!isReadWrite())
return false;
Kate::TextLine l = kateTextLine(line);
Kate::TextLine nextLine = kateTextLine(line+1);
if (!l || !nextLine)
return false;
editStart ();
int col = l->length ();
m_undoManager->slotLineUnWrapped(line, col, length, removeLine);
if (removeLine)
{
m_buffer->unwrapLine (line+1);
}
else
{
m_buffer->wrapLine (KTextEditor::Cursor (line + 1, length));
m_buffer->unwrapLine (line + 1);
}
QList<KTextEditor::Mark*> list;
for (QHash<int, KTextEditor::Mark*>::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i)
{
if( i.value()->line >= line+1 )
list.append( i.value() );
if ( i.value()->line == line+1 )
{
KTextEditor::Mark* mark = m_marks.take( line );
if (mark)
{
i.value()->type |= mark->type;
}
}
}
for( int i=0; i < list.size(); ++i )
m_marks.take( list.at(i)->line );
for( int i=0; i < list.size(); ++i )
{
list.at(i)->line--;
m_marks.insert( list.at(i)->line, list.at(i) );
}
if( !list.isEmpty() )
emit marksChanged( this );
emit KTextEditor::Document::textRemoved(this, KTextEditor::Range(line, col, line+1, 0));
emit KTextEditor::Document::textRemoved(this, KTextEditor::Range(line, col, line+1, 0), "\n");
editEnd ();
return true;
}
bool KateDocument::editInsertLine ( int line, const QString &s )
{
// verbose debug
EDIT_DEBUG << "editInsertLine" << line << s;
if (line < 0)
return false;
if (!isReadWrite())
return false;
if ( line > lines() )
return false;
editStart ();
m_undoManager->slotLineInserted(line, s);
// wrap line
if (line > 0) {
Kate::TextLine previousLine = m_buffer->line (line-1);
m_buffer->wrapLine (KTextEditor::Cursor (line-1, previousLine->text().size()));
} else {
m_buffer->wrapLine (KTextEditor::Cursor (0, 0));
}
// insert text
m_buffer->insertText (KTextEditor::Cursor (line, 0), s);
Kate::TextLine tl = m_buffer->line (line);
QList<KTextEditor::Mark*> list;
for (QHash<int, KTextEditor::Mark*>::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i)
{
if( i.value()->line >= line )
list.append( i.value() );
}
for( int i=0; i < list.size(); ++i )
m_marks.take( list.at(i)->line );
for( int i=0; i < list.size(); ++i )
{
list.at(i)->line++;
m_marks.insert( list.at(i)->line, list.at(i) );
}
if( !list.isEmpty() )
emit marksChanged( this );
KTextEditor::Range rangeInserted(line, 0, line, tl->length());
if (line) {
Kate::TextLine prevLine = plainKateTextLine(line - 1);
rangeInserted.start().setPosition(line - 1, prevLine->length());
} else {
rangeInserted.end().setPosition(line + 1, 0);
}
emit KTextEditor::Document::textInserted(this, rangeInserted);
editEnd ();
return true;
}
bool KateDocument::editRemoveLine ( int line )
{
return editRemoveLines(line, line);
}
bool KateDocument::editRemoveLines ( int from, int to )
{
// verbose debug
EDIT_DEBUG << "editRemoveLines" << from << to;
if (to < from || from < 0 || to > lastLine())
return false;
if (!isReadWrite())
return false;
if (lines() == 1)
return editRemoveText(0, 0, kateTextLine(0)->length());
editStart();
QStringList oldText;
for (int line = to; line >= from; line--) {
Kate::TextLine tl = m_buffer->line (line);
oldText.prepend(this->line(line));
m_undoManager->slotLineRemoved(line, this->line(line));
m_buffer->removeText (KTextEditor::Range (KTextEditor::Cursor (line, 0), KTextEditor::Cursor (line, tl->text().size())));
}
m_buffer->unwrapLines(from, to);
QList<int> rmark;
QList<int> list;
foreach (KTextEditor::Mark* mark, m_marks) {
int line = mark->line;
if (line > to)
list << line;
else if (line >= from)
rmark << line;
}
foreach (int line, rmark)
delete m_marks.take(line);
foreach (int line, list)
{
KTextEditor::Mark* mark = m_marks.take(line);
mark->line -= to - from + 1;
m_marks.insert(mark->line, mark);
}
if (!list.isEmpty())
emit marksChanged(this);
KTextEditor::Range rangeRemoved(from, 0, to + 1, 0);
if (to == lastLine() + to - from + 1) {
rangeRemoved.end().setPosition(to, oldText.last().length());
if (from > 0) {
Kate::TextLine prevLine = plainKateTextLine(from - 1);
rangeRemoved.start().setPosition(from - 1, prevLine->length());
}
}
emit KTextEditor::Document::textRemoved(this, rangeRemoved);
emit KTextEditor::Document::textRemoved(this, rangeRemoved, oldText.join("\n") + '\n');
editEnd();
return true;
}
//END
//BEGIN KTextEditor::UndoInterface stuff
uint KateDocument::undoCount () const
{
return m_undoManager->undoCount ();
}
uint KateDocument::redoCount () const
{
return m_undoManager->redoCount ();
}
void KateDocument::undo()
{
m_undoManager->undo();
}
void KateDocument::redo()
{
m_undoManager->redo();
}
//END
//BEGIN KTextEditor::SearchInterface stuff
QVector<KTextEditor::Range> KateDocument::searchText(
const KTextEditor::Range & range,
const QString & pattern,
const KTextEditor::Search::SearchOptions options)
{
// TODO
// * support BlockInputRange
// * support DotMatchesNewline
const bool escapeSequences = options.testFlag(KTextEditor::Search::EscapeSequences);
const bool regexMode = options.testFlag(KTextEditor::Search::Regex);
const bool backwards = options.testFlag(KTextEditor::Search::Backwards);
const bool wholeWords = options.testFlag(KTextEditor::Search::WholeWords);
const Qt::CaseSensitivity caseSensitivity = options.testFlag(KTextEditor::Search::CaseInsensitive) ? Qt::CaseInsensitive : Qt::CaseSensitive;
if (regexMode)
{
// regexp search
// escape sequences are supported by definition
KateRegExpSearch searcher(this, caseSensitivity);
return searcher.search(pattern, range, backwards);
}
if (escapeSequences)
{
// escaped search
KatePlainTextSearch searcher(this, caseSensitivity, wholeWords);
KTextEditor::Range match = searcher.search(KateRegExpSearch::escapePlaintext(pattern), range, backwards);
QVector<KTextEditor::Range> result;
result.append(match);
return result;
}
// plaintext search
KatePlainTextSearch searcher(this, caseSensitivity, wholeWords);
KTextEditor::Range match = searcher.search(pattern, range, backwards);
QVector<KTextEditor::Range> result;
result.append(match);
return result;
}
KTextEditor::Search::SearchOptions KateDocument::supportedSearchOptions() const
{
KTextEditor::Search::SearchOptions supported(KTextEditor::Search::Default);
supported |= KTextEditor::Search::Regex;
supported |= KTextEditor::Search::CaseInsensitive;
supported |= KTextEditor::Search::Backwards;
// supported |= KTextEditor::Search::BlockInputRange;
supported |= KTextEditor::Search::EscapeSequences;
supported |= KTextEditor::Search::WholeWords;
// supported |= KTextEditor::Search::DotMatchesNewline;
return supported;
}
//END
QWidget * KateDocument::dialogParent()
{
QWidget *w=widget();
if(!w)
{
w=activeView();
if(!w)
w=QApplication::activeWindow();
}
return w;
}
//BEGIN KTextEditor::HighlightingInterface stuff
bool KateDocument::setMode (const QString &name)
{
updateFileType (name);
return true;
}
QString KateDocument::mode () const
{
return m_fileType;
}
QStringList KateDocument::modes () const
{
QStringList m;
const QList<KateFileType *> &modeList = KateGlobal::self()->modeManager()->list();
foreach(KateFileType* type, modeList)
m << type->name;
return m;
}
bool KateDocument::setHighlightingMode (const QString &name)
{
int mode = KateHlManager::self()->nameFind(name);
if (mode == -1) {
return false;
}
m_buffer->setHighlight(mode);
return true;
}
QString KateDocument::highlightingMode () const
{
return highlight()->name ();
}
QStringList KateDocument::highlightingModes () const
{
QStringList hls;
for (int i = 0; i < KateHlManager::self()->highlights(); ++i)
hls << KateHlManager::self()->hlName (i);
return hls;
}
QString KateDocument::highlightingModeSection( int index ) const
{
return KateHlManager::self()->hlSection( index );
}
QString KateDocument::modeSection( int index ) const
{
return KateGlobal::self()->modeManager()->list().at( index )->section;
}
void KateDocument::bufferHlChanged ()
{
// update all views
makeAttribs(false);
// deactivate indenter if necessary
m_indenter->checkRequiredStyle();
emit highlightingModeChanged(this);
}
void KateDocument::setDontChangeHlOnSave()
{
m_hlSetByUser = true;
}
void KateDocument::bomSetByUser()
{
m_bomSetByUser=true;
}
//END
//BEGIN KTextEditor::SessionConfigInterface and KTextEditor::ParameterizedSessionConfigInterface stuff
void KateDocument::readSessionConfig(const KConfigGroup &kconfig)
{
readParameterizedSessionConfig(kconfig, SkipNone);
}
void KateDocument::readParameterizedSessionConfig(const KConfigGroup &kconfig,
unsigned long configParameters)
{
if(!(configParameters & KTextEditor::ParameterizedSessionConfigInterface::SkipEncoding)) {
// get the encoding
QString tmpenc=kconfig.readEntry("Encoding");
if (!tmpenc.isEmpty() && (tmpenc != encoding()))
setEncoding(tmpenc);
}
if(!(configParameters & KTextEditor::ParameterizedSessionConfigInterface::SkipUrl)) {
// restore the url
KUrl url (kconfig.readEntry("URL"));
// open the file if url valid
if (!url.isEmpty() && url.isValid())
openUrl (url);
else completed(); //perhaps this should be emitted at the end of this function
}
else {
completed(); //perhaps this should be emitted at the end of this function
}
if(!(configParameters & KTextEditor::ParameterizedSessionConfigInterface::SkipMode)) {
// restore the filetype
if (kconfig.hasKey("Mode")) {
updateFileType (kconfig.readEntry("Mode", fileType()));
}
}
if(!(configParameters & KTextEditor::ParameterizedSessionConfigInterface::SkipHighlighting)) {
// restore the hl stuff
if (kconfig.hasKey("Highlighting")) {
int mode = KateHlManager::self()->nameFind(kconfig.readEntry("Highlighting"));
if (mode >= 0) {
m_buffer->setHighlight(mode);
}
}
}
// indent mode
config()->setIndentationMode( kconfig.readEntry("Indentation Mode", config()->indentationMode() ) );
// Restore Bookmarks
const QList<int> marks = kconfig.readEntry("Bookmarks", QList<int>());
for( int i = 0; i < marks.count(); i++ )
addMark( marks.at(i), KateDocument::markType01 );
}
void KateDocument::writeSessionConfig(KConfigGroup &kconfig)
{
writeParameterizedSessionConfig(kconfig, SkipNone);
}
void KateDocument::writeParameterizedSessionConfig(KConfigGroup &kconfig,
unsigned long configParameters)
{
if ( this->url().isLocalFile() ) {
const QString path = this->url().toLocalFile();
if ( KGlobal::dirs()->relativeLocation( "tmp", path ) != path ) {
return; // inside tmp resource, do not save
}
}
if(!(configParameters & KTextEditor::ParameterizedSessionConfigInterface::SkipUrl)) {
// save url
kconfig.writeEntry("URL", this->url().prettyUrl() );
}
if(!(configParameters & KTextEditor::ParameterizedSessionConfigInterface::SkipEncoding)) {
// save encoding
kconfig.writeEntry("Encoding",encoding());
}
if(!(configParameters & KTextEditor::ParameterizedSessionConfigInterface::SkipMode)) {
// save file type
kconfig.writeEntry("Mode", m_fileType);
}
if(!(configParameters & KTextEditor::ParameterizedSessionConfigInterface::SkipHighlighting)) {
// save hl
kconfig.writeEntry("Highlighting", highlight()->name());
}
// indent mode
kconfig.writeEntry("Indentation Mode", config()->indentationMode() );
// Save Bookmarks
QList<int> marks;
for (QHash<int, KTextEditor::Mark*>::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i)
if (i.value()->type & KTextEditor::MarkInterface::markType01)
marks << i.value()->line;
kconfig.writeEntry( "Bookmarks", marks );
}
//END KTextEditor::SessionConfigInterface and KTextEditor::ParameterizedSessionConfigInterface stuff
uint KateDocument::mark( int line )
{
KTextEditor::Mark* m = m_marks.value(line);
if( !m )
return 0;
return m->type;
}
void KateDocument::setMark( int line, uint markType )
{
clearMark( line );
addMark( line, markType );
}
void KateDocument::clearMark( int line )
{
if( line < 0 || line > lastLine() )
return;
if( !m_marks.value(line) )
return;
KTextEditor::Mark* mark = m_marks.take( line );
emit markChanged( this, *mark, MarkRemoved );
emit marksChanged( this );
delete mark;
tagLines( line, line );
repaintViews(true);
}
void KateDocument::addMark( int line, uint markType )
{
KTextEditor::Mark *mark;
if( line < 0 || line > lastLine())
return;
if( markType == 0 )
return;
if( (mark = m_marks.value(line)) ) {
// Remove bits already set
markType &= ~mark->type;
if( markType == 0 )
return;
// Add bits
mark->type |= markType;
} else {
mark = new KTextEditor::Mark;
mark->line = line;
mark->type = markType;
m_marks.insert( line, mark );
}
// Emit with a mark having only the types added.
KTextEditor::Mark temp;
temp.line = line;
temp.type = markType;
emit markChanged( this, temp, MarkAdded );
emit marksChanged( this );
tagLines( line, line );
repaintViews(true);
}
void KateDocument::removeMark( int line, uint markType )
{
if( line < 0 || line > lastLine() )
return;
KTextEditor::Mark* mark = m_marks.value(line);
if( !mark )
return;
// Remove bits not set
markType &= mark->type;
if( markType == 0 )
return;
// Subtract bits
mark->type &= ~markType;
// Emit with a mark having only the types removed.
KTextEditor::Mark temp;
temp.line = line;
temp.type = markType;
emit markChanged( this, temp, MarkRemoved );
if( mark->type == 0 )
m_marks.remove( line );
emit marksChanged( this );
tagLines( line, line );
repaintViews(true);
}
const QHash<int, KTextEditor::Mark*> &KateDocument::marks()
{
return m_marks;
}
void KateDocument::requestMarkTooltip( int line, QPoint position )
{
KTextEditor::Mark* mark = m_marks.value(line);
if(!mark)
return;
bool handled = false;
emit markToolTipRequested( this, *mark, position, handled );
}
bool KateDocument::handleMarkClick( int line )
{
KTextEditor::Mark* mark = m_marks.value(line);
if(!mark)
return false;
bool handled = false;
emit markClicked( this, *mark, handled );
return handled;
}
bool KateDocument::handleMarkContextMenu( int line, QPoint position )
{
KTextEditor::Mark* mark = m_marks.value(line);
if(!mark)
return false;
bool handled = false;
emit markContextMenuRequested( this, *mark, position, handled );
return handled;
}
void KateDocument::clearMarks()
{
while (!m_marks.isEmpty())
{
QHash<int, KTextEditor::Mark*>::iterator it = m_marks.begin();
KTextEditor::Mark mark = *it.value();
delete it.value();
m_marks.erase (it);
emit markChanged( this, mark, MarkRemoved );
tagLines( mark.line, mark.line );
}
m_marks.clear();
emit marksChanged( this );
repaintViews(true);
}
void KateDocument::setMarkPixmap( MarkInterface::MarkTypes type, const QPixmap& pixmap )
{
m_markPixmaps.insert( type, pixmap );
}
void KateDocument::setMarkDescription( MarkInterface::MarkTypes type, const QString& description )
{
m_markDescriptions.insert( type, description );
}
QPixmap KateDocument::markPixmap( MarkInterface::MarkTypes type ) const
{
return m_markPixmaps.value(type, QPixmap());
}
QColor KateDocument::markColor( MarkInterface::MarkTypes type ) const
{
uint reserved = (0x1 << KTextEditor::MarkInterface::reservedMarkersCount()) - 1;
if ((uint)type >= (uint)markType01 && (uint)type <= reserved) {
return KateRendererConfig::global()->lineMarkerColor(type);
} else {
return QColor();
}
}
QString KateDocument::markDescription( MarkInterface::MarkTypes type ) const
{
return m_markDescriptions.value(type, QString());
}
void KateDocument::setEditableMarks( uint markMask )
{
m_editableMarks = markMask;
}
uint KateDocument::editableMarks() const
{
return m_editableMarks;
}
//END
//BEGIN KTextEditor::PrintInterface stuff
bool KateDocument::printDialog ()
{
return KatePrinter::print (this);
}
bool KateDocument::print ()
{
return KatePrinter::print (this);
}
//END
//BEGIN KTextEditor::DocumentInfoInterface (### unfinished)
QString KateDocument::mimeType()
{
KMimeType::Ptr result = KMimeType::defaultMimeTypePtr();
// if the document has a URL, try KMimeType::findByURL
if ( ! this->url().isEmpty() )
result = KMimeType::findByUrl( this->url() );
else if ( this->url().isEmpty() || ! this->url().isLocalFile() )
result = mimeTypeForContent();
return result->name();
}
KMimeType::Ptr KateDocument::mimeTypeForContent()
{
QByteArray buf (1024,'\0');
uint bufpos = 0;
for (int i=0; i < lines(); ++i)
{
QString line = this->line( i );
uint len = line.length() + 1;
if (bufpos + len > 1024)
len = 1024 - bufpos;
QString ld (line + QChar::fromAscii('\n'));
buf.replace(bufpos,len,ld.toLatin1()); //memcpy(buf.data() + bufpos, ld.toLatin1().constData(), len);
bufpos += len;
if (bufpos >= 1024)
break;
}
buf.resize( bufpos );
int accuracy = 0;
KMimeType::Ptr mt = KMimeType::findByContent(buf, &accuracy);
return mt ? mt : KMimeType::defaultMimeTypePtr();
}
//END KTextEditor::DocumentInfoInterface
//BEGIN: error
void KateDocument::showAndSetOpeningErrorAccess() {
QPointer<KTextEditor::Message> message
= new KTextEditor::Message(i18n ("The file %1 could not be loaded, as it was not possible to read from it.<br />Check if you have read access to this file.", this->url().pathOrUrl()),
KTextEditor::Message::Error);
message->setWordWrap(true);
QAction* tryAgainAction = new QAction(KIcon("view-refresh"), i18nc("translators: you can also translate 'Try Again' with 'Reload'", "Try Again"), 0);
connect(tryAgainAction, SIGNAL(triggered()), SLOT(documentReload()), Qt::QueuedConnection);
QAction* closeAction = new QAction(KIcon("window-close"), i18n("&Close"), 0);
closeAction->setToolTip(i18n("Close message"));
// add try again and close actions
message->addAction(tryAgainAction);
message->addAction(closeAction);
// finally post message
postMessage(message);
// remember error
setOpeningError(true);
setOpeningErrorMessage(i18n ("The file %1 could not be loaded, as it was not possible to read from it.\n\nCheck if you have read access to this file.",this->url().pathOrUrl()));
}
//END: error
//BEGIN KParts::ReadWrite stuff
bool KateDocument::openFile()
{
/**
* we are about to invalidate all cursors/ranges/.. => m_buffer->openFile will do so
*/
emit aboutToInvalidateMovingInterfaceContent (this);
// no open errors until now...
setOpeningError(false);
// add new m_file to dirwatch
activateDirWatch ();
// remember current encoding
QString currentEncoding = encoding();
//
// mime type magic to get encoding right
//
QString mimeType = arguments().mimeType();
int pos = mimeType.indexOf(';');
if (pos != -1 && !(m_reloading && m_userSetEncodingForNextReload))
setEncoding (mimeType.mid(pos+1));
// do we have success ?
emit KTextEditor::Document::textRemoved(this, documentRange());
emit KTextEditor::Document::textRemoved(this, documentRange(), m_buffer->text());
// update file type, we do this here PRE-LOAD, therefore pass file name for reading from
updateFileType (KateGlobal::self()->modeManager()->fileType (this, localFilePath()));
// read dir config (if possible and wanted)
// do this PRE-LOAD to get encoding info!
readDirConfig ();
// perhaps we need to re-set again the user encoding
if (m_reloading && m_userSetEncodingForNextReload && (currentEncoding != encoding()))
setEncoding (currentEncoding);
bool success = m_buffer->openFile (localFilePath(), (m_reloading && m_userSetEncodingForNextReload));
// disable view updates
foreach (KateView * view, m_views)
view->setUpdatesEnabled (false);
//
// yeah, success
// read variables
//
if (success)
readVariables ();
//
// update views
//
foreach (KateView * view, m_views)
{
// This is needed here because inserting the text moves the view's start position (it is a MovingCursor)
view->setCursorPosition (KTextEditor::Cursor());
view->setUpdatesEnabled (true);
view->updateView (true);
}
// emit all signals about new text after view updates
emit KTextEditor::Document::textInserted(this, documentRange());
// Inform that the text has changed (required as we're not inside the usual editStart/End stuff)
emit textChanged (this);
if (!m_reloading)
{
//
// emit the signal we need for example for kate app
//
emit documentUrlChanged (this);
//
// set doc name, dummy value as arg, don't need it
//
updateDocName ();
}
//
// to houston, we are not modified
//
if (m_modOnHd)
{
m_modOnHd = false;
m_modOnHdReason = OnDiskUnmodified;
emit modifiedOnDisk (this, m_modOnHd, m_modOnHdReason);
}
//
// display errors
//
if (!success) {
showAndSetOpeningErrorAccess();
}
// warn: broken encoding
if (m_buffer->brokenEncoding()) {
// this file can't be saved again without killing it
setReadWrite( false );
QPointer<KTextEditor::Message> message
= new KTextEditor::Message(i18n ("The file %1 was opened with %2 encoding but contained invalid characters.<br />"
"It is set to read-only mode, as saving might destroy its content.<br />"
"Either reopen the file with the correct encoding chosen or enable the read-write mode again in the menu to be able to edit it.", this->url().pathOrUrl(),
QString (m_buffer->textCodec()->name ())),
KTextEditor::Message::Warning);
message->setWordWrap(true);
postMessage(message);
// remember error
setOpeningError(true);
setOpeningErrorMessage(i18n ("The file %1 was opened with %2 encoding but contained invalid characters."
" It is set to read-only mode, as saving might destroy its content."
" Either reopen the file with the correct encoding chosen or enable the read-write mode again in the menu to be able to edit it.", this->url().pathOrUrl(), QString (m_buffer->textCodec()->name ())));
}
// warn: too long lines
if (m_buffer->tooLongLinesWrapped()) {
// this file can't be saved again without modifications
setReadWrite( false );
m_readWriteStateBeforeLoading = false;
QPointer<KTextEditor::Message> message
= new KTextEditor::Message(i18n ("The file %1 was opened and contained lines longer than the configured Line Length Limit (%2 characters).<br />"
"Those lines were wrapped and the document is set to read-only mode, as saving will modify its content.",
this->url().pathOrUrl(),config()->lineLengthLimit()),
KTextEditor::Message::Warning);
message->setWordWrap(true);
postMessage(message);
// remember error
setOpeningError(true);
setOpeningErrorMessage(i18n ("The file %1 was opened and contained lines longer than the configured Line Length Limit (%2 characters)."
" Those lines were wrapped and the document is set to read-only mode, as saving will modify its content.", this->url().pathOrUrl(),config()->lineLengthLimit()));
}
//
// return the success
//
return success;
}
bool KateDocument::saveFile()
{
QWidget *parentWidget(dialogParent());
// some warnings, if file was changed by the outside!
if ( !url().isEmpty() )
{
if (m_fileChangedDialogsActivated && m_modOnHd)
{
QString str = reasonedMOHString() + "\n\n";
if (!isModified())
{
if (KMessageBox::warningContinueCancel(parentWidget,
str + i18n("Do you really want to save this unmodified file? You could overwrite changed data in the file on disk."),i18n("Trying to Save Unmodified File"),KGuiItem(i18n("Save Nevertheless"))) != KMessageBox::Continue)
return false;
}
else
{
if (KMessageBox::warningContinueCancel(parentWidget,
str + i18n("Do you really want to save this file? Both your open file and the file on disk were changed. There could be some data lost."),i18n("Possible Data Loss"),KGuiItem(i18n("Save Nevertheless"))) != KMessageBox::Continue)
return false;
}
}
}
//
// can we encode it if we want to save it ?
//
if (!m_buffer->canEncode ()
&& (KMessageBox::warningContinueCancel(parentWidget,
i18n("The selected encoding cannot encode every unicode character in this document. Do you really want to save it? There could be some data lost."),i18n("Possible Data Loss"),KGuiItem(i18n("Save Nevertheless"))) != KMessageBox::Continue))
{
return false;
}
//
// try to create backup file..
//
// local file or not is here the question
bool l ( url().isLocalFile() );
// does the user want any backup, if not, not our problem?
if ( ( l && config()->backupFlags() & KateDocumentConfig::LocalFiles )
|| ( ! l && config()->backupFlags() & KateDocumentConfig::RemoteFiles ) )
{
KUrl u( url() );
if (config()->backupPrefix().contains(QDir::separator())) {
u.setPath( config()->backupPrefix() + url().fileName() + config()->backupSuffix() );
} else {
u.setFileName( config()->backupPrefix() + url().fileName() + config()->backupSuffix() );
}
kDebug( 13020 ) << "backup src file name: " << url();
kDebug( 13020 ) << "backup dst file name: " << u;
// handle the backup...
bool backupSuccess = false;
// local file mode, no kio
if (u.isLocalFile ())
{
if (QFile::exists (url().toLocalFile ()))
{
// first: check if backupFile is already there, if true, unlink it
QFile backupFile (u.toLocalFile ());
if (backupFile.exists()) backupFile.remove ();
backupSuccess = QFile::copy (url().toLocalFile (), u.toLocalFile ());
}
else
backupSuccess = true;
}
else // remote file mode, kio
{
// get the right permissions, start with safe default
KIO::UDSEntry fentry;
if (KIO::NetAccess::stat (url(), fentry, QApplication::activeWindow())) {
// do a evil copy which will overwrite target if possible
KFileItem item (fentry, url());
KIO::FileCopyJob *job = KIO::file_copy ( url(), u, item.permissions(), KIO::Overwrite );
backupSuccess = KIO::NetAccess::synchronousRun(job, QApplication::activeWindow());
}
else
backupSuccess = true;
}
// backup has failed, ask user how to proceed
if (!backupSuccess && (KMessageBox::warningContinueCancel (parentWidget
, i18n ("For file %1 no backup copy could be created before saving."
" If an error occurs while saving, you might lose the data of this file."
" A reason could be that the media you write to is full or the directory of the file is read-only for you.", url().pathOrUrl())
, i18n ("Failed to create backup copy.")
, KGuiItem(i18n("Try to Save Nevertheless"))
, KStandardGuiItem::cancel(), "Backup Failed Warning") != KMessageBox::Continue))
{
return false;
}
}
// update file type, pass no file path, read file type content from this document
updateFileType (KateGlobal::self()->modeManager()->fileType (this, QString ()));
// remember the oldpath...
QString oldPath = m_dirWatchFile;
// read dir config (if possible and wanted)
if ( url().isLocalFile())
{
QFileInfo fo (oldPath), fn (localFilePath());
if (fo.path() != fn.path())
readDirConfig();
}
// read our vars
readVariables();
// remove file from dirwatch
deactivateDirWatch ();
// remove all trailing spaces in the document (as edit actions)
// NOTE: we need this as edit actions, since otherwise the edit actions
// in the swap file recovery may happen at invalid cursor positions
removeTrailingSpaces ();
//
// try to save
//
if (!m_buffer->saveFile (localFilePath()))
{
// add m_file again to dirwatch
activateDirWatch (oldPath);
KMessageBox::error (parentWidget, i18n ("The document could not be saved, as it was not possible to write to %1.\n\nCheck that you have write access to this file or that enough disk space is available.", this->url().pathOrUrl()));
return false;
}
// update the digest
createDigest ();
// add m_file again to dirwatch
activateDirWatch ();
//
// we are not modified
//
if (m_modOnHd)
{
m_modOnHd = false;
m_modOnHdReason = OnDiskUnmodified;
emit modifiedOnDisk (this, m_modOnHd, m_modOnHdReason);
}
// update document name...
updateDocName ();
// url may have changed...
emit documentUrlChanged (this);
// (dominik) mark last undo group as not mergeable, otherwise the next
// edit action might be merged and undo will never stop at the saved state
m_undoManager->undoSafePoint();
m_undoManager->updateLineModifications();
//
// return success
//
return true;
}
void KateDocument::readDirConfig ()
{
int depth = config()->searchDirConfigDepth ();
if (this->url().isLocalFile() && (depth > -1))
{
QString currentDir = QFileInfo (localFilePath()).absolutePath();
// only search as deep as specified or not at all ;)
while (depth > -1)
{
//kDebug (13020) << "search for config file in path: " << currentDir;
// try to open config file in this dir
QFile f (currentDir + "/.kateconfig");
if (f.open (QIODevice::ReadOnly))
{
QTextStream stream (&f);
uint linesRead = 0;
QString line = stream.readLine();
while ((linesRead < 32) && !line.isNull())
{
readVariableLine( line );
line = stream.readLine();
linesRead++;
}
break;
}
QString newDir = QFileInfo (currentDir).absolutePath();
// bail out on looping (for example reached /)
if (currentDir == newDir)
break;
currentDir = newDir;
--depth;
}
}
}
void KateDocument::activateDirWatch (const QString &useFileName)
{
QString fileToUse = useFileName;
if (fileToUse.isEmpty())
fileToUse = localFilePath();
QFileInfo fileInfo = QFileInfo(fileToUse);
if (fileInfo.isSymLink()) {
// Monitor the actual data and not the symlink
fileToUse = fileInfo.canonicalFilePath();
}
// same file as we are monitoring, return
if (fileToUse == m_dirWatchFile)
return;
// remove the old watched file
deactivateDirWatch ();
// add new file if needed
if (url().isLocalFile() && !fileToUse.isEmpty())
{
KateGlobal::self()->dirWatch ()->addFile (fileToUse);
m_dirWatchFile = fileToUse;
}
}
void KateDocument::deactivateDirWatch ()
{
if (!m_dirWatchFile.isEmpty())
KateGlobal::self()->dirWatch ()->removeFile (m_dirWatchFile);
m_dirWatchFile.clear();
}
bool KateDocument::openUrl( const KUrl &url ) {
bool res=KTextEditor::Document::openUrl(url);
updateDocName();
return res;
}
bool KateDocument::closeUrl()
{
//
// file mod on hd
//
if ( !m_reloading && !url().isEmpty() )
{
if (m_fileChangedDialogsActivated && m_modOnHd)
{
QWidget *parentWidget(dialogParent());
if (!(KMessageBox::warningContinueCancel(
parentWidget,
reasonedMOHString() + "\n\n" + i18n("Do you really want to continue to close this file? Data loss may occur."),
i18n("Possible Data Loss"), KGuiItem(i18n("Close Nevertheless")), KStandardGuiItem::cancel(),
QString("kate_close_modonhd_%1").arg( m_modOnHdReason ) ) == KMessageBox::Continue)) {
/**
* reset reloading
*/
m_reloading = false;
return false;
}
}
}
//
// first call the normal kparts implementation
//
if (!KParts::ReadWritePart::closeUrl ()) {
/**
* reset reloading
*/
m_reloading = false;
return false;
}
// Tell the world that we're about to go ahead with the close
if (!m_reloading)
emit aboutToClose(this);
/**
* delete all KTE::Messages
*/
if (m_messageHash.count()) {
QList<KTextEditor::Message*> keys = m_messageHash.keys ();
foreach (KTextEditor::Message* message, keys)
delete message;
}
/**
* we are about to invalidate all cursors/ranges/.. => m_buffer->clear will do so
*/
emit aboutToInvalidateMovingInterfaceContent (this);
// remove file from dirwatch
deactivateDirWatch ();
//
// empty url + fileName
//
setUrl(KUrl());
setLocalFilePath(QString());
// we are not modified
if (m_modOnHd)
{
m_modOnHd = false;
m_modOnHdReason = OnDiskUnmodified;
emit modifiedOnDisk (this, m_modOnHd, m_modOnHdReason);
}
emit KTextEditor::Document::textRemoved(this, documentRange());
emit KTextEditor::Document::textRemoved(this, documentRange(), m_buffer->text());
{
// remove all marks
clearMarks ();
// clear the buffer
m_buffer->clear();
// clear undo/redo history
m_undoManager->clearUndo();
m_undoManager->clearRedo();
}
// no, we are no longer modified
setModified(false);
// we have no longer any hl
m_buffer->setHighlight(0);
// update all our views
foreach (KateView * view, m_views )
{
view->clearSelection(); // fix bug #118588
view->clear();
}
if (!m_reloading)
{
// uh, fileName changed
emit documentUrlChanged (this);
// update doc name
updateDocName ();
}
// purge swap file
m_swapfile->fileClosed ();
// success
return true;
}
bool KateDocument::isDataRecoveryAvailable() const
{
return m_swapfile->shouldRecover();
}
void KateDocument::recoverData()
{
if (isDataRecoveryAvailable())
m_swapfile->recover();
}
void KateDocument::discardDataRecovery()
{
if (isDataRecoveryAvailable())
m_swapfile->discard();
}
void KateDocument::setReadWrite( bool rw )
{
if (isReadWrite() != rw)
{
KParts::ReadWritePart::setReadWrite (rw);
foreach( KateView* view, m_views)
{
view->slotUpdateUndo();
view->slotReadWriteChanged ();
}
emit readWriteChanged(this);
}
}
void KateDocument::setModified(bool m) {
if (isModified() != m) {
KParts::ReadWritePart::setModified (m);
foreach( KateView* view,m_views)
{
view->slotUpdateUndo();
}
emit modifiedChanged (this);
}
m_undoManager->setModified (m);
}
//END
//BEGIN Kate specific stuff ;)
void KateDocument::makeAttribs(bool needInvalidate)
{
foreach(KateView *view,m_views)
view->renderer()->updateAttributes ();
if (needInvalidate)
m_buffer->invalidateHighlighting();
foreach(KateView *view,m_views)
{
view->tagAll();
view->updateView (true);
}
}
// the attributes of a hl have changed, update
void KateDocument::internalHlChanged()
{
makeAttribs();
}
void KateDocument::addView(KTextEditor::View *view) {
if (!view)
return;
m_views.append( static_cast<KateView*>(view) );
m_textEditViews.append( view );
// apply the view & renderer vars from the file type
if (!m_fileType.isEmpty())
readVariableLine(KateGlobal::self()->modeManager()->fileType(m_fileType).varLine, true);
// apply the view & renderer vars from the file
readVariables (true);
setActiveView(view);
}
void KateDocument::removeView(KTextEditor::View *view) {
if (!view)
return;
if (activeView() == view)
setActiveView(0L);
m_views.removeAll( static_cast<KateView *>(view) );
m_textEditViews.removeAll( view );
}
void KateDocument::setActiveView(KTextEditor::View* view)
{
if ( m_activeView == view )
return;
m_activeView = static_cast<KateView*>(view);
}
bool KateDocument::ownedView(KateView *view) {
// do we own the given view?
return (m_views.contains(view));
}
int KateDocument::toVirtualColumn( int line, int column ) const
{
Kate::TextLine textLine = m_buffer->plainLine(line);
if (textLine)
return textLine->toVirtualColumn(column, config()->tabWidth());
else
return 0;
}
int KateDocument::toVirtualColumn( const KTextEditor::Cursor& cursor ) const
{
return toVirtualColumn(cursor.line(), cursor.column());
}
int KateDocument::fromVirtualColumn( int line, int column ) const
{
Kate::TextLine textLine = m_buffer->plainLine(line);
if (textLine)
return textLine->fromVirtualColumn(column, config()->tabWidth());
else
return 0;
}
int KateDocument::fromVirtualColumn( const KTextEditor::Cursor& cursor ) const
{
return fromVirtualColumn(cursor.line(), cursor.column());
}
bool KateDocument::typeChars ( KateView *view, const QString &realChars )
{
Kate::TextLine textLine = m_buffer->plainLine(view->cursorPosition().line ());
if (!textLine)
return false;
/**
* filter out non-printable
*/
QString chars;
Q_FOREACH (QChar c, realChars)
if (c.isPrint() || c == QChar::fromLatin1('\t'))
chars.append (c);
if (chars.isEmpty())
return false;
editStart ();
if (!view->config()->persistentSelection() && view->selection() )
view->removeSelectedText();
KTextEditor::Cursor oldCur (view->cursorPosition());
if (config()->ovr()) {
KTextEditor::Range r = KTextEditor::Range(view->cursorPosition(), qMin(chars.length(),
textLine->length() - view->cursorPosition().column()));
removeText(r);
}
if (view->blockSelection() && view->selection()) {
KTextEditor::Range selectionRange = view->selectionRange();
int startLine = qMax(0, selectionRange.start().line());
int endLine = qMin(selectionRange.end().line(), lastLine());
int column = toVirtualColumn(selectionRange.end());
for (int line = endLine; line >= startLine; --line)
editInsertText(line, fromVirtualColumn(line, column), chars);
int newSelectionColumn = toVirtualColumn(view->cursorPosition());
selectionRange.start().setColumn(fromVirtualColumn(selectionRange.start().line(), newSelectionColumn));
selectionRange.end().setColumn(fromVirtualColumn(selectionRange.end().line(), newSelectionColumn));
view->setSelection(selectionRange);
}
else
insertText(view->cursorPosition(), chars);
// end edit session here, to have updated HL in userTypedChar!
editEnd();
KTextEditor::Cursor b(view->cursorPosition());
m_indenter->userTypedChar (view, b, chars.isEmpty() ? QChar() : chars.at(chars.length() - 1));
view->slotTextInserted (view, oldCur, chars);
return true;
}
void KateDocument::newLine( KateView *v )
{
editStart();
if( !v->config()->persistentSelection() && v->selection() ) {
v->removeSelectedText();
v->clearSelection();
}
// query cursor position
KTextEditor::Cursor c = v->cursorPosition();
if (c.line() > (int)lastLine())
c.setLine(lastLine());
if (c.line() < 0)
c.setLine(0);
uint ln = c.line();
Kate::TextLine textLine = plainKateTextLine(ln);
if (c.column() > (int)textLine->length())
c.setColumn(textLine->length());
// first: wrap line
editWrapLine (c.line(), c.column());
// end edit session here, to have updated HL in userTypedChar!
editEnd();
// second: indent the new line, if needed...
m_indenter->userTypedChar(v, v->cursorPosition(), '\n');
}
void KateDocument::transpose( const KTextEditor::Cursor& cursor)
{
Kate::TextLine textLine = m_buffer->plainLine(cursor.line());
if (!textLine || (textLine->length() < 2))
return;
uint col = cursor.column();
if (col > 0)
col--;
if ((textLine->length() - col) < 2)
return;
uint line = cursor.line();
QString s;
//clever swap code if first character on the line swap right&left
//otherwise left & right
s.append (textLine->at(col+1));
s.append (textLine->at(col));
//do the swap
// do it right, never ever manipulate a textline
editStart ();
editRemoveText (line, col, 2);
editInsertText (line, col, s);
editEnd ();
}
void KateDocument::backspace( KateView *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
KTextEditor::Range range = view->selectionRange();
range.start().setColumn(range.start().column() - 1);
view->setSelection(range);
}
view->removeSelectedText();
return;
}
uint col = qMax( c.column(), 0 );
uint line = qMax( c.line(), 0 );
if ((col == 0) && (line == 0))
return;
if (col > 0)
{
if (!(config()->backspaceIndents()))
{
// ordinary backspace
//c.cursor.col--;
removeText(KTextEditor::Range(line, col-1, line, col));
// 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(KTextEditor::Cursor(line, col-1));
}
else
{
// backspace indents: erase to next indent position
Kate::TextLine textLine = m_buffer->plainLine(line);
// don't forget this check!!!! really!!!!
if (!textLine)
return;
int colX = textLine->toVirtualColumn(col, config()->tabWidth());
int pos = textLine->firstChar();
if (pos > 0)
pos = textLine->toVirtualColumn(pos, config()->tabWidth());
if (pos < 0 || pos >= (int)colX)
{
// only spaces on left side of cursor
indent( KTextEditor::Range( line, 0, line, 0), -1);
}
else
{
removeText(KTextEditor::Range(line, col-1, line, col));
// 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(KTextEditor::Cursor(line, col-1));
}
}
}
else
{
// col == 0: wrap to previous line
if (line >= 1)
{
Kate::TextLine textLine = m_buffer->plainLine(line-1);
// don't forget this check!!!! really!!!!
if (!textLine)
return;
if (config()->wordWrap() && textLine->endsWith(QLatin1String(" ")))
{
// gg: in hard wordwrap mode, backspace must also eat the trailing space
removeText (KTextEditor::Range(line-1, textLine->length()-1, line, 0));
}
else
removeText (KTextEditor::Range(line-1, textLine->length(), line, 0));
}
}
}
void KateDocument::del( KateView *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
KTextEditor::Range range = view->selectionRange();
range.end().setColumn(range.end().column() + 1);
view->setSelection(range);
}
view->removeSelectedText();
return;
}
if( c.column() < (int) m_buffer->plainLine(c.line())->length())
{
removeText(KTextEditor::Range(c, 1));
}
else if ( c.line() < lastLine() )
{
removeText(KTextEditor::Range(c.line(), c.column(), c.line()+1, 0));
}
}
void KateDocument::paste ( KateView* view, const QString &text )
{
static const QChar newLineChar('\n');
QString s = text;
if (s.isEmpty())
return;
int lines = s.count (newLineChar);
m_undoManager->undoSafePoint();
editStart ();
KTextEditor::Cursor pos = view->cursorPosition();
if (!view->config()->persistentSelection() && view->selection()) {
pos = view->selectionRange().start();
if (view->blockSelection()) {
pos = rangeOnLine(view->selectionRange(), pos.line()).start();
if (lines == 0) {
s += newLineChar;
s = s.repeated(view->selectionRange().numberOfLines() + 1);
s.chop(1);
}
}
view->removeSelectedText();
}
if (config()->ovr()) {
QStringList pasteLines = s.split(newLineChar);
if (!view->blockSelection()) {
int endColumn = (pasteLines.count() == 1 ? pos.column() : 0) + pasteLines.last().length();
removeText(KTextEditor::Range(pos,
pos.line()+pasteLines.count()-1, endColumn));
} else {
int maxi = qMin(pos.line() + pasteLines.count(), this->lines());
for (int i = pos.line(); i < maxi; ++i) {
int pasteLength = pasteLines.at(i-pos.line()).length();
removeText(KTextEditor::Range(i, pos.column(),
i, qMin(pasteLength + pos.column(), lineLength(i))));
}
}
}
insertText(pos, s, view->blockSelection());
editEnd();
// move cursor right for block select, as the user is moved right internal
// even in that case, but user expects other behavior in block selection
// mode !
// just let cursor stay, that was it before I changed to moving ranges!
if (view->blockSelection())
view->setCursorPositionInternal(pos);
if (config()->indentPastedText())
{
KTextEditor::Range range = KTextEditor::Range(KTextEditor::Cursor(pos.line(), 0),
KTextEditor::Cursor(pos.line() + lines, 0));
m_indenter->indent(view, range);
}
if (!view->blockSelection())
emit charactersSemiInteractivelyInserted (pos, s);
m_undoManager->undoSafePoint();
}
void KateDocument::indent (KTextEditor::Range range, int change)
{
if (!isReadWrite())
return;
editStart();
m_indenter->changeIndent(range, change);
editEnd();
}
void KateDocument::align(KateView *view, const KTextEditor::Range &range)
{
m_indenter->indent(view, range);
}
void KateDocument::insertTab( KateView *view, const KTextEditor::Cursor&)
{
if (!isReadWrite())
return;
int lineLen = line(view->cursorPosition().line()).length();
KTextEditor::Cursor c = view->cursorPosition();
editStart();
if (!view->config()->persistentSelection() && view->selection() ) {
view->removeSelectedText();
}
else if (config()->ovr() && c.column() < lineLen)
{
KTextEditor::Range r = KTextEditor::Range(view->cursorPosition(), 1);
removeText(r);
}
c = view->cursorPosition();
editInsertText(c.line(), c.column(), QChar('\t'));
editEnd();
}
/*
Remove a given string at the beginning
of the current line.
*/
bool KateDocument::removeStringFromBeginning(int line, const QString &str)
{
Kate::TextLine textline = m_buffer->plainLine(line);
KTextEditor::Cursor cursor (line, 0);
bool there = textline->startsWith(str);
if (!there)
{
cursor.setColumn(textline->firstChar());
there = textline->matchesAt(cursor.column(), str);
}
if (there)
{
// Remove some chars
removeText (KTextEditor::Range(cursor, str.length()));
}
return there;
}
/*
Remove a given string at the end
of the current line.
*/
bool KateDocument::removeStringFromEnd(int line, const QString &str)
{
Kate::TextLine textline = m_buffer->plainLine(line);
KTextEditor::Cursor cursor (line, 0);
bool there = textline->endsWith(str);
if (there)
{
cursor.setColumn(textline->length() - str.length());
}
else
{
cursor.setColumn(textline->lastChar() - str.length() + 1);
there = textline->matchesAt(cursor.column(), str);
}
if (there)
{
// Remove some chars
removeText (KTextEditor::Range(cursor, str.length()));
}
return there;
}
/*
Add to the current line a comment line mark at the beginning.
*/
void KateDocument::addStartLineCommentToSingleLine( int line, int attrib )
{
QString commentLineMark = highlight()->getCommentSingleLineStart(attrib);
int pos = -1;
if (highlight()->getCommentSingleLinePosition(attrib) == KateHighlighting::CSLPosColumn0)
{
pos = 0;
commentLineMark += ' ';
} else {
const Kate::TextLine l = kateTextLine(line);
pos = l->firstChar();
}
if (pos >= 0)
insertText (KTextEditor::Cursor(line, pos), commentLineMark);
}
/*
Remove from the current line a comment line mark at
the beginning if there is one.
*/
bool KateDocument::removeStartLineCommentFromSingleLine( int line, int attrib )
{
const QString shortCommentMark = highlight()->getCommentSingleLineStart( attrib );
const QString longCommentMark = shortCommentMark + ' ';
editStart();
// Try to remove the long comment mark first
bool removed = (removeStringFromBeginning(line, longCommentMark)
|| removeStringFromBeginning(line, shortCommentMark));
editEnd();
return removed;
}
/*
Add to the current line a start comment mark at the
beginning and a stop comment mark at the end.
*/
void KateDocument::addStartStopCommentToSingleLine( int line, int attrib )
{
const QString startCommentMark = highlight()->getCommentStart( attrib ) + ' ';
const QString stopCommentMark = ' ' + highlight()->getCommentEnd( attrib );
editStart();
// Add the start comment mark
insertText (KTextEditor::Cursor(line, 0), startCommentMark);
// Go to the end of the line
const int col = m_buffer->plainLine(line)->length();
// Add the stop comment mark
insertText (KTextEditor::Cursor(line, col), stopCommentMark);
editEnd();
}
/*
Remove from the current line a start comment mark at
the beginning and a stop comment mark at the end.
*/
bool KateDocument::removeStartStopCommentFromSingleLine( int line, int attrib )
{
QString shortStartCommentMark = highlight()->getCommentStart( attrib );
QString longStartCommentMark = shortStartCommentMark + ' ';
QString shortStopCommentMark = highlight()->getCommentEnd( attrib );
QString longStopCommentMark = ' ' + shortStopCommentMark;
editStart();
// TODO "that's a bad idea, can lead to stray endings, FIXME"
// Try to remove the long start comment mark first
bool removedStart = (removeStringFromBeginning(line, longStartCommentMark)
|| removeStringFromBeginning(line, shortStartCommentMark));
bool removedStop = false;
if (removedStart)
{
// Try to remove the long stop comment mark first
removedStop = (removeStringFromEnd(line, longStopCommentMark)
|| removeStringFromEnd(line, shortStopCommentMark));
}
editEnd();
return (removedStart || removedStop);
}
/*
Add to the current selection a start comment mark at the beginning
and a stop comment mark at the end.
*/
void KateDocument::addStartStopCommentToSelection( KateView *view, int attrib )
{
const QString startComment = highlight()->getCommentStart( attrib );
const QString endComment = highlight()->getCommentEnd( attrib );
KTextEditor::Range range = view->selectionRange();
if ((range.end().column() == 0) && (range.end().line() > 0))
range.end().setPosition(range.end().line() - 1, lineLength(range.end().line() - 1));
editStart();
if (!view->blockSelection()) {
insertText(range.end(), endComment);
insertText(range.start(), startComment);
} else {
for (int line = range.start().line(); line <= range.end().line(); line++ ) {
KTextEditor::Range subRange = rangeOnLine(range, line);
insertText(subRange.end(), endComment);
insertText(subRange.start(), startComment);
}
}
editEnd ();
// selection automatically updated (MovingRange)
}
/*
Add to the current selection a comment line mark at the beginning of each line.
*/
void KateDocument::addStartLineCommentToSelection( KateView *view, int attrib )
{
const QString commentLineMark = highlight()->getCommentSingleLineStart( attrib ) + ' ';
int sl = view->selectionRange().start().line();
int el = view->selectionRange().end().line();
// if end of selection is in column 0 in last line, omit the last line
if ((view->selectionRange().end().column() == 0) && (el > 0))
{
el--;
}
editStart();
// For each line of the selection
for (int z = el; z >= sl; z--) {
//insertText (z, 0, commentLineMark);
addStartLineCommentToSingleLine(z, attrib );
}
editEnd ();
// selection automatically updated (MovingRange)
}
bool KateDocument::nextNonSpaceCharPos(int &line, int &col)
{
for(; line < (int)m_buffer->count(); line++) {
Kate::TextLine textLine = m_buffer->plainLine(line);
if (!textLine)
break;
col = textLine->nextNonSpaceChar(col);
if(col != -1)
return true; // Next non-space char found
col = 0;
}
// No non-space char found
line = -1;
col = -1;
return false;
}
bool KateDocument::previousNonSpaceCharPos(int &line, int &col)
{
while(true)
{
Kate::TextLine textLine = m_buffer->plainLine(line);
if (!textLine)
break;
col = textLine->previousNonSpaceChar(col);
if(col != -1) return true;
if(line == 0) return false;
--line;
col = textLine->length();
}
// No non-space char found
line = -1;
col = -1;
return false;
}
/*
Remove from the selection a start comment mark at
the beginning and a stop comment mark at the end.
*/
bool KateDocument::removeStartStopCommentFromSelection( KateView *view, int attrib )
{
const QString startComment = highlight()->getCommentStart( attrib );
const QString endComment = highlight()->getCommentEnd( attrib );
int sl = qMax<int> (0, view->selectionRange().start().line());
int el = qMin<int> (view->selectionRange().end().line(), lastLine());
int sc = view->selectionRange().start().column();
int ec = view->selectionRange().end().column();
// The selection ends on the char before selectEnd
if (ec != 0) {
--ec;
} else if (el > 0) {
--el;
ec = m_buffer->plainLine(el)->length() - 1;
}
const int startCommentLen = startComment.length();
const int endCommentLen = endComment.length();
// had this been perl or sed: s/^\s*$startComment(.+?)$endComment\s*/$2/
bool remove = nextNonSpaceCharPos(sl, sc)
&& m_buffer->plainLine(sl)->matchesAt(sc, startComment)
&& previousNonSpaceCharPos(el, ec)
&& ( (ec - endCommentLen + 1) >= 0 )
&& m_buffer->plainLine(el)->matchesAt(ec - endCommentLen + 1, endComment);
if (remove) {
editStart();
removeText (KTextEditor::Range(el, ec - endCommentLen + 1, el, ec + 1));
removeText (KTextEditor::Range(sl, sc, sl, sc + startCommentLen));
editEnd ();
// selection automatically updated (MovingRange)
}
return remove;
}
bool KateDocument::removeStartStopCommentFromRegion(const KTextEditor::Cursor &start,const KTextEditor::Cursor &end,int attrib)
{
const QString startComment = highlight()->getCommentStart( attrib );
const QString endComment = highlight()->getCommentEnd( attrib );
const int startCommentLen = startComment.length();
const int endCommentLen = endComment.length();
const bool remove = m_buffer->plainLine(start.line())->matchesAt(start.column(), startComment)
&& m_buffer->plainLine(end.line())->matchesAt(end.column() - endCommentLen , endComment);
if (remove) {
editStart();
removeText(KTextEditor::Range(end.line(), end.column() - endCommentLen, end.line(), end.column()));
removeText(KTextEditor::Range(start, startCommentLen));
editEnd();
}
return remove;
}
/*
Remove from the beginning of each line of the
selection a start comment line mark.
*/
bool KateDocument::removeStartLineCommentFromSelection( KateView *view, int attrib )
{
const QString shortCommentMark = highlight()->getCommentSingleLineStart( attrib );
const QString longCommentMark = shortCommentMark + ' ';
int sl = view->selectionRange().start().line();
int el = view->selectionRange().end().line();
if ((view->selectionRange().end().column() == 0) && (el > 0))
{
el--;
}
bool removed = false;
editStart();
// For each line of the selection
for (int z = el; z >= sl; z--)
{
// Try to remove the long comment mark first
removed = (removeStringFromBeginning(z, longCommentMark)
|| removeStringFromBeginning(z, shortCommentMark)
|| removed);
}
editEnd();
// selection automatically updated (MovingRange)
return removed;
}
/*
Comment or uncomment the selection or the current
line if there is no selection.
*/
void KateDocument::comment( KateView *v, uint line,uint column, int change)
{
// skip word wrap bug #105373
const bool skipWordWrap = wordWrap();
if (skipWordWrap) setWordWrap(false);
bool hassel = v->selection();
int c = 0;
if ( hassel )
c = v->selectionRange().start().column();
int startAttrib = 0;
Kate::TextLine ln = kateTextLine( line );
if ( c < ln->length() )
startAttrib = ln->attribute( c );
else if ( !ln->contextStack().isEmpty() )
startAttrib = highlight()->attribute( ln->contextStack().last() );
bool hasStartLineCommentMark = !(highlight()->getCommentSingleLineStart( startAttrib ).isEmpty());
bool hasStartStopCommentMark = ( !(highlight()->getCommentStart( startAttrib ).isEmpty())
&& !(highlight()->getCommentEnd( startAttrib ).isEmpty()) );
bool removed = false;
if (change > 0) // comment
{
if ( !hassel )
{
if ( hasStartLineCommentMark )
addStartLineCommentToSingleLine( line, startAttrib );
else if ( hasStartStopCommentMark )
addStartStopCommentToSingleLine( line, startAttrib );
}
else
{
// anders: prefer single line comment to avoid nesting probs
// If the selection starts after first char in the first line
// or ends before the last char of the last line, we may use
// multiline comment markers.
// TODO We should try to detect nesting.
// - if selection ends at col 0, most likely she wanted that
// line ignored
const KTextEditor::Range sel = v->selectionRange();
if ( hasStartStopCommentMark &&
( !hasStartLineCommentMark || (
( sel.start().column() > m_buffer->plainLine( sel.start().line() )->firstChar() ) ||
( sel.end().column() > 0 &&
sel.end().column() < (m_buffer->plainLine( sel.end().line() )->length()) )
) ) )
addStartStopCommentToSelection( v, startAttrib );
else if ( hasStartLineCommentMark )
addStartLineCommentToSelection( v, startAttrib );
}
}
else // uncomment
{
if ( !hassel )
{
removed = ( hasStartLineCommentMark
&& removeStartLineCommentFromSingleLine( line, startAttrib ) )
|| ( hasStartStopCommentMark
&& removeStartStopCommentFromSingleLine( line, startAttrib ) );
}
else
{
// anders: this seems like it will work with above changes :)
removed = ( hasStartStopCommentMark
&& removeStartStopCommentFromSelection( v, startAttrib ) )
|| ( hasStartLineCommentMark
&& removeStartLineCommentFromSelection( v, startAttrib ) );
}
// recursive call for toggle comment
if (!removed && change == 0)
comment(v, line, column, 1);
}
if (skipWordWrap) setWordWrap(true); // see begin of function ::comment (bug #105373)
}
void KateDocument::transform( KateView *v, const KTextEditor::Cursor &c,
KateDocument::TextTransform t )
{
if ( v->selection() )
{
editStart();
// remember cursor
KTextEditor::Cursor cursor = c;
// cache the selection and cursor, so we can be sure to restore.
KTextEditor::Range selection = v->selectionRange();
KTextEditor::Range range(selection.start(), 0);
while ( range.start().line() <= selection.end().line() )
{
int start = 0;
int end = lineLength( range.start().line() );
if (range.start().line() == selection.start().line() || v->blockSelection())
start = selection.start().column();
if (range.start().line() == selection.end().line() || v->blockSelection())
end = selection.end().column();
if ( start > end )
{
int swapCol = start;
start = end;
end = swapCol;
}
range.start().setColumn( start );
range.end().setColumn( end );
QString s = text( range );
QString old = s;
if ( t == Uppercase )
s = s.toUpper();
else if ( t == Lowercase )
s = s.toLower();
else // Capitalize
{
Kate::TextLine l = m_buffer->plainLine( range.start().line() );
int p ( 0 );
while( p < s.length() )
{
// If bol or the character before is not in a word, up this one:
// 1. if both start and p is 0, upper char.
// 2. if blockselect or first line, and p == 0 and start-1 is not in a word, upper
// 3. if p-1 is not in a word, upper.
if ( ( ! range.start().column() && ! p ) ||
( ( range.start().line() == selection.start().line() || v->blockSelection() ) &&
! p && ! highlight()->isInWord( l->at( range.start().column() - 1 )) ) ||
( p && ! highlight()->isInWord( s.at( p-1 ) ) )
) {
s[p] = s.at(p).toUpper();
}
p++;
}
}
if ( s != old )
{
removeText( range );
insertText( range.start(), s );
}
range.setBothLines(range.start().line() + 1);
}
editEnd();
// restore selection & cursor
v->setSelection( selection );
v->setCursorPosition( c );
} else { // no selection
editStart();
// get cursor
KTextEditor::Cursor cursor = c;
QString old = text( KTextEditor::Range(cursor, 1) );
QString s;
switch ( t ) {
case Uppercase:
s = old.toUpper();
break;
case Lowercase:
s = old.toLower();
break;
case Capitalize:
{
Kate::TextLine l = m_buffer->plainLine( cursor.line() );
while ( cursor.column() > 0 && highlight()->isInWord( l->at( cursor.column() - 1 ), l->attribute( cursor.column() - 1 ) ) )
cursor.setColumn(cursor.column() - 1);
old = text( KTextEditor::Range(cursor, 1) );
s = old.toUpper();
}
break;
default:
break;
}
removeText( KTextEditor::Range(cursor, 1) );
insertText( cursor, s );
editEnd();
}
}
void KateDocument::joinLines( uint first, uint last )
{
// if ( first == last ) last += 1;
editStart();
int line( first );
while ( first < last )
{
// Normalize the whitespace in the joined lines by making sure there's
// always exactly one space between the joined lines
// This cannot be done in editUnwrapLine, because we do NOT want this
// behavior when deleting from the start of a line, just when explicitly
// calling the join command
Kate::TextLine l = kateTextLine( line );
Kate::TextLine tl = kateTextLine( line + 1 );
if ( !l || !tl )
{
editEnd();
return;
}
int pos = tl->firstChar();
if ( pos >= 0 )
{
if (pos != 0)
editRemoveText( line + 1, 0, pos );
if ( !( l->length() == 0 || l->at( l->length() - 1 ).isSpace() ) )
editInsertText( line + 1, 0, " " );
}
else
{
// Just remove the whitespace and let Kate handle the rest
editRemoveText( line + 1, 0, tl->length() );
}
editUnWrapLine( line );
first++;
}
editEnd();
}
QString KateDocument::getWord( const KTextEditor::Cursor& cursor )
{
int start, end, len;
Kate::TextLine textLine = m_buffer->plainLine(cursor.line());
len = textLine->length();
start = end = cursor.column();
if (start > len) // Probably because of non-wrapping cursor mode.
return QString();
while (start > 0 && highlight()->isInWord(textLine->at(start - 1), textLine->attribute(start - 1))) start--;
while (end < len && highlight()->isInWord(textLine->at(end), textLine->attribute(end))) end++;
len = end - start;
return textLine->string().mid(start, len);
}
void KateDocument::tagLines(int start, int end)
{
foreach(KateView *view,m_views)
view->tagLines (start, end, true);
}
void KateDocument::repaintViews(bool paintOnlyDirty)
{
foreach(KateView *view,m_views)
view->repaintText(paintOnlyDirty);
}
/*
Bracket matching uses the following algorithm:
If in overwrite mode, match the bracket currently underneath the cursor.
Otherwise, if the character to the left is a bracket,
match it. Otherwise if the character to the right of the cursor is a
bracket, match it. Otherwise, don't match anything.
*/
void KateDocument::newBracketMark( const KTextEditor::Cursor& cursor, KTextEditor::Range& bm, int maxLines )
{
// search from cursor for brackets
KTextEditor::Range range (cursor, cursor);
// if match found, remember the range
if( findMatchingBracket( range, maxLines ) ) {
bm = range;
return;
}
// else, invalidate, if still valid
if (bm.isValid())
bm = KTextEditor::Range::invalid();
}
bool KateDocument::findMatchingBracket( KTextEditor::Range& range, int maxLines )
{
Kate::TextLine textLine = m_buffer->plainLine( range.start().line() );
if( !textLine )
return false;
QChar right = textLine->at( range.start().column() );
QChar left = textLine->at( range.start().column() - 1 );
QChar bracket;
if ( config()->ovr() ) {
if( isBracket( right ) ) {
bracket = right;
} else {
return false;
}
} else if ( isBracket( left ) ) {
range.start().setColumn(range.start().column() - 1);
bracket = left;
} else if ( isBracket( right ) ) {
bracket = right;
} else {
return false;
}
QChar opposite;
switch( bracket.toAscii() ) {
case '{': opposite = '}'; break;
case '}': opposite = '{'; break;
case '[': opposite = ']'; break;
case ']': opposite = '['; break;
case '(': opposite = ')'; break;
case ')': opposite = '('; break;
default: return false;
}
const int searchDir = isStartBracket( bracket ) ? 1 : -1;
uint nesting = 0;
int minLine = qMax( range.start().line() - maxLines, 0 );
int maxLine = qMin( range.start().line() + maxLines, documentEnd().line() );
range.end() = range.start();
KTextEditor::DocumentCursor cursor(this);
cursor.setPosition(range.start());
int validAttr = kateTextLine(cursor.line())->attribute(cursor.column());
while( cursor.line() >= minLine && cursor.line() <= maxLine ) {
if (!cursor.move(searchDir))
return false;
Kate::TextLine textLine = kateTextLine(cursor.line());
if (textLine->attribute(cursor.column()) == validAttr )
{
// Check for match
QChar c = textLine->at(cursor.column());
if( c == opposite ) {
if( nesting == 0 ) {
if (searchDir > 0) // forward
range.end() = cursor.toCursor();
else
range.start() = cursor.toCursor();
return true;
}
nesting--;
} else if( c == bracket ) {
nesting++;
}
}
}
return false;
}
// helper: remove \r and \n from visible document name (bug #170876)
inline static QString removeNewLines(const QString& str)
{
QString tmp(str);
return tmp.replace(QLatin1String("\r\n"), QLatin1String(" "))
.replace(QChar('\r'), QLatin1Char(' '))
.replace(QChar('\n'), QLatin1Char(' '));
}
void KateDocument::updateDocName ()
{
// if the name is set, and starts with FILENAME, it should not be changed!
if ( ! url().isEmpty()
&& (m_docName == removeNewLines(url().fileName()) ||
m_docName.startsWith (removeNewLines(url().fileName()) + " (") ) ) {
return;
}
int count = -1;
foreach(KateDocument* doc, KateGlobal::self()->kateDocuments())
{
if ( (doc != this) && (doc->url().fileName() == url().fileName()) )
if ( doc->m_docNameNumber > count )
count = doc->m_docNameNumber;
}
m_docNameNumber = count + 1;
QString oldName = m_docName;
m_docName = removeNewLines(url().fileName());
m_isUntitled = m_docName.isEmpty();
if (m_isUntitled) {
m_docName = i18n ("Untitled");
}
if (m_docNameNumber > 0)
m_docName = QString(m_docName + " (%1)").arg(m_docNameNumber+1);
/**
* avoid to emit this, if name doesn't change!
*/
if (oldName != m_docName)
emit documentNameChanged (this);
}
void KateDocument::slotModifiedOnDisk( KTextEditor::View * /*v*/ )
{
if ( m_isasking < 0 )
{
m_isasking = 0;
return;
}
if ( !m_fileChangedDialogsActivated || m_isasking )
return;
if (m_modOnHd && !url().isEmpty())
{
m_isasking = 1;
QWidget *parentWidget(dialogParent());
KateModOnHdPrompt p( this, m_modOnHdReason, reasonedMOHString(), parentWidget );
switch ( p.exec() )
{
case KateModOnHdPrompt::Save:
{
m_modOnHd = false;
KEncodingFileDialog::Result res=KEncodingFileDialog::getSaveUrlAndEncoding(config()->encoding(),
url().url(),QString(),parentWidget,i18n("Save File"));
kDebug(13020)<<"got "<<res.URLs.count()<<" URLs";
if( ! res.URLs.isEmpty() && ! res.URLs.first().isEmpty() && checkOverwrite( res.URLs.first(), parentWidget ) )
{
setEncoding( res.encoding );
if( ! saveAs( res.URLs.first() ) )
{
KMessageBox::error( parentWidget, i18n("Save failed") );
m_modOnHd = true;
}
else
emit modifiedOnDisk( this, false, OnDiskUnmodified );
}
else // the save as dialog was canceled, we are still modified on disk
{
m_modOnHd = true;
}
m_isasking = 0;
break;
}
case KateModOnHdPrompt::Reload:
m_modOnHd = false;
emit modifiedOnDisk( this, false, OnDiskUnmodified );
documentReload();
m_isasking = 0;
break;
case KateModOnHdPrompt::Ignore:
m_modOnHd = false;
emit modifiedOnDisk( this, false, OnDiskUnmodified );
m_isasking = 0;
break;
case KateModOnHdPrompt::Overwrite:
m_modOnHd = false;
emit modifiedOnDisk( this, false, OnDiskUnmodified );
m_isasking = 0;
save();
break;
default: // Delay/cancel: ignore next focus event
m_isasking = -1;
}
}
}
void KateDocument::setModifiedOnDisk( ModifiedOnDiskReason reason )
{
m_modOnHdReason = reason;
m_modOnHd = (reason != OnDiskUnmodified);
emit modifiedOnDisk( this, (reason != OnDiskUnmodified), reason );
}
class KateDocumentTmpMark
{
public:
QString line;
KTextEditor::Mark mark;
};
void KateDocument::setModifiedOnDiskWarning (bool on)
{
m_fileChangedDialogsActivated = on;
}
bool KateDocument::documentReload()
{
if ( !url().isEmpty() )
{
if (m_modOnHd && m_fileChangedDialogsActivated)
{
QWidget *parentWidget(dialogParent());
int i = KMessageBox::warningYesNoCancel
(parentWidget, reasonedMOHString() + "\n\n" + i18n("What do you want to do?"),
i18n("File Was Changed on Disk"),
KGuiItem(i18n("&Reload File"), "view-refresh"),
KGuiItem(i18n("&Ignore Changes"), "dialog-warning"));
if ( i != KMessageBox::Yes)
{
if (i == KMessageBox::No)
{
m_modOnHd = false;
m_modOnHdReason = OnDiskUnmodified;
emit modifiedOnDisk (this, m_modOnHd, m_modOnHdReason);
}
// reset some flags only valid for one reload!
m_userSetEncodingForNextReload = false;
return false;
}
}
emit aboutToReload(this);
QList<KateDocumentTmpMark> tmp;
for (QHash<int, KTextEditor::Mark*>::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i)
{
KateDocumentTmpMark m;
m.line = line (i.value()->line);
m.mark = *i.value();
tmp.append (m);
}
const QString oldMode = mode ();
const bool byUser = m_fileTypeSetByUser;
const QString hl_mode = highlightingMode ();
KTextEditor::View* oldActiveView = activeView();
m_storedVariables.clear();
// save cursor positions for all views
QVector<KTextEditor::Cursor> cursorPositions;
cursorPositions.reserve(m_views.size());
foreach (KateView *v, m_views)
cursorPositions.append( v->cursorPosition() );
m_reloading = true;
KateDocument::openUrl( url() );
// reset some flags only valid for one reload!
m_userSetEncodingForNextReload = false;
// restore cursor positions for all views
QList<KateView*>::iterator it = m_views.begin();
for(int i = 0; i < m_views.size(); ++i, ++it) {
setActiveView(*it);
(*it)->setCursorPositionInternal( cursorPositions.at(i), m_config->tabWidth(), false );
if ((*it)->isVisible()) {
(*it)->repaintText(false);
}
}
setActiveView(oldActiveView);
for (int z=0; z < tmp.size(); z++)
{
if (z < (int)lines())
{
if (line(tmp.at(z).mark.line) == tmp.at(z).line)
setMark (tmp.at(z).mark.line, tmp.at(z).mark.type);
}
}
if (byUser)
setMode (oldMode);
setHighlightingMode (hl_mode);
emit reloaded(this);
return true;
}
return false;
}
bool KateDocument::documentSave()
{
if( !url().isValid() || !isReadWrite() )
return documentSaveAs();
return save();
}
bool KateDocument::documentSaveAs()
{
QWidget *parentWidget(dialogParent());
KEncodingFileDialog::Result res=KEncodingFileDialog::getSaveUrlAndEncoding(config()->encoding(),
url().url(),QString(),parentWidget,i18n("Save File"));
if( res.URLs.isEmpty() || !checkOverwrite( res.URLs.first(), parentWidget ) )
return false;
setEncoding( res.encoding );
return saveAs( res.URLs.first() );
}
void KateDocument::setWordWrap (bool on)
{
config()->setWordWrap (on);
}
bool KateDocument::wordWrap () const
{
return config()->wordWrap ();
}
void KateDocument::setWordWrapAt (uint col)
{
config()->setWordWrapAt (col);
}
unsigned int KateDocument::wordWrapAt () const
{
return config()->wordWrapAt ();
}
void KateDocument::setPageUpDownMovesCursor (bool on)
{
config()->setPageUpDownMovesCursor (on);
}
bool KateDocument::pageUpDownMovesCursor () const
{
return config()->pageUpDownMovesCursor ();
}
//END
bool KateDocument::setEncoding (const QString &e)
{
return m_config->setEncoding(e);
}
const QString &KateDocument::encoding() const
{
return m_config->encoding();
}
void KateDocument::updateConfig ()
{
m_undoManager->updateConfig ();
// switch indenter if needed and update config....
m_indenter->setMode (m_config->indentationMode());
m_indenter->updateConfig();
// set tab width there, too
m_buffer->setTabWidth (config()->tabWidth());
// update all views, does tagAll and updateView...
foreach (KateView * view, m_views)
view->updateDocumentConfig ();
// update on-the-fly spell checking as spell checking defaults might have changes
if(m_onTheFlyChecker) {
m_onTheFlyChecker->updateConfig();
}
emit configChanged();
}
//BEGIN Variable reader
// "local variable" feature by anders, 2003
/* TODO
add config options (how many lines to read, on/off)
add interface for plugins/apps to set/get variables
add view stuff
*/
QRegExp KateDocument::kvLine = QRegExp("kate:(.*)");
QRegExp KateDocument::kvLineWildcard = QRegExp("kate-wildcard\\((.*)\\):(.*)");
QRegExp KateDocument::kvLineMime = QRegExp("kate-mimetype\\((.*)\\):(.*)");
QRegExp KateDocument::kvVar = QRegExp("([\\w\\-]+)\\s+([^;]+)");
void KateDocument::readVariables(bool onlyViewAndRenderer)
{
if (!onlyViewAndRenderer)
m_config->configStart();
// views!
foreach (KateView *v,m_views)
{
v->config()->configStart();
v->renderer()->config()->configStart();
}
// read a number of lines in the top/bottom of the document
for (int i=0; i < qMin( 9, lines() ); ++i )
{
readVariableLine( line( i ), onlyViewAndRenderer );
}
if ( lines() > 10 )
{
for ( int i = qMax( 10, lines() - 10); i < lines(); i++ )
{
readVariableLine( line( i ), onlyViewAndRenderer );
}
}
if (!onlyViewAndRenderer)
m_config->configEnd();
foreach (KateView *v,m_views)
{
v->config()->configEnd();
v->renderer()->config()->configEnd();
}
}
void KateDocument::readVariableLine( QString t, bool onlyViewAndRenderer )
{
// simple check first, no regex
// no kate inside, no vars, simple...
if (!t.contains("kate"))
return;
// found vars, if any
QString s;
// now, try first the normal ones
if ( kvLine.indexIn( t ) > -1 )
{
s = kvLine.cap(1);
kDebug (13020) << "normal variable line kate: matched: " << s;
}
else if (kvLineWildcard.indexIn( t ) > -1) // regex given
{
const QStringList wildcards (kvLineWildcard.cap(1).split (';', QString::SkipEmptyParts));
const QString nameOfFile = url().fileName();
bool found = false;
foreach(const QString& pattern, wildcards)
{
QRegExp wildcard (pattern, Qt::CaseSensitive, QRegExp::Wildcard);
found = wildcard.exactMatch (nameOfFile);
}
// nothing usable found...
if (!found)
return;
s = kvLineWildcard.cap(2);
kDebug (13020) << "guarded variable line kate-wildcard: matched: " << s;
}
else if (kvLineMime.indexIn( t ) > -1) // mime-type given
{
const QStringList types (kvLineMime.cap(1).split (';', QString::SkipEmptyParts));
// no matching type found
if (!types.contains (mimeType ()))
return;
s = kvLineMime.cap(2);
kDebug (13020) << "guarded variable line kate-mimetype: matched: " << s;
}
else // nothing found
{
return;
}
QStringList vvl; // view variable names
vvl << "dynamic-word-wrap" << "dynamic-word-wrap-indicators"
<< "line-numbers" << "icon-border" << "folding-markers"
<< "bookmark-sorting" << "auto-center-lines"
<< "icon-bar-color"
// renderer
<< "background-color" << "selection-color"
<< "current-line-color" << "bracket-highlight-color"
<< "word-wrap-marker-color"
<< "font" << "font-size" << "scheme";
int spaceIndent = -1; // for backward compatibility; see below
bool replaceTabsSet = false;
int p( 0 );
QString var, val;
while ( (p = kvVar.indexIn( s, p )) > -1 )
{
p += kvVar.matchedLength();
var = kvVar.cap( 1 );
val = kvVar.cap( 2 ).trimmed();
bool state; // store booleans here
int n; // store ints here
// only apply view & renderer config stuff
if (onlyViewAndRenderer)
{
if ( vvl.contains( var ) ) // FIXME define above
setViewVariable( var, val );
}
else
{
// BOOL SETTINGS
if ( var == "word-wrap" && checkBoolValue( val, &state ) )
setWordWrap( state ); // ??? FIXME CHECK
// KateConfig::configFlags
// FIXME should this be optimized to only a few calls? how?
else if ( var == "backspace-indents" && checkBoolValue( val, &state ) )
m_config->setBackspaceIndents( state );
else if ( var == "indent-pasted-text" && checkBoolValue( val, &state ) )
m_config->setIndentPastedText( state );
else if ( var == "replace-tabs" && checkBoolValue( val, &state ) )
{
m_config->setReplaceTabsDyn( state );
replaceTabsSet = true; // for backward compatibility; see below
}
else if ( var == "remove-trailing-space" && checkBoolValue( val, &state ) ) {
kWarning() << i18n("Using deprecated modeline 'remove-trailing-space'. "
"Please replace with 'remove-trailing-spaces modified;', see "
"http://docs.kde.org/stable/en/applications/kate/config-variables.html#variable-remove-trailing-spaces");
m_config->setRemoveSpaces( state ? 1 : 0 );
}
else if ( var == "replace-trailing-space-save" && checkBoolValue( val, &state ) ) {
kWarning() << i18n("Using deprecated modeline 'replace-trailing-space-save'. "
"Please replace with 'remove-trailing-spaces all;', see "
"http://docs.kde.org/stable/en/applications/kate/config-variables.html#variable-remove-trailing-spaces");
m_config->setRemoveSpaces( state ? 2 : 0 );
}
else if ( var == "overwrite-mode" && checkBoolValue( val, &state ) )
m_config->setOvr( state );
else if ( var == "keep-extra-spaces" && checkBoolValue( val, &state ) )
m_config->setKeepExtraSpaces( state );
else if ( var == "tab-indents" && checkBoolValue( val, &state ) )
m_config->setTabIndents( state );
else if ( var == "show-tabs" && checkBoolValue( val, &state ) )
m_config->setShowTabs( state );
else if ( var == "show-trailing-spaces" && checkBoolValue( val, &state ) )
m_config->setShowSpaces( state );
else if ( var == "space-indent" && checkBoolValue( val, &state ) )
{
// this is for backward compatibility; see below
spaceIndent = state;
}
else if ( var == "smart-home" && checkBoolValue( val, &state ) )
m_config->setSmartHome( state );
else if ( var == "newline-at-eof" && checkBoolValue( val, &state ) )
m_config->setNewLineAtEof( state );
// INTEGER SETTINGS
else if ( var == "tab-width" && checkIntValue( val, &n ) )
m_config->setTabWidth( n );
else if ( var == "indent-width" && checkIntValue( val, &n ) )
m_config->setIndentationWidth( n );
else if ( var == "indent-mode" )
{
m_config->setIndentationMode( val );
}
else if ( var == "word-wrap-column" && checkIntValue( val, &n ) && n > 0 ) // uint, but hard word wrap at 0 will be no fun ;)
m_config->setWordWrapAt( n );
// STRING SETTINGS
else if ( var == "eol" || var == "end-of-line" )
{
QStringList l;
l << "unix" << "dos" << "mac";
if ( (n = l.indexOf( val.toLower() )) != -1 )
m_config->setEol( n );
}
else if (var == "bom" || var =="byte-order-marker")
{
if (checkBoolValue(val,&state)) {
m_config->setBom(state);
}
}
else if ( var == "remove-trailing-spaces" ) {
val = val.toLower();
if (val == "1" || val == "modified" || val == "mod" || val == "+") {
m_config->setRemoveSpaces(1);
} else if (val == "2" || val == "all" || val == "*") {
m_config->setRemoveSpaces(2);
} else {
m_config->setRemoveSpaces(0);
}
}
else if ( var == "syntax" || var == "hl" )
{
setHighlightingMode( val );
}
else if ( var == "mode" )
{
setMode( val );
}
else if ( var == "encoding" )
{
setEncoding( val );
}
else if ( var == "default-dictionary" )
{
setDefaultDictionary( val );
}
else if ( var == "automatic-spell-checking" && checkBoolValue( val, &state ) )
{
onTheFlySpellCheckingEnabled( state );
}
// VIEW SETTINGS
else if ( vvl.contains( var ) )
setViewVariable( var, val );
else
{
m_storedVariables.insert( var, val );
emit variableChanged( this, var, val );
}
}
}
// Backward compatibility
// If space-indent was set, but replace-tabs was not set, we assume
// that the user wants to replace tabulators and set that flag.
// If both were set, replace-tabs has precedence.
// At this point spaceIndent is -1 if it was never set,
// 0 if it was set to off, and 1 if it was set to on.
// Note that if onlyViewAndRenderer was requested, spaceIndent is -1.
if ( !replaceTabsSet && spaceIndent >= 0 )
{
m_config->setReplaceTabsDyn( spaceIndent > 0 );
}
}
void KateDocument::setViewVariable( QString var, QString val )
{
bool state;
int n;
QColor c;
foreach (KateView *v,m_views)
{
if ( var == "dynamic-word-wrap" && checkBoolValue( val, &state ) )
v->config()->setDynWordWrap( state );
else if ( var == "persistent-selection" && checkBoolValue( val, &state ) )
v->config()->setPersistentSelection( state );
else if ( var == "block-selection" && checkBoolValue( val, &state ) )
v->setBlockSelection( state );
//else if ( var = "dynamic-word-wrap-indicators" )
else if ( var == "line-numbers" && checkBoolValue( val, &state ) )
v->config()->setLineNumbers( state );
else if (var == "icon-border" && checkBoolValue( val, &state ) )
v->config()->setIconBar( state );
else if (var == "folding-markers" && checkBoolValue( val, &state ) )
v->config()->setFoldingBar( state );
else if ( var == "auto-center-lines" && checkIntValue( val, &n ) )
v->config()->setAutoCenterLines( n );
else if ( var == "icon-bar-color" && checkColorValue( val, c ) )
v->renderer()->config()->setIconBarColor( c );
// RENDERER
else if ( var == "background-color" && checkColorValue( val, c ) )
v->renderer()->config()->setBackgroundColor( c );
else if ( var == "selection-color" && checkColorValue( val, c ) )
v->renderer()->config()->setSelectionColor( c );
else if ( var == "current-line-color" && checkColorValue( val, c ) )
v->renderer()->config()->setHighlightedLineColor( c );
else if ( var == "bracket-highlight-color" && checkColorValue( val, c ) )
v->renderer()->config()->setHighlightedBracketColor( c );
else if ( var == "word-wrap-marker-color" && checkColorValue( val, c ) )
v->renderer()->config()->setWordWrapMarkerColor( c );
else if ( var == "font" || ( var == "font-size" && checkIntValue( val, &n ) ) )
{
QFont _f( v->renderer()->config()->font() );
if ( var == "font" )
{
_f.setFamily( val );
_f.setFixedPitch( QFont( val ).fixedPitch() );
}
else
_f.setPointSize( n );
v->renderer()->config()->setFont( _f );
}
else if ( var == "scheme" )
{
v->renderer()->config()->setSchema( val );
}
}
}
bool KateDocument::checkBoolValue( QString val, bool *result )
{
val = val.trimmed().toLower();
QStringList l;
l << "1" << "on" << "true";
if ( l.contains( val ) )
{
*result = true;
return true;
}
l.clear();
l << "0" << "off" << "false";
if ( l.contains( val ) )
{
*result = false;
return true;
}
return false;
}
bool KateDocument::checkIntValue( QString val, int *result )
{
bool ret( false );
*result = val.toInt( &ret );
return ret;
}
bool KateDocument::checkColorValue( QString val, QColor &c )
{
c.setNamedColor( val );
return c.isValid();
}
// KTextEditor::variable
QString KateDocument::variable( const QString &name ) const
{
return m_storedVariables.value(name, QString());
}
QString KateDocument::setVariable( const QString &name, const QString &value)
{
QString s = "kate: ";
s.append(name);
s.append(" ");
s.append(value);
readVariableLine(s);
return m_storedVariables.value(name, QString());
}
//END
void KateDocument::slotModOnHdDirty (const QString &path)
{
if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskModified))
{
// compare digest with the one we have (if we have one)
if ( !digest().isEmpty() )
{
QByteArray oldDigest = digest();
if ( createDigest () && oldDigest == digest() ) {
return;
}
}
m_modOnHd = true;
m_modOnHdReason = OnDiskModified;
// reenable dialog if not running atm
if (m_isasking == -1)
m_isasking = false;
emit modifiedOnDisk (this, m_modOnHd, m_modOnHdReason);
}
}
void KateDocument::slotModOnHdCreated (const QString &path)
{
if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskCreated))
{
m_modOnHd = true;
m_modOnHdReason = OnDiskCreated;
// reenable dialog if not running atm
if (m_isasking == -1)
m_isasking = false;
emit modifiedOnDisk (this, m_modOnHd, m_modOnHdReason);
}
}
void KateDocument::slotModOnHdDeleted (const QString &path)
{
if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskDeleted))
{
m_modOnHd = true;
m_modOnHdReason = OnDiskDeleted;
// reenable dialog if not running atm
if (m_isasking == -1)
m_isasking = false;
emit modifiedOnDisk (this, m_modOnHd, m_modOnHdReason);
}
}
const QByteArray &KateDocument::digest () const
{
return m_buffer->digest();
}
bool KateDocument::createDigest ()
{
QByteArray sha1sum;
if ( url().isLocalFile() )
{
QFile f ( url().toLocalFile() );
if ( f.open( QIODevice::ReadOnly) )
{
QCryptographicHash crypto(KATE_HASH_ALGORITHM);
crypto.addData(&f);
sha1sum = crypto.result();
}
}
m_buffer->setDigest( sha1sum );
return !sha1sum.isEmpty();
}
QString KateDocument::reasonedMOHString() const
{
// squeeze path
QString str = KStringHandler::csqueeze(url().pathOrUrl());
switch( m_modOnHdReason )
{
case OnDiskModified:
return i18n("The file '%1' was modified by another program.", str );
break;
case OnDiskCreated:
return i18n("The file '%1' was created by another program.", str );
break;
case OnDiskDeleted:
return i18n("The file '%1' was deleted by another program.", str );
break;
default:
return QString();
}
return QString();
}
void KateDocument::removeTrailingSpaces()
{
const int remove = config()->removeSpaces();
if (remove == 0)
return;
// temporarily disable static word wrap (see bug #328900)
const bool wordWrapEnabled = config()->wordWrap ();
if (wordWrapEnabled) setWordWrap(false);
// get cursor position of active view
KTextEditor::Cursor curPos = KTextEditor::Cursor::invalid();
if (activeView()) {
curPos = activeView()->cursorPosition();
}
editStart();
for (int line = 0; line < lines(); ++line)
{
Kate::TextLine textline = plainKateTextLine(line);
// remove trailing spaces in entire document, remove = 2
// remove trailing spaces of touched lines, remove = 1
// remove trailing spaces of lines saved on disk, remove = 1
if (remove == 2 || textline->markedAsModified() || textline->markedAsSavedOnDisk()) {
const int p = textline->lastChar() + 1;
const int l = textline->length() - p;
if (l > 0 ) {
// if the cursor is in the trailing space, only delete behind cursor
if (curPos.line() != line || curPos.column() <= p || curPos.column() > p + l) {
editRemoveText(line, p, l);
} else {
editRemoveText(line, curPos.column(), l - (curPos.column() - p));
}
}
}
}
editEnd();
// enable word wrap again, if it was enabled (see bug #328900)
if (wordWrapEnabled) setWordWrap(true); // see begin of this function
}
void KateDocument::updateFileType (const QString &newType, bool user)
{
if (user || !m_fileTypeSetByUser)
{
if (!newType.isEmpty())
{
// remember that we got set by user
m_fileTypeSetByUser = user;
m_fileType = newType;
m_config->configStart();
if (!m_hlSetByUser && !KateGlobal::self()->modeManager()->fileType(newType).hl.isEmpty())
{
int hl (KateHlManager::self()->nameFind (KateGlobal::self()->modeManager()->fileType(newType).hl));
if (hl >= 0)
m_buffer->setHighlight(hl);
}
/**
* set the indentation mode, if any in the mode...
* and user did not set it before!
*/
if (!m_indenterSetByUser && !KateGlobal::self()->modeManager()->fileType(newType).indenter.isEmpty())
config()->setIndentationMode (KateGlobal::self()->modeManager()->fileType(newType).indenter);
// views!
foreach (KateView *v,m_views)
{
v->config()->configStart();
v->renderer()->config()->configStart();
}
bool bom_settings = false;
if (m_bomSetByUser)
bom_settings=m_config->bom();
readVariableLine( KateGlobal::self()->modeManager()->fileType(newType).varLine );
if (m_bomSetByUser)
m_config->setBom(bom_settings);
m_config->configEnd();
foreach (KateView *v,m_views)
{
v->config()->configEnd();
v->renderer()->config()->configEnd();
}
}
}
// fixme, make this better...
emit modeChanged (this);
}
void KateDocument::slotQueryClose_save(bool *handled, bool* abortClosing) {
*handled=true;
*abortClosing=true;
if (this->url().isEmpty())
{
QWidget *parentWidget(dialogParent());
KEncodingFileDialog::Result res=KEncodingFileDialog::getSaveUrlAndEncoding(config()->encoding(),
QString(),QString(),parentWidget,i18n("Save File"));
if( res.URLs.isEmpty() || !checkOverwrite( res.URLs.first(), parentWidget ) ) {
*abortClosing=true;
return;
}
setEncoding( res.encoding );
saveAs( res.URLs.first() );
*abortClosing=false;
}
else
{
save();
*abortClosing=false;
}
}
bool KateDocument::checkOverwrite( KUrl u, QWidget *parent )
{
if( !u.isLocalFile() )
return true;
QFileInfo info( u.path() );
if( !info.exists() )
return true;
return KMessageBox::Cancel != KMessageBox::warningContinueCancel( parent,
i18n( "A file named \"%1\" already exists. "
"Are you sure you want to overwrite it?" , info.fileName() ),
i18n( "Overwrite File?" ), KStandardGuiItem::overwrite(),
KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous );
}
//BEGIN KTextEditor::ConfigInterface
// BEGIN ConfigInterface stff
QStringList KateDocument::configKeys() const
{
return QStringList() << "tab-width" << "indent-width";
}
QVariant KateDocument::configValue(const QString &key)
{
if (key == "backup-on-save-local") {
return m_config->backupFlags() & KateDocumentConfig::LocalFiles;
} else if (key == "backup-on-save-remote") {
return m_config->backupFlags() & KateDocumentConfig::RemoteFiles;
} else if (key == "backup-on-save-suffix") {
return m_config->backupSuffix();
} else if (key == "backup-on-save-prefix") {
return m_config->backupPrefix();
} else if (key == "replace-tabs") {
return m_config->replaceTabsDyn();
} else if (key == "indent-pasted-text") {
return m_config->indentPastedText();
} else if (key == "tab-width") {
return m_config->tabWidth();
} else if (key == "indent-width") {
return m_config->indentationWidth();
}
// return invalid variant
return QVariant();
}
void KateDocument::setConfigValue(const QString &key, const QVariant &value)
{
if (value.type() == QVariant::String) {
if (key == "backup-on-save-suffix") {
m_config->setBackupSuffix(value.toString());
} else if (key == "backup-on-save-prefix") {
m_config->setBackupPrefix(value.toString());
}
} else if (value.canConvert(QVariant::Bool)) {
const bool bValue = value.toBool();
if (key == "backup-on-save-local" && value.type() == QVariant::String) {
uint f = m_config->backupFlags();
if (bValue) {
f |= KateDocumentConfig::LocalFiles;
} else {
f ^= KateDocumentConfig::LocalFiles;
}
m_config->setBackupFlags(f);
} else if (key == "backup-on-save-remote") {
uint f = m_config->backupFlags();
if (bValue) {
f |= KateDocumentConfig::RemoteFiles;
} else {
f ^= KateDocumentConfig::RemoteFiles;
}
m_config->setBackupFlags(f);
} else if (key == "replace-tabs") {
m_config->setReplaceTabsDyn(bValue);
} else if (key == "indent-pasted-text") {
m_config->setIndentPastedText(bValue);
}
} else if (value.canConvert(QVariant::Int)) {
if (key == "tab-width") {
config()->setTabWidth(value.toInt());
} else if (key == "indent-width") {
config()->setIndentationWidth(value.toInt());
}
}
}
//END KTextEditor::ConfigInterface
KateView * KateDocument::activeKateView( ) const
{
return static_cast<KateView*>(m_activeView);
}
KTextEditor::Cursor KateDocument::documentEnd( ) const
{
return KTextEditor::Cursor(lastLine(), lineLength(lastLine()));
}
bool KateDocument::replaceText( const KTextEditor::Range & range, const QString & s, bool block )
{
// TODO more efficient?
editStart();
bool changed = removeText(range, block);
changed |= insertText(range.start(), s, block);
editEnd();
return changed;
}
void KateDocument::ignoreModifiedOnDiskOnce( )
{
m_isasking = -1;
}
KateHighlighting * KateDocument::highlight( ) const
{
return m_buffer->highlight();
}
Kate::TextLine KateDocument::kateTextLine( uint i )
{
m_buffer->ensureHighlighted (i);
return m_buffer->plainLine (i);
}
Kate::TextLine KateDocument::plainKateTextLine( uint i )
{
return m_buffer->plainLine (i);
}
bool KateDocument::isEditRunning() const
{
return editIsRunning;
}
void KateDocument::setUndoMergeAllEdits(bool merge)
{
if (merge && m_undoMergeAllEdits)
{
// Don't add another undo safe point: it will override our current one,
// meaning we'll need two undo's to get back there - which defeats the object!
return;
}
m_undoManager->undoSafePoint();
m_undoManager->setAllowComplexMerge(merge);
m_undoMergeAllEdits = merge;
}
//BEGIN KTextEditor::MovingInterface
KTextEditor::MovingCursor *KateDocument::newMovingCursor (const KTextEditor::Cursor &position, KTextEditor::MovingCursor::InsertBehavior insertBehavior)
{
return new Kate::TextCursor(buffer(), position, insertBehavior);
}
KTextEditor::MovingRange *KateDocument::newMovingRange (const KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors, KTextEditor::MovingRange::EmptyBehavior emptyBehavior)
{
return new Kate::TextRange(buffer(), range, insertBehaviors, emptyBehavior);
}
qint64 KateDocument::revision () const
{
return m_buffer->history().revision ();
}
qint64 KateDocument::lastSavedRevision () const
{
return m_buffer->history().lastSavedRevision ();
}
void KateDocument::lockRevision (qint64 revision)
{
m_buffer->history().lockRevision (revision);
}
void KateDocument::unlockRevision (qint64 revision)
{
m_buffer->history().unlockRevision (revision);
}
void KateDocument::transformCursor(int& line, int& column, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision)
{
m_buffer->history().transformCursor (line, column, insertBehavior, fromRevision, toRevision);
}
void KateDocument::transformCursor (KTextEditor::Cursor &cursor, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision)
{
int line = cursor.line(), column = cursor.column();
m_buffer->history().transformCursor (line, column, insertBehavior, fromRevision, toRevision);
cursor.setLine(line);
cursor.setColumn(column);
}
void KateDocument::transformRange (KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors, KTextEditor::MovingRange::EmptyBehavior emptyBehavior, qint64 fromRevision, qint64 toRevision)
{
m_buffer->history().transformRange (range, insertBehaviors, emptyBehavior, fromRevision, toRevision);
}
//END
bool KateDocument::simpleMode ()
{
return KateGlobal::self()->simpleMode () && KateGlobal::self()->documentConfig()->allowSimpleMode ();
}
//BEGIN KTextEditor::AnnotationInterface
void KateDocument::setAnnotationModel( KTextEditor::AnnotationModel* model )
{
KTextEditor::AnnotationModel* oldmodel = m_annotationModel;
m_annotationModel = model;
emit annotationModelChanged(oldmodel, m_annotationModel);
}
KTextEditor::AnnotationModel* KateDocument::annotationModel() const
{
return m_annotationModel;
}
//END KTextEditor::AnnotationInterface
//TAKEN FROM kparts.h
bool KateDocument::queryClose()
{
if ( !isReadWrite() || !isModified() )
return true;
QString docName = documentName();
int res = KMessageBox::warningYesNoCancel( dialogParent(),
i18n( "The document \"%1\" has been modified.\n"
"Do you want to save your changes or discard them?" , docName ),
i18n( "Close Document" ), KStandardGuiItem::save(), KStandardGuiItem::discard() );
bool abortClose=false;
bool handled=false;
switch(res) {
case KMessageBox::Yes :
sigQueryClose(&handled,&abortClose);
if (!handled)
{
if (url().isEmpty())
{
KUrl url = KFileDialog::getSaveUrl(KUrl(), QString(), dialogParent());
if (url.isEmpty())
return false;
saveAs( url );
}
else
{
save();
}
} else if (abortClose) return false;
return waitSaveComplete();
case KMessageBox::No :
return true;
default : // case KMessageBox::Cancel :
return false;
}
}
void KateDocument::slotStarted (KIO::Job *job)
{
/**
* if we are idle before, we are now loading!
*/
if (m_documentState == DocumentIdle)
m_documentState = DocumentLoading;
/**
* if loading:
* - remember pre loading read-write mode
* if remote load:
* - set to read-only
* - trigger possible message
*/
if (m_documentState == DocumentLoading) {
/**
* remember state
*/
m_readWriteStateBeforeLoading = isReadWrite ();
/**
* perhaps show loading message, but wait one second
*/
if (job) {
/**
* only read only if really remote file!
*/
setReadWrite (false);
/**
* perhaps some message about loading in one second!
* remember job pointer, we want to be able to kill it!
*/
m_loadingJob = job;
QTimer::singleShot (1000, this, SLOT(slotTriggerLoadingMessage()));
}
}
}
void KateDocument::slotCompleted() {
/**
* if were loading, reset back to old read-write mode before loading
* and kill the possible loading message
*/
if (m_documentState == DocumentLoading) {
setReadWrite (m_readWriteStateBeforeLoading);
delete m_loadingMessage;
}
/**
* Emit signal that we saved the document, if needed
*/
if (m_documentState == DocumentSaving || m_documentState == DocumentSavingAs)
emit documentSavedOrUploaded (this, m_documentState == DocumentSavingAs);
/**
* back to idle mode
*/
m_documentState = DocumentIdle;
m_reloading = false;
}
void KateDocument::slotCanceled() {
/**
* if were loading, reset back to old read-write mode before loading
* and kill the possible loading message
*/
if (m_documentState == DocumentLoading) {
setReadWrite (m_readWriteStateBeforeLoading);
delete m_loadingMessage;
showAndSetOpeningErrorAccess();
updateDocName();
}
/**
* back to idle mode
*/
m_documentState = DocumentIdle;
m_reloading = false;
}
void KateDocument::slotTriggerLoadingMessage ()
{
/**
* no longer loading?
* no message needed!
*/
if (m_documentState != DocumentLoading)
return;
/**
* create message about file loading in progress
*/
delete m_loadingMessage;
m_loadingMessage = new KTextEditor::Message(i18n ("The file <a href=\"%1\">%2</a> is still loading.", url().pathOrUrl(), url().fileName()));
m_loadingMessage->setPosition(KTextEditor::Message::TopInView);
/**
* if around job: add cancel action
*/
if (m_loadingJob) {
QAction *cancel = new QAction ( i18n ("&Abort Loading"), 0);
connect (cancel, SIGNAL(triggered()), this, SLOT(slotAbortLoading()));
m_loadingMessage->addAction (cancel);
}
/**
* really post message
*/
postMessage(m_loadingMessage);
}
void KateDocument::slotAbortLoading ()
{
/**
* no job, no work
*/
if (!m_loadingJob)
return;
/**
* abort loading if any job
* signal results!
*/
m_loadingJob->kill (KJob::EmitResult);
m_loadingJob = 0;
}
bool KateDocument::save()
{
/**
* no double save/load
* we need to allow DocumentPreSavingAs here as state, as save is called in saveAs!
*/
if ((m_documentState != DocumentIdle) && (m_documentState != DocumentPreSavingAs))
return false;
/**
* if we are idle, we are now saving
*/
if (m_documentState == DocumentIdle)
m_documentState = DocumentSaving;
else
m_documentState = DocumentSavingAs;
/**
* call back implementation for real work
*/
return KTextEditor::Document::save();
}
bool KateDocument::saveAs( const KUrl &url )
{
/**
* abort on bad URL
* that is done in saveAs below, too
* but we must check it here already to avoid messing up
* as no signals will be send, then
*/
if (!url.isValid())
return false;
/**
* no double save/load
*/
if (m_documentState != DocumentIdle)
return false;
/**
* we enter the pre save as phase
*/
m_documentState = DocumentPreSavingAs;
/**
* call base implementation for real work
*/
return KTextEditor::Document::saveAs(url);
}
QString KateDocument::defaultDictionary() const
{
return m_defaultDictionary;
}
QList<QPair<KTextEditor::MovingRange*, QString> > KateDocument::dictionaryRanges() const
{
return m_dictionaryRanges;
}
void KateDocument::clearDictionaryRanges()
{
for (QList<QPair<KTextEditor::MovingRange*, QString> >::iterator i = m_dictionaryRanges.begin();
i != m_dictionaryRanges.end(); ++i)
{
delete (*i).first;
}
m_dictionaryRanges.clear();
if (m_onTheFlyChecker) {
m_onTheFlyChecker->refreshSpellCheck();
}
emit dictionaryRangesPresent(false);
}
void KateDocument::setDictionary(const QString& newDictionary, const KTextEditor::Range &range)
{
KTextEditor::Range newDictionaryRange = range;
if (!newDictionaryRange.isValid() || newDictionaryRange.isEmpty()) {
return;
}
QList<QPair<KTextEditor::MovingRange*, QString> > newRanges;
// all ranges is 'm_dictionaryRanges' are assumed to be mutually disjoint
for(QList<QPair<KTextEditor::MovingRange*, QString> >::iterator i = m_dictionaryRanges.begin();
i != m_dictionaryRanges.end();)
{
kDebug(13000) << "new iteration" << newDictionaryRange;
if (newDictionaryRange.isEmpty()) {
break;
}
QPair<KTextEditor::MovingRange*, QString> pair = *i;
QString dictionarySet = pair.second;
KTextEditor::MovingRange *dictionaryRange = pair.first;
kDebug(13000) << *dictionaryRange << dictionarySet;
if(dictionaryRange->contains(newDictionaryRange) && newDictionary == dictionarySet)
{
kDebug(13000) << "dictionaryRange contains newDictionaryRange";
return;
}
if(newDictionaryRange.contains(dictionaryRange->toRange()))
{
delete dictionaryRange;
i = m_dictionaryRanges.erase(i);
kDebug(13000) << "newDictionaryRange contains dictionaryRange";
continue;
}
KTextEditor::Range intersection = dictionaryRange->toRange().intersect(newDictionaryRange);
if(!intersection.isEmpty() && intersection.isValid())
{
if(dictionarySet == newDictionary) // we don't have to do anything for 'intersection'
{ // except cut off the intersection
QList<KTextEditor::Range> remainingRanges = KateSpellCheckManager::rangeDifference(newDictionaryRange, intersection);
Q_ASSERT(remainingRanges.size() == 1);
newDictionaryRange = remainingRanges.first();
++i;
kDebug(13000) << "dictionarySet == newDictionary";
continue;
}
QList<KTextEditor::Range> remainingRanges = KateSpellCheckManager::rangeDifference(dictionaryRange->toRange(), intersection);
for(QList<KTextEditor::Range>::iterator j = remainingRanges.begin(); j != remainingRanges.end(); ++j)
{
KTextEditor::MovingRange *remainingRange = newMovingRange(*j,
KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight);
remainingRange->setFeedback(this);
newRanges.push_back(QPair<KTextEditor::MovingRange*, QString>(remainingRange, dictionarySet));
}
i = m_dictionaryRanges.erase(i);
delete dictionaryRange;
} else {
++i;
}
}
m_dictionaryRanges += newRanges;
if(!newDictionaryRange.isEmpty() && !newDictionary.isEmpty()) // we don't add anything for the default dictionary
{
KTextEditor::MovingRange *newDictionaryMovingRange = newMovingRange(newDictionaryRange,
KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight);
newDictionaryMovingRange->setFeedback(this);
m_dictionaryRanges.push_back(QPair<KTextEditor::MovingRange*, QString>(newDictionaryMovingRange, newDictionary));
}
if(m_onTheFlyChecker && !newDictionaryRange.isEmpty())
{
m_onTheFlyChecker->refreshSpellCheck(newDictionaryRange);
}
emit dictionaryRangesPresent(!m_dictionaryRanges.isEmpty());
}
void KateDocument::revertToDefaultDictionary(const KTextEditor::Range &range)
{
setDictionary(QString(), range);
}
void KateDocument::setDefaultDictionary(const QString& dict)
{
if (m_defaultDictionary == dict) {
return;
}
m_defaultDictionary = dict;
if (m_onTheFlyChecker) {
m_onTheFlyChecker->updateConfig();
refreshOnTheFlyCheck();
}
emit defaultDictionaryChanged(this);
}
void KateDocument::onTheFlySpellCheckingEnabled(bool enable)
{
if (isOnTheFlySpellCheckingEnabled() == enable) {
return;
}
if (enable) {
Q_ASSERT(m_onTheFlyChecker == 0);
m_onTheFlyChecker = new KateOnTheFlyChecker(this);
} else {
delete m_onTheFlyChecker;
m_onTheFlyChecker = 0;
}
foreach (KateView *view, m_views) {
view->reflectOnTheFlySpellCheckStatus(enable);
}
}
bool KateDocument::isOnTheFlySpellCheckingEnabled() const
{
return m_onTheFlyChecker != 0;
}
QString KateDocument::dictionaryForMisspelledRange(const KTextEditor::Range& range) const
{
if (!m_onTheFlyChecker) {
return QString();
} else {
return m_onTheFlyChecker->dictionaryForMisspelledRange(range);
}
}
void KateDocument::clearMisspellingForWord(const QString& word)
{
if (m_onTheFlyChecker) {
m_onTheFlyChecker->clearMisspellingForWord(word);
}
}
void KateDocument::refreshOnTheFlyCheck(const KTextEditor::Range &range)
{
if (m_onTheFlyChecker) {
m_onTheFlyChecker->refreshSpellCheck(range);
}
}
void KateDocument::rangeInvalid(KTextEditor::MovingRange *movingRange)
{
deleteDictionaryRange(movingRange);
}
void KateDocument::rangeEmpty(KTextEditor::MovingRange *movingRange)
{
deleteDictionaryRange(movingRange);
}
void KateDocument::deleteDictionaryRange(KTextEditor::MovingRange *movingRange)
{
kDebug(13020) << "deleting" << movingRange;
for(QList<QPair<KTextEditor::MovingRange*, QString> >::iterator i = m_dictionaryRanges.begin();
i != m_dictionaryRanges.end();)
{
KTextEditor::MovingRange *dictionaryRange = (*i).first;
if(dictionaryRange == movingRange)
{
delete movingRange;
i = m_dictionaryRanges.erase(i);
} else {
++i;
}
}
}
bool KateDocument::containsCharacterEncoding(const KTextEditor::Range& range)
{
KateHighlighting *highlighting = highlight();
Kate::TextLine textLine;
const int rangeStartLine = range.start().line();
const int rangeStartColumn = range.start().column();
const int rangeEndLine = range.end().line();
const int rangeEndColumn = range.end().column();
for(int line = range.start().line(); line <= rangeEndLine; ++line) {
textLine = kateTextLine(line);
int startColumn = (line == rangeStartLine) ? rangeStartColumn : 0;
int endColumn = (line == rangeEndLine) ? rangeEndColumn : textLine->length();
for(int col = startColumn; col < endColumn; ++col) {
int attr = textLine->attribute(col);
const KatePrefixStore& prefixStore = highlighting->getCharacterEncodingsPrefixStore(attr);
if(!prefixStore.findPrefix(textLine, col).isEmpty()) {
return true;
}
}
}
return false;
}
int KateDocument::computePositionWrtOffsets(const OffsetList& offsetList, int pos)
{
int previousOffset = 0;
for(OffsetList::const_iterator i = offsetList.begin(); i != offsetList.end(); ++i) {
if((*i).first > pos) {
break;
}
previousOffset = (*i).second;
}
return pos + previousOffset;
}
QString KateDocument::decodeCharacters(const KTextEditor::Range& range, KateDocument::OffsetList& decToEncOffsetList,
KateDocument::OffsetList& encToDecOffsetList)
{
QString toReturn;
KTextEditor::Cursor previous = range.start();
int decToEncCurrentOffset = 0, encToDecCurrentOffset = 0;
int i = 0;
int newI = 0;
KateHighlighting *highlighting = highlight();
Kate::TextLine textLine;
const int rangeStartLine = range.start().line();
const int rangeStartColumn = range.start().column();
const int rangeEndLine = range.end().line();
const int rangeEndColumn = range.end().column();
for(int line = range.start().line(); line <= rangeEndLine; ++line) {
textLine = kateTextLine(line);
int startColumn = (line == rangeStartLine) ? rangeStartColumn : 0;
int endColumn = (line == rangeEndLine) ? rangeEndColumn : textLine->length();
for(int col = startColumn; col < endColumn;) {
int attr = textLine->attribute(col);
const KatePrefixStore& prefixStore = highlighting->getCharacterEncodingsPrefixStore(attr);
const QHash<QString, QChar>& characterEncodingsHash = highlighting->getCharacterEncodings(attr);
QString matchingPrefix = prefixStore.findPrefix(textLine, col);
if(!matchingPrefix.isEmpty()) {
toReturn += text(KTextEditor::Range(previous, KTextEditor::Cursor(line, col)));
const QChar& c = characterEncodingsHash.value(matchingPrefix);
const bool isNullChar = c.isNull();
if(!c.isNull()) {
toReturn += c;
}
i += matchingPrefix.length();
col += matchingPrefix.length();
previous = KTextEditor::Cursor(line, col);
decToEncCurrentOffset = decToEncCurrentOffset - (isNullChar ? 0 : 1) + matchingPrefix.length();
encToDecCurrentOffset = encToDecCurrentOffset - matchingPrefix.length() + (isNullChar ? 0 : 1);
newI += (isNullChar ? 0 : 1);
decToEncOffsetList.push_back(QPair<int, int>(newI, decToEncCurrentOffset));
encToDecOffsetList.push_back(QPair<int, int>(i, encToDecCurrentOffset));
continue;
}
++col;
++i;
++newI;
}
++i;
++newI;
}
if(previous < range.end()) {
toReturn += text(KTextEditor::Range(previous, range.end()));
}
return toReturn;
}
void KateDocument::replaceCharactersByEncoding(const KTextEditor::Range& range)
{
KateHighlighting *highlighting = highlight();
Kate::TextLine textLine;
const int rangeStartLine = range.start().line();
const int rangeStartColumn = range.start().column();
const int rangeEndLine = range.end().line();
const int rangeEndColumn = range.end().column();
for(int line = range.start().line(); line <= rangeEndLine; ++line) {
textLine = kateTextLine(line);
int startColumn = (line == rangeStartLine) ? rangeStartColumn : 0;
int endColumn = (line == rangeEndLine) ? rangeEndColumn : textLine->length();
for(int col = startColumn; col < endColumn;) {
int attr = textLine->attribute(col);
const QHash<QChar, QString>& reverseCharacterEncodingsHash = highlighting->getReverseCharacterEncodings(attr);
QHash<QChar, QString>::const_iterator it = reverseCharacterEncodingsHash.find(textLine->at(col));
if(it != reverseCharacterEncodingsHash.end()) {
replaceText(KTextEditor::Range(line, col, line, col + 1), *it);
col += (*it).length();
continue;
}
++col;
}
}
}
//
// KTextEditor::HighlightInterface
//
KTextEditor::Attribute::Ptr KateDocument::defaultStyle(const KTextEditor::HighlightInterface::DefaultStyle ds) const
{
///TODO: move attributes to document, they are not view-dependant
KateView* view = activeKateView();
if ( !view ) {
kWarning() << "ATTENTION: cannot access defaultStyle() without any View (will be fixed eventually)";
return KTextEditor::Attribute::Ptr(0);
}
KTextEditor::Attribute::Ptr style = highlight()->attributes(view->renderer()->config()->schema()).at(ds);
if (!style->hasProperty(QTextFormat::BackgroundBrush)) {
// make sure the returned style has the default background color set
style.attach(new KTextEditor::Attribute(*style));
style->setBackground( QBrush(view->renderer()->config()->backgroundColor()) );
}
return style;
}
QList< KTextEditor::HighlightInterface::AttributeBlock > KateDocument::lineAttributes(const unsigned int line)
{
///TODO: move attributes to document, they are not view-dependant
QList< KTextEditor::HighlightInterface::AttributeBlock > attribs;
KateView* view = activeKateView();
if ( !view ) {
kWarning() << "ATTENTION: cannot access lineAttributes() without any View (will be fixed eventually)";
return attribs;
}
Kate::TextLine kateLine = kateTextLine(line);
if ( !kateLine )
return attribs;
const QVector<Kate::TextLineData::Attribute> &intAttrs = kateLine->attributesList();
for ( int i = 0; i < intAttrs.size(); ++i ) {
if (intAttrs[i].length > 0 && intAttrs[i].attributeValue > 0)
attribs << KTextEditor::HighlightInterface::AttributeBlock(
intAttrs.at(i).offset,
intAttrs.at(i).length,
view->renderer()->attribute(intAttrs.at(i).attributeValue)
);
}
return attribs;
}
KTextEditor::Attribute::Ptr KateDocument::attributeAt(const KTextEditor::Cursor & position)
{
KTextEditor::Attribute::Ptr attrib(new KTextEditor::Attribute());
KateView* view = activeKateView();
if ( !view ) {
kWarning() << "ATTENTION: cannot access lineAttributes() without any View (will be fixed eventually)";
return attrib;
}
Kate::TextLine kateLine = kateTextLine(position.line());
if ( !kateLine )
return attrib;
*attrib = *view->renderer()->attribute(kateLine->attribute(position.column()));
return attrib;
}
QStringList KateDocument::embeddedHighlightingModes() const
{
return highlight()->getEmbeddedHighlightingModes();
}
QString KateDocument::highlightingModeAt(const KTextEditor::Cursor& position)
{
Kate::TextLine kateLine = kateTextLine(position.line());
// const QVector< short >& attrs = kateLine->ctxArray();
// kDebug() << "----------------------------------------------------------------------";
// foreach( short a, attrs ) {
// kDebug() << a;
// }
// kDebug() << "----------------------------------------------------------------------";
// kDebug() << "col: " << position.column() << " lastchar:" << kateLine->lastChar() << " length:" << kateLine->length() << "global mode:" << highlightingMode();
int len = kateLine->length();
int pos = position.column();
if ( pos >= len ) {
const Kate::TextLineData::ContextStack &ctxs = kateLine->contextStack();
int ctxcnt = ctxs.count();
if ( ctxcnt == 0 ) {
return highlightingMode();
}
int ctx = ctxs.at(ctxcnt-1);
if ( ctx == 0 ) {
return highlightingMode();
}
return KateHlManager::self()->nameForIdentifier(highlight()->hlKeyForContext(ctx));
}
int attr = kateLine->attribute(pos);
if ( attr == 0 ) {
return mode();
}
return KateHlManager::self()->nameForIdentifier(highlight()->hlKeyForAttrib(attr));
}
Kate::SwapFile* KateDocument::swapFile()
{
return m_swapfile;
}
/**
* \return \c -1 if \c line or \c column invalid, otherwise one of
* standard style attribute number
*/
int KateDocument::defStyleNum(int line, int column)
{
// Validate parameters to prevent out of range access
if (line < 0 || line >= lines() || column < 0)
return -1;
// get highlighted line
Kate::TextLine tl = kateTextLine(line);
// make sure the textline is a valid pointer
if (!tl)
return -1;
/**
* either get char attribute or attribute of context still active at end of line
*/
int attribute = 0;
if (column < tl->length())
attribute = tl->attribute (column);
else if (column == tl->length()) {
KateHlContext *context = tl->contextStack().isEmpty() ? highlight()->contextNum(0) : highlight()->contextNum (tl->contextStack().back());
attribute = context->attr;
} else
return -1;
KateView* view = static_cast<KateView*>(activeView());
if (!view) {
if (!m_views.isEmpty()) {
view = m_views.first();
} else {
//FIXME: find a way to use this function without any view, or move it to the KateView.
return -1;
}
}
QList<KTextEditor::Attribute::Ptr> attributes = highlight()->attributes(
view->renderer()->config()->schema()
);
// sanity check for the attribute
if (attribute < 0 || attribute >= attributes.size())
return -1;
KTextEditor::Attribute::Ptr a = attributes[attribute];
return a->property(KateExtendedAttribute::AttributeDefaultStyleIndex).toInt();
}
bool KateDocument::isComment(int line, int column)
{
const int defaultStyle = defStyleNum(line, column);
return defaultStyle == KTextEditor::HighlightInterface::dsComment;
}
int KateDocument::findModifiedLine(int startLine, bool down)
{
const int offset = down ? 1 : -1;
const int lineCount = lines();
while (startLine >= 0 && startLine < lineCount) {
Kate::TextLine tl = m_buffer->plainLine(startLine);
if (tl && (tl->markedAsModified() || tl->markedAsSavedOnDisk())) {
return startLine;
}
startLine += offset;
}
return -1;
}
//BEGIN KTextEditor::MessageInterface
bool KateDocument::postMessage(KTextEditor::Message* message)
{
// no message -> cancel
if (!message)
return false;
// make sure the desired view belongs to this document
if (message->view() && message->view()->document() != this) {
kWarning(13020) << "trying to post a message to a view of another document:" << message->text();
return false;
}
message->setParent(this);
message->setDocument(this);
// if there are no actions, add a close action by default if widget does not auto-hide
if (message->actions().count() == 0 && message->autoHide() < 0) {
QAction* closeAction = new QAction(KIcon("window-close"), i18n("&Close"), 0);
closeAction->setToolTip(i18n("Close message"));
message->addAction(closeAction);
}
// make sure the message is registered even if no actions and no views exist
m_messageHash[message] = QList<QSharedPointer<QAction> >();
// reparent actions, as we want full control over when they are deleted
foreach (QAction *action, message->actions()) {
action->setParent(0);
m_messageHash[message].append(QSharedPointer<QAction>(action));
}
// post message to requested view, or to all views
if (KateView *view = qobject_cast<KateView*>(message->view())) {
view->postMessage(message, m_messageHash[message]);
} else {
foreach (KateView *view, m_views)
view->postMessage(message, m_messageHash[message]);
}
// also catch if the user manually calls delete message
connect(message, SIGNAL(closed(KTextEditor::Message*)), SLOT(messageDestroyed(KTextEditor::Message*)));
return true;
}
void KateDocument::messageDestroyed(KTextEditor::Message* message)
{
// KTE:Message is already in destructor
Q_ASSERT(m_messageHash.contains(message));
m_messageHash.remove(message);
}
//END KTextEditor::MessageInterface
// kate: space-indent on; indent-width 2; replace-tabs on;